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:
2026-05-01 00:10:19 +02:00
parent e71641c094
commit c4d657043c
17 changed files with 2917 additions and 289 deletions

View File

@@ -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;