From 9acbbbfbca5e8049b200d344027ee1199db262c3 Mon Sep 17 00:00:00 2001 From: admin Date: Fri, 15 Aug 2025 20:53:56 +0200 Subject: various changes --- build/Makefile | 15 +- build/common.mk | 45 ++--- build/cross-compile.mk | 94 --------- build/native.mk | 19 +- include/glamac/graphics/glamac_view.h | 59 +++++- src/glamac/glamac.c | 57 ++++-- src/glamac/glamac_render.c | 355 ++++++++++++++++++---------------- src/glamac/glamac_view.c | 3 + 8 files changed, 301 insertions(+), 346 deletions(-) delete mode 100644 build/cross-compile.mk diff --git a/build/Makefile b/build/Makefile index d77ff68..572db07 100644 --- a/build/Makefile +++ b/build/Makefile @@ -6,7 +6,6 @@ include common.mk # Include build modules include native.mk -include cross-compile.mk include dependencies.mk # Help system @@ -19,13 +18,6 @@ help: @echo " glautils - Build utilities only" @echo " clean - Clean build files" @echo " rebuild - Clean and rebuild" -ifeq ($(PLATFORM),linux) - @echo "" - @echo "Cross-compilation (Linux → Windows):" - @echo " win - Build glamac for Windows" - @echo " win-all - Build everything for Windows" - @echo " setup-cross - Setup cross-compilation" -endif @echo "" @echo "Dependencies:" @echo " deps - Install SDL3 dependencies" @@ -35,11 +27,6 @@ endif @echo " convert-catalogs - Convert Excel files to JSON" @echo "" @echo "Quick start:" -ifeq ($(PLATFORM),linux) - @echo " make deps && make all # Native build" - @echo " make setup-cross && make win # Windows build" -else - @echo " Install SDL3, then: make all" -endif + @echo " make deps && make all # Install dependencies and build" .PHONY: help \ No newline at end of file diff --git a/build/common.mk b/build/common.mk index d1262c8..967a08d 100644 --- a/build/common.mk +++ b/build/common.mk @@ -1,34 +1,19 @@ # Common build variables and functions for GlaMaC # This file contains shared configuration used by all build modules -# Detect OS and set platform-specific variables -UNAME_S := $(shell uname -s 2>/dev/null || echo Windows_NT) -ifeq ($(UNAME_S),Linux) - PLATFORM := linux - EXE_EXT := - MKDIR := mkdir -p - RM := rm -f - RMDIR := rm -rf - CC := gcc - MINGW_CC := x86_64-w64-mingw32-gcc - SDL3_LIBS := $(shell pkg-config --libs sdl3 SDL3_ttf 2>/dev/null || echo -lSDL3 -lSDL3_ttf) -lm - CROSS_PREFIX := /usr/x86_64-w64-mingw32 -else - PLATFORM := windows - EXE_EXT := .exe - MKDIR := mkdir - RM := del /Q - RMDIR := rmdir /s /q - CC := gcc - SDL3_LIBS := -lSDL3 -lSDL3_ttf -mwindows -endif +# Linux-only build configuration +PLATFORM := linux +EXE_EXT := +MKDIR := mkdir -p +RM := rm -f +RMDIR := rm -rf +CC := gcc +SDL3_LIBS := $(shell pkg-config --libs sdl3 SDL3_ttf 2>/dev/null || echo -lSDL3 -lSDL3_ttf) -lm # Directories (relative to project root) SRCDIR := ../src BINDIR := ../bin -BINDIR_WIN := ../bin/win INCDIR := ../include -DLL_CACHE := $(HOME)/.cache/glamac-dlls # Base flags CFLAGS_BASE := -I$(INCDIR) -O2 -flto @@ -37,24 +22,16 @@ SECURITY_FLAGS := -fstack-protector-strong -D_FORTIFY_SOURCE=2 -fPIE WARNING_FLAGS := -Wall -Wextra -Wformat=2 -Wformat-security -Wnull-dereference -Wstack-protector -Wvla # Compiler flags -CFLAGS := $(CFLAGS_BASE) $(SECURITY_FLAGS) $(WARNING_FLAGS) -CFLAGS_NATIVE := $(CFLAGS) -march=native -# Windows cross-compilation flags (without stack protector to avoid libssp dependency) -WARNING_FLAGS_WIN := -Wall -Wextra -Wformat=2 -Wformat-security -Wnull-dereference -Wvla -CFLAGS_CROSS := $(CFLAGS_BASE) $(WARNING_FLAGS_WIN) -I$(CROSS_PREFIX)/include +CFLAGS := $(CFLAGS_BASE) $(SECURITY_FLAGS) $(WARNING_FLAGS) -march=native # Source files GLAMAC_SRCS := $(wildcard $(SRCDIR)/glamac/*.c) GLAUTILS_SRCS := $(wildcard $(SRCDIR)/glautils/*.c) -GLAUTILS_BINS := $(patsubst $(SRCDIR)/glautils/%.c, $(BINDIR)/%$(EXE_EXT), $(GLAUTILS_SRCS)) -GLAUTILS_BINS_WIN := $(patsubst $(SRCDIR)/glautils/%.c, $(BINDIR_WIN)/%.exe, $(GLAUTILS_SRCS)) +GLAUTILS_BINS := $(patsubst $(SRCDIR)/glautils/%.c, $(BINDIR)/%, $(GLAUTILS_SRCS)) # Glass data dependencies for fgla GLASS_DATA_SRCS := $(SRCDIR)/glamac/glass_data.c $(SRCDIR)/glamac/glamac_errors.c # Common directory creation rules $(BINDIR): - $(MKDIR) $(BINDIR) - -$(BINDIR_WIN): - $(MKDIR) $(BINDIR_WIN) \ No newline at end of file + $(MKDIR) $(BINDIR) \ No newline at end of file diff --git a/build/cross-compile.mk b/build/cross-compile.mk deleted file mode 100644 index 5dc65f8..0000000 --- a/build/cross-compile.mk +++ /dev/null @@ -1,94 +0,0 @@ -# Cross-compilation rules for GlaMaC -# This file contains rules for Windows cross-compilation from Linux - -# Windows cross-compilation (Linux only) -ifeq ($(PLATFORM),linux) - -win: $(BINDIR_WIN)/glamac.exe win-dlls win-data - -win-all: win $(GLAUTILS_BINS_WIN) - -$(BINDIR_WIN)/glamac.exe: $(GLAMAC_SRCS) | $(BINDIR_WIN) - @echo "Cross-compiling glamac for Windows..." - @which $(MINGW_CC) >/dev/null 2>&1 || (echo "ERROR: Install mingw-w64-gcc first" && exit 1) - $(MINGW_CC) $^ $(CFLAGS_CROSS) -L$(CROSS_PREFIX)/lib -lmingw32 -lSDL3 -lSDL3_ttf -mwindows -static-libgcc -o $@ - -$(BINDIR_WIN)/%.exe: $(SRCDIR)/glautils/%.c | $(BINDIR_WIN) - $(MINGW_CC) $< $(CFLAGS_CROSS) -static-libgcc -o $@ - -# Windows DLL management -win-dlls: | $(BINDIR_WIN) - @echo "Getting Windows DLLs..." - @$(MKDIR) $(DLL_CACHE) - @if [ ! -f "$(DLL_CACHE)/SDL3.dll" ]; then \ - echo "Downloading SDL3.dll..."; \ - cd $(DLL_CACHE) && \ - wget -q https://github.com/libsdl-org/SDL/releases/download/release-3.2.10/SDL3-3.2.10-win32-x64.zip && \ - unzip -j SDL3-3.2.10-win32-x64.zip SDL3.dll && \ - $(RM) SDL3-3.2.10-win32-x64.zip; \ - fi - @if [ ! -f "$(DLL_CACHE)/SDL3_ttf.dll" ]; then \ - echo "Downloading SDL3_ttf.dll..."; \ - cd $(DLL_CACHE) && \ - wget -q https://github.com/libsdl-org/SDL_ttf/releases/download/release-3.2.2/SDL3_ttf-3.2.2-win32-x64.zip && \ - unzip -j SDL3_ttf-3.2.2-win32-x64.zip SDL3_ttf.dll && \ - $(RM) SDL3_ttf-3.2.2-win32-x64.zip; \ - fi - @cp $(DLL_CACHE)/*.dll $(BINDIR_WIN)/ - @echo "Windows build ready in $(BINDIR_WIN)/" - -# Copy data files for Windows build -win-data: | $(BINDIR_WIN) - @echo "Copying data files for Windows..." - @$(MKDIR) $(BINDIR_WIN)/data/json - @if [ -f "../data/json/glasses.json" ]; then \ - cp ../data/json/glasses.json $(BINDIR_WIN)/data/json/; \ - cp ../data/json/glasses.json $(BINDIR_WIN)/; \ - echo "Copied glasses.json to Windows build"; \ - else \ - echo "Warning: glasses.json not found, Windows build may use fallback data"; \ - fi - @echo "Copying font for Windows..." - @if [ -f "/usr/share/fonts/TTF/DejaVuSans.ttf" ]; then \ - cp /usr/share/fonts/TTF/DejaVuSans.ttf $(BINDIR_WIN)/; \ - echo "Copied DejaVuSans.ttf to Windows build"; \ - elif [ -f "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" ]; then \ - cp /usr/share/fonts/truetype/dejavu/DejaVuSans.ttf $(BINDIR_WIN)/; \ - echo "Copied DejaVuSans.ttf to Windows build"; \ - else \ - echo "Warning: DejaVu font not found, Windows build may fail to start"; \ - fi - -# Cross-compilation setup -setup-cross: - @echo "Setting up cross-compilation..." - @which wget >/dev/null 2>&1 || (echo "Install wget first: sudo pacman -S wget" && exit 1) - @which $(MINGW_CC) >/dev/null 2>&1 || (echo "Install mingw-w64-gcc first: sudo pacman -S mingw-w64-gcc" && exit 1) - sudo $(MKDIR) $(CROSS_PREFIX)/include $(CROSS_PREFIX)/lib - @echo "Downloading SDL3 development libraries..." - cd /tmp && \ - wget -q https://github.com/libsdl-org/SDL/releases/download/release-3.2.10/SDL3-devel-3.2.10-mingw.tar.gz && \ - wget -q https://github.com/libsdl-org/SDL_ttf/releases/download/release-3.2.2/SDL3_ttf-devel-3.2.2-mingw.tar.gz && \ - tar -xzf SDL3-devel-3.2.10-mingw.tar.gz && \ - tar -xzf SDL3_ttf-devel-3.2.2-mingw.tar.gz && \ - sudo cp -r SDL3-3.2.10/x86_64-w64-mingw32/include/* $(CROSS_PREFIX)/include/ && \ - sudo cp -r SDL3-3.2.10/x86_64-w64-mingw32/lib/* $(CROSS_PREFIX)/lib/ && \ - sudo cp -r SDL3_ttf-3.2.2/x86_64-w64-mingw32/include/* $(CROSS_PREFIX)/include/ && \ - sudo cp -r SDL3_ttf-3.2.2/x86_64-w64-mingw32/lib/* $(CROSS_PREFIX)/lib/ && \ - $(RM) -rf SDL3-3.2.10* SDL3_ttf-3.2.2* - @echo "Cross-compilation setup complete!" - -else -# Windows host - disable cross-compilation -win win-all win-dlls setup-cross: - @echo "Cross-compilation not available on Windows. Use 'make all' instead." -endif - -# Cross-compilation cleanup -clean-cache: - $(RMDIR) $(DLL_CACHE) 2>/dev/null || true - -clean-all: clean clean-cache clean-deps-files - -# Cross-compilation phony targets -.PHONY: win win-all win-dlls setup-cross win-data clean-cache clean-all \ No newline at end of file diff --git a/build/native.mk b/build/native.mk index e4495d5..133c417 100644 --- a/build/native.mk +++ b/build/native.mk @@ -1,25 +1,24 @@ -# Native build rules for GlaMaC -# This file contains rules for building on the native platform (Linux/Windows) +# Native build rules for GlaMaC (Linux-only) # Default target all: glamac glautils -# Native build targets -glamac: $(BINDIR)/glamac$(EXE_EXT) +# Build targets +glamac: $(BINDIR)/glamac glautils: $(GLAUTILS_BINS) -$(BINDIR)/glamac$(EXE_EXT): $(GLAMAC_SRCS) | $(BINDIR) +$(BINDIR)/glamac: $(GLAMAC_SRCS) | $(BINDIR) @echo "Building glamac..." - $(CC) $^ $(CFLAGS_NATIVE) $(SDL3_LIBS) -o $@ + $(CC) $^ $(CFLAGS) $(SDL3_LIBS) -o $@ # Special rule for fgla which needs glass_data dependencies -$(BINDIR)/fgla$(EXE_EXT): $(SRCDIR)/glautils/fgla.c $(GLASS_DATA_SRCS) | $(BINDIR) - $(CC) $^ $(CFLAGS_NATIVE) -o $@ +$(BINDIR)/fgla: $(SRCDIR)/glautils/fgla.c $(GLASS_DATA_SRCS) | $(BINDIR) + $(CC) $^ $(CFLAGS) -o $@ # General rule for other glautils (excluding fgla) -$(BINDIR)/%$(EXE_EXT): $(SRCDIR)/glautils/%.c | $(BINDIR) - $(CC) $< $(CFLAGS_NATIVE) -o $@ +$(BINDIR)/%: $(SRCDIR)/glautils/%.c | $(BINDIR) + $(CC) $< $(CFLAGS) -o $@ # Excel to JSON conversion EXCEL_FILES := $(wildcard ../data/Excel/*.xlsx) diff --git a/include/glamac/graphics/glamac_view.h b/include/glamac/graphics/glamac_view.h index c7b00d9..9a556e2 100644 --- a/include/glamac/graphics/glamac_view.h +++ b/include/glamac/graphics/glamac_view.h @@ -43,6 +43,15 @@ struct TightCluster { f32 centerVd; // Average vd of cluster }; +// Cached coordinate transformation parameters +typedef struct { + f32 scale_x, scale_y; // Scaling factors + f32 offset_x, offset_y; // Translation offsets + f32 abbe_range, ri_range; // Data ranges + i32 padding; // Current padding + b32 valid; // Whether cache is valid +} CoordTransform; + // State for zooming and panning typedef struct { f32 zoomLevel; @@ -61,6 +70,9 @@ typedef struct { b32 viewDirty; // Flag to track if view needs re-rendering u32 forceRenderFrames; // Number of additional frames to force rendering (for transitions) + // Coordinate transformation cache + CoordTransform transform_cache; + // Tight clustering data TightCluster* tightClusters; // Array of tight clusters u32 tightClusterCount; // Number of tight clusters @@ -87,23 +99,53 @@ static inline i32 get_adaptive_padding_for_size(i32 windowWidth, i32 windowHeigh return padding > MIN_PADDING ? padding : MIN_PADDING; } -// Convert glass data to screen coordinates with zoom and offset +// Update coordinate transformation cache +static inline void update_transform_cache(ViewState* view) { + CoordTransform* cache = &view->transform_cache; + const i32 padding = get_adaptive_padding(view); + const f32 abbe_range = view->maxAbbe - view->minAbbe; + const f32 ri_range = view->maxRI - view->minRI; + + // Check if cache is valid (validate all relevant parameters) + if (cache->valid && + cache->padding == padding && + cache->abbe_range == abbe_range && + cache->ri_range == ri_range && + cache->scale_x != 0.0f && cache->scale_y != 0.0f) { + return; // Cache is still valid + } + + // Update cache + cache->abbe_range = abbe_range; + cache->ri_range = ri_range; + cache->scale_x = (view->windowWidth - 2 * padding) * view->zoomLevel / abbe_range; + cache->scale_y = (view->windowHeight - 2 * padding) * view->zoomLevel / ri_range; + cache->offset_x = padding + (view->windowWidth - 2 * padding) * (0.5f + view->offsetX); + cache->offset_y = view->windowHeight - padding - (view->windowHeight - 2 * padding) * (0.5f + view->offsetY); + cache->padding = padding; + cache->valid = 1; +} + +// Coordinate transformation (temporarily without cache to debug) static inline void data_to_screen_coords(f32 abbeNumber, f32 refractiveIndex, - const ViewState* view, i32 *x, i32 *y) { + ViewState* view, i32 *x, i32 *y) { const i32 padding = get_adaptive_padding(view); - // Apply zoom and offset transformation + // Calculate ranges + const f32 abbeRange = view->maxAbbe - view->minAbbe; + const f32 riRange = view->maxRI - view->minRI; + // FLIPPED: Use 1.0f - normalized to flip the Abbe number axis - f32 normalizedX = 1.0f - (abbeNumber - view->minAbbe) / (view->maxAbbe - view->minAbbe); - f32 normalizedY = (refractiveIndex - view->minRI) / (view->maxRI - view->minRI); + f32 normalizedX = 1.0f - (abbeNumber - view->minAbbe) / abbeRange; + f32 normalizedY = (refractiveIndex - view->minRI) / riRange; - // Transform with zoom and offset + // Apply zoom and offset transformations normalizedX = (normalizedX - 0.5f) * view->zoomLevel + 0.5f + view->offsetX; normalizedY = (normalizedY - 0.5f) * view->zoomLevel + 0.5f + view->offsetY; // Convert to screen coordinates - *x = padding + (i32)(normalizedX * (view->windowWidth - 2 * padding)); - *y = view->windowHeight - padding - (i32)(normalizedY * (view->windowHeight - 2 * padding)); + *x = (i32)(padding + normalizedX * (view->windowWidth - 2 * padding)); + *y = (i32)(view->windowHeight - padding - normalizedY * (view->windowHeight - 2 * padding)); } // Convert screen coordinates to data values @@ -153,6 +195,7 @@ static inline b32 is_glass_visible(f32 abbeNumber, f32 refractiveIndex, const Vi // Mark view as dirty (needs re-rendering) static inline void mark_view_dirty(ViewState* view) { view->viewDirty = 1; + view->transform_cache.valid = 0; // Invalidate coordinate cache } // Handle mouse wheel zoom 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