Compare commits

...

4 Commits

Author SHA1 Message Date
tomatih
6f0a44b944
Merge 1c636096e5 into 47d1d84bc8 2025-03-24 18:01:06 -04:00
Nic Barker
47d1d84bc8
[Core] Switch text content hashing to default behaviour (#335)
Some checks failed
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Has been cancelled
2025-03-25 10:13:04 +13:00
to_matih
1c636096e5
Fix SDL_Rect argument casting 2025-03-14 18:29:50 +00:00
to_matih
e5bd453f5e
Fix SDL_FRect argument casting 2025-03-14 16:06:33 +00:00
9 changed files with 156 additions and 45 deletions

View File

@ -17,6 +17,7 @@ when ODIN_OS == .Windows {
} }
String :: struct { String :: struct {
isStaticallyAllocated: c.bool,
length: c.int32_t, length: c.int32_t,
chars: [^]c.char, chars: [^]c.char,
} }
@ -419,7 +420,13 @@ UI :: proc() -> proc (config: ElementDeclaration) -> bool {
return ConfigureOpenElement return ConfigureOpenElement
} }
Text :: proc(text: string, config: ^TextElementConfig) { Text :: proc($text: string, config: ^TextElementConfig) {
wrapped := MakeString(text)
wrapped.isStaticallyAllocated = true
_OpenTextElement(wrapped, config)
}
TextDynamic :: proc(text: string, config: ^TextElementConfig) {
_OpenTextElement(MakeString(text), config) _OpenTextElement(MakeString(text), config)
} }

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -62,7 +62,7 @@ border2pxRed := clay.BorderElementConfig {
color = COLOR_RED color = COLOR_RED
} }
LandingPageBlob :: proc(index: u32, fontSize: u16, fontId: u16, color: clay.Color, text: string, image: ^raylib.Texture2D) { LandingPageBlob :: proc(index: u32, fontSize: u16, fontId: u16, color: clay.Color, $text: string, image: ^raylib.Texture2D) {
if clay.UI()({ if clay.UI()({
id = clay.ID("HeroBlob", index), id = clay.ID("HeroBlob", index),
layout = { sizing = { width = clay.SizingGrow({ max = 480 }) }, padding = clay.PaddingAll(16), childGap = 16, childAlignment = clay.ChildAlignment{ y = .Center } }, layout = { sizing = { width = clay.SizingGrow({ max = 480 }) }, padding = clay.PaddingAll(16), childGap = 16, childAlignment = clay.ChildAlignment{ y = .Center } },
@ -252,7 +252,7 @@ ColorLerp :: proc(a: clay.Color, b: clay.Color, amount: f32) -> clay.Color {
return clay.Color{a.r + (b.r - a.r) * amount, a.g + (b.g - a.g) * amount, a.b + (b.b - a.b) * amount, a.a + (b.a - a.a) * amount} return clay.Color{a.r + (b.r - a.r) * amount, a.g + (b.g - a.g) * amount, a.b + (b.b - a.b) * amount, a.a + (b.a - a.a) * amount}
} }
LOREM_IPSUM_TEXT := "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." LOREM_IPSUM_TEXT :: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
HighPerformancePage :: proc(lerpValue: f32, titleTextConfig: clay.TextElementConfig, widthSizing: clay.SizingAxis) { HighPerformancePage :: proc(lerpValue: f32, titleTextConfig: clay.TextElementConfig, widthSizing: clay.SizingAxis) {
if clay.UI()({ id = clay.ID("PerformanceLeftText"), layout = { sizing = { width = widthSizing }, layoutDirection = .TopToBottom, childGap = 8 } }) { if clay.UI()({ id = clay.ID("PerformanceLeftText"), layout = { sizing = { width = widthSizing }, layoutDirection = .TopToBottom, childGap = 8 } }) {
@ -321,7 +321,7 @@ HighPerformancePageMobile :: proc(lerpValue: f32) {
} }
} }
RendererButtonActive :: proc(index: i32, text: string) { RendererButtonActive :: proc(index: i32, $text: string) {
if clay.UI()({ if clay.UI()({
layout = { sizing = { width = clay.SizingFixed(300) }, padding = clay.PaddingAll(16) }, layout = { sizing = { width = clay.SizingFixed(300) }, padding = clay.PaddingAll(16) },
backgroundColor = COLOR_RED, backgroundColor = COLOR_RED,
@ -331,7 +331,7 @@ RendererButtonActive :: proc(index: i32, text: string) {
} }
} }
RendererButtonInactive :: proc(index: u32, text: string) { RendererButtonInactive :: proc(index: u32, $text: string) {
if clay.UI()({ border = border2pxRed }) { if clay.UI()({ border = border2pxRed }) {
if clay.UI()({ if clay.UI()({
id = clay.ID("RendererButtonInactiveInner", index), id = clay.ID("RendererButtonInactiveInner", index),

166
clay.h
View File

@ -96,9 +96,9 @@
#define CLAY__ENSURE_STRING_LITERAL(x) ("" x "") #define CLAY__ENSURE_STRING_LITERAL(x) ("" x "")
// Note: If an error led you here, it's because CLAY_STRING can only be used with string literals, i.e. CLAY_STRING("SomeString") and not CLAY_STRING(yourString) // Note: If an error led you here, it's because CLAY_STRING can only be used with string literals, i.e. CLAY_STRING("SomeString") and not CLAY_STRING(yourString)
#define CLAY_STRING(string) (CLAY__INIT(Clay_String) { .length = CLAY__STRING_LENGTH(CLAY__ENSURE_STRING_LITERAL(string)), .chars = (string) }) #define CLAY_STRING(string) (CLAY__INIT(Clay_String) { .isStaticallyAllocated = true, .length = CLAY__STRING_LENGTH(CLAY__ENSURE_STRING_LITERAL(string)), .chars = (string) })
#define CLAY_STRING_CONST(string) { .length = CLAY__STRING_LENGTH(CLAY__ENSURE_STRING_LITERAL(string)), .chars = (string) } #define CLAY_STRING_CONST(string) { .isStaticallyAllocated = true, .length = CLAY__STRING_LENGTH(CLAY__ENSURE_STRING_LITERAL(string)), .chars = (string) }
static uint8_t CLAY__ELEMENT_DEFINITION_LATCH; static uint8_t CLAY__ELEMENT_DEFINITION_LATCH;
@ -185,6 +185,9 @@ extern "C" {
// Note: Clay_String is not guaranteed to be null terminated. It may be if created from a literal C string, // Note: Clay_String is not guaranteed to be null terminated. It may be if created from a literal C string,
// but it is also used to represent slices. // but it is also used to represent slices.
typedef struct { typedef struct {
// Set this boolean to true if the char* data underlying this string will live for the entire lifetime of the program.
// This will automatically be set for strings created with CLAY_STRING, as the macro requires a string literal.
bool isStaticallyAllocated;
int32_t length; int32_t length;
// The underlying character memory. Note: this will not be copied and will not extend the lifetime of the underlying memory. // The underlying character memory. Note: this will not be copied and will not extend the lifetime of the underlying memory.
const char *chars; const char *chars;
@ -384,10 +387,6 @@ typedef struct {
// CLAY_TEXT_ALIGN_CENTER - Horizontally aligns wrapped lines of text to the center of their bounding box. // CLAY_TEXT_ALIGN_CENTER - Horizontally aligns wrapped lines of text to the center of their bounding box.
// CLAY_TEXT_ALIGN_RIGHT - Horizontally aligns wrapped lines of text to the right hand side of their bounding box. // CLAY_TEXT_ALIGN_RIGHT - Horizontally aligns wrapped lines of text to the right hand side of their bounding box.
Clay_TextAlignment textAlignment; Clay_TextAlignment textAlignment;
// When set to true, clay will hash the entire text contents of this string as an identifier for its internal
// text measurement cache, rather than just the pointer and length. This will incur significant performance cost for
// long bodies of text.
bool hashStringContents;
} Clay_TextElementConfig; } Clay_TextElementConfig;
CLAY__WRAPPER_STRUCT(Clay_TextElementConfig); CLAY__WRAPPER_STRUCT(Clay_TextElementConfig);
@ -876,8 +875,7 @@ CLAY_DLL_EXPORT int32_t Clay_GetMaxMeasureTextCacheWordCount(void);
// Modifies the maximum number of measured "words" (whitespace seperated runs of characters) that Clay can store in its internal text measurement cache. // Modifies the maximum number of measured "words" (whitespace seperated runs of characters) that Clay can store in its internal text measurement cache.
// This may require reallocating additional memory, and re-calling Clay_Initialize(); // This may require reallocating additional memory, and re-calling Clay_Initialize();
CLAY_DLL_EXPORT void Clay_SetMaxMeasureTextCacheWordCount(int32_t maxMeasureTextCacheWordCount); CLAY_DLL_EXPORT void Clay_SetMaxMeasureTextCacheWordCount(int32_t maxMeasureTextCacheWordCount);
// Resets Clay's internal text measurement cache, useful if memory to represent strings is being re-used. // Resets Clay's internal text measurement cache. Useful if font mappings have changed or fonts have been reloaded.
// Similar behaviour can be achieved on an individual text element level by using Clay_TextElementConfig.hashStringContents
CLAY_DLL_EXPORT void Clay_ResetMeasureTextCache(void); CLAY_DLL_EXPORT void Clay_ResetMeasureTextCache(void);
// Internal API functions required by macros ---------------------- // Internal API functions required by macros ----------------------
@ -1348,26 +1346,140 @@ Clay_ElementId Clay__HashString(Clay_String key, const uint32_t offset, const ui
return CLAY__INIT(Clay_ElementId) { .id = hash + 1, .offset = offset, .baseId = base + 1, .stringId = key }; // Reserve the hash result of zero as "null id" return CLAY__INIT(Clay_ElementId) { .id = hash + 1, .offset = offset, .baseId = base + 1, .stringId = key }; // Reserve the hash result of zero as "null id"
} }
uint32_t Clay__HashTextWithConfig(Clay_String *text, Clay_TextElementConfig *config) { #if !defined(CLAY_DISABLE_SIMD) && (defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64))
uint32_t hash = 0; static inline __m128i Clay__SIMDRotateLeft(__m128i x, int r) {
uintptr_t pointerAsNumber = (uintptr_t)text->chars; return _mm_or_si128(_mm_slli_epi64(x, r), _mm_srli_epi64(x, 64 - r));
}
if (config->hashStringContents) { static inline void Clay__SIMDARXMix(__m128i* a, __m128i* b) {
uint32_t maxLengthToHash = CLAY__MIN(text->length, 256); *a = _mm_add_epi64(*a, *b);
for (uint32_t i = 0; i < maxLengthToHash; i++) { *b = _mm_xor_si128(Clay__SIMDRotateLeft(*b, 17), *a);
hash += text->chars[i]; }
hash += (hash << 10);
hash ^= (hash >> 6); uint64_t Clay__HashData(const uint8_t* data, size_t length) {
// Pinched these constants from the BLAKE implementation
__m128i v0 = _mm_set1_epi64x(0x6a09e667f3bcc908ULL);
__m128i v1 = _mm_set1_epi64x(0xbb67ae8584caa73bULL);
__m128i v2 = _mm_set1_epi64x(0x3c6ef372fe94f82bULL);
__m128i v3 = _mm_set1_epi64x(0xa54ff53a5f1d36f1ULL);
uint8_t overflowBuffer[16] = { 0 }; // Temporary buffer for small inputs
while (length > 0) {
__m128i msg;
if (length >= 16) {
msg = _mm_loadu_si128((const __m128i*)data);
data += 16;
length -= 16;
} }
} else { else {
hash += pointerAsNumber; for (int i = 0; i < length; i++) {
overflowBuffer[i] = data[i];
}
msg = _mm_loadu_si128((const __m128i*)overflowBuffer);
length = 0;
}
v0 = _mm_xor_si128(v0, msg);
Clay__SIMDARXMix(&v0, &v1);
Clay__SIMDARXMix(&v2, &v3);
v0 = _mm_add_epi64(v0, v2);
v1 = _mm_add_epi64(v1, v3);
}
Clay__SIMDARXMix(&v0, &v1);
Clay__SIMDARXMix(&v2, &v3);
v0 = _mm_add_epi64(v0, v2);
v1 = _mm_add_epi64(v1, v3);
uint64_t result[2];
_mm_storeu_si128((__m128i*)result, v0);
return result[0] ^ result[1];
}
#elif !defined(CLAY_DISABLE_SIMD) && defined(__aarch64__)
static inline uint64x2_t Clay__SIMDRotateLeft(uint64x2_t x, int r) {
return vorrq_u64(vshlq_n_u64(x, 17), vshrq_n_u64(x, 64 - 17));
}
static inline void Clay__SIMDARXMix(uint64x2_t* a, uint64x2_t* b) {
*a = vaddq_u64(*a, *b);
*b = veorq_u64(Clay__SIMDRotateLeft(*b, 17), *a);
}
uint64_t Clay__HashData(const uint8_t* data, size_t length) {
// Pinched these constants from the BLAKE implementation
uint64x2_t v0 = vdupq_n_u64(0x6a09e667f3bcc908ULL);
uint64x2_t v1 = vdupq_n_u64(0xbb67ae8584caa73bULL);
uint64x2_t v2 = vdupq_n_u64(0x3c6ef372fe94f82bULL);
uint64x2_t v3 = vdupq_n_u64(0xa54ff53a5f1d36f1ULL);
uint8_t overflowBuffer[8] = { 0 };
while (length > 0) {
uint64x2_t msg;
if (length > 16) {
msg = vld1q_u64((const uint64_t*)data);
data += 16;
length -= 16;
}
else if (length > 8) {
msg = vcombine_u64(vld1_u64((const uint64_t*)data), vdup_n_u64(0));
data += 8;
length -= 8;
}
else {
for (int i = 0; i < length; i++) {
overflowBuffer[i] = data[i];
}
uint8x8_t lower = vld1_u8(overflowBuffer);
msg = vcombine_u8(lower, vdup_n_u8(0));
length = 0;
}
v0 = veorq_u64(v0, msg);
Clay__SIMDARXMix(&v0, &v1);
Clay__SIMDARXMix(&v2, &v3);
v0 = vaddq_u64(v0, v2);
v1 = vaddq_u64(v1, v3);
}
Clay__SIMDARXMix(&v0, &v1);
Clay__SIMDARXMix(&v2, &v3);
v0 = vaddq_u64(v0, v2);
v1 = vaddq_u64(v1, v3);
uint64_t result[2];
vst1q_u64(result, v0);
return result[0] ^ result[1];
}
#else
uint64_t Clay__HashData(const uint8_t* data, size_t length) {
uint64_t hash = 0;
for (int32_t i = 0; i < length; i++) {
hash += data[i];
hash += (hash << 10); hash += (hash << 10);
hash ^= (hash >> 6); hash ^= (hash >> 6);
} }
return hash;
}
#endif
hash += text->length; uint32_t Clay__HashStringContentsWithConfig(Clay_String *text, Clay_TextElementConfig *config) {
hash += (hash << 10); uint32_t hash = 0;
hash ^= (hash >> 6); if (text->isStaticallyAllocated) {
hash += (uintptr_t)text->chars;
hash += (hash << 10);
hash ^= (hash >> 6);
hash += text->length;
hash += (hash << 10);
hash ^= (hash >> 6);
} else {
hash = Clay__HashData((const uint8_t *)text->chars, text->length) % UINT32_MAX;
}
hash += config->fontId; hash += config->fontId;
hash += (hash << 10); hash += (hash << 10);
@ -1377,18 +1489,10 @@ uint32_t Clay__HashTextWithConfig(Clay_String *text, Clay_TextElementConfig *con
hash += (hash << 10); hash += (hash << 10);
hash ^= (hash >> 6); hash ^= (hash >> 6);
hash += config->lineHeight;
hash += (hash << 10);
hash ^= (hash >> 6);
hash += config->letterSpacing; hash += config->letterSpacing;
hash += (hash << 10); hash += (hash << 10);
hash ^= (hash >> 6); hash ^= (hash >> 6);
hash += config->wrapMode;
hash += (hash << 10);
hash ^= (hash >> 6);
hash += (hash << 3); hash += (hash << 3);
hash ^= (hash >> 11); hash ^= (hash >> 11);
hash += (hash << 15); hash += (hash << 15);
@ -1423,7 +1527,7 @@ Clay__MeasureTextCacheItem *Clay__MeasureTextCached(Clay_String *text, Clay_Text
return &Clay__MeasureTextCacheItem_DEFAULT; return &Clay__MeasureTextCacheItem_DEFAULT;
} }
#endif #endif
uint32_t id = Clay__HashTextWithConfig(text, config); uint32_t id = Clay__HashStringContentsWithConfig(text, config);
uint32_t hashBucket = id % (context->maxMeasureTextCacheWordCount / 32); uint32_t hashBucket = id % (context->maxMeasureTextCacheWordCount / 32);
int32_t elementIndexPrevious = 0; int32_t elementIndexPrevious = 0;
int32_t elementIndex = context->measureTextHashMap.internalArray[hashBucket]; int32_t elementIndex = context->measureTextHashMap.internalArray[hashBucket];

View File

@ -148,7 +148,7 @@ static void SDL_Clay_RenderClayCommands(Clay_SDL3RendererData *rendererData, Cla
for (size_t i = 0; i < rcommands->length; i++) { for (size_t i = 0; i < rcommands->length; i++) {
Clay_RenderCommand *rcmd = Clay_RenderCommandArray_Get(rcommands, i); Clay_RenderCommand *rcmd = Clay_RenderCommandArray_Get(rcommands, i);
const Clay_BoundingBox bounding_box = rcmd->boundingBox; const Clay_BoundingBox bounding_box = rcmd->boundingBox;
const SDL_FRect rect = { (int)bounding_box.x, (int)bounding_box.y, (int)bounding_box.width, (int)bounding_box.height }; const SDL_FRect rect = { (float)bounding_box.x, (float)bounding_box.y, (float)bounding_box.width, (float)bounding_box.height };
switch (rcmd->commandType) { switch (rcmd->commandType) {
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
@ -184,27 +184,27 @@ static void SDL_Clay_RenderClayCommands(Clay_SDL3RendererData *rendererData, Cla
if (config->width.left > 0) { if (config->width.left > 0) {
const float starting_y = rect.y + clampedRadii.topLeft; const float starting_y = rect.y + clampedRadii.topLeft;
const float length = rect.h - clampedRadii.topLeft - clampedRadii.bottomLeft; const float length = rect.h - clampedRadii.topLeft - clampedRadii.bottomLeft;
SDL_FRect line = { rect.x, starting_y, config->width.left, length }; SDL_FRect line = { rect.x, starting_y, (float)config->width.left, length };
SDL_RenderFillRect(rendererData->renderer, &line); SDL_RenderFillRect(rendererData->renderer, &line);
} }
if (config->width.right > 0) { if (config->width.right > 0) {
const float starting_x = rect.x + rect.w - (float)config->width.right; const float starting_x = rect.x + rect.w - (float)config->width.right;
const float starting_y = rect.y + clampedRadii.topRight; const float starting_y = rect.y + clampedRadii.topRight;
const float length = rect.h - clampedRadii.topRight - clampedRadii.bottomRight; const float length = rect.h - clampedRadii.topRight - clampedRadii.bottomRight;
SDL_FRect line = { starting_x, starting_y, config->width.right, length }; SDL_FRect line = { starting_x, starting_y, (float)config->width.right, length };
SDL_RenderFillRect(rendererData->renderer, &line); SDL_RenderFillRect(rendererData->renderer, &line);
} }
if (config->width.top > 0) { if (config->width.top > 0) {
const float starting_x = rect.x + clampedRadii.topLeft; const float starting_x = rect.x + clampedRadii.topLeft;
const float length = rect.w - clampedRadii.topLeft - clampedRadii.topRight; const float length = rect.w - clampedRadii.topLeft - clampedRadii.topRight;
SDL_FRect line = { starting_x, rect.y, length, config->width.top }; SDL_FRect line = { starting_x, rect.y, length, (float)config->width.top };
SDL_RenderFillRect(rendererData->renderer, &line); SDL_RenderFillRect(rendererData->renderer, &line);
} }
if (config->width.bottom > 0) { if (config->width.bottom > 0) {
const float starting_x = rect.x + clampedRadii.bottomLeft; const float starting_x = rect.x + clampedRadii.bottomLeft;
const float starting_y = rect.y + rect.h - (float)config->width.bottom; const float starting_y = rect.y + rect.h - (float)config->width.bottom;
const float length = rect.w - clampedRadii.bottomLeft - clampedRadii.bottomRight; const float length = rect.w - clampedRadii.bottomLeft - clampedRadii.bottomRight;
SDL_FRect line = { starting_x, starting_y, length, config->width.bottom }; SDL_FRect line = { starting_x, starting_y, length, (float)config->width.bottom };
SDL_SetRenderDrawColor(rendererData->renderer, config->color.r, config->color.g, config->color.b, config->color.a); SDL_SetRenderDrawColor(rendererData->renderer, config->color.r, config->color.g, config->color.b, config->color.a);
SDL_RenderFillRect(rendererData->renderer, &line); SDL_RenderFillRect(rendererData->renderer, &line);
} }
@ -238,10 +238,10 @@ static void SDL_Clay_RenderClayCommands(Clay_SDL3RendererData *rendererData, Cla
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: {
Clay_BoundingBox boundingBox = rcmd->boundingBox; Clay_BoundingBox boundingBox = rcmd->boundingBox;
currentClippingRectangle = (SDL_Rect) { currentClippingRectangle = (SDL_Rect) {
.x = boundingBox.x, .x = (int)boundingBox.x,
.y = boundingBox.y, .y = (int)boundingBox.y,
.w = boundingBox.width, .w = (int)boundingBox.width,
.h = boundingBox.height, .h = (int)boundingBox.height,
}; };
SDL_SetRenderClipRect(rendererData->renderer, &currentClippingRectangle); SDL_SetRenderClipRect(rendererData->renderer, &currentClippingRectangle);
break; break;