Tiling system for SDF multi resolution

This commit is contained in:
2026-05-07 20:55:44 +02:00
parent 936043340c
commit beea8a0281
2 changed files with 180 additions and 81 deletions

View File

@@ -1,25 +1,49 @@
#include "api.h"
typedef struct uniform_t {
float x, y;
float zoom;
} uniform_t;
#define COMPUTE_VIEWIDX_segments 0
#define COMPUTE_VIEWIDX_shapes 1
#define COMPUTE_VIEWIDX_SDF 2
#define DISPLAY_VIEWIDX_SDF 0
typedef struct display_uniforms {
uint32_t frame;
uint32_t time;
float mvp[16];
} display_uniforms;
typedef struct renderer_t {
sg_pipeline pipeline;
sg_pass_action clear_pass;
uniform_t uniforms;
sg_bindings bindings;
display_uniforms uniforms;
} 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 {
sg_pipeline pipeline;
sg_bindings bindings;
compute_uniforms uniforms
} compute_t;
typedef struct userdata_t {
scene_t scene;
renderer_t renderer;
compute_t compute;
//Viewport related values
float pan_x, pan_y;
float zoom, rotation;
} 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)
@@ -33,34 +57,42 @@ static void log_fn(const char* tag, uint32_t log_level, uint32_t log_item_id, co
static void draw_scene(userdata_t* ud)
{
if(ud->scene.dirty)
{
sg_buffer seg_buffer = sg_query_view_buffer(ud->compute.bindings.views[0]);
//_SG_VALIDATE(seg_buffer.id == SG_INVALID_ID, COMPUTE_INVALID_BUFFER);
sg_update_buffer(seg_buffer, &(sg_range) { .ptr = ud->scene.segments, .size = sizeof(segment_t) * ud->scene.num_segments });
//Get visible tiles
float view_world_w = sapp_widthf() * ud->zoom;
float view_world_h = sapp_heightf() * ud->zoom;
box_t viewport = (box_t) {
ud->pan_x - view_world_w * 0.5f,
ud->pan_y - view_world_h * 0.5f,
ud->pan_x + view_world_w * 0.5f,
ud->pan_y + view_world_h * 0.5f,
};
sg_buffer shape_buffer = sg_query_view_buffer(ud->compute.bindings.views[1]);
//_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 });
sg_buffer seg_buffer = sg_query_view_buffer(ud->compute.bindings.views[0]);
//_SG_VALIDATE(seg_buffer.id == SG_INVALID_ID, COMPUTE_INVALID_BUFFER);
sg_update_buffer(seg_buffer, &(sg_range) { .ptr = ud->scene.segments, .size = sizeof(segment_t) * ud->scene.num_segments });
sg_begin_pass(&(sg_pass){ .compute = true, .label = "Compute Pass" });
sg_apply_pipeline(ud->compute.pipeline);
sg_apply_uniforms(0, &SG_RANGE(ud->renderer.uniforms));
sg_apply_uniforms(1, &SG_RANGE(ud->scene.tiles[0]));
sg_apply_bindings(&ud->compute.bindings);
sg_buffer shape_buffer = sg_query_view_buffer(ud->compute.bindings.views[1]);
//_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 });
sg_dispatch(TILE_SIZE / 8, TILE_SIZE / 8, 1);
sg_begin_pass(&(sg_pass){ .compute = true, .label = "Compute Pass" });
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_end_pass();
sg_dispatch(TILE_SIZE / 8, TILE_SIZE / 8, 1);
ud->scene.dirty = false;
}
sg_end_pass();
}
static void frame(void* _userdata)
{
userdata_t* ud = (userdata_t*) _userdata;
ud->renderer.uniforms.frame = ud->compute.uniforms.frame = sapp_frame_count();
ud->renderer.uniforms.time = ud->compute.uniforms.time += sapp_frame_duration_unfiltered();
draw_scene(ud);
sg_begin_pass(&(sg_pass){
@@ -71,13 +103,13 @@ static void frame(void* _userdata)
simgui_new_frame(&(simgui_frame_desc_t){
.width = sapp_width(),
.height = sapp_height(),
.delta_time = sapp_frame_duration(),
.delta_time = sapp_frame_duration_unfiltered(),
.dpi_scale = sapp_dpi_scale(),
});
igBegin("Framerate", (bool*) true, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
igText("%.0f FPS", 1 / sapp_frame_duration());
igText("%.3fms", sapp_frame_duration() * 1000);
igText("%.0f FPS", 1 / sapp_frame_duration_unfiltered());
igText("%.3fms", sapp_frame_duration_unfiltered() * 1000);
igEnd();
simgui_render();
@@ -96,7 +128,7 @@ static void init(void* _userdata)
simgui_setup(&(simgui_desc_t){0});
ud->scene = (scene_t) {0};
if(!scene_init(&ud->scene, 128, 1024)) return;
if(!scene_init(&ud->scene, 128, 1024, (vec2) { 8192.0f, 8192.0f })) return;
ud->compute = (compute_t) {
.pipeline = sg_make_pipeline(&(sg_pipeline_desc){
@@ -131,7 +163,7 @@ static void init(void* _userdata)
},
.uniform_blocks = {
[0] = {
.size = sizeof(uniform_t),
.size = sizeof(compute_uniforms),
.stage = SG_SHADERSTAGE_COMPUTE,
.wgsl_group0_binding_n = 0,
},
@@ -146,7 +178,7 @@ static void init(void* _userdata)
.label = "SDF Compute Pipeline",
}),
.bindings = (sg_bindings) {
.views[0] = sg_make_view(&(sg_view_desc) {
.views[COMPUTE_VIEWIDX_segments] = sg_make_view(&(sg_view_desc) {
.label = "Segments view",
.storage_buffer = {
.buffer = sg_make_buffer(&(sg_buffer_desc) {
@@ -159,7 +191,7 @@ static void init(void* _userdata)
}),
},
}),
.views[1] = sg_make_view(&(sg_view_desc) {
.views[COMPUTE_VIEWIDX_shapes] = sg_make_view(&(sg_view_desc) {
.label = "Shapes metadata view",
.storage_buffer = {
.buffer = sg_make_buffer(&(sg_buffer_desc) {
@@ -172,19 +204,33 @@ static void init(void* _userdata)
}),
},
}),
.views[2] = sg_make_view(&(sg_view_desc) {
.label = "SDF field view",
.storage_buffer = {
.buffer = sg_make_buffer(&(sg_buffer_desc) {
.label = "SDF field buffer",
.size = sizeof(float),
.usage.storage_buffer = true,
.views[COMPUTE_VIEWIDX_SDF] = sg_make_view(&(sg_view_desc) {
.label = "SDF tiles view",
.storage_image = {
.image = sg_make_image(&(sg_image_desc) {
.height = TILE_SIZE,
.width = TILE_SIZE,
.label = "SDF tiles texture",
.num_slices = TILE_LAYERS,
.pixel_format = SG_PIXELFORMAT_R32F,
.type = SG_IMAGETYPE_ARRAY,
.usage = { .storage_image = true },
}),
},
}),
}
};
const vec2 quad[4] = {
{ 0.0f, 1.0f }, // bottom left
{ 1.0f, 1.0f }, // bottom right
{ 1.0f, 0.0f }, // top right
{ 0.0f, 0.0f }, // top left
};
const uint16_t indices[] = {
0, 1, 2, 0, 2, 3,
};
ud->renderer = (renderer_t) {
.pipeline = sg_make_pipeline(&(sg_pipeline_desc){
.shader = sg_make_shader(&(sg_shader_desc) {
@@ -197,7 +243,7 @@ static void init(void* _userdata)
.entry = "fs_main",
},
.views = {
[0] = {
[DISPLAY_VIEWIDX_SDF] = {
.storage_buffer = {
.stage = SG_SHADERSTAGE_FRAGMENT,
.wgsl_group1_binding_n = 1,
@@ -207,7 +253,7 @@ static void init(void* _userdata)
},
.uniform_blocks = {
[0] = {
.size = sizeof(uniform_t),
.size = sizeof(display_uniforms),
.stage = SG_SHADERSTAGE_VERTEX,
.wgsl_group0_binding_n = 0,
},
@@ -219,16 +265,38 @@ static void init(void* _userdata)
},
.label = "Display Shader",
}),
.cull_mode = SG_CULLMODE_FRONT,
.index_type = SG_INDEXTYPE_UINT16,
.layout.attrs = {
[0].format = SG_VERTEXFORMAT_FLOAT2,
},
.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 = {
.index_buffer = sg_make_buffer(&(sg_buffer_desc) {
.label = "Index buffer",
.usage.index_buffer = true,
.size = sizeof(uint16_t),
.data = SG_RANGE(indices),
}),
.vertex_buffers = sg_make_buffer(&(sg_buffer_desc) {
.label = "Vertex buffer",
.usage.vertex_buffer = true,
.size = sizeof(vec2),
.data = SG_RANGE(quad),
}),
.views = {
[DISPLAY_VIEWIDX_SDF] = ud->compute.bindings.views[COMPUTE_VIEWIDX_SDF]
}
}
};
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);
scene_add_tile(&ud->scene, (tile_cache_entry) { .id = { .lod = 0, .tile = TILE_SIZE, .world_min = { 0.0f, 0.0f }, .world_max = { 1024.0f, 1024.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)

View File

@@ -3,6 +3,10 @@
typedef struct uvec2 { uint32_t x, y; } uvec2;
typedef struct vec2 { float x, y; } vec2;
typedef struct box_t {
float min_x, min_y, max_x, max_y;
} box_t;
typedef struct segment_t {
vec2 p0, p1, p2, p3; // cubic Bézier: start, control1, control2, end
} segment_t;
@@ -14,26 +18,32 @@ typedef struct shape_meta_t {
} shape_meta_t;
#define TILE_SIZE 128 // pixels per tile (same for all LODs)
#define TILE_LAYERS 256 // 16 MB buffer
// Tile descriptor
#define _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)
// Tile descriptor (separated from tile_cache_entry to be sent to the GPU)
typedef struct tile_ID {
uint32_t lod; // LOD level (0 = coarsest)
uvec2 tile; // position in the grid at that LOD
uint32_t lod;
uvec2 tile;
// world-space bounds (derived from lod, tile_x, tile_y)
vec2 world_min;
vec2 world_max;
// Cached tile bounds
box_t bounds;
} tile_ID;
// Tile cache entry (CPU side)
typedef struct tile_cache_entry {
tile_ID id;
// GPU texture holding the SDF values (R32Float)
sg_buffer SDF; //TODO
bool ready;
} tile_cache_entry;
typedef struct LOD_t {
tile_ID* tiles;
uvec2 dimension; //Tile amount per dimension
vec2 range; //Zoom min-max range
} LOD_t;
typedef struct scene_t {
vec2 world_size;
uint32_t num_shapes;
uint32_t max_shapes;
shape_meta_t* shapes;
@@ -42,15 +52,17 @@ typedef struct scene_t {
uint32_t max_segments;
segment_t* segments;
uint32_t num_tiles;
uint32_t max_tiles;
tile_cache_entry* tiles;
//Theorically, the LOD amount and tile per LOD are computable, so it's not relevant to store them.
uint32_t max_LOD;
LOD_t* LODs;
bool dirty;
sg_view texture_cache;
} scene_t;
typedef int (*tile_callback)(scene_t* s, tile_ID* tile);
// 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) {
static int scene_init(scene_t* s, int init_shape_cap, int init_seg_cap, vec2 world_size) {
s->num_shapes = 0;
s->max_shapes = init_shape_cap;
s->shapes = (shape_meta_t*) malloc(init_shape_cap * sizeof(shape_meta_t));
@@ -59,16 +71,49 @@ static int scene_init(scene_t* s, int init_shape_cap, int init_seg_cap) {
s->max_segments = init_seg_cap;
s->segments = (segment_t*) malloc(init_seg_cap * sizeof(segment_t));
s->num_tiles = 0;
s->max_tiles = 8;
s->tiles = (tile_cache_entry*) malloc(8 * sizeof(tile_cache_entry));
const uint32_t max_LOD = max(ceilf(log2(world_size.x / TILE_SIZE)), ceilf(log2(world_size.y / TILE_SIZE)));
s->dirty = true;
s->max_LOD = max_LOD;
s->LODs = malloc(max_LOD * sizeof(LOD_t));
for(int i = 0; i < max_LOD; ++i)
{
const float factor = 1 << i;
s->LODs[i].dimension = (uvec2) { (uint32_t) ceilf(world_size.x * factor / TILE_SIZE), (uint32_t) ceilf(world_size.y * factor / TILE_SIZE) };
if (!s->shapes || !s->segments || !s->tiles) return 0; // allocation failure
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++;
if(x >= s->LODs[i].dimension.x) { x = 0; y++; }
s->LODs[i].tiles[j] = (tile_ID) { .bounds = { x * factor, y * factor, (x + 1) * factor, (y + 1) * factor }, .lod = i, .tile = { x, y } };
}
}
s->world_size = world_size;
if (!s->shapes || !s->segments || !s->LODs) return 0; // allocation failure
return 1;
}
//Viewport bounds in *world* space
static void scene_for_each_tiles(scene_t* s, box_t viewport, tile_callback callback)
{
const uint32_t lod = 0;
const uint32_t count = s->LODs[lod].dimension.x * s->LODs[lod].dimension.y;
for(uint32_t i = 0; i < count; ++i)
{
tile_ID tile = s->LODs[lod].tiles[i];
if(_INTERSECTS(tile.bounds, viewport))
callback(s, &tile);
}
}
// Append a segment, returns its index. (Reallocs if needed, simplified.)
static int scene_add_segment(scene_t* s, segment_t seg) {
if (s->num_segments >= s->max_segments) {
@@ -81,7 +126,6 @@ static int scene_add_segment(scene_t* s, segment_t seg) {
int idx = s->num_segments++;
s->segments[idx] = seg;
s->dirty = true;
return idx;
}
@@ -97,30 +141,17 @@ static int scene_add_shape(scene_t* s, shape_meta_t meta) {
int idx = s->num_shapes++;
s->shapes[idx] = meta;
s->dirty = true;
return idx;
}
static int scene_add_tile(scene_t* s, tile_cache_entry tile)
{
if (s->num_tiles >= s->max_tiles) {
int new_cap = s->max_tiles * 2;
tile_cache_entry* tmp = (tile_cache_entry*) realloc(s->tiles, new_cap * sizeof(tile_cache_entry));
if (!tmp) return -1;
s->tiles = tmp;
s->max_tiles = new_cap;
}
int idx = s->num_tiles++;
s->tiles[idx] = tile;
s->dirty = true;
return idx;
}
static int scene_shutdown(scene_t* s) {
free(s->segments);
free(s->shapes);
free(s->tiles);
for(uint32_t i = 0; i < s->max_LOD; ++i)
free(s->LODs[i].tiles);
free(s->LODs);
free(s);
return 1;