From 9acbbbfbca5e8049b200d344027ee1199db262c3 Mon Sep 17 00:00:00 2001 From: admin Date: Fri, 15 Aug 2025 20:53:56 +0200 Subject: various changes --- src/glamac/glamac.c | 57 +++++--- src/glamac/glamac_render.c | 355 ++++++++++++++++++++++++--------------------- src/glamac/glamac_view.c | 3 + 3 files changed, 229 insertions(+), 186 deletions(-) (limited to 'src') diff --git a/src/glamac/glamac.c b/src/glamac/glamac.c index 7f361ab..c2ba13d 100644 --- a/src/glamac/glamac.c +++ b/src/glamac/glamac.c @@ -14,13 +14,14 @@ extern b32 process_events(SDL_Event *event, ViewState *view, SDL_Window *window, i32 *lastMouseX, i32 *lastMouseY, b32 *dragging, b32 *quit); -// Function to reload fonts on window resize with improved error handling and hysteresis +// Function to reload fonts on window resize with improved error handling and debounce b32 reload_fonts_if_needed(FontSet *fonts, ViewState *view, f32 dpi) { static i32 lastWidth = 0; static i32 lastHeight = 0; static u32 lastReloadTime = 0; static i32 pendingWidth = 0; static i32 pendingHeight = 0; + static u32 lastSizeChangeTime = 0; // For debounce mechanism // Validate inputs if (!fonts || !view) { @@ -35,28 +36,36 @@ b32 reload_fonts_if_needed(FontSet *fonts, ViewState *view, f32 dpi) { return 0; } - // Hysteresis parameters - reduced for better responsiveness - const f32 threshold = 0.25f; // 25% change threshold - const u32 minReloadInterval = 100; // Minimum 100ms between font reloads - const u32 maxPendingTime = 500; // Maximum time to wait for pending reload + // Optimized parameters to reduce excessive reloading + const f32 threshold = 0.35f; // 35% change threshold + const u32 minReloadInterval = 250; // Minimum 250ms between font reloads + const u32 maxPendingTime = 750; // Maximum time to wait for pending reload + const u32 debounceTime = 150; // Wait 150ms for window size to stabilize u32 currentTime = SDL_GetTicks(); - // Check if window size changed significantly + // Check if window size changed i32 widthDiff = abs(view->windowWidth - lastWidth); i32 heightDiff = abs(view->windowHeight - lastHeight); - b32 significantChange = (widthDiff > (i32)(view->windowWidth * threshold) || - heightDiff > (i32)(view->windowHeight * threshold)); + b32 sizeChanged = (widthDiff > 0 || heightDiff > 0); - if (significantChange) { - // Update pending dimensions + // Debounce: Update last change time if size changed + if (sizeChanged) { + lastSizeChangeTime = currentTime; pendingWidth = view->windowWidth; pendingHeight = view->windowHeight; - + } + + // Check if change is significant enough and debounced + b32 significantChange = (widthDiff > (i32)(view->windowWidth * threshold) || + heightDiff > (i32)(view->windowHeight * threshold)); + b32 debounced = (currentTime - lastSizeChangeTime >= debounceTime); + + if (significantChange && debounced) { // Check if enough time has passed since last reload if (currentTime - lastReloadTime >= minReloadInterval) { if (is_debug_mode()) { - printf("Window size changed significantly: %dx%d -> %dx%d, reloading fonts...\n", + printf("Window size changed significantly (debounced): %dx%d -> %dx%d, reloading fonts...\n", lastWidth, lastHeight, view->windowWidth, view->windowHeight); } @@ -101,6 +110,7 @@ b32 reload_fonts_if_needed(FontSet *fonts, ViewState *view, f32 dpi) { lastWidth = view->windowWidth; lastHeight = view->windowHeight; lastReloadTime = currentTime; + lastSizeChangeTime = 0; // Reset debounce timer pendingWidth = pendingHeight = 0; // Clear pending state // Mark view as dirty to re-render with new fonts @@ -116,19 +126,28 @@ b32 reload_fonts_if_needed(FontSet *fonts, ViewState *view, f32 dpi) { } else { // Can't reload yet due to rate limiting, check if we should force it if (pendingWidth != 0 && pendingHeight != 0 && - currentTime - lastReloadTime >= maxPendingTime) { + currentTime - lastReloadTime >= maxPendingTime && debounced) { if (is_debug_mode()) { - printf("Forcing delayed font reload after %ums\n", maxPendingTime); + printf("Forcing delayed font reload after %ums (debounced)\n", maxPendingTime); } - // Recursive call will now pass the time check - return reload_fonts_if_needed(fonts, view, dpi); + // Force reload by temporarily adjusting the time check + u32 savedTime = lastReloadTime; + lastReloadTime = currentTime - minReloadInterval; + b32 result = reload_fonts_if_needed(fonts, view, dpi); + if (!result) { + lastReloadTime = savedTime; // Restore on failure + } + return result; } // Silently rate limit font reloads } } else if (pendingWidth != 0 && pendingHeight != 0 && - currentTime - lastReloadTime >= minReloadInterval) { - // Handle pending resize that wasn't significant enough initially - // but has been waiting for the rate limit + currentTime - lastReloadTime >= minReloadInterval && debounced) { + // Handle pending resize that has been debounced but wasn't significant initially + if (is_debug_mode()) { + printf("Processing debounced pending resize: %dx%d -> %dx%d\n", + lastWidth, lastHeight, pendingWidth, pendingHeight); + } view->windowWidth = pendingWidth; view->windowHeight = pendingHeight; return reload_fonts_if_needed(fonts, view, dpi); diff --git a/src/glamac/glamac_render.c b/src/glamac/glamac_render.c index c375754..1398ba9 100644 --- a/src/glamac/glamac_render.c +++ b/src/glamac/glamac_render.c @@ -28,12 +28,10 @@ #define AXIS_LABEL_OFFSET 15 #define AXIS_MARGIN 45 -// Safe font paths (in order of preference) +// Linux font paths (in order of preference) static const char* SAFE_FONT_PATHS[] = { "/usr/share/fonts/TTF/DejaVuSans.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", - "/System/Library/Fonts/Arial.ttf", // macOS - "C:\\Windows\\Fonts\\arial.ttf", // Windows "fonts/DejaVuSans.ttf", // Local fallback "DejaVuSans.ttf", // Current directory NULL @@ -42,64 +40,85 @@ static const char* SAFE_FONT_PATHS[] = { static const char* SAFE_BOLD_FONT_PATHS[] = { "/usr/share/fonts/TTF/DejaVuSans-Bold.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", - "/System/Library/Fonts/Arial Bold.ttf", // macOS - "C:\\Windows\\Fonts\\arialbd.ttf", // Windows "fonts/DejaVuSans-Bold.ttf", // Local fallback "DejaVuSans-Bold.ttf", // Current directory NULL }; -// Text rendering cache with LRU eviction +// Hash-based text cache for O(1) lookup #define TEXT_CACHE_SIZE 512 #define MAX_TEXT_LENGTH 128 -typedef struct { +typedef struct CacheEntry { char text[MAX_TEXT_LENGTH]; SDL_Texture* texture; i32 width, height; SDL_Color color; TTF_Font* font; u32 last_used_time; // For LRU eviction - b32 in_use; // Slot availability flag -} CachedText; + struct CacheEntry* next; // For collision chaining +} CacheEntry; -static CachedText textCache[TEXT_CACHE_SIZE]; -static i32 cacheSize = 0; +static CacheEntry* textCache[TEXT_CACHE_SIZE] = {NULL}; static u32 cache_access_counter = 0; // Global counter for LRU -// Find least recently used cache entry for eviction -static i32 find_lru_cache_entry(void) { - i32 lru_index = 0; - u32 oldest_time = textCache[0].last_used_time; - - for (i32 i = 1; i < TEXT_CACHE_SIZE; i++) { - if (!textCache[i].in_use) { - return i; // Found empty slot - } - if (textCache[i].last_used_time < oldest_time) { - oldest_time = textCache[i].last_used_time; - lru_index = i; - } +// Simple hash function for cache key +static inline u32 hash_cache_key(TTF_Font* font, const char* text, SDL_Color color) { + u32 hash = 2166136261u; // FNV-1a hash + hash ^= (uintptr_t)font; + hash *= 16777619u; + hash ^= color.r; + hash *= 16777619u; + hash ^= color.g; + hash *= 16777619u; + hash ^= color.b; + hash *= 16777619u; + + for (const char* p = text; *p; ++p) { + hash ^= (u8)*p; + hash *= 16777619u; } - return lru_index; + return hash % TEXT_CACHE_SIZE; } -// Evict cache entry and free its resources -static void evict_cache_entry(i32 index) { - if (index >= 0 && index < TEXT_CACHE_SIZE && textCache[index].in_use) { - if (textCache[index].texture) { - SDL_DestroyTexture(textCache[index].texture); - textCache[index].texture = NULL; +// Find and remove least recently used entry from a collision chain +static void evict_lru_from_chain(CacheEntry** head) { + if (!*head) return; + + if (!(*head)->next) { + // Only one entry, remove it + SDL_DestroyTexture((*head)->texture); + free(*head); + *head = NULL; + return; + } + + // Find LRU entry + CacheEntry* lru = *head; + CacheEntry* lru_prev = NULL; + CacheEntry* prev = NULL; + + for (CacheEntry* curr = *head; curr; curr = curr->next) { + if (curr->last_used_time < lru->last_used_time) { + lru = curr; + lru_prev = prev; } - textCache[index].in_use = 0; - textCache[index].text[0] = '\0'; - textCache[index].font = NULL; - textCache[index].last_used_time = 0; + prev = curr; + } + + // Remove LRU entry + if (lru_prev) { + lru_prev->next = lru->next; + } else { + *head = lru->next; } + + SDL_DestroyTexture(lru->texture); + free(lru); } -// Find cached text texture with LRU management +// O(1) hash-based cached text lookup static SDL_Texture* get_cached_text(SDL_Renderer* renderer, TTF_Font* font, const char* text, SDL_Color color, i32* width, i32* height) { if (!renderer || !font || !text || !width || !height) { return NULL; @@ -111,32 +130,31 @@ static SDL_Texture* get_cached_text(SDL_Renderer* renderer, TTF_Font* font, cons return NULL; } - // Security: Ensure text is properly null-terminated if (text[text_len] != '\0') { printf("Warning: Cached text not properly null-terminated\n"); return NULL; } cache_access_counter++; - - // Search for existing cache entry - for (i32 i = 0; i < TEXT_CACHE_SIZE; i++) { - if (textCache[i].in_use && - textCache[i].font == font && - textCache[i].color.r == color.r && - textCache[i].color.g == color.g && - textCache[i].color.b == color.b && - strcmp(textCache[i].text, text) == 0) { + u32 hash = hash_cache_key(font, text, color); + + // Search collision chain for existing entry + for (CacheEntry* entry = textCache[hash]; entry; entry = entry->next) { + if (entry->font == font && + entry->color.r == color.r && + entry->color.g == color.g && + entry->color.b == color.b && + strcmp(entry->text, text) == 0) { // Update LRU timestamp - textCache[i].last_used_time = cache_access_counter; - *width = textCache[i].width; - *height = textCache[i].height; - return textCache[i].texture; + entry->last_used_time = cache_access_counter; + *width = entry->width; + *height = entry->height; + return entry->texture; } } - // Create new cached texture + // Create new texture SDL_Surface* surface = TTF_RenderText_Blended(font, text, text_len, color); if (!surface) { return NULL; @@ -148,30 +166,39 @@ static SDL_Texture* get_cached_text(SDL_Renderer* renderer, TTF_Font* font, cons return NULL; } - // Find cache slot (either empty or LRU) - i32 cache_index; - if (cacheSize < TEXT_CACHE_SIZE) { - // Use next available slot - cache_index = cacheSize; - cacheSize++; - } else { - // Cache is full, evict LRU entry - cache_index = find_lru_cache_entry(); - evict_cache_entry(cache_index); + // Count entries in chain - if too many, evict LRU + i32 chain_length = 0; + for (CacheEntry* entry = textCache[hash]; entry; entry = entry->next) { + chain_length++; + } + + if (chain_length >= 8) { // Limit chain length + evict_lru_from_chain(&textCache[hash]); + } + + // Create new cache entry + CacheEntry* new_entry = malloc(sizeof(CacheEntry)); + if (!new_entry) { + SDL_DestroyTexture(texture); + SDL_DestroySurface(surface); + return NULL; } // Store in cache with safe string copying - size_t max_copy = sizeof(textCache[cache_index].text) - 1; + size_t max_copy = sizeof(new_entry->text) - 1; size_t copy_len = (text_len < max_copy) ? text_len : max_copy; - memcpy(textCache[cache_index].text, text, copy_len); - textCache[cache_index].text[copy_len] = '\0'; - textCache[cache_index].texture = texture; - textCache[cache_index].width = surface->w; - textCache[cache_index].height = surface->h; - textCache[cache_index].color = color; - textCache[cache_index].font = font; - textCache[cache_index].last_used_time = cache_access_counter; - textCache[cache_index].in_use = 1; + memcpy(new_entry->text, text, copy_len); + new_entry->text[copy_len] = '\0'; + new_entry->texture = texture; + new_entry->width = surface->w; + new_entry->height = surface->h; + new_entry->color = color; + new_entry->font = font; + new_entry->last_used_time = cache_access_counter; + + // Insert at head of collision chain + new_entry->next = textCache[hash]; + textCache[hash] = new_entry; *width = surface->w; *height = surface->h; @@ -182,16 +209,15 @@ static SDL_Texture* get_cached_text(SDL_Renderer* renderer, TTF_Font* font, cons void clear_text_cache(void) { for (i32 i = 0; i < TEXT_CACHE_SIZE; i++) { - if (textCache[i].in_use && textCache[i].texture) { - SDL_DestroyTexture(textCache[i].texture); - textCache[i].texture = NULL; + CacheEntry* entry = textCache[i]; + while (entry) { + CacheEntry* next = entry->next; + SDL_DestroyTexture(entry->texture); + free(entry); + entry = next; } - textCache[i].in_use = 0; - textCache[i].text[0] = '\0'; - textCache[i].font = NULL; - textCache[i].last_used_time = 0; + textCache[i] = NULL; } - cacheSize = 0; cache_access_counter = 0; } @@ -295,31 +321,20 @@ static TTF_Font* load_font_safe(const char* const* font_paths, int size) { continue; } - // Enhanced security: Check for directory traversal and other unsafe patterns - if (strstr(path, "..") || strstr(path, "//") || - strstr(path, "%2E%2E") || // URL-encoded .. - strstr(path, "%2F%2F") || // URL-encoded // - strstr(path, "\\\\") || // Windows double backslash - strstr(path, "\\.\\")) { // Windows .\ pattern + // Security: Check for directory traversal + if (strstr(path, "..") || strstr(path, "//")) { if (is_debug_mode()) { printf("Warning: Skipping potentially unsafe font path: %s\n", path); } continue; } - // Additional validation: ensure path is within safe directories - b32 safe_path = 0; - if (strncmp(path, "/usr/share/fonts/", 17) == 0 || - strncmp(path, "/System/Library/Fonts/", 22) == 0 || - strncmp(path, "C:\\Windows\\Fonts\\", 17) == 0 || - strncmp(path, "fonts/", 6) == 0 || - (strlen(path) < 50 && strchr(path, '/') == NULL)) { // Simple filename only - safe_path = 1; - } - - if (!safe_path) { + // Validate path is in safe directories + if (!(strncmp(path, "/usr/share/fonts/", 17) == 0 || + strncmp(path, "fonts/", 6) == 0 || + strchr(path, '/') == NULL)) { // Simple filename only if (is_debug_mode()) { - printf("Warning: Font path not in safe directory list: %s\n", path); + printf("Warning: Font path not in safe directory: %s\n", path); } continue; } @@ -805,80 +820,25 @@ void render(SDL_Renderer *renderer, const FontSet *fonts, ViewState* view) { view->viewDirty = 0; } -// Font management functions -b32 load_fonts(FontSet *fonts) { - if (!fonts) return 0; - - // Initialize to NULL for safe cleanup - fonts->regular = NULL; - fonts->title = NULL; - fonts->label = NULL; - fonts->axis = NULL; - fonts->label_bold = NULL; - - fonts->regular = load_font_safe(SAFE_FONT_PATHS, 16); - fonts->title = load_font_safe(SAFE_BOLD_FONT_PATHS, 20); - fonts->label = load_font_safe(SAFE_FONT_PATHS, 11); - fonts->axis = load_font_safe(SAFE_FONT_PATHS, 8); - fonts->label_bold = load_font_safe(SAFE_BOLD_FONT_PATHS, 12); - - // Debug output to check font loading - if (is_debug_mode()) { - printf("Font loading results:\n"); - printf(" Regular font: %s\n", fonts->regular ? "loaded" : "failed"); - printf(" Title/Bold font: %s\n", fonts->title ? "loaded" : "failed"); - printf(" Label font: %s\n", fonts->label ? "loaded" : "failed"); - printf(" Axis font: %s\n", fonts->axis ? "loaded" : "failed"); - printf(" Label bold font: %s\n", fonts->label_bold ? "loaded" : "failed"); - } - - // Check if at least one font loaded successfully - if (!fonts->regular && !fonts->title && !fonts->label) { - printf("Critical error: No fonts could be loaded\n"); - return 0; - } - - // Use fallbacks if specific fonts failed - if (!fonts->title && fonts->regular) { - fonts->title = fonts->regular; - if (is_debug_mode()) { - printf("Warning: Using regular font as title font fallback - bold/regular distinction will be lost!\n"); - } - } - if (!fonts->label && fonts->regular) { - fonts->label = fonts->regular; - if (is_debug_mode()) { - printf("Warning: Using regular font as label font fallback\n"); - } - } - if (!fonts->regular && fonts->title) { - fonts->regular = fonts->title; - if (is_debug_mode()) { - printf("Warning: Using title font as regular font fallback\n"); - } - } - if (!fonts->axis && fonts->regular) { - fonts->axis = fonts->regular; - if (is_debug_mode()) { - printf("Warning: Using regular font as axis font fallback\n"); - } - } - if (!fonts->label_bold && fonts->title) { - fonts->label_bold = fonts->title; - if (is_debug_mode()) { - printf("Warning: Using title font as label bold font fallback\n"); - } - } else if (!fonts->label_bold && fonts->label) { - fonts->label_bold = fonts->label; - if (is_debug_mode()) { - printf("Warning: Using label font as label bold font fallback\n"); - } +// Simplified font fallback system +static void setup_font_fallbacks(FontSet *fonts) { + // Ensure we have at least one base font + TTF_Font* base_font = fonts->regular ? fonts->regular : + fonts->title ? fonts->title : fonts->label; + + if (!base_font) { + return; // Nothing to work with } - return (fonts->regular && fonts->title && fonts->label && fonts->axis && fonts->label_bold) ? 1 : 0; + // Simple fallback chain - use base font for any missing font + if (!fonts->regular) fonts->regular = base_font; + if (!fonts->title) fonts->title = base_font; + if (!fonts->label) fonts->label = base_font; + if (!fonts->axis) fonts->axis = base_font; + if (!fonts->label_bold) fonts->label_bold = base_font; } -b32 load_adaptive_fonts(FontSet *fonts, i32 windowWidth, i32 windowHeight, f32 dpi) { +b32 load_adaptive_fonts_OLD_COMPLEX(FontSet *fonts, i32 windowWidth, i32 windowHeight, f32 dpi) { (void)windowHeight; // Suppress unused parameter warning if (!fonts) return 0; @@ -997,6 +957,67 @@ b32 load_adaptive_fonts(FontSet *fonts, i32 windowWidth, i32 windowHeight, f32 d return (fonts->regular && fonts->title && fonts->label && fonts->axis && fonts->label_bold) ? 1 : 0; } +// Simplified font loading with adaptive sizing +b32 load_adaptive_fonts(FontSet *fonts, i32 windowWidth, i32 windowHeight, f32 dpi) { + (void)windowHeight; // Suppress unused parameter warning + + if (!fonts) return 0; + + // Validate and use defaults if needed + if (windowWidth <= 0 || dpi <= 0) { + windowWidth = 800; + dpi = 96.0f; + } + + // Calculate font sizes with proper validation + i32 regularSize = (i32)(16.0f * dpi / 96.0f * windowWidth / 800.0f); + i32 titleSize = (i32)(20.0f * dpi / 96.0f * windowWidth / 800.0f); + i32 labelSize = (i32)(17.0f * dpi / 96.0f * windowWidth / 800.0f); // +6pt from 11pt + i32 axisSize = (i32)(14.0f * dpi / 96.0f * windowWidth / 800.0f); // +6pt from 8pt + i32 labelBoldSize = (i32)(18.0f * dpi / 96.0f * windowWidth / 800.0f); // +6pt from 12pt + + // Clamp to safe ranges + if (regularSize < 10) regularSize = 10; + if (regularSize > 32) regularSize = 32; + if (titleSize < 12) titleSize = 12; + if (titleSize > 36) titleSize = 36; + if (labelSize < 12) labelSize = 12; // Updated minimum for larger labels + if (labelSize > 26) labelSize = 26; // Updated maximum for larger labels + if (axisSize < 10) axisSize = 10; // Updated minimum for larger axis fonts + if (axisSize > 24) axisSize = 24; // Updated maximum for larger axis fonts + if (labelBoldSize < 12) labelBoldSize = 12; // Updated minimum for larger bold labels + if (labelBoldSize > 26) labelBoldSize = 26; // Updated maximum for larger bold labels + + // Initialize all to NULL + fonts->regular = fonts->title = fonts->label = fonts->axis = fonts->label_bold = NULL; + + // Load fonts with calculated sizes + fonts->regular = load_font_safe(SAFE_FONT_PATHS, regularSize); + fonts->title = load_font_safe(SAFE_BOLD_FONT_PATHS, titleSize); + fonts->label = load_font_safe(SAFE_FONT_PATHS, labelSize); + fonts->axis = load_font_safe(SAFE_FONT_PATHS, axisSize); + fonts->label_bold = load_font_safe(SAFE_BOLD_FONT_PATHS, labelBoldSize); + + // Apply fallback system + setup_font_fallbacks(fonts); + + if (is_debug_mode()) { + printf("Fonts loaded (sizes: reg=%d, title=%d, label=%d, axis=%d): regular=%s, title=%s, label=%s, axis=%s\n", + regularSize, titleSize, labelSize, axisSize, + fonts->regular ? "ok" : "fail", + fonts->title ? "ok" : "fail", + fonts->label ? "ok" : "fail", + fonts->axis ? "ok" : "fail"); + } + + return fonts->regular ? 1 : 0; +} + +// Simple font loading with fixed sizes +b32 load_fonts(FontSet *fonts) { + return load_adaptive_fonts(fonts, 800, 600, 96.0f); +} + void free_fonts(FontSet *fonts) { if (fonts->regular) { TTF_CloseFont(fonts->regular); diff --git a/src/glamac/glamac_view.c b/src/glamac/glamac_view.c index 12c96fd..1fb8395 100644 --- a/src/glamac/glamac_view.c +++ b/src/glamac/glamac_view.c @@ -27,6 +27,9 @@ void init_view_state(ViewState* view, i32 windowWidth, i32 windowHeight) { view->viewDirty = 1; // Initial render needed view->forceRenderFrames = 0; // No forced rendering initially + // Initialize coordinate transformation cache + view->transform_cache.valid = 0; + // Initialize tight clustering data view->tightClusters = NULL; view->tightClusterCount = 0; -- cgit v1.2.3