UI_Ctx UI = Zi; //////////////////////////////////////////////////////////// //~ Key helpers b32 UI_MatchKey(UI_Key a, UI_Key b) { return a.v == b.v; } b32 UI_IsKeyNil(UI_Key key) { return key.v == 0; } UI_Key UI_KeyFromString(String str) { u64 top_tag = UI_Top(Tag); UI_Key key = Zi; key.v = HashStringEx(top_tag, str); return key; } UI_Key UI_KeyF_(String fmt, ...) { UI_Key key = Zi; TempArena scratch = BeginScratchNoConflict(); { va_list args; va_start(args, fmt); String name = FormatString(scratch.arena, fmt, FmtArgsFromVaList(scratch.arena, args)); key = UI_KeyFromString(name); va_end(args); } EndScratch(scratch); return key; } UI_Key UI_RandKey(void) { UI_Key key = Zi; key.v = RandU64FromState(&UI.rand); return key; } UI_Box *UI_BoxFromKey(UI_Key key) { UI_Box *box = 0; if (key.v != 0) { UI_BoxBin *bin = &UI.box_bins[key.v % countof(UI.box_bins)]; for (box = bin->first; box; box = box->next_in_bin) { if (UI_MatchKey(box->key, key)) { break; } } } return box; } //////////////////////////////////////////////////////////// //~ Anchor helpers UI_Region UI_RegionFromPair(UI_RegionPair pair) { UI_Region result = 0; switch(pair.y) { case UI_AxisRegion_Start: { switch(pair.x) { case UI_AxisRegion_Start: { result = UI_Region_TopLeft; } break; case UI_AxisRegion_Center: { result = UI_Region_Top; } break; case UI_AxisRegion_End: { result = UI_Region_TopRight; } break; } } break; case UI_AxisRegion_Center: { switch(pair.x) { case UI_AxisRegion_Start: { result = UI_Region_Left; } break; case UI_AxisRegion_Center: { result = UI_Region_Center; } break; case UI_AxisRegion_End: { result = UI_Region_Right; } break; } } break; case UI_AxisRegion_End: { switch(pair.x) { case UI_AxisRegion_Start: { result = UI_Region_BottomLeft; } break; case UI_AxisRegion_Center: { result = UI_Region_Bottom; } break; case UI_AxisRegion_End: { result = UI_Region_BottomRight; } break; } } break; } return result; } UI_RegionPair UI_PairFromRegion(UI_Region region) { UI_RegionPair result = Zi; // Horizontal switch(region) { default: break; case UI_Region_TopLeft: case UI_Region_Left: case UI_Region_BottomLeft: { result.x = UI_AxisRegion_Start; } break; case UI_Region_Top: case UI_Region_Center: case UI_Region_Bottom: { result.x = UI_AxisRegion_Center; } break; case UI_Region_TopRight: case UI_Region_Right: case UI_Region_BottomRight: { result.x = UI_AxisRegion_End; } break; } // Vertical switch(region) { default: break; case UI_Region_TopLeft: case UI_Region_Top: case UI_Region_TopRight: { result.y = UI_AxisRegion_Start; } break; case UI_Region_Left: case UI_Region_Center: case UI_Region_Right: { result.y = UI_AxisRegion_Center; } break; case UI_Region_BottomLeft: case UI_Region_Bottom: case UI_Region_BottomRight: { result.y = UI_AxisRegion_End; } break; } return result; } //////////////////////////////////////////////////////////// //~ Iteration helpers UI_BoxIterResult UI_FirstBox(Arena *arena, UI_BoxIter *iter, UI_Key start_key) { // Free old stack if (iter->first) { if (iter->last_free) { iter->last_free->next = iter->first; } else { iter->first_free = iter->first; } iter->last_free = iter->first; iter->first = 0; } // Create root dfs node UI_BoxIterDfsNode *dfs = iter->first_free; if (dfs) { SllQueuePop(iter->first_free, iter->last_free); ZeroStruct(dfs); } else { dfs = PushStruct(arena, UI_BoxIterDfsNode); } dfs->box = UI_BoxFromKey(start_key); iter->first = dfs; return UI_NextBox(arena, iter); } UI_BoxIterResult UI_NextBox(Arena *arena, UI_BoxIter *iter) { UI_BoxIterResult result = Zi; if (iter->first) { UI_BoxIterDfsNode *dfs = iter->first; UI_Box *box = dfs->box; if (!dfs->visited) { // Pre order dfs->visited = 1; result.box = box; result.pre = 1; // Push floating children to dfs stack for (UI_Box *child = box->last; child; child = child->prev) { if (AnyBit(child->desc.flags, UI_BoxFlag_Floating)) { UI_BoxIterDfsNode *child_dfs = iter->first_free; if (child_dfs) { SllQueuePop(iter->first_free, iter->last_free); ZeroStruct(child_dfs); } else { child_dfs = PushStruct(arena, UI_BoxIterDfsNode); } child_dfs->box = child; SllStackPush(iter->first, child_dfs); } } // Push non-floating children to dfs stack for (UI_Box *child = box->last; child; child = child->prev) { if (!AnyBit(child->desc.flags, UI_BoxFlag_Floating)) { UI_BoxIterDfsNode *child_dfs = iter->first_free; if (child_dfs) { SllQueuePop(iter->first_free, iter->last_free); ZeroStruct(child_dfs); } else { child_dfs = PushStruct(arena, UI_BoxIterDfsNode); } child_dfs->box = child; SllStackPush(iter->first, child_dfs); } } } else { // Post order result.box = box; result.pre = 0; SllStackPop(iter->first); SllQueuePush(iter->first_free, iter->last_free, dfs); } } return result; } //////////////////////////////////////////////////////////// //~ String helpers String UI_StringF_(String fmt, ...) { UI_Frame *frame = UI_CurrentFrame(); String result = Zi; TempArena scratch = BeginScratchNoConflict(); { va_list args; va_start(args, fmt); String str = FormatString(frame->arena, fmt, FmtArgsFromVaList(scratch.arena, args)); va_end(args); } EndScratch(scratch); return result; } //////////////////////////////////////////////////////////// //~ Checkpoint helpers UI_Checkpoint UI_PushCP(UI_Key parent) { UI_Frame *frame = UI_CurrentFrame(); UI_Stack *stack = frame->top_stack; stack->top_checkpoint.v += 1; if (parent.v != 0) { UI_Push(Parent, parent); } return stack->top_checkpoint; } void UI_PopCP(UI_Checkpoint cp) { UI_Frame *frame = UI_CurrentFrame(); UI_Stack *stack = frame->top_stack; for (UI_StyleKind kind = UI_StyleKind_None; kind < UI_StyleKind_COUNT; ++kind) { UI_StyleNode *n = stack->style_tops[kind]; while (n && n->checkpoint.v >= cp.v) { UI_StyleNode *next = n->next; n->next = frame->first_free_style_node; frame->first_free_style_node = n; stack->style_tops[kind] = next; n = next; } } stack->top_checkpoint.v = cp.v - 1; } UI_Checkpoint UI_TopCP(void) { UI_Frame *frame = UI_CurrentFrame(); UI_Stack *stack = frame->top_stack; return stack->top_checkpoint; } //////////////////////////////////////////////////////////// //~ Style stack helpers void UI_PushDefaults(void) { UI_Frame *frame = UI_CurrentFrame(); UI_Stack *stack = frame->top_stack; UI_Checkpoint checkpoint = stack->top_checkpoint; { for (UI_StyleKind kind = UI_StyleKind_None; kind < UI_StyleKind_COUNT; ++kind) { UI_StyleDesc desc = Zi; desc.style.kind = kind; switch (kind) { default: break; case UI_StyleKind_Parent: { desc.style.Parent = UI_RootKey; } break; case UI_StyleKind_Width: { desc.style.Width = UI_GROW(1, 0); } break; case UI_StyleKind_Height: { desc.style.Height = UI_GROW(1, 0); } break; case UI_StyleKind_Scale: { desc.style.Scale = VEC2(1, 1); } break; case UI_StyleKind_Font: { desc.style.Font = UI_BuiltinTextFont(); } break; case UI_StyleKind_FontSize: { desc.style.FontSize = 16.0f; } break; case UI_StyleKind_Tint: { desc.style.Tint = Color_White; } break; case UI_StyleKind_TextColor: { desc.style.TextColor = Color_White; } break; case UI_StyleKind_Tag: { desc.style.Tag = HashString(Lit("root")); } break; case UI_StyleKind_DebugColor: { desc.style.DebugColor = Rgba(1, 0, 1, 0.5); } break; case UI_StyleKind_InvisibleDebugColor: { desc.style.InvisibleDebugColor = Rgba(0, 1, 1, 0.25); } break; case UI_StyleKind_BackgroundTextureSliceUv: { desc.style.BackgroundTextureSliceUv = RNG2(VEC2(0, 0), VEC2(1, 1)); } break; }; UI_PushStyle(desc); } } } void UI_PushStyle(UI_StyleDesc desc) { UI_Frame *frame = UI_CurrentFrame(); UI_Stack *stack = frame->top_stack; UI_StyleKind kind = desc.style.kind; if (kind >= UI_StyleKind_BeginVirtualStyles_) { switch(kind) { default: break; case UI_StyleKind_AxisSize: { if (desc.axis == Axis_X) { UI_PushCopy(Width, desc, desc.style.AxisSize); } else { UI_PushCopy(Height, desc, desc.style.AxisSize); } } break; } } else { UI_StyleNode *n = stack->style_tops[kind]; if (!(n && n->override)) { { if (n && n->pop_when_used) { UI_StyleNode *next = n->next; ZeroStruct(n); n->next = next; } else { n = frame->first_free_style_node; if (n) { frame->first_free_style_node = n->next; ZeroStruct(n); } else { n = PushStruct(frame->arena, UI_StyleNode); } n->next = stack->style_tops[kind]; stack->style_tops[kind] = n; } n->style = desc.style; n->pop_when_used = desc.pop_when_used; n->override = desc.override; n->checkpoint = stack->top_checkpoint; } // Initialize style data from desc switch (kind) { default: break; case UI_StyleKind_Tag: { if (n->next != 0) { n->style.Tag = MixU64s(n->next->style.Tag, n->style.Tag); } } break; case UI_StyleKind_Text: { n->style.Text = PushString(frame->arena, n->style.Text); } break; } } } } UI_Style UI_PopStyle(UI_StyleDesc desc) { UI_Frame *frame = UI_CurrentFrame(); UI_Stack *stack = frame->top_stack; UI_Style result = Zi; UI_StyleKind kind = desc.style.kind; result.kind = kind; if (kind >= UI_StyleKind_BeginVirtualStyles_) { switch(kind) { default: break; case UI_StyleKind_AxisSize: { if (desc.axis == Axis_X) { desc.style.kind = UI_StyleKind_Width; result = UI_PopStyle(desc); } else { desc.style.kind = UI_StyleKind_Height; result = UI_PopStyle(desc); } }; } } else { UI_StyleNode *n = stack->style_tops[kind]; result = n->style; if (desc.force_pop || (desc.use && n->pop_when_used)) { stack->style_tops[kind] = n->next; n->next = frame->first_free_style_node; frame->first_free_style_node = n; } } return result; } //////////////////////////////////////////////////////////// //~ Command helpers UI_Key UI_BuildBoxEx(UI_Key semantic_key) { UI_Key key = semantic_key; b32 is_transient = 0; if (UI_IsKeyNil(key)) { key = UI_RandKey(); is_transient = 1; } UI_Frame *frame = UI_CurrentFrame(); UI_CmdNode *n = PushStruct(frame->arena, UI_CmdNode); n->cmd.kind = UI_CmdKind_BuildBox; { n->cmd.box.key = key; n->cmd.box.is_transient = is_transient; n->cmd.box.parent = UI_Top(Parent); n->cmd.box.flags = (UI_Top(Flags) | UI_Top(OrFlags)) & ~UI_Top(OmitFlags); n->cmd.box.pref_semantic_dims[Axis_X] = UI_Top(Width); n->cmd.box.pref_semantic_dims[Axis_Y] = UI_Top(Height); n->cmd.box.scale = UI_Top(Scale); n->cmd.box.child_alignment = UI_Top(ChildAlignment); n->cmd.box.child_layout_axis = UI_Top(ChildLayoutAxis); n->cmd.box.background_color = UI_Top(BackgroundColor); n->cmd.box.border_color = UI_Top(BorderColor); n->cmd.box.debug_color = UI_Top(DebugColor); n->cmd.box.invisible_debug_color = UI_Top(InvisibleDebugColor); n->cmd.box.tint = UI_Top(Tint); n->cmd.box.border_size = UI_Top(BorderSize); n->cmd.box.font = UI_Top(Font); n->cmd.box.font_size = UI_Top(FontSize); n->cmd.box.rounding = UI_Top(Rounding); n->cmd.box.text_color = UI_Top(TextColor); n->cmd.box.text = UI_Top(Text); n->cmd.box.icon = UI_Top(Icon); n->cmd.box.anchor = UI_Top(Anchor); n->cmd.box.floating_pos = UI_Top(FloatingPos); n->cmd.box.sprite_sheet = UI_Top(SpriteSheet); n->cmd.box.sprite_span = UI_Top(SpriteSpan); n->cmd.box.sprite_seq = UI_Top(SpriteSeq); n->cmd.box.misc = UI_Top(Misc); } ++frame->cmds_count; SllQueuePush(frame->first_cmd_node, frame->last_cmd_node, n); return key; } void UI_SetRawTexture(UI_Key key, G_TextureRef tex, Rng2 uv) { UI_Frame *frame = UI_CurrentFrame(); UI_CmdNode *n = PushStruct(frame->arena, UI_CmdNode); n->cmd.kind = UI_CmdKind_SetRawTexture; { n->cmd.set_raw_texture.key = key; n->cmd.set_raw_texture.tex = tex; n->cmd.set_raw_texture.slice_uv = uv; } ++frame->cmds_count; SllQueuePush(frame->first_cmd_node, frame->last_cmd_node, n); } //////////////////////////////////////////////////////////// //~ Report UI_BoxReports UI_ReportsFromKey(UI_Key key) { UI_BoxReports result = Zi; UI_Box *box = UI_BoxFromKey(key); if (box) { result = box->reports; } return result; } //////////////////////////////////////////////////////////// //~ Begin frame UI_Frame *UI_BeginFrame(UI_FrameFlag frame_flags) { ////////////////////////////// //- Init persistent state if (!UI.box_arena) { UI.box_arena = AcquireArena(Gibi(64)); UI.gpu_frame_arena = G_AcquireArena(); // Init frames for (u64 i = 0; i < countof(UI.frames); ++i) { UI_Frame *frame = &UI.frames[i]; frame->arena = AcquireArena(Gibi(64)); frame->rects_arena = AcquireArena(Gibi(64)); } // Init root box { UI_Box *box = PushStruct(UI.box_arena, UI_Box); box->key = UI_RootKey; box->gen = 1; UI.boxes_count += 1; UI_BoxBin *bin = &UI.box_bins[box->key.v % countof(UI.box_bins)]; bin->first = box; bin->last = box; UI.root_box = box; } } ////////////////////////////// //- Begin frame UI.cur_frame_tick += 1; UI_Frame *prev_frame = UI_PrevFrame(); UI_Frame *frame = UI_CurrentFrame(); { Arena *old_arena = frame->arena; Arena *old_rects_arena = frame->rects_arena; ZeroStruct(frame); frame->arena = old_arena; frame->rects_arena = old_rects_arena; } frame->window_frame = WND_BeginFrame(G_Format_R16G16B16A16_Float, WND_BackbufferSizeMode_MatchMonitor); UI.cl = G_PrepareCommandList(G_QueueKind_Direct); ResetArena(frame->arena); ResetArena(frame->rects_arena); G_ResetArena(UI.cl, UI.gpu_frame_arena); { i64 now_ns = TimeNs(); i64 dt_ns = now_ns - prev_frame->time_ns; frame->time_ns = now_ns; frame->dt_ns = dt_ns; frame->dt = SecondsFromNs(frame->dt_ns); frame->tick = UI.cur_frame_tick; } // Init style stack { frame->top_stack = PushStruct(frame->arena, UI_Stack); UI_PushDefaults(); } frame->frame_flags = frame_flags; ////////////////////////////// //- Process controller events frame->cursor_pos = prev_frame->cursor_pos; frame->drag_cursor_pos = prev_frame->drag_cursor_pos; if (prev_frame->boxes_pre != 0) { ControllerEventsArray controller_events = frame->window_frame.controller_events; //- Locate boxes UI_Box *top_hovered_box = 0; UI_Box *active_box = UI_BoxFromKey(prev_frame->active_box); //- Update cursor pos for (u64 cev_index = 0; cev_index < controller_events.count; ++cev_index) { ControllerEvent cev = controller_events.events[cev_index]; if (cev.kind == ControllerEventKind_CursorMove) { frame->cursor_pos = Vec2FromVec(cev.cursor_pos); } } //- Init box reports for (u64 pre_index = UI.boxes_count; pre_index-- > 0;) { UI_Box *box = prev_frame->boxes_pre[pre_index]; UI_BoxReport *report = &box->reports.draw; b32 is_cursor_in_box = 0; { // TODO: More efficient test. This logic is just copied from the renderer's SDF function for now. Vec2 p0 = box->screen_rect.p0; Vec2 p1 = box->screen_rect.p1; Vec2 point = frame->cursor_pos; b32 is_corner = 0; f32 non_corner_edge_dist = MinF32(MinF32(point.x - p0.x, p1.x - point.x), MinF32(point.y - p0.y, p1.y - point.y)); f32 corner_edge_dist = non_corner_edge_dist; if (non_corner_edge_dist >= 0) { f32 tl_radius = box->rounding_tl; f32 tr_radius = box->rounding_tr; f32 br_radius = box->rounding_br; f32 bl_radius = box->rounding_bl; Vec2 tl = VEC2(p0.x + tl_radius, p0.y + tl_radius); Vec2 tr = VEC2(p1.x - tr_radius, p0.y + tr_radius); Vec2 br = VEC2(p1.x - br_radius, p1.y - br_radius); Vec2 bl = VEC2(p0.x + bl_radius, p1.y - bl_radius); if (point.x < tl.x && point.y < tl.y) corner_edge_dist = MinF32(corner_edge_dist, tl_radius - Vec2Len(SubVec2(tl, point))); if (point.x > tr.x && point.y < tr.y) corner_edge_dist = MinF32(corner_edge_dist, tr_radius - Vec2Len(SubVec2(tr, point))); if (point.x > br.x && point.y > br.y) corner_edge_dist = MinF32(corner_edge_dist, br_radius - Vec2Len(SubVec2(br, point))); if (point.x < bl.x && point.y > bl.y) corner_edge_dist = MinF32(corner_edge_dist, bl_radius - Vec2Len(SubVec2(bl, point))); } is_cursor_in_box = non_corner_edge_dist >= 0 && corner_edge_dist >= 0; } report->is_hovered = is_cursor_in_box; if (top_hovered_box == 0 && box->desc.flags & UI_BoxFlag_CaptureMouse && is_cursor_in_box) { top_hovered_box = box; } report->m1.ups = 0; report->m1.downs = 0; report->m1.presses = 0; report->m2.ups = 0; report->m2.downs = 0; report->m2.presses = 0; report->m3.ups = 0; report->m3.downs = 0; report->m3.presses = 0; } //- Update state from controller events i32 mouse_downs = 0; for (u64 cev_index = 0; cev_index < controller_events.count; ++cev_index) { ControllerEvent cev = controller_events.events[cev_index]; switch (cev.kind) { default: break; case ControllerEventKind_ButtonDown: { if (cev.button == Button_M1 || cev.button == Button_M2 || cev.button == Button_M3) { mouse_downs += 1; if (top_hovered_box && active_box == 0) { if (cev.button == Button_M1) { ++top_hovered_box->reports.draw.m1.downs; top_hovered_box->reports.draw.m1.held = 1; } else if (cev.button == Button_M2) { ++top_hovered_box->reports.draw.m2.downs; top_hovered_box->reports.draw.m2.held = 1; } else if (cev.button == Button_M3) { ++top_hovered_box->reports.draw.m3.downs; top_hovered_box->reports.draw.m3.held = 1; } active_box = top_hovered_box; } } } break; case ControllerEventKind_ButtonUp: { if (cev.button == Button_M1 || cev.button == Button_M2 || cev.button == Button_M3) { if (active_box) { if (cev.button == Button_M1) { if (active_box == top_hovered_box) { ++active_box->reports.draw.m1.presses; } ++active_box->reports.draw.m1.ups; if (active_box->reports.draw.m1.held) { active_box->reports.draw.m1.held = 0; active_box = 0; } } else if (cev.button == Button_M2) { if (active_box == top_hovered_box) { ++active_box->reports.draw.m2.presses; } ++active_box->reports.draw.m2.ups; if (active_box->reports.draw.m2.held) { active_box->reports.draw.m2.held = 0; active_box = 0; } } else if (cev.button == Button_M3) { if (active_box == top_hovered_box) { ++active_box->reports.draw.m3.presses; } ++active_box->reports.draw.m3.ups; if (active_box->reports.draw.m3.held) { active_box->reports.draw.m3.held = 0; active_box = 0; } } } } } break; case ControllerEventKind_Quit: { SignalExit(0); } break; } } UI_Box *hot_box = active_box; if (top_hovered_box && !active_box) { hot_box = top_hovered_box; } //- Update box reports { f32 lower_target = TweakFloat("UI lower blend target", -0.05, -1, 0); f32 upper_target = TweakFloat("UI upper blend target", 1.05, 1, 10); for (u64 pre_index = 0; pre_index < UI.boxes_count; ++pre_index) { UI_Box *box = prev_frame->boxes_pre[pre_index]; UI_BoxReport *report = &box->reports.draw; report->is_hot = box == hot_box; f32 target_exists = (box->last_build_tick >= (frame->tick - 1)) ? upper_target : lower_target; f32 target_hovered = report->is_hovered ? Inf : lower_target; f32 target_hot = report->is_hot ? Inf : lower_target; f32 target_active = box == active_box ? Inf : lower_target; f64 target_misc = box->desc.misc; // TODO: Configurable per-box blend rates f32 exists_blend_rate = (30 * frame->dt); f32 hot_blend_rate = (15 * frame->dt); f32 active_blend_rate = (15 * frame->dt); f32 hovered_blend_rate = (15 * frame->dt); // f64 misc_blend_rate = (30 * frame->dt); f64 misc_blend_rate = 1; report->exists = SaturateF32(LerpF32(report->exists, target_exists, exists_blend_rate)); report->hot = SaturateF32(LerpF32(report->hot, target_hot, hot_blend_rate)); report->active = SaturateF32(LerpF32(report->active, target_active, active_blend_rate)); report->hovered = SaturateF32(LerpF32(report->hovered, target_hovered, hovered_blend_rate)); report->misc = SaturateF32(LerpF32(report->misc, target_misc, misc_blend_rate)); report->screen_rect = box->screen_rect; report->screen_anchor = box->screen_anchor; if (mouse_downs > 0) { box->reports.drag = *report; } } } if (mouse_downs > 0) { frame->drag_cursor_pos = frame->cursor_pos; } frame->top_hovered_box = top_hovered_box ? top_hovered_box->key : UI_NilKey; frame->hot_box = hot_box ? hot_box->key : UI_NilKey; frame->active_box = active_box ? active_box->key : UI_NilKey; } ////////////////////////////// //- Build root box { UI_SetNext(Width, UI_PIX(frame->window_frame.draw_size.x, 1)); UI_SetNext(Height, UI_PIX(frame->window_frame.draw_size.y, 1)); UI_SetNext(Parent, UI_NilKey); UI_BuildBoxEx(UI_RootKey); } return frame; } //////////////////////////////////////////////////////////// //~ Frame helpers UI_Frame *UI_CurrentFrame(void) { return &UI.frames[UI.cur_frame_tick % countof(UI.frames)]; }; UI_Frame *UI_PrevFrame(void) { return &UI.frames[(UI.cur_frame_tick - 1) % countof(UI.frames)]; }; Arena *UI_FrameArena(void) { return UI_CurrentFrame()->arena; } Vec2 UI_CursorPos(void) { return UI_CurrentFrame()->cursor_pos; } //////////////////////////////////////////////////////////// //~ Layout helpers GC_Run UI_ScaleRun(Arena *arena, GC_Run unscaled_run, Vec2 scale) { GC_Run result = Zi; result = unscaled_run; result.font_size *= scale.y; result.font_ascent *= scale.y; result.font_descent *= scale.y; result.font_cap *= scale.y; result.baseline_length *= scale.x; result.rects_count = unscaled_run.rects_count; result.rects = PushStructsNoZero(arena, GC_RunRect, result.rects_count); for (u64 rect_idx = 0; rect_idx < result.rects_count; ++rect_idx) { GC_RunRect *src = &unscaled_run.rects[rect_idx]; GC_RunRect *dst = &result.rects[rect_idx]; *dst = *src; dst->bounds = MulRng2Vec2(dst->bounds, scale); dst->advance *= scale.x; dst->baseline_pos *= scale.x; } return result; } //////////////////////////////////////////////////////////// //~ End frame void UI_EndFrame(UI_Frame *frame, i32 vsync) { TempArena scratch = BeginScratchNoConflict(); UI_BoxIter box_iter = Zi; Vec2I32 monitor_size = frame->window_frame.monitor_size; Rng3 monitor_viewport = RNG3(VEC3(0, 0, 0), VEC3(monitor_size.x, monitor_size.y, 1)); Rng2 monitor_scissor = RNG2(VEC2(0, 0), VEC2(monitor_size.x, monitor_size.y)); Vec2I32 draw_size = frame->window_frame.draw_size; Rng3 draw_viewport = RNG3(VEC3(0, 0, 0), VEC3(draw_size.x, draw_size.y, 1)); Rng2 draw_scissor = RNG2(VEC2(0, 0), VEC2(draw_size.x, draw_size.y)); ////////////////////////////// //- Create boxes from build cmds for (UI_CmdNode *cmd_node = frame->first_cmd_node; cmd_node; cmd_node = cmd_node->next) { UI_Cmd cmd = cmd_node->cmd; if (cmd.kind == UI_CmdKind_BuildBox) { UI_Key key = cmd.box.key; UI_Box *box = 0; { UI_BoxBin *bin = &UI.box_bins[key.v % countof(UI.box_bins)]; for (box = bin->first; box; box = box->next_in_bin) { if (UI_MatchKey(box->key, key)) { break; } } // Allocate new box if (box == 0) { // Allocate new box box = UI.first_free_box; i64 old_gen = 0; if (box) { old_gen = box->gen; SllStackPop(UI.first_free_box); ZeroStruct(box); } else { box = PushStruct(UI.box_arena, UI_Box); } box->key = key; box->old_gen = old_gen; box->gen = old_gen + 1; DllQueuePushNP(bin->first, bin->last, box, next_in_bin, prev_in_bin); ++UI.boxes_count; } } } } ////////////////////////////// //- Update boxes from cmds for (UI_CmdNode *cmd_node = frame->first_cmd_node; cmd_node; cmd_node = cmd_node->next) { UI_Cmd cmd = cmd_node->cmd; switch (cmd.kind) { case UI_CmdKind_BuildBox: { UI_Key key = cmd.box.key; if (UI_MatchKey(key, UI_NilKey)) { key = UI_RandKey(); } UI_Box *box = UI_BoxFromKey(key); UI_Box *parent = 0; if (box != UI.root_box) { parent = UI_BoxFromKey(cmd.box.parent); } // Update parent if (box->parent) { // Remove from old parent DllQueueRemove(box->parent->first, box->parent->last, box); --box->parent->count; } if (parent) { // Add to new parent DllQueuePush(parent->first, parent->last, box); ++parent->count; } box->parent = parent; // Update box from cmd { box->desc = cmd.box; String32 codepoints = Zi; if (box->desc.icon != UI_Icon_None) { codepoints.len = 1; codepoints.text = (u32 *)&box->desc.icon; } else { codepoints = String32FromString(scratch.arena, box->desc.text); } if (AnyBit(box->desc.flags, UI_BoxFlag_DrawText) && codepoints.len > 0) { box->glyph_run = GC_RunFromString32(frame->arena, codepoints, box->desc.font, box->desc.font_size); } if (!SPR_IsSheetKeyNil(box->desc.sprite_sheet)) { box->sprite = SPR_SpriteFromSheet(box->desc.sprite_sheet, box->desc.sprite_span, box->desc.sprite_seq); } } box->last_build_tick = frame->tick; } break; case UI_CmdKind_SetRawTexture: { UI_Key key = cmd.set_raw_texture.key; UI_Box *box = UI_BoxFromKey(key); if (box) { box->raw_texture = cmd.set_raw_texture.tex; box->raw_texture_slice_uv = cmd.set_raw_texture.slice_uv; } } break; } } ////////////////////////////// //- Prune cached boxes { u64 prunes_count = 0; UI_Box **prunes = PushStructsNoZero(scratch.arena, UI_Box *, UI.boxes_count); for (UI_BoxIterResult ir = UI_FirstBox(scratch.arena, &box_iter, UI_RootKey); ir.box; ir = UI_NextBox(scratch.arena, &box_iter)) { if (ir.pre) { UI_Box *box = ir.box; if (box->last_build_tick < frame->tick) { // Cause children to prune for (UI_Box *child = box->first; child; child = child->next) { child->last_build_tick = box->last_build_tick; } // Push box to prunes array prunes[prunes_count++] = box; } } } for (u64 prune_idx = 0; prune_idx < prunes_count; ++prune_idx) { UI_Box *box = prunes[prune_idx]; UI_Box *parent = box->parent; UI_BoxBin *bin = &UI.box_bins[box->key.v % countof(UI.box_bins)]; // Re-parent children if (box->first) { for (UI_Box *child = box->first; child; child = child->next) { child->parent = parent; } if (parent->last) { parent->last->next = box->first; box->first->prev = parent->last; } else { parent->first = box->first; } parent->last = box->last; parent->count += box->count; } // Remove from parent DllQueueRemove(parent->first, parent->last, box); --parent->count; // Remove from lookup table DllQueueRemoveNP(bin->first, bin->last, box, next_in_bin, prev_in_bin); // Add to free list SllStackPush(UI.first_free_box, box); --UI.boxes_count; } } ////////////////////////////// //- Layout // Prepare layout data u64 boxes_count = UI.boxes_count; UI_Box **boxes_pre = PushStructsNoZero(frame->arena, UI_Box *, boxes_count); UI_Box **boxes_post = PushStructsNoZero(frame->arena, UI_Box *, boxes_count); frame->boxes_pre = boxes_pre; frame->boxes_post = boxes_post; { u64 pre_index = 0; u64 post_index = 0; for (UI_BoxIterResult ir = UI_FirstBox(scratch.arena, &box_iter, UI_RootKey); ir.box; ir = UI_NextBox(scratch.arena, &box_iter)) { UI_Box *box = ir.box; if (ir.pre) { box->pre_index = pre_index; boxes_pre[pre_index] = box; pre_index += 1; // Reset layout data box->cursor = 0; box->final_children_size_accum = VEC2(0, 0); box->solved_dims = VEC2(0, 0); // Solve scale { UI_Box *parent = box->parent; box->solved_scale = box->desc.scale; if (parent) { box->solved_scale = MulVec2Vec2(parent->solved_scale, box->solved_scale); } } } else { box->post_index = post_index; boxes_post[post_index] = box; post_index += 1; } } Assert(pre_index == boxes_count); Assert(post_index == boxes_count); } // Solve independent sizes for (u64 pre_index = 0; pre_index < boxes_count; ++pre_index) { UI_Box *box = boxes_pre[pre_index]; for (Axis axis = 0; axis < Axis_COUNTXY; ++axis) { UI_Size size = box->desc.pref_semantic_dims[axis]; if (size.kind == UI_SizeKind_Pixel) { box->solved_dims.v[axis] = size.v; } else if (size.kind == UI_SizeKind_Shrink) { if (AnyBit(box->desc.flags, UI_BoxFlag_DrawText)) { f32 text_size = 0; if (axis == Axis_X) { text_size = box->glyph_run.baseline_length; } else { text_size = box->glyph_run.font_ascent + box->glyph_run.font_descent; } box->solved_dims.v[axis] = text_size + (size.v * 2); } else if (!SPR_IsSheetKeyNil(box->desc.sprite_sheet)) { box->solved_dims.v[axis] = box->sprite.tex_rect.p1.v[axis] - box->sprite.tex_rect.p0.v[axis] + (size.v * 2); } } } } // Solve upwards-dependent sizes along layout axis for (u64 pre_index = 0; pre_index < boxes_count; ++pre_index) { UI_Box *box = boxes_pre[pre_index]; if (box->parent) { Axis axis = box->parent->desc.child_layout_axis; UI_Size size = box->desc.pref_semantic_dims[axis]; if (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->desc.pref_semantic_dims[axis]; if ( ancestor_size.kind == UI_SizeKind_Pixel || ( ancestor_size.kind == UI_SizeKind_Shrink && ( AnyBit(box->desc.flags, UI_BoxFlag_DrawText) || !SPR_IsSheetKeyNil(box->desc.sprite_sheet) ) ) ) { // Match independent ancestor match_size = ancestor->solved_dims.v[axis]; found_match = 1; } } box->solved_dims.v[axis] = match_size * size.v; } } } // Solve downwards-dependent sizes for (u64 post_index = 0; post_index < boxes_count; ++post_index) { UI_Box *box = boxes_post[post_index]; for (Axis axis = 0; axis < Axis_COUNTXY; ++axis) { UI_Size size = box->desc.pref_semantic_dims[axis]; if (size.kind == UI_SizeKind_Shrink && !(AnyBit(box->desc.flags, UI_BoxFlag_DrawText) || !SPR_IsSheetKeyNil(box->desc.sprite_sheet))) { f32 accum = 0; for (UI_Box *child = box->first; child; child = child->next) { if (!AnyBit(child->desc.flags, UI_BoxFlag_Floating)) { f32 child_size = child->solved_dims.v[axis]; if (axis == box->desc.child_layout_axis) { accum += child_size; } else { accum = MaxF32(child_size, accum); } } } box->solved_dims.v[axis] = CeilF32(accum + (size.v * 2)); } } } // Solve upwards-dependent sizes along non-layout axis for (u64 pre_index = 0; pre_index < boxes_count; ++pre_index) { UI_Box *box = boxes_pre[pre_index]; if (box->parent) { Axis axis = !box->parent->desc.child_layout_axis; UI_Size size = box->desc.pref_semantic_dims[axis]; if (size.kind == UI_SizeKind_Grow) { box->solved_dims.v[axis] = box->parent->solved_dims.v[axis] * size.v; } } } // Solve violations for (u64 pre_index = 0; pre_index < boxes_count; ++pre_index) { UI_Box *box = boxes_pre[pre_index]; for (Axis axis = 0; axis < Axis_COUNTXY; ++axis) { f32 box_size = box->solved_dims.v[axis]; { // Accumulate non-floating sizes f32 unconstrained_size_accum = 0; f32 flex_accum = 0; f32 violation = 0; { for (UI_Box *child = box->first; child; child = child->next) { b32 is_floating = AnyBit(child->desc.flags, UI_BoxFlag_Floating); if (!is_floating) { f32 size = child->solved_dims.v[axis]; f32 strictness = child->desc.pref_semantic_dims[axis].strictness; f32 flex = size * (1.0 - strictness); if (axis == box->desc.child_layout_axis) { unconstrained_size_accum += size; flex_accum += flex; } else { unconstrained_size_accum = MaxF32(unconstrained_size_accum, size); flex_accum = MaxF32(flex_accum, flex); } } } unconstrained_size_accum = FloorF32(unconstrained_size_accum); violation = unconstrained_size_accum - box_size; } { f32 size_accum = 0; for (UI_Box *child = box->first; child; child = child->next) { b32 is_floating = AnyBit(child->desc.flags, UI_BoxFlag_Floating); f32 unconstrained_size = child->solved_dims.v[axis]; f32 strictness = child->desc.pref_semantic_dims[axis].strictness; f32 flex = unconstrained_size * (1.0 - strictness); f32 new_size = unconstrained_size; // Solve non-floating violation if (!is_floating && violation > 0 && flex_accum > 0) { if (axis == box->desc.child_layout_axis) { f32 chopoff = MinF32(flex, violation * (flex / flex_accum)); new_size = new_size - chopoff; } else if (new_size > box_size) { new_size = MaxF32(new_size - flex, box_size); } } // Solve floating violation if (is_floating && new_size > box_size && !AnyBit(child->desc.flags, UI_BoxFlag_NoFloatingClamp)) { new_size = MaxF32(new_size - flex, box_size); } if (!is_floating) { size_accum += new_size; } child->solved_dims.v[axis] = new_size; } box->final_children_size_accum.v[axis] = size_accum; } } } } // Solve final positions for (u64 pre_index = 0; pre_index < boxes_count; ++pre_index) { UI_Box *box = boxes_pre[pre_index]; UI_Box *parent = box->parent; // TODO: Distinguish between baseline alignment & visual alignment UI_RegionPair child_alignment = UI_PairFromRegion(box->desc.child_alignment); UI_RegionPair alignment_in_parent = UI_PairFromRegion(parent ? parent->desc.child_alignment : UI_Region_TopLeft); Axis child_layout_axis = box->desc.child_layout_axis; Axis layout_axis_in_parent = parent ? parent->desc.child_layout_axis : Axis_X; b32 is_floating = AnyBit(box->desc.flags, UI_BoxFlag_Floating); // Apply scale for (Axis axis = 0; axis < Axis_COUNTXY; ++axis) { f32 unscaled_size = box->solved_dims.v[axis]; f32 scaled_size = 0; if (box->solved_scale.v[axis] == 1) { // scaled_size = RoundF32(unscaled_size); scaled_size = unscaled_size; } else { scaled_size = unscaled_size * box->solved_scale.v[axis]; } box->solved_dims.v[axis] = scaled_size; } // Compute anchor offset Vec2 anchor_offset = Zi; { UI_RegionPair anchor_region = UI_PairFromRegion(box->desc.anchor); for (Axis axis = 0; axis < Axis_COUNTXY; ++axis) { UI_AxisRegion anchor = anchor_region.v[axis]; switch (anchor) { default: break; case UI_AxisRegion_Center: { anchor_offset.v[axis] = box->solved_dims.v[axis] * 0.5; } break; case UI_AxisRegion_End: { anchor_offset.v[axis] = box->solved_dims.v[axis]; } break; } } } // Initialize layout cursor based on alignment { Axis axis = child_layout_axis; UI_AxisRegion alignment = child_alignment.v[axis]; f32 box_size = box->solved_dims.v[axis]; f32 size_accum = box->final_children_size_accum.v[axis]; switch(alignment) { default: break; case UI_AxisRegion_Center: { box->cursor = box_size / 2 - size_accum / 2; } break; case UI_AxisRegion_End: { box->cursor = box_size - size_accum; } break; } // box->cursor = FloorF32(box->cursor); } // Solve screen rect { // Compute offset Vec2 offset = Zi; { // Floating box offset if (is_floating) { offset = box->desc.floating_pos; offset = SubVec2(offset, anchor_offset); } // Non-floating box offset else if (parent) { // Compute offset in layout direction (based on parent cursor) offset.v[layout_axis_in_parent] = parent->cursor; // Compute offset in non-layout direction (based on alignment) { Axis axis = !layout_axis_in_parent; UI_AxisRegion alignment = alignment_in_parent.v[axis]; switch(alignment) { default: break; case UI_AxisRegion_Center: { f32 parent_size = parent->solved_dims.v[axis]; f32 box_size = box->solved_dims.v[axis]; offset.v[axis] = parent_size / 2 - box_size / 2; } break; case UI_AxisRegion_End: { f32 parent_size = parent->solved_dims.v[axis]; f32 box_size = box->solved_dims.v[axis]; offset.v[axis] = parent_size - box_size; } break; } } } if (box->solved_scale.x == 1) { offset.x = RoundF32(offset.x); } if (box->solved_scale.y == 1) { offset.y = RoundF32(offset.y); } } // Compute rect Vec2 screen_pos = parent ? AddVec2(parent->screen_rect.p0, offset) : VEC2(0, 0); if (is_floating && !AnyBit(box->desc.flags, UI_BoxFlag_NoFloatingClamp)) { Vec2 overshoot = Zi; overshoot.x = MaxF32(0, (screen_pos.x + box->solved_dims.x) - parent->screen_rect.p1.x); overshoot.y = MaxF32((screen_pos.y + box->solved_dims.y) - parent->screen_rect.p1.y, 0); screen_pos.x = MaxF32(parent->screen_rect.p0.x, screen_pos.x - overshoot.x); screen_pos.y = MaxF32(parent->screen_rect.p0.y, screen_pos.y - overshoot.y); } box->screen_rect.p0 = screen_pos; box->screen_rect.p1 = AddVec2(box->screen_rect.p0, box->solved_dims); box->screen_anchor = AddVec2(box->screen_rect.p0, anchor_offset); // Update parent cursor if (parent && !is_floating) { parent->cursor += box->solved_dims.v[layout_axis_in_parent]; } } // Solve screen rounding { UI_Round rounding = box->desc.rounding; Vec2 half_dims = MulVec2(SubVec2(box->screen_rect.p1, box->screen_rect.p0), 0.5); f32 min_half_dims = MinF32(half_dims.x, half_dims.y); f32 final_rounding_tl = 0; f32 final_rounding_tr = 0; f32 final_rounding_br = 0; f32 final_rounding_bl = 0; switch(rounding.kind) { default: break; case UI_RoundKind_Pixel: { final_rounding_tl = rounding.v; final_rounding_tr = final_rounding_tl; final_rounding_br = final_rounding_tl; final_rounding_bl = final_rounding_tl; } break; case UI_RoundKind_Grow: { final_rounding_tl = rounding.v * min_half_dims; final_rounding_tr = final_rounding_tl; final_rounding_br = final_rounding_tl; final_rounding_bl = final_rounding_tl; } break; } // Clamp rounding based on parent rounding if (parent && !AllBits(box->desc.flags, UI_BoxFlag_Floating | UI_BoxFlag_NoFloatingClamp)) { Vec2 vtl = SubVec2(VEC2(parent->screen_rect.p0.x, parent->screen_rect.p0.y), VEC2(box->screen_rect.p0.x, box->screen_rect.p0.y)); Vec2 vtr = SubVec2(VEC2(parent->screen_rect.p1.x, parent->screen_rect.p0.y), VEC2(box->screen_rect.p1.x, box->screen_rect.p0.y)); Vec2 vbr = SubVec2(VEC2(parent->screen_rect.p1.x, parent->screen_rect.p1.y), VEC2(box->screen_rect.p1.x, box->screen_rect.p1.y)); Vec2 vbl = SubVec2(VEC2(parent->screen_rect.p0.x, parent->screen_rect.p1.y), VEC2(box->screen_rect.p0.x, box->screen_rect.p1.y)); final_rounding_tl = MaxF32(final_rounding_tl, parent->rounding_tl - Vec2Len(vtl)); final_rounding_tr = MaxF32(final_rounding_tr, parent->rounding_tr - Vec2Len(vtr)); final_rounding_br = MaxF32(final_rounding_br, parent->rounding_br - Vec2Len(vbr)); final_rounding_bl = MaxF32(final_rounding_bl, parent->rounding_bl - Vec2Len(vbl)); } box->rounding_tl = final_rounding_tl; box->rounding_tr = final_rounding_tr; box->rounding_br = final_rounding_br; box->rounding_bl = final_rounding_bl; } box->gen = box->old_gen; } ////////////////////////////// //- Render G_TextureRef draw_target = Zi; { ////////////////////////////// //- Build render data // Build rect instance data for (u64 pre_index = 0; pre_index < boxes_count; ++pre_index) { UI_Box *box = boxes_pre[pre_index]; GC_Run raw_run_unscaled = box->glyph_run; GC_Run raw_run = UI_ScaleRun(frame->arena, raw_run_unscaled, box->solved_scale); b32 is_visible = 1; is_visible = is_visible && (box->desc.tint.w >= 0.0025); is_visible = is_visible && (box->screen_rect.p1.x - box->screen_rect.p0.x > 0.0025); is_visible = is_visible && (box->screen_rect.p1.y - box->screen_rect.p0.y > 0.0025); is_visible = is_visible && ( !G_IsRefNil(box->raw_texture) || !SPR_IsSheetKeyNil(box->desc.sprite_sheet) || (AnyBit(box->desc.flags, UI_BoxFlag_DrawText) && raw_run.ready) || box->desc.tint.a * box->desc.background_color.a >= 0.0025 || box->desc.tint.a * box->desc.border_color.a >= 0.0025 ); if (is_visible || AnyBit(frame->frame_flags, UI_FrameFlag_Debug)) { Vec4 debug_lin = is_visible ? LinearFromSrgb(box->desc.debug_color) : LinearFromSrgb(box->desc.invisible_debug_color); Vec4 tint_lin = LinearFromSrgb(box->desc.tint); Vec2 box_dims = DimsFromRng2(box->screen_rect); UI_RegionPair child_alignment = UI_PairFromRegion(box->desc.child_alignment); UI_AxisRegion x_alignment = child_alignment.v[Axis_X]; UI_AxisRegion y_alignment = child_alignment.v[Axis_Y]; // Box rect { UI_GpuRect *rect = PushStruct(frame->rects_arena, UI_GpuRect); rect->bounds = box->screen_rect; rect->background_lin = LinearFromSrgb(box->desc.background_color); rect->border_lin = LinearFromSrgb(box->desc.border_color); rect->debug_lin = debug_lin; rect->tint_lin = tint_lin; rect->border_size = box->desc.border_size; rect->tl_rounding = box->rounding_tl; rect->tr_rounding = box->rounding_tr; rect->br_rounding = box->rounding_br; rect->bl_rounding = box->rounding_bl; if (!G_IsRefNil(box->raw_texture)) { rect->tex = box->raw_texture; rect->tex_slice_uv = box->raw_texture_slice_uv; } else if (!SPR_IsSheetKeyNil(box->desc.sprite_sheet)) { rect->tex = box->sprite.tex; rect->tex_slice_uv = DivRng2Vec2(box->sprite.tex_rect, box->sprite.tex_dims); } } if (AnyBit(box->desc.flags, UI_BoxFlag_DrawText) && raw_run.ready) { f32 max_baseline_length = CeilF32(DimsFromRng2(box->screen_rect).x); b32 should_truncate = FloorF32(raw_run.baseline_length) > max_baseline_length && !AnyBit(box->desc.flags, UI_BoxFlag_NoTextTruncation); // Truncate run u64 final_rects_count = 0; GC_RunRect *final_rects = 0; f32 final_baseline_length = 0; if (should_truncate) { // Get elipses run GC_Run elipses_run_unscaled = GC_RunFromString32(scratch.arena, String32FromString(scratch.arena, Lit("...")), box->desc.font, box->desc.font_size); GC_Run elipses_run = UI_ScaleRun(frame->arena, elipses_run_unscaled, box->solved_scale); f32 truncation_offset = max_baseline_length - elipses_run.baseline_length; // Append non-overflowed rects f32 elipses_offset = 0; final_rects = PushStructsNoZero(scratch.arena, GC_RunRect, raw_run.rects_count); for (u64 rect_idx = 0; rect_idx < raw_run.rects_count; ++rect_idx) { GC_RunRect rr = raw_run.rects[rect_idx]; if (rr.baseline_pos + rr.advance <= truncation_offset) { final_rects[final_rects_count] = rr; final_rects_count += 1; elipses_offset = MaxF32(elipses_offset, rr.baseline_pos + rr.advance); final_baseline_length = MaxF32(final_baseline_length, rr.baseline_pos + rr.advance); } } // Append elipses for (u64 rect_idx = 0; rect_idx < elipses_run.rects_count; ++rect_idx) { GC_RunRect rr = elipses_run.rects[rect_idx]; rr.baseline_pos += elipses_offset; if (rr.baseline_pos + rr.advance <= max_baseline_length) { final_rects[final_rects_count] = rr; final_rects_count += 1; final_baseline_length = MaxF32(final_baseline_length, rr.baseline_pos + rr.advance); } } } else { final_rects = raw_run.rects; final_rects_count = raw_run.rects_count; final_baseline_length = raw_run.baseline_length; } // Compute baseline f32 ascent = raw_run.font_ascent; f32 font_descent = raw_run.font_descent; f32 cap = raw_run.font_cap; f32 baseline_height = ascent + font_descent; Vec2 baseline = Zi; switch (x_alignment) { case UI_AxisRegion_Start: { baseline.x = box->screen_rect.p0.x; } break; case UI_AxisRegion_End: { baseline.x = box->screen_rect.p1.x; baseline.x -= final_baseline_length; } break; case UI_AxisRegion_Center: { baseline.x = box->screen_rect.p0.x; baseline.x += (box_dims.x - final_baseline_length) / 2; } break; } switch (y_alignment) { case UI_AxisRegion_Start: { baseline.y = box->screen_rect.p0.y; baseline.y += ascent; } break; case UI_AxisRegion_End: { baseline.y = box->screen_rect.p1.y; baseline.y -= font_descent; } break; case UI_AxisRegion_Center: { baseline.y = box->screen_rect.p0.y; baseline.y += box_dims.y / 2; baseline.y += cap / 2; } break; } baseline = CeilVec2(baseline); // Push text rects Vec4 text_color_lin = LinearFromSrgb(box->desc.text_color); text_color_lin = MulVec4Vec4(text_color_lin, tint_lin); for (u64 rect_idx = 0; rect_idx < final_rects_count; ++rect_idx) { GC_RunRect rr = final_rects[rect_idx]; Vec2 glyph_dims = DimsFromRng2(rr.bounds); if (glyph_dims.x != 0 || glyph_dims.y != 0) { UI_GpuRect *rect = PushStruct(frame->rects_arena, UI_GpuRect); rect->debug_lin = debug_lin; rect->tint_lin = text_color_lin; rect->tex = rr.tex; rect->tex_slice_uv = rr.tex_slice_uv; rect->bounds = rr.bounds; rect->bounds = AddRng2Vec2(rect->bounds, baseline); rect->bounds = AddRng2Vec2(rect->bounds, VEC2(rr.baseline_pos, 0)); } } } } } ////////////////////////////// //- Upload data to GPU UI_GpuParams params = Zi; // Target draw_target = G_PushTexture2D( UI.cl, UI.gpu_frame_arena, G_TextureLayout_Family, G_Format_R16G16B16A16_Float, monitor_size, .flags = G_MemoryFlag_AllowTextureDraw, .name = Lit("UI draw target") ); // Rects UI_GpuRect *rects = ArenaFirst(frame->rects_arena, UI_GpuRect); u64 rects_count = ArenaCount(frame->rects_arena, UI_GpuRect); params.rects = G_PushStructsFromCpu( UI.cl, UI.gpu_frame_arena, rects, rects_count, .name = Lit("UI rects") ); // Params params.target = draw_target; params.target_size = draw_size; params.sampler = G_BasicSamplerFromKind(G_BasicSamplerKind_PointClamp); params.cursor_pos = frame->cursor_pos; params.anti_aliasing = TweakFloat("UI anti-aliasing", 1, 0, 1); G_BufferRef params_buff = G_PushStructFromCpu( UI.cl, UI.gpu_frame_arena, ¶ms, .name = Lit("UI gpu params") ); // Init constants G_SetConstant(UI.cl, UI_GpuConst_Params, params_buff); // Sync G_Sync(UI.cl); ////////////////////////////// //- Dispatch shaders //- Clear pass G_ZoneDF(UI.cl, "UI clear") { G_ClearRenderTarget(UI.cl, draw_target, VEC4(0, 0, 0, 0), 0); } //- Rect pass // Render rects G_ZoneDF(UI.cl, "UI rects") { G_Draw( UI.cl, UI_DRectVS, UI_DRectPS, rects_count, G_QuadIndices(), 1, &G_RT(draw_target, G_BlendMode_CompositePremultipliedAlpha), draw_viewport, draw_scissor, G_DrawMode_TriangleList ); } // Render rect wireframes if (AnyBit(frame->frame_flags, UI_FrameFlag_Debug)) { G_ZoneDF(UI.cl, "UI debug rects") { G_SetConstant(UI.cl, UI_GpuConst_DebugDraw, 1); G_Draw( UI.cl, UI_DRectVS, UI_DRectPS, rects_count, G_QuadIndices(), 1, &G_RT(draw_target, G_BlendMode_CompositePremultipliedAlpha), draw_viewport, draw_scissor, G_DrawMode_WireTriangleList ); G_SetConstant(UI.cl, UI_GpuConst_DebugDraw, 0); } } } ////////////////////////////// //- End frame G_CommitCommandList(UI.cl); WND_EndFrame(frame->window_frame, VEC2I32(0, 0), draw_target, RNG2I32(VEC2I32(0, 0), draw_size), vsync); EndScratch(scratch); }