refactor renderer_handle -> renderer_texture. retrieve backbuffer as texture.

This commit is contained in:
jacob 2025-01-20 11:50:18 -06:00
parent ceb1ac2059
commit cebaed5aa5
10 changed files with 434 additions and 548 deletions

View File

@ -487,10 +487,6 @@ struct buffer {
u8 *data;
};
struct renderer_handle {
u64 v[1];
};
struct entity_handle {
u64 idx;
u64 gen;

View File

@ -7,7 +7,7 @@
#include "collider.h"
GLOBAL struct {
struct renderer_handle solid_white;
struct renderer_texture solid_white;
} G = ZI, DEBUG_ALIAS(G, G_draw);
/* ========================== *
@ -20,7 +20,7 @@ struct draw_startup_receipt draw_startup(struct renderer_startup_receipt *render
(UNUSED)renderer_sr;
(UNUSED)font_sr;
u32 pixel_white = 0xFFFFFFFF;
G.solid_white = renderer_texture_image_alloc((struct image_rgba) { .width = 1, .height = 1, .pixels = &pixel_white } );
G.solid_white = renderer_texture_alloc(RENDERER_TEXTURE_FORMAT_R8G8B8A8_UNORM, 0, 1, 1, &pixel_white);
return (struct draw_startup_receipt) { 0 };
}
@ -28,7 +28,7 @@ struct draw_startup_receipt draw_startup(struct renderer_startup_receipt *render
* Texture
* ========================== */
void draw_quad_texture_ex(struct renderer_cmd_buffer *cmdbuff, struct renderer_handle texture, struct sprite_tag sprite, struct clip_rect clip, u32 tint0, u32 tint1, struct quad quad)
void draw_quad_texture_ex(struct renderer_cmd_buffer *cmdbuff, struct renderer_texture texture, struct sprite_tag sprite, struct clip_rect clip, u32 tint0, u32 tint1, struct quad quad)
{
struct renderer_cmd_parameters cmd_params = ZI;
cmd_params.kind = SHADER_TEXTURE;
@ -365,7 +365,7 @@ void draw_text_ex(struct renderer_cmd_buffer *cmdbuff, struct font *font, struct
};
struct quad quad = quad_from_rect(RECT(x, y, width, height));
draw_quad_texture_ex(cmdbuff, font->image_renderer_handle, sprite_tag_nil(), clip, 0xFFFFFFFF, 0xFFFFFFFF, quad);
draw_quad_texture_ex(cmdbuff, font->texture, sprite_tag_nil(), clip, 0xFFFFFFFF, 0xFFFFFFFF, quad);
draw_pos.x += glyph->advance * scale;
}

View File

@ -1,6 +1,8 @@
#ifndef DRAW_H
#define DRAW_H
#include "renderer.h"
struct renderer_cmd_buffer;
struct font;
struct renderer_startup_receipt;
@ -13,7 +15,7 @@ struct font_startup_receipt;
})
struct draw_texture_params {
struct renderer_handle texture; /* Overrides sprite if set */
struct renderer_texture texture; /* Overrides sprite if set */
struct sprite_tag sprite;
struct clip_rect clip;
u32 tint;
@ -23,7 +25,7 @@ struct draw_startup_receipt { i32 _; };
struct draw_startup_receipt draw_startup(struct renderer_startup_receipt *renderer_sr,
struct font_startup_receipt *font_sr);
void draw_quad_texture_ex(struct renderer_cmd_buffer *cmdbuff, struct renderer_handle texture, struct sprite_tag sprite, struct clip_rect clip, u32 tint0, u32 tint1, struct quad quad);
void draw_quad_texture_ex(struct renderer_cmd_buffer *cmdbuff, struct renderer_texture texture, struct sprite_tag sprite, struct clip_rect clip, u32 tint0, u32 tint1, struct quad quad);
void draw_quad_texture(struct renderer_cmd_buffer *cmdbuff, struct draw_texture_params params, struct quad quad);
void draw_poly(struct renderer_cmd_buffer *cmdbuff, struct v2_array array, u32 color);

View File

@ -120,7 +120,7 @@ INTERNAL WORK_TASK_FUNC_DEF(font_load_asset_task, vparams)
resource_close(res);
/* Send texture to GPU */
struct renderer_handle image_renderer_handle = renderer_texture_image_alloc(result.image_data);
struct renderer_texture texture = renderer_texture_alloc(RENDERER_TEXTURE_FORMAT_R8G8B8A8_UNORM, 0, result.image_data.width, result.image_data.height, result.image_data.pixels);
/* Allocate store memory */
struct font *font = NULL;
@ -133,7 +133,7 @@ INTERNAL WORK_TASK_FUNC_DEF(font_load_asset_task, vparams)
}
/* Set font data */
font->image_renderer_handle = image_renderer_handle;
font->texture = texture;
font->image_width = result.image_data.width;
font->image_height = result.image_data.height;
font->glyphs_count = result.glyphs_count;

View File

@ -2,6 +2,7 @@
#define FONT_H
#include "util.h"
#include "renderer.h"
struct asset;
struct work_startup_receipt;
@ -20,7 +21,7 @@ struct font_glyph {
};
struct font {
struct renderer_handle image_renderer_handle;
struct renderer_texture texture;
u32 image_width;
u32 image_height;
f32 point_size;

View File

@ -11,6 +11,10 @@ typedef u32 vidx;
struct renderer_cmd_buffer;
struct renderer_texture {
u64 handle;
};
/* ========================== *
* Shaders
* ========================== */
@ -27,7 +31,7 @@ struct renderer_cmd_parameters {
enum shader_kind kind;
union {
struct {
struct renderer_handle texture; /* Overrides sprite if set */
struct renderer_texture texture; /* Overrides sprite if set */
struct sprite_tag sprite;
} texture_params;
};
@ -54,6 +58,38 @@ PACK(struct grid_shader_vertex {
struct renderer_startup_receipt { i32 _; };
struct renderer_startup_receipt renderer_startup(struct sys_window *window);
/* ========================== *
* Texture
* ========================== */
enum renderer_texture_format {
RENDERER_TEXTURE_FORMAT_NONE,
RENDERER_TEXTURE_FORMAT_R8G8B8A8_UNORM,
NUM_RENDERER_TEXTURE_FORMATS
};
#define RENDERER_TEXTURE_FLAG_NONE (0)
#define RENDERER_TEXTURE_FLAG_TARGET (1<<0)
struct renderer_texture renderer_texture_alloc(enum renderer_texture_format format, u32 flags, u32 width, u32 height, void *initial_data);
void renderer_texture_release(struct renderer_texture t);
void renderer_texture_clear(struct renderer_texture target_texture, u32 clear_color);
void renderer_texture_render(struct renderer_texture texture, struct renderer_cmd_buffer *cmdbuff, struct xform view, struct rect viewport, struct sprite_scope *sprite_scope);
/* ========================== *
* Backbuffer
* ========================== */
/* Returns a texture linking to the internal backbuffer. Lifetime is managed by renderer. */
struct renderer_texture renderer_get_backbuffer_texture(void);
void renderer_backbuffer_resize(u32 width, u32 height);
void renderer_backbuffer_present(i32 vsync);
/* ========================== *
* Cmd buffer
* ========================== */
@ -78,32 +114,5 @@ void renderer_cmd_buffer_ensure_cmd(struct renderer_cmd_buffer *cmdbuff, struct
void renderer_cmd_buffer_send_to_gpu(struct renderer_cmd_buffer *cmdbuff);
void renderer_render_to_texture(struct renderer_handle target_handle, struct renderer_cmd_buffer *cmdbuff, struct xform view, struct rect viewport, struct sprite_scope *sprite_scope);
void renderer_render_to_backbuffer(struct renderer_cmd_buffer *cmdbuff, struct xform view, struct rect viewport, struct sprite_scope *sprite_scope);
void renderer_clear_texture(struct renderer_handle target_handle, u32 clear_color);
void renderer_clear_backbuffer(u32 clear_color);
void renderer_resize_backbuffer(struct v2 size);
void renderer_present_backbuffer(i32 vsync);
/* ========================== *
* Texture
* ========================== */
INLINE struct renderer_handle renderer_handle_nil(void) { return (struct renderer_handle) { 0 }; }
INLINE b32 renderer_handle_is_nil(struct renderer_handle h) { return h.v[0] == 0; }
/* Allocates a texture on the GPU filled with supplied image data */
struct renderer_handle renderer_texture_image_alloc(struct image_rgba data);
/* Allocates a texture on the GPU that can be rendered to */
struct renderer_handle renderer_texture_target_alloc(struct v2 size);
void renderer_texture_release(struct renderer_handle handle);
b32 renderer_texture_is_nil(struct renderer_handle handle);
#endif

View File

@ -60,7 +60,7 @@ struct dx11_buffer {
struct renderer_cmd {
struct dx11_shader *shader;
struct renderer_handle texture_handle; /* Overrides sprite */
struct renderer_texture texture; /* Overrides sprite if set */
struct sprite_tag sprite;
/* Associated buffer data */
@ -88,6 +88,20 @@ struct renderer_cmd_buffer {
b32 valid; /* False if uninitialized (in sparse array) */
};
struct dx11_format {
DXGI_FORMAT format;
u32 pixel_size;
};
struct dx11_texture {
ID3D11Texture2D *texture;
b32 is_backbuffer;
struct dx11_texture *prev;
struct dx11_texture *next;
struct dx11_texture *next_free;
};
INTERNAL void renderer_capture_image_for_profiler(void);
/* ========================== *
@ -123,6 +137,7 @@ GLOBAL struct {
ID3D11DeviceContext *devcon;
IDXGISwapChain1 *swapchain;
struct dx11_texture backbuffer_texture;
ID3D11BlendState *blend_state;
ID3D11RasterizerState *rasterizer_state;
@ -131,13 +146,17 @@ GLOBAL struct {
ID3D11Buffer *vs_constant_buffer;
struct handle_store handle_store;
/* Texture store */
struct sys_mutex textures_mutex;
struct arena textures_arena;
struct dx11_texture *textures_first;
struct dx11_texture *textures_last;
struct dx11_texture *textures_first_free;
/* Sparse array (cmdbuff.valid) */
struct renderer_cmd_buffer cmdbuffs[MAX_CMD_BUFFERS];
struct dx11_shader shaders[NUM_SHADERS];
struct dx11_shader_desc shader_info[NUM_SHADERS];
} G = ZI, DEBUG_ALIAS(G, G_renderer_d3d11);
@ -166,122 +185,6 @@ INTERNAL void send_constant_buffer_data(ID3D11Buffer *buffer, struct mat4x4 vp)
ID3D11DeviceContext_Unmap(G.devcon, (ID3D11Resource *)buffer, 0);
}
/* ========================== *
* Handle
* ========================== */
/* Handle layout
* bits 0-32: Index
* bits 33-64: Generation */
#define HANDLE_IDX_MASK 0x00000000FFFFFFFF
#define HANDLE_GEN_MASK 0xFFFFFFFF00000000
#define HANDLE_IDX_MAX (U32_MAX)
#define HANDLE_GEN_MAX (U32_MAX)
#define HANDLE_CREATE(idx, gen) ((struct renderer_handle) { .v[0] = (u64)(gen) << 32 | ((u64)(idx) & 0xFFFFFFFF) } )
#define HANDLE_IDX(handle) ((u32)((handle).v[0] & HANDLE_IDX_MASK))
#define HANDLE_GEN(handle) ((u32)(((handle).v[0] & HANDLE_GEN_MASK) >> 32))
INTERNAL struct renderer_handle handle_alloc(void *data)
{
__prof;
struct handle_store *store = &G.handle_store;
struct handle_slot *slot = NULL;
struct sys_lock lock = sys_mutex_lock_e(&store->mutex);
{
if (store->head_free) {
/* Take first from free list */
slot = store->head_free;
store->head_free = slot->next_free;
slot->next_free = NULL;
++slot->gen;
} else {
/* Or push onto arena */
if (store->count + 1 >= HANDLE_IDX_MAX) {
sys_panic(STR("Maximum renderer handles exceeded"));
}
slot = arena_push_zero(&store->arena, struct handle_slot);
slot->idx = store->count;
slot->gen = 1;
++store->count;
}
slot->data = data;
}
sys_mutex_unlock(&lock);
struct renderer_handle handle = HANDLE_CREATE(slot->idx, slot->gen);
return handle;
}
INTERNAL void handle_release(struct renderer_handle handle)
{
__prof;
struct handle_store *store = &G.handle_store;
u32 idx = HANDLE_IDX(handle);
u32 gen = HANDLE_GEN(handle);
struct sys_lock lock = sys_mutex_lock_e(&store->mutex);
{
if (idx < store->count) {
struct handle_slot *slot = &store->array[idx];
if (slot->gen == gen) {
/* Insert into free list */
if (gen + 1 < HANDLE_GEN_MAX) {
slot->next_free = store->head_free;
store->head_free = slot;
} else {
/* Maximum generations exceeded. Not a runtime error since it
* shouldn't cause issues in practice (just can't recycle this handle).
* Still probably means there's a problem in the code. */
ASSERT(false);
}
++slot->gen;
} else {
/* Tried to release handle not in store (non-matching generation) */
ASSERT(false);
}
} else {
/* Tried to release out-of-bounds handle */
ASSERT(false);
}
}
sys_mutex_unlock(&lock);
}
INTERNAL void *handle_data(struct renderer_handle handle)
{
__prof;
void *data = NULL;
struct handle_store *store = &G.handle_store;
u32 idx = HANDLE_IDX(handle);
u32 gen = HANDLE_GEN(handle);
if (idx < store->count) {
struct handle_slot *slot = &store->array[idx];
if (slot->gen == gen) {
data = slot->data;
}
}
return data;
}
INTERNAL b32 handle_eq(struct renderer_handle h1, struct renderer_handle h2)
{
CT_ASSERT(sizeof(struct renderer_handle) == 8);
return h1.v[0] == h2.v[0];
}
INTERNAL b32 handle_is_nil(struct renderer_handle h)
{
return h.v[0] == 0;
}
/* ========================== *
* Shader
* ========================== */
@ -394,7 +297,6 @@ INTERNAL void shader_init(struct dx11_shader *shader, enum shader_kind kind)
scratch_end(scratch);
}
/* ========================== *
* Startup
* ========================== */
@ -405,10 +307,9 @@ struct renderer_startup_receipt renderer_startup(struct sys_window *window)
G.arena = arena_alloc(GIGABYTE(64));
/* Allocate store */
G.handle_store.arena = arena_alloc(GIGABYTE(64));
G.handle_store.array = (struct handle_slot *)G.handle_store.arena.base;
G.handle_store.mutex = sys_mutex_alloc();
/* Initialize texture store */
G.textures_mutex = sys_mutex_alloc();
G.textures_arena = arena_alloc(GIGABYTE(64));
/* Load shader archive */
struct buffer embedded_data = inc_shaders_tar();
@ -494,7 +395,7 @@ struct renderer_startup_receipt renderer_startup(struct sys_window *window)
.BufferCount = 2,
.Scaling = DXGI_SCALING_NONE,
/* Use more efficient FLIP presentation model.
/* More efficient FLIP presentation model.
* Windows 10 allows to use DXGI_SWAP_EFFECT_FLIP_DISCARD
* For Windows 8 compatibility use DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL
* For Windows 7 compatibility use DXGI_SWAP_EFFECT_DISCARD
@ -617,9 +518,330 @@ struct renderer_startup_receipt renderer_startup(struct sys_window *window)
shader_init(&G.shaders[i], i);
}
/* Set backbuffer texture */
MEMZERO_STRUCT(&G.backbuffer_texture);
IDXGISwapChain_GetBuffer(G.swapchain, 0, &IID_ID3D11Texture2D, (LPVOID *)&G.backbuffer_texture.texture);
G.backbuffer_texture.is_backbuffer = true;
return (struct renderer_startup_receipt) { 0 };
}
/* ========================== *
* Render
* ========================== */
/* TODO: Lock cmdbuff or at least global state? (in-case multi-threaded present).
* Another option is to store a separate device on each cmdbuff (need to
* research if that is smart first).
*
* I'm thinking we may also just need to lock texture modification access while presenting */
INTERNAL void dx11_render_to_target(ID3D11RenderTargetView *target, struct renderer_cmd_buffer *cmdbuff, struct xform view, struct rect viewport, struct sprite_scope *sprite_scope)
{
__prof;
ID3D11DeviceContext_OMSetRenderTargets(G.devcon, 1, &target, NULL);
D3D11_VIEWPORT d3d11_viewport = ZI;
d3d11_viewport.Width = viewport.width;
d3d11_viewport.Height = viewport.height;
d3d11_viewport.MinDepth = 0.0f;
d3d11_viewport.MaxDepth = 1.0f;
d3d11_viewport.TopLeftX = viewport.x;
d3d11_viewport.TopLeftY = viewport.y;
ID3D11DeviceContext_RSSetViewports(G.devcon, 1, &d3d11_viewport);
/* Fill and set constant buffer
* NOTE: We're only doing this once per render, rather than once per
* draw call since the only constant right now is VP. */
struct mat4x4 vp_matrix = calculate_vp(view, viewport.width, viewport.height);
send_constant_buffer_data(G.vs_constant_buffer, vp_matrix);
ID3D11DeviceContext_VSSetConstantBuffers(G.devcon, 0, 1, &G.vs_constant_buffer);
struct dx11_shader *last_shader = NULL;
struct renderer_cmd *cmd = cmdbuff ? cmdbuff->gpu_cmd_store.cmd_first : NULL;
for (; cmd; cmd = cmd->next) {
struct dx11_shader *shader = cmd->shader;
struct dx11_buffer *buffer = &cmdbuff->buffers[shader->kind];
/* Activate shader */
if (shader != last_shader) {
ID3D11DeviceContext_VSSetShader(G.devcon, shader->vs, 0, 0);
ID3D11DeviceContext_PSSetShader(G.devcon, shader->ps, 0, 0);
ID3D11DeviceContext_IASetInputLayout(G.devcon, shader->input_layout);
last_shader = shader;
}
ID3D11ShaderResourceView *null_srv[1] = { NULL };
switch (shader->kind) {
default:
{
/* Unknown shader */
ASSERT(false);
} break;
case SHADER_TEXTURE:
{
/* FIXME: Texture refcount needs to be increased here to prevent release mid-render */
ID3D11Texture2D *texture = NULL;
if (cmd->texture.handle) {
/* Load texture if handle is set */
texture = ((struct dx11_texture *)cmd->texture.handle)->texture;
} else {
/* Otherwise load sprite */
struct sprite_texture *sprite_texture = sprite_texture_from_tag_async(sprite_scope, cmd->sprite);
if (sprite_texture->loaded) {
texture = ((struct dx11_texture *)sprite_texture->texture.handle)->texture;
}
}
if (texture) {
/* Bind texture */
D3D11_TEXTURE2D_DESC texture_desc = ZI;
ID3D11Texture2D_GetDesc(texture, &texture_desc);
/* Create SRV */
D3D11_SHADER_RESOURCE_VIEW_DESC srv_desc = { .Format = texture_desc.Format, .ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D, .Texture2D.MipLevels = texture_desc.MipLevels, .Texture2D.MostDetailedMip = 0 };
ID3D11ShaderResourceView *texture_srv = NULL;
ID3D11Device_CreateShaderResourceView(G.dev, (ID3D11Resource *)texture, &srv_desc, &texture_srv);
ID3D11DeviceContext_PSSetShaderResources(G.devcon, 0, 1, &texture_srv);
ID3D11DeviceContext_IASetPrimitiveTopology(G.devcon, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
/* Activate buffer */
u32 zero = 0;
UINT vertex_stride = shader->vertex_size;
ID3D11DeviceContext_IASetVertexBuffers(G.devcon, 0, 1, &buffer->gpu_vertex_buffer, &vertex_stride, &zero);
ID3D11DeviceContext_IASetIndexBuffer(G.devcon, buffer->gpu_index_buffer, DXGI_FORMAT_R32_UINT, zero);
/* Draw */
u32 vertex_offset = cmd->vertex_offset;
u32 index_offset = cmd->index_offset;
u32 index_count = cmd->index_count;
ID3D11DeviceContext_DrawIndexed(G.devcon, index_count, index_offset, vertex_offset);
/* Release SRV */
ID3D11ShaderResourceView_Release(texture_srv);
/* Unbind */
ID3D11DeviceContext_PSSetShaderResources(G.devcon, 0, 1, null_srv);
}
} break;
case SHADER_GRID:
{
ID3D11DeviceContext_IASetPrimitiveTopology(G.devcon, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
/* Activate buffer */
u32 zero = 0;
UINT vertex_stride = shader->vertex_size;
ID3D11DeviceContext_IASetVertexBuffers(G.devcon, 0, 1, &buffer->gpu_vertex_buffer, &vertex_stride, &zero);
ID3D11DeviceContext_IASetIndexBuffer(G.devcon, buffer->gpu_index_buffer, DXGI_FORMAT_R32_UINT, zero);
/* Draw */
u32 vertex_offset = cmd->vertex_offset;
u32 index_offset = cmd->index_offset;
u32 index_count = cmd->index_count;
ID3D11DeviceContext_DrawIndexed(G.devcon, index_count, index_offset, vertex_offset);
} break;
}
}
}
/* ========================== *
* Texture
* ========================== */
INTERNAL struct dx11_format dx11_format_from_renderer_format(enum renderer_texture_format format)
{
LOCAL_PERSIST const struct dx11_format sizes[NUM_RENDERER_TEXTURE_FORMATS] = {
[RENDERER_TEXTURE_FORMAT_R8G8B8A8_UNORM] = {
.format = DXGI_FORMAT_R8G8B8A8_UNORM,
.pixel_size = 4
}
};
struct dx11_format res = ZI;
if ((u32)format < ARRAY_COUNT(sizes)) {
res = sizes[format];
}
return res;
}
INTERNAL struct dx11_texture *dx11_texture_alloc(enum renderer_texture_format format, u32 flags, u32 width, u32 height, void *initial_data)
{
struct dx11_texture *t = NULL;
{
/* Allocate to list */
struct sys_lock lock = sys_mutex_lock_e(&G.textures_mutex);
if (G.textures_first_free) {
t = G.textures_first_free;
G.textures_first_free = t->next_free;
} else {
t = arena_push(&G.textures_arena, struct dx11_texture);
}
MEMZERO_STRUCT(t);
if (!G.textures_first) {
G.textures_first = t;
} else {
G.textures_last->next = t;
}
t->prev = G.textures_last;
G.textures_last = t;
sys_mutex_unlock(&lock);
}
struct dx11_format dx11_format = dx11_format_from_renderer_format(format);
if (dx11_format.format == DXGI_FORMAT_UNKNOWN) {
/* Unknown format */
ASSERT(false);
dx11_format = (struct dx11_format) { .format = DXGI_FORMAT_R8G8B8A8_UNORM, .pixel_size = 4 };
}
u32 bind_flags = D3D11_BIND_SHADER_RESOURCE;
if (flags & RENDERER_TEXTURE_FLAG_TARGET) {
bind_flags |= D3D11_BIND_RENDER_TARGET;
}
D3D11_TEXTURE2D_DESC desc = ZI;
desc.Width = width;
desc.Height = height;
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.SampleDesc.Count = 1;
desc.Usage = D3D11_USAGE_DEFAULT;
desc.CPUAccessFlags = 0;
desc.BindFlags = bind_flags;
desc.Format = dx11_format.format;
ID3D11Texture2D *texture = NULL;
if (initial_data) {
D3D11_SUBRESOURCE_DATA subresource_data = { .pSysMem = initial_data, .SysMemPitch = width * dx11_format.pixel_size, .SysMemSlicePitch = 0 };
ID3D11Device_CreateTexture2D(G.dev, &desc, &subresource_data, &texture);
} else {
ID3D11Device_CreateTexture2D(G.dev, &desc, NULL, &texture);
}
ASSERT(texture != NULL);
t->texture = texture;
return t;
}
INTERNAL void dx11_texture_release(struct dx11_texture *t)
{
if (!t->is_backbuffer) {
{
/* Free from list */
struct sys_lock lock = sys_mutex_lock_e(&G.textures_mutex);
struct dx11_texture *prev = t->prev;
struct dx11_texture *next = t->next;
if (prev) {
prev->next = next;
}
if (next) {
next->prev = prev;
}
if (G.textures_first == t) {
G.textures_first = next;
}
if (G.textures_last == t) {
G.textures_last = prev;
}
t->next_free = G.textures_first_free;
G.textures_first_free = t;
sys_mutex_unlock(&lock);
}
if (t->texture) {
ID3D11Texture2D_Release(t->texture);
}
}
}
struct renderer_texture renderer_texture_alloc(enum renderer_texture_format format, u32 flags, u32 width, u32 height, void *initial_data)
{
__prof;
struct renderer_texture res = ZI;
struct dx11_texture *t = dx11_texture_alloc(format, flags, width, height, initial_data);
res.handle = (u64)t;
return res;
}
void renderer_texture_release(struct renderer_texture t)
{
__prof;
dx11_texture_release((struct dx11_texture *)t.handle);
}
void renderer_texture_clear(struct renderer_texture target_texture, u32 clear_color)
{
__prof;
ID3D11Texture2D *texture = ((struct dx11_texture *)target_texture.handle)->texture;
ID3D11RenderTargetView *target_view = NULL;
ID3D11Device_CreateRenderTargetView(G.dev, (ID3D11Resource *)texture, NULL, &target_view);
if (target_view) {
f32 r = (f32)((clear_color >> 0) & 0xFF) / 255.0f;
f32 g = (f32)((clear_color >> 8) & 0xFF) / 255.0f;
f32 b = (f32)((clear_color >> 16) & 0xFF) / 255.0f;
f32 a = (f32)((clear_color >> 24) & 0xFF) / 255.0f;
f32 fill[4] = { r, g, b, a };
ID3D11DeviceContext_ClearRenderTargetView(G.devcon, target_view, fill);
}
if (target_view) {
ID3D11RenderTargetView_Release(target_view);
}
}
void renderer_texture_render(struct renderer_texture texture, struct renderer_cmd_buffer *cmdbuff, struct xform view, struct rect viewport, struct sprite_scope *sprite_scope)
{
__prof;
struct dx11_texture *t = (struct dx11_texture *)texture.handle;
ID3D11RenderTargetView *target_view = NULL;
ID3D11Device_CreateRenderTargetView(G.dev, (ID3D11Resource *)t->texture, NULL, &target_view);
if (target_view) {
dx11_render_to_target(target_view, cmdbuff, view, viewport, sprite_scope);
}
if (target_view) {
ID3D11RenderTargetView_Release(target_view);
}
}
/* ========================== *
* Backbuffer
* ========================== */
struct renderer_texture renderer_get_backbuffer_texture(void)
{
struct renderer_texture res = ZI;
res.handle = (u64)&G.backbuffer_texture;
return res;
}
void renderer_backbuffer_resize(u32 width, u32 height)
{
__prof;
ID3D11Texture2D_Release(G.backbuffer_texture.texture);
IDXGISwapChain_ResizeBuffers(G.swapchain, 0, width, height, DXGI_FORMAT_UNKNOWN, 0);
IDXGISwapChain_GetBuffer(G.swapchain, 0, &IID_ID3D11Texture2D, (LPVOID *)&G.backbuffer_texture.texture);
}
void renderer_backbuffer_present(i32 vsync)
{
__prof;
{
__profscope(IDXGISwapchain_Present);
IDXGISwapChain1_Present(G.swapchain, vsync, 0);
__profframe(0);
}
renderer_capture_image_for_profiler();
}
/* ========================== *
* Cmd buffer
* ========================== */
@ -719,12 +941,12 @@ void renderer_cmd_buffer_ensure_cmd(struct renderer_cmd_buffer *cmdbuff, struct
{
if (!last_cmd
|| last_cmd->shader->kind != SHADER_TEXTURE
|| !handle_eq(last_cmd->texture_handle, params->texture_params.texture)
|| (last_cmd->texture.handle != params->texture_params.texture.handle)
|| !sprite_tag_eq(last_cmd->sprite, params->texture_params.sprite)) {
new_cmd = arena_push(&cmdbuff->cpu_cmd_store.arena, struct renderer_cmd);
*new_cmd = (struct renderer_cmd) {
.shader = &G.shaders[SHADER_TEXTURE],
.texture_handle = params->texture_params.texture,
.texture = params->texture_params.texture,
.sprite = params->texture_params.sprite
};
}
@ -751,10 +973,6 @@ void renderer_cmd_buffer_ensure_cmd(struct renderer_cmd_buffer *cmdbuff, struct
}
}
/* ========================== *
* Send cmdbuff to GPU
* ========================== */
void renderer_cmd_buffer_send_to_gpu(struct renderer_cmd_buffer *cmdbuff)
{
__prof;
@ -822,351 +1040,6 @@ void renderer_cmd_buffer_send_to_gpu(struct renderer_cmd_buffer *cmdbuff)
arena_reset(&cmdbuff->cpu_cmd_store.arena);
}
/* ========================== *
* Render
* ========================== */
/* TODO: Lock cmdbuff or at least global state? (in-case multi-threaded present).
* Another option is to store a separate device on each cmdbuff (need to
* research if that is smart first).
*
* I'm thinking we may also just need to lock texture modification access while presenting */
INTERNAL void dx11_render_to_target(ID3D11RenderTargetView *target, struct renderer_cmd_buffer *cmdbuff, struct xform view, struct rect viewport, struct sprite_scope *sprite_scope)
{
__prof;
ID3D11DeviceContext_OMSetRenderTargets(G.devcon, 1, &target, NULL);
D3D11_VIEWPORT d3d11_viewport = ZI;
d3d11_viewport.Width = viewport.width;
d3d11_viewport.Height = viewport.height;
d3d11_viewport.MinDepth = 0.0f;
d3d11_viewport.MaxDepth = 1.0f;
d3d11_viewport.TopLeftX = viewport.x;
d3d11_viewport.TopLeftY = viewport.y;
ID3D11DeviceContext_RSSetViewports(G.devcon, 1, &d3d11_viewport);
/* Fill and set constant buffer
* NOTE: We're only doing this once per render, rather than once per
* draw call since the only constant right now is VP. */
struct mat4x4 vp_matrix = calculate_vp(view, viewport.width, viewport.height);
send_constant_buffer_data(G.vs_constant_buffer, vp_matrix);
ID3D11DeviceContext_VSSetConstantBuffers(G.devcon, 0, 1, &G.vs_constant_buffer);
struct dx11_shader *last_shader = NULL;
struct renderer_cmd *cmd = cmdbuff ? cmdbuff->gpu_cmd_store.cmd_first : NULL;
for (; cmd; cmd = cmd->next) {
struct dx11_shader *shader = cmd->shader;
struct dx11_buffer *buffer = &cmdbuff->buffers[shader->kind];
/* Activate shader */
if (shader != last_shader) {
ID3D11DeviceContext_VSSetShader(G.devcon, shader->vs, 0, 0);
ID3D11DeviceContext_PSSetShader(G.devcon, shader->ps, 0, 0);
ID3D11DeviceContext_IASetInputLayout(G.devcon, shader->input_layout);
last_shader = shader;
}
switch (shader->kind) {
default:
{
/* Unknown shader */
ASSERT(false);
} break;
case SHADER_TEXTURE:
{
b32 texture_loaded;
struct renderer_handle texture_handle;
if (!handle_is_nil(cmd->texture_handle)) {
/* Load texture if handle is set */
texture_loaded = true;
texture_handle = cmd->texture_handle;
/* FIXME: Texture SRV refcount needs to be increased here to prevent release mid-render */
} else {
/* Otherwise load sprite */
struct sprite_texture *sprite_texture = sprite_texture_from_tag_async(sprite_scope, cmd->sprite);
texture_loaded = sprite_texture->loaded;
texture_handle = sprite_texture->renderer_handle;
}
if (texture_loaded) {
/* Bind texture */
ID3D11ShaderResourceView *texture_srv = handle_data(texture_handle);
ID3D11DeviceContext_PSSetShaderResources(G.devcon, 0, 1, &texture_srv);
ID3D11DeviceContext_IASetPrimitiveTopology(G.devcon, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
/* Activate buffer */
u32 zero = 0;
UINT vertex_stride = shader->vertex_size;
ID3D11DeviceContext_IASetVertexBuffers(G.devcon, 0, 1, &buffer->gpu_vertex_buffer, &vertex_stride, &zero);
ID3D11DeviceContext_IASetIndexBuffer(G.devcon, buffer->gpu_index_buffer, DXGI_FORMAT_R32_UINT, zero);
/* Draw */
u32 vertex_offset = cmd->vertex_offset;
u32 index_offset = cmd->index_offset;
u32 index_count = cmd->index_count;
ID3D11DeviceContext_DrawIndexed(G.devcon, index_count, index_offset, vertex_offset);
/* Unbind */
ID3D11ShaderResourceView *null_srv[1] = { NULL };
ID3D11DeviceContext_PSSetShaderResources(G.devcon, 0, 1, null_srv);
}
} break;
case SHADER_GRID:
{
ID3D11DeviceContext_IASetPrimitiveTopology(G.devcon, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
/* Activate buffer */
u32 zero = 0;
UINT vertex_stride = shader->vertex_size;
ID3D11DeviceContext_IASetVertexBuffers(G.devcon, 0, 1, &buffer->gpu_vertex_buffer, &vertex_stride, &zero);
ID3D11DeviceContext_IASetIndexBuffer(G.devcon, buffer->gpu_index_buffer, DXGI_FORMAT_R32_UINT, zero);
/* Draw */
u32 vertex_offset = cmd->vertex_offset;
u32 index_offset = cmd->index_offset;
u32 index_count = cmd->index_count;
ID3D11DeviceContext_DrawIndexed(G.devcon, index_count, index_offset, vertex_offset);
} break;
}
}
}
void renderer_render_to_texture(struct renderer_handle target_handle, struct renderer_cmd_buffer *cmdbuff, struct xform view, struct rect viewport, struct sprite_scope *sprite_scope)
{
__prof;
ID3D11ShaderResourceView *target_srv = handle_data(target_handle);
if (!target_srv) {
/* Invalid handle */
ASSERT(false);
return;
}
ID3D11Texture2D *target_texture = NULL;
ID3D11View_GetResource(target_srv, (ID3D11Resource **)&target_texture);
ID3D11RenderTargetView *target_view = NULL;
ID3D11Device_CreateRenderTargetView(G.dev, (ID3D11Resource *)target_texture, NULL, &target_view);
if (target_view) {
dx11_render_to_target(target_view, cmdbuff, view, viewport, sprite_scope);
}
if (target_view) {
ID3D11RenderTargetView_Release(target_view);
}
if (target_texture) {
ID3D11Texture2D_Release(target_texture);
}
}
void renderer_render_to_backbuffer(struct renderer_cmd_buffer *cmdbuff, struct xform view, struct rect viewport, struct sprite_scope *sprite_scope)
{
__prof;
ID3D11Texture2D *backbuffer_texture = NULL;
IDXGISwapChain_GetBuffer(G.swapchain, 0, &IID_ID3D11Texture2D, (LPVOID *)&backbuffer_texture);
ID3D11RenderTargetView *backbuffer_view = NULL;
ID3D11Device_CreateRenderTargetView(G.dev, (ID3D11Resource *)backbuffer_texture, NULL, &backbuffer_view);
if (backbuffer_view) {
dx11_render_to_target(backbuffer_view, cmdbuff, view, viewport, sprite_scope);
}
if (backbuffer_view) {
ID3D11RenderTargetView_Release(backbuffer_view);
}
if (backbuffer_texture) {
ID3D11Texture2D_Release(backbuffer_texture);
}
}
void renderer_clear_texture(struct renderer_handle target_handle, u32 clear_color)
{
__prof;
ID3D11ShaderResourceView *target_srv = handle_data(target_handle);
if (!target_srv) {
/* Invalid handle */
ASSERT(false);
return;
}
ID3D11Texture2D *target_resource = NULL;
ID3D11View_GetResource(target_srv, (ID3D11Resource **)&target_resource);
ID3D11RenderTargetView *target_view = NULL;
ID3D11Device_CreateRenderTargetView(G.dev, (ID3D11Resource *)target_resource, NULL, &target_view);
if (target_view) {
f32 r = (f32)((clear_color >> 0) & 0xFF) / 255.0f;
f32 g = (f32)((clear_color >> 8) & 0xFF) / 255.0f;
f32 b = (f32)((clear_color >> 16) & 0xFF) / 255.0f;
f32 a = (f32)((clear_color >> 24) & 0xFF) / 255.0f;
f32 fill[4] = { r, g, b, a };
ID3D11DeviceContext_ClearRenderTargetView(G.devcon, target_view, fill);
}
if (target_view) {
ID3D11RenderTargetView_Release(target_view);
}
if (target_resource) {
ID3D11Texture2D_Release(target_resource);
}
}
void renderer_clear_backbuffer(u32 clear_color)
{
__prof;
ID3D11Texture2D *backbuffer_texture = NULL;
IDXGISwapChain_GetBuffer(G.swapchain, 0, &IID_ID3D11Texture2D, (LPVOID *)&backbuffer_texture);
ID3D11RenderTargetView *backbuffer_view = NULL;
ID3D11Device_CreateRenderTargetView(G.dev, (ID3D11Resource *)backbuffer_texture, NULL, &backbuffer_view);
if (backbuffer_view) {
f32 r = (f32)((clear_color >> 0) & 0xFF) / 255.0f;
f32 g = (f32)((clear_color >> 8) & 0xFF) / 255.0f;
f32 b = (f32)((clear_color >> 16) & 0xFF) / 255.0f;
f32 a = (f32)((clear_color >> 24) & 0xFF) / 255.0f;
f32 fill[4] = { r, g, b, a };
ID3D11DeviceContext_ClearRenderTargetView(G.devcon, backbuffer_view, fill);
}
if (backbuffer_view) {
ID3D11RenderTargetView_Release(backbuffer_view);
}
if (backbuffer_texture) {
ID3D11Texture2D_Release(backbuffer_texture);
}
}
void renderer_resize_backbuffer(struct v2 size)
{
__prof;
IDXGISwapChain_ResizeBuffers(G.swapchain, 0, (UINT)size.x, (UINT)size.y, DXGI_FORMAT_UNKNOWN, 0);
}
void renderer_present_backbuffer(i32 vsync)
{
__prof;
/* Present */
{
__profscope(IDXGISwapchain_Present);
IDXGISwapChain1_Present(G.swapchain, vsync, 0);
__profframe(0);
}
renderer_capture_image_for_profiler();
}
/* ========================== *
* Texture
* ========================== */
struct renderer_handle renderer_texture_image_alloc(struct image_rgba data)
{
__prof;
/* Create texture */
ID3D11Texture2D *texture = NULL;
D3D11_TEXTURE2D_DESC desc = {
.Width = data.width,
.Height = data.height,
.MipLevels = 1,
.ArraySize = 1,
.Format = DXGI_FORMAT_R8G8B8A8_UNORM,
.SampleDesc.Count = 1,
.Usage = D3D11_USAGE_DEFAULT,
.BindFlags = D3D11_BIND_SHADER_RESOURCE,
.CPUAccessFlags = 0
};
D3D11_SUBRESOURCE_DATA subresource_data = {
.pSysMem = data.pixels,
.SysMemPitch = data.width * 4,
.SysMemSlicePitch = 0
};
ID3D11Device_CreateTexture2D(G.dev, &desc, &subresource_data, &texture);
/* Create srv */
ID3D11ShaderResourceView *texture_srv = NULL;
if (texture) {
D3D11_SHADER_RESOURCE_VIEW_DESC shader_resource_view_desc = {
.Format = desc.Format,
.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D,
.Texture2D.MipLevels = desc.MipLevels,
.Texture2D.MostDetailedMip = 0
};
ID3D11Device_CreateShaderResourceView(G.dev, (ID3D11Resource *)texture, &shader_resource_view_desc, &texture_srv);
ID3D11Texture2D_Release(texture);
}
ASSERT(texture_srv != NULL);
struct renderer_handle handle = handle_alloc(texture_srv);
return handle;
}
struct renderer_handle renderer_texture_target_alloc(struct v2 size)
{
__prof;
/* Create texture */
ID3D11Texture2D *texture = NULL;
D3D11_TEXTURE2D_DESC desc = {
.Width = size.x,
.Height = size.y,
.MipLevels = 1,
.ArraySize = 1,
.Format = DXGI_FORMAT_R8G8B8A8_UNORM,
.SampleDesc.Count = 1,
.Usage = D3D11_USAGE_DEFAULT,
.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET,
.CPUAccessFlags = 0
};
ID3D11Device_CreateTexture2D(G.dev, &desc, NULL, &texture);
/* Create srv */
ID3D11ShaderResourceView *texture_srv = NULL;
if (texture) {
D3D11_SHADER_RESOURCE_VIEW_DESC shader_resource_view_desc = {
.Format = desc.Format,
.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D,
.Texture2D.MipLevels = desc.MipLevels,
.Texture2D.MostDetailedMip = 0
};
ID3D11Device_CreateShaderResourceView(G.dev, (ID3D11Resource *)texture, &shader_resource_view_desc, &texture_srv);
ID3D11Texture2D_Release(texture);
}
ASSERT(texture_srv != NULL);
struct renderer_handle handle = handle_alloc(texture_srv);
return handle;
}
void renderer_texture_release(struct renderer_handle handle)
{
__prof;
ID3D11ShaderResourceView *texture_srv = handle_data(handle);
if (texture_srv) {
ID3D11ShaderResourceView_Release(texture_srv);
}
handle_release(handle);
}
b32 renderer_texture_is_nil(struct renderer_handle handle)
{
return handle_is_nil(handle);
}
/* ========================== *
* Profiling frame capture
* ========================== */

View File

@ -229,7 +229,7 @@ struct sprite_startup_receipt sprite_startup(struct renderer_startup_receipt *re
{
struct temp_arena scratch = scratch_begin_no_conflict();
struct image_rgba purple_black_image = generate_purple_black_image(scratch.arena, 64, 64);
G.nil_texture->renderer_handle = renderer_texture_image_alloc(purple_black_image);
G.nil_texture->texture = renderer_texture_alloc(RENDERER_TEXTURE_FORMAT_R8G8B8A8_UNORM, 0, purple_black_image.width, purple_black_image.height, purple_black_image.pixels);
scratch_end(scratch);
}
@ -365,7 +365,7 @@ INTERNAL void cache_node_load_texture(struct cache_node *n, struct sprite_tag ta
n->texture = arena_push(&n->arena, struct sprite_texture);
n->texture->width = decoded.image.width;
n->texture->height = decoded.image.height;
n->texture->renderer_handle = renderer_texture_image_alloc(decoded.image);
n->texture->texture = renderer_texture_alloc(RENDERER_TEXTURE_FORMAT_R8G8B8A8_UNORM, 0, decoded.image.width, decoded.image.height, decoded.image.pixels);
n->texture->valid = true;
n->texture->loaded = true;
/* TODO: Query renderer for more accurate texture size in VRAM */
@ -1196,7 +1196,7 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(sprite_evictor_thread_entry_point, arg)
for (struct evict_node *en = head_evicted; en; en = en->next_evicted) {
struct cache_node *n = en->cache_node;
if (n->kind == CACHE_NODE_KIND_TEXTURE && n->texture->valid) {
renderer_texture_release(n->texture->renderer_handle);
renderer_texture_release(n->texture->texture);
}
arena_release(&n->arena);
}

View File

@ -1,6 +1,7 @@
#ifndef SPRITE_H
#define SPRITE_H
#include "renderer.h"
#include "util.h"
struct renderer_startup_receipt;
@ -45,7 +46,7 @@ void sprite_scope_end(struct sprite_scope *scope);
struct sprite_texture {
b32 loaded;
b32 valid;
struct renderer_handle renderer_handle;
struct renderer_texture texture;
u32 width;
u32 height;
};

View File

@ -42,13 +42,14 @@ GLOBAL struct {
struct sys_window *window;
/* Render targets */
struct renderer_handle final_rt;
struct renderer_handle world_rt;
struct renderer_handle ui_rt;
struct renderer_texture final_rt;
struct renderer_texture world_rt;
struct renderer_texture ui_rt;
struct renderer_texture backbuffer_rt;
struct v2 ui_rt_resolution;
struct v2 world_rt_resolution;
struct v2 backbuffer_size;
struct v2 backbuffer_rt_resolution;
struct renderer_cmd_buffer *world_cmd_buffer;
struct renderer_cmd_buffer *ui_cmd_buffer;
@ -1604,36 +1605,39 @@ INTERNAL void user_update(void)
struct rect backbuffer_viewport = RECT_FROM_V2(V2(0, 0), G.screen_size);
/* Allocate rt textures */
struct v2 backbuffer_rt_resolution = G.screen_size;
struct v2 ui_rt_resolution = G.ui_size;
struct v2 world_rt_resolution = V2(target_viewport.width + target_viewport.x, target_viewport.height + target_viewport.y);
{
/* World rt */
if (renderer_handle_is_nil(G.world_rt) || !v2_eq(G.world_rt_resolution, world_rt_resolution)) {
if (!renderer_handle_is_nil(G.world_rt)) {
if (!G.world_rt.handle || !v2_eq(G.world_rt_resolution, world_rt_resolution)) {
if (G.world_rt.handle) {
renderer_texture_release(G.world_rt);
}
G.world_rt = renderer_texture_target_alloc(world_rt_resolution);
G.world_rt = renderer_texture_alloc(RENDERER_TEXTURE_FORMAT_R8G8B8A8_UNORM, RENDERER_TEXTURE_FLAG_TARGET, math_round_to_int(world_rt_resolution.x), math_round_to_int(world_rt_resolution.y), NULL);
}
/* Ui rt */
if (renderer_handle_is_nil(G.ui_rt) || !v2_eq(G.ui_rt_resolution, ui_rt_resolution)) {
if (!renderer_handle_is_nil(G.ui_rt)) {
if (!G.ui_rt.handle || !v2_eq(G.ui_rt_resolution, ui_rt_resolution)) {
if (G.ui_rt.handle) {
renderer_texture_release(G.ui_rt);
}
G.ui_rt = renderer_texture_target_alloc(ui_rt_resolution);
G.ui_rt = renderer_texture_alloc(RENDERER_TEXTURE_FORMAT_R8G8B8A8_UNORM, RENDERER_TEXTURE_FLAG_TARGET, math_round_to_int(ui_rt_resolution.x), math_round_to_int(ui_rt_resolution.y), NULL);
}
/* Final rt */
if (renderer_handle_is_nil(G.final_rt) || !v2_eq(G.ui_rt_resolution, ui_rt_resolution)) {
if (!renderer_handle_is_nil(G.final_rt)) {
if (!G.final_rt.handle || !v2_eq(G.ui_rt_resolution, ui_rt_resolution)) {
if (G.final_rt.handle) {
renderer_texture_release(G.final_rt);
}
G.final_rt = renderer_texture_target_alloc(ui_rt_resolution);
G.final_rt = renderer_texture_alloc(RENDERER_TEXTURE_FORMAT_R8G8B8A8_UNORM, RENDERER_TEXTURE_FLAG_TARGET, math_round_to_int(ui_rt_resolution.x), math_round_to_int(ui_rt_resolution.y), NULL);
}
G.world_rt_resolution = world_rt_resolution;
/* Backbuffer rt */
if (!G.backbuffer_rt.handle || !v2_eq(G.backbuffer_rt_resolution, backbuffer_rt_resolution)) {
renderer_backbuffer_resize(math_round_to_int(backbuffer_rt_resolution.x), math_round_to_int(backbuffer_rt_resolution.y));
G.backbuffer_rt = renderer_get_backbuffer_texture();
}
G.backbuffer_rt_resolution = backbuffer_rt_resolution;
G.ui_rt_resolution = ui_rt_resolution;
}
if (!v2_eq(G.backbuffer_size, G.screen_size)) {
G.backbuffer_size = G.screen_size;
renderer_resize_backbuffer(G.backbuffer_size);
G.world_rt_resolution = world_rt_resolution;
}
{
@ -1668,26 +1672,26 @@ INTERNAL void user_update(void)
/* Execute render cmds */
{
/* Clear textures */
renderer_clear_texture(G.world_rt, RGBA_32_F(0.2f, 0.2f, 0.2f, 1.f));
renderer_clear_texture(G.ui_rt, RGBA_32_F(0, 0, 0, 0));
renderer_clear_texture(G.final_rt, RGBA_32_F(0, 0, 0, 0));
renderer_clear_backbuffer(RGBA_32_F(0, 0, 0, 1));
renderer_texture_clear(G.world_rt, RGBA_32_F(0.2f, 0.2f, 0.2f, 1.f));
renderer_texture_clear(G.ui_rt, RGBA_32_F(0, 0, 0, 0));
renderer_texture_clear(G.final_rt, RGBA_32_F(0, 0, 0, 0));
renderer_texture_clear(G.backbuffer_rt, RGBA_32_F(0, 0, 0, 1));
/* Render to world texture */
renderer_render_to_texture(G.world_rt, G.world_cmd_buffer, G.world_to_ui_xf, target_viewport, sprite_frame_scope);
renderer_texture_render(G.world_rt, G.world_cmd_buffer, G.world_to_ui_xf, target_viewport, sprite_frame_scope);
/* Render to UI texture */
renderer_render_to_texture(G.ui_rt, G.ui_cmd_buffer, XFORM_IDENT, target_viewport, sprite_frame_scope);
renderer_texture_render(G.ui_rt, G.ui_cmd_buffer, XFORM_IDENT, target_viewport, sprite_frame_scope);
/* Render to final texture */
renderer_render_to_texture(G.final_rt, G.final_cmd_buffer, XFORM_IDENT, target_viewport, sprite_frame_scope);
renderer_texture_render(G.final_rt, G.final_cmd_buffer, XFORM_IDENT, target_viewport, sprite_frame_scope);
/* Render to backbuffer */
renderer_render_to_backbuffer(G.backbuffer_cmd_buffer, XFORM_IDENT, backbuffer_viewport, sprite_frame_scope);
renderer_texture_render(G.backbuffer_rt, G.backbuffer_cmd_buffer, XFORM_IDENT, backbuffer_viewport, sprite_frame_scope);
}
/* Present */
renderer_present_backbuffer(VSYNC_ENABLED);
renderer_backbuffer_present(VSYNC_ENABLED);
}
/* ========================== *