prep vis renderer

This commit is contained in:
jacob 2025-11-12 18:37:24 -06:00
parent 030d9ad6a8
commit fe34752146
11 changed files with 572 additions and 220 deletions

View File

@ -493,4 +493,4 @@ void GPU_YieldOnSwapchain(GPU_Swapchain *swapchain);
* 2. Blits `texture` into position `dst` in the backbuffer
* 3. Presents the backbuffer
* 4. Returns the value that the Direct queue fence will reach once GPU completes blitting (`texture` shouldn't be released while blit is in flight) */
i64 GPU_PresentSwapchain(GPU_Swapchain *swapchain, GPU_Resource *texture, i32 vsync, Vec2I32 backbuffer_size, Vec2I32 dst_p0, Vec2I32 dst_p1, Vec2I32 src_p0, Vec2I32 src_p1);
i64 GPU_PresentSwapchain(GPU_Swapchain *swapchain, GPU_Resource *texture, i32 vsync, Vec2I32 backbuffer_size, Vec2I32 dst_p0, Vec2I32 dst_p1, Vec2I32 src_p0, Vec2I32 src_p1, Vec4 clear_color);

View File

@ -1960,7 +1960,7 @@ GPU_D12_SwapchainBuffer *GPU_D12_UpdateSwapchain(GPU_D12_Swapchain *swapchain, V
return &swapchain->buffers[backbuffer_index];
}
i64 GPU_D12_BlitToSwapchain(GPU_D12_SwapchainBuffer *dst, GPU_D12_Resource *texture, Vec2I32 dst_p0, Vec2I32 dst_p1, Vec2I32 src_p0, Vec2I32 src_p1)
i64 GPU_D12_BlitToSwapchain(GPU_D12_SwapchainBuffer *dst, GPU_D12_Resource *texture, Vec2I32 dst_p0, Vec2I32 dst_p1, Vec2I32 src_p0, Vec2I32 src_p1, Vec4 clear_color)
{
GPU_D12_SharedState *g = &GPU_D12_shared_state;
@ -1986,8 +1986,7 @@ i64 GPU_D12_BlitToSwapchain(GPU_D12_SwapchainBuffer *dst, GPU_D12_Resource *text
/* Clear */
{
f32 clear_color[4] = ZI;
ID3D12GraphicsCommandList_ClearRenderTargetView(rcl, dst->rtv_descriptor->handle, clear_color, 0, 0);
ID3D12GraphicsCommandList_ClearRenderTargetView(rcl, dst->rtv_descriptor->handle, (f32 *)&clear_color, 0, 0);
}
{
@ -2194,7 +2193,7 @@ void GPU_YieldOnSwapchain(GPU_Swapchain *gpu_swapchain)
}
}
i64 GPU_PresentSwapchain(GPU_Swapchain *gpu_swapchain, GPU_Resource *gpu_texture, i32 vsync, Vec2I32 backbuffer_size, Vec2I32 dst_p0, Vec2I32 dst_p1, Vec2I32 src_p0, Vec2I32 src_p1)
i64 GPU_PresentSwapchain(GPU_Swapchain *gpu_swapchain, GPU_Resource *gpu_texture, i32 vsync, Vec2I32 backbuffer_size, Vec2I32 dst_p0, Vec2I32 dst_p1, Vec2I32 src_p0, Vec2I32 src_p1, Vec4 clear_color)
{
GPU_D12_Swapchain *swapchain = (GPU_D12_Swapchain *)gpu_swapchain;
GPU_D12_Resource *texture = (GPU_D12_Resource *)gpu_texture;
@ -2214,7 +2213,7 @@ i64 GPU_PresentSwapchain(GPU_Swapchain *gpu_swapchain, GPU_Resource *gpu_texture
if (is_blitable)
{
/* Blit */
fence_target = GPU_D12_BlitToSwapchain(swapchain_buffer, texture, dst_p0, dst_p1, src_p0, src_p1);
fence_target = GPU_D12_BlitToSwapchain(swapchain_buffer, texture, dst_p0, dst_p1, src_p0, src_p1, clear_color);
u32 present_flags = 0;
if (GPU_D12_TearingIsAllowed && vsync == 0)

View File

@ -380,7 +380,7 @@ u64 GPU_D12_EndRawCommandList(GPU_D12_RawCommandList *cl);
void GPU_D12_InitSwapchainResources(GPU_D12_Swapchain *swapchain);
GPU_D12_SwapchainBuffer *GPU_D12_UpdateSwapchain(GPU_D12_Swapchain *swapchain, Vec2I32 resolution);
i64 GPU_D12_BlitToSwapchain(GPU_D12_SwapchainBuffer *dst, GPU_D12_Resource *texture, Vec2I32 dst_p0, Vec2I32 dst_p1, Vec2I32 src_p0, Vec2I32 src_p1);
i64 GPU_D12_BlitToSwapchain(GPU_D12_SwapchainBuffer *dst, GPU_D12_Resource *texture, Vec2I32 dst_p0, Vec2I32 dst_p1, Vec2I32 src_p0, Vec2I32 src_p1, Vec4 clear_color);
////////////////////////////////////////////////////////////
//~ Sync job

View File

@ -48,6 +48,117 @@ b32 S_IsEntNil(S_Ent *ent)
return ent == 0 || ent == &S_nil_ent;
}
b32 S_MatchEntKey(S_EntKey a, S_EntKey b)
{
return a.v.hi == b.v.hi && a.v.lo == b.v.lo;
}
////////////////////////////////////////////////////////////
//~ Lookup helpers
S_Ent *S_EntFromKey(S_World *world, S_EntKey key)
{
S_Ent *ent = &S_nil_ent;
if (!S_IsKeyNil(key))
{
S_EntLookupNode *n = world->ent_bins[key.v.lo % world->ent_bins_count];
for (; n; n = n->next)
{
if (S_MatchEntKey(n->ent->key, key))
{
ent = n->ent;
break;
}
}
}
return ent;
}
////////////////////////////////////////////////////////////
//~ Snapshot helpers
S_World *S_WorldFromSnapshot(Arena *arena, S_Snapshot *snapshot)
{
TempArena scratch = BeginScratch(arena);
S_World *world = PushStruct(arena, S_World);
/* Copy ents */
world->ents = PushStructsNoZero(arena, S_Ent, snapshot->ents_count);
CopyStructs(world->ents, snapshot->ents, snapshot->ents_count);
world->ents_count = snapshot->ents_count;
world->tick = snapshot->tick;
/* Init lookup */
world->ent_bins_count = 4096;
world->ent_bins = PushStructs(arena, S_EntLookupNode *, world->ent_bins_count);
for (i64 ent_idx = 0; ent_idx < world->ents_count; ++ent_idx)
{
S_Ent *ent = &world->ents[ent_idx];
S_EntKey key = ent->key;
S_EntLookupNode *n = PushStruct(arena, S_EntLookupNode);
n->ent = ent;
S_EntLookupNode **bin = &world->ent_bins[ent->key.v.lo % world->ent_bins_count];
SllStackPush(*bin, n);
}
/* Sort tree */
{
i64 ents_count = world->ents_count;
S_Ent **ents_pre = PushStructsNoZero(arena, S_Ent *, ents_count);
S_Ent **ents_post = PushStructsNoZero(arena, S_Ent *, ents_count);
{
Struct(EntNode) { EntNode *next; b32 visited; S_Ent *ent; };
EntNode *first_dfs = 0;
i64 pre_idx = 0;
i64 post_idx = 0;
{
EntNode *n = PushStruct(scratch.arena, EntNode);
for (i64 ent_idx = 0; ent_idx < world->ents_count; ++ent_idx)
{
S_Ent *ent = &world->ents[ent_idx];
if (S_MatchEntKey(ent->key, S_RootEntKey))
{
n->ent = ent;
break;
}
}
SllStackPush(first_dfs, n);
}
while (first_dfs)
{
EntNode *n = first_dfs;
S_Ent *ent = n->ent;
if (!n->visited)
{
/* Push children to dfs stack */
for (S_Ent *child = S_EntFromKey(world, ent->first); !S_IsEntNil(child); child = S_EntFromKey(world, child->prev))
{
EntNode *child_n = PushStruct(scratch.arena, EntNode);
child_n->ent = child;
SllStackPush(first_dfs, child_n);
}
ent->pre_idx = pre_idx++;
ents_pre[ent->pre_idx] = ent;
n->visited = 1;
}
else
{
SllStackPop(first_dfs);
ent->post_idx = post_idx++;
ents_post[ent->post_idx] = ent;
}
}
Assert(pre_idx == ents_count);
Assert(post_idx == ents_count);
world->ents_pre = ents_pre;
world->ents_post = ents_post;
}
}
EndScratch(scratch);
return world;
}
////////////////////////////////////////////////////////////
//~ Sim worker
@ -58,24 +169,34 @@ JobDef(S_SimWorker, sig, job_id)
Arena *perm = PermArena();
//- World data
Arena *ents_arena = AcquireArena(Gibi(64));
S_Ent *ents = ArenaFirst(ents_arena, S_Ent);
i64 tick = 0;
Arena *world_arena = AcquireArena(Gibi(64));
S_World *world = 0;
/* Create root ent */
{
S_Ent *root_ent = PushStruct(ents_arena, S_Ent);
*root_ent = S_nil_ent;
root_ent->key = S_RootEntKey;
}
S_Snapshot *empty_ss = PushStruct(frame_arena, S_Snapshot);
{
empty_ss->ents = PushStructs(frame_arena, S_Ent, 1024);
/* Create root ent */
S_Ent *root_ent = &empty_ss->ents[empty_ss->ents_count++];
{
*root_ent = S_nil_ent;
root_ent->key = S_RootEntKey;
}
/* Create test ent */
S_Ent *test_ent = &empty_ss->ents[empty_ss->ents_count++];
{
*test_ent = S_nil_ent;
test_ent->shape.points_count = 1;
test_ent->shape.radius = 0.25;
test_ent->key = ((S_EntKey) { .v.hi = 0x66444f20b7e41f3d, .v.lo = 0x5a2df684b9430943 });
/* Create test ent */
{
S_Ent *test_ent = PushStruct(ents_arena, S_Ent);
*test_ent = S_nil_ent;
test_ent->parent = S_RootEntKey;
test_ent->shape.points_count = 1;
test_ent->shape.radius = 0.25;
test_ent->parent = root_ent->key;
root_ent->first = test_ent->key;
root_ent->last = test_ent->key;
++root_ent->count;
}
}
world = S_WorldFromSnapshot(world_arena, empty_ss);
}
//////////////////////////////
@ -127,17 +248,18 @@ JobDef(S_SimWorker, sig, job_id)
LockTicketMutex(&shared->output_back_tm);
{
S_OutputState *output = &shared->output_states[shared->output_back_idx];
S_WorldNode *world_node = PushStruct(output->arena, S_WorldNode);
S_World *world = &world_node->world;
SllQueuePush(output->first_world_node, output->last_world_node, world_node);
++output->worlds_count;
world->ents_count = ArenaCount(ents_arena, S_Ent);
world->tick = tick;
world->ents = PushStructsNoZero(output->arena, S_Ent, world->ents_count);
for (i64 ent_idx = 0; ent_idx < world->ents_count; ++ent_idx)
S_SnapshotNode *snapshot_node = PushStruct(output->arena, S_SnapshotNode);
S_Snapshot *snapshot = &snapshot_node->snapshot;
SllQueuePush(output->first_snapshot_node, output->last_snapshot_node, snapshot_node);
++output->snapshots_count;
snapshot->ents_count = world->ents_count;
snapshot->tick = world->tick;
snapshot->ents = PushStructsNoZero(output->arena, S_Ent, snapshot->ents_count);
for (i64 ent_idx = 0; ent_idx < snapshot->ents_count; ++ent_idx)
{
S_Ent *ent = &ents[ent_idx];
world->ents[ent_idx] = *ent;
S_Ent *src = &world->ents[ent_idx];
S_Ent *dst = &snapshot->ents[ent_idx];
*dst = *src;
}
}
UnlockTicketMutex(&shared->output_back_tm);
@ -155,7 +277,7 @@ JobDef(S_SimWorker, sig, job_id)
i64 frame_end_ns = TimeNs();
++tick;
++world->tick;
//////////////////////////////
//- Sleep

View File

@ -35,19 +35,32 @@ Enum(S_EntProp)
Struct(S_Ent)
{
//- Tree data
S_EntKey parent;
S_EntKey first;
S_EntKey last;
S_EntKey next;
S_EntKey prev;
i64 count;
//- Persistent data
S_EntKey key;
S_Shape shape;
//- Per-world data
i64 pre_idx;
i64 post_idx;
} extern Readonly S_nil_ent;
//////////////////////////////
//- Ent list
//- Ent containers
Struct(S_EntArray)
{
i64 count;
S_Ent *ents;
};
Struct(S_EntListNode)
{
@ -62,28 +75,40 @@ Struct(S_EntList)
u64 count;
};
////////////////////////////////////////////////////////////
//~ Lookup types
Struct(S_EntLookupBin)
Struct(S_EntLookupNode)
{
i32 _;
S_EntLookupNode *next;
S_Ent *ent;
};
////////////////////////////////////////////////////////////
//~ World types
Struct(S_World)
{
i64 tick;
S_Ent *ents;
i64 ents_count;
S_EntLookupNode **ent_bins;
i64 ent_bins_count;
S_Ent **ents_pre;
S_Ent **ents_post;
};
Struct(S_Snapshot)
{
i64 tick;
S_Ent *ents;
i64 ents_count;
};
Struct(S_WorldNode)
Struct(S_SnapshotNode)
{
S_WorldNode *next;
S_World world;
S_SnapshotNode *next;
S_Snapshot snapshot;
};
////////////////////////////////////////////////////////////
@ -124,9 +149,9 @@ Struct(S_InputState)
Struct(S_OutputState)
{
Arena *arena;
S_WorldNode *first_world_node;
S_WorldNode *last_world_node;
u64 worlds_count;
S_SnapshotNode *first_snapshot_node;
S_SnapshotNode *last_snapshot_node;
u64 snapshots_count;
};
Struct(S_SharedState)
@ -158,6 +183,17 @@ void S_Shutdown(void);
b32 S_IsKeyNil(S_EntKey key);
b32 S_IsEntNil(S_Ent *ent);
b32 S_MatchEntKey(S_EntKey a, S_EntKey b);
////////////////////////////////////////////////////////////
//~ Lookup helpers
S_Ent *S_EntFromKey(S_World *world, S_EntKey key);
////////////////////////////////////////////////////////////
//~ Snapshot helpers
S_World *S_WorldFromSnapshot(Arena *arena, S_Snapshot *snapshot);
////////////////////////////////////////////////////////////
//~ Sim worker

View File

@ -15,14 +15,21 @@
//- Api
@IncludeC pp_vis_widgets.h
@IncludeC pp_vis_draw.h
@IncludeC pp_vis_core.h
@IncludeGpu pp_vis_draw.h
//- Impl
@IncludeC pp_vis_widgets.c
@IncludeC pp_vis_core.c
@IncludeGpu pp_vis_draw.gpu
//- Embeds
@EmbedDir V_Resources pp_vis_res
//- Shaders
@VertexShader V_DQuadVS
@PixelShader V_DQuadPS
//- Startup
@Startup V_Startup

View File

@ -32,17 +32,22 @@ JobDef(V_VisWorker, sig, job_id)
Arena *frame_arena = AcquireArena(Gibi(64));
Arena *perm = PermArena();
i64 gpu_fence_target = 0;
u64 frame_gen = 0;
//////////////////////////////
//- State
Struct(VisPersist)
Fence *gpu_fence = GPU_FenceFromQueue(GPU_QueueKind_Direct);
i64 gpu_fence_target = 0;
i64 frame_gen = 0;
GPU_Resource *draw_target = 0;
Struct(Persist)
{
V_CommandsWidget commands_widget;
b32 ui_debug;
b32 show_command_palette;
b32 show_console;
};
VisPersist persist = ZI;
Persist persist = ZI;
String window_restore = ZI;
Button held_buttons[Button_Count] = ZI;
@ -87,7 +92,7 @@ JobDef(V_VisWorker, sig, job_id)
BB_Reader br = BB_ReaderFromBuff(&bb);
String swap_str = BB_ReadString(scratch.arena, &br);
if (swap_str.len == sizeof(VisPersist))
if (swap_str.len == sizeof(Persist))
{
CopyBytes(&persist, swap_str.text, swap_str.len);
}
@ -102,14 +107,8 @@ JobDef(V_VisWorker, sig, job_id)
Arena *ents_arena = AcquireArena(Gibi(64));
S_Ent *ents = ArenaFirst(ents_arena, S_Ent);
i64 tick = 0;
Arena *world_arena = AcquireArena(Gibi(64));
S_World *world = PushStruct(world_arena, S_World);
b32 shutdown = 0;
while (!shutdown)
@ -120,11 +119,13 @@ JobDef(V_VisWorker, sig, job_id)
//- Begin vis frame
UI_FrameFlag ui_frame_flags = 0;
Vec4 swapchain_color = V_GetWidgetTheme().window_background_color;
ui_frame_flags |= UI_FrameFlag_Debug * !!persist.ui_debug;
ui_frame_flags |= UI_FrameFlag_Vsync * !!VSYNC;
UI_Frame ui_frame = UI_BeginFrame(ui_frame_flags);
UI_Frame ui_frame = UI_BeginFrame(ui_frame_flags, swapchain_color);
WND_Frame window_frame = ui_frame.window_frame;
Vec2 ui_cursor = ui_frame.cursor_pos;
Vec2I32 draw_size = window_frame.monitor_size;
/* Restore window */
{
@ -142,8 +143,8 @@ JobDef(V_VisWorker, sig, job_id)
UI_Push(ChildLayoutAxis, Axis_Y);
UI_Push(Width, UI_GROW(1, 0));
UI_Push(Height, UI_GROW(1, 0));
UI_Push(Parent, UI_BuildColumnEx(UI_KeyF("AAH")));
UI_Key vis_box = UI_KeyF("vis box");
UI_Push(Parent, UI_BuildColumnEx(vis_box));
//////////////////////////////
//- Pop sim output
@ -160,6 +161,12 @@ JobDef(V_VisWorker, sig, job_id)
}
UnlockTicketMutex(&sim_shared->output_back_tm);
if (sim_output->last_snapshot_node && sim_output->last_snapshot_node->snapshot.tick > world->tick)
{
ResetArena(world_arena);
world = S_WorldFromSnapshot(world_arena, &sim_output->last_snapshot_node->snapshot);
}
//////////////////////////////
//- Process controller events vis cmds
@ -329,13 +336,53 @@ JobDef(V_VisWorker, sig, job_id)
//////////////////////////////
//- Render
/* Acquire draw target */
{
if (draw_target && !MatchVec2I32(draw_size, GPU_GetTextureSize2D(draw_target)))
{
YieldOnFence(gpu_fence, gpu_fence_target);
GPU_ReleaseResource(draw_target, GPU_ReleaseFlag_None);
draw_target = 0;
}
if (!draw_target)
{
GPU_ResourceDesc desc = ZI;
desc.kind = GPU_ResourceKind_Texture2D;
desc.flags = GPU_ResourceFlag_Writable | GPU_ResourceFlag_Renderable;
desc.texture.format = GPU_Format_R16G16B16A16_Float;
desc.clear_color = LinearFromSrgb(swapchain_color);
draw_target = GPU_AcquireResource(desc);
}
}
GPU_CommandList *cl = GPU_BeginCommandList(GPU_QueueKind_Direct);
{
/* Prep draw target */
{
GPU_TransitionToRenderable(cl, draw_target, 0);
GPU_ClearRenderable(cl, draw_target);
}
/* Prep shapes pass */
{
}
/* Shapes pass */
{
}
/* Transition draw target for UI composition */
{
GPU_TransitionToReadable(cl, draw_target);
}
}
gpu_fence_target = GPU_EndCommandList(cl);
//////////////////////////////
//- End vis frame
UI_SetRawTexture(vis_box, draw_target, VEC2(0, 0), VEC2(1, 1));
gpu_fence_target = UI_EndFrame(ui_frame);
++frame_gen;

View File

@ -0,0 +1,49 @@
ConstantBuffer<V_DQuadSig> V_dquad_sig : register (b0);
////////////////////////////////////////////////////////////
//~ Quad
Struct(V_DQuadPS_Input)
{
Semantic(Vec4, sv_position);
Semantic(nointerpolation u32, quad_idx);
};
Struct(V_DQuadPS_Output)
{
Semantic(Vec4, sv_target0);
};
//- Vertex shader
V_DQuadPS_Input VSDef(V_DQuadVS, Semantic(u32, sv_instanceid), Semantic(u32, sv_vertexid))
{
ConstantBuffer<V_DQuadSig> sig = V_dquad_sig;
StructuredBuffer<V_DQuad> quads = UniformResourceFromRid(sig.quads);
V_DQuad quad = quads[sv_instanceid];
Vec2 rect_uv = RectUvFromVertexId(sv_vertexid);
// Vec2 tex_uv = lerp(quad.tex_uv0, quad.tex_uv1, rect_uv);
// Vec2 screen_vert = lerp(quad.p0, quad.p1, rect_uv);
Vec2 screen_vert = 0;
V_DQuadPS_Input result;
result.sv_position = Vec4(NdcFromViewport(sig.viewport_size, screen_vert).xy, 0, 1);
result.quad_idx = sv_instanceid;
return result;
}
//- Pixel shader
V_DQuadPS_Output PSDef(V_DQuadPS, V_DQuadPS_Input input)
{
ConstantBuffer<V_DQuadSig> sig = V_dquad_sig;
StructuredBuffer<V_DQuad> quads = UniformResourceFromRid(sig.quads);
V_DQuad quad = quads[input.quad_idx];
Vec4 final_color = 0;
V_DQuadPS_Output output;
output.sv_target0 = final_color;
return output;
}

View File

@ -0,0 +1,28 @@
////////////////////////////////////////////////////////////
//~ Quad types
Struct(V_DQuadSig)
{
/* ----------------------------------------------------- */
Vec2I32 viewport_size; /* 02 consts */
StructuredBufferRid quads; /* 01 consts */
SamplerStateRid sampler; /* 01 consts */
/* ----------------------------------------------------- */
b32 debug_enabled; /* 01 consts */
u32 _pad0; /* 01 consts (padding) */
u32 _pad1; /* 01 consts (padding) */
u32 _pad2; /* 01 consts (padding) */
/* ----------------------------------------------------- */
};
AssertRootConst(V_DQuadSig, 8);
Enum(V_DQuadFlag)
{
V_DQuadFlag_None = 0,
V_DQuadFlag_DrawGrid = (1 << 0),
};
Struct(V_DQuad)
{
V_DQuadFlag flags;
};

View File

@ -376,31 +376,48 @@ UI_Key UI_BuildBoxEx(UI_Key key)
{
UI_State *g = &UI_state;
UI_CmdNode *n = PushStruct(g->bframe.cmds_arena, UI_CmdNode);
n->cmd.kind = UI_CmdKind_BuildBox;
{
n->cmd.key = key;
n->cmd.parent = UI_UseTop(Parent);
n->cmd.flags = UI_UseTop(Flags);
n->cmd.pref_size[Axis_X] = UI_UseTop(Width);
n->cmd.pref_size[Axis_Y] = UI_UseTop(Height);
n->cmd.child_alignment[Axis_X] = UI_UseTop(ChildAlignmentX);
n->cmd.child_alignment[Axis_Y] = UI_UseTop(ChildAlignmentY);
n->cmd.child_layout_axis = UI_UseTop(ChildLayoutAxis);
n->cmd.background_color = UI_UseTop(BackgroundColor);
n->cmd.border_color = UI_UseTop(BorderColor);
n->cmd.debug_color = UI_UseTop(DebugColor);
n->cmd.tint = UI_UseTop(Tint);
n->cmd.border = UI_UseTop(Border);
n->cmd.font_resource = UI_UseTop(Font);
n->cmd.font_size = UI_UseTop(FontSize);
n->cmd.rounding = UI_UseTop(Rounding);
n->cmd.text = UI_UseTop(Text);
n->cmd.floating_pos = UI_UseTop(FloatingPos);
n->cmd.box.key = key;
n->cmd.box.parent = UI_UseTop(Parent);
n->cmd.box.flags = UI_UseTop(Flags);
n->cmd.box.pref_size[Axis_X] = UI_UseTop(Width);
n->cmd.box.pref_size[Axis_Y] = UI_UseTop(Height);
n->cmd.box.child_alignment[Axis_X] = UI_UseTop(ChildAlignmentX);
n->cmd.box.child_alignment[Axis_Y] = UI_UseTop(ChildAlignmentY);
n->cmd.box.child_layout_axis = UI_UseTop(ChildLayoutAxis);
n->cmd.box.background_color = UI_UseTop(BackgroundColor);
n->cmd.box.border_color = UI_UseTop(BorderColor);
n->cmd.box.debug_color = UI_UseTop(DebugColor);
n->cmd.box.tint = UI_UseTop(Tint);
n->cmd.box.border = UI_UseTop(Border);
n->cmd.box.font_resource = UI_UseTop(Font);
n->cmd.box.font_size = UI_UseTop(FontSize);
n->cmd.box.rounding = UI_UseTop(Rounding);
n->cmd.box.text = UI_UseTop(Text);
n->cmd.box.floating_pos = UI_UseTop(FloatingPos);
}
++g->bframe.cmds_count;
SllQueuePush(g->bframe.first_cmd_node, g->bframe.last_cmd_node, n);
return key;
}
void UI_SetRawTexture(UI_Key key, GPU_Resource *texture, Vec2 uv0, Vec2 uv1)
{
UI_State *g = &UI_state;
UI_CmdNode *n = PushStruct(g->bframe.cmds_arena, UI_CmdNode);
n->cmd.kind = UI_CmdKind_SetRawTexture;
{
n->cmd.set_raw_texture.key = key;
n->cmd.set_raw_texture.texture = texture;
n->cmd.set_raw_texture.uv0 = uv0;
n->cmd.set_raw_texture.uv1 = uv1;
}
++g->bframe.cmds_count;
SllQueuePush(g->bframe.first_cmd_node, g->bframe.last_cmd_node, n);
}
////////////////////////////////////////////////////////////
//~ Report
@ -421,7 +438,7 @@ UI_Report UI_ReportFromKey(UI_Key key)
////////////////////////////////////////////////////////////
//~ Begin frame
UI_Frame UI_BeginFrame(UI_FrameFlag frame_flags)
UI_Frame UI_BeginFrame(UI_FrameFlag frame_flags, Vec4 swapchain_color)
{
UI_State *g = &UI_state;
UI_Frame result = ZI;
@ -474,6 +491,7 @@ UI_Frame UI_BeginFrame(UI_FrameFlag frame_flags)
}
g->bframe.frame_flags = frame_flags;
g->bframe.swapchain_color = swapchain_color;
//////////////////////////////
//- Begin window frame
@ -509,7 +527,7 @@ UI_Frame UI_BeginFrame(UI_FrameFlag frame_flags)
for (u64 pre_index = g->boxes_count; pre_index-- > 0;)
{
UI_Box *box = g->eframe.boxes_pre[pre_index];
if (hovered_box == 0 && box->cmd.flags & UI_BoxFlag_Interactable)
if (hovered_box == 0 && box->desc.flags & UI_BoxFlag_Interactable)
{
b32 is_cursor_in_box = 0;
{
@ -713,104 +731,125 @@ i64 UI_EndFrame(UI_Frame frame)
//////////////////////////////
//- Process commands
/* FIXME: Reset raw texture data */
g->boxes_count = 0;
for (UI_CmdNode *cmd_node = g->bframe.first_cmd_node; cmd_node; cmd_node = cmd_node->next)
{
UI_Cmd cmd = cmd_node->cmd;
UI_Key key = cmd.key;
if (key.hash == 0)
switch (cmd.kind)
{
key = UI_TransKey();
}
b32 is_root = g->eframe.root_box == 0;
UI_Box *parent = 0;
if (!is_root)
{
parent = UI_BoxFromKey(cmd.parent);
if (!parent)
case UI_CmdKind_BuildBox:
{
parent = g->eframe.root_box;
}
}
/* Allocate box */
UI_Box *box = 0;
{
UI_BoxBin *bin = &g->box_bins[key.hash % UI_NumBoxLookupBins];
for (UI_Box *tmp = bin->first; tmp && !box; tmp = tmp->next_in_bin)
{
if (tmp->key.hash == key.hash)
UI_Key key = cmd.box.key;
if (key.hash == 0)
{
box = tmp;
key = UI_TransKey();
}
}
if (box)
{
/* Remove box from old parent */
if (box->parent)
b32 is_root = g->eframe.root_box == 0;
UI_Box *parent = 0;
if (!is_root)
{
DllQueueRemove(box->parent->first, box->parent->last, box);
--box->parent->count;
parent = UI_BoxFromKey(cmd.box.parent);
if (!parent)
{
parent = g->eframe.root_box;
}
}
}
else
/* Allocate box */
UI_Box *box = 0;
{
UI_BoxBin *bin = &g->box_bins[key.hash % UI_NumBoxLookupBins];
for (UI_Box *tmp = bin->first; tmp && !box; tmp = tmp->next_in_bin)
{
if (tmp->key.hash == key.hash)
{
box = tmp;
}
}
if (box)
{
/* Remove box from old parent */
if (box->parent)
{
DllQueueRemove(box->parent->first, box->parent->last, box);
--box->parent->count;
}
}
else
{
box = g->first_free_box;
if (box)
{
SllStackPop(g->first_free_box);
ZeroStruct(box);
}
else
{
box = PushStruct(g->box_arena, UI_Box);
}
DllQueuePushNP(bin->first, bin->last, box, next_in_bin, prev_in_bin);
}
}
++g->boxes_count;
/* Reset box */
UI_Box old_box = *box;
{
ZeroStruct(box);
box->next_in_bin = old_box.next_in_bin;
box->prev_in_bin = old_box.prev_in_bin;
box->report = old_box.report;
}
box->key = key;
box->last_updated_tick = g->eframe.tick;
/* Update box */
{
box->desc = cmd.box;
/* Insert box into parent */
if (parent)
{
DllQueuePush(parent->first, parent->last, box);
box->parent = parent;
++parent->count;
}
/* Prefetch font */
if (box->desc.text.len > 0)
{
box->font = F_LoadFontAsync(box->desc.font_resource, box->desc.font_size);
if (box->font)
{
box->glyph_run = F_RunFromString(g->eframe.layout_arena, box->font, box->desc.text);
}
}
}
if (is_root)
{
g->eframe.root_box = box;
}
} break;
case UI_CmdKind_SetRawTexture:
{
box = g->first_free_box;
UI_Key key = cmd.set_raw_texture.key;
UI_Box *box = UI_BoxFromKey(key);
if (box)
{
SllStackPop(g->first_free_box);
ZeroStruct(box);
box->raw_texture = cmd.set_raw_texture.texture;
box->raw_texture_uv0 = cmd.set_raw_texture.uv0;
box->raw_texture_uv1 = cmd.set_raw_texture.uv1;
}
else
{
box = PushStruct(g->box_arena, UI_Box);
}
DllQueuePushNP(bin->first, bin->last, box, next_in_bin, prev_in_bin);
}
}
++g->boxes_count;
/* Reset box */
UI_Box old_box = *box;
{
ZeroStruct(box);
box->next_in_bin = old_box.next_in_bin;
box->prev_in_bin = old_box.prev_in_bin;
box->report = old_box.report;
}
box->key = key;
box->last_updated_tick = g->eframe.tick;
/* Update box */
{
box->cmd = cmd;
/* Insert box into parent */
if (parent)
{
DllQueuePush(parent->first, parent->last, box);
box->parent = parent;
++parent->count;
}
/* Prefetch font */
if (box->cmd.text.len > 0)
{
box->font = F_LoadFontAsync(box->cmd.font_resource, box->cmd.font_size);
if (box->font)
{
box->glyph_run = F_RunFromString(g->eframe.layout_arena, box->font, box->cmd.text);
}
}
} break;
}
if (is_root)
{
g->eframe.root_box = box;
}
}
//////////////////////////////
@ -855,7 +894,7 @@ i64 UI_EndFrame(UI_Frame frame)
/* Push floating children to dfs stack */
for (UI_Box *child = box->last; child; child = child->prev)
{
if (AnyBit(child->cmd.flags, UI_BoxFlag_Floating))
if (AnyBit(child->desc.flags, UI_BoxFlag_Floating))
{
BoxNode *child_n = PushStruct(scratch.arena, BoxNode);
child_n->box = child;
@ -865,7 +904,7 @@ i64 UI_EndFrame(UI_Frame frame)
/* Push non-floating children to dfs stack */
for (UI_Box *child = box->last; child; child = child->prev)
{
if (!AnyBit(child->cmd.flags, UI_BoxFlag_Floating))
if (!AnyBit(child->desc.flags, UI_BoxFlag_Floating))
{
BoxNode *child_n = PushStruct(scratch.arena, BoxNode);
child_n->box = child;
@ -893,12 +932,12 @@ i64 UI_EndFrame(UI_Frame frame)
UI_Box *box = boxes_pre[pre_index];
for (Axis axis = 0; axis < Axis_CountXY; ++axis)
{
UI_Size pref_size = box->cmd.pref_size[axis];
UI_Size pref_size = box->desc.pref_size[axis];
if (pref_size.kind == UI_SizeKind_Pixel)
{
box->solved_dims[axis] = pref_size.v;
}
else if (pref_size.kind == UI_SizeKind_Shrink && AnyBit(box->cmd.flags, UI_BoxFlag_DrawText))
else if (pref_size.kind == UI_SizeKind_Shrink && AnyBit(box->desc.flags, UI_BoxFlag_DrawText))
{
/* TODO: Distinguish between baseline alignment & visual alignment */
f32 text_size = 0;
@ -929,16 +968,16 @@ i64 UI_EndFrame(UI_Frame frame)
UI_Box *box = boxes_pre[pre_index];
if (box->parent)
{
Axis axis = box->parent->cmd.child_layout_axis;
UI_Size pref_size = box->cmd.pref_size[axis];
Axis axis = box->parent->desc.child_layout_axis;
UI_Size pref_size = box->desc.pref_size[axis];
if (pref_size.kind == UI_SizeKind_Grow)
{
f32 match_size = 0;
b32 found_match = 0;
for (UI_Box *ancestor = box->parent; ancestor != 0 && !found_match; ancestor = ancestor->parent)
{
UI_Size ancestor_size = ancestor->cmd.pref_size[axis];
if (ancestor_size.kind == UI_SizeKind_Pixel || (ancestor_size.kind == UI_SizeKind_Shrink && AnyBit(box->cmd.flags, UI_BoxFlag_DrawText)))
UI_Size ancestor_size = ancestor->desc.pref_size[axis];
if (ancestor_size.kind == UI_SizeKind_Pixel || (ancestor_size.kind == UI_SizeKind_Shrink && AnyBit(box->desc.flags, UI_BoxFlag_DrawText)))
{
/* Match independent ancestor */
match_size = ancestor->solved_dims[axis];
@ -956,15 +995,15 @@ i64 UI_EndFrame(UI_Frame frame)
UI_Box *box = boxes_post[post_index];
for (Axis axis = 0; axis < Axis_CountXY; ++axis)
{
UI_Size pref_size = box->cmd.pref_size[axis];
if (pref_size.kind == UI_SizeKind_Shrink && !AnyBit(box->cmd.flags, UI_BoxFlag_DrawText))
UI_Size pref_size = box->desc.pref_size[axis];
if (pref_size.kind == UI_SizeKind_Shrink && !AnyBit(box->desc.flags, UI_BoxFlag_DrawText))
{
f32 accum = 0;
for (UI_Box *child = box->first; child; child = child->next)
{
if (!AnyBit(child->cmd.flags, UI_BoxFlag_Floating))
if (!AnyBit(child->desc.flags, UI_BoxFlag_Floating))
{
if (axis == box->cmd.child_layout_axis)
if (axis == box->desc.child_layout_axis)
{
accum += child->solved_dims[axis];
}
@ -985,8 +1024,8 @@ i64 UI_EndFrame(UI_Frame frame)
UI_Box *box = boxes_pre[pre_index];
if (box->parent)
{
Axis axis = !box->parent->cmd.child_layout_axis;
UI_Size pref_size = box->cmd.pref_size[axis];
Axis axis = !box->parent->desc.child_layout_axis;
UI_Size pref_size = box->desc.pref_size[axis];
if (pref_size.kind == UI_SizeKind_Grow)
{
box->solved_dims[axis] = box->parent->solved_dims[axis] * pref_size.v;
@ -1007,12 +1046,12 @@ i64 UI_EndFrame(UI_Frame frame)
f32 flex_accum = 0;
for (UI_Box *child = box->first; child; child = child->next)
{
if (!AnyBit(child->cmd.flags, UI_BoxFlag_Floating))
if (!AnyBit(child->desc.flags, UI_BoxFlag_Floating))
{
f32 size = child->solved_dims[axis];
f32 strictness = child->cmd.pref_size[axis].strictness;
f32 strictness = child->desc.pref_size[axis].strictness;
f32 flex = size * (1.0 - strictness);
if (axis == box->cmd.child_layout_axis)
if (axis == box->desc.child_layout_axis)
{
size_accum += size;
flex_accum += flex;
@ -1030,13 +1069,13 @@ i64 UI_EndFrame(UI_Frame frame)
f32 adjusted_size_accum = 0;
for (UI_Box *child = box->first; child; child = child->next)
{
if (!AnyBit(child->cmd.flags, UI_BoxFlag_Floating))
if (!AnyBit(child->desc.flags, UI_BoxFlag_Floating))
{
f32 size = child->solved_dims[axis];
f32 strictness = child->cmd.pref_size[axis].strictness;
f32 strictness = child->desc.pref_size[axis].strictness;
f32 flex = size * (1.0 - strictness);
f32 new_size = size;
if (axis == box->cmd.child_layout_axis)
if (axis == box->desc.child_layout_axis)
{
f32 chopoff = MinF32(flex, violation * (flex / flex_accum));
new_size = size - chopoff;
@ -1059,12 +1098,12 @@ i64 UI_EndFrame(UI_Frame frame)
/* Solve floating violations */
for (UI_Box *child = box->first; child; child = child->next)
{
if (AnyBit(child->cmd.flags, UI_BoxFlag_Floating) && !AnyBit(child->cmd.flags, UI_BoxFlag_NoFloatingClamp))
if (AnyBit(child->desc.flags, UI_BoxFlag_Floating) && !AnyBit(child->desc.flags, UI_BoxFlag_NoFloatingClamp))
{
f32 size = child->solved_dims[axis];
if (size > box_size)
{
f32 strictness = child->cmd.pref_size[axis].strictness;
f32 strictness = child->desc.pref_size[axis].strictness;
f32 flex = size * (1.0 - strictness);
child->solved_dims[axis] = MaxF32(size - flex, box_size);
}
@ -1081,8 +1120,8 @@ i64 UI_EndFrame(UI_Frame frame)
/* Initialize layout cursor based on alignment */
{
Axis axis = box->cmd.child_layout_axis;
UI_AxisAlignment alignment = box->cmd.child_alignment[axis];
Axis axis = box->desc.child_layout_axis;
UI_AxisAlignment alignment = box->desc.child_alignment[axis];
f32 box_size = box->solved_dims[axis];
f32 size_accum = box->final_children_size_accum[axis];
switch(alignment)
@ -1106,11 +1145,11 @@ i64 UI_EndFrame(UI_Frame frame)
Vec2 final_pos = ZI;
/* Floating box position */
if (AnyBit(box->cmd.flags, UI_BoxFlag_Floating))
if (AnyBit(box->desc.flags, UI_BoxFlag_Floating))
{
Vec2 offset = box->cmd.floating_pos;
Vec2 offset = box->desc.floating_pos;
final_pos = AddVec2(parent->p0, offset);
if (!AnyBit(box->cmd.flags, UI_BoxFlag_NoFloatingClamp))
if (!AnyBit(box->desc.flags, UI_BoxFlag_NoFloatingClamp))
{
{
f32 overshoot = MaxF32(0, (final_pos.x + dims_vec.x) - parent->p1.x);
@ -1129,13 +1168,13 @@ i64 UI_EndFrame(UI_Frame frame)
f32 offset[2] = ZI;
/* Calculate offset in layout direction */
{
Axis axis = parent->cmd.child_layout_axis;
Axis axis = parent->desc.child_layout_axis;
offset[axis] = layout_cursor;
}
/* Calculate offset in non-layout direction (based on alignment) */
{
Axis axis = !parent->cmd.child_layout_axis;
UI_AxisAlignment alignment = parent->cmd.child_alignment[axis];
Axis axis = !parent->desc.child_layout_axis;
UI_AxisAlignment alignment = parent->desc.child_alignment[axis];
switch(alignment)
{
default: break;
@ -1155,7 +1194,7 @@ i64 UI_EndFrame(UI_Frame frame)
}
final_pos.x = parent->p0.x + offset[0];
final_pos.y = parent->p0.y + offset[1];
parent->layout_cursor += dims_arr[parent->cmd.child_layout_axis];
parent->layout_cursor += dims_arr[parent->desc.child_layout_axis];
}
/* Submit position */
@ -1167,7 +1206,7 @@ i64 UI_EndFrame(UI_Frame frame)
/* Rounding */
{
UI_Round rounding = box->cmd.rounding;
UI_Round rounding = box->desc.rounding;
Vec2 half_dims = MulVec2(SubVec2(box->p1, box->p0), 0.5);
f32 min_half_dims = MinF32(half_dims.x, half_dims.y);
f32 final_rounding_tl = 0;
@ -1194,7 +1233,7 @@ i64 UI_EndFrame(UI_Frame frame)
} break;
}
if (parent && !AllBits(box->cmd.flags, UI_BoxFlag_Floating | UI_BoxFlag_NoFloatingClamp))
if (parent && !AllBits(box->desc.flags, UI_BoxFlag_Floating | UI_BoxFlag_NoFloatingClamp))
{
Vec2 vtl = SubVec2(VEC2(parent->p0.x, parent->p0.y), VEC2(box->p0.x, box->p0.y));
Vec2 vtr = SubVec2(VEC2(parent->p1.x, parent->p0.y), VEC2(box->p1.x, box->p0.y));
@ -1225,7 +1264,7 @@ i64 UI_EndFrame(UI_Frame frame)
{
UI_Box *box = boxes_pre[pre_index];
b32 is_visible = 1;
is_visible = is_visible && (box->cmd.tint.w != 0);
is_visible = is_visible && (box->desc.tint.w != 0);
is_visible = is_visible && (box->p1.x > box->p0.x);
is_visible = is_visible && (box->p1.y > box->p0.y);
if (is_visible || AnyBit(g->bframe.frame_flags, UI_FrameFlag_Debug))
@ -1233,32 +1272,32 @@ i64 UI_EndFrame(UI_Frame frame)
/* Box rect */
{
UI_DRect *rect = PushStruct(g->eframe.rects_arena, UI_DRect);
rect->flags |= UI_DRectFlag_DrawTexture * !!(box->cmd.background_texture != 0);
rect->flags |= UI_DRectFlag_DrawTexture * !!(box->raw_texture != 0);
rect->p0 = box->p0;
rect->p1 = box->p1;
rect->tex_uv0 = VEC2(0, 0);
rect->tex_uv1 = VEC2(1, 1);
rect->background_lin = LinearFromSrgb(box->cmd.background_color);
rect->border_lin = LinearFromSrgb(box->cmd.border_color);
rect->debug_lin = LinearFromSrgb(box->cmd.debug_color);
rect->tint_lin = LinearFromSrgb(box->cmd.tint);
rect->border = box->cmd.border;
rect->background_lin = LinearFromSrgb(box->desc.background_color);
rect->border_lin = LinearFromSrgb(box->desc.border_color);
rect->debug_lin = LinearFromSrgb(box->desc.debug_color);
rect->tint_lin = LinearFromSrgb(box->desc.tint);
rect->border = box->desc.border;
rect->tl_rounding = box->rounding_tl;
rect->tr_rounding = box->rounding_tr;
rect->br_rounding = box->rounding_br;
rect->bl_rounding = box->rounding_bl;
/* Texture */
if (box->cmd.background_texture != 0)
if (box->raw_texture != 0)
{
rect->tex = GPU_Texture2DRidFromResource(box->cmd.background_texture);
rect->tex_uv0 = box->cmd.background_texture_uv0;
rect->tex_uv1 = box->cmd.background_texture_uv1;
rect->tex = GPU_Texture2DRidFromResource(box->raw_texture);
rect->tex_uv0 = box->raw_texture_uv0;
rect->tex_uv1 = box->raw_texture_uv1;
}
}
/* Text rects */
if (AnyBit(box->cmd.flags, UI_BoxFlag_DrawText) && box->glyph_run.count > 0 && box->font)
if (AnyBit(box->desc.flags, UI_BoxFlag_DrawText) && box->glyph_run.count > 0 && box->font)
{
Texture2DRid tex_rid = GPU_Texture2DRidFromResource(box->font->texture);
Vec2 inv_font_image_size = VEC2(1.0f / (f32)box->font->image_width, 1.0f / (f32)box->font->image_height);
@ -1268,7 +1307,7 @@ i64 UI_EndFrame(UI_Frame frame)
b32 should_truncate = run.count > 0 && (run.rects[run.count - 1].pos + run.rects[run.count - 1].advance) > max_baseline;
/* Truncate run */
if (should_truncate && !AnyBit(box->cmd.flags, UI_BoxFlag_NoTextTruncation))
if (should_truncate && !AnyBit(box->desc.flags, UI_BoxFlag_NoTextTruncation))
{
/* Get elipses run */
F_Run trunc_run = F_RunFromString(scratch.arena, box->font, Lit("..."));
@ -1305,8 +1344,8 @@ i64 UI_EndFrame(UI_Frame frame)
run.rects = new_rects;
}
UI_AxisAlignment x_alignment = box->cmd.child_alignment[Axis_X];
UI_AxisAlignment y_alignment = box->cmd.child_alignment[Axis_Y];
UI_AxisAlignment x_alignment = box->desc.child_alignment[Axis_X];
UI_AxisAlignment y_alignment = box->desc.child_alignment[Axis_Y];
if (should_truncate)
{
x_alignment = UI_AxisAlignment_Start;
@ -1373,8 +1412,8 @@ i64 UI_EndFrame(UI_Frame frame)
rect->p0 = AddVec2(baseline, VEC2(rr.pos, 0));
rect->p0 = AddVec2(rect->p0, rr.offset);
rect->p1 = AddVec2(rect->p0, glyph_size);
rect->debug_lin = LinearFromSrgb(box->cmd.debug_color);
rect->tint_lin = LinearFromSrgb(box->cmd.tint);
rect->debug_lin = LinearFromSrgb(box->desc.debug_color);
rect->tint_lin = LinearFromSrgb(box->desc.tint);
rect->tex_uv0 = MulVec2Vec2(atlas_p0, inv_font_image_size);
rect->tex_uv1 = MulVec2Vec2(atlas_p1, inv_font_image_size);
rect->tex = tex_rid;
@ -1467,7 +1506,7 @@ i64 UI_EndFrame(UI_Frame frame)
Vec2I32 dst_p1 = VEC2I32(0, 0);
Vec2I32 src_p0 = VEC2I32(0, 0);
Vec2I32 src_p1 = draw_size;
g->eframe.gpu_submit_fence_target = GPU_PresentSwapchain(g->eframe.swapchain, g->eframe.render_target, AnyBit(g->bframe.frame_flags, UI_FrameFlag_Vsync), backbuffer_size, dst_p0, dst_p1, src_p0, src_p1);
g->eframe.gpu_submit_fence_target = GPU_PresentSwapchain(g->eframe.swapchain, g->eframe.render_target, AnyBit(g->bframe.frame_flags, UI_FrameFlag_Vsync), backbuffer_size, dst_p0, dst_p1, src_p0, src_p1, LinearFromSrgb(g->bframe.swapchain_color));
}
WND_EndFrame(frame.window_frame);

View File

@ -194,17 +194,20 @@ Struct(UI_Report)
////////////////////////////////////////////////////////////
//~ Command types
Struct(UI_Cmd)
Enum(UI_CmdKind)
{
UI_CmdKind_None,
UI_CmdKind_BuildBox,
UI_CmdKind_SetRawTexture,
};
Struct(UI_BoxDesc)
{
UI_BoxFlag flags;
UI_Key key;
UI_Key parent;
GPU_Resource *background_texture;
Vec2 background_texture_uv0;
Vec2 background_texture_uv1;
UI_Size pref_size[Axis_CountXY];
UI_Round rounding;
Vec4 background_color;
@ -220,6 +223,22 @@ Struct(UI_Cmd)
UI_AxisAlignment child_alignment[Axis_CountXY];
};
Struct(UI_Cmd)
{
UI_CmdKind kind;
union
{
UI_BoxDesc box;
struct
{
UI_Key key;
GPU_Resource *texture;
Vec2 uv0;
Vec2 uv1;
} set_raw_texture;
};
};
Struct(UI_CmdNode)
{
UI_CmdNode *next;
@ -248,7 +267,10 @@ Struct(UI_Box)
u64 count;
//- Cmd data
UI_Cmd cmd;
UI_BoxDesc desc;
GPU_Resource *raw_texture;
Vec2 raw_texture_uv0;
Vec2 raw_texture_uv1;
//- Pre-layout data
u64 pre_index;
@ -333,6 +355,7 @@ Struct(UI_State)
UI_Key active_box;
/* Cmds */
Vec4 swapchain_color;
UI_FrameFlag frame_flags;
UI_CmdNode *first_cmd_node;
UI_CmdNode *last_cmd_node;
@ -449,6 +472,8 @@ UI_Style UI_PopStyle(UI_StyleDesc desc);
UI_Key UI_BuildBoxEx(UI_Key key);
#define UI_BuildBox() UI_BuildBoxEx(UI_TransKey())
void UI_SetRawTexture(UI_Key key, GPU_Resource *texture, Vec2 uv0, Vec2 uv1);
////////////////////////////////////////////////////////////
//~ Report
@ -457,7 +482,7 @@ UI_Report UI_ReportFromKey(UI_Key key);
////////////////////////////////////////////////////////////
//~ Begin frame
UI_Frame UI_BeginFrame(UI_FrameFlag frame_flags);
UI_Frame UI_BeginFrame(UI_FrameFlag frame_flags, Vec4 swapchain_color);
////////////////////////////////////////////////////////////
//~ Frame helpers