diff options
author | admin <admin@optics-design.com> | 2025-08-15 20:53:56 +0200 |
---|---|---|
committer | admin <admin@optics-design.com> | 2025-08-15 20:53:56 +0200 |
commit | 9acbbbfbca5e8049b200d344027ee1199db262c3 (patch) | |
tree | 2ede5dc4f04d04a8ea940544f07e42a8cb183f2c | |
parent | 91c024d42e54c3db70fa9693525c0dc2b5e775fc (diff) |
-rw-r--r-- | build/Makefile | 15 | ||||
-rw-r--r-- | build/common.mk | 45 | ||||
-rw-r--r-- | build/cross-compile.mk | 94 | ||||
-rw-r--r-- | build/native.mk | 19 | ||||
-rw-r--r-- | include/glamac/graphics/glamac_view.h | 59 | ||||
-rw-r--r-- | src/glamac/glamac.c | 57 | ||||
-rw-r--r-- | src/glamac/glamac_render.c | 355 | ||||
-rw-r--r-- | src/glamac/glamac_view.c | 3 |
8 files changed, 301 insertions, 346 deletions
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;
|