You've already forked flecs_tests
Tiling system for SDF multi resolution
This commit is contained in:
146
src/main.c
146
src/main.c
@@ -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)
|
||||
|
||||
115
src/shape.h
115
src/shape.h
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user