refactor: eliminate globals, add pen tool + edit mode, frustum culling

Remove all file-scope mutable state so the codebase compiles cleanly
as a single translation unit. State moved into structs owned by
userdata_t:

- group_index_ctx_t replaces g_group_by_id/g_group_by_id_cap
- shape_pool_ctx_t replaces g_shape_pool_dirty/g_shape_data_dirty
  and g_shape_groups/g_shape_groups_count
- panel_log_ctx_t wired through all subsystems explicitly
- pipeline_ctx_t owns render pipeline handles
- overlay_upload_state_t (per-buffer flags) replaces single bool

New features piggybacking on the refactor:

- Pen tool: click-to-place anchors, Catmull-Rom preview, finalize
  into Bezier shapes with control points
- Edit mode: anchor/handle hit testing, dragging, pre-drag undo
  snapshots, dedicated GPU buffers for edit overlays
- Frustum culling: spatial-grid-based viewport cull in draw_shapes
  with linear-scan fallback for oversized viewports
- Log dedup: FNV-1a 64-bit hash to skip duplicate messages
- Buffer shrink: halve draw buffers after 60 frames of low usage
- Shape geometry hashing for instanced-draw vertex-buffer grouping
- Group member_indices arrays with O(n) rebuild
- Log ring expanded 64→256 entries, added log_filter

Debug build: added --profiling-funcs and -sASSERTIONS flags.
This commit is contained in:
2026-05-03 00:38:45 +02:00
parent c4d657043c
commit 7e3da1c424
19 changed files with 2610 additions and 2945 deletions

View File

@@ -33,7 +33,11 @@ static void vec_grow(vector_t *v, int min_capacity) {
int new_cap = v->capacity ? v->capacity * 2 : 8;
if (new_cap < min_capacity) new_cap = min_capacity;
uint8_t *new_data = (uint8_t*) ALLOC(new_cap * v->stride);
assert(new_data != NULL);
if (!new_data) {
EM_ASM({ console.error("vec_grow: ALLOC failed for %d elements of %d bytes", $0, $1); },
new_cap, v->stride);
return;
}
if (v->data) {
memcpy(new_data, v->data, v->count * v->stride);
FREE(v->data);
@@ -72,6 +76,25 @@ static void vec_remove_ordered(vector_t *v, int index) {
v->count--;
}
// Remove `count` elements at given indices in a single compaction pass.
// Indices must be sorted in ascending order and must be valid.
static void vec_remove_ordered_bulk(vector_t *v, const int *indices, int count) {
if (count <= 0) return;
int write = indices[0];
for (int k = 0; k < count; k++) {
int gap_start = indices[k];
int gap_end = (k + 1 < count) ? indices[k + 1] : v->count;
int keep = gap_end - gap_start - 1;
if (keep > 0) {
memmove(v->data + write * v->stride,
v->data + (gap_start + 1) * v->stride,
(size_t)keep * (size_t)v->stride);
write += keep;
}
}
v->count -= count;
}
static void *vec_insert(vector_t *v, int index) {
if (index < 0 || index > v->count) return NULL;
if (v->count >= v->capacity) vec_grow(v, v->count + 1);