commit 6fad3dedd51e6f70b0d16596393c61329d6558de
parent 2aed9170f846b7c8036b4ea0c50634024803ed44
Author: Samdal <samdal@protonmail.com>
Date: Sat, 22 Mar 2025 23:59:01 +0100
instancing
Diffstat:
7 files changed, 203 insertions(+), 52 deletions(-)
diff --git a/build.sh b/build.sh
@@ -13,6 +13,7 @@ do
"simple_triangle") sources=(./examples/simple_triangle.c) ;;
"simple_texture") sources=(./examples/simple_texture.c) ;;
"hello_window") sources=(./examples/hello_window.c) ;;
+ "instancing") sources=(./examples/instancing.c) ;;
esac
done
diff --git a/examples/instancing.c b/examples/instancing.c
@@ -0,0 +1,136 @@
+//////////////////////////////////////////////////////////////////
+// instancing.c
+
+#include "revolver_inc.h"
+#include "revolver_inc.c"
+
+//////////////////////////////////////////////////////////////////
+// data
+
+f32 v_data[] = {
+ 0.00, 0.05, 1, 0, 0,
+ -0.05, -0.05, 0, 1, 0,
+ 0.05, -0.05, 0, 0, 1,
+};
+
+rv_str8 v_src = S("#version 330 core\n" rv_strify(
+ layout(location = 0) in vec2 a_pos;
+ layout(location = 1) in vec3 a_color;
+ layout(location = 2) in vec2 a_offset;
+ precision mediump float;
+ out vec3 f_color;
+ void main()
+ {
+ gl_Position = vec4(a_pos + a_offset, 0.0, 1.0);
+ f_color = a_color;
+ }
+));
+
+rv_str8 f_src = S("#version 330 core\n" rv_strify(
+ precision mediump float;
+ in vec3 f_color;
+ out vec4 frag_color;
+ void main()
+ {
+ frag_color = vec4(f_color, 1.0);
+ }
+));
+
+rv_vec2 g_translations[100] = {0};
+
+//////////////////////////////////////////////////////////////////
+
+int main(void) {
+ rv_window_desc_t desc = {.name = S("App"), .attach_render = true};
+ rv_window_handle_t* window = rv_create_window(desc);
+
+ rv_arena* arena = rv_arena_alloc();
+ rv_render_pass_list_t rpass_list = {0};
+
+ // Translation data
+ int32_t index = 0;
+ float offset = 0.1f;
+ for (int32_t y = -10; y < 10; y += 2) {
+ for (int32_t x = -10; x < 10; x += 2) {
+ g_translations[index].x = (float)x / 10.0f + offset;
+ g_translations[index].y = (float)y / 10.0f + offset;
+ index++;
+ }
+ }
+
+ // make render objects
+ rv_vbo_t* vbo = rv_push_compound(arena, rv_vbo_t, {.data = v_data, .size = sizeof(v_data)});
+ rv_vbo_t* vboi = rv_push_compound(arena, rv_vbo_t, {.data = g_translations, .size = sizeof(g_translations)});
+ rv_shader_t* vertex = rv_push_compound(arena, rv_shader_t, {.source = v_src, .type = RV_SHADER_TYPE_VERTEX});
+ rv_shader_t* fragment = rv_push_compound(arena, rv_shader_t, {.source = f_src, .type = RV_SHADER_TYPE_FRAGMENT});
+ rv_pipeline_t* pip = rv_push(arena, rv_pipeline_t);
+
+ // construct pipeline
+ rv_pipeline_push_shader(arena, pip, vertex);
+ rv_pipeline_push_shader(arena, pip, fragment);
+ rv_pipeline_push_vattr(arena, pip, RV_VATTR_TYPE_FLOAT2, 0, 0);
+ rv_pipeline_push_vattr(arena, pip, RV_VATTR_TYPE_FLOAT3, 0, 0);
+ rv_pipeline_push_vattr(arena, pip, RV_VATTR_TYPE_FLOAT2, 1, 1);
+
+ // instructions to create render objects
+ rv_command_list_t create_instructions = {0};
+ rv_cmd_push_obj(arena, &create_instructions, RV_COMMAND_OBJ_VERTEX, RV_RENDER_OBJ_OP_CREATE, vbo);
+ rv_cmd_push_obj(arena, &create_instructions, RV_COMMAND_OBJ_VERTEX, RV_RENDER_OBJ_OP_CREATE, vboi);
+ rv_cmd_push_obj(arena, &create_instructions, RV_COMMAND_OBJ_SHADER, RV_RENDER_OBJ_OP_CREATE, vertex);
+ rv_cmd_push_obj(arena, &create_instructions, RV_COMMAND_OBJ_SHADER, RV_RENDER_OBJ_OP_CREATE, fragment);
+ rv_cmd_push_obj(arena, &create_instructions, RV_COMMAND_OBJ_PIPELINE, RV_RENDER_OBJ_OP_CREATE, pip);
+
+ // instructions to render
+ rv_command_list_t render_instructions = {0};
+ rv_cmd_push_obj(arena, &render_instructions, RV_COMMAND_OBJ_PIPELINE, RV_RENDER_OBJ_OP_BIND, pip);
+ rv_cmd_push_obj(arena, &render_instructions, RV_COMMAND_OBJ_VERTEX, RV_RENDER_OBJ_OP_BIND, vbo);
+ rv_cmd_push_obj(arena, &render_instructions, RV_COMMAND_OBJ_VERTEX, RV_RENDER_OBJ_OP_BIND, vboi)->obj.vbo_bind.bind_index = 1;
+ rv_cmd_push_compound(arena, &render_instructions, RV_COMMAND_DRAW, {.draw = {.count = 3, .instance_count = 100}});
+ rv_cmd_push_obj(arena, &render_instructions, RV_COMMAND_OBJ_PIPELINE, RV_RENDER_OBJ_OP_BIND, NULL);
+
+ // copy over create commands
+ rv_render_pass_t* create_rpass = rv_render_push_render_pass(arena, &rpass_list);
+ rv_render_copy_commands(arena, &create_rpass->commands, &create_instructions);
+
+ while(1) {
+ rv_temp_arena scratch = rv_scratch_begin(0, 0);
+
+ // pick renderpass
+ rv_render_pass_t* rpass = rv_render_push_render_pass(scratch.arena, &rpass_list);
+
+ // process events
+ for (rv_event_t* e = rv_get_events(scratch.arena, 0); e; e = e->next) {
+ if (e->type == RV_EVENT_WINDOW_CLOSE) {
+ if (e->window_close == window) {
+ goto exit_program;
+ }
+ }
+ if (e->type == RV_EVENT_WINDOW_RESIZE) {
+ // set viewport
+ rv_command_t* viewport = rv_cmd_push_type(scratch.arena, &rpass->commands, RV_COMMAND_SET_VIEWPORT);
+ viewport->viewport = rv_v4(.xy = {0, 0}, .zw = rv_window_size(window));
+ }
+ }
+
+
+ { // clear
+ rv_command_t* clear = rv_cmd_push_type(scratch.arena, &rpass->commands, RV_COMMAND_CLEAR);
+ rv_render_clear_desc_t* clear_desc = rv_render_push_clear_desc(scratch.arena, clear, RV_RENDER_CLEAR_FLAG_COLOR);
+ clear_desc->color_v = rv_v4(0.1, 0.1, 0.1, 1.0);
+ }
+
+ { // draw custom shader
+ // copy render commands
+ rv_render_copy_commands(scratch.arena, &rpass->commands, &render_instructions);
+ }
+
+ if (window) { // render screen
+ rv_window_render_commit(window, &rpass_list);
+ }
+ rv_scratch_end(scratch);
+ rpass_list = (rv_render_pass_list_t){0};
+ }
+exit_program:
+
+ return 0;
+}
diff --git a/examples/simple_triangle.c b/examples/simple_triangle.c
@@ -53,8 +53,8 @@ int main(void) {
// construct pipeline
rv_pipeline_push_shader(arena, pip, vertex);
rv_pipeline_push_shader(arena, pip, fragment);
- rv_pipeline_push_vattr(arena, pip, RV_VATTR_TYPE_FLOAT2);
- rv_pipeline_push_vattr(arena, pip, RV_VATTR_TYPE_FLOAT3);
+ rv_pipeline_push_vattr(arena, pip, RV_VATTR_TYPE_FLOAT2, 0, 0);
+ rv_pipeline_push_vattr(arena, pip, RV_VATTR_TYPE_FLOAT3, 0, 0);
// instructions to create render objects
rv_command_list_t create_instructions = {0};
diff --git a/examples/test.c b/examples/test.c
@@ -105,8 +105,8 @@ int main(void) {
// construct pipeline
rv_pipeline_push_shader(arena, triangle_pip, triangle_vertex);
rv_pipeline_push_shader(arena, triangle_pip, triangle_fragment);
- rv_pipeline_push_vattr(arena, triangle_pip, RV_VATTR_TYPE_FLOAT2);
- rv_pipeline_push_vattr(arena, triangle_pip, RV_VATTR_TYPE_FLOAT3);
+ rv_pipeline_push_vattr(arena, triangle_pip, RV_VATTR_TYPE_FLOAT2, 0, 0);
+ rv_pipeline_push_vattr(arena, triangle_pip, RV_VATTR_TYPE_FLOAT3, 0, 0);
// instructions to create render objects
rv_cmd_push_obj(arena, &create_instructions, RV_COMMAND_OBJ_VERTEX, RV_RENDER_OBJ_OP_CREATE, triangle_vbo);
@@ -133,8 +133,8 @@ int main(void) {
// construct pipeline
rv_pipeline_push_shader(arena, post_pip, post_vertex);
rv_pipeline_push_shader(arena, post_pip, post_fragment);
- rv_pipeline_push_vattr(arena, post_pip, RV_VATTR_TYPE_FLOAT2);
- rv_pipeline_push_vattr(arena, post_pip, RV_VATTR_TYPE_FLOAT2);
+ rv_pipeline_push_vattr(arena, post_pip, RV_VATTR_TYPE_FLOAT2, 0, 0);
+ rv_pipeline_push_vattr(arena, post_pip, RV_VATTR_TYPE_FLOAT2, 0, 0);
rv_uniform_t* u_tex = rv_pipeline_push_uniform(arena, post_pip, S("u_tex"), RV_UNIFORM_TEX);
rv_uniform_t* u_time = rv_pipeline_push_uniform(arena, post_pip, S("u_time"), RV_UNIFORM_F32);
diff --git a/src/render/impl/opengl.c b/src/render/impl/opengl.c
@@ -75,7 +75,6 @@ RV_INTERNAL GLenum rv_tex_wrap_to_gl(rv_texture_wrap_t type) {
struct {
rv_ibo_t* bound_ibo;
- rv_vbo_t* bound_vbo;
rv_pipeline_t* bound_pip;
rv_framebuffer_t* bound_fbuf;
} ogl_state;
@@ -116,11 +115,6 @@ RV_GLOBAL void rv_window_render_commit(rv_window_handle_t* window, rv_render_pas
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
- if (ogl_state.bound_vbo) {
- ogl_state.bound_vbo = NULL;
- glBindBuffer(GL_ARRAY_BUFFER, 0);
- }
-
if (ogl_state.bound_pip) {
ogl_state.bound_pip = NULL;
glUseProgram(0);
@@ -171,29 +165,61 @@ RV_GLOBAL void rv_window_render_commit(rv_window_handle_t* window, rv_render_pas
glGenBuffers(1, &vbo->handle.u);
glBindBuffer(GL_ARRAY_BUFFER, vbo->handle.u);
glBufferData(GL_ARRAY_BUFFER, vbo->size, vbo->data, rv_usage_to_gl(vbo->usage));
-
- glBindBuffer(GL_ARRAY_BUFFER, ogl_safe_set(ogl_state.bound_vbo, handle));
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
} break;
case RV_RENDER_OBJ_OP_UPDATE: {
glBindBuffer(GL_ARRAY_BUFFER, vbo->handle.u);
glBufferData(GL_ARRAY_BUFFER, c->obj.vbo_update.size, c->obj.vbo_update.data, rv_usage_to_gl(vbo->usage));
-
- glBindBuffer(GL_ARRAY_BUFFER, ogl_safe_set(ogl_state.bound_vbo, handle));
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
} break;
case RV_RENDER_OBJ_OP_BIND: {
- ogl_state.bound_vbo = vbo;
- glBindBuffer(GL_ARRAY_BUFFER, ogl_safe_set(ogl_state.bound_vbo, handle));
+ if (ogl_state.bound_pip) {
+ s32 bind_index = c->obj.vbo_bind.bind_index;
+ glBindBuffer(GL_ARRAY_BUFFER, vbo->handle.u);
+
+ // set vattrs
+ s64 stride = 0;
+ for (rv_vattr_t* v = ogl_state.bound_pip->vattr_first; v; v = v->next) {
+ if (v->bind_index == bind_index) {
+ stride += rv_vattr_type_size(v->type);
+ }
+ }
+ s64 offset = 0;
+ s64 i = 0;
+ for (rv_vattr_t* v = ogl_state.bound_pip->vattr_first; v; v = v->next, i++) {
+ if (v->bind_index == bind_index) {
+ glEnableVertexAttribArray(i);
+ switch (v->type) {
+ case RV_VATTR_TYPE_FLOAT4: glVertexAttribPointer(i, 4, GL_FLOAT, GL_FALSE, stride, (void*)offset); break;
+ case RV_VATTR_TYPE_FLOAT3: glVertexAttribPointer(i, 3, GL_FLOAT, GL_FALSE, stride, (void*)offset); break;
+ case RV_VATTR_TYPE_FLOAT2: glVertexAttribPointer(i, 2, GL_FLOAT, GL_FALSE, stride, (void*)offset); break;
+ case RV_VATTR_TYPE_FLOAT: glVertexAttribPointer(i, 1, GL_FLOAT, GL_FALSE, stride, (void*)offset); break;
+ case RV_VATTR_TYPE_UINT4: glVertexAttribIPointer(i, 4, GL_UNSIGNED_INT, stride, (void*)offset); break;
+ case RV_VATTR_TYPE_UINT3: glVertexAttribIPointer(i, 3, GL_UNSIGNED_INT, stride, (void*)offset); break;
+ case RV_VATTR_TYPE_UINT2: glVertexAttribIPointer(i, 2, GL_UNSIGNED_INT, stride, (void*)offset); break;
+ case RV_VATTR_TYPE_UINT: glVertexAttribIPointer(i, 1, GL_UNSIGNED_INT, stride, (void*)offset); break;
+ case RV_VATTR_TYPE_BYTE: glVertexAttribPointer(i, 1, GL_UNSIGNED_BYTE, GL_TRUE, stride, (void*)offset); break;
+ case RV_VATTR_TYPE_BYTE2: glVertexAttribPointer(i, 2, GL_UNSIGNED_BYTE, GL_TRUE, stride, (void*)offset); break;
+ case RV_VATTR_TYPE_BYTE3: glVertexAttribPointer(i, 3, GL_UNSIGNED_BYTE, GL_TRUE, stride, (void*)offset); break;
+ case RV_VATTR_TYPE_BYTE4: glVertexAttribPointer(i, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, (void*)offset); break;
+ default: rv_unreachable(); break;
+ }
+ offset += rv_vattr_type_size(v->type);
+ if (v->divisor) {
+ glVertexAttribDivisor(i, v->divisor);
+ }
+ }
+ }
+ } else {
+ rv_unreachable();
+ }
} break;
case RV_RENDER_OBJ_OP_DELETE: {
glDeleteBuffers(1, &vbo->handle.u);
vbo->handle.u = 0;
- if (ogl_state.bound_vbo == vbo) {
- ogl_state.bound_vbo = NULL;
- glBindBuffer(GL_ARRAY_BUFFER, 0);
- }
} break;
}
} break;
@@ -531,31 +557,6 @@ RV_GLOBAL void rv_window_render_commit(rv_window_handle_t* window, rv_render_pas
case RV_COMMAND_DRAW: {
if (ogl_state.bound_pip) {
- s64 stride = 0;
- for (rv_vattr_t* v = ogl_state.bound_pip->vattr_first; v; v = v->next) {
- stride += rv_vattr_type_size(v->type);
- }
- s64 offset = 0;
- s64 i = 0;
- for (rv_vattr_t* v = ogl_state.bound_pip->vattr_first; v; v = v->next, i++) {
- glEnableVertexAttribArray(i);
- switch (v->type) {
- case RV_VATTR_TYPE_FLOAT4: glVertexAttribPointer(i, 4, GL_FLOAT, GL_FALSE, stride, (void*)offset); break;
- case RV_VATTR_TYPE_FLOAT3: glVertexAttribPointer(i, 3, GL_FLOAT, GL_FALSE, stride, (void*)offset); break;
- case RV_VATTR_TYPE_FLOAT2: glVertexAttribPointer(i, 2, GL_FLOAT, GL_FALSE, stride, (void*)offset); break;
- case RV_VATTR_TYPE_FLOAT: glVertexAttribPointer(i, 1, GL_FLOAT, GL_FALSE, stride, (void*)offset); break;
- case RV_VATTR_TYPE_UINT4: glVertexAttribIPointer(i, 4, GL_UNSIGNED_INT, stride, (void*)offset); break;
- case RV_VATTR_TYPE_UINT3: glVertexAttribIPointer(i, 3, GL_UNSIGNED_INT, stride, (void*)offset); break;
- case RV_VATTR_TYPE_UINT2: glVertexAttribIPointer(i, 2, GL_UNSIGNED_INT, stride, (void*)offset); break;
- case RV_VATTR_TYPE_UINT: glVertexAttribIPointer(i, 1, GL_UNSIGNED_INT, stride, (void*)offset); break;
- case RV_VATTR_TYPE_BYTE: glVertexAttribPointer(i, 1, GL_UNSIGNED_BYTE, GL_TRUE, stride, (void*)offset); break;
- case RV_VATTR_TYPE_BYTE2: glVertexAttribPointer(i, 2, GL_UNSIGNED_BYTE, GL_TRUE, stride, (void*)offset); break;
- case RV_VATTR_TYPE_BYTE3: glVertexAttribPointer(i, 3, GL_UNSIGNED_BYTE, GL_TRUE, stride, (void*)offset); break;
- case RV_VATTR_TYPE_BYTE4: glVertexAttribPointer(i, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, (void*)offset); break;
- default: rv_unreachable(); break;
- }
- offset += rv_vattr_type_size(v->type);
- }
GLenum prim = rv_primitive_to_gl(c->draw.primitive);
// TODO(Samdal): instancing...
if (ogl_state.bound_ibo) {
@@ -567,9 +568,17 @@ RV_GLOBAL void rv_window_render_commit(rv_window_handle_t* window, rv_render_pas
case 2: itype = GL_UNSIGNED_SHORT; break;
case 1: itype = GL_UNSIGNED_BYTE; break;
}
- glDrawElements(prim, c->draw.count, itype, (void*)(s64)c->draw.first);
+ if (c->draw.instance_count) {
+ glDrawElementsInstanced(prim, c->draw.count, itype, (void*)(s64)c->draw.first, c->draw.instance_count);
+ } else {
+ glDrawElements(prim, c->draw.count, itype, (void*)(s64)c->draw.first);
+ }
} else {
- glDrawArrays(prim, c->draw.first, c->draw.count);
+ if (c->draw.instance_count) {
+ glDrawArraysInstanced(prim, c->draw.first, c->draw.count, c->draw.instance_count);
+ } else {
+ glDrawArrays(prim, c->draw.first, c->draw.count);
+ }
}
} else {
rv_unreachable();
diff --git a/src/render/render.h b/src/render/render.h
@@ -155,6 +155,8 @@ typedef struct rv_vattr_t rv_vattr_t;
struct rv_vattr_t {
rv_vattr_t* next;
rv_vattr_type_t type;
+ s32 bind_index;
+ s32 divisor;
};
typedef struct {
@@ -248,6 +250,8 @@ struct rv_command_t {
struct {void* data; s64 size;} ibo_update;
struct {void* data; s64 size;} vbo_update;
+ struct { s32 bind_index; } vbo_bind;
+
struct {
void* data;
rv_rect sub_part;
@@ -275,6 +279,7 @@ struct rv_command_t {
rv_primitive_type_t primitive;
s32 first;
s64 count;
+ s64 instance_count;
} draw;
struct {
@@ -344,7 +349,7 @@ RV_GLOBAL rv_framebuffer_t* rv_framebuffer_color_simple(rv_arena* arena, rv_vec2
// pipeline modificatoin helpers
RV_GLOBAL rv_shader_node_t* rv_pipeline_push_shader(rv_arena* arena, rv_pipeline_t* pipeline, rv_shader_t* shader);
RV_GLOBAL rv_uniform_t* rv_pipeline_push_uniform(rv_arena* arena, rv_pipeline_t* pipeline, rv_str8 name, rv_uniform_type_t type);
-RV_GLOBAL rv_vattr_t* rv_pipeline_push_vattr(rv_arena* arena, rv_pipeline_t* pipeline, rv_vattr_type_t type);
+RV_GLOBAL rv_vattr_t* rv_pipeline_push_vattr(rv_arena* arena, rv_pipeline_t* pipeline, rv_vattr_type_t type, s32 bind_index, s32 divisor);
//////////////////////////////////////////////////////////////////
// Submit
diff --git a/src/render/render_helpers.c b/src/render/render_helpers.c
@@ -121,9 +121,9 @@ RV_GLOBAL rv_uniform_t* rv_pipeline_push_uniform(rv_arena* arena, rv_pipeline_t*
return res;
}
-RV_GLOBAL rv_vattr_t* rv_pipeline_push_vattr(rv_arena* arena, rv_pipeline_t* pipeline, rv_vattr_type_t type)
+RV_GLOBAL rv_vattr_t* rv_pipeline_push_vattr(rv_arena* arena, rv_pipeline_t* pipeline, rv_vattr_type_t type, s32 bind_index, s32 divisor)
{
- rv_vattr_t* res = rv_push_compound(arena, rv_vattr_t, {.type = type});
+ rv_vattr_t* res = rv_push_compound(arena, rv_vattr_t, {.type = type, .bind_index = bind_index, .divisor = divisor});
rv_llist_queue_push(pipeline->vattr_first, pipeline->vattr_last, res);
return res;
}