You've already forked flecs_tests
Compare commits
3 Commits
beea8a0281
...
rework
| Author | SHA1 | Date | |
|---|---|---|---|
| af0a39166c | |||
| 8ea6a0bf3e | |||
| dc708de354 |
87
README.md
87
README.md
@@ -1,31 +1,6 @@
|
|||||||
# Cartograph
|
# Cartograph
|
||||||
|
|
||||||
A browser-based world map creation tool inspired by Wonderdraft and Inkarnate. Uses line-strip vector shapes with procedural geometry.
|
A browser-based world map creation tool inspired by Wonderdraft and Inkarnate. Uses a hierarchical tile-based SDF rendering.
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- **Shapes** — parametric circles, stars, rectangles; freeform pen tool with Catmull-Rom splines
|
|
||||||
- **Instanced rendering** — shapes with identical local-space geometry share vertex buffers; per-instance transforms (position, scale, rotation) uploaded via SSBO
|
|
||||||
- **Bezier editing** — double-click any shape to edit its Bezier control points and handles in local space
|
|
||||||
- **Shape editing** — select, move, rotate, scale individual shapes; rect-select multiple shapes
|
|
||||||
- **Groups** — group/ungroup shapes with nested hierarchy support, group-level selection and focus mode
|
|
||||||
- **Clipboard** — copy/paste shapes with deep-copy of geometry, group remapping for pasted items
|
|
||||||
- **Undo/redo** — property-level history stack (position, scale, rotation, vertex edits, create/delete, group) with batch operations
|
|
||||||
- **Pen tool** — click to place control points; Enter, double-click, or close-to-start to commit; Escape to cancel
|
|
||||||
- **Spatial index** — open-addressing hash grid for fast hit testing, rect-selection, and viewport culling on large shape counts
|
|
||||||
- **Viewport** — zoom (scroll) and pan (right-click drag) with screen↔world coordinate transforms
|
|
||||||
- **Debug panel** — toggleable log overlay (backtick), FPS meter with 60-frame rolling average, log filtering
|
|
||||||
|
|
||||||
## Tech stack
|
|
||||||
|
|
||||||
| Layer | Library |
|
|
||||||
|---|---|
|
|
||||||
| Language | C (C99) |
|
|
||||||
| Compiler | [Emscripten](https://emscripten.org/) (emcc) → WebAssembly |
|
|
||||||
| Graphics | [Sokol](https://github.com/floooh/sokol) with WebGPU backend |
|
|
||||||
| UI | [Dear ImGui](https://github.com/ocornut/imgui) via [cimgui](https://github.com/cimgui/cimgui) |
|
|
||||||
| Math | [cglm](https://github.com/recp/cglm) |
|
|
||||||
| Shaders | WGSL (compiled to C headers via `xxd -i`) |
|
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
@@ -42,66 +17,6 @@ make debug
|
|||||||
|
|
||||||
Output is `app.html`, served by Emscripten's built-in web server or any static server.
|
Output is `app.html`, served by Emscripten's built-in web server or any static server.
|
||||||
|
|
||||||
## Project structure
|
|
||||||
|
|
||||||
```
|
|
||||||
src/
|
|
||||||
main.c Entry point, sokol init, render loop, input dispatch, UI panels, debug stats
|
|
||||||
api.h Central include hub — backend defines, all library headers, ALLOC/FREE macros
|
|
||||||
types.h Shared type definitions, constants, userdata_t
|
|
||||||
camera.h Viewport state, zoom/pan, MVP matrix (via glm_ortho), screen↔world transforms
|
|
||||||
render.h Shape & overlay pipeline init/shutdown, shader definitions
|
|
||||||
shape.h Shape geometry types, procedural generation, Bezier editing, vertex hash grouping, instanced buffer pool
|
|
||||||
spatial.h Spatial hash grid with linear probing, AABB queries, viewport culling
|
|
||||||
history.h Undo/redo stack — property-level tracking, vertex snapshots, batch operations
|
|
||||||
interact.h Selection AABB, group recursive helpers, resize handle hit-test, group rebuild
|
|
||||||
overlay.h Selection overlay geometry, rotate/corner handles, edit-mode anchor & handle visualization
|
|
||||||
draw.h Draw dispatch — frustum culling, instance map sorting, instanced draw calls
|
|
||||||
input.h Mouse/keyboard event handlers — select, move, rotate, resize, pen, edit mode, clipboard
|
|
||||||
ui_panels.h ImGui panels — toolbar, shape list tree, properties, debug log
|
|
||||||
util.h Stripe-based vector_t (dynamic array)
|
|
||||||
rand.h Xorshift32 PRNG
|
|
||||||
shaders/ WGSL shader sources (sprite, shape, overlay)
|
|
||||||
generated/ xxd-generated C headers from shaders
|
|
||||||
lib/
|
|
||||||
sokol/ Sokol single-file headers (gfx, app, glue, log, memtrack)
|
|
||||||
imgui/ Dear ImGui + cimgui
|
|
||||||
cglm/ C linear math library
|
|
||||||
util/ Sokol utility headers
|
|
||||||
```
|
|
||||||
|
|
||||||
## Architecture notes
|
|
||||||
|
|
||||||
### Instanced rendering with vertex hash grouping
|
|
||||||
|
|
||||||
Shapes share vertex buffers when their local-space geometry is identical. Each shape stores a 64-bit FNV-1a hash of its vertex data. The geometry pool groups shapes by `(num_elements, vertex_hash)` — not just vertex count. This means:
|
|
||||||
|
|
||||||
- **Parametric shapes** (circles, stars, rectangles from fixed formulas): all instances of the same type naturally produce the same hash, so they share one vertex buffer regardless of count.
|
|
||||||
- **Freeform paths** (pen tool): each path gets a unique hash, guaranteeing its own vertex buffer and preventing geometry corruption.
|
|
||||||
- **Bezier edits**: `shape_regenerate_from_ctrl` updates the hash and modifies the group buffer in-place via `sg_update_buffer` rather than destroying/recreating it.
|
|
||||||
|
|
||||||
The previous implementation grouped only by vertex count, which caused pen-drawn paths with matching counts to silently share the wrong geometry.
|
|
||||||
|
|
||||||
### Lazy group index rebuild
|
|
||||||
|
|
||||||
The `g_group_by_id` lookup array is rebuilt lazily. Operations that modify groups set a `g_group_index_dirty` flag; the actual rebuild happens on the first `find_group()` call afterward. This avoids redundant rebuilds when multiple group operations occur within the same frame (e.g., undo then redo, or group then ungroup).
|
|
||||||
|
|
||||||
### Hover state optimization
|
|
||||||
|
|
||||||
`handle_hover` (called every frame) tracks the previous set of highlighted shapes (up to 64) and only toggles state on shapes entering or leaving the highlight set. The O(n) full-array sweep is only used as a fallback when the highlight set exceeds 64 shapes.
|
|
||||||
|
|
||||||
### Pool rebuild granularity
|
|
||||||
|
|
||||||
When the geometry pool rebuilds, existing group vertex buffers whose `(num_elements, vertex_hash)` key still exists are preserved rather than destroyed and recreated. Only new keys trigger buffer creation, and only orphaned keys trigger destruction. The shape data SSBO is also preserved when its size hasn't changed.
|
|
||||||
|
|
||||||
### Spatial grid memory reuse
|
|
||||||
|
|
||||||
The spatial hash grid retains per-slot entry arrays across rebuilds. Only slots whose shape count has grown beyond their current capacity trigger a reallocation.
|
|
||||||
|
|
||||||
### Log deduplication
|
|
||||||
|
|
||||||
The debug log ring buffer uses a 64-bit message hash for fast deduplication of warnings and errors (levels 0-2). Debug-level messages (level 3) skip dedup entirely to avoid the linear scan cost when verbose logging is active.
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT
|
MIT
|
||||||
|
|||||||
@@ -59,14 +59,14 @@ else
|
|||||||
echo " > cglm already present"
|
echo " > cglm already present"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 5. sokol_gp.h
|
# 5. stb_ds.h
|
||||||
if [ ! -f "$LIB_DIR/sokol/sokol_gp.h" ]; then
|
if [ ! -f "$LIB_DIR/util/stb_ds.h" ]; then
|
||||||
echo " > Fetching cglm..."
|
echo " > Fetching STB..."
|
||||||
git clone --depth 1 https://github.com/edubart/sokol_gp.git "$LIB_DIR/sokol_gp_tmp"
|
git clone --depth 1 https://github.com/nothings/stb.git "$LIB_DIR/stb_tmp"
|
||||||
cp -r "$LIB_DIR/sokol_gp_tmp/sokol_gp.h" "$LIB_DIR/sokol/sokol_gp.h"
|
cp -r "$LIB_DIR/stb_tmp/stb_ds.h" "$LIB_DIR/util/stb_ds.h"
|
||||||
rm -rf "$LIB_DIR/sokol_gp_tmp"
|
rm -rf "$LIB_DIR/stb_tmp"
|
||||||
else
|
else
|
||||||
echo " > sokol_gp.h already present"
|
echo " > stb_ds.h already present"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "=== Done ==="
|
echo "=== Done ==="
|
||||||
|
|||||||
7
makefile
7
makefile
@@ -31,8 +31,8 @@ EMCC_FLAGS = --use-port=emdawnwebgpu \
|
|||||||
-sALLOW_MEMORY_GROWTH \
|
-sALLOW_MEMORY_GROWTH \
|
||||||
-msimd128 \
|
-msimd128 \
|
||||||
-sFILESYSTEM=0 \
|
-sFILESYSTEM=0 \
|
||||||
-flto \
|
-sMALLOC=emmalloc \
|
||||||
-Rpass=loop-vectorize
|
-flto
|
||||||
|
|
||||||
# Shell template
|
# Shell template
|
||||||
SHELL_FILE = shell.html
|
SHELL_FILE = shell.html
|
||||||
@@ -46,6 +46,7 @@ $(TARGET): $(SHADER_HEADERS) $(C_SOURCES) $(IMGUI_SOURCES) $(CGLM_SOURCES) $(SHE
|
|||||||
-o $(TARGET) \
|
-o $(TARGET) \
|
||||||
$(EMCC_FLAGS) \
|
$(EMCC_FLAGS) \
|
||||||
-O3 \
|
-O3 \
|
||||||
|
--closure 1 \
|
||||||
-I$(LIB_DIR)/sokol \
|
-I$(LIB_DIR)/sokol \
|
||||||
-I$(LIB_DIR)/imgui \
|
-I$(LIB_DIR)/imgui \
|
||||||
-I$(LIB_DIR)/imgui/imgui \
|
-I$(LIB_DIR)/imgui/imgui \
|
||||||
@@ -66,7 +67,7 @@ debug: $(FETCH) $(SHADER_HEADERS) $(C_SOURCES) $(IMGUI_SOURCES) $(CGLM_SOURCES)
|
|||||||
$(CC) $(C_SOURCES) $(IMGUI_SOURCES) $(CGLM_SOURCES) \
|
$(CC) $(C_SOURCES) $(IMGUI_SOURCES) $(CGLM_SOURCES) \
|
||||||
-o $(TARGET) \
|
-o $(TARGET) \
|
||||||
$(EMCC_FLAGS) \
|
$(EMCC_FLAGS) \
|
||||||
-g --profiling-funcs -gsource-map=inline \
|
-g3 --profiling-funcs -gsource-map \
|
||||||
-sASSERTIONS \
|
-sASSERTIONS \
|
||||||
-I$(LIB_DIR)/sokol \
|
-I$(LIB_DIR)/sokol \
|
||||||
-I$(LIB_DIR)/imgui \
|
-I$(LIB_DIR)/imgui \
|
||||||
|
|||||||
57
src/api.h
57
src/api.h
@@ -1,25 +1,58 @@
|
|||||||
#ifndef API_DEFINITION
|
#ifndef API_DEFINITION
|
||||||
#define API_DEFINITION
|
#define API_DEFINITION
|
||||||
|
|
||||||
#define CIMGUI_DEFINE_ENUMS_AND_STRUCTS
|
|
||||||
|
|
||||||
#define SOKOL_IMPL
|
|
||||||
#define SOKOL_WGPU
|
|
||||||
#define SOKOL_IMGUI_IMPL
|
|
||||||
|
|
||||||
#include "sokol_gfx.h"
|
|
||||||
#include "sokol_app.h"
|
|
||||||
#include "sokol_glue.h"
|
|
||||||
#include "cimgui.h"
|
|
||||||
#include "sokol_imgui.h"
|
|
||||||
|
|
||||||
#include <emscripten.h>
|
#include <emscripten.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
|
||||||
|
#include <emscripten/emmalloc.h>
|
||||||
|
|
||||||
|
#define CIMGUI_DEFINE_ENUMS_AND_STRUCTS
|
||||||
|
|
||||||
|
static void log_fn(const char* tag, uint32_t log_level, uint32_t log_item_id, const char* message_or_null, uint32_t line_nr, const char* filename_or_null, void* user_data)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "[%s - %s]: (%d) %s \tat %s:%d\r\n", tag, log_level == 0 ? "FATAL" : log_level == 1 ? "ERROR" : log_level == 2 ? "WARNING" : "INFO", log_item_id, message_or_null, filename_or_null, line_nr);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define SOKOL_ASSERT(x) ((void)((x) || (log_fn("ASSERT", 1, 1, "Assertion failed", __LINE__, __FILE__, NULL),0)))
|
||||||
|
|
||||||
|
#define SOKOL_IMPL
|
||||||
|
#define SOKOL_WGPU
|
||||||
|
#define SOKOL_IMGUI_IMPL
|
||||||
|
|
||||||
|
#define STB_DS_IMPLEMENTATION
|
||||||
|
#define STBDS_NO_SHORT_NAMES
|
||||||
|
|
||||||
|
#define STBDS_REALLOC(context,ptr,size) emmalloc_realloc(ptr, size)
|
||||||
|
#define STBDS_FREE(context,ptr) emmalloc_free(ptr)
|
||||||
|
|
||||||
|
#define COMPUTE_VIEWIDX_segments 0
|
||||||
|
#define COMPUTE_VIEWIDX_shapes 1
|
||||||
|
#define COMPUTE_VIEWIDX_circles 2
|
||||||
|
#define COMPUTE_VIEWIDX_tiles 3
|
||||||
|
#define COMPUTE_VIEWIDX_indices 4
|
||||||
|
#define COMPUTE_VIEWIDX_SDF 5
|
||||||
|
|
||||||
|
#define DISPLAY_VIEWIDX_SDF 0
|
||||||
|
#define DISPLAY_VIEWIDX_Sampler 1
|
||||||
|
|
||||||
|
static float clampf(float x, float a, float b)
|
||||||
|
{
|
||||||
|
return x < a ? a : (x > b ? b : x);
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "sokol_gfx.h"
|
||||||
|
#include "sokol_app.h"
|
||||||
|
#include "sokol_glue.h"
|
||||||
|
#include "cimgui.h"
|
||||||
|
#include "sokol_imgui.h"
|
||||||
|
#include "stb_ds.h"
|
||||||
|
|
||||||
#include "shape.h"
|
#include "shape.h"
|
||||||
|
//#include "cache.h"
|
||||||
|
#include "pool.h"
|
||||||
#include "generated/compute.h"
|
#include "generated/compute.h"
|
||||||
#include "generated/display.h"
|
#include "generated/display.h"
|
||||||
|
|
||||||
|
|||||||
107
src/cache.h
Normal file
107
src/cache.h
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
#include "api.h"
|
||||||
|
|
||||||
|
tile_slot_t* cache_evict(scene_t* s)
|
||||||
|
{
|
||||||
|
tile_slot_t* best = NULL;
|
||||||
|
uint64_t oldest = UINT64_MAX;
|
||||||
|
|
||||||
|
for(uint32_t i = 0; i < TILE_LAYERS; i++)
|
||||||
|
{
|
||||||
|
tile_slot_t* slot = &s->cache.slots[i];
|
||||||
|
if(slot->key.lod == UINT32_MAX) continue; // LOD == UINT32_MAX means the slot is free.
|
||||||
|
|
||||||
|
//Found a better candidate
|
||||||
|
if(slot->last_used < oldest)
|
||||||
|
{
|
||||||
|
oldest = slot->last_used;
|
||||||
|
best = slot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(best == NULL)
|
||||||
|
return best;
|
||||||
|
|
||||||
|
stbds_hmdel(s->cache.map, best->key);
|
||||||
|
|
||||||
|
s->cache.free_layers[s->cache.free_count] = best->layer;
|
||||||
|
s->cache.free_count++;
|
||||||
|
s->cache.slots[best->layer].key.lod = UINT32_MAX; //Mark the slot as free for the evict scan using lod == UINT32_MAX
|
||||||
|
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
uint32_t cache_allocate(scene_t* s)
|
||||||
|
{
|
||||||
|
assert(s->cache.free_count > 0);
|
||||||
|
s->cache.free_count--;
|
||||||
|
uint32_t layer = s->cache.free_layers[s->cache.free_count];
|
||||||
|
|
||||||
|
return layer;
|
||||||
|
}
|
||||||
|
tile_slot_t* cache_search(scene_t* s, tile_key_t* key, uint64_t frame_count)
|
||||||
|
{
|
||||||
|
uint32_t index = stbds_hmgeti(s->cache.map, *key);
|
||||||
|
|
||||||
|
if(index >= 0)
|
||||||
|
{
|
||||||
|
uint32_t layer = s->cache.map[index].value;
|
||||||
|
tile_slot_t* slot = &s->cache.slots[layer];
|
||||||
|
slot->last_used = frame_count;
|
||||||
|
return slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evict the least recently used (LRU) slot
|
||||||
|
if(stbds_hmlen(s->cache.map) >= TILE_LAYERS)
|
||||||
|
{
|
||||||
|
tile_slot_t* evict = cache_evict(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate a free layer
|
||||||
|
uint32_t layer = cache_allocate(s);
|
||||||
|
tile_slot_t* slot = &s->cache.slots[layer];
|
||||||
|
slot->key.lod = key->lod; slot->key.tx = key->tx; slot->key.ty = key->ty;
|
||||||
|
slot->layer = layer;
|
||||||
|
slot->state = TILE_STATE_DIRTY;
|
||||||
|
slot->last_used = frame_count;
|
||||||
|
|
||||||
|
stbds_hmput(s->cache.map, *key, layer);
|
||||||
|
return slot;
|
||||||
|
}
|
||||||
|
// Works in 3 steps, first we select every required tiles and store the mising one from the cache
|
||||||
|
// Then we evict the LRU cache until we have enough space for the missing tiles
|
||||||
|
// Finally we allocate the missing tiles
|
||||||
|
static int cache_query(scene_t* s, uint32_t lod, box_t* box, tile_key_t** buffer)
|
||||||
|
{
|
||||||
|
uint64_t frame = sapp_frame_count();
|
||||||
|
tile_key_t tile_key = { lod, 0, 0 };
|
||||||
|
|
||||||
|
for(uint32_t ty = box->min_y; ty < box->max_y; ty++)
|
||||||
|
{
|
||||||
|
tile_key.ty = ty;
|
||||||
|
for(uint32_t tx = box->min_x; tx < box->max_x; tx++)
|
||||||
|
{
|
||||||
|
tile_key.tx = tx;
|
||||||
|
stbds_hmget(s->cache.map, tile_key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static int cache_cull(scene_t* s, uint32_t tile_count)
|
||||||
|
{
|
||||||
|
uint32_t count = 0;
|
||||||
|
for(uint32_t i = 0; i < tile_count; i++)
|
||||||
|
{
|
||||||
|
tile_slot_t slot = s->cache.slots[i];
|
||||||
|
tile_task_t* tile = &s->cache.tiles[i];
|
||||||
|
tile->bounds.min_x = slot.key.tx * s->LODs[slot.key.lod].texel_size;
|
||||||
|
tile->bounds.min_y = slot.key.ty * s->LODs[slot.key.lod].texel_size;
|
||||||
|
tile->bounds.max_x = (slot.key.tx + 1) * s->LODs[slot.key.lod].texel_size;
|
||||||
|
tile->bounds.max_y = (slot.key.ty + 1) * s->LODs[slot.key.lod].texel_size;
|
||||||
|
tile->layer = slot.layer;
|
||||||
|
|
||||||
|
uint32_t start = count;
|
||||||
|
for(uint32_t j = 0; j < s->num_shapes; j++)
|
||||||
|
{
|
||||||
|
if(BOX_INTERSECTS(tile->bounds, s->shapes[j].aabb)) s->cache.indices[count++] = j;
|
||||||
|
}
|
||||||
|
tile->offset = start; tile->count = count - start;
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,53 +1,47 @@
|
|||||||
unsigned char src_shaders_display_wgsl[] = {
|
unsigned char src_shaders_display_wgsl[] = {
|
||||||
0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x55, 0x6e, 0x69, 0x66, 0x6f,
|
0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x55, 0x6e, 0x69, 0x66, 0x6f,
|
||||||
0x72, 0x6d, 0x73, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70,
|
0x72, 0x6d, 0x73, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74,
|
||||||
0x61, 0x6e, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x32, 0x66, 0x2c, 0x0d, 0x0a,
|
0x69, 0x6d, 0x65, 0x3a, 0x20, 0x75, 0x33, 0x32, 0x2c, 0x0d, 0x0a, 0x20,
|
||||||
0x20, 0x20, 0x20, 0x20, 0x7a, 0x6f, 0x6f, 0x6d, 0x3a, 0x20, 0x66, 0x33,
|
0x20, 0x20, 0x20, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x3a, 0x20, 0x75, 0x33,
|
||||||
0x32, 0x2c, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63,
|
0x32, 0x2c, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x76, 0x70, 0x3a,
|
||||||
0x74, 0x20, 0x54, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73,
|
0x20, 0x6d, 0x61, 0x74, 0x34, 0x78, 0x34, 0x66, 0x2c, 0x0d, 0x0a, 0x7d,
|
||||||
0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x64, 0x3a,
|
0x0d, 0x0a, 0x0d, 0x0a, 0x40, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x28, 0x30,
|
||||||
0x20, 0x75, 0x33, 0x32, 0x2c, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74,
|
0x29, 0x20, 0x40, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x28, 0x30,
|
||||||
0x69, 0x6c, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x66, 0x33,
|
0x29, 0x20, 0x76, 0x61, 0x72, 0x3c, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72,
|
||||||
0x32, 0x2c, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x77, 0x6f, 0x72, 0x6c,
|
0x6d, 0x3e, 0x20, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x3a,
|
||||||
0x64, 0x5f, 0x6d, 0x69, 0x6e, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x32, 0x66,
|
0x20, 0x55, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x3b, 0x0d, 0x0a,
|
||||||
0x2c, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64,
|
0x0d, 0x0a, 0x40, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x28, 0x31, 0x29, 0x20,
|
||||||
0x5f, 0x6d, 0x61, 0x78, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x32, 0x66, 0x2c,
|
0x40, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x28, 0x30, 0x29, 0x20,
|
||||||
0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x40, 0x67, 0x72, 0x6f, 0x75,
|
0x76, 0x61, 0x72, 0x20, 0x73, 0x64, 0x66, 0x20, 0x3a, 0x20, 0x74, 0x65,
|
||||||
0x70, 0x28, 0x30, 0x29, 0x20, 0x40, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e,
|
0x78, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x32, 0x64, 0x3c, 0x66, 0x33, 0x32,
|
||||||
0x67, 0x28, 0x30, 0x29, 0x20, 0x76, 0x61, 0x72, 0x3c, 0x75, 0x6e, 0x69,
|
0x3e, 0x3b, 0x0d, 0x0a, 0x40, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x28, 0x31,
|
||||||
0x66, 0x6f, 0x72, 0x6d, 0x3e, 0x20, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72,
|
0x29, 0x20, 0x40, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x28, 0x31,
|
||||||
0x6d, 0x73, 0x3a, 0x20, 0x55, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x73,
|
0x29, 0x20, 0x76, 0x61, 0x72, 0x20, 0x73, 0x64, 0x66, 0x5f, 0x73, 0x61,
|
||||||
0x3b, 0x0d, 0x0a, 0x40, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x28, 0x30, 0x29,
|
0x6d, 0x70, 0x6c, 0x65, 0x72, 0x20, 0x3a, 0x20, 0x73, 0x61, 0x6d, 0x70,
|
||||||
0x20, 0x40, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x28, 0x31, 0x29,
|
0x6c, 0x65, 0x72, 0x3b, 0x0d, 0x0a, 0x0d, 0x0a, 0x40, 0x76, 0x65, 0x72,
|
||||||
0x20, 0x76, 0x61, 0x72, 0x3c, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d,
|
0x74, 0x65, 0x78, 0x20, 0x66, 0x6e, 0x20, 0x76, 0x73, 0x5f, 0x6d, 0x61,
|
||||||
0x3e, 0x20, 0x74, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d,
|
0x69, 0x6e, 0x28, 0x40, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
||||||
0x73, 0x3a, 0x20, 0x54, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d,
|
0x28, 0x30, 0x29, 0x20, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e,
|
||||||
0x73, 0x3b, 0x0d, 0x0a, 0x0d, 0x0a, 0x40, 0x76, 0x65, 0x72, 0x74, 0x65,
|
0x3a, 0x20, 0x76, 0x65, 0x63, 0x32, 0x66, 0x29, 0x20, 0x2d, 0x3e, 0x20,
|
||||||
0x78, 0x20, 0x66, 0x6e, 0x20, 0x76, 0x73, 0x5f, 0x6d, 0x61, 0x69, 0x6e,
|
0x40, 0x62, 0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, 0x28, 0x70, 0x6f, 0x73,
|
||||||
0x28, 0x40, 0x62, 0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, 0x28, 0x76, 0x65,
|
0x69, 0x74, 0x69, 0x6f, 0x6e, 0x29, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66,
|
||||||
0x72, 0x74, 0x65, 0x78, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x29, 0x20,
|
0x0d, 0x0a, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74,
|
||||||
0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78,
|
0x75, 0x72, 0x6e, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x28, 0x70, 0x6f,
|
||||||
0x20, 0x3a, 0x20, 0x75, 0x33, 0x32, 0x29, 0x20, 0x2d, 0x3e, 0x20, 0x40,
|
0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c,
|
||||||
0x62, 0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, 0x28, 0x70, 0x6f, 0x73, 0x69,
|
0x20, 0x31, 0x2e, 0x30, 0x29, 0x20, 0x2a, 0x20, 0x75, 0x6e, 0x69, 0x66,
|
||||||
0x74, 0x69, 0x6f, 0x6e, 0x29, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x20,
|
0x6f, 0x72, 0x6d, 0x73, 0x2e, 0x6d, 0x76, 0x70, 0x3b, 0x0d, 0x0a, 0x7d,
|
||||||
0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x20, 0x70,
|
|
||||||
0x6f, 0x73, 0x20, 0x3d, 0x20, 0x61, 0x72, 0x72, 0x61, 0x79, 0x28, 0x0d,
|
|
||||||
0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x32, 0x28, 0x20, 0x30,
|
|
||||||
0x2e, 0x30, 0x2c, 0x20, 0x20, 0x30, 0x2e, 0x35, 0x29, 0x2c, 0x0d, 0x0a,
|
|
||||||
0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x32, 0x28, 0x2d, 0x30, 0x2e,
|
|
||||||
0x35, 0x2c, 0x20, 0x2d, 0x30, 0x2e, 0x35, 0x29, 0x2c, 0x0d, 0x0a, 0x20,
|
|
||||||
0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x32, 0x28, 0x20, 0x30, 0x2e, 0x35,
|
|
||||||
0x2c, 0x20, 0x2d, 0x30, 0x2e, 0x35, 0x29, 0x0d, 0x0a, 0x20, 0x20, 0x29,
|
|
||||||
0x3b, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72,
|
|
||||||
0x6e, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x28, 0x70, 0x6f, 0x73, 0x5b,
|
|
||||||
0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78,
|
|
||||||
0x5d, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x31, 0x29, 0x3b, 0x0d, 0x0a, 0x7d,
|
|
||||||
0x0d, 0x0a, 0x0d, 0x0a, 0x40, 0x66, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e,
|
0x0d, 0x0a, 0x0d, 0x0a, 0x40, 0x66, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e,
|
||||||
0x74, 0x20, 0x66, 0x6e, 0x20, 0x66, 0x73, 0x5f, 0x6d, 0x61, 0x69, 0x6e,
|
0x74, 0x20, 0x66, 0x6e, 0x20, 0x66, 0x73, 0x5f, 0x6d, 0x61, 0x69, 0x6e,
|
||||||
0x28, 0x29, 0x20, 0x2d, 0x3e, 0x20, 0x40, 0x6c, 0x6f, 0x63, 0x61, 0x74,
|
0x28, 0x40, 0x62, 0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, 0x28, 0x70, 0x6f,
|
||||||
0x69, 0x6f, 0x6e, 0x28, 0x30, 0x29, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66,
|
0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x29, 0x20, 0x70, 0x6f, 0x73, 0x69,
|
||||||
0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e,
|
0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x29,
|
||||||
0x20, 0x76, 0x65, 0x63, 0x34, 0x28, 0x31, 0x2c, 0x20, 0x30, 0x2c, 0x20,
|
0x20, 0x2d, 0x3e, 0x20, 0x40, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f,
|
||||||
0x30, 0x2c, 0x20, 0x31, 0x29, 0x3b, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a
|
0x6e, 0x28, 0x30, 0x29, 0x20, 0x76, 0x65, 0x63, 0x34, 0x66, 0x0d, 0x0a,
|
||||||
|
0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72,
|
||||||
|
0x6e, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x53, 0x61, 0x6d,
|
||||||
|
0x70, 0x6c, 0x65, 0x28, 0x73, 0x64, 0x66, 0x2c, 0x20, 0x73, 0x64, 0x66,
|
||||||
|
0x5f, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x2c, 0x20, 0x70, 0x6f,
|
||||||
|
0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x78, 0x79, 0x29, 0x3b, 0x0d,
|
||||||
|
0x0a, 0x7d
|
||||||
};
|
};
|
||||||
unsigned int src_shaders_display_wgsl_len = 599;
|
unsigned int src_shaders_display_wgsl_len = 518;
|
||||||
|
|||||||
324
src/main.c
324
src/main.c
@@ -1,11 +1,5 @@
|
|||||||
#include "api.h"
|
#include "api.h"
|
||||||
|
|
||||||
#define COMPUTE_VIEWIDX_segments 0
|
|
||||||
#define COMPUTE_VIEWIDX_shapes 1
|
|
||||||
#define COMPUTE_VIEWIDX_SDF 2
|
|
||||||
|
|
||||||
#define DISPLAY_VIEWIDX_SDF 0
|
|
||||||
|
|
||||||
typedef struct display_uniforms {
|
typedef struct display_uniforms {
|
||||||
uint32_t frame;
|
uint32_t frame;
|
||||||
uint32_t time;
|
uint32_t time;
|
||||||
@@ -21,19 +15,10 @@ typedef struct renderer_t {
|
|||||||
display_uniforms uniforms;
|
display_uniforms uniforms;
|
||||||
} renderer_t;
|
} renderer_t;
|
||||||
|
|
||||||
typedef struct compute_uniforms {
|
|
||||||
uint32_t frame;
|
|
||||||
uint32_t time;
|
|
||||||
|
|
||||||
float pan_x, pan_y;
|
|
||||||
float zoom, rotation;
|
|
||||||
} compute_uniforms;
|
|
||||||
|
|
||||||
typedef struct compute_t {
|
typedef struct compute_t {
|
||||||
sg_pipeline pipeline;
|
sg_pipeline pipeline;
|
||||||
sg_bindings bindings;
|
sg_bindings bindings;
|
||||||
|
sg_pass pass;
|
||||||
compute_uniforms uniforms
|
|
||||||
} compute_t;
|
} compute_t;
|
||||||
|
|
||||||
typedef struct userdata_t {
|
typedef struct userdata_t {
|
||||||
@@ -46,16 +31,18 @@ typedef struct userdata_t {
|
|||||||
float zoom, rotation;
|
float zoom, rotation;
|
||||||
} userdata_t;
|
} userdata_t;
|
||||||
|
|
||||||
static void log_fn(const char* tag, uint32_t log_level, uint32_t log_item_id, const char* message_or_null, uint32_t line_nr, const char* filename_or_null, void* user_data)
|
static void* _malloc(size_t size, void* userdata)
|
||||||
{
|
{
|
||||||
if(log_level < 2) return;
|
(void) userdata;
|
||||||
fprintf(stderr, "[%s - %s]: (%d) %s \nat %s:%d", tag, log_level == 0 ? "FATAL" : log_level == 1 ? "ERROR" : "WARNING", log_item_id, message_or_null, filename_or_null, line_nr);
|
return emmalloc_malloc(size);
|
||||||
|
}
|
||||||
|
static void _free(void* ptr, void* userdata)
|
||||||
|
{
|
||||||
|
(void) userdata;
|
||||||
|
emmalloc_free(ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
//#define _SG_LOG_ITEMS \
|
static void refresh_SDF(userdata_t* ud)
|
||||||
_SG_LOGITEM_XMACRO(COMPUTE_INVALID_BUFFER, "invalid buffer");
|
|
||||||
|
|
||||||
static void draw_scene(userdata_t* ud)
|
|
||||||
{
|
{
|
||||||
//Get visible tiles
|
//Get visible tiles
|
||||||
float view_world_w = sapp_widthf() * ud->zoom;
|
float view_world_w = sapp_widthf() * ud->zoom;
|
||||||
@@ -67,51 +54,112 @@ static void draw_scene(userdata_t* ud)
|
|||||||
ud->pan_y + view_world_h * 0.5f,
|
ud->pan_y + view_world_h * 0.5f,
|
||||||
};
|
};
|
||||||
|
|
||||||
sg_buffer seg_buffer = sg_query_view_buffer(ud->compute.bindings.views[0]);
|
bool dirty = false;
|
||||||
//_SG_VALIDATE(seg_buffer.id == SG_INVALID_ID, COMPUTE_INVALID_BUFFER);
|
if(ud->scene.segment_dirty)
|
||||||
sg_update_buffer(seg_buffer, &(sg_range) { .ptr = ud->scene.segments, .size = sizeof(segment_t) * ud->scene.num_segments });
|
{
|
||||||
|
pool_view_grow(ud->compute.bindings.views[COMPUTE_VIEWIDX_segments], sizeof(segment_t), ud->scene.shapes.num_segments, ud->scene.shapes.segments);
|
||||||
|
ud->scene.segment_dirty = false;
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
sg_buffer shape_buffer = sg_query_view_buffer(ud->compute.bindings.views[1]);
|
if(ud->scene.shape_dirty)
|
||||||
//_SG_VALIDATE(shape_buffer.id == SG_INVALID_ID, COMPUTE_INVALID_BUFFER);
|
{
|
||||||
sg_update_buffer(shape_buffer, &(sg_range) { .ptr = ud->scene.shapes, .size = sizeof(shape_meta_t) * ud->scene.num_shapes });
|
pool_view_grow(ud->compute.bindings.views[COMPUTE_VIEWIDX_shapes], sizeof(shape_meta_t), ud->scene.shapes.num_shapes, ud->scene.shapes.shapes);
|
||||||
|
ud->scene.shape_dirty = false;
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
sg_begin_pass(&(sg_pass){ .compute = true, .label = "Compute Pass" });
|
if(ud->scene.circle_dirty)
|
||||||
|
{
|
||||||
|
pool_view_grow(ud->compute.bindings.views[COMPUTE_VIEWIDX_circles], sizeof(circle_t), ud->scene.circles.num_circles, ud->scene.circles.circles);
|
||||||
|
ud->scene.circle_dirty = false;
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(dirty)
|
||||||
|
{
|
||||||
|
uint32_t tile_count = scene_process_tiles(&ud->scene, ud->pan_x, ud->pan_y, ud->zoom, &ud->compute.bindings);
|
||||||
|
|
||||||
|
if(tile_count > 0)
|
||||||
|
{
|
||||||
|
pool_view_grow(ud->compute.bindings.views[COMPUTE_VIEWIDX_tiles], sizeof(tile_task_t), tile_count, ud->scene.cache.tiles);
|
||||||
|
pool_view_grow(ud->compute.bindings.views[COMPUTE_VIEWIDX_indices], sizeof(uint32_t), ud->scene.cache.num_indices, ud->scene.cache.indices);
|
||||||
|
|
||||||
|
sg_begin_pass(&ud->compute.pass);
|
||||||
sg_apply_pipeline(ud->compute.pipeline);
|
sg_apply_pipeline(ud->compute.pipeline);
|
||||||
sg_apply_uniforms(0, &SG_RANGE(ud->renderer.uniforms));
|
|
||||||
sg_apply_uniforms(1, &SG_RANGE(ud->scene.LODs));
|
|
||||||
sg_apply_bindings(&ud->compute.bindings);
|
sg_apply_bindings(&ud->compute.bindings);
|
||||||
|
|
||||||
sg_dispatch(TILE_SIZE / 8, TILE_SIZE / 8, 1);
|
sg_dispatch(tile_count * 16, 16, 1);
|
||||||
|
|
||||||
sg_end_pass();
|
sg_end_pass();
|
||||||
}
|
}
|
||||||
|
dirty = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static sg_pass pass = (sg_pass){
|
||||||
|
.label = "Render pass",
|
||||||
|
};
|
||||||
|
static simgui_frame_desc_t imgui_frame = {0};
|
||||||
|
|
||||||
|
static void compute_mvp(userdata_t *ud)
|
||||||
|
{
|
||||||
|
const float w = sapp_widthf();
|
||||||
|
const float h = sapp_heightf();
|
||||||
|
const float z = ud->zoom;
|
||||||
|
const float px = ud->pan_x;
|
||||||
|
const float py = ud->pan_y;
|
||||||
|
float* mvp = ud->renderer.uniforms.mvp;
|
||||||
|
|
||||||
|
mvp[0] = (2.0f / w) * z;
|
||||||
|
mvp[1] = 0.0f;
|
||||||
|
mvp[2] = 0.0f;
|
||||||
|
mvp[3] = (2.0f / w) * px;
|
||||||
|
|
||||||
|
mvp[4] = 0.0f;
|
||||||
|
mvp[5] = (2.0f / h) * z;
|
||||||
|
mvp[6] = 0.0f;
|
||||||
|
mvp[7] = (2.0f / h) * py;
|
||||||
|
|
||||||
|
mvp[8] = 0.0f;
|
||||||
|
mvp[9] = 0.0f;
|
||||||
|
mvp[10] = 0.0f;
|
||||||
|
mvp[11] = 0.0f;
|
||||||
|
|
||||||
|
mvp[12] = 0.0f;
|
||||||
|
mvp[13] = 0.0f;
|
||||||
|
mvp[14] = 0.0f;
|
||||||
|
mvp[15] = 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
static void frame(void* _userdata)
|
static void frame(void* _userdata)
|
||||||
{
|
{
|
||||||
userdata_t* ud = (userdata_t*) _userdata;
|
userdata_t* ud = (userdata_t*) _userdata;
|
||||||
|
|
||||||
ud->renderer.uniforms.frame = ud->compute.uniforms.frame = sapp_frame_count();
|
ud->renderer.uniforms.frame = sapp_frame_count();
|
||||||
ud->renderer.uniforms.time = ud->compute.uniforms.time += sapp_frame_duration_unfiltered();
|
ud->renderer.uniforms.time += (uint32_t) ceil(sapp_frame_duration_unfiltered() / 1000.0f);
|
||||||
|
|
||||||
draw_scene(ud);
|
refresh_SDF(ud);
|
||||||
|
|
||||||
sg_begin_pass(&(sg_pass){
|
pass.swapchain = sglue_swapchain();
|
||||||
.action = ud->renderer.clear_pass,
|
sg_begin_pass(&pass);
|
||||||
.swapchain = sglue_swapchain(),
|
|
||||||
});
|
|
||||||
|
|
||||||
simgui_new_frame(&(simgui_frame_desc_t){
|
imgui_frame.width = sapp_width(),
|
||||||
.width = sapp_width(),
|
imgui_frame.height = sapp_height(),
|
||||||
.height = sapp_height(),
|
imgui_frame.delta_time = sapp_frame_duration_unfiltered(),
|
||||||
.delta_time = sapp_frame_duration_unfiltered(),
|
imgui_frame.dpi_scale = sapp_dpi_scale(),
|
||||||
.dpi_scale = sapp_dpi_scale(),
|
simgui_new_frame(&imgui_frame);
|
||||||
});
|
|
||||||
|
|
||||||
igBegin("Framerate", (bool*) true, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
|
igBegin("Framerate", (bool*) true, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
|
||||||
igText("%.0f FPS", 1 / sapp_frame_duration_unfiltered());
|
igText("%.0f FPS", 1 / sapp_frame_duration_unfiltered());
|
||||||
igText("%.3fms", sapp_frame_duration_unfiltered() * 1000);
|
igText("%.3fms", sapp_frame_duration_unfiltered() * 1000);
|
||||||
igEnd();
|
igEnd();
|
||||||
|
|
||||||
|
sg_apply_pipeline(ud->renderer.pipeline);
|
||||||
|
sg_apply_uniforms(0, &SG_RANGE(ud->renderer.uniforms));
|
||||||
|
sg_apply_bindings(&ud->renderer.bindings);
|
||||||
|
sg_draw(0, 6, 1);
|
||||||
|
|
||||||
simgui_render();
|
simgui_render();
|
||||||
sg_end_pass();
|
sg_end_pass();
|
||||||
sg_commit();
|
sg_commit();
|
||||||
@@ -124,11 +172,20 @@ static void init(void* _userdata)
|
|||||||
sg_setup(&(sg_desc){
|
sg_setup(&(sg_desc){
|
||||||
.environment = sglue_environment(),
|
.environment = sglue_environment(),
|
||||||
.logger.func = log_fn,
|
.logger.func = log_fn,
|
||||||
|
.allocator = {
|
||||||
|
.alloc_fn = _malloc,
|
||||||
|
.free_fn = _free,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
simgui_setup(&(simgui_desc_t){
|
||||||
|
.allocator = {
|
||||||
|
.alloc_fn = _malloc,
|
||||||
|
.free_fn = _free,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
simgui_setup(&(simgui_desc_t){0});
|
|
||||||
|
|
||||||
ud->scene = (scene_t) {0};
|
ud->scene = (scene_t) {0};
|
||||||
if(!scene_init(&ud->scene, 128, 1024, (vec2) { 8192.0f, 8192.0f })) return;
|
if(!scene_init(&ud->scene, (vec2) { 8192.0f, 8192.0f })) return;
|
||||||
|
|
||||||
ud->compute = (compute_t) {
|
ud->compute = (compute_t) {
|
||||||
.pipeline = sg_make_pipeline(&(sg_pipeline_desc){
|
.pipeline = sg_make_pipeline(&(sg_pipeline_desc){
|
||||||
@@ -139,39 +196,50 @@ static void init(void* _userdata)
|
|||||||
.entry = "main",
|
.entry = "main",
|
||||||
},
|
},
|
||||||
.views = {
|
.views = {
|
||||||
[0] = {
|
[COMPUTE_VIEWIDX_segments] = {
|
||||||
.storage_buffer = {
|
.storage_buffer = {
|
||||||
.stage = SG_SHADERSTAGE_COMPUTE,
|
.stage = SG_SHADERSTAGE_COMPUTE,
|
||||||
.wgsl_group1_binding_n = 0,
|
.wgsl_group1_binding_n = COMPUTE_VIEWIDX_segments,
|
||||||
.readonly = true,
|
.readonly = true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[1] = {
|
[COMPUTE_VIEWIDX_shapes] = {
|
||||||
.storage_buffer = {
|
.storage_buffer = {
|
||||||
.stage = SG_SHADERSTAGE_COMPUTE,
|
.stage = SG_SHADERSTAGE_COMPUTE,
|
||||||
.wgsl_group1_binding_n = 1,
|
.wgsl_group1_binding_n = COMPUTE_VIEWIDX_shapes,
|
||||||
.readonly = true,
|
.readonly = true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[2] = {
|
[COMPUTE_VIEWIDX_circles] = {
|
||||||
.storage_buffer = {
|
.storage_buffer = {
|
||||||
.stage = SG_SHADERSTAGE_COMPUTE,
|
.stage = SG_SHADERSTAGE_COMPUTE,
|
||||||
.wgsl_group1_binding_n = 2,
|
.wgsl_group1_binding_n = COMPUTE_VIEWIDX_circles,
|
||||||
.readonly = false,
|
.readonly = true,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.uniform_blocks = {
|
[COMPUTE_VIEWIDX_tiles] = {
|
||||||
[0] = {
|
.storage_buffer = {
|
||||||
.size = sizeof(compute_uniforms),
|
|
||||||
.stage = SG_SHADERSTAGE_COMPUTE,
|
.stage = SG_SHADERSTAGE_COMPUTE,
|
||||||
.wgsl_group0_binding_n = 0,
|
.wgsl_group1_binding_n = COMPUTE_VIEWIDX_tiles,
|
||||||
|
.readonly = true,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[1] = {
|
[COMPUTE_VIEWIDX_indices] = {
|
||||||
.size = sizeof(tile_ID),
|
.storage_buffer = {
|
||||||
.stage = SG_SHADERSTAGE_COMPUTE,
|
.stage = SG_SHADERSTAGE_COMPUTE,
|
||||||
.wgsl_group0_binding_n = 1,
|
.wgsl_group1_binding_n = COMPUTE_VIEWIDX_indices,
|
||||||
|
.readonly = true,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
[COMPUTE_VIEWIDX_SDF] = {
|
||||||
|
.storage_image = {
|
||||||
|
.stage = SG_SHADERSTAGE_COMPUTE,
|
||||||
|
.wgsl_group1_binding_n = COMPUTE_VIEWIDX_SDF,
|
||||||
|
.access_format = SG_PIXELFORMAT_R16F,
|
||||||
|
.image_type = SG_IMAGETYPE_2D,
|
||||||
|
.writeonly = true,
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
.label = "SDF Compute Shader",
|
.label = "SDF Compute Shader",
|
||||||
}),
|
}),
|
||||||
@@ -187,7 +255,7 @@ static void init(void* _userdata)
|
|||||||
.storage_buffer = true,
|
.storage_buffer = true,
|
||||||
.stream_update = true,
|
.stream_update = true,
|
||||||
},
|
},
|
||||||
.size = sizeof(segment_t),
|
.size = sizeof(segment_t) * INITIAL_SEGMENTS_SIZE,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@@ -200,25 +268,52 @@ static void init(void* _userdata)
|
|||||||
.storage_buffer = true,
|
.storage_buffer = true,
|
||||||
.stream_update = true,
|
.stream_update = true,
|
||||||
},
|
},
|
||||||
.size = sizeof(shape_meta_t),
|
.size = sizeof(shape_meta_t) * INITIAL_SHAPE_SIZE,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
.views[COMPUTE_VIEWIDX_SDF] = sg_make_view(&(sg_view_desc) {
|
.views[COMPUTE_VIEWIDX_circles] = sg_make_view(&(sg_view_desc) {
|
||||||
.label = "SDF tiles view",
|
.label = "Circles view",
|
||||||
.storage_image = {
|
.storage_buffer = {
|
||||||
.image = sg_make_image(&(sg_image_desc) {
|
.buffer = sg_make_buffer(&(sg_buffer_desc) {
|
||||||
.height = TILE_SIZE,
|
.label = "Circles buffer",
|
||||||
.width = TILE_SIZE,
|
.usage = {
|
||||||
.label = "SDF tiles texture",
|
.storage_buffer = true,
|
||||||
.num_slices = TILE_LAYERS,
|
.stream_update = true,
|
||||||
.pixel_format = SG_PIXELFORMAT_R32F,
|
},
|
||||||
.type = SG_IMAGETYPE_ARRAY,
|
.size = sizeof(circle_t) * INITIAL_CIRCLE_SIZE,
|
||||||
.usage = { .storage_image = true },
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
}
|
.views[COMPUTE_VIEWIDX_tiles] = sg_make_view(&(sg_view_desc) {
|
||||||
|
.label = "Tiles rebuild view",
|
||||||
|
.storage_buffer = {
|
||||||
|
.buffer = sg_make_buffer(&(sg_buffer_desc) {
|
||||||
|
.label = "Tiles rebuild buffer",
|
||||||
|
.usage = {
|
||||||
|
.storage_buffer = true,
|
||||||
|
.stream_update = true,
|
||||||
|
},
|
||||||
|
.size = sizeof(tile_task_t) * 256,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
.views[COMPUTE_VIEWIDX_indices] = sg_make_view(&(sg_view_desc) {
|
||||||
|
.label = "Culling indices view",
|
||||||
|
.storage_buffer = {
|
||||||
|
.buffer = sg_make_buffer(&(sg_buffer_desc) {
|
||||||
|
.label = "Culling indices buffer",
|
||||||
|
.usage = {
|
||||||
|
.storage_buffer = true,
|
||||||
|
.stream_update = true,
|
||||||
|
},
|
||||||
|
.size = sizeof(uint32_t) * INITIAL_INDICES_SIZE,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
.views[COMPUTE_VIEWIDX_SDF] = ud->scene.texture,
|
||||||
|
},
|
||||||
|
.pass = { .compute = true, .label = "Compute Pass" }
|
||||||
};
|
};
|
||||||
|
|
||||||
const vec2 quad[4] = {
|
const vec2 quad[4] = {
|
||||||
@@ -242,14 +337,18 @@ static void init(void* _userdata)
|
|||||||
.source = (const char*) src_shaders_display_wgsl,
|
.source = (const char*) src_shaders_display_wgsl,
|
||||||
.entry = "fs_main",
|
.entry = "fs_main",
|
||||||
},
|
},
|
||||||
|
.attrs[0] = {
|
||||||
|
.base_type = SG_SHADERATTRBASETYPE_FLOAT,
|
||||||
|
},
|
||||||
.views = {
|
.views = {
|
||||||
[DISPLAY_VIEWIDX_SDF] = {
|
[DISPLAY_VIEWIDX_SDF] = {
|
||||||
.storage_buffer = {
|
.texture = {
|
||||||
|
.image_type = SG_IMAGETYPE_2D,
|
||||||
.stage = SG_SHADERSTAGE_FRAGMENT,
|
.stage = SG_SHADERSTAGE_FRAGMENT,
|
||||||
.wgsl_group1_binding_n = 1,
|
.wgsl_group1_binding_n = DISPLAY_VIEWIDX_SDF,
|
||||||
.readonly = true,
|
.sample_type = SG_IMAGESAMPLETYPE_FLOAT,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
.uniform_blocks = {
|
.uniform_blocks = {
|
||||||
[0] = {
|
[0] = {
|
||||||
@@ -257,46 +356,67 @@ static void init(void* _userdata)
|
|||||||
.stage = SG_SHADERSTAGE_VERTEX,
|
.stage = SG_SHADERSTAGE_VERTEX,
|
||||||
.wgsl_group0_binding_n = 0,
|
.wgsl_group0_binding_n = 0,
|
||||||
},
|
},
|
||||||
[1] = {
|
|
||||||
.size = sizeof(tile_ID),
|
|
||||||
.stage = SG_SHADERSTAGE_VERTEX,
|
|
||||||
.wgsl_group0_binding_n = 1,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
.label = "Display Shader",
|
.label = "Display Shader",
|
||||||
|
.samplers[DISPLAY_VIEWIDX_Sampler] = {
|
||||||
|
.sampler_type = SG_SAMPLERTYPE_FILTERING,
|
||||||
|
.stage = SG_SHADERSTAGE_FRAGMENT,
|
||||||
|
.wgsl_group1_binding_n = DISPLAY_VIEWIDX_Sampler,
|
||||||
|
},
|
||||||
|
.texture_sampler_pairs[0] = {
|
||||||
|
.sampler_slot = DISPLAY_VIEWIDX_Sampler,
|
||||||
|
.view_slot = DISPLAY_VIEWIDX_SDF,
|
||||||
|
.stage = SG_SHADERSTAGE_FRAGMENT,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
.cull_mode = SG_CULLMODE_FRONT,
|
.cull_mode = SG_CULLMODE_NONE,
|
||||||
.index_type = SG_INDEXTYPE_UINT16,
|
.index_type = SG_INDEXTYPE_UINT16,
|
||||||
.layout.attrs = {
|
.layout.attrs = {
|
||||||
[0].format = SG_VERTEXFORMAT_FLOAT2,
|
[0].format = SG_VERTEXFORMAT_FLOAT2,
|
||||||
},
|
},
|
||||||
.label = "Render pipeline",
|
.label = "Render pipeline",
|
||||||
}),
|
}),
|
||||||
.clear_pass = (sg_pass_action) {
|
|
||||||
.colors[0] = { .clear_value = { 0.0f, 0.0f, 0.0f, 1.0f }, .load_action = SG_LOADACTION_CLEAR }
|
|
||||||
},
|
|
||||||
.bindings = {
|
.bindings = {
|
||||||
.index_buffer = sg_make_buffer(&(sg_buffer_desc) {
|
.index_buffer = sg_make_buffer(&(sg_buffer_desc) {
|
||||||
.label = "Index buffer",
|
.label = "Index buffer",
|
||||||
.usage.index_buffer = true,
|
.usage.index_buffer = true,
|
||||||
.size = sizeof(uint16_t),
|
.size = sizeof(indices),
|
||||||
.data = SG_RANGE(indices),
|
.data = SG_RANGE(indices),
|
||||||
}),
|
}),
|
||||||
.vertex_buffers = sg_make_buffer(&(sg_buffer_desc) {
|
.vertex_buffers = {
|
||||||
|
[0] = sg_make_buffer(&(sg_buffer_desc) {
|
||||||
.label = "Vertex buffer",
|
.label = "Vertex buffer",
|
||||||
.usage.vertex_buffer = true,
|
.usage.vertex_buffer = true,
|
||||||
.size = sizeof(vec2),
|
.size = sizeof(quad),
|
||||||
.data = SG_RANGE(quad),
|
.data = SG_RANGE(quad),
|
||||||
}),
|
})
|
||||||
|
},
|
||||||
.views = {
|
.views = {
|
||||||
[DISPLAY_VIEWIDX_SDF] = ud->compute.bindings.views[COMPUTE_VIEWIDX_SDF]
|
[DISPLAY_VIEWIDX_SDF] = sg_make_view(&(sg_view_desc) {
|
||||||
}
|
.texture.image = sg_query_view_image(ud->scene.texture),
|
||||||
|
.label = "SDF texture view",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
.samplers = {
|
||||||
|
[DISPLAY_VIEWIDX_Sampler] = sg_make_sampler(&(sg_sampler_desc) {
|
||||||
|
.label = "SDF sampler",
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
.uniforms = {
|
||||||
|
.frame = 0,
|
||||||
|
.time = 0,
|
||||||
|
.mvp = { 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0 },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
compute_mvp(ud);
|
||||||
|
|
||||||
|
pass.action = (sg_pass_action) {
|
||||||
|
.colors[0] = { .clear_value = { 0.0f, 0.0f, 0.0f, 1.0f }, .load_action = SG_LOADACTION_CLEAR }
|
||||||
};
|
};
|
||||||
|
|
||||||
add_rectangle(&ud->scene, (vec2) { 200.0f, 100.0f }, (vec2) { 100.0f, 150.0f });
|
add_rectangle(&ud->scene, (vec2) { 200.0f, 100.0f }, (vec2) { 100.0f, 150.0f });
|
||||||
add_circle(&ud->scene, (vec2) { 400.0f, 300.0f }, 125.0f);
|
add_circle(&ud->scene, (vec2) { 400.0f, 300.0f }, 125.0f);
|
||||||
scene_add_tile(&ud->scene, (tile_ID) { .lod = 0, .tile = TILE_SIZE, .bounds = { 0.0f, 0.0f, 1024.0f, 1024.0f } });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void cleanup(void* _userdata)
|
static void cleanup(void* _userdata)
|
||||||
@@ -304,7 +424,7 @@ static void cleanup(void* _userdata)
|
|||||||
userdata_t* ud = (userdata_t*) _userdata;
|
userdata_t* ud = (userdata_t*) _userdata;
|
||||||
|
|
||||||
scene_shutdown(&ud->scene);
|
scene_shutdown(&ud->scene);
|
||||||
free(ud);
|
emmalloc_free(ud);
|
||||||
|
|
||||||
simgui_shutdown();
|
simgui_shutdown();
|
||||||
sg_shutdown();
|
sg_shutdown();
|
||||||
@@ -344,7 +464,7 @@ static void event(const sapp_event* event, void* _userdata)
|
|||||||
|
|
||||||
sapp_desc sokol_main(int argc, char* argv[])
|
sapp_desc sokol_main(int argc, char* argv[])
|
||||||
{
|
{
|
||||||
userdata_t* userdata = (userdata_t*) malloc(sizeof(userdata_t));
|
userdata_t* userdata = (userdata_t*) emmalloc_malloc(sizeof(userdata_t));
|
||||||
|
|
||||||
(void)argc;
|
(void)argc;
|
||||||
(void)argv;
|
(void)argv;
|
||||||
@@ -355,6 +475,10 @@ sapp_desc sokol_main(int argc, char* argv[])
|
|||||||
.cleanup_userdata_cb = cleanup,
|
.cleanup_userdata_cb = cleanup,
|
||||||
.event_userdata_cb = event,
|
.event_userdata_cb = event,
|
||||||
.window_title = "Sokol",
|
.window_title = "Sokol",
|
||||||
|
.allocator = {
|
||||||
|
.alloc_fn = _malloc,
|
||||||
|
.free_fn = _free,
|
||||||
|
},
|
||||||
.logger.func = log_fn,
|
.logger.func = log_fn,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
36
src/pool.h
Normal file
36
src/pool.h
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#include "api.h"
|
||||||
|
|
||||||
|
#define MAX_BUFFER_SIZE 1024 * 1024 * 1024 // 1GB
|
||||||
|
|
||||||
|
static void pool_buffer_grow(sg_buffer buffer, uint32_t stripe, uint32_t count, void* data)
|
||||||
|
{
|
||||||
|
size_t size = sg_query_buffer_size(buffer);
|
||||||
|
if(size == MAX_BUFFER_SIZE) return sg_update_buffer(buffer, &(sg_range) { data, MAX_BUFFER_SIZE });
|
||||||
|
if(size < stripe * count)
|
||||||
|
{
|
||||||
|
sg_buffer_desc desc = sg_query_buffer_desc(buffer);
|
||||||
|
while(desc.size < stripe * count) desc.size = fmaxf(MAX_BUFFER_SIZE, size * 2);
|
||||||
|
|
||||||
|
sg_uninit_buffer(buffer);
|
||||||
|
sg_init_buffer(buffer, &desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
sg_update_buffer(buffer, &(sg_range) { data, stripe * count });
|
||||||
|
}
|
||||||
|
static void pool_view_grow(sg_view view, uint32_t stripe, uint32_t count, void* data)
|
||||||
|
{
|
||||||
|
sg_buffer buffer = sg_query_view_buffer(view);
|
||||||
|
|
||||||
|
size_t size = sg_query_buffer_size(buffer);
|
||||||
|
if(size == MAX_BUFFER_SIZE) return sg_update_buffer(buffer, &(sg_range) { data, MAX_BUFFER_SIZE });
|
||||||
|
if(size < stripe * count)
|
||||||
|
{
|
||||||
|
sg_buffer_desc desc = sg_query_buffer_desc(buffer);
|
||||||
|
while(desc.size < stripe * count) desc.size = fmaxf(MAX_BUFFER_SIZE, size * 2);
|
||||||
|
|
||||||
|
sg_uninit_buffer(buffer);
|
||||||
|
sg_init_buffer(buffer, &desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
sg_update_buffer(buffer, &(sg_range) { data, stripe * count });
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
// ---------- Bindings (exactly as you defined) ----------
|
|
||||||
struct Segment {
|
struct Segment {
|
||||||
p0: vec2f,
|
p0: vec2f,
|
||||||
p1: vec2f,
|
p1: vec2f,
|
||||||
@@ -6,30 +5,31 @@ struct Segment {
|
|||||||
p3: vec2f,
|
p3: vec2f,
|
||||||
}
|
}
|
||||||
struct ShapeMeta {
|
struct ShapeMeta {
|
||||||
start_segment: u32,
|
start_segment: u32, //Offset in the segments buffer
|
||||||
segment_count: u32,
|
segment_count: u32,
|
||||||
bbox_min: vec2f,
|
bbox_min: vec2f,
|
||||||
bbox_max: vec2f,
|
bbox_max: vec2f,
|
||||||
}
|
}
|
||||||
|
struct Circle {
|
||||||
|
center: vec2f,
|
||||||
|
radius: f32,
|
||||||
|
}
|
||||||
|
struct Tile {
|
||||||
|
world_min: vec2f,
|
||||||
|
world_max: vec2f,
|
||||||
|
offset: u32,
|
||||||
|
circle_count: u32,
|
||||||
|
count: u32,
|
||||||
|
};
|
||||||
|
|
||||||
@group(1) @binding(0) var<storage> segments: array<Segment>;
|
@group(1) @binding(0) var<storage> segments: array<Segment>;
|
||||||
@group(1) @binding(1) var<storage> shapes: array<ShapeMeta>;
|
@group(1) @binding(1) var<storage> shapes: array<ShapeMeta>;
|
||||||
@group(1) @binding(2) var<storage, read_write> sdf_buffer: array<f32>;
|
@group(1) @binding(2) var<storage> circles: array<Circle>;
|
||||||
|
@group(1) @binding(3) var<storage> tiles: array<Tile>;
|
||||||
struct Uniforms {
|
@group(1) @binding(4) var<storage> indices: array<u32>;
|
||||||
pan: vec2f,
|
@group(1) @binding(5) var sdf_buffer: texture_storage_2d<r16float, write>;
|
||||||
zoom: f32,
|
|
||||||
};
|
|
||||||
struct TileParams {
|
|
||||||
lod: u32,
|
|
||||||
tile_size: f32,
|
|
||||||
world_min: vec2f,
|
|
||||||
world_max: vec2f,
|
|
||||||
};
|
|
||||||
|
|
||||||
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
|
||||||
@group(0) @binding(1) var<uniform> tile_params: TileParams;
|
|
||||||
|
|
||||||
|
override TILE_SIZE: u32 = 256u;
|
||||||
|
|
||||||
// ---------- Cubic Bézier helpers ----------
|
// ---------- Cubic Bézier helpers ----------
|
||||||
fn eval_cubic(t: f32, p0: vec2f, p1: vec2f, p2: vec2f, p3: vec2f) -> vec2f {
|
fn eval_cubic(t: f32, p0: vec2f, p1: vec2f, p2: vec2f, p3: vec2f) -> vec2f {
|
||||||
@@ -174,31 +174,48 @@ fn ray_intersections_cubic(p: vec2f, seg: Segment) -> i32 {
|
|||||||
|
|
||||||
// ---------- Main compute shader ----------
|
// ---------- Main compute shader ----------
|
||||||
@compute @workgroup_size(8, 8)
|
@compute @workgroup_size(8, 8)
|
||||||
fn main(@builtin(global_invocation_id) id: vec3<u32>) {
|
fn main(@builtin(workgroup_id) wg: vec3<u32>, // each workgroup is a tile
|
||||||
let tile_dim = vec2f(tile_params.tile_size);
|
@builtin(local_invocation_id) local: vec3<u32>) // each local invocation is a pixel
|
||||||
if id.x >= u32(tile_dim.x) || id.y >= u32(tile_dim.y) {
|
{
|
||||||
|
let tile_idx = wg.x / 16u;
|
||||||
|
let tile = tiles[tile_idx];
|
||||||
|
|
||||||
|
let px = (wg.x % 16u) * 8u + local.x;
|
||||||
|
let py = wg.y * 8u + local.y;
|
||||||
|
|
||||||
|
if (px >= TILE_SIZE || py >= TILE_SIZE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pixel center UV, then map to world space
|
let uv = (vec2f(f32(px), f32(py)) + 0.5) / f32(TILE_SIZE);
|
||||||
let uv = (vec2f(id.xy) + 0.5) / tile_dim;
|
let world = mix(tile.world_min, tile.world_max, uv);
|
||||||
let world = mix(tile_params.world_min, tile_params.world_max, uv);
|
|
||||||
|
|
||||||
var minDist: f32 = 1e20;
|
var minDist: f32 = 1e20;
|
||||||
|
var sdf: f32 = 1e20;
|
||||||
var winding: i32 = 0;
|
var winding: i32 = 0;
|
||||||
|
|
||||||
for (var s = 0u; s < arrayLength(&shapes); s++) {
|
for (var s = 0u; s < tile.count; s++) {
|
||||||
let shape = shapes[s];
|
// Use a pre-culled shape range per tile to reduce the per-pixel processing time
|
||||||
|
let index = indices[tile.offset + s];
|
||||||
|
|
||||||
// Quick bbox culling in world space
|
// If the index is lower than the circle count, it mean we are processing circles
|
||||||
let bbox_min = shape.bbox_min;
|
if(index < tile.circle_count)
|
||||||
|
{
|
||||||
|
let c = circles[index];
|
||||||
|
sdf = min(sdf, length(world - c.center) - c.radius);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
let shape = shapes[index];
|
||||||
|
|
||||||
|
/*let bbox_min = shape.bbox_min;
|
||||||
let bbox_max = shape.bbox_max;
|
let bbox_max = shape.bbox_max;
|
||||||
let dx = max(bbox_min.x - world.x, max(0.0, world.x - bbox_max.x));
|
let dx = max(bbox_min.x - world.x, max(0.0, world.x - bbox_max.x));
|
||||||
let dy = max(bbox_min.y - world.y, max(0.0, world.y - bbox_max.y));
|
let dy = max(bbox_min.y - world.y, max(0.0, world.y - bbox_max.y));
|
||||||
let dist_to_box = sqrt(dx * dx + dy * dy);
|
let dist_to_box = sqrt(dx * dx + dy * dy);
|
||||||
if dist_to_box >= minDist {
|
if dist_to_box >= minDist {
|
||||||
continue; // This shape cannot improve the distance
|
continue;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
// Process all segments of the shape
|
// Process all segments of the shape
|
||||||
for (var i = shape.start_segment; i < shape.start_segment + shape.segment_count; i++) {
|
for (var i = shape.start_segment; i < shape.start_segment + shape.segment_count; i++) {
|
||||||
@@ -208,10 +225,11 @@ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
|
|||||||
|
|
||||||
winding += ray_intersections_cubic(world, seg);
|
winding += ray_intersections_cubic(world, seg);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let sign = select(1.0, -1.0, winding != 0i);
|
let sign = select(1.0, -1.0, winding != 0i);
|
||||||
let sdf = sign * minDist;
|
sdf = min(sdf, sign * minDist);
|
||||||
|
}
|
||||||
sdf_buffer[id.y * u32(tile_params.tile_size) + id.x] = sdf;
|
}
|
||||||
|
|
||||||
|
textureStore(sdf_buffer, vec2(u32(world.x), u32(world.y)), vec4f(sdf, 0.0, 0.0, 1.0));
|
||||||
}
|
}
|
||||||
@@ -1,27 +1,20 @@
|
|||||||
struct Uniforms {
|
struct Uniforms {
|
||||||
pan: vec2f,
|
time: u32,
|
||||||
zoom: f32,
|
frame: u32,
|
||||||
}
|
mvp: mat4x4f,
|
||||||
struct TileParams {
|
|
||||||
lod: u32,
|
|
||||||
tile_size: f32,
|
|
||||||
world_min: vec2f,
|
|
||||||
world_max: vec2f,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
||||||
@group(0) @binding(1) var<uniform> tile_params: TileParams;
|
|
||||||
|
|
||||||
@vertex fn vs_main(@builtin(vertex_index) vertex_index : u32) -> @builtin(position) vec4f {
|
@group(1) @binding(0) var sdf : texture_2d<f32>;
|
||||||
const pos = array(
|
@group(1) @binding(1) var sdf_sampler : sampler;
|
||||||
vec2( 0.0, 0.5),
|
|
||||||
vec2(-0.5, -0.5),
|
|
||||||
vec2( 0.5, -0.5)
|
|
||||||
);
|
|
||||||
|
|
||||||
return vec4f(pos[vertex_index], 0, 1);
|
@vertex fn vs_main(@location(0) position: vec2f) -> @builtin(position) vec4f
|
||||||
|
{
|
||||||
|
return vec4f(position, 0.0, 1.0) * uniforms.mvp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@fragment fn fs_main() -> @location(0) vec4f {
|
@fragment fn fs_main(@builtin(position) position: vec4f) -> @location(0) vec4f
|
||||||
return vec4(1, 0, 0, 1);
|
{
|
||||||
|
return textureSample(sdf, sdf_sampler, position.xy);
|
||||||
}
|
}
|
||||||
290
src/shape.h
290
src/shape.h
@@ -3,47 +3,59 @@
|
|||||||
typedef struct uvec2 { uint32_t x, y; } uvec2;
|
typedef struct uvec2 { uint32_t x, y; } uvec2;
|
||||||
typedef struct vec2 { float x, y; } vec2;
|
typedef struct vec2 { float x, y; } vec2;
|
||||||
|
|
||||||
|
typedef struct ubox_t {
|
||||||
|
uint32_t min_x, min_y, max_x, max_y;
|
||||||
|
} ubox_t;
|
||||||
typedef struct box_t {
|
typedef struct box_t {
|
||||||
float min_x, min_y, max_x, max_y;
|
float min_x, min_y, max_x, max_y;
|
||||||
} box_t;
|
} box_t;
|
||||||
|
|
||||||
|
#define BOX_INTERSECTS(a, b) !(b.min_x > a.max_x \
|
||||||
|
|| b.max_x < a.min_x \
|
||||||
|
|| b.min_y > a.max_y \
|
||||||
|
|| b.max_y < a.min_y)
|
||||||
|
|
||||||
|
#define CIRCLE_INTERSECTS(box, circle) !(circle.center.x - circle.radius > box.max_x \
|
||||||
|
|| circle.center.x + circle.radius < box.min_x \
|
||||||
|
|| circle.center.y - circle.radius > box.max_y \
|
||||||
|
|| circle.center.y + circle.radius < box.min_y)
|
||||||
|
|
||||||
typedef struct segment_t {
|
typedef struct segment_t {
|
||||||
vec2 p0, p1, p2, p3; // cubic Bézier: start, control1, control2, end
|
vec2 p0, p1, p2, p3; // cubic Bézier: start, control1, control2, end
|
||||||
} segment_t;
|
} segment_t;
|
||||||
|
|
||||||
typedef struct shape_meta_t {
|
typedef struct shape_meta_t {
|
||||||
int start_segment; // index into global segments array
|
uint32_t start_segment; // index into global segments array
|
||||||
int segment_count; // number of segments forming this closed loop
|
uint32_t segment_count; // number of segments forming this closed loop
|
||||||
float bbox_min_x, bbox_min_y, bbox_max_x, bbox_max_y;
|
box_t aabb;
|
||||||
} shape_meta_t;
|
} shape_meta_t;
|
||||||
|
|
||||||
#define TILE_SIZE 128 // pixels per tile (same for all LODs)
|
typedef struct circle_t {
|
||||||
#define TILE_LAYERS 256 // 16 MB buffer
|
vec2 center;
|
||||||
|
float radius;
|
||||||
|
} circle_t;
|
||||||
|
|
||||||
#define _INTERSECTS(a, b) !(b.min_x > a.max_x \
|
#define TILE_SIZE 256 // texels per tile
|
||||||
|| b.max_x < a.min_x \
|
|
||||||
|| b.min_y > a.max_y \
|
|
||||||
|| b.max_y < a.min_y)
|
|
||||||
|
|
||||||
// Tile descriptor (separated from tile_cache_entry to be sent to the GPU)
|
#define TILE_COUNT(size) (uint32_t)ceilf(size.x / TILE_SIZE) * (uint32_t)ceilf(size.y / TILE_SIZE)
|
||||||
typedef struct tile_ID {
|
|
||||||
uint32_t lod;
|
|
||||||
uvec2 tile;
|
|
||||||
|
|
||||||
// Cached tile bounds
|
#define INITIAL_SHAPE_SIZE 256
|
||||||
|
#define INITIAL_SEGMENTS_SIZE 8192
|
||||||
|
#define INITIAL_CIRCLE_SIZE 1024
|
||||||
|
#define INITIAL_INDICES_SIZE 16384
|
||||||
|
|
||||||
|
// Tile metadata for targetted updates
|
||||||
|
typedef struct tile_task_t {
|
||||||
box_t bounds;
|
box_t bounds;
|
||||||
} tile_ID;
|
uint32_t offset;
|
||||||
|
uint32_t circle_count;
|
||||||
typedef struct LOD_t {
|
uint32_t count;
|
||||||
tile_ID* tiles;
|
} tile_task_t;
|
||||||
|
|
||||||
uvec2 dimension; //Tile amount per dimension
|
|
||||||
vec2 range; //Zoom min-max range
|
|
||||||
} LOD_t;
|
|
||||||
|
|
||||||
typedef struct scene_t {
|
typedef struct scene_t {
|
||||||
vec2 world_size;
|
vec2 world_size;
|
||||||
|
|
||||||
|
struct {
|
||||||
uint32_t num_shapes;
|
uint32_t num_shapes;
|
||||||
uint32_t max_shapes;
|
uint32_t max_shapes;
|
||||||
shape_meta_t* shapes;
|
shape_meta_t* shapes;
|
||||||
@@ -51,115 +63,199 @@ typedef struct scene_t {
|
|||||||
uint32_t num_segments;
|
uint32_t num_segments;
|
||||||
uint32_t max_segments;
|
uint32_t max_segments;
|
||||||
segment_t* segments;
|
segment_t* segments;
|
||||||
|
} shapes;
|
||||||
|
|
||||||
//Theorically, the LOD amount and tile per LOD are computable, so it's not relevant to store them.
|
struct {
|
||||||
uint32_t max_LOD;
|
uint32_t num_circles;
|
||||||
LOD_t* LODs;
|
uint32_t max_circles;
|
||||||
|
circle_t* circles;
|
||||||
|
} circles;
|
||||||
|
|
||||||
sg_view texture_cache;
|
struct {
|
||||||
|
tile_task_t* tiles; // Array of tiles metadata for the compute buffer
|
||||||
|
bool* tile_dirty; // Array of tiles state, since this info don't needs to be uploaded to the GPU, it is stored separately
|
||||||
|
|
||||||
|
uint32_t max_indices;
|
||||||
|
uint32_t num_indices;
|
||||||
|
uint32_t* indices; // Array of shapes/circles indices per tile, pre culled on the CPU side
|
||||||
|
} cache;
|
||||||
|
|
||||||
|
sg_view texture; //Layer count depends on the world size so the texture has to be allocate on the scene side
|
||||||
|
|
||||||
|
bool shape_dirty, segment_dirty, circle_dirty;
|
||||||
} scene_t;
|
} scene_t;
|
||||||
|
|
||||||
typedef int (*tile_callback)(scene_t* s, tile_ID* tile);
|
|
||||||
|
|
||||||
// Initialise a scene_t with a given capacity for shapes and segments.
|
// Initialise a scene_t with a given capacity for shapes and segments.
|
||||||
static int scene_init(scene_t* s, int init_shape_cap, int init_seg_cap, vec2 world_size) {
|
static uint32_t scene_init(scene_t* s, vec2 world_size) {
|
||||||
s->num_shapes = 0;
|
s->shapes.num_shapes = 0;
|
||||||
s->max_shapes = init_shape_cap;
|
s->shapes.max_shapes = INITIAL_SHAPE_SIZE;
|
||||||
s->shapes = (shape_meta_t*) malloc(init_shape_cap * sizeof(shape_meta_t));
|
s->shapes.shapes = (shape_meta_t*) emmalloc_malloc(INITIAL_SHAPE_SIZE * sizeof(shape_meta_t));
|
||||||
|
|
||||||
s->num_segments = 0;
|
s->shapes.num_segments = 0;
|
||||||
s->max_segments = init_seg_cap;
|
s->shapes.max_segments = INITIAL_SEGMENTS_SIZE;
|
||||||
s->segments = (segment_t*) malloc(init_seg_cap * sizeof(segment_t));
|
s->shapes.segments = (segment_t*) emmalloc_malloc(INITIAL_SEGMENTS_SIZE * sizeof(segment_t));
|
||||||
|
|
||||||
const uint32_t max_LOD = max(ceilf(log2(world_size.x / TILE_SIZE)), ceilf(log2(world_size.y / TILE_SIZE)));
|
s->circles.num_circles = 0;
|
||||||
|
s->circles.max_circles = INITIAL_SEGMENTS_SIZE;
|
||||||
|
s->circles.circles = (circle_t*) emmalloc_malloc(INITIAL_CIRCLE_SIZE * sizeof(circle_t));
|
||||||
|
|
||||||
s->max_LOD = max_LOD;
|
const uint32_t tile_count_x = (uint32_t)ceilf(world_size.x / TILE_SIZE), tile_count_y = (uint32_t)ceilf(world_size.y / TILE_SIZE);
|
||||||
s->LODs = malloc(max_LOD * sizeof(LOD_t));
|
const uint32_t tile_count = tile_count_x * tile_count_y;
|
||||||
for(int i = 0; i < max_LOD; ++i)
|
s->cache.tiles = (tile_task_t*) emmalloc_malloc(sizeof(tile_task_t) * tile_count);
|
||||||
|
s->cache.tile_dirty = (bool*) emmalloc_malloc(sizeof(bool) * tile_count);
|
||||||
|
|
||||||
|
uint32_t idx = 0;
|
||||||
|
for(uint32_t y = 0; y < tile_count_y; y++)
|
||||||
{
|
{
|
||||||
const float factor = 1 << i;
|
for(uint32_t x = 0; x < tile_count_x; x++)
|
||||||
s->LODs[i].dimension = (uvec2) { (uint32_t) ceilf(world_size.x * factor / TILE_SIZE), (uint32_t) ceilf(world_size.y * factor / TILE_SIZE) };
|
|
||||||
|
|
||||||
const uint32_t tiles_count = s->LODs[i].dimension.x * s->LODs[i].dimension.y;
|
|
||||||
s->LODs[i].tiles = malloc(sizeof(tile_ID) * tiles_count);
|
|
||||||
|
|
||||||
s->LODs[i].range = (vec2) { }; //TODO
|
|
||||||
|
|
||||||
uint32_t x = 0; uint32_t y = 0;
|
|
||||||
for(int j = 0; j < tiles_count; j++)
|
|
||||||
{
|
{
|
||||||
x++;
|
s->cache.tile_dirty[idx] = true;
|
||||||
if(x >= s->LODs[i].dimension.x) { x = 0; y++; }
|
idx++;
|
||||||
|
|
||||||
s->LODs[i].tiles[j] = (tile_ID) { .bounds = { x * factor, y * factor, (x + 1) * factor, (y + 1) * factor }, .lod = i, .tile = { x, y } };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s->cache.max_indices = INITIAL_INDICES_SIZE;
|
||||||
|
s->cache.num_indices = 0;
|
||||||
|
s->cache.indices = (uint32_t*) emmalloc_malloc(sizeof(uint32_t) * INITIAL_INDICES_SIZE);
|
||||||
|
|
||||||
s->world_size = world_size;
|
s->world_size = world_size;
|
||||||
|
s->texture = sg_make_view(&(sg_view_desc) {
|
||||||
|
.label = "SDF tiles view",
|
||||||
|
.storage_image = {
|
||||||
|
.image = sg_make_image(&(sg_image_desc) {
|
||||||
|
.width = (uint32_t) ceilf(world_size.x),
|
||||||
|
.height = (uint32_t) ceilf(world_size.y),
|
||||||
|
.label = "SDF tiles texture",
|
||||||
|
.pixel_format = SG_PIXELFORMAT_R16F,
|
||||||
|
.type = SG_IMAGETYPE_2D,
|
||||||
|
.usage = { .storage_image = true },
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (!s->shapes || !s->segments || !s->LODs) return 0; // allocation failure
|
if (!s->shapes.shapes || !s->shapes.segments || !s->circles.circles || !s->cache.indices || !s->cache.tiles || !s->cache.tile_dirty) return 0; // allocation failure
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Viewport bounds in *world* space
|
#define COORD_TO_INDEX(x, y, scene) y * ceilf(s->world_size.x / TILE_SIZE) + x
|
||||||
static void scene_for_each_tiles(scene_t* s, box_t viewport, tile_callback callback)
|
|
||||||
|
static void scene_compute_culling(scene_t* s, tile_task_t* task)
|
||||||
{
|
{
|
||||||
const uint32_t lod = 0;
|
task->count = 0;
|
||||||
const uint32_t count = s->LODs[lod].dimension.x * s->LODs[lod].dimension.y;
|
for(uint32_t i = 0; i < s->circles.num_circles; i++)
|
||||||
for(uint32_t i = 0; i < count; ++i)
|
|
||||||
{
|
{
|
||||||
tile_ID tile = s->LODs[lod].tiles[i];
|
if(CIRCLE_INTERSECTS(task->bounds, s->circles.circles[i]))
|
||||||
if(_INTERSECTS(tile.bounds, viewport))
|
{
|
||||||
callback(s, &tile);
|
if(s->cache.num_indices >= s->cache.max_indices)
|
||||||
|
{
|
||||||
|
s->cache.max_indices *= 2;
|
||||||
|
s->cache.indices = emmalloc_realloc(s->cache.indices, s->cache.max_indices * sizeof(uint32_t));
|
||||||
|
}
|
||||||
|
s->cache.indices[s->cache.num_indices++] = i;
|
||||||
|
task->count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
task->circle_count = task->count;
|
||||||
|
for(uint32_t i = 0; i < s->shapes.num_shapes; i++)
|
||||||
|
{
|
||||||
|
if(BOX_INTERSECTS(task->bounds, s->shapes.shapes[i].aabb))
|
||||||
|
{
|
||||||
|
if(s->cache.num_indices >= s->cache.max_indices)
|
||||||
|
{
|
||||||
|
s->cache.max_indices *= 2;
|
||||||
|
s->cache.indices = emmalloc_realloc(s->cache.indices, s->cache.max_indices * sizeof(uint32_t));
|
||||||
|
}
|
||||||
|
s->cache.indices[s->cache.num_indices++] = i;
|
||||||
|
task->count++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append a segment, returns its index. (Reallocs if needed, simplified.)
|
// Find the proper LOD, the relevant tiles then run the culling process for the dirty tiles and upload data to the GPU for SDF rendering
|
||||||
static int scene_add_segment(scene_t* s, segment_t seg) {
|
static uint32_t scene_process_tiles(scene_t* s, float pan_x, float pan_y, float zoom, sg_bindings* bindings)
|
||||||
if (s->num_segments >= s->max_segments) {
|
{
|
||||||
int new_cap = s->max_segments * 2;
|
const float width = (float)sapp_width(), height = (float) sapp_height();
|
||||||
segment_t* tmp = (segment_t*) realloc(s->segments, new_cap * sizeof(segment_t));
|
const float wpp = fmaxf(s->world_size.y / zoom / height, s->world_size.x / zoom / width); //World point per pixel
|
||||||
if (!tmp) return -1;
|
|
||||||
s->segments = tmp;
|
float view_w = width * wpp, view_h = height * wpp;
|
||||||
s->max_segments = new_cap;
|
box_t frustum = {
|
||||||
|
.min_x = ceilf((pan_x - view_w * 0.5) / TILE_SIZE),
|
||||||
|
.min_y = ceilf((pan_y - view_h * 0.5) / TILE_SIZE),
|
||||||
|
.max_x = ceilf((pan_x + view_w * 0.5) / TILE_SIZE),
|
||||||
|
.max_y = ceilf((pan_y + view_h * 0.5) / TILE_SIZE)
|
||||||
|
};
|
||||||
|
|
||||||
|
uint32_t dirty_tile_count = 0, offset = 0;
|
||||||
|
for(uint32_t y = frustum.min_y; y < frustum.max_y; y++)
|
||||||
|
{
|
||||||
|
for(uint32_t x = frustum.min_x; x < frustum.max_x; x++)
|
||||||
|
{
|
||||||
|
uint32_t idx = COORD_TO_INDEX(x, y, s);
|
||||||
|
if(s->cache.tile_dirty[idx])
|
||||||
|
{
|
||||||
|
tile_task_t* task = &s->cache.tiles[dirty_tile_count++];
|
||||||
|
task->bounds.min_x = x * TILE_SIZE;
|
||||||
|
task->bounds.min_y = y * TILE_SIZE;
|
||||||
|
task->bounds.max_x = fminf((x + 1) * TILE_SIZE, s->world_size.x);
|
||||||
|
task->bounds.max_y = fminf((y + 1) * TILE_SIZE, s->world_size.y);
|
||||||
|
|
||||||
|
task->offset = offset;
|
||||||
|
scene_compute_culling(s, task);
|
||||||
|
offset += task->count;
|
||||||
|
|
||||||
|
s->cache.tile_dirty[idx] = false;
|
||||||
}
|
}
|
||||||
int idx = s->num_segments++;
|
}
|
||||||
s->segments[idx] = seg;
|
}
|
||||||
|
|
||||||
|
return dirty_tile_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append a segment, returns its index. (Reallocs if needed, simplified.)
|
||||||
|
static uint32_t scene_add_segment(scene_t* s, segment_t seg) {
|
||||||
|
if (s->shapes.num_segments >= s->shapes.max_segments) {
|
||||||
|
int new_cap = s->shapes.max_segments * 2;
|
||||||
|
segment_t* tmp = (segment_t*) realloc(s->shapes.segments, new_cap * sizeof(segment_t));
|
||||||
|
if (!tmp) return -1;
|
||||||
|
s->shapes.segments = tmp;
|
||||||
|
s->shapes.max_segments = new_cap;
|
||||||
|
}
|
||||||
|
int idx = s->shapes.num_segments++;
|
||||||
|
s->shapes.segments[idx] = seg;
|
||||||
|
|
||||||
return idx;
|
return idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append a shape (meta data) and return its index.
|
// Append a shape (meta data) and return its index.
|
||||||
static int scene_add_shape(scene_t* s, shape_meta_t meta) {
|
static uint32_t scene_add_shape(scene_t* s, shape_meta_t meta) {
|
||||||
if (s->num_shapes >= s->max_shapes) {
|
if (s->shapes.num_shapes >= s->shapes.max_shapes) {
|
||||||
int new_cap = s->max_shapes * 2;
|
int new_cap = s->shapes.max_shapes * 2;
|
||||||
shape_meta_t* tmp = (shape_meta_t*) realloc(s->shapes, new_cap * sizeof(shape_meta_t));
|
shape_meta_t* tmp = (shape_meta_t*) emmalloc_realloc(s->shapes.shapes, new_cap * sizeof(shape_meta_t));
|
||||||
if (!tmp) return -1;
|
if (!tmp) return -1;
|
||||||
s->shapes = tmp;
|
s->shapes.shapes = tmp;
|
||||||
s->max_shapes = new_cap;
|
s->shapes.max_shapes = new_cap;
|
||||||
}
|
}
|
||||||
int idx = s->num_shapes++;
|
int idx = s->shapes.num_shapes++;
|
||||||
s->shapes[idx] = meta;
|
s->shapes.shapes[idx] = meta;
|
||||||
|
|
||||||
return idx;
|
return idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int scene_shutdown(scene_t* s) {
|
static uint32_t scene_shutdown(scene_t* s) {
|
||||||
free(s->segments);
|
emmalloc_free(s->shapes.segments);
|
||||||
free(s->shapes);
|
emmalloc_free(s->shapes.shapes);
|
||||||
|
emmalloc_free(s->circles.circles);
|
||||||
|
|
||||||
for(uint32_t i = 0; i < s->max_LOD; ++i)
|
emmalloc_free(s->cache.indices);
|
||||||
free(s->LODs[i].tiles);
|
emmalloc_free(s->cache.tiles);
|
||||||
|
emmalloc_free(s->cache.tile_dirty);
|
||||||
|
|
||||||
free(s->LODs);
|
emmalloc_free(s);
|
||||||
|
|
||||||
free(s);
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute axis-aligned bounding box for a set of segments (used after adding a shape).
|
// Compute axis-aligned bounding box for a set of segments (used after adding a shape).
|
||||||
static void compute_bbox(segment_t* segs, int start, int count,
|
static void compute_bbox(segment_t* segs, uint32_t start, uint32_t count, float* minx, float* miny, float* maxx, float* maxy) {
|
||||||
float* minx, float* miny, float* maxx, float* maxy) {
|
|
||||||
float mx = 1e30f, my = 1e30f, Mx = -1e30f, My = -1e30f;
|
float mx = 1e30f, my = 1e30f, Mx = -1e30f, My = -1e30f;
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
segment_t s = segs[start + i];
|
segment_t s = segs[start + i];
|
||||||
@@ -177,7 +273,7 @@ static void compute_bbox(segment_t* segs, int start, int count,
|
|||||||
|
|
||||||
// Add an axis‑aligned rectangle centred at `center` with given width and height.
|
// Add an axis‑aligned rectangle centred at `center` with given width and height.
|
||||||
// Returns the shape index, or -1 on error.
|
// Returns the shape index, or -1 on error.
|
||||||
int add_rectangle(scene_t* s, vec2 center, vec2 size) {
|
uint32_t add_rectangle(scene_t* s, vec2 center, vec2 size) {
|
||||||
float hw = size.x * 0.5f, hh = size.y * 0.5f;
|
float hw = size.x * 0.5f, hh = size.y * 0.5f;
|
||||||
vec2 corners[4] = {
|
vec2 corners[4] = {
|
||||||
{center.x - hw, center.y - hh}, // bottom‑left
|
{center.x - hw, center.y - hh}, // bottom‑left
|
||||||
@@ -205,14 +301,14 @@ int add_rectangle(scene_t* s, vec2 center, vec2 size) {
|
|||||||
shape_meta_t meta;
|
shape_meta_t meta;
|
||||||
meta.start_segment = start;
|
meta.start_segment = start;
|
||||||
meta.segment_count = 4;
|
meta.segment_count = 4;
|
||||||
compute_bbox(s->segments, start, 4, &meta.bbox_min_x, &meta.bbox_min_y, &meta.bbox_max_x, &meta.bbox_max_y);
|
meta.aabb = (box_t) { center.x - hw, center.y - hh, center.x + hw, center.y + hh };
|
||||||
return scene_add_shape(s, meta);
|
return scene_add_shape(s, meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a circle centred at `center` with given radius.
|
// Add a circle centred at `center` with given radius.
|
||||||
// approximated by 4 cubic Bézier segments (one per quadrant).
|
// approximated by 4 cubic Bézier segments (one per quadrant).
|
||||||
// Returns shape index or -1.
|
// Returns shape index or -1.
|
||||||
int add_circle(scene_t* s, vec2 center, float radius) {
|
uint32_t add_circle_as_shape(scene_t* s, vec2 center, float radius) {
|
||||||
const float k = 0.552284749831f; // magic constant for 90° arc (4/3 * (sqrt(2)-1))
|
const float k = 0.552284749831f; // magic constant for 90° arc (4/3 * (sqrt(2)-1))
|
||||||
float rk = radius * k;
|
float rk = radius * k;
|
||||||
|
|
||||||
@@ -256,6 +352,12 @@ int add_circle(scene_t* s, vec2 center, float radius) {
|
|||||||
shape_meta_t meta;
|
shape_meta_t meta;
|
||||||
meta.start_segment = start;
|
meta.start_segment = start;
|
||||||
meta.segment_count = 4;
|
meta.segment_count = 4;
|
||||||
compute_bbox(s->segments, start, 4, &meta.bbox_min_x, &meta.bbox_min_y, &meta.bbox_max_x, &meta.bbox_max_y);
|
compute_bbox(s->shapes.segments, start, 4, &meta.aabb.min_x, &meta.aabb.min_y, &meta.aabb.max_x, &meta.aabb.max_y);
|
||||||
return scene_add_shape(s, meta);
|
return scene_add_shape(s, meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t add_circle(scene_t* s, vec2 center, float radius) {
|
||||||
|
s->circles.circles[s->circles.num_circles++] = (circle_t) { center, radius };
|
||||||
|
|
||||||
|
return s->circles.num_circles - 1;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user