summaryrefslogtreecommitdiff
path: root/rend.c
diff options
context:
space:
mode:
authorVasco <vasco.guilherme.alves@gmail.com>2026-08-12 17:47:50 +0100
committerVasco <vasco.guilherme.alves@gmail.com>2026-08-12 17:47:50 +0100
commit95e3e76fd9c789cbf2d4409ef49456addc71643f (patch)
tree34161614f5f0ae909f58eb5e58a2ffd26c2f0e9f /rend.c
rend 1.0.1HEADmain
Diffstat (limited to 'rend.c')
-rw-r--r--rend.c604
1 files changed, 604 insertions, 0 deletions
diff --git a/rend.c b/rend.c
new file mode 100644
index 0000000..68634d3
--- /dev/null
+++ b/rend.c
@@ -0,0 +1,604 @@
+#include <assert.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+#include <unistd.h>
+
+#include "rend.h"
+#include "rend_internal.h"
+#include "rend_vk.c"
+#include "rend_vk_internal.h"
+
+/* clean up functions */
+
+static void rend__renderer_destroy_recursive(RendRenderer renderer);
+static void rend__pipeline_destroy_recursive(RendPipeline pipeline);
+static bool rend__renderer_init_recursive(RendRenderer renderer, RendBackendType backend, bool auto_pick);
+
+static RendRenderer rend_renderers_head = NULL;
+
+// TODO: automatically clear buffers and textures
+// static RendBuffer rend_buffer_head = NULL;
+// static RendTexture rend_texture_head = NULL;
+
+bool rend_backend_vk14_initialized = false;
+
+RendVTable rend_vtables[] = {
+ [REND_BACKEND_VULKAN_14] = {
+ .renderer_create = rend_vk14_renderer_create,
+ .renderer_destroy = rend_vk14_renderer_destroy,
+ .renderer_frame_begin = rend_vk14_renderer_frame_begin,
+ .renderer_frame_end = rend_vk14_renderer_frame_end,
+
+ .buffer_create_lifetime = rend_vk14_buffer_create_lifetime,
+ .buffer_destroy = rend_vk14_buffer_destroy,
+ .buffer_copy = rend_vk14_buffer_copy,
+
+ .texture_create = rend_vk14_texture_create,
+ .texture_destroy = rend_vk14_texture_destroy,
+ .texture_copy_buffer = rend_vk14_texture_copy_buffer,
+ .texture_blit = rend_vk14_texture_blit,
+
+ .pipeline_create = rend_vk14_pipeline_create,
+ .pipeline_bind = rend_vk14_pipeline_bind,
+ .pipeline_push_constants = rend_vk14_pipeline_push_constants,
+
+ .pipeline_bind_vertex_buffer = rend_vk14_pipeline_bind_vertex_buffer,
+ .pipeline_bind_index_buffer = rend_vk14_pipeline_bind_index_buffer,
+
+ .pipeline_dispatch = rend_vk14_pipeline_dispatch,
+ .pipeline_draw = rend_vk14_pipeline_draw,
+ .pipeline_draw_indexed = rend_vk14_pipeline_draw_indexed,
+ .pipeline_set_blend = rend_vk14_pipeline_set_blend,
+
+ .renderer_render_pass_begin = rend_vk14_renderer_render_pass_begin,
+ .renderer_render_pass_begin_texture = rend_vk14_renderer_render_pass_begin_texture,
+ .renderer_render_pass_end = rend_vk14_renderer_render_pass_end,
+ .renderer_render_pass_end_texture = rend_vk14_renderer_render_pass_end_texture,
+
+ .descriptor_write_buffer = rend_vk14_descriptor_write_buffer,
+ .descriptor_write_texture = rend_vk14_descriptor_write_texture,
+ },
+};
+
+extern void
+rend_quit()
+{
+ rend__renderer_destroy_recursive(rend_renderers_head);
+
+ if (rend_backend_vk14_initialized) {
+ rend_vk14_quit();
+ rend_backend_vk14_initialized = false;
+ }
+
+}
+
+extern RendRenderer
+rend_renderer_create(P_Window *target, RendBackendType backend, void* device, bool vsync, RendBindingInfo *bind_info)
+{
+ RendRenderer rend = rmalloc(sizeof *rend);
+ rend->next = rend_renderers_head;
+ rend->prev = NULL;
+ rend_renderers_head = rend;
+ if(rend->next) {
+ rend->next->prev = rend;
+ }
+
+ /* create vtable for backend */
+ rend__renderer_init_recursive(rend, backend, false);
+
+ rend->vsync = vsync;
+ rend->bind_info = *bind_info;
+ rend->window = target;
+
+ rend->pipeline_head = 0;
+
+ if (rend_vtables[rend->backend].renderer_create(rend, target)) {
+ return rend;
+ }
+
+ return NULL;
+}
+
+extern void
+rend_renderer_destroy(RendRenderer renderer)
+{
+ if (!renderer) return;
+
+ rend__pipeline_destroy_recursive(renderer->pipeline_head);
+
+ if (renderer->backend != 0) rend_vtables[renderer->backend].renderer_destroy(renderer);
+
+ if (renderer->prev) renderer->prev->next = renderer->next;
+ else rend_renderers_head = renderer->next;
+
+ if (renderer->next) renderer->next->prev = renderer->prev;
+
+ rfree(renderer);
+}
+
+extern bool
+rend_renderer_frame_begin(RendRenderer renderer)
+{
+ renderer->in_frame = 1;
+ return rend_vtables[renderer->backend].renderer_frame_begin(renderer);
+}
+
+extern void
+rend_renderer_frame_end(RendRenderer renderer, float *delta)
+{
+ renderer->in_frame = 0;
+ rend_vtables[renderer->backend].renderer_frame_end(renderer, delta);
+ renderer->frame_count++;
+}
+
+extern void
+rend_renderer_render_pass_begin(RendRenderer renderer, float r, float g ,float b, float a)
+{
+ renderer->in_pass = 1;
+ rend_vtables[renderer->backend].renderer_render_pass_begin(renderer, r, g, b, a);
+}
+
+extern void
+rend_renderer_render_pass_begin_texture(RendRenderer renderer, RendTexture *texture)
+{
+ renderer->in_pass = 1;
+ rend_vtables[renderer->backend].renderer_render_pass_begin_texture(renderer, texture);
+}
+
+extern void
+rend_renderer_render_pass_end_texture(RendRenderer renderer, RendTexture *texture)
+{
+ renderer->in_pass = 0;
+ rend_vtables[renderer->backend].renderer_render_pass_end_texture(renderer, texture);
+}
+
+
+extern void
+rend_renderer_render_pass_end(RendRenderer renderer)
+{
+ renderer->in_pass = 0;
+ rend_vtables[renderer->backend].renderer_render_pass_end(renderer);
+}
+
+
+extern void
+rend_descriptor_write_ubo(RendRenderer renderer, RendBuffer ubo, uint32_t binding, uint32_t slot)
+{
+ rend_vtables[renderer->backend].descriptor_write_buffer(renderer, ubo, binding, slot, 0, ubo.size, true);
+}
+
+extern void
+rend_descriptor_write_ssbo(RendRenderer renderer, RendBuffer ssbo, uint32_t binding, uint32_t slot)
+{
+ rend_vtables[renderer->backend].descriptor_write_buffer(renderer, ssbo, binding, slot, 0, ssbo.size, false);
+}
+
+extern RendBuffer
+rend_buffer_create(RendRenderer renderer, size_t size, RendBufferType type, bool gpu) {
+ RendBuffer buffer = rend_vtables[renderer->backend].buffer_create_lifetime(renderer, size, type, gpu, REND_LIFETIME_PERMANENT);
+ buffer.backend = renderer->backend;
+ return buffer;
+}
+
+extern void
+rend_buffer_destroy(RendBuffer *buffer) {
+ rend_vtables[buffer->backend].buffer_destroy(buffer);
+}
+
+extern void
+rend_buffer_write(RendRenderer renderer, RendBuffer *buffer, const void *data, size_t size, size_t offset)
+{
+ if (buffer->mapped_memory) {
+ uint8_t *ptr = buffer->mapped_memory;
+ memcpy(ptr + offset, data, size);
+ } else {
+ /* if the memory is bound to the device we must create and deallocate a transfer buffer */
+ RendBuffer transfer_buffer = rend_vtables[buffer->backend].buffer_create_lifetime(renderer, size, REND_BUFFER_TRANSFER, false, REND_LIFETIME_FRAME);
+ memcpy(transfer_buffer.mapped_memory, data, size);
+ rend_vtables[buffer->backend].buffer_copy(renderer, buffer, offset, &transfer_buffer, 0, size);
+ rend_vtables[buffer->backend].buffer_destroy(&transfer_buffer);
+ }
+}
+
+extern void
+rend_buffer_copy(RendRenderer renderer, RendBuffer *dest, size_t dest_offset, RendBuffer *src, size_t src_offset, size_t bytes)
+{
+ rend_vtables[renderer->backend].buffer_copy(renderer, dest, dest_offset, src, src_offset, bytes);
+}
+
+extern uint64_t
+rend_buffer_address(RendBuffer *buffer)
+{
+ assert(buffer != NULL);
+ return buffer->gpu_address;
+}
+
+extern RendTexture
+rend_texture_create(RendRenderer renderer, uint32_t width, uint32_t height, uint32_t depth, uint32_t mip_levels, uint32_t layers, RendFormat format)
+{
+ RendTexture tex = rend_vtables[renderer->backend].texture_create(renderer, width, height, depth, mip_levels, layers, format);
+ tex.backend = renderer->backend;
+ return tex;
+}
+
+extern RendTexture
+rend_texture_create_from_data(RendRenderer renderer, const void *data, uint32_t width, uint32_t height, RendFormat format)
+{
+ RendTexture tex = rend_vtables[renderer->backend].texture_create(renderer, width, height, 1, 1, 1, format);
+ tex.backend = renderer->backend;
+
+ uint32_t size = width * height * rend_format_size[format];
+
+ RendBuffer staging_buffer = rend_vtables[renderer->backend].buffer_create_lifetime(renderer, size, REND_BUFFER_TRANSFER, false, REND_LIFETIME_FRAME);
+ memcpy(staging_buffer.mapped_memory, data, size);
+ rend_vtables[renderer->backend].texture_copy_buffer(renderer, &tex, &staging_buffer);
+ rend_vtables[renderer->backend].buffer_destroy(&staging_buffer);
+ return tex;
+}
+
+extern void
+rend_texture_copy_data(RendRenderer renderer, RendTexture *texture, const void *data, size_t size)
+{
+ RendBuffer staging_buffer = rend_vtables[renderer->backend].buffer_create_lifetime(renderer, size, REND_BUFFER_TRANSFER, false, REND_LIFETIME_FRAME);
+ memcpy(staging_buffer.mapped_memory, data, size);
+ rend_vtables[renderer->backend].texture_copy_buffer(renderer, texture, &staging_buffer);
+ rend_vtables[renderer->backend].buffer_destroy(&staging_buffer);
+}
+
+
+extern void
+rend_texture_copy_buffer(RendRenderer renderer, RendTexture *texture, RendBuffer *buffer)
+{
+ rend_vtables[renderer->backend].texture_copy_buffer(renderer, texture, buffer);
+}
+
+extern void
+rend_texture_blit(RendRenderer renderer, RendTexture *src, RendTexture *dst, uint32_t src_x, uint32_t src_y, uint32_t src_w, uint32_t src_h, uint32_t dst_x, uint32_t dst_y, uint32_t dst_w, uint32_t dst_h)
+{
+ rend_vtables[renderer->backend].texture_blit(renderer, src, dst, src_x, src_y, src_w, src_h, dst_x, dst_y, dst_w, dst_h);
+}
+
+extern uint64_t
+rend_texture_id(RendTexture *texture)
+{
+ return texture->id;
+}
+
+extern void
+rend_texture_destroy(RendRenderer renderer, RendTexture *tex)
+{
+ rend_vtables[renderer->backend].texture_destroy(renderer, tex);
+}
+
+extern void
+rend_pipeline_push_constants(RendPipeline pipeline, void *push_data, size_t size)
+{
+ assert(pipeline != NULL && "Pipeline handle is NULL!");
+ assert(rend_vtables[pipeline->backend].pipeline_push_constants && "Backend function not implemented!");
+ rend_vtables[pipeline->backend].pipeline_push_constants(pipeline, push_data, size);
+}
+
+extern void
+rend_pipeline_bind_vertex_buffer(RendPipeline pipeline, uint32_t binding, RendBuffer buffer, size_t offset)
+{
+ rend_vtables[pipeline->backend].pipeline_bind_vertex_buffer(pipeline, binding, buffer, offset);
+}
+
+extern void
+rend_pipeline_bind_index_buffer(RendPipeline pipeline, RendBuffer buffer, size_t offset, RendIndexType index_type)
+{
+ rend_vtables[pipeline->backend].pipeline_bind_index_buffer(pipeline, buffer, offset, index_type);
+}
+
+extern void
+rend_pipeline_bind_texture(RendPipeline pipeline, RendTexture *texture, uint32_t binding, uint32_t slot)
+{
+ rend_vtables[pipeline->backend].descriptor_write_texture(pipeline->backend_ctx, texture, binding, slot);
+}
+
+extern void
+rend_descriptor_write_texture(RendRenderer renderer, RendTexture *texture, uint32_t binding, uint32_t slot)
+{
+ rend_vtables[renderer->backend].descriptor_write_texture(renderer->context, texture, binding, slot);
+}
+
+
+extern RendPipeline
+rend_pipeline_create_graphics_spirv(RendRenderer renderer, uint8_t *vertex_bytes, size_t vertex_size, uint8_t *frag_bytes, size_t frag_size, const RendVertexBinding *vertex_bindings, uint32_t vertex_binding_count, const RendVertexAttributes *vertex_attributes, uint32_t vertex_attribute_count, const RendPushConstantInfo *push_constants, uint32_t push_constant_count, RendPolygonMode polygon_mode, RendCullMode cull_mode, RendTopology topology, RendFormat color_format, bool depth_test_enable)
+{
+ RendPipeline pipeline = rmalloc(sizeof *pipeline);
+
+ Rend__PipelineConfig config = {
+ .vertex_bindings = vertex_bindings,
+ .vertex_binding_count = vertex_binding_count,
+ .vertex_attributes = vertex_attributes,
+ .vertex_attribute_count = vertex_attribute_count,
+ .push_constants = push_constants,
+ .push_constant_count = push_constant_count,
+ .polygon_mode = polygon_mode,
+ .cull_mode = cull_mode,
+ .topology = topology,
+ .depth_test_enable = depth_test_enable,
+
+ .color_format = color_format,
+ };
+
+ if (rend_vtables[renderer->backend].pipeline_create(renderer, pipeline, config, REND__PIPELINE_GRAPHICS, vertex_bytes, vertex_size, frag_bytes, frag_size, NULL, 0)) {
+ pipeline->backend = renderer->backend;
+
+ pipeline->next = renderer->pipeline_head;
+ pipeline->prev = NULL;
+ renderer->pipeline_head = pipeline;
+ pipeline->type = REND__PIPELINE_GRAPHICS;
+ if(pipeline->next) {
+ pipeline->next->prev = pipeline;
+ }
+
+ return pipeline;
+ }
+ rfree(pipeline);
+ return NULL;
+}
+
+extern RendPipeline
+rend_pipeline_create_graphics_bindless_spirv(RendRenderer renderer, uint8_t *vertex_bytes, size_t vertex_size, uint8_t *frag_bytes, size_t frag_size, const RendPushConstantInfo *push_constants, uint32_t push_constant_count, RendPolygonMode polygon_mode, RendCullMode cull_mode, RendTopology topology, RendFormat color_format, bool depth_test_enable)
+{
+ RendPipeline pipeline = rmalloc(sizeof *pipeline);
+
+ Rend__PipelineConfig config = {
+ .vertex_bindings = NULL,
+ .vertex_binding_count = 0,
+ .vertex_attributes = NULL,
+ .vertex_attribute_count = 0,
+ .push_constants = push_constants,
+ .push_constant_count = push_constant_count,
+ .polygon_mode = polygon_mode,
+ .cull_mode = cull_mode,
+ .topology = topology,
+ .depth_test_enable = depth_test_enable,
+
+ .color_format = color_format,
+ };
+
+ if (rend_vtables[renderer->backend].pipeline_create(renderer, pipeline, config, REND__PIPELINE_GRAPHICS, vertex_bytes, vertex_size, frag_bytes, frag_size, NULL, 0)) {
+ pipeline->backend = renderer->backend;
+
+ pipeline->next = renderer->pipeline_head;
+ pipeline->prev = NULL;
+ renderer->pipeline_head = pipeline;
+ pipeline->type = REND__PIPELINE_GRAPHICS;
+ if(pipeline->next) {
+ pipeline->next->prev = pipeline;
+ }
+
+ return pipeline;
+ }
+ rfree(pipeline);
+ return NULL;
+}
+
+extern RendPipeline
+rend_pipeline_create_compute_spirv(RendRenderer renderer, const uint8_t *compute_bytes, size_t compute_size, const RendPushConstantInfo *push_constants, uint32_t push_constant_count)
+{
+ RendPipeline pipeline = rmalloc(sizeof *pipeline);
+ Rend__PipelineConfig config = {
+ .push_constants = push_constants,
+ .push_constant_count = push_constant_count,
+ };
+
+ if (rend_vtables[renderer->backend].pipeline_create(renderer, pipeline, config, REND__PIPELINE_COMPUTE, compute_bytes, compute_size, NULL, 0, NULL, 0)) {
+ pipeline->backend = renderer->backend;
+ pipeline->type = REND__PIPELINE_COMPUTE;
+
+ pipeline->next = renderer->pipeline_head;
+ pipeline->prev = NULL;
+ renderer->pipeline_head = pipeline;
+ if(pipeline->next) {
+ pipeline->next->prev = pipeline;
+ }
+
+ return pipeline;
+ }
+ rfree(pipeline);
+ return NULL;
+}
+
+extern RendPipeline
+rend_pipeline_create_meshlet_spirv(RendRenderer renderer, uint8_t *meshlet_bytes, size_t meshlet_size, uint8_t *frag_bytes, size_t frag_size, const RendPushConstantInfo *push_constants, uint32_t push_constant_count, RendPolygonMode polygon_mode, RendCullMode cull_mode, bool depth_test_enable)
+{
+ RendPipeline pipeline = rmalloc(sizeof *pipeline);
+ Rend__PipelineConfig config = {
+ .push_constants = push_constants,
+ .push_constant_count = push_constant_count,
+ .polygon_mode = polygon_mode,
+ .cull_mode = cull_mode,
+ .depth_test_enable = depth_test_enable
+ };
+ if (rend_vtables[renderer->backend].pipeline_create(renderer, pipeline, config, REND__PIPELINE_MESH, meshlet_bytes, meshlet_size, frag_bytes, frag_size, NULL, 0)) {
+ pipeline->backend = renderer->backend;
+ pipeline->type = REND__PIPELINE_MESH;
+
+ pipeline->next = renderer->pipeline_head;
+ pipeline->prev = NULL;
+ renderer->pipeline_head = pipeline;
+ if(pipeline->next) {
+ pipeline->next->prev = pipeline;
+ }
+
+ return pipeline;
+ }
+ rfree(pipeline);
+ return NULL;
+}
+
+extern void
+rend_pipeline_bind(RendPipeline pipeline)
+{
+ rend_vtables[pipeline->backend].pipeline_bind(pipeline);
+}
+
+extern void
+rend_pipeline_dispatch(RendPipeline pipeline, uint32_t x, uint32_t y, uint32_t z)
+{
+ rend_vtables[pipeline->backend].pipeline_dispatch(pipeline, x, y, z);
+}
+
+extern void
+rend_pipeline_draw(RendPipeline pipeline, size_t count, uint32_t instance_count)
+{
+ rend_vtables[pipeline->backend].pipeline_draw(pipeline, count, instance_count);
+}
+
+extern void
+rend_pipeline_draw_indexed(RendPipeline pipeline, uint32_t index_count, uint32_t first_index, int32_t vertex_offset, uint32_t instance_count)
+{
+ rend_vtables[pipeline->backend].pipeline_draw_indexed(pipeline, index_count, first_index, vertex_offset, instance_count);
+}
+
+extern void
+rend_pipeline_set_blend(RendPipeline pipeline, bool blend)
+{
+ rend_vtables[pipeline->backend].pipeline_set_blend(pipeline, blend);
+}
+
+extern void
+rend_cmd_render_begin(RendRenderer renderer, float r, float g, float b, float a)
+{
+ renderer->in_pass = 1;
+ rend_vtables[renderer->backend].renderer_render_pass_begin(renderer, r, g, b, a);
+}
+
+extern void
+rend_cmd_render_begin_texture(RendRenderer renderer, RendTexture *texture)
+{
+ renderer->in_pass = 1;
+ rend_vtables[renderer->backend].renderer_render_pass_begin_texture(renderer, texture);
+}
+
+extern void
+rend_cmd_render_end(RendRenderer renderer)
+{
+ renderer->in_pass = 0;
+ rend_vtables[renderer->backend].renderer_render_pass_end(renderer);
+}
+
+extern void
+rend_cmd_render_end_texture(RendRenderer renderer, RendTexture *texture)
+{
+ renderer->in_pass = 0;
+ rend_vtables[renderer->backend].renderer_render_pass_end_texture(renderer, texture);
+}
+
+extern void
+rend_cmd_bind_pipeline(RendPipeline pipeline)
+{
+ rend_vtables[pipeline->backend].pipeline_bind(pipeline);
+}
+
+extern void
+rend_cmd_bind_vertex_buffer(RendPipeline pipeline, uint32_t binding, RendBuffer buffer, size_t offset)
+{
+ rend_vtables[pipeline->backend].pipeline_bind_vertex_buffer(pipeline, binding, buffer, offset);
+}
+
+extern void
+rend_cmd_bind_index_buffer(RendPipeline pipeline, RendBuffer buffer, size_t offset, RendIndexType index_type)
+{
+ rend_vtables[pipeline->backend].pipeline_bind_index_buffer(pipeline, buffer, offset, index_type);
+}
+
+extern void
+rend_cmd_push_constants(RendPipeline pipeline, void *push_data, size_t size)
+{
+ assert(pipeline != NULL && "Pipeline handle is NULL!");
+ assert(rend_vtables[pipeline->backend].pipeline_push_constants && "Backend function not implemented!");
+ rend_vtables[pipeline->backend].pipeline_push_constants(pipeline, push_data, size);
+}
+
+extern void
+rend_cmd_dispatch(RendPipeline pipeline, uint32_t x, uint32_t y, uint32_t z)
+{
+ rend_vtables[pipeline->backend].pipeline_dispatch(pipeline, x, y, z);
+}
+
+extern void
+rend_cmd_draw(RendPipeline pipeline, size_t count, uint32_t instance_count)
+{
+ rend_vtables[pipeline->backend].pipeline_draw(pipeline, count, instance_count);
+}
+
+extern void
+rend_cmd_draw_indexed(RendPipeline pipeline, uint32_t index_count, uint32_t first_index, int32_t vertex_offset, uint32_t instance_count)
+{
+ rend_vtables[pipeline->backend].pipeline_draw_indexed(pipeline, index_count, first_index, vertex_offset, instance_count);
+}
+
+extern void
+rend_cmd_blit(RendRenderer renderer, RendTexture *src, RendTexture *dst, uint32_t src_x, uint32_t src_y, uint32_t src_w, uint32_t src_h, uint32_t dst_x, uint32_t dst_y, uint32_t dst_w, uint32_t dst_h)
+{
+ assert(renderer->in_frame && "Must be called while rendering a frame!");
+ assert(!renderer->in_pass && "Must be called outside a render pass!");
+ renderer->in_pass = true;
+ rend_vtables[renderer->backend].texture_blit(renderer, src, dst, src_x, src_y, src_w, src_h, dst_x, dst_y, dst_w, dst_h);
+}
+
+static bool
+rend__renderer_init_recursive(RendRenderer renderer, RendBackendType backend, bool auto_pick)
+{
+ /* set autopick to true and set backend to first backend*/
+ if (backend == REND_BACKEND_AUTO) {
+ backend++;
+ auto_pick = true;
+ }
+
+ switch (backend) {
+ default:
+ /* unless using auto pick to pick renderers, crash */
+ if (!auto_pick) REND__CRASH("Invalid backend type!");
+
+ case REND_BACKEND_VULKAN_14:
+ if (rend_vk14_init()) {
+ rend_backend_vk14_initialized = true;
+ renderer->backend = REND_BACKEND_VULKAN_14;
+ return true;
+ }
+ break;
+ }
+
+ if (auto_pick) {
+ backend++;
+ if (backend >= REND_BACKEND_COUNT) {
+ return false;
+ }
+
+ /* recursively try next backend */
+ return rend__renderer_init_recursive(renderer, backend, auto_pick);
+ }
+
+ return false;
+}
+
+static void
+rend__renderer_destroy_recursive(RendRenderer renderer)
+{
+ if (renderer == NULL) return;
+
+ rend__renderer_destroy_recursive(renderer->next);
+
+ if (renderer->backend != 0) {
+ rend_vtables[renderer->backend].renderer_destroy(renderer);
+ }
+
+ rend__pipeline_destroy_recursive(renderer->pipeline_head);
+ rfree(renderer);
+}
+
+static void
+rend__pipeline_destroy_recursive(RendPipeline pipeline)
+{
+ if (pipeline == NULL) return;
+ rend__pipeline_destroy_recursive(pipeline->next);
+ rfree(pipeline);
+}