diff --git a/src/base/base_inc.h b/src/base/base_inc.h index 6dbcbe4d..e123fc82 100644 --- a/src/base/base_inc.h +++ b/src/base/base_inc.h @@ -1,5 +1,5 @@ // This is the includable version of the base layer manifest. -// The base layer uses an includable file rather than a '.lay' file so +// The base layer uses an includable header rather than a typical .lay file so // that it may be depended on by the metaprogram. //- Api @@ -7,6 +7,7 @@ #if IsCpu #include "base_memory.h" #include "base_arena.h" + #include "base_prof.h" #include "base_futex.h" #include "base_sync.h" #include "base_time.h" @@ -36,6 +37,7 @@ #if IsCpu #include "base_tweak.c" #include "base_arena.c" + #include "base_prof.c" #include "base_sync.c" #include "base_uid.c" #include "base_string.c" diff --git a/src/base/base_math.c b/src/base/base_math.c index 9a96044a..f8ff5689 100644 --- a/src/base/base_math.c +++ b/src/base/base_math.c @@ -342,6 +342,26 @@ Vec4 SrgbFromLinear(Vec4 lin) return result; } +Vec4 SrgbFromHsv(f32 h, f32 s, f32 v) +{ + h = ModF32(h, 360.0f); + if (h < 0) + { + h += 360.0f; + } + f32 c = v * s; + f32 x = c * (1.0f - AbsF32(ModF32(h / 60.0f, 2.0f) - 1.0f)); + f32 m = v - c; + Vec4 result = VEC4(0, 0, 0, 1); + if (h < 60.0f) { result.r = c; result.g = x; result.b = 0; } + else if (h < 120.0f) { result.r = x; result.g = c; result.b = 0; } + else if (h < 180.0f) { result.r = 0; result.g = c; result.b = x; } + else if (h < 240.0f) { result.r = 0; result.g = x; result.b = c; } + else if (h < 300.0f) { result.r = x; result.g = 0; result.b = c; } + else { result.r = c; result.g = 0; result.b = x; } + return result; +} + Vec4 PremulFromLinear(Vec4 lin) { Vec4 result = Zi; diff --git a/src/base/base_math.h b/src/base/base_math.h index ff390cc8..aa99b0eb 100644 --- a/src/base/base_math.h +++ b/src/base/base_math.h @@ -339,6 +339,7 @@ f32 LinearFromSrgbF32(f32 srgb); Vec4 LinearFromSrgb(Vec4 srgb); Vec4 SrgbFromLinear(Vec4 lin); +Vec4 SrgbFromHsv(f32 h, f32 s, f32 v); Vec4 PremulFromLinear(Vec4 lin); Vec4 PremulFromSrgb(Vec4 srgb); @@ -437,7 +438,7 @@ u32 U32FromVec4(Vec4 v); //- Rng1 f32 NormRng(Rng r, f32 v); -#define Norm(min, max, v) NormRng(RNG((min), (max)), (v)) +#define Norm(v, min, max) NormRng(RNG((min), (max)), (v)) //- Rng2 b32 IsRng2Empty(Rng2 r); diff --git a/src/base/base_prof.c b/src/base/base_prof.c new file mode 100644 index 00000000..174c1bd8 --- /dev/null +++ b/src/base/base_prof.c @@ -0,0 +1,49 @@ +//////////////////////////////////////////////////////////// +//~ Profile zone + +void PushProfZoneEx(char *name_cstr_lit, b32 end_zone) +{ + ProfTrack *track = Base_tl.prof.thread_track; + if (!track) + { + Arena *perm = PermArena(); + { + track = PushStruct(perm, ProfTrack); + Base_tl.prof.thread_track = track; + } + LockTicketMutex(&Base.prof.register_track_tm); + { + Base_tl.prof.track_idx = Atomic32Fetch(&Base.prof.tracks_count); + Base.prof.tracks[Base_tl.prof.track_idx] = track; + Atomic32FetchAdd(&Base.prof.tracks_count, 1); + } + UnlockTicketMutex(&Base.prof.register_track_tm); + } + + u64 sample_seq = track->top_sample_seq; + ProfSample *sample = &track->samples[sample_seq % ProfTrackSamplesCap]; + { + // TODO: Wrap rdtsc for cpu-relative <-> program-relative timestamp conversion + // sample->stamp = __rdtsc() | ((u64)end_zone << 63); + sample->stamp = TimeNs() | ((u64)end_zone << 63); + sample->name_cstr_lit = name_cstr_lit; + } + track->top_sample_seq = sample_seq + 1; +} + +//////////////////////////////////////////////////////////// +//~ Retrieve tracks + +RegisteredProfTracksArray FetchRegisteredProfTracks(Arena *arena) +{ + RegisteredProfTracksArray result = Zi; + { + result.count = Atomic32Fetch(&Base.prof.tracks_count); + result.tracks = PushStructsNoZero(arena, ProfTrack *, result.count); + for (u64 track_idx = 0; track_idx < result.count; ++track_idx) + { + result.tracks[track_idx] = Base.prof.tracks[track_idx]; + } + } + return result; +} diff --git a/src/base/base_prof.h b/src/base/base_prof.h new file mode 100644 index 00000000..adc9c8fa --- /dev/null +++ b/src/base/base_prof.h @@ -0,0 +1,59 @@ +//////////////////////////////////////////////////////////// +//~ Profile types + +#define ProfTrackSamplesCap Kibi(256) +#define MaxRegisteredProfTracks Kibi(32) + +#define NsFromProfStamp(stamp) (stamp & ~((u64)1 << 63)) + +Struct(ProfSample) +{ + // Bit 63: zone-end, Bits 62-0: timestamp + u64 stamp; + char *name_cstr_lit; +}; + +Struct(ProfTrack) +{ + u64 top_sample_seq; + ProfSample samples[ProfTrackSamplesCap]; +}; + +Struct(RegisteredProfTracksArray) +{ + u64 count; + ProfTrack **tracks; +}; + +//////////////////////////////////////////////////////////// +//~ State types + +Struct(ProfCtx) +{ + TicketMutex register_track_tm; + + Atomic32 tracks_count; + ProfTrack *tracks[MaxRegisteredProfTracks]; +}; + +Struct(ThreadLocalProfCtx) +{ + u32 track_idx; + ProfTrack *thread_track; +}; + +//////////////////////////////////////////////////////////// +//~ Profile zone + +void PushProfZoneEx(char *name_cstr_lit, b32 end_zone); + +#if PROFILING_ENABLED + #define ProfZoneDF(name_cstr_lit) DeferFor(PushProfZoneEx(name_cstr_lit"\0", 0), PushProfZoneEx(name_cstr_lit"\0", 1)) +#else + #define ProfZoneDF(...) +#endif + +//////////////////////////////////////////////////////////// +//~ Retrieve tracks + +RegisteredProfTracksArray FetchRegisteredProfTracks(Arena *arena); diff --git a/src/base/base_state.h b/src/base/base_state.h index 0f29ab7e..5f6e1ce2 100644 --- a/src/base/base_state.h +++ b/src/base/base_state.h @@ -8,6 +8,7 @@ Struct(BaseCtx) ResourceCtx resource; GstatCtx gstat; AsyncCtx async; + ProfCtx prof; }; extern BaseCtx Base; @@ -18,6 +19,7 @@ extern BaseCtx Base; Struct(BaseThreadLocalCtx) { ThreadLocalArenaCtx arenas; + ThreadLocalProfCtx prof; }; extern ThreadLocal BaseThreadLocalCtx Base_tl; diff --git a/src/pp/pp_sim/pp_sim_core.c b/src/pp/pp_sim/pp_sim_core.c index 7eb1150c..7b09a761 100644 --- a/src/pp/pp_sim/pp_sim_core.c +++ b/src/pp/pp_sim/pp_sim_core.c @@ -86,6 +86,7 @@ void S_TickForever(WaveLaneCtx *lane) b32 shutdown = 0; while (!shutdown) + ProfZoneDF("Sim Frame") { shutdown = Atomic32Fetch(&S.shutdown); P_tl.debug_draw_enabled = TweakBool("Simulation debug draw", 0); diff --git a/src/pp/pp_vis/pp_vis_core.c b/src/pp/pp_vis/pp_vis_core.c index bcf93a70..467977b5 100644 --- a/src/pp/pp_vis/pp_vis_core.c +++ b/src/pp/pp_vis/pp_vis_core.c @@ -1,28 +1,5 @@ V_Ctx V = Zi; - - - - - - - - - - - - -// FIXME: Remove this - -ProfCtx Prof = Zi; -ThreadLocal ProfThreadLocalCtx Prof_tl = Zi; - - - - - - - //////////////////////////////////////////////////////////// //~ Bootstrap @@ -894,6 +871,7 @@ void V_TickForever(WaveLaneCtx *lane) b32 shutdown = 0; while (!shutdown) + ProfZoneDF("Vis frame") { shutdown = Atomic32Fetch(&V.shutdown); P_tl.debug_draw_enabled = TweakBool("Vis debug draw", 0); @@ -4777,7 +4755,7 @@ void V_TickForever(WaveLaneCtx *lane) UI_SetNext(Width, UI_GROW(1, 0)); UI_SetNext(Height, UI_SHRINK(0, 1)); UI_SetNext(FloatingPos, VEC2(0, -lister_offset)); - UI_SetNext(Flags, UI_BoxFlag_Floating | UI_BoxFlag_NoFloatingClampY | UI_BoxFlag_CaptureMouse | UI_BoxFlag_CaptureThroughChildren); + UI_SetNext(Flags, UI_BoxFlag_Floating | UI_BoxFlag_DontClampFloatingY | UI_BoxFlag_CaptureMouse | UI_BoxFlag_CaptureThroughChildren); UI_PushCp(UI_BuildRowEx(lister_key)); { UI_SetNext(Tint, 0); @@ -4999,7 +4977,7 @@ void V_TickForever(WaveLaneCtx *lane) UI_SetNext(Width, UI_SHRINK(0, 1)); UI_SetNext(Height, UI_SHRINK(0, 1)); UI_SetNext(Text, new_tweak_str); - UI_SetNext(Flags, UI_BoxFlag_DrawText | UI_BoxFlag_NoTextTruncation); + UI_SetNext(Flags, UI_BoxFlag_DrawText | UI_BoxFlag_DontTruncateText); UI_SetNext(BackgroundColor, 0); UI_SetNext(BorderColor, 0); UI_BuildBox(); @@ -5134,7 +5112,7 @@ void V_TickForever(WaveLaneCtx *lane) // UI_SetNext(Anchor, UI_Region_Center); // UI_SetNext(FloatingPos, VEC2(marker_pos, (marker_size_px * 0.5))); UI_SetNext(FloatingPos, VEC2(marker_pos, -marker_dims.y * 0.125)); - UI_SetNext(Flags, UI_BoxFlag_Floating | UI_BoxFlag_NoFloatingClampX | UI_BoxFlag_NoFloatingClampY | UI_BoxFlag_CaptureMouse); + UI_SetNext(Flags, UI_BoxFlag_Floating | UI_BoxFlag_DontClampFloatingX | UI_BoxFlag_DontClampFloatingY | UI_BoxFlag_CaptureMouse); UI_BuildBoxEx(marker_key); } } @@ -5263,7 +5241,7 @@ void V_TickForever(WaveLaneCtx *lane) UI_SetNext(Anchor, UI_Region_Center); UI_SetNext(Anchor, UI_Region_Top); // UI_SetNext(Flags, UI_BoxFlag_CaptureMouse | UI_BoxFlag_Floating); - UI_SetNext(Flags, UI_BoxFlag_CaptureMouse | UI_BoxFlag_Floating | UI_BoxFlag_NoFloatingClampY); + UI_SetNext(Flags, UI_BoxFlag_CaptureMouse | UI_BoxFlag_Floating | UI_BoxFlag_DontClampFloatingY); UI_SetNext(Anchor, UI_Region_Top); UI_BuildBoxEx(thumb_key); } @@ -5428,7 +5406,7 @@ void V_TickForever(WaveLaneCtx *lane) if (frame->tick == 1) { - profiler->ns_per_px = NsFromSeconds(0.010); + profiler->ns_per_px = NsFromSeconds(0.0001); } profiler->graph_dims = DimsFromRng2(UI_Rect(profiler_graph_box)); @@ -5494,12 +5472,13 @@ void V_TickForever(WaveLaneCtx *lane) UI_Size profiler_height = UI_FNT(40, 1); UI_Size header_height = UI_FNT(2, 1); - UI_Size window_padding = UI_FNT(0.5, 1); + UI_Size footer_height = UI_FNT(2, 1); + UI_Size window_padding = UI_FNT(0.60, 1); // UI_Size graph_height = UI_GROW(0.25, 0); UI_Size graph_height = UI_FNT(2, 1); UI_Size main_height = UI_GROW(1, 0); - UI_Size zone_height = UI_FNT(0.75, 0); - UI_Size track_height = UI_FNT(4, 0); + UI_Size zone_height = UI_FNT(1, 0); + UI_Size track_height = UI_FNT(25, 0); // Vec2 old_main_dims = DimsFromRng2(UI_Rect(main_box)); // old_main_dims.x = MaxF32(old_main_dims.x, 1); @@ -5519,7 +5498,13 @@ void V_TickForever(WaveLaneCtx *lane) // i64 old_cursor_ns = profiler->view_start_ns + old_cursor_offset_ns; f32 cursor_px = frame->screen_cursor.x - UI_Rect(main_box).p0.x; - i64 cursor_ns = (cursor_px * profiler->ns_per_px) + profiler->view_ns; + + b32 is_hovered = UI_HotAbsolute(main_box); + if (is_hovered) + { + profiler->cursor_ns = (cursor_px * profiler->ns_per_px) + profiler->view_ns; + } + f32 view_offset_px = profiler->view_ns / profiler->ns_per_px; @@ -5541,8 +5526,8 @@ void V_TickForever(WaveLaneCtx *lane) profiler->ns_per_px *= zoom_factor; - // profiler->view_ns -= (cursor_ns - profiler->view_ns) / zoom_factor; - profiler->view_ns = cursor_ns - (cursor_ns - profiler->view_ns) * zoom_factor; + // profiler->view_ns -= (profiler->cursor_ns - profiler->view_ns) / zoom_factor; + profiler->view_ns = profiler->cursor_ns - (profiler->cursor_ns - profiler->view_ns) * zoom_factor; @@ -5573,13 +5558,23 @@ void V_TickForever(WaveLaneCtx *lane) - if (UI_Downs(main_box, Button_M3)) + + + // TODO: Drag in px units for sharper resolution + if (UI_Downs(main_box, Button_M1) || UI_Downs(main_box, Button_M2) || UI_Downs(main_box, Button_M3)) { profiler->drag_view_ns = profiler->view_ns; profiler->drag_cursor_px = cursor_px; } - if (UI_Held(main_box, Button_M3)) + // Drag ruler + if (UI_Held(main_box, Button_M1)) + { + // profiler-> + } + + // Drag view + if (UI_Held(main_box, Button_M2) || UI_Held(main_box, Button_M3)) { profiler->view_ns = profiler->drag_view_ns - ((cursor_px - profiler->drag_cursor_px) * profiler->ns_per_px); } @@ -5605,7 +5600,12 @@ void V_TickForever(WaveLaneCtx *lane) // f64 ns_per_px = MaxF64((profiler->view_end_ns - profiler->view_start_ns) / old_main_dims.x, 1); Vec4 profiler_color = theme.col.window_bg; - profiler_color.a = 1; + f32 profiler_opacity = 1; + + Vec4 main_color = profiler_color; + main_color.r *= 0.75; + main_color.g *= 0.75; + main_color.b *= 0.75; UI_SetNext(Parent, vis_ui_box); UI_SetNext(BorderColor, theme.col.window_bd); @@ -5614,6 +5614,7 @@ void V_TickForever(WaveLaneCtx *lane) UI_SetNext(Rounding, UI_RPIX(10 * theme.rounding)); UI_SetNext(Height, profiler_height); UI_SetNext(Flags, UI_BoxFlag_CaptureMouse); + UI_SetNext(Opacity, profiler_opacity); UI_PushDF(Width, UI_GROW(1, 0)) UI_PushDF(Parent, UI_BuildColumnEx(profiler_box)) UI_PushDF(Parent, UI_BuildRow()) @@ -5640,16 +5641,18 @@ void V_TickForever(WaveLaneCtx *lane) UI_PushDF(Parent, UI_BuildBoxEx(profiler_graph_box)) { } - UI_BuildDivider(UI_PIX(1, 1), theme.col.divider, Axis_Y); + // UI_BuildDivider(UI_PIX(1, 1), theme.col.divider, Axis_Y); - //- Main + //- Main area if (do_break) { - UI_SetNext(DebugBreakFlags, UI_DebugBreakFlag_BuildFeedback); - UI_SetNext(Text, Lit("MAIN")); + // UI_SetNext(DebugBreakFlags, UI_DebugBreakFlag_BuildFeedback); } + UI_SetNext(Text, Lit("MAIN")); + UI_SetNext(BackgroundColor, main_color); UI_SetNext(Height, main_height); - UI_SetNext(Flags, UI_BoxFlag_CaptureMouse | UI_BoxFlag_CaptureThroughChildren); + UI_SetNext(Rounding, UI_RPIX(4)); + UI_SetNext(Flags, UI_BoxFlag_CaptureMouse | UI_BoxFlag_CaptureThroughChildren | UI_BoxFlag_Scissor); // UI_SetNext(Flags, UI_BoxFlag_CaptureMouse); UI_PushDF(Parent, UI_BuildColumnEx(main_box)) { @@ -5687,24 +5690,16 @@ void V_TickForever(WaveLaneCtx *lane) // }; - - Struct(V_ZoneDesc) - { - Vec4 color; - i64 start_ns; - i64 end_ns; - }; - - u32 tracks_count = Atomic32Fetch(&Prof.tracks_count); - for (u64 track_idx = 0; track_idx < tracks_count; ++track_idx) + RegisteredProfTracksArray tracks = FetchRegisteredProfTracks(frame->arena); + for (u64 track_idx = 0; track_idx < tracks.count; ++track_idx) { UI_Key track_box = UI_KeyF("track %F", FmtUint(track_idx)); UI_SetNext(Height, track_height); // UI_SetNext(Flags, UI_BoxFlag_CaptureMouse); UI_PushDF(Tag, track_box.v) - UI_PushDF(Parent, UI_BuildRowEx(track_box)) + UI_PushDF(Parent, UI_BuildColumnEx(track_box)) { - ProfTrack *track = Prof.tracks[track_idx]; + ProfTrack *track = tracks.tracks[track_idx]; // u64 zones_count = 64; // V_ZoneDesc *zones = PushStructs(frame->arena, V_ZoneDesc, zones_count); @@ -5760,6 +5755,7 @@ void V_TickForever(WaveLaneCtx *lane) + samples_end_seq = MinU64(samples_end_seq, samples_start_seq + 512); @@ -5768,82 +5764,291 @@ void V_TickForever(WaveLaneCtx *lane) - u64 zones_count = samples_end_seq - samples_start_seq; - zones_count = MinU64(zones_count, 100); - V_ZoneDesc *zones = PushStructs(frame->arena, V_ZoneDesc, zones_count); - for (u64 zone_idx = 0; zone_idx < zones_count; ++zone_idx) + + + + Struct(V_ZoneDesc) { - V_ZoneDesc *zone = &zones[zone_idx]; + V_ZoneDesc *prev; + V_ZoneDesc *next; - ProfSample *sample = &samples[samples_start_seq % ProfTrackSamplesCap]; + V_ZoneDesc *prev_in_row; + V_ZoneDesc *next_in_row; - // zone->color = VEC4(0.5, 0.2, 0.2, 0.5); - // zone->color.r += 0.5 * Norm24(MixU64(zone_idx)); + String name; + u64 start_sample_seq; + u64 end_sample_seq; - u64 seed0 = MixU64(zone_idx + 1); - u64 seed1 = MixU64(seed0); - u64 seed2 = MixU64(seed1); - u64 seed3 = MixU64(seed2); + u32 depth; - zone->color = VEC4(Norm24(seed0), Norm24(seed1), Norm24(seed2), 1); - zone->color.r += (1.0 - zone->color.r) * 0.25; - - zone->start_ns = NsFromSeconds(zone_idx * 2); - zone->end_ns = NsFromSeconds(zone_idx * 2 + 1); - - // zone->start_ns = NsFromProfStamp(sample->stamp); - // zone->end_ns = zone->start_ns + NsFromSeconds(0.100); + Vec4 color; + i64 start_ns; + i64 end_ns; }; + // u64 zones_count = samples_end_seq - samples_start_seq; + // zones_count = MinU64(zones_count, 1000); + // V_ZoneDesc *zones = PushStructs(frame->arena, V_ZoneDesc, zones_count); - - //- Zones - for (u64 zone_idx = 0; zone_idx < zones_count; ++zone_idx) + //- Create zones + V_ZoneDesc *first_zone = 0; + V_ZoneDesc *last_zone = 0; + u64 zone_rows_count = 0; { - V_ZoneDesc *zone = &zones[zone_idx]; + // FIXME: Clamp depth + // u64 zone_idx = 0; + u64 depth = 0; - UI_Key zone_box = UI_KeyF("Zone %F", FmtUint(zone_idx)); - - Vec4 zone_color = zone->color; - zone_color = LerpSrgb(zone_color, Color_Cyan, UI_Hot(zone_box)); - - // Vec4 zone_color_bd = Zi; - // zone_color_bd - - i64 zone_len_ns = zone->end_ns - zone->start_ns; - f32 zone_len_px = zone_len_ns / profiler->ns_per_px; - // i6 - - f32 zone_offset_px = (zone->start_ns - profiler->view_ns) / profiler->ns_per_px; - Vec2 zone_pos = VEC2(zone_offset_px, 0); - UI_Size zone_width = UI_PIX(zone_len_px, 1); - - UI_SetNext(Text, StringF(frame->arena, "ZONE %F", FmtUint(zone_idx))); - // if (do_break && UI_IsMouseHovered(zone_box)) - if (do_break && zone_idx == 0) + for (u64 sample_seq = 0; sample_seq < samples_end_seq; ++sample_seq) { - UI_SetNext(DebugBreakFlags, UI_DebugBreakFlag_BuildFeedback | UI_DebugBreakFlag_CheckCursorHover); - } + ProfSample *sample = &samples[sample_seq % ProfTrackSamplesCap]; + + i64 time_ns = NsFromProfStamp(sample->stamp); + + b32 is_end = !!(sample->stamp & ((u64)1 << 63)); + if (is_end) + { + depth -= 1; + V_ZoneDesc *zone = last_zone; + for (; zone->depth > depth;) + { + zone = zone->prev; + } + zone->end_ns = time_ns; + zone->end_sample_seq = sample_seq; + } + else + { + V_ZoneDesc *zone = PushStruct(frame->arena, V_ZoneDesc); + zone->start_ns = time_ns; + DllQueuePush(first_zone, last_zone, zone); + + char *name_cstr = sample->name_cstr_lit; + String name = StringFromCstrNoLimit(name_cstr); + + u64 seed = HashString(name); + // u64 seed = MixU64((u64)name_cstr); + + f32 h = (Norm16(seed >> 0) * 1) * 360; + f32 s = TweakFloat("Profiler zone saturation", 0.85, 0, 1); + f32 v = TweakFloat("Profiler zone brightness", 0.50, 0, 1); + + zone->color = SrgbFromHsv(h, s, v); + + zone->start_sample_seq = sample_seq; + zone->name = name; + zone->depth = depth; - - UI_SetNext(Width, zone_width); - UI_SetNext(Height, zone_height); - UI_SetNext(BackgroundColor, zone_color); - UI_SetNext(FloatingPos, zone_pos); - UI_SetNext(Flags, UI_BoxFlag_Floating | UI_BoxFlag_NoFloatingClampX | UI_BoxFlag_NoFloatingClampY | UI_BoxFlag_CaptureMouse); - // UI_SetNext(Flags, UI_BoxFlag_Floating | UI_BoxFlag_NoFloatingClampX | UI_BoxFlag_NoFloatingClampY); - UI_PushDF(Parent, UI_BuildBoxEx(zone_box)) - { + depth += 1; + zone_rows_count = MaxU64(depth, zone_rows_count); + } } } + + + + Struct(V_ZoneRow) + { + u64 count; + V_ZoneDesc *first_zone; + V_ZoneDesc *last_zone; + }; + + //- Push zones to rows + V_ZoneRow *zone_rows = PushStructs(frame->arena, V_ZoneRow, zone_rows_count); + for (V_ZoneDesc *zone = first_zone; zone; zone = zone->next) + { + V_ZoneRow *row = &zone_rows[zone->depth]; + row->count += 1; + DllQueuePushNP(row->first_zone, row->last_zone, zone, next_in_row, prev_in_row); + } + + + + + + + + + + + // for (u64 zone_idx = 0; zone_idx < zones_count; ++zone_idx) + // { + // V_ZoneDesc *zone = &zones[zone_idx]; + // ProfSample *sample = &samples[(zone_idx + samples_start_seq) % ProfTrackSamplesCap]; + + // // zone->color = VEC4(0.5, 0.2, 0.2, 0.5); + // // zone->color.r += 0.5 * Norm24(MixU64(zone_idx)); + + // u64 seed0 = MixU64(zone_idx + 1); + // u64 seed1 = MixU64(seed0); + // u64 seed2 = MixU64(seed1); + // u64 seed3 = MixU64(seed2); + + // zone->color = VEC4(Norm24(seed0), Norm24(seed1), Norm24(seed2), 1); + // zone->color.r += (1.0 - zone->color.r) * 0.25; + + // // zone->start_ns = NsFromSeconds(zone_idx * 2); + // // zone->end_ns = NsFromSeconds(zone_idx * 2 + 1); + + // zone->start_ns = NsFromProfStamp(sample->stamp); + // zone->end_ns = zone->start_ns + NsFromSeconds(0.100); + // } + + + + + + + + // for (u64 zone_idx = 0; zone_idx < zones_count; ++zone_idx) + // { + // V_ZoneDesc *zone = &zones[zone_idx]; + + // ProfSample *sample = &samples[(zone_idx + samples_start_seq) % ProfTrackSamplesCap]; + + // // zone->color = VEC4(0.5, 0.2, 0.2, 0.5); + // // zone->color.r += 0.5 * Norm24(MixU64(zone_idx)); + + // u64 seed0 = MixU64(zone_idx + 1); + // u64 seed1 = MixU64(seed0); + // u64 seed2 = MixU64(seed1); + // u64 seed3 = MixU64(seed2); + + // zone->color = VEC4(Norm24(seed0), Norm24(seed1), Norm24(seed2), 1); + // zone->color.r += (1.0 - zone->color.r) * 0.25; + + // // zone->start_ns = NsFromSeconds(zone_idx * 2); + // // zone->end_ns = NsFromSeconds(zone_idx * 2 + 1); + + // zone->start_ns = NsFromProfStamp(sample->stamp); + // zone->end_ns = zone->start_ns + NsFromSeconds(0.100); + // } + + + + + + + + + { + for (u64 zone_row_idx = 0; zone_row_idx < zone_rows_count; ++zone_row_idx) + { + V_ZoneRow *row = &zone_rows[zone_row_idx]; + + //- Zone row + // UI_SetNext(Height, zone_height); + UI_SetNext(Height, zone_height); + UI_PushDF(Parent, UI_BuildRow()) + { + //- Zones in row + u64 zone_idx = 0; + for (V_ZoneDesc *zone = row->first_zone; zone; zone = zone->next_in_row) + { + if (zone->end_ns > zone->start_ns) + { + UI_Key zone_box = UI_KeyF("Zone %F", FmtUint(zone->start_sample_seq)); + + Vec4 zone_color = zone->color; + Vec4 zone_color_bd = zone_color; + // zone_color = LerpSrgb(zone_color, Color_Cyan, UI_Hot(zone_box)); + + // zone_color_bd = LerpSrgb(zone_color_bd, theme.col.button_selected, UI_Hot(zone_box)); + // zone_color_bd = LerpSrgb(zone_color_bd, theme.col.button_active, UI_Hot(zone_box)); + zone_color_bd = LerpSrgb(zone_color_bd, Color_White, UI_Hot(zone_box)); + + i64 zone_len_ns = zone->end_ns - zone->start_ns; + f32 zone_len_px = zone_len_ns / profiler->ns_per_px; + // i6 + + f32 zone_offset_px = (zone->start_ns - profiler->view_ns) / profiler->ns_per_px; + Vec2 zone_pos = VEC2(zone_offset_px, 0); + UI_Size zone_width = UI_PIX(zone_len_px, 1); + + UI_SetNext(Text, StringF(frame->arena, "ZONE %F", FmtUint(zone_idx))); + // if (do_break && UI_IsMouseHovered(zone_box)) + if (do_break && zone_idx == 0) + { + // UI_SetNext(DebugBreakFlags, UI_DebugBreakFlag_BuildFeedback | UI_DebugBreakFlag_CheckCursorHover); + } + + + + UI_SetNext(Width, zone_width); + UI_SetNext(Height, UI_GROW(1, 0)); + UI_SetNext(BackgroundColor, zone_color); + UI_SetNext(BorderColor, zone_color_bd); + UI_SetNext(BorderSize, 1); + UI_SetNext(FloatingPos, zone_pos); + UI_SetNext(ChildAlignment, UI_Region_Center); + UI_SetNext(Flags, UI_BoxFlag_Floating | UI_BoxFlag_DontClampFloatingX | UI_BoxFlag_DontClampFloatingY | UI_BoxFlag_CaptureMouse | UI_BoxFlag_Scissor); + // UI_SetNext(Flags, UI_BoxFlag_Floating | UI_BoxFlag_DontClampFloatingX | UI_BoxFlag_DontClampFloatingY); + UI_PushDF(Parent, UI_BuildRowEx(zone_box)) + { + UI_SetNext(ChildAlignment, UI_Region_Center); + UI_SetNext(FontSize, UI_Top(FontSize) * 0.75); + UI_SetNext(Text, zone->name); + // UI_SetNext(Flags, UI_BoxFlag_DrawText | UI_BoxFlag_DontTruncateText); + UI_SetNext(Flags, UI_BoxFlag_DrawText); + UI_BuildRow(); + } + zone_idx += 1; + } + } + } + + } + } + + + // for (u64 zone_idx = 0; zone_idx < zones_count; ++zone_idx) + // { + // V_ZoneDesc *zone = &zones[zone_idx]; + + // UI_Key zone_box = UI_KeyF("Zone %F", FmtUint(zone_idx)); + + // Vec4 zone_color = zone->color; + // zone_color = LerpSrgb(zone_color, Color_Cyan, UI_Hot(zone_box)); + + // // Vec4 zone_color_bd = Zi; + // // zone_color_bd + + // i64 zone_len_ns = zone->end_ns - zone->start_ns; + // f32 zone_len_px = zone_len_ns / profiler->ns_per_px; + // // i6 + + // f32 zone_offset_px = (zone->start_ns - profiler->view_ns) / profiler->ns_per_px; + // Vec2 zone_pos = VEC2(zone_offset_px, 0); + // UI_Size zone_width = UI_PIX(zone_len_px, 1); + + // UI_SetNext(Text, StringF(frame->arena, "ZONE %F", FmtUint(zone_idx))); + // // if (do_break && UI_IsMouseHovered(zone_box)) + // if (do_break && zone_idx == 0) + // { + // UI_SetNext(DebugBreakFlags, UI_DebugBreakFlag_BuildFeedback | UI_DebugBreakFlag_CheckCursorHover); + // } + + + + // UI_SetNext(Width, zone_width); + // UI_SetNext(Height, zone_height); + // UI_SetNext(BackgroundColor, zone_color); + // UI_SetNext(FloatingPos, zone_pos); + // UI_SetNext(Flags, UI_BoxFlag_Floating | UI_BoxFlag_DontClampFloatingX | UI_BoxFlag_DontClampFloatingY | UI_BoxFlag_CaptureMouse); + // // UI_SetNext(Flags, UI_BoxFlag_Floating | UI_BoxFlag_DontClampFloatingX | UI_BoxFlag_DontClampFloatingY); + // UI_PushDF(Parent, UI_BuildBoxEx(zone_box)) + // { + // } + // } } } @@ -5858,9 +6063,9 @@ void V_TickForever(WaveLaneCtx *lane) //- Timeline cursor { // Vec2 timeline_cursor_pos = VEC2(old_cursor_offset_px, 0); - Vec2 timeline_cursor_pos = VEC2((cursor_ns - profiler->view_ns) / profiler->ns_per_px, 0); + Vec2 timeline_cursor_pos = VEC2((profiler->cursor_ns - profiler->view_ns) / profiler->ns_per_px, 0); Vec4 timeline_cursor_color = theme.col.window_bd; - timeline_cursor_color.a = UI_Hot(main_box); + timeline_cursor_color.a = UI_Hot(main_box) * 0.5; UI_SetNext(Width, UI_FNT(0.15, 1)); UI_SetNext(Height, UI_GROW(1, 0)); @@ -5871,346 +6076,29 @@ void V_TickForever(WaveLaneCtx *lane) } } + // UI_BuildSpacer(window_padding, Axis_Y); + + //- Footer + UI_PushDF(Height, footer_height) + UI_PushDF(ChildAlignment, UI_Region_Center) + UI_PushDF(Parent, UI_BuildRow()) + { + UI_BuildSpacer(UI_GROW(1, 0), Axis_X); + + if (do_break) + { + UI_ForceNext(DebugBreakFlags, UI_DebugBreakFlag_PrepLayout); + } + + UI_SetNext(Opacity, UI_Hot(main_box)); + UI_BuildLabelF("%Fs", FmtFloat(SecondsFromNs(profiler->cursor_ns), .p = 3)); + } } UI_BuildSpacer(window_padding, Axis_X); } } } - - - - - - - - - - - - - - - - - - - - - - // ////////////////////////////// - // //- Build profiler - - - // // FIXME: Finalize - - - // V_Profiler *profiler = &frame->profiler; - // UI_Key profiler_graph_box = UI_KeyF("graph"); - // UI_PushDF(OmitFlags, UI_BoxFlag_CaptureMouse * !!(frame->is_looking)) - // if (TweakBool("Show profiler", 1)) - // { - // profiler->is_showing = 1; - - // UI_BoxReports profiler_graph_box = UI_ReportsFromKey(profiler_graph_box); - // profiler->graph_dims = DimsFromRng2(profiler_graph_box.UI_Rect()); - - // //- Init test samples - // // // FIXME: Remove this - // // Struct(SampleState) - // // { - // // i64 ready_samples_count; - // // Arena *samples_arena; - // // }; - // // Struct(Sample) - // // { - // // u64 zone_id; - // // u64 thread_id; - // // i64 start_ns; - // // i64 end_ns; - // // }; - // // PERSIST i64 sample_states_count = 1; - // // PERSIST SampleState *sample_states = 0; - // // if (!sample_states) - // // { - // // sample_states = PushStructs(perm, SampleState, sample_states_count); - // // for (i64 sample_state_idx = 0; sample_state_idx < sample_states_count; ++sample_state_idx) - // // { - // // SampleState *ss = &sample_states[sample_state_idx]; - // // ss->samples_arena = AcquireArena(Gibi(64)); - - - - - - - // // ss->ready_samples_count = 128; - // // for (i64 sample_idx = 0; sample_idx < ss->ready_samples_count; ++sample_idx) - // // { - // // Sample *sample = PushStruct(ss->samples_arena, Sample); - // // sample->zone_id = (u64)"Hello there"; - // // sample->thread_id = 0; - // // sample->start_ns = NsFromSeconds(sample_idx); - // // sample->end_ns = NsFromSeconds(sample_idx + 1); - // // } - // // } - // // } - - - // // FIXME: Remove this - // { - // V_ProfilerFrame *pf = &V.profiler_frames[V.profiler_frame_seq % V_ProfilerFramesCap]; - // V.profiler_frame_seq += 1; - // } - - - - - - // //- Build profiler - // UI_Key profiler_box = UI_KeyF("profiler"); - // UI_PushDF(Tag, profiler_box.v) - // UI_PushDF(Parent, profiler_box) - // { - // UI_Key main_box = UI_KeyF("main"); - - // UI_Size profiler_height = UI_FNT(40, 1); - // UI_Size header_height = UI_FNT(2, 1); - // UI_Size window_padding = UI_FNT(0.5, 1); - // // UI_Size graph_height = UI_GROW(0.25, 0); - // UI_Size graph_height = UI_FNT(2, 1); - // UI_Size main_height = UI_GROW(1, 0); - // UI_Size zone_height = UI_FNT(0.75, 0); - - // UI_BoxReports main_box = UI_ReportsFromKey(main_box); - - - // Vec2 old_main_dims = DimsFromRng2(UI_Rect(main_box)); - // old_main_dims.x = MaxF32(old_main_dims.x, 1); - // old_main_dims.y = MaxF32(old_main_dims.y, 1); - - - - // if (profiler->view_start_ns == 0 && profiler->view_end_ns == 0) - // { - // profiler->view_start_ns = NsFromSeconds(0); - // profiler->view_end_ns = NsFromSeconds(10); - // } - - - // f32 old_cursor_offset_px = frame->screen_cursor.x - UI_Rect(main_box).p0.x; - // i64 old_cursor_offset_ns = (old_cursor_offset_px / old_main_dims.x) * (profiler->view_end_ns - profiler->view_start_ns); - // i64 old_cursor_ns = profiler->view_start_ns + old_cursor_offset_ns; - - - - - - - // // FIXME: Remove this - // f32 prof_zoom_rate = 2; - // // profiler->view_start_ns += NsFromSeconds(1) * main_box.draw.buttons[Button_WheelUp].presses * prof_zoom_rate; - // // profiler->view_start_ns -= NsFromSeconds(1) * main_box.draw.buttons[Button_WheelDown].presses * prof_zoom_rate; - - // // profiler->view_start_ns -= main_box.draw.buttons[Button_WheelUp].presses * ns_to_left * prof_zoom_rate; - // // profiler->view_end_ns += main_box.draw.buttons[Button_WheelDown].presses * ns_to_right * prof_zoom_rate; - - // // profiler->view_start_ns -= main_box.draw.buttons[Button_WheelDown].presses * ns_to_left * prof_zoom_rate; - // // profiler->view_end_ns += main_box.draw.buttons[Button_WheelDown].presses * ns_to_right * prof_zoom_rate; - - // if (main_box.draw.buttons[Button_WheelUp].presses) - // { - // DEBUGBREAKABLE; - // } - - // // profiler->view_start_ns += main_box.draw.buttons[Button_WheelUp].presses * ns_to_left / prof_zoom_rate; - // // profiler->view_end_ns -= main_box.draw.buttons[Button_WheelUp].presses * ns_to_right / prof_zoom_rate; - - // for (i32 zoom_idx = 0; zoom_idx < main_box.draw.buttons[Button_WheelUp].presses; ++zoom_idx) - // { - // i64 ns_to_left = old_cursor_ns - profiler->view_start_ns; - // i64 ns_to_right = profiler->view_end_ns - old_cursor_ns; - // profiler->view_start_ns += (old_cursor_ns - profiler->view_start_ns) / prof_zoom_rate; - // profiler->view_end_ns -= (profiler->view_end_ns - old_cursor_ns) / prof_zoom_rate; - // } - // for (i32 zoom_idx = 0; zoom_idx < main_box.draw.buttons[Button_WheelDown].presses; ++zoom_idx) - // { - // i64 ns_to_left = old_cursor_ns - profiler->view_start_ns; - // i64 ns_to_right = profiler->view_end_ns - old_cursor_ns; - // profiler->view_start_ns -= (old_cursor_ns - profiler->view_start_ns) * prof_zoom_rate; - // profiler->view_end_ns += (profiler->view_end_ns - old_cursor_ns) * prof_zoom_rate; - // } - - // if (main_box.draw.buttons[Button_M3].downs) - // { - // profiler->drag_view_start_ns = profiler->view_start_ns; - // profiler->drag_cursor_ns = old_cursor_ns; - // } - - // if (main_box.draw.buttons[Button_M3].held) - // { - // // i64 ns_delta = prev_frame->profiler. - // i64 ns_delta = old_cursor_ns - profiler->drag_cursor_ns; - - // // i64 dims_ns = profiler->view_end_ns - profiler->view_start_ns; - - // // profiler->view_start_ns = profiler->drag_view_start_ns - ns_delta; - // // profiler->view_end_ns = profiler->view_start_ns + dims_ns; - - // i64 dims_ns = profiler->view_end_ns - profiler->view_start_ns; - // profiler->view_start_ns = profiler->drag_view_start_ns - ns_delta; - // profiler->view_end_ns = profiler->view_start_ns + dims_ns; - // } - - - - - - - - - - - - // f64 ns_per_px = MaxF64((profiler->view_end_ns - profiler->view_start_ns) / old_main_dims.x, 1); - - // Vec4 profiler_color = theme.col.window_bg; - // profiler_color.a = 1; - - // UI_SetNext(Parent, vis_ui_box); - // UI_SetNext(BorderColor, theme.col.window_bd); - // UI_SetNext(BorderSize, 2); - // UI_SetNext(BackgroundColor, profiler_color); - // UI_SetNext(Rounding, UI_RPIX(10 * theme.rounding)); - // UI_SetNext(Height, profiler_height); - // UI_SetNext(Flags, UI_BoxFlag_CaptureMouse); - // UI_PushDF(Width, UI_GROW(1, 0)) - // UI_PushDF(Parent, UI_BuildColumnEx(profiler_box)) - // UI_PushDF(Parent, UI_BuildRow()) - // { - // UI_BuildSpacer(window_padding, Axis_X); - // UI_PushDF(Parent, UI_BuildColumn()) - // { - // //- Header - // // UI_PushDF(BackgroundColor, Color_Red) - // // UI_SetNext(BorderColor, Color_Red); - // // UI_SetNext(BorderSize, 1); - // UI_SetNext(ChildAlignment, UI_Region_Center); - // UI_PushDF(Height, header_height) - // UI_PushDF(Parent, UI_BuildBoxF("profiler header")) - // { - // UI_PushDF(TextColor, theme.col.hint) - // UI_BuildLabelF("Profiler"); - // } - // UI_BuildDivider(UI_PIX(1, 1), theme.col.divider, Axis_Y); - - // //- Graph - // // UI_SetNext(BackgroundColor, Color_Cyan); - // UI_SetNext(Height, graph_height); - // UI_PushDF(Parent, UI_BuildBoxEx(profiler_graph_box)) - // { - // } - // UI_BuildDivider(UI_PIX(1, 1), theme.col.divider, Axis_Y); - - // //- Main - // UI_SetNext(Height, main_height); - // UI_SetNext(Flags, UI_BoxFlag_CaptureMouse); - // UI_PushDF(Parent, UI_BuildRowEx(main_box)) - // { - - // // FIXME: Remove this - - // Struct(V_ZoneDesc) - // { - // Vec4 color; - // i64 start_ns; - // i64 end_ns; - // }; - - // u64 zones_count = 64; - // V_ZoneDesc *zones = PushStructs(frame->arena, V_ZoneDesc, zones_count); - // for (u64 zone_idx = 0; zone_idx < zones_count; ++zone_idx) - // { - // V_ZoneDesc *zone = &zones[zone_idx]; - - // // zone->color = VEC4(0.5, 0.2, 0.2, 0.5); - // // zone->color.r += 0.5 * Norm24(MixU64(zone_idx)); - - // u64 seed0 = MixU64(zone_idx + 1); - // u64 seed1 = MixU64(seed0); - // u64 seed2 = MixU64(seed1); - // u64 seed3 = MixU64(seed2); - - // zone->color = VEC4(Norm24(seed0), Norm24(seed1), Norm24(seed2), 1); - // zone->color.r += (1.0 - zone->color.r) * 0.25; - - // zone->start_ns = NsFromSeconds(zone_idx * 2); - // zone->end_ns = NsFromSeconds(zone_idx * 2 + 1); - // }; - - // //- Zones - // for (u64 zone_idx = 0; zone_idx < zones_count; ++zone_idx) - // { - // V_ZoneDesc *zone = &zones[zone_idx]; - - // UI_Key zone_box = UI_KeyF("Zone %F", FmtUint(zone_idx)); - // // UI_BoxReport zone_box = UI_ReportFromKey(zone_box).draw; - - // Vec4 zone_color = zone->color; - - // // Vec4 zone_color_bd = Zi; - // // zone_color_bd - - // i64 zone_len_ns = zone->end_ns - zone->start_ns; - // f32 zone_len_px = zone_len_ns / ns_per_px; - // // i6 - - // f32 zone_offset_px = (zone->start_ns - profiler->view_start_ns) / ns_per_px; - // Vec2 zone_pos = VEC2(zone_offset_px, 0); - // UI_Size zone_width = UI_PIX(zone_len_px, 1); - - // // UI_Report - - - // UI_SetNext(Width, zone_width); - // UI_SetNext(Height, zone_height); - // UI_SetNext(BackgroundColor, zone_color); - // UI_SetNext(FloatingPos, zone_pos); - // UI_SetNext(Flags, UI_BoxFlag_Floating | UI_BoxFlag_NoFloatingClampX | UI_BoxFlag_NoFloatingClampY); - // UI_PushDF(Parent, UI_BuildBoxEx(zone_box)) - // { - // } - // } - - // //- Timeline cursor - // { - // // Vec2 timeline_cursor_pos = VEC2(old_cursor_offset_px, 0); - // Vec2 timeline_cursor_pos = VEC2((old_cursor_ns - profiler->view_start_ns) / ns_per_px, 0); - // Vec4 timeline_cursor_color = theme.col.window_bd; - // timeline_cursor_color.a = UI_Hot(main_box); - - // UI_SetNext(Width, UI_FNT(0.15, 1)); - // UI_SetNext(Height, UI_GROW(1, 0)); - // UI_SetNext(BackgroundColor, timeline_cursor_color); - // UI_SetNext(FloatingPos, timeline_cursor_pos); - // UI_SetNext(Flags, UI_BoxFlag_Floating); - // UI_BuildBox(); - // } - // } - - // } - // UI_BuildSpacer(window_padding, Axis_X); - // } - // } - // } - - - - - - - - ////////////////////////////// //- Build notifications @@ -6531,7 +6419,7 @@ void V_TickForever(WaveLaneCtx *lane) UI_SetNext(Anchor, UI_Region_Center); UI_SetNext(FloatingPos, timeline_pos); UI_SetNext(BackgroundColor, tmld_bg); - UI_SetNext(Flags, UI_BoxFlag_Floating | UI_BoxFlag_NoFloatingClampX | UI_BoxFlag_NoFloatingClampY); + UI_SetNext(Flags, UI_BoxFlag_Floating | UI_BoxFlag_DontClampFloatingX | UI_BoxFlag_DontClampFloatingY); UI_PushCp(UI_BuildRowEx(timeline_key)); { } @@ -6591,7 +6479,7 @@ void V_TickForever(WaveLaneCtx *lane) tint.a *= marker_opacity; UI_SetNext(Tint, tint); - UI_SetNext(Flags, UI_BoxFlag_Floating | UI_BoxFlag_NoFloatingClampX | UI_BoxFlag_NoFloatingClampY); + UI_SetNext(Flags, UI_BoxFlag_Floating | UI_BoxFlag_DontClampFloatingX | UI_BoxFlag_DontClampFloatingY); UI_SetNext(BackgroundColor, marker_color); UI_SetNext(Anchor, UI_Region_Center); UI_SetNext(Width, width); diff --git a/src/pp/pp_vis/pp_vis_core.h b/src/pp/pp_vis/pp_vis_core.h index 6f9d2fac..1db428f6 100644 --- a/src/pp/pp_vis/pp_vis_core.h +++ b/src/pp/pp_vis/pp_vis_core.h @@ -1,153 +1,3 @@ - - - - - - - - - - - - - - -// FIXME: Remove this - - -// // Enum(ProfFamilyKind) -// // { -// // ProfFamilyKind_Unknown, -// // ProfFamilyKind_Async, -// // ProfFamilyKind_Vis, -// // ProfFamilyKind_Sim, - -// // ProfFamilyKind_COUNT -// // }; - - - - - -// // // Struct( - - - - - - - -#define ProfTrackSamplesCap Kibi(256) -#define MaxRegisteredProfTracks Kibi(32) - -#define NsFromProfStamp(stamp) (stamp & ~((u64)1 << 63)) - - - - - - - -Struct(ProfSample) -{ - // Bit 63: zone-end, Bits 62-0: timestamp - u64 stamp; -}; - -Struct(ProfTrack) -{ - u64 top_sample_seq; - ProfSample samples[ProfTrackSamplesCap]; -}; - -Struct(ProfCtx) -{ - TicketMutex register_track_tm; - - Atomic32 tracks_count; - ProfTrack *tracks[MaxRegisteredProfTracks]; -}; - -Struct(ProfThreadLocalCtx) -{ - u32 track_idx; - ProfTrack *thread_track; -}; - -extern ProfCtx Prof; -extern ThreadLocal ProfThreadLocalCtx Prof_tl; - - -Inline void PushProfZoneEx(b32 end_zone) -{ - ProfTrack *track = Prof_tl.thread_track; - if (!track) - { - Arena *perm = PermArena(); - { - track = PushStruct(perm, ProfTrack); - Prof_tl.thread_track = track; - } - LockTicketMutex(&Prof.register_track_tm); - { - Prof_tl.track_idx = Atomic32Fetch(&Prof.tracks_count); - Prof.tracks[Prof_tl.track_idx] = track; - Atomic32FetchAdd(&Prof.tracks_count, 1); - } - UnlockTicketMutex(&Prof.register_track_tm); - } - - u64 sample_seq = track->top_sample_seq; - ProfSample *sample = &track->samples[sample_seq % ProfTrackSamplesCap]; - { - // TODO: Wrap rdtsc for cpu-relative <-> program-relative timestamp conversion - // sample->stamp = __rdtsc() | ((u64)end_zone << 63); - sample->stamp = TimeNs() | ((u64)end_zone << 63); - } - track->top_sample_seq = sample_seq + 1; -} - - - - - - -#if PROFILING_ENABLED - #define ProfZoneDF(...) DeferFor(PushProfZoneEx(0), PushProfZoneEx(1)) -#else - #define ProfZoneDF(...) -#endif - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - //////////////////////////////////////////////////////////// //~ Command table @@ -398,6 +248,7 @@ Struct(V_Profiler) f32 drag_view_ns; f32 drag_cursor_px; + i64 cursor_ns; i64 ns_per_px; i64 view_ns; }; diff --git a/src/ui/ui_core.c b/src/ui/ui_core.c index ed4ac520..74e0e587 100644 --- a/src/ui/ui_core.c +++ b/src/ui/ui_core.c @@ -332,6 +332,7 @@ void UI_PushDefaults(void) 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_Opacity: { desc.style.Opacity = 1; } 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; @@ -495,6 +496,7 @@ UI_Key UI_BuildBoxEx(UI_Key semantic_key) 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.opacity = UI_Top(Opacity); n->cmd.box.border_size = UI_Top(BorderSize); n->cmd.box.font = UI_Top(Font); n->cmd.box.font_size = UI_Top(FontSize); @@ -533,352 +535,354 @@ void UI_SetRawTexture(UI_Key key, G_TextureRef tex, Rng2 uv) UI_Frame *UI_BeginFrame(UI_FrameFlag frame_flags) { - ////////////////////////////// - //- Init persistent state - - if (!UI.box_arena) + ProfZoneDF("Begin UI") { - UI.box_arena = AcquireArena(Gibi(64)); - UI.gpu_frame_arena = G_AcquireArena(); - // Init frames - for (u64 i = 0; i < countof(UI.frames); ++i) + ////////////////////////////// + //- Init persistent state + + if (!UI.box_arena) { - 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; - - ////////////////////////////// - //- Build feedback & input state - - frame->cursor_pos = prev_frame->cursor_pos; - frame->drag_cursor_pos = prev_frame->drag_cursor_pos; - frame->input = prev_frame->input; - frame->drag_input = prev_frame->drag_input; - if (prev_frame->boxes_pre != 0) - { - ControllerEventsArray controller_events = frame->window_frame.controller_events; - - //- Locate boxes - UI_Box *prev_top_active_box = UI_BoxFromKey(prev_frame->top_active_box); - UI_Box *top_active_box = prev_top_active_box; - UI_Box *top_hot_box = 0; - UI_Box *top_hovered_box = 0; - - //- 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) + UI.box_arena = AcquireArena(Gibi(64)); + UI.gpu_frame_arena = G_AcquireArena(); + // Init frames + for (u64 i = 0; i < countof(UI.frames); ++i) { - frame->cursor_pos = Vec2FromVec(cev->cursor_pos); + 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; } } - //- Locate hovered box - for (u64 pre_index = UI.boxes_count; pre_index-- > 0;) + ////////////////////////////// + //- Begin frame + + UI.cur_frame_tick += 1; + UI_Frame *prev_frame = UI_PrevFrame(); + UI_Frame *frame = UI_CurrentFrame(); + { - UI_Box *box = prev_frame->boxes_pre[pre_index]; - UI_DebugBreak(box, UI_DebugBreakFlag_CheckCursorHover); - //- Reset state + 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; + + ////////////////////////////// + //- Build feedback & input state + + frame->cursor_pos = prev_frame->cursor_pos; + frame->drag_cursor_pos = prev_frame->drag_cursor_pos; + frame->input = prev_frame->input; + frame->drag_input = prev_frame->drag_input; + if (prev_frame->boxes_pre != 0) + { + ControllerEventsArray controller_events = frame->window_frame.controller_events; + + //- Locate boxes + UI_Box *prev_top_active_box = UI_BoxFromKey(prev_frame->top_active_box); + UI_Box *top_active_box = prev_top_active_box; + UI_Box *top_hot_box = 0; + UI_Box *top_hovered_box = 0; + + //- Update cursor pos + for (u64 cev_index = 0; cev_index < controller_events.count; ++cev_index) { - box->mouse_hovered = 0; - box->mouse_captured = 0; - } - //- Test for hover - b32 is_cursor_in_box = 0; - { - // TODO: More efficient test. This logic is just copied from the renderer's SDF function for now. - Rng2 interactable_region = IntersectRng2(box->solved_scissor, box->screen_rect); - Vec2 p0 = interactable_region.p0; - Vec2 p1 = interactable_region.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) + ControllerEvent *cev = &controller_events.events[cev_index]; + if (cev->kind == ControllerEventKind_CursorMove) { - 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; - if (top_hovered_box == 0 && (box->desc.flags & UI_BoxFlag_CaptureMouse) && is_cursor_in_box) - { - top_hovered_box = box; + frame->cursor_pos = Vec2FromVec(cev->cursor_pos); } } - } - //- Reset input state - for (u32 button_idx = 0; button_idx < countof(frame->input.buttons); ++button_idx) - { - UI_ButtonState *btn = &frame->input.buttons[button_idx]; - b32 old_held = btn->held; - { - ZeroStruct(btn); - } - btn->held = old_held; - } - - //- 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 (IsClickButton(cev->button) || IsWheelButton(cev->button)) - { - mouse_downs += 1; - if (top_hovered_box && top_active_box == 0) - { - if (cev->button == Button_M1) - { - ++frame->input.buttons[Button_M1].downs; - frame->input.buttons[Button_M1].held = 1; - top_active_box = top_hovered_box; - } - else if (cev->button == Button_M2) - { - ++frame->input.buttons[Button_M2].downs; - frame->input.buttons[Button_M2].held = 1; - top_active_box = top_hovered_box; - } - else if (cev->button == Button_M3) - { - ++frame->input.buttons[Button_M3].downs; - frame->input.buttons[Button_M3].held = 1; - top_active_box = top_hovered_box; - } - else if (cev->button == Button_WheelUp) - { - ++frame->input.buttons[Button_WheelUp].downs; - ++frame->input.buttons[Button_WheelUp].ups; - ++frame->input.buttons[Button_WheelUp].presses; - } - else if (cev->button == Button_WheelDown) - { - ++frame->input.buttons[Button_WheelDown].downs; - ++frame->input.buttons[Button_WheelDown].ups; - ++frame->input.buttons[Button_WheelDown].presses; - } - cev->captures += 1; - } - } - } break; - - case ControllerEventKind_ButtonUp: - { - if (IsClickButton(cev->button)) - { - if (top_active_box) - { - if (cev->button == Button_M1) - { - if (top_active_box == top_hovered_box) - { - ++frame->input.buttons[Button_M1].presses; - } - ++frame->input.buttons[Button_M1].ups; - if (frame->input.buttons[Button_M1].held) - { - frame->input.buttons[Button_M1].held = 0; - top_active_box = 0; - } - } - else if (cev->button == Button_M2) - { - if (top_active_box == top_hovered_box) - { - ++frame->input.buttons[Button_M2].presses; - } - ++frame->input.buttons[Button_M2].ups; - if (frame->input.buttons[Button_M2].held) - { - frame->input.buttons[Button_M2].held = 0; - top_active_box = 0; - } - } - else if (cev->button == Button_M3) - { - if (top_active_box == top_hovered_box) - { - ++frame->input.buttons[Button_M3].presses; - } - ++frame->input.buttons[Button_M3].ups; - if (frame->input.buttons[Button_M3].held) - { - frame->input.buttons[Button_M3].held = 0; - top_active_box = 0; - } - } - } - } - else if (IsWheelButton(cev->button) && top_hovered_box) - { - cev->captures += 1; - } - } break; - - case ControllerEventKind_Quit: - { - SignalExit(0); - } break; - } - } - - //- Update mouse capture tree - if (top_hovered_box) - { - top_hovered_box->mouse_hovered = 1; - for (UI_Box *parent = top_hovered_box->parent; parent; parent = parent->parent) - { - if (parent->desc.flags & UI_BoxFlag_CaptureThroughChildren) - { - parent->mouse_hovered = 1; - } - } - } - if (top_active_box) - { - top_active_box->mouse_hovered = 1; - top_active_box->mouse_captured = 1; - for (UI_Box *parent = top_active_box->parent; parent; parent = parent->parent) - { - if (parent->desc.flags & UI_BoxFlag_CaptureThroughChildren) - { - parent->mouse_hovered = 1; - parent->mouse_captured = 1; - } - } - } - top_hot_box = top_active_box ? top_active_box : top_hovered_box; - - //- Update box feedback - { - f32 lower_target = TweakFloat("UI lower blend target", -0.05, -1, 0); - f32 upper_target = TweakFloat("UI upper blend target", 1.05, 1, 10); + //- Locate hovered box for (u64 pre_index = UI.boxes_count; pre_index-- > 0;) { UI_Box *box = prev_frame->boxes_pre[pre_index]; - UI_Feedback *feedback = &box->feedback; - UI_DebugBreak(box, UI_DebugBreakFlag_BuildFeedback); - - feedback->active_absolute = box->mouse_captured; - feedback->hot_absolute = feedback->active_absolute || (top_active_box == 0 && box->mouse_hovered); - feedback->exists_absolute = (box->last_build_tick >= (frame->tick - 1)); - - f32 target_active = feedback->active_absolute ? Inf : lower_target; - f32 target_hot = feedback->hot_absolute ? Inf : lower_target; - f32 target_exists = feedback->exists_absolute ? upper_target : lower_target; - f32 target_misc = box->desc.misc; - - // TODO: Configurable per-box blend rates - f32 active_blend_rate = (15 * frame->dt); - f32 hot_blend_rate = (15 * frame->dt); - f32 exists_blend_rate = (30 * frame->dt); - // f64 misc_blend_rate = (30 * frame->dt); - f64 misc_blend_rate = 1; - - feedback->active_smooth = SaturateF32(LerpF32(feedback->active_smooth, target_active, active_blend_rate)); - feedback->hot_smooth = SaturateF32(LerpF32(feedback->hot_smooth, target_hot, hot_blend_rate)); - feedback->exists_smooth = SaturateF32(LerpF32(feedback->exists_smooth, target_exists, exists_blend_rate)); - feedback->misc_smooth = SaturateF32(LerpF32(feedback->misc_smooth, target_misc, misc_blend_rate)); - - feedback->screen_rect = box->screen_rect; - feedback->screen_anchor = box->screen_anchor; - - if (mouse_downs > 0) + UI_DebugBreak(box, UI_DebugBreakFlag_CheckCursorHover); + //- Reset state { - box->drag_feedback = *feedback; + box->mouse_hovered = 0; + box->mouse_captured = 0; + } + //- Test for hover + b32 is_cursor_in_box = 0; + { + // TODO: More efficient test. This logic is just copied from the renderer's SDF function for now. + Rng2 interactable_region = IntersectRng2(box->solved_scissor, box->screen_rect); + Vec2 p0 = interactable_region.p0; + Vec2 p1 = interactable_region.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; + if (top_hovered_box == 0 && (box->desc.flags & UI_BoxFlag_CaptureMouse) && is_cursor_in_box) + { + top_hovered_box = box; + } } } + + //- Reset input state + for (u32 button_idx = 0; button_idx < countof(frame->input.buttons); ++button_idx) + { + UI_ButtonState *btn = &frame->input.buttons[button_idx]; + b32 old_held = btn->held; + { + ZeroStruct(btn); + } + btn->held = old_held; + } + + //- 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 (IsClickButton(cev->button) || IsWheelButton(cev->button)) + { + mouse_downs += 1; + if (top_hovered_box && top_active_box == 0) + { + if (cev->button == Button_M1) + { + ++frame->input.buttons[Button_M1].downs; + frame->input.buttons[Button_M1].held = 1; + top_active_box = top_hovered_box; + } + else if (cev->button == Button_M2) + { + ++frame->input.buttons[Button_M2].downs; + frame->input.buttons[Button_M2].held = 1; + top_active_box = top_hovered_box; + } + else if (cev->button == Button_M3) + { + ++frame->input.buttons[Button_M3].downs; + frame->input.buttons[Button_M3].held = 1; + top_active_box = top_hovered_box; + } + else if (cev->button == Button_WheelUp) + { + ++frame->input.buttons[Button_WheelUp].downs; + ++frame->input.buttons[Button_WheelUp].ups; + ++frame->input.buttons[Button_WheelUp].presses; + } + else if (cev->button == Button_WheelDown) + { + ++frame->input.buttons[Button_WheelDown].downs; + ++frame->input.buttons[Button_WheelDown].ups; + ++frame->input.buttons[Button_WheelDown].presses; + } + cev->captures += 1; + } + } + } break; + + case ControllerEventKind_ButtonUp: + { + if (IsClickButton(cev->button)) + { + if (top_active_box) + { + if (cev->button == Button_M1) + { + if (top_active_box == top_hovered_box) + { + ++frame->input.buttons[Button_M1].presses; + } + ++frame->input.buttons[Button_M1].ups; + if (frame->input.buttons[Button_M1].held) + { + frame->input.buttons[Button_M1].held = 0; + top_active_box = 0; + } + } + else if (cev->button == Button_M2) + { + if (top_active_box == top_hovered_box) + { + ++frame->input.buttons[Button_M2].presses; + } + ++frame->input.buttons[Button_M2].ups; + if (frame->input.buttons[Button_M2].held) + { + frame->input.buttons[Button_M2].held = 0; + top_active_box = 0; + } + } + else if (cev->button == Button_M3) + { + if (top_active_box == top_hovered_box) + { + ++frame->input.buttons[Button_M3].presses; + } + ++frame->input.buttons[Button_M3].ups; + if (frame->input.buttons[Button_M3].held) + { + frame->input.buttons[Button_M3].held = 0; + top_active_box = 0; + } + } + } + } + else if (IsWheelButton(cev->button) && top_hovered_box) + { + cev->captures += 1; + } + } break; + + case ControllerEventKind_Quit: + { + SignalExit(0); + } break; + } + } + + //- Update mouse capture tree + if (top_hovered_box) + { + top_hovered_box->mouse_hovered = 1; + for (UI_Box *parent = top_hovered_box->parent; parent; parent = parent->parent) + { + if (parent->desc.flags & UI_BoxFlag_CaptureThroughChildren) + { + parent->mouse_hovered = 1; + } + } + } + if (top_active_box) + { + top_active_box->mouse_hovered = 1; + top_active_box->mouse_captured = 1; + for (UI_Box *parent = top_active_box->parent; parent; parent = parent->parent) + { + if (parent->desc.flags & UI_BoxFlag_CaptureThroughChildren) + { + parent->mouse_hovered = 1; + parent->mouse_captured = 1; + } + } + } + top_hot_box = top_active_box ? top_active_box : top_hovered_box; + + //- Update box feedback + { + 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 = UI.boxes_count; pre_index-- > 0;) + { + UI_Box *box = prev_frame->boxes_pre[pre_index]; + UI_Feedback *feedback = &box->feedback; + UI_DebugBreak(box, UI_DebugBreakFlag_BuildFeedback); + + feedback->active_absolute = box->mouse_captured; + feedback->hot_absolute = feedback->active_absolute || (top_active_box == 0 && box->mouse_hovered); + feedback->exists_absolute = (box->last_build_tick >= (frame->tick - 1)); + + f32 target_active = feedback->active_absolute ? Inf : lower_target; + f32 target_hot = feedback->hot_absolute ? Inf : lower_target; + f32 target_exists = feedback->exists_absolute ? upper_target : lower_target; + f32 target_misc = box->desc.misc; + + // TODO: Configurable per-box blend rates + f32 active_blend_rate = (15 * frame->dt); + f32 hot_blend_rate = (15 * frame->dt); + f32 exists_blend_rate = (30 * frame->dt); + // f64 misc_blend_rate = (30 * frame->dt); + f64 misc_blend_rate = 1; + + feedback->active_smooth = SaturateF32(LerpF32(feedback->active_smooth, target_active, active_blend_rate)); + feedback->hot_smooth = SaturateF32(LerpF32(feedback->hot_smooth, target_hot, hot_blend_rate)); + feedback->exists_smooth = SaturateF32(LerpF32(feedback->exists_smooth, target_exists, exists_blend_rate)); + feedback->misc_smooth = SaturateF32(LerpF32(feedback->misc_smooth, target_misc, misc_blend_rate)); + + feedback->screen_rect = box->screen_rect; + feedback->screen_anchor = box->screen_anchor; + + if (mouse_downs > 0) + { + box->drag_feedback = *feedback; + } + } + } + + if (mouse_downs > 0) + { + frame->drag_cursor_pos = frame->cursor_pos; + } + + frame->top_active_box = top_active_box ? top_active_box->key : UI_NilKey; + frame->top_hot_box = top_hot_box ? top_hot_box->key : UI_NilKey; + frame->top_hovered_box = top_hovered_box ? top_hovered_box->key : UI_NilKey; } - if (mouse_downs > 0) + ////////////////////////////// + //- Build root box + { - frame->drag_cursor_pos = frame->cursor_pos; + 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); } - - frame->top_active_box = top_active_box ? top_active_box->key : UI_NilKey; - frame->top_hot_box = top_hot_box ? top_hot_box->key : UI_NilKey; - frame->top_hovered_box = top_hovered_box ? top_hovered_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; + return UI_CurrentFrame(); } //////////////////////////////////////////////////////////// @@ -1134,938 +1138,954 @@ GC_Run UI_ScaleRun(Arena *arena, GC_Run unscaled_run, Vec2 scale) 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) + ProfZoneDF("End UI") { - 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; - } - } - } - } + UI_BoxIter box_iter = Zi; - ////////////////////////////// - //- Update boxes from cmds + 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)); - 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) + 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) { - case UI_CmdKind_BuildBox: + UI_Cmd cmd = cmd_node->cmd; + if (cmd.kind == UI_CmdKind_BuildBox) { UI_Key key = cmd.box.key; - if (UI_MatchKey(key, UI_NilKey)) + UI_Box *box = 0; { - 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) + UI_BoxBin *bin = &UI.box_bins[key.v % countof(UI.box_bins)]; + for (box = bin->first; box; box = box->next_in_bin) { - 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) - { - UI_DebugBreak(box, UI_DebugBreakFlag_PrepLayout); - - 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]; - UI_DebugBreak(box, UI_DebugBreakFlag_IndependentSolve); - 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]; - UI_DebugBreak(box, UI_DebugBreakFlag_UpwardsDependentSolveLayoutAxis); - 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]; - UI_DebugBreak(box, UI_DebugBreakFlag_DownwardsDependentSolve); - 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) + if (UI_MatchKey(box->key, key)) { - accum += child_size; + 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 { - accum = MaxF32(child_size, accum); + 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; } } - 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]; - UI_DebugBreak(box, UI_DebugBreakFlag_UpwardsDependentSolveNonLayoutAxis); - if (box->parent) + ////////////////////////////// + //- Update boxes from cmds + + for (UI_CmdNode *cmd_node = frame->first_cmd_node; cmd_node; cmd_node = cmd_node->next) { - Axis axis = !box->parent->desc.child_layout_axis; - UI_Size size = box->desc.pref_semantic_dims[axis]; - if (size.kind == UI_SizeKind_Grow) + UI_Cmd cmd = cmd_node->cmd; + switch (cmd.kind) { - box->solved_dims.v[axis] = box->parent->solved_dims.v[axis] * size.v; + 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; } } - } - // Solve violations - for (u64 pre_index = 0; pre_index < boxes_count; ++pre_index) - { - UI_Box *box = boxes_pre[pre_index]; - UI_DebugBreak(box, UI_DebugBreakFlag_SolveViolations); - for (Axis axis = 0; axis < Axis_COUNTXY; ++axis) + ////////////////////////////// + //- Prune cached boxes + { - f32 box_size = box->solved_dims.v[axis]; + 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)) { - // Accumulate non-floating sizes - f32 unconstrained_size_accum = 0; - f32 flex_accum = 0; - f32 violation = 0; + 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) { - b32 is_floating = AnyBit(child->desc.flags, UI_BoxFlag_Floating); - if (!is_floating) + 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) + { + UI_DebugBreak(box, UI_DebugBreakFlag_PrepLayout); + + 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 & opacity + { + UI_Box *parent = box->parent; + box->solved_opacity = box->desc.opacity; + box->solved_scale = box->desc.scale; + if (parent) { - f32 size = child->solved_dims.v[axis]; - f32 strictness = child->desc.pref_semantic_dims[axis].strictness; - f32 flex = size * (1.0 - strictness); + box->solved_opacity = parent->solved_opacity * box->solved_opacity; + 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]; + UI_DebugBreak(box, UI_DebugBreakFlag_IndependentSolve); + 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]; + UI_DebugBreak(box, UI_DebugBreakFlag_UpwardsDependentSolveLayoutAxis); + 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]; + UI_DebugBreak(box, UI_DebugBreakFlag_DownwardsDependentSolve); + 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) { - unconstrained_size_accum += size; - flex_accum += flex; + accum += child_size; } else { - unconstrained_size_accum = MaxF32(unconstrained_size_accum, size); - flex_accum = MaxF32(flex_accum, flex); + accum = MaxF32(child_size, accum); } } } - unconstrained_size_accum = FloorF32(unconstrained_size_accum); - violation = unconstrained_size_accum - box_size; + 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]; + UI_DebugBreak(box, UI_DebugBreakFlag_UpwardsDependentSolveNonLayoutAxis); + 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 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) - { - b32 should_clamp = ( - !(axis == Axis_X && AnyBit(child->desc.flags, UI_BoxFlag_NoFloatingClampX)) && - !(axis == Axis_Y && AnyBit(child->desc.flags, UI_BoxFlag_NoFloatingClampY)) - ); - if (should_clamp) - { - 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; + box->solved_dims.v[axis] = box->parent->solved_dims.v[axis] * size.v; } } } - } - // Solve final positions - for (u64 pre_index = 0; pre_index < boxes_count; ++pre_index) - { - UI_Box *box = boxes_pre[pre_index]; - UI_DebugBreak(box, UI_DebugBreakFlag_FinalSolve); - - 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) + // Solve violations + for (u64 pre_index = 0; pre_index < boxes_count; ++pre_index) { - 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); + UI_Box *box = boxes_pre[pre_index]; + UI_DebugBreak(box, UI_DebugBreakFlag_SolveViolations); for (Axis axis = 0; axis < Axis_COUNTXY; ++axis) { - UI_AxisRegion anchor = anchor_region.v[axis]; - switch (anchor) + 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) + { + b32 should_clamp = ( + !(axis == Axis_X && AnyBit(child->desc.flags, UI_BoxFlag_DontClampFloatingX)) && + !(axis == Axis_Y && AnyBit(child->desc.flags, UI_BoxFlag_DontClampFloatingY)) + ); + if (should_clamp) + { + 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_DebugBreak(box, UI_DebugBreakFlag_FinalSolve); + + 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: { - anchor_offset.v[axis] = box->solved_dims.v[axis] * 0.5; + box->cursor = box_size / 2 - size_accum / 2; } break; case UI_AxisRegion_End: { - anchor_offset.v[axis] = box->solved_dims.v[axis]; + box->cursor = box_size - size_accum; } break; } + // box->cursor = FloorF32(box->cursor); } - } - // 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) + // Solve screen rect { - default: break; - case UI_AxisRegion_Center: + // Compute offset + Vec2 offset = Zi; { - 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); - } + // 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); + } + } - // Solve screen rect - { - // Compute offset - Vec2 offset = Zi; - { - // Floating box offset + // Compute rect + Vec2 screen_pos = parent ? AddVec2(parent->screen_rect.p0, offset) : VEC2(0, 0); 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) + Vec2 overshoot = Zi; + if (!AnyBit(box->desc.flags, UI_BoxFlag_DontClampFloatingX)) { - Axis axis = !layout_axis_in_parent; - UI_AxisRegion alignment = alignment_in_parent.v[axis]; - switch(alignment) + overshoot.x = MaxF32(0, (screen_pos.x + box->solved_dims.x) - parent->screen_rect.p1.x); + screen_pos.x = MaxF32(parent->screen_rect.p0.x, screen_pos.x - overshoot.x); + } + if (!AnyBit(box->desc.flags, UI_BoxFlag_DontClampFloatingY)) + { + overshoot.y = MaxF32((screen_pos.y + box->solved_dims.y) - parent->screen_rect.p1.y, 0); + 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 scissor + { + box->solved_scissor = Rng2Inf; + if (box->desc.flags & UI_BoxFlag_Scissor || !parent) + { + box->solved_scissor = box->screen_rect; + } + if (parent) + { + box->solved_scissor = IntersectRng2(box->solved_scissor, parent->solved_scissor); + } + } + + // 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_DontClampFloating)) + if (parent) + { + 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 + + UI_GpuFrame gpu_frame = Zi; + ProfZoneDF("Render UI") + { + ////////////////////////////// + //- Build render data + + // Build GPU rect data + for (u64 pre_index = 0; pre_index < boxes_count; ++pre_index) + { + UI_Box *box = boxes_pre[pre_index]; + UI_DebugBreak(box, UI_DebugBreakFlag_BuildGpuData); + + GC_Run raw_run_unscaled = box->glyph_run; + GC_Run raw_run = UI_ScaleRun(frame->arena, raw_run_unscaled, box->solved_scale); + + Vec4 final_tint_lin = LinearFromSrgb(box->desc.tint); + final_tint_lin.a *= box->solved_opacity; + + Vec4 final_text_color_lin = LinearFromSrgb(box->desc.text_color); + final_text_color_lin.a *= box->desc.opacity; + final_text_color_lin = MulVec4Vec4(final_text_color_lin, final_tint_lin); + + f32 alpha_cull_threshold = 0.0025; + + // TODO: Check that sprite image is not empty + b32 should_upload_box = 1; + b32 should_upload_text = 1; + should_upload_box = should_upload_box && (final_tint_lin.a >= alpha_cull_threshold); + should_upload_box = should_upload_box && (box->screen_rect.p1.x - box->screen_rect.p0.x > 0.0025); + should_upload_box = should_upload_box && (box->screen_rect.p1.y - box->screen_rect.p0.y > 0.0025); + should_upload_box = should_upload_box && ( + !G_IsRefNil(box->raw_texture) || + !SPR_IsSheetKeyNil(box->desc.sprite_sheet) || + (AnyBit(box->desc.flags, UI_BoxFlag_DrawText) && raw_run.ready) || + final_tint_lin.a * box->desc.background_color.a >= alpha_cull_threshold || + final_tint_lin.a * box->desc.border_color.a >= alpha_cull_threshold + ); + should_upload_box = should_upload_box && (!IsRng2Empty(IntersectRng2(box->screen_rect, box->solved_scissor))); + should_upload_text = should_upload_text && should_upload_box && final_text_color_lin.a > alpha_cull_threshold; + should_upload_text = should_upload_text && AnyBit(box->desc.flags, UI_BoxFlag_DrawText) && raw_run.ready; + + if (should_upload_box || AnyBit(frame->frame_flags, UI_FrameFlag_Debug)) + { + Vec4 debug_lin = should_upload_box ? LinearFromSrgb(box->desc.debug_color) : LinearFromSrgb(box->desc.invisible_debug_color); + 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->scissor = box->solved_scissor; + rect->background_lin = LinearFromSrgb(box->desc.background_color); + rect->border_lin = LinearFromSrgb(box->desc.border_color); + rect->debug_lin = debug_lin; + rect->tint_lin = final_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)) { - default: break; - case UI_AxisRegion_Center: + 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 (should_upload_text) + { + 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_DontTruncateText); + + // 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) { - 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; + 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: { - f32 parent_size = parent->solved_dims.v[axis]; - f32 box_size = box->solved_dims.v[axis]; - offset.v[axis] = parent_size - box_size; + 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; } - } - } - 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) - { - Vec2 overshoot = Zi; - if (!AnyBit(box->desc.flags, UI_BoxFlag_NoFloatingClampX)) - { - overshoot.x = MaxF32(0, (screen_pos.x + box->solved_dims.x) - parent->screen_rect.p1.x); - screen_pos.x = MaxF32(parent->screen_rect.p0.x, screen_pos.x - overshoot.x); - } - if (!AnyBit(box->desc.flags, UI_BoxFlag_NoFloatingClampY)) - { - overshoot.y = MaxF32((screen_pos.y + box->solved_dims.y) - parent->screen_rect.p1.y, 0); - 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 scissor - { - box->solved_scissor = Rng2Inf; - if (box->desc.flags & UI_BoxFlag_Scissor || !parent) - { - box->solved_scissor = box->screen_rect; - } - if (parent) - { - box->solved_scissor = IntersectRng2(box->solved_scissor, parent->solved_scissor); - } - } - - // 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)) - if (parent) - { - 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 - - UI_GpuFrame gpu_frame = Zi; - { - ////////////////////////////// - //- Build render data - - // Build GPU rect data - for (u64 pre_index = 0; pre_index < boxes_count; ++pre_index) - { - UI_Box *box = boxes_pre[pre_index]; - UI_DebugBreak(box, UI_DebugBreakFlag_BuildGpuData); - - GC_Run raw_run_unscaled = box->glyph_run; - GC_Run raw_run = UI_ScaleRun(frame->arena, raw_run_unscaled, box->solved_scale); - - // TODO: Check that sprite image is not empty - b32 should_upload = 1; - should_upload = should_upload && (box->desc.tint.w >= 0.0025); - should_upload = should_upload && (box->screen_rect.p1.x - box->screen_rect.p0.x > 0.0025); - should_upload = should_upload && (box->screen_rect.p1.y - box->screen_rect.p0.y > 0.0025); - should_upload = should_upload && ( - !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 - ); - should_upload = should_upload && (!IsRng2Empty(IntersectRng2(box->screen_rect, box->solved_scissor))); - - if (should_upload || AnyBit(frame->frame_flags, UI_FrameFlag_Debug)) - { - Vec4 debug_lin = should_upload ? 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->scissor = box->solved_scissor; - 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) + switch (y_alignment) { - GC_RunRect rr = raw_run.rects[rect_idx]; - if (rr.baseline_pos + rr.advance <= truncation_offset) + case UI_AxisRegion_Start: { - 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); - } + 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); + baseline.x = MaxF32(baseline.x, 0); + baseline.y = MaxF32(baseline.y, 0); - // Append elipses - for (u64 rect_idx = 0; rect_idx < elipses_run.rects_count; ++rect_idx) + // Push text rects + for (u64 rect_idx = 0; rect_idx < final_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) + GC_RunRect rr = final_rects[rect_idx]; + Vec2 glyph_dims = DimsFromRng2(rr.bounds); + if (glyph_dims.x != 0 || glyph_dims.y != 0) { - final_rects[final_rects_count] = rr; - final_rects_count += 1; - final_baseline_length = MaxF32(final_baseline_length, rr.baseline_pos + rr.advance); + UI_GpuRect *rect = PushStruct(frame->rects_arena, UI_GpuRect); + rect->debug_lin = debug_lin; + rect->tint_lin = final_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)); + rect->scissor = box->solved_scissor; } } } - 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)); - rect->scissor = box->solved_scissor; - } - } } } - } - ////////////////////////////// - //- Upload data to GPU + ////////////////////////////// + //- Upload data to GPU - G_ProfZoneDF(UI.cl, "UI upload") - { - // Target - gpu_frame.target_size = draw_size; - gpu_frame.target = G_PushTexture2D( - UI.cl, UI.gpu_frame_arena, - G_TextureLayout_Family, - G_Format_R16G16B16A16_Float, - gpu_frame.target_size, - .flags = G_MemoryFlag_AllowTextureDraw, - .name = Lit("UI draw target") - ); - - // Rects - gpu_frame.rects_count = ArenaCount(frame->rects_arena, UI_GpuRect); - gpu_frame.rects = G_PushStructsFromCpu( - UI.cl, UI.gpu_frame_arena, - ArenaFirst(frame->rects_arena, UI_GpuRect), gpu_frame.rects_count, - .name = Lit("UI rects") - ); - - // Frame - gpu_frame.sampler = G_BasicSamplerFromKind(G_BasicSamplerKind_PointClamp); - gpu_frame.cursor_pos = frame->cursor_pos; - gpu_frame.anti_aliasing = TweakFloat("UI anti-aliasing", 1, 0, 1); - G_BufferRef gpu_frame_buff = G_PushStructFromCpu( - UI.cl, UI.gpu_frame_arena, - &gpu_frame, - .name = Lit("UI gpu frame") - ); - - // Init constants - G_SetConstant(UI.cl, UI_GpuConst_Frame, gpu_frame_buff); - } - - // Sync - G_Sync(UI.cl); - - ////////////////////////////// - //- Dispatch shaders - - //- Clear pass - - G_ProfZoneDF(UI.cl, "UI clear") - { - G_ClearRenderTarget(UI.cl, gpu_frame.target, VEC4(0, 0, 0, 0), 0); - } - - //- Rects pass - - G_ProfZoneDF(UI.cl, "UI rects") - { - G_Draw( - UI.cl, - UI_DRectVS, UI_DRectPS, - gpu_frame.rects_count, G_QuadIndices(), - 1, &G_RT(gpu_frame.target, G_BlendMode_CompositePremultipliedAlpha), - draw_viewport, draw_scissor, - G_DrawMode_TriangleList - ); - } - - //- Wireframe rects pass - - if (AnyBit(frame->frame_flags, UI_FrameFlag_Debug)) - { - G_ProfZoneDF(UI.cl, "UI debug rects") + G_ProfZoneDF(UI.cl, "UI upload") + { + // Target + gpu_frame.target_size = draw_size; + gpu_frame.target = G_PushTexture2D( + UI.cl, UI.gpu_frame_arena, + G_TextureLayout_Family, + G_Format_R16G16B16A16_Float, + gpu_frame.target_size, + .flags = G_MemoryFlag_AllowTextureDraw, + .name = Lit("UI draw target") + ); + + // Rects + gpu_frame.rects_count = ArenaCount(frame->rects_arena, UI_GpuRect); + gpu_frame.rects = G_PushStructsFromCpu( + UI.cl, UI.gpu_frame_arena, + ArenaFirst(frame->rects_arena, UI_GpuRect), gpu_frame.rects_count, + .name = Lit("UI rects") + ); + + // Frame + gpu_frame.sampler = G_BasicSamplerFromKind(G_BasicSamplerKind_PointClamp); + gpu_frame.cursor_pos = frame->cursor_pos; + gpu_frame.anti_aliasing = TweakFloat("UI anti-aliasing", 1, 0, 1); + G_BufferRef gpu_frame_buff = G_PushStructFromCpu( + UI.cl, UI.gpu_frame_arena, + &gpu_frame, + .name = Lit("UI gpu frame") + ); + + // Init constants + G_SetConstant(UI.cl, UI_GpuConst_Frame, gpu_frame_buff); + } + + // Sync + G_Sync(UI.cl); + + ////////////////////////////// + //- Dispatch shaders + + //- Clear pass + + G_ProfZoneDF(UI.cl, "UI clear") + { + G_ClearRenderTarget(UI.cl, gpu_frame.target, VEC4(0, 0, 0, 0), 0); + } + + //- Rects pass + + G_ProfZoneDF(UI.cl, "UI rects") { - G_SetConstant(UI.cl, UI_GpuConst_DebugDraw, 1); G_Draw( UI.cl, UI_DRectVS, UI_DRectPS, gpu_frame.rects_count, G_QuadIndices(), 1, &G_RT(gpu_frame.target, G_BlendMode_CompositePremultipliedAlpha), draw_viewport, draw_scissor, - G_DrawMode_WireTriangleList + G_DrawMode_TriangleList ); - G_SetConstant(UI.cl, UI_GpuConst_DebugDraw, 0); + } + + //- Wireframe rects pass + + if (AnyBit(frame->frame_flags, UI_FrameFlag_Debug)) + { + G_ProfZoneDF(UI.cl, "UI debug rects") + { + G_SetConstant(UI.cl, UI_GpuConst_DebugDraw, 1); + G_Draw( + UI.cl, + UI_DRectVS, UI_DRectPS, + gpu_frame.rects_count, G_QuadIndices(), + 1, &G_RT(gpu_frame.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), gpu_frame.target, RNG2I32(VEC2I32(0, 0), gpu_frame.target_size), vsync); } - - ////////////////////////////// - //- End frame - - G_CommitCommandList(UI.cl); - WND_EndFrame(frame->window_frame, VEC2I32(0, 0), gpu_frame.target, RNG2I32(VEC2I32(0, 0), gpu_frame.target_size), vsync); - EndScratch(scratch); } diff --git a/src/ui/ui_core.h b/src/ui/ui_core.h index 79888e9a..ce1797e4 100644 --- a/src/ui/ui_core.h +++ b/src/ui/ui_core.h @@ -89,10 +89,10 @@ Enum(UI_BoxFlag) UI_BoxFlag_DrawText = (1 << 0), UI_BoxFlag_CaptureMouse = (1 << 1), UI_BoxFlag_CaptureThroughChildren = (1 << 2), - UI_BoxFlag_NoTextTruncation = (1 << 3), + UI_BoxFlag_DontTruncateText = (1 << 3), UI_BoxFlag_Floating = (1 << 4), - UI_BoxFlag_NoFloatingClampX = (1 << 5), - UI_BoxFlag_NoFloatingClampY = (1 << 6), + UI_BoxFlag_DontClampFloatingX = (1 << 5), + UI_BoxFlag_DontClampFloatingY = (1 << 6), UI_BoxFlag_Scissor = (1 << 7), }; @@ -136,6 +136,7 @@ Enum(UI_DebugBreakFlag) X(DebugColor, Vec4) \ X(InvisibleDebugColor, Vec4) \ X(Tint, Vec4) \ + X(Opacity, f32) \ X(BorderSize, f32) \ X(Anchor, UI_Region) \ X(FloatingPos, Vec2) \ @@ -271,6 +272,7 @@ Struct(UI_BoxDesc) Vec4 debug_color; Vec4 invisible_debug_color; Vec4 tint; + f32 opacity; f32 border_size; Vec2 scale; Vec4 text_color; @@ -344,6 +346,7 @@ Struct(UI_Box) u64 post_index; //- Layout data + f32 solved_opacity; Vec2 solved_scale; Rng2 solved_scissor; Vec2 final_children_size_accum; diff --git a/src/ui/ui_extras.c b/src/ui/ui_extras.c index 96c4abfa..06749916 100644 --- a/src/ui/ui_extras.c +++ b/src/ui/ui_extras.c @@ -21,6 +21,7 @@ UI_Key UI_BuildLabel(String text) f32 font_size = UI_Top(FontSize); Vec2 scale = UI_Top(Scale); Vec4 tint = UI_Top(Tint); + f32 opacity = UI_Top(Opacity); Vec4 text_color = UI_Top(TextColor); UI_Region alignment = UI_Top(ChildAlignment); @@ -31,6 +32,7 @@ UI_Key UI_BuildLabel(String text) UI_Push(Parent, parent); UI_Push(Scale, scale); UI_Push(Tint, tint); + UI_Push(Opacity, opacity); UI_Push(Font, font); UI_Push(FontSize, font_size); UI_Push(TextColor, text_color); @@ -100,12 +102,14 @@ UI_Key UI_BuildDivider(UI_Size size, Vec4 color, Axis axis) UI_Key parent = UI_Top(Parent); Vec2 scale = UI_Top(Scale); Vec4 tint = UI_Top(Tint); + f32 opacity = UI_Top(Opacity); UI_PushCp(UI_NilKey); { UI_PushDefaults(); UI_Push(Parent, parent); UI_Push(Scale, scale); UI_Push(Tint, tint); + UI_Push(Opacity, opacity); UI_Push(BackgroundColor, color); UI_Push(AxisSize, UI_GROW(1, 0), .axis = !axis); UI_Push(AxisSize, size, .axis = axis);