You've already forked flecs_tests
Add copy/paste, rewrite rendering pipeline with instanced draw calls and add a lot of caching to minimize overhead on huge edits.
This commit is contained in:
282
src/shape.h
282
src/shape.h
@@ -20,6 +20,12 @@ typedef struct shape_uniform_t {
|
||||
uint8_t _pad[12];
|
||||
} shape_uniform_t;
|
||||
|
||||
typedef struct {
|
||||
mat4 transform;
|
||||
uint32_t state;
|
||||
uint8_t _pad[12];
|
||||
} shape_gpu_data_t;
|
||||
|
||||
typedef struct shape_t {
|
||||
shape_vertex_t *verts;
|
||||
uint16_t *indices;
|
||||
@@ -32,11 +38,9 @@ typedef struct shape_t {
|
||||
float cx, cy;
|
||||
float sx, sy;
|
||||
float rotation;
|
||||
float cos_r, sin_r;
|
||||
int kind;
|
||||
|
||||
uint32_t vertex_base;
|
||||
uint32_t index_base;
|
||||
|
||||
int group_id;
|
||||
} shape_t;
|
||||
|
||||
@@ -47,15 +51,55 @@ typedef struct {
|
||||
int parent_id; // 0 = top-level group
|
||||
} group_t;
|
||||
|
||||
static group_t* find_group(vector_t *groups, int id) {
|
||||
static group_t **g_group_by_id = NULL;
|
||||
static int g_group_by_id_cap = 0;
|
||||
|
||||
static void group_index_rebuild(vector_t *groups)
|
||||
{
|
||||
int max_id = 0;
|
||||
for (int i = 0; i < groups->count; i++) {
|
||||
int gid = ((group_t*) vec_get(groups, i))->id;
|
||||
if (gid > max_id) max_id = gid;
|
||||
}
|
||||
if (max_id >= g_group_by_id_cap) {
|
||||
if (g_group_by_id) FREE(g_group_by_id);
|
||||
int new_cap = max_id + 64;
|
||||
g_group_by_id = (group_t**) ALLOC((size_t)new_cap * sizeof(group_t*));
|
||||
memset(g_group_by_id, 0, (size_t)new_cap * sizeof(group_t*));
|
||||
g_group_by_id_cap = new_cap;
|
||||
} else {
|
||||
for (int i = 0; i <= max_id; i++) g_group_by_id[i] = NULL;
|
||||
}
|
||||
for (int i = 0; i < groups->count; i++) {
|
||||
group_t *g = (group_t*) vec_get(groups, i);
|
||||
if (g->id == id) return g;
|
||||
g_group_by_id[g->id] = g;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void group_index_ensure_cap(int max_id)
|
||||
{
|
||||
if (max_id >= g_group_by_id_cap) {
|
||||
int new_cap = max_id + 64;
|
||||
group_t **old = g_group_by_id;
|
||||
g_group_by_id = (group_t**) ALLOC((size_t)new_cap * sizeof(group_t*));
|
||||
if (old) {
|
||||
memcpy(g_group_by_id, old, (size_t)g_group_by_id_cap * sizeof(group_t*));
|
||||
FREE(old);
|
||||
}
|
||||
memset(g_group_by_id + g_group_by_id_cap, 0,
|
||||
(size_t)(new_cap - g_group_by_id_cap) * sizeof(group_t*));
|
||||
g_group_by_id_cap = new_cap;
|
||||
}
|
||||
}
|
||||
|
||||
static group_t* find_group(vector_t *groups, int id) {
|
||||
(void)groups;
|
||||
if (id <= 0 || id >= g_group_by_id_cap) return NULL;
|
||||
return g_group_by_id[id];
|
||||
}
|
||||
|
||||
static int get_topmost_group(vector_t *groups, int gid) {
|
||||
(void)groups;
|
||||
while (gid != 0) {
|
||||
group_t *g = find_group(groups, gid);
|
||||
if (!g || g->parent_id == 0) return gid;
|
||||
@@ -65,6 +109,7 @@ static int get_topmost_group(vector_t *groups, int gid) {
|
||||
}
|
||||
|
||||
static bool is_shape_in_group_hierarchy(int shape_gid, int target_gid, vector_t *groups) {
|
||||
(void)groups;
|
||||
int cur = shape_gid;
|
||||
while (cur != 0) {
|
||||
if (cur == target_gid) return true;
|
||||
@@ -75,74 +120,189 @@ static bool is_shape_in_group_hierarchy(int shape_gid, int target_gid, vector_t
|
||||
return false;
|
||||
}
|
||||
|
||||
static void group_index_shutdown(void)
|
||||
{
|
||||
if (g_group_by_id) FREE(g_group_by_id);
|
||||
g_group_by_id = NULL;
|
||||
g_group_by_id_cap = 0;
|
||||
}
|
||||
|
||||
// -- shared geometry buffers (one vbuf + one ibuf for all shapes) --
|
||||
|
||||
static sg_buffer g_shape_vbuf = {0};
|
||||
static sg_buffer g_shape_ibuf = {0};
|
||||
static sg_buffer g_shape_data_sbuf = {0};
|
||||
static sg_buffer g_instance_map_sbuf = {0};
|
||||
static sg_view g_shape_data_view = {0};
|
||||
static sg_view g_instance_map_view = {0};
|
||||
|
||||
// Per-group vertex buffers: one per unique num_elements
|
||||
typedef struct {
|
||||
uint32_t num_elements;
|
||||
sg_buffer vbuf;
|
||||
} shape_group_buf_t;
|
||||
static shape_group_buf_t *g_shape_groups = NULL;
|
||||
static int g_shape_group_count = 0;
|
||||
|
||||
static bool g_shape_pool_dirty;
|
||||
static uint32_t g_shape_vert_count;
|
||||
static uint32_t g_shape_idx_count;
|
||||
static bool g_shape_data_dirty;
|
||||
static size_t g_shape_data_buf_size = 0;
|
||||
static int g_instance_map_capacity = 0;
|
||||
|
||||
static void shape_make_view_for_buffer(sg_view *view, sg_buffer buf)
|
||||
{
|
||||
if (view->id) sg_destroy_view(*view);
|
||||
*view = sg_make_view(&(sg_view_desc){
|
||||
.storage_buffer = { .buffer = buf },
|
||||
});
|
||||
}
|
||||
|
||||
static shape_gpu_data_t *g_upload_buf = NULL;
|
||||
static int g_upload_buf_cap = 0;
|
||||
|
||||
static void shape_upload_data(vector_t *shapes)
|
||||
{
|
||||
int n = shapes->count;
|
||||
if (n == 0 || !g_shape_data_sbuf.id) return;
|
||||
|
||||
size_t need = (size_t)n * sizeof(shape_gpu_data_t);
|
||||
if (need > g_shape_data_buf_size) {
|
||||
panel_log(2, "[shapes] upload_data: buffer too small (%zu < %zu), forcing rebuild",
|
||||
g_shape_data_buf_size, need);
|
||||
g_shape_pool_dirty = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (n > g_upload_buf_cap) {
|
||||
if (g_upload_buf) FREE(g_upload_buf);
|
||||
g_upload_buf = (shape_gpu_data_t*) ALLOC(need);
|
||||
g_upload_buf_cap = n;
|
||||
}
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
shape_t *s = (shape_t*) vec_get(shapes, i);
|
||||
memcpy(g_upload_buf[i].transform, s->uniform.transform, sizeof(mat4));
|
||||
g_upload_buf[i].state = s->uniform.state;
|
||||
memset(g_upload_buf[i]._pad, 0, sizeof(g_upload_buf[i]._pad));
|
||||
}
|
||||
sg_update_buffer(g_shape_data_sbuf, &(sg_range){g_upload_buf, need});
|
||||
}
|
||||
|
||||
static void shape_upload_instance_map(const uint32_t *map, int count)
|
||||
{
|
||||
if (count > g_instance_map_capacity) {
|
||||
if (g_instance_map_sbuf.id) sg_destroy_buffer(g_instance_map_sbuf);
|
||||
g_instance_map_sbuf = sg_make_buffer(&(sg_buffer_desc){
|
||||
.size = (size_t)count * sizeof(uint32_t),
|
||||
.usage = { .storage_buffer = true, .stream_update = true },
|
||||
.label = "Instance map",
|
||||
});
|
||||
g_instance_map_capacity = count;
|
||||
shape_make_view_for_buffer(&g_instance_map_view, g_instance_map_sbuf);
|
||||
}
|
||||
sg_update_buffer(g_instance_map_sbuf, &(sg_range){map, (size_t)count * sizeof(uint32_t)});
|
||||
panel_log(3, "[shapes] upload_instance_map: count=%d buf=%d view=%d",
|
||||
count, g_instance_map_sbuf.id, g_instance_map_view.id);
|
||||
}
|
||||
|
||||
static void shape_pool_rebuild(vector_t *shapes)
|
||||
{
|
||||
// count total vertices / indices (line strips: num_elements == num_verts + 1)
|
||||
uint32_t total_verts = 0, total_indices = 0;
|
||||
for (int i = 0; i < shapes->count; i++) {
|
||||
shape_t *s = (shape_t*) vec_get(shapes, i);
|
||||
total_verts += s->num_elements;
|
||||
total_indices += s->num_elements;
|
||||
int n = shapes->count;
|
||||
|
||||
// Destroy old groups
|
||||
for (int i = 0; i < g_shape_group_count; i++) {
|
||||
if (g_shape_groups[i].vbuf.id) sg_destroy_buffer(g_shape_groups[i].vbuf);
|
||||
}
|
||||
FREE(g_shape_groups);
|
||||
g_shape_groups = NULL;
|
||||
g_shape_group_count = 0;
|
||||
|
||||
if (g_shape_vbuf.id) { sg_destroy_buffer(g_shape_vbuf); g_shape_vbuf.id = 0; }
|
||||
if (g_shape_ibuf.id) { sg_destroy_buffer(g_shape_ibuf); g_shape_ibuf.id = 0; }
|
||||
g_shape_vert_count = 0;
|
||||
g_shape_idx_count = 0;
|
||||
if (g_shape_data_sbuf.id) { sg_destroy_buffer(g_shape_data_sbuf); g_shape_data_sbuf.id = 0; }
|
||||
if (g_shape_data_view.id) { sg_destroy_view(g_shape_data_view); g_shape_data_view.id = 0; }
|
||||
|
||||
if (total_verts == 0) {
|
||||
if (n == 0) {
|
||||
g_shape_pool_dirty = false;
|
||||
return;
|
||||
}
|
||||
|
||||
shape_vertex_t *all_v = (shape_vertex_t*) ALLOC((size_t)total_verts * sizeof(shape_vertex_t));
|
||||
uint16_t *all_i = (uint16_t*) ALLOC((size_t)total_indices * sizeof(uint16_t));
|
||||
g_shape_data_buf_size = (size_t)n * sizeof(shape_gpu_data_t);
|
||||
g_shape_data_sbuf = sg_make_buffer(&(sg_buffer_desc){
|
||||
.size = g_shape_data_buf_size,
|
||||
.usage = { .storage_buffer = true, .stream_update = true },
|
||||
.label = "Shape data",
|
||||
});
|
||||
shape_make_view_for_buffer(&g_shape_data_view, g_shape_data_sbuf);
|
||||
// Data filled by shape_upload_data() in draw_shapes
|
||||
|
||||
uint32_t voff = 0, ioff = 0;
|
||||
for (int i = 0; i < shapes->count; i++) {
|
||||
shape_t *s = (shape_t*) vec_get(shapes, i);
|
||||
uint32_t n = s->num_elements;
|
||||
memcpy(&all_v[voff], s->verts, (size_t)n * sizeof(shape_vertex_t));
|
||||
for (uint32_t j = 0; j < n; j++)
|
||||
all_i[ioff + j] = (uint16_t)(voff + s->indices[j]);
|
||||
s->vertex_base = voff;
|
||||
s->index_base = ioff;
|
||||
voff += n;
|
||||
ioff += n;
|
||||
// Count unique num_elements
|
||||
uint32_t max_ne = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
uint32_t ne = ((shape_t*) vec_get(shapes, i))->num_elements;
|
||||
if (ne > max_ne) max_ne = ne;
|
||||
}
|
||||
|
||||
g_shape_vbuf = sg_make_buffer(&(sg_buffer_desc){
|
||||
.data = { all_v, (size_t)total_verts * sizeof(shape_vertex_t) },
|
||||
.label = "Shape verts (shared)",
|
||||
});
|
||||
g_shape_ibuf = sg_make_buffer(&(sg_buffer_desc){
|
||||
.data = { all_i, (size_t)total_indices * sizeof(uint16_t) },
|
||||
.usage = { .index_buffer = true },
|
||||
.label = "Shape indices (shared)",
|
||||
});
|
||||
int *ne_seen = (int*) ALLOC((size_t)(max_ne + 1) * sizeof(int));
|
||||
memset(ne_seen, 0, (size_t)(max_ne + 1) * sizeof(int));
|
||||
for (int i = 0; i < n; i++) {
|
||||
uint32_t ne = ((shape_t*) vec_get(shapes, i))->num_elements;
|
||||
ne_seen[ne] = 1;
|
||||
}
|
||||
int group_count = 0;
|
||||
for (uint32_t ne = 0; ne <= max_ne; ne++)
|
||||
if (ne_seen[ne]) group_count++;
|
||||
|
||||
FREE(all_v);
|
||||
FREE(all_i);
|
||||
// Create per-group vertex buffers (one copy of vertex data per unique num_elements)
|
||||
g_shape_groups = (shape_group_buf_t*) ALLOC((size_t)group_count * sizeof(shape_group_buf_t));
|
||||
memset(g_shape_groups, 0, (size_t)group_count * sizeof(shape_group_buf_t));
|
||||
|
||||
int gi = 0;
|
||||
for (uint32_t ne = 0; ne <= max_ne; ne++) {
|
||||
if (!ne_seen[ne]) continue;
|
||||
|
||||
// Find first shape with this num_elements to use as vertex template
|
||||
shape_t *ref = NULL;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (((shape_t*) vec_get(shapes, i))->num_elements == ne) {
|
||||
ref = (shape_t*) vec_get(shapes, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
g_shape_groups[gi].num_elements = ne;
|
||||
g_shape_groups[gi].vbuf = sg_make_buffer(&(sg_buffer_desc){
|
||||
.data = { ref->verts, (size_t)ne * sizeof(shape_vertex_t) },
|
||||
.label = "Shape group verts",
|
||||
});
|
||||
gi++;
|
||||
}
|
||||
g_shape_group_count = group_count;
|
||||
|
||||
FREE(ne_seen);
|
||||
|
||||
panel_log(3, "[shapes] pool_rebuild: %d shapes, %d groups, data_buf=%d data_view=%d",
|
||||
n, group_count, g_shape_data_sbuf.id, g_shape_data_view.id);
|
||||
for (int gi = 0; gi < group_count; gi++) {
|
||||
panel_log(3, "[shapes] group[%d]: ne=%u vbuf=%d",
|
||||
gi, g_shape_groups[gi].num_elements, g_shape_groups[gi].vbuf.id);
|
||||
}
|
||||
|
||||
g_shape_vert_count = total_verts;
|
||||
g_shape_idx_count = total_indices;
|
||||
g_shape_pool_dirty = false;
|
||||
}
|
||||
|
||||
static void shape_pool_shutdown(void)
|
||||
{
|
||||
if (g_shape_vbuf.id) { sg_destroy_buffer(g_shape_vbuf); g_shape_vbuf.id = 0; }
|
||||
if (g_shape_ibuf.id) { sg_destroy_buffer(g_shape_ibuf); g_shape_ibuf.id = 0; }
|
||||
g_shape_vert_count = 0;
|
||||
g_shape_idx_count = 0;
|
||||
for (int i = 0; i < g_shape_group_count; i++) {
|
||||
if (g_shape_groups[i].vbuf.id) sg_destroy_buffer(g_shape_groups[i].vbuf);
|
||||
}
|
||||
FREE(g_shape_groups);
|
||||
g_shape_groups = NULL;
|
||||
g_shape_group_count = 0;
|
||||
|
||||
if (g_shape_data_view.id) { sg_destroy_view(g_shape_data_view); g_shape_data_view.id = 0; }
|
||||
if (g_instance_map_view.id) { sg_destroy_view(g_instance_map_view); g_instance_map_view.id = 0; }
|
||||
if (g_shape_data_sbuf.id) { sg_destroy_buffer(g_shape_data_sbuf); g_shape_data_sbuf.id = 0; }
|
||||
if (g_instance_map_sbuf.id) { sg_destroy_buffer(g_instance_map_sbuf); g_instance_map_sbuf.id = 0; }
|
||||
g_instance_map_capacity = 0;
|
||||
if (g_upload_buf) { FREE(g_upload_buf); g_upload_buf = NULL; }
|
||||
g_upload_buf_cap = 0;
|
||||
}
|
||||
|
||||
#define SHAPE_HOVER_PX 6.0f
|
||||
@@ -172,11 +332,23 @@ static void shape_build_transform(shape_t *s)
|
||||
glm_scale_make(S, (vec3){s->sx, s->sy, 1.0f});
|
||||
glm_mat4_mul(R, S, RS);
|
||||
glm_mat4_mul(T, RS, s->uniform.transform);
|
||||
s->cos_r = R[0][0];
|
||||
s->sin_r = R[0][1];
|
||||
g_shape_data_dirty = true;
|
||||
}
|
||||
|
||||
static void shape_retranslate(shape_t *s)
|
||||
{
|
||||
s->uniform.transform[3][0] = s->cx;
|
||||
s->uniform.transform[3][1] = s->cy;
|
||||
g_shape_data_dirty = true;
|
||||
}
|
||||
|
||||
static void shape_make_buffers(shape_t *s)
|
||||
{
|
||||
(void)s;
|
||||
for (int i = 0; i < g_shape_group_count; i++) {
|
||||
if (g_shape_groups[i].num_elements == s->num_elements) return;
|
||||
}
|
||||
g_shape_pool_dirty = true;
|
||||
}
|
||||
|
||||
@@ -194,9 +366,11 @@ static void shape_regenerate(shape_t *s)
|
||||
|
||||
static void shape_set_state(shape_t *s, bool hovered, bool selected)
|
||||
{
|
||||
uint32_t new_state = selected ? 2u : (hovered ? 1u : 0u);
|
||||
if (s->uniform.state != new_state) g_shape_data_dirty = true;
|
||||
s->hovered = hovered;
|
||||
s->selected = selected;
|
||||
s->uniform.state = selected ? 2u : (hovered ? 1u : 0u);
|
||||
s->uniform.state = new_state;
|
||||
}
|
||||
|
||||
static bool point_in_polygon(float px, float py, shape_vertex_t *verts, uint32_t n)
|
||||
@@ -213,7 +387,7 @@ static bool point_in_polygon(float px, float py, shape_vertex_t *verts, uint32_t
|
||||
|
||||
static bool shape_hit_test(shape_t *s, float wx, float wy, float world_tol)
|
||||
{
|
||||
float sc = cosf(s->rotation), ss = sinf(s->rotation);
|
||||
float sc = s->cos_r, ss = s->sin_r;
|
||||
float dx = wx - s->cx, dy = wy - s->cy;
|
||||
float lx = (dx * sc + dy * ss) / s->sx;
|
||||
float ly = (-dx * ss + dy * sc) / s->sy;
|
||||
|
||||
Reference in New Issue
Block a user