From 5559d628265176c36b6614e0ed92041d2e136f7d Mon Sep 17 00:00:00 2001 From: Ed_ Date: Wed, 1 Jan 2025 14:13:30 -0500 Subject: [PATCH] Got render_list proper setup for UI_State rendering... Still need a spacial indexing method to prevent redundant enqueues to the drawlist (esp for text) I'm in the process of trying to see if I can keep getting vefontcache even more performant... Making it multi-threaded can help but there might be some single-thread per still possible.. --- code/font/vefontcache/LRU.odin | 9 +- code/font/vefontcache/atlas.odin | 30 +- code/font/vefontcache/draw.odin | 417 ++++++++++++++----------- code/font/vefontcache/mappings.odin | 19 ++ code/font/vefontcache/misc.odin | 20 +- code/font/vefontcache/parser.odin | 15 +- code/font/vefontcache/shaped_text.odin | 114 ++++--- code/font/vefontcache/shaper.odin | 51 ++- code/font/vefontcache/vefontcache.odin | 75 +++-- code/grime/hashmap_chained.odin | 1 - code/grime/profiler.odin | 6 +- code/sectr/engine/client_api.odin | 4 +- code/sectr/engine/render.odin | 118 ++++--- code/sectr/font/provider.odin | 2 - code/sectr/ui/core/base.odin | 290 ++++++----------- code/sectr/ui/core/collision.odin | 29 ++ code/sectr/ui/core/layout_compute.odin | 12 +- code/sectr/ui/tests.odin | 1 + code/sectr/ui/widgets.odin | 6 +- scripts/build.ps1 | 4 +- 20 files changed, 659 insertions(+), 564 deletions(-) create mode 100644 code/sectr/ui/core/collision.odin diff --git a/code/font/vefontcache/LRU.odin b/code/font/vefontcache/LRU.odin index 3682ccf..0cf422b 100644 --- a/code/font/vefontcache/LRU.odin +++ b/code/font/vefontcache/LRU.odin @@ -139,7 +139,7 @@ pool_list_erase :: proc( pool : ^Pool_List, iter : Pool_ListIter ) } } -pool_list_move_to_front :: #force_inline proc( pool : ^Pool_List, iter : Pool_ListIter ) +pool_list_move_to_front :: #force_inline proc "contextless" ( pool : ^Pool_List, iter : Pool_ListIter ) { using pool @@ -156,7 +156,7 @@ pool_list_move_to_front :: #force_inline proc( pool : ^Pool_List, iter : Pool_Li front = iter } -pool_list_peek_back :: #force_inline proc ( pool : ^Pool_List ) -> Pool_ListValue { +pool_list_peek_back :: #force_inline proc ( pool : ^Pool_List ) -> Pool_ListValue #no_bounds_check { assert( pool.back != - 1 ) value := pool.items[ pool.back ].value return value @@ -217,7 +217,7 @@ lru_find :: #force_inline proc "contextless" ( cache : ^LRU_Cache, key : u64, mu return link, success } -lru_get :: #force_inline proc( cache: ^LRU_Cache, key : u64 ) -> i32 { +lru_get :: #force_inline proc ( cache: ^LRU_Cache, key : u64 ) -> i32 #no_bounds_check { if link, ok := &cache.table[ key ]; ok { pool_list_move_to_front(&cache.key_queue, link.ptr) return link.value @@ -233,7 +233,7 @@ lru_get_next_evicted :: #force_inline proc ( cache : ^LRU_Cache ) -> u64 { return 0xFFFFFFFFFFFFFFFF } -lru_peek :: #force_inline proc ( cache : ^LRU_Cache, key : u64, must_find := false ) -> i32 { +lru_peek :: #force_inline proc "contextless" ( cache : ^LRU_Cache, key : u64, must_find := false ) -> i32 { iter, success := lru_find( cache, key, must_find ) if success == false { return -1 @@ -243,6 +243,7 @@ lru_peek :: #force_inline proc ( cache : ^LRU_Cache, key : u64, must_find := fal lru_put :: #force_inline proc( cache : ^LRU_Cache, key : u64, value : i32 ) -> u64 { + profile(#procedure) if link, ok := & cache.table[ key ]; ok { pool_list_move_to_front( & cache.key_queue, link.ptr ) link.value = value diff --git a/code/font/vefontcache/atlas.odin b/code/font/vefontcache/atlas.odin index c9acd96..e63ca91 100644 --- a/code/font/vefontcache/atlas.odin +++ b/code/font/vefontcache/atlas.odin @@ -27,7 +27,7 @@ Atlas :: struct { width : i32, height : i32, - glyph_padding : i32, // Padding to add to bounds__scaled for choosing which atlas region. + glyph_padding : f32, // Padding to add to bounds__scaled for choosing which atlas region. glyph_over_scalar : f32, // Scalar to apply to bounds__scaled for choosing which atlas region. region_a : Atlas_Region, @@ -36,8 +36,9 @@ Atlas :: struct { region_d : Atlas_Region, } -atlas_bbox :: proc( atlas : ^Atlas, region : Atlas_Region_Kind, local_idx : i32 ) -> (position, size: Vec2) +atlas_bbox :: #force_inline proc "contextless" ( atlas : ^Atlas, region : Atlas_Region_Kind, local_idx : i32 ) -> (position, size: Vec2) { + profile(#procedure) switch region { case .A: @@ -85,22 +86,21 @@ atlas_bbox :: proc( atlas : ^Atlas, region : Atlas_Region_Kind, local_idx : i32 return } -decide_codepoint_region :: proc(ctx : ^Context, entry : ^Entry, glyph_index : Glyph ) -> (region_kind : Atlas_Region_Kind, region : ^Atlas_Region, over_sample : Vec2) +decide_codepoint_region :: #force_inline proc (ctx : ^Context, entry : ^Entry, glyph_index : Glyph ) -> (region_kind : Atlas_Region_Kind, region : ^Atlas_Region, over_sample : Vec2) { + profile(#procedure) if parser_is_glyph_empty(&entry.parser_info, glyph_index) { return .None, nil, {} } bounds_0, bounds_1 := parser_get_glyph_box(&entry.parser_info, glyph_index) - bounds_width := f32(bounds_1.x - bounds_0.x) - bounds_height := f32(bounds_1.y - bounds_0.y) + bounds_size := vec2(bounds_1) - vec2(bounds_0) - atlas := & ctx.atlas - glyph_buffer := & ctx.glyph_buffer - glyph_padding := f32( atlas.glyph_padding ) * 2 + atlas := & ctx.atlas + glyph_buffer := & ctx.glyph_buffer + glyph_padding_dbl := atlas.glyph_padding * 2 - bounds_width_scaled := i32(bounds_width * entry.size_scale * atlas.glyph_over_scalar + glyph_padding) - bounds_height_scaled := i32(bounds_height * entry.size_scale * atlas.glyph_over_scalar + glyph_padding) + bounds_size_scaled := bounds_size * entry.size_scale * atlas.glyph_over_scalar + glyph_padding_dbl // Use a lookup table for faster region selection region_lookup := [4]struct { kind: Atlas_Region_Kind, region: ^Atlas_Region } { @@ -110,15 +110,15 @@ decide_codepoint_region :: proc(ctx : ^Context, entry : ^Entry, glyph_index : Gl { .D, & atlas.region_d }, } - for region in region_lookup do if bounds_width_scaled <= region.region.width && bounds_height_scaled <= region.region.height { + for region in region_lookup do if bounds_size_scaled.x <= f32(region.region.width) && bounds_size_scaled.y <= f32(region.region.height) { return region.kind, region.region, glyph_buffer.over_sample } - if bounds_width_scaled <= glyph_buffer.width \ - && bounds_height_scaled <= glyph_buffer.height { + if bounds_size_scaled.x <= f32(glyph_buffer.width) \ + && bounds_size_scaled.y <= f32(glyph_buffer.height) { over_sample = \ - bounds_width_scaled <= glyph_buffer.width / 2 && - bounds_height_scaled <= glyph_buffer.height / 2 ? \ + bounds_size_scaled.x <= f32(glyph_buffer.width / 2) && + bounds_size_scaled.y <= f32(glyph_buffer.height / 2) ? \ {2.0, 2.0} \ : {1.0, 1.0} return .E, nil, over_sample diff --git a/code/font/vefontcache/draw.odin b/code/font/vefontcache/draw.odin index 4db32fc..919e2e0 100644 --- a/code/font/vefontcache/draw.odin +++ b/code/font/vefontcache/draw.odin @@ -53,9 +53,9 @@ Glyph_Draw_Buffer :: struct { draw_list : Draw_List, } -blit_quad :: proc( draw_list : ^Draw_List, p0 : Vec2 = {0, 0}, p1 : Vec2 = {1, 1}, uv0 : Vec2 = {0, 0}, uv1 : Vec2 = {1, 1} ) +blit_quad :: #force_inline proc ( draw_list : ^Draw_List, p0 : Vec2 = {0, 0}, p1 : Vec2 = {1, 1}, uv0 : Vec2 = {0, 0}, uv1 : Vec2 = {1, 1} ) { - // profile(#procedure) + profile(#procedure) // logf("Blitting: xy0: %0.2f, %0.2f xy1: %0.2f, %0.2f uv0: %0.2f, %0.2f uv1: %0.2f, %0.2f", // p0.x, p0.y, p1.x, p1.y, uv0.x, uv0.y, uv1.x, uv1.y); v_offset := cast(u32) len(draw_list.vertices) @@ -89,173 +89,175 @@ blit_quad :: proc( draw_list : ^Draw_List, p0 : Vec2 = {0, 0}, p1 : Vec2 = {1, 1 } // TODO(Ed): glyph caching cannot be handled in a 'font parser' abstraction. Just going to have explicit procedures to grab info neatly... -cache_glyph_freetype :: proc(ctx: ^Context, font: Font_ID, glyph_index: Glyph, entry: ^Entry, bounds_0, bounds_1: Vec2, scale, translate: Vec2) -> b32 -{ - draw_filled_path_freetype :: proc( draw_list : ^Draw_List, outside_point : Vec2, path : []Vertex, - scale := Vec2 { 1, 1 }, - translate := Vec2 { 0, 0 }, - debug_print_verbose : b32 = false - ) - { - if debug_print_verbose { - log("outline_path:") - for point in path { - vec := point.pos * scale + translate - logf(" %0.2f %0.2f", vec.x, vec.y ) - } - } +// cache_glyph_freetype :: proc(ctx: ^Context, font: Font_ID, glyph_index: Glyph, entry: ^Entry, bounds_0, bounds_1: Vec2, scale, translate: Vec2) -> b32 +// { +// draw_filled_path_freetype :: proc( draw_list : ^Draw_List, outside_point : Vec2, path : []Vertex, +// scale := Vec2 { 1, 1 }, +// translate := Vec2 { 0, 0 }, +// debug_print_verbose : b32 = false +// ) +// { +// if debug_print_verbose { +// log("outline_path:") +// for point in path { +// vec := point.pos * scale + translate +// logf(" %0.2f %0.2f", vec.x, vec.y ) +// } +// } - v_offset := cast(u32) len(draw_list.vertices) - for point in path - { - transformed_point := Vertex { - pos = point.pos * scale + translate, - u = 0, - v = 0 - } - append( & draw_list.vertices, transformed_point ) - } +// v_offset := cast(u32) len(draw_list.vertices) +// for point in path +// { +// transformed_point := Vertex { +// pos = point.pos * scale + translate, +// u = 0, +// v = 0 +// } +// append( & draw_list.vertices, transformed_point ) +// } - if len(path) > 2 - { - indices := & draw_list.indices - for index : u32 = 1; index < cast(u32) len(path) - 1; index += 1 { - to_add := [3]u32 { - v_offset, - v_offset + index, - v_offset + index + 1 - } - append( indices, ..to_add[:] ) - } +// if len(path) > 2 +// { +// indices := & draw_list.indices +// for index : u32 = 1; index < cast(u32) len(path) - 1; index += 1 { +// to_add := [3]u32 { +// v_offset, +// v_offset + index, +// v_offset + index + 1 +// } +// append( indices, ..to_add[:] ) +// } - // Close the path by connecting the last vertex to the first two - to_add := [3]u32 { - v_offset, - v_offset + cast(u32)(len(path) - 1), - v_offset + 1 - } - append( indices, ..to_add[:] ) - } - } +// // Close the path by connecting the last vertex to the first two +// to_add := [3]u32 { +// v_offset, +// v_offset + cast(u32)(len(path) - 1), +// v_offset + 1 +// } +// append( indices, ..to_add[:] ) +// } +// } - if glyph_index == Glyph(0) { - return false - } +// if glyph_index == Glyph(0) { +// return false +// } - face := entry.parser_info.freetype_info - error := freetype.load_glyph(face, u32(glyph_index), {.No_Bitmap, .No_Scale}) - if error != .Ok { - return false - } +// face := entry.parser_info.freetype_info +// error := freetype.load_glyph(face, u32(glyph_index), {.No_Bitmap, .No_Scale}) +// if error != .Ok { +// return false +// } - glyph := face.glyph - if glyph.format != .Outline { - return false - } +// glyph := face.glyph +// if glyph.format != .Outline { +// return false +// } - outline := &glyph.outline - if outline.n_points == 0 { - return false - } +// outline := &glyph.outline +// if outline.n_points == 0 { +// return false +// } - draw := Draw_Call_Default - draw.pass = Frame_Buffer_Pass.Glyph - draw.start_index = cast(u32) len(ctx.draw_list.indices) +// draw := Draw_Call_Default +// draw.pass = Frame_Buffer_Pass.Glyph +// draw.start_index = cast(u32) len(ctx.draw_list.indices) - contours := slice.from_ptr(cast( [^]i16) outline.contours, int(outline.n_contours)) - points := slice.from_ptr(cast( [^]freetype.Vector) outline.points, int(outline.n_points)) - tags := slice.from_ptr(cast( [^]u8) outline.tags, int(outline.n_points)) +// contours := slice.from_ptr(cast( [^]i16) outline.contours, int(outline.n_contours)) +// points := slice.from_ptr(cast( [^]freetype.Vector) outline.points, int(outline.n_points)) +// tags := slice.from_ptr(cast( [^]u8) outline.tags, int(outline.n_points)) - path := &ctx.temp_path - clear(path) +// path := &ctx.temp_path +// clear(path) - outside := Vec2{ bounds_0.x - 21, bounds_0.y - 33 } +// outside := Vec2{ bounds_0.x - 21, bounds_0.y - 33 } - start_index: int = 0 - for contour_index in 0 ..< int(outline.n_contours) - { - end_index := int(contours[contour_index]) + 1 - prev_point : Vec2 - first_point : Vec2 +// start_index: int = 0 +// for contour_index in 0 ..< int(outline.n_contours) +// { +// end_index := int(contours[contour_index]) + 1 +// prev_point : Vec2 +// first_point : Vec2 - for idx := start_index; idx < end_index; idx += 1 - { - current_pos := Vec2 { f32( points[idx].x ), f32( points[idx].y ) } - if ( tags[idx] & 1 ) == 0 - { - // If current point is off-curve - if (idx == start_index || (tags[ idx - 1 ] & 1) != 0) - { - // current is the first or following an on-curve point - prev_point = current_pos - } - else - { - // current and previous are off-curve, calculate midpoint - midpoint := (prev_point + current_pos) * 0.5 - append( path, Vertex { pos = midpoint } ) // Add midpoint as on-curve point - if idx < end_index - 1 - { - // perform interp from prev_point to current_pos via midpoint - step := 1.0 / entry.curve_quality - for alpha : f32 = 0.0; alpha <= 1.0; alpha += step - { - bezier_point := eval_point_on_bezier3( prev_point, midpoint, current_pos, alpha ) - append( path, Vertex{ pos = bezier_point } ) - } - } +// for idx := start_index; idx < end_index; idx += 1 +// { +// current_pos := Vec2 { f32( points[idx].x ), f32( points[idx].y ) } +// if ( tags[idx] & 1 ) == 0 +// { +// // If current point is off-curve +// if (idx == start_index || (tags[ idx - 1 ] & 1) != 0) +// { +// // current is the first or following an on-curve point +// prev_point = current_pos +// } +// else +// { +// // current and previous are off-curve, calculate midpoint +// midpoint := (prev_point + current_pos) * 0.5 +// append( path, Vertex { pos = midpoint } ) // Add midpoint as on-curve point +// if idx < end_index - 1 +// { +// // perform interp from prev_point to current_pos via midpoint +// step := 1.0 / entry.curve_quality +// for alpha : f32 = 0.0; alpha <= 1.0; alpha += step +// { +// bezier_point := eval_point_on_bezier3( prev_point, midpoint, current_pos, alpha ) +// append( path, Vertex{ pos = bezier_point } ) +// } +// } - prev_point = current_pos - } - } - else - { - if idx == start_index { - first_point = current_pos - } - if prev_point != (Vec2{}) { - // there was an off-curve point before this - append(path, Vertex{ pos = prev_point}) // Ensure previous off-curve is handled - } - append(path, Vertex{ pos = current_pos}) - prev_point = {} - } - } +// prev_point = current_pos +// } +// } +// else +// { +// if idx == start_index { +// first_point = current_pos +// } +// if prev_point != (Vec2{}) { +// // there was an off-curve point before this +// append(path, Vertex{ pos = prev_point}) // Ensure previous off-curve is handled +// } +// append(path, Vertex{ pos = current_pos}) +// prev_point = {} +// } +// } - // ensure the contour is closed - if path[0].pos != path[ len(path) - 1 ].pos { - append(path, Vertex{pos = path[0].pos}) - } - draw_filled_path(&ctx.draw_list, bounds_0, path[:], scale, translate, ctx.debug_print_verbose) - clear(path) - start_index = end_index - } +// // ensure the contour is closed +// if path[0].pos != path[ len(path) - 1 ].pos { +// append(path, Vertex{pos = path[0].pos}) +// } +// draw_filled_path(&ctx.draw_list, bounds_0, path[:], scale, translate) +// // draw_filled_path(&ctx.draw_list, bounds_0, path[:], scale, translate, ctx.debug_print_verbose) +// clear(path) +// start_index = end_index +// } - if len(path) > 0 { - draw_filled_path(&ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose) - } +// if len(path) > 0 { +// // draw_filled_path(&ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose) +// draw_filled_path(&ctx.draw_list, outside, path[:], scale, translate) +// } - draw.end_index = cast(u32) len(ctx.draw_list.indices) - if draw.end_index > draw.start_index { - append( & ctx.draw_list.calls, draw) - } +// draw.end_index = cast(u32) len(ctx.draw_list.indices) +// if draw.end_index > draw.start_index { +// append( & ctx.draw_list.calls, draw) +// } - return true -} +// return true +// } // TODO(Ed): Is it better to cache the glyph vertices for when it must be re-drawn (directly or two atlas)? cache_glyph :: proc(ctx : ^Context, font : Font_ID, glyph_index : Glyph, entry : ^Entry, bounds_0, bounds_1 : Vec2, scale, translate : Vec2) -> b32 { - // profile(#procedure) + profile(#procedure) if glyph_index == Glyph(0) { return false } // Glyph shape handling are not abstractable between freetype and stb_truetype - if entry.parser_info.kind == .Freetype { - result := cache_glyph_freetype( ctx, font, glyph_index, entry, bounds_0, bounds_1, scale, translate ) - return result - } + // if entry.parser_info.kind == .Freetype { + // result := cache_glyph_freetype( ctx, font, glyph_index, entry, bounds_0, bounds_1, scale, translate ) + // return result + // } shape, error := parser_get_glyph_shape(&entry.parser_info, glyph_index) assert(error == .None) @@ -277,7 +279,8 @@ cache_glyph :: proc(ctx : ^Context, font : Font_ID, glyph_index : Glyph, entry : { case .Move: if len(path) > 0 { - draw_filled_path(&ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose) + // draw_filled_path(&ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose) + draw_filled_path(&ctx.draw_list, outside, path[:], scale, translate) clear(path) } fallthrough @@ -310,7 +313,8 @@ cache_glyph :: proc(ctx : ^Context, font : Font_ID, glyph_index : Glyph, entry : } if len(path) > 0 { - draw_filled_path(&ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose) + // draw_filled_path(&ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose) + draw_filled_path(&ctx.draw_list, outside, path[:], scale, translate) } draw.end_index = u32(len(ctx.draw_list.indices)) @@ -337,7 +341,7 @@ cache_glyph_to_atlas :: proc( ctx : ^Context, region : ^Atlas_Region, over_sample : Vec2 ) { - // profile(#procedure) + profile(#procedure) // Get hb_font text metrics. These are unscaled! bounds_0, bounds_1 := parser_get_glyph_box( & entry.parser_info, glyph_index ) @@ -354,8 +358,8 @@ cache_glyph_to_atlas :: proc( ctx : ^Context, { if region.next_idx < region.state.capacity { - evicted := lru_put( & region.state, lru_code, i32(region.next_idx) ) - atlas_index = i32(region.next_idx) + evicted := lru_put( & region.state, lru_code, region.next_idx ) + atlas_index = region.next_idx region.next_idx += 1 assert( evicted == lru_code ) } @@ -460,20 +464,19 @@ check_glyph_in_atlas :: #force_inline proc( ctx : ^Context, font : Font_ID, entr over_sample : Vec2 ) -> b32 { - // profile(#procedure) + profile(#procedure) assert( glyph_index != -1 ) // E region can't batch - if region_kind == .E || region_kind == .None do return false - if ctx.temp_codepoint_seen_num > 1024 do return false - // TODO(Ed): Why 1024? + if region_kind == .E || region_kind == .None do return false + if ctx.temp_codepoint_seen_num > i32(cap(ctx.temp_codepoint_seen)) do return false if atlas_index == - 1 { if region.next_idx > region.state.capacity { // We will evict LRU. We must predict which LRU will get evicted, and if it's something we've seen then we need to take slowpath and flush batch. next_evict_codepoint := lru_get_next_evicted( & region.state ) - seen, success := ctx.temp_codepoint_seen[next_evict_codepoint] + seen, success := ctx.temp_codepoint_seen[next_evict_codepoint] assert(success != false) if (seen) { @@ -503,7 +506,7 @@ directly_draw_massive_glyph :: proc( ctx : ^Context, bounds_size : Vec2, over_sample, position, scale : Vec2 ) { - // profile(#procedure) + profile(#procedure) flush_glyph_buffer_to_atlas( ctx ) glyph_padding := f32(ctx.atlas.glyph_padding) @@ -567,22 +570,23 @@ directly_draw_massive_glyph :: proc( ctx : ^Context, // ve_fontcache_draw_filled_path draw_filled_path :: proc( draw_list : ^Draw_List, outside_point : Vec2, path : []Vertex, scale := Vec2 { 1, 1 }, - translate := Vec2 { 0, 0 }, - debug_print_verbose : b32 = false -) + translate := Vec2 { 0, 0 } + // debug_print_verbose : b32 = false +) #no_bounds_check { - if debug_print_verbose - { - log("outline_path:") - for point in path { - vec := point.pos * scale + translate - logf(" %0.2f %0.2f", vec.x, vec.y ) - } - } + profile(#procedure) + // if debug_print_verbose + // { + // log("outline_path:") + // for point in path { + // vec := point.pos * scale + translate + // logf(" %0.2f %0.2f", vec.x, vec.y ) + // } + // } v_offset := cast(u32) len(draw_list.vertices) for point in path { - point := point + point := point point.pos = point.pos * scale + translate append( & draw_list.vertices, point ) } @@ -613,14 +617,16 @@ draw_text_batch :: proc(ctx: ^Context, entry: ^Entry, shaped: ^Shaped_Text, position, scale : Vec2, snap_width, snap_height : f32 ) { + profile(#procedure) flush_glyph_buffer_to_atlas(ctx) atlas := & ctx.atlas atlas_size := Vec2{ f32(atlas.width), f32(atlas.height) } - glyph_padding := f32(atlas.glyph_padding) + glyph_padding := atlas.glyph_padding for index := batch_start_idx; index < batch_end_idx; index += 1 { + profile("glyph") glyph_index := shaped.glyphs[index] if glyph_index == 0 || parser_is_glyph_empty( & entry.parser_info, glyph_index) do continue @@ -645,39 +651,40 @@ draw_text_batch :: proc(ctx: ^Context, entry: ^Entry, shaped: ^Shaped_Text, } else if atlas_index != -1 { + profile("derive manual") + call := Draw_Call_Default + call.pass = .Target + call.colour = ctx.colour + call.start_index = u32(len(ctx.draw_list.indices)) + // Draw cached glyph slot_position, _ := atlas_bbox( atlas, region_kind, atlas_index ) glyph_scale := bounds_size * entry.size_scale + glyph_padding bounds_0_scaled := ceil(vbounds_0 * entry.size_scale - 0.5 ) dst := glyph_translate + bounds_0_scaled * scale dst_scale := glyph_scale * scale + textspace_x_form( & slot_position, & glyph_scale, atlas_size ) - - call := Draw_Call_Default - call.pass = .Target - call.colour = ctx.colour - call.start_index = u32(len(ctx.draw_list.indices)) - blit_quad(&ctx.draw_list, dst, dst + dst_scale, slot_position, slot_position + glyph_scale ) - call.end_index = u32(len(ctx.draw_list.indices)) + append(&ctx.draw_list.calls, call) } } } // Helper for draw_text, all raw text content should be confirmed to be either formatting or visible shapes before getting cached. -draw_text_shape :: proc( ctx : ^Context, +draw_text_shape :: #force_inline proc( ctx : ^Context, font : Font_ID, entry : ^Entry, shaped : ^Shaped_Text, position, scale : Vec2, snap_width, snap_height : f32 -) -> (cursor_pos : Vec2) +) -> (cursor_pos : Vec2) #no_bounds_check { - // profile(#procedure) + profile(#procedure) batch_start_idx : i32 = 0 for index : i32 = 0; index < cast(i32) len(shaped.glyphs); index += 1 { @@ -710,9 +717,51 @@ draw_text_shape :: proc( ctx : ^Context, return } -flush_glyph_buffer_to_atlas :: proc( ctx : ^Context ) +// Helper for draw_text_latin_mono +draw_text_mono_latin_batch :: #force_inline proc( ctx : ^Context, + font : Font_ID, + entry : ^Entry, + shaped : ^Shaped_Text, + position, scale : Vec2, + snap_width, snap_height : f32 +) -> (cursor_pos : Vec2) #no_bounds_check { - // profile(#procedure) + profile(#procedure) + batch_start_idx : i32 = 0 + for index : i32 = 0; index < cast(i32) len(shaped.glyphs); index += 1 + { + glyph_index := shaped.glyphs[ index ] + if is_empty( ctx, entry, glyph_index ) do continue + + region_kind, region, over_sample := decide_codepoint_region( ctx, entry, glyph_index ) + lru_code := font_glyph_lru_code(entry.id, glyph_index) + atlas_index := cast(i32) -1 + + if region_kind != .E do atlas_index = lru_get( & region.state, lru_code ) + if check_glyph_in_atlas( ctx, font, entry, glyph_index, lru_code, atlas_index, region_kind, region, over_sample ) do continue + + // We can no longer directly append the shape as it has missing glyphs in the atlas + + // First batch the other cached glyphs + // flush_glyph_buffer_to_atlas(ctx) + draw_text_batch( ctx, entry, shaped, batch_start_idx, index, position, scale, snap_width, snap_height ) + reset_batch_codepoint_state( ctx ) + + cache_glyph_to_atlas( ctx, font, glyph_index, lru_code, atlas_index, entry, region_kind, region, over_sample ) + mark_batch_codepoint_seen( ctx, lru_code) + batch_start_idx = index + } + + draw_text_batch( ctx, entry, shaped, batch_start_idx, cast(i32) len(shaped.glyphs), position, scale, snap_width , snap_height ) + reset_batch_codepoint_state( ctx ) + + cursor_pos = position + shaped.end_cursor_pos * scale + return +} + +flush_glyph_buffer_to_atlas :: #force_inline proc( ctx : ^Context ) +{ + profile(#procedure) // Flush Draw_Calls to draw list merge_draw_list( & ctx.draw_list, & ctx.glyph_buffer.clear_draw_list ) merge_draw_list( & ctx.draw_list, & ctx.glyph_buffer.draw_list) @@ -733,9 +782,9 @@ flush_glyph_buffer_to_atlas :: proc( ctx : ^Context ) } // ve_fontcache_merge_Draw_List -merge_draw_list :: proc( dst, src : ^Draw_List ) +merge_draw_list :: #force_inline proc ( #no_alias dst, src : ^Draw_List ) { - // profile(#procedure) + profile(#procedure) error : Allocator_Error v_offset := cast(u32) len( dst.vertices ) @@ -744,13 +793,13 @@ merge_draw_list :: proc( dst, src : ^Draw_List ) assert( error == .None ) i_offset := cast(u32) len(dst.indices) - for index : int = 0; index < len(src.indices); index += 1 { + for index : i32 = 0; index < cast(i32) len(src.indices); index += 1 { ignored : int ignored, error = append( & dst.indices, src.indices[index] + v_offset ) assert( error == .None ) } - for index : int = 0; index < len(src.calls); index += 1 { + for index : i32 = 0; index < cast(i32) len(src.calls); index += 1 { src_call := src.calls[ index ] src_call.start_index += i_offset src_call.end_index += i_offset @@ -759,9 +808,9 @@ merge_draw_list :: proc( dst, src : ^Draw_List ) } } -optimize_draw_list :: proc(draw_list: ^Draw_List, call_offset: int) +optimize_draw_list :: proc (draw_list: ^Draw_List, call_offset: int) { - // profile(#procedure) + profile(#procedure) assert(draw_list != nil) can_merge_draw_calls :: #force_inline proc "contextless" ( a, b : ^Draw_Call ) -> bool { diff --git a/code/font/vefontcache/mappings.odin b/code/font/vefontcache/mappings.odin index f6afc8d..0f4f220 100644 --- a/code/font/vefontcache/mappings.odin +++ b/code/font/vefontcache/mappings.odin @@ -105,4 +105,23 @@ vec2_64 :: proc { vec2_64_from_vec2, } +import "../../grime" + +DISABLE_PROFILING :: false + +@(deferred_none = profile_end, disabled = DISABLE_PROFILING) +profile :: #force_inline proc "contextless" ( name : string, loc := #caller_location ) { + grime.profile_begin(name, loc) +} + +@(disabled = DISABLE_PROFILING) +profile_begin :: #force_inline proc "contextless" ( name : string, loc := #caller_location ) { + grime.profile_begin(name, loc) +} + +@(disabled = DISABLE_PROFILING) +profile_end :: #force_inline proc "contextless" () { + grime.profile_end() +} + //#endregion("Proc overload mappings") diff --git a/code/font/vefontcache/misc.odin b/code/font/vefontcache/misc.odin index e34fbf0..ba889c0 100644 --- a/code/font/vefontcache/misc.odin +++ b/code/font/vefontcache/misc.odin @@ -21,20 +21,20 @@ vec2i_from_vec2 :: #force_inline proc "contextless" ( v2 : Vec2 ) -> Vec2 // This buffer is used below excluisvely to prevent any allocator recusion when verbose logging from allocators. // This means a single line is limited to 4k buffer -// Logger_Allocator_Buffer : [4 * Kilobyte]u8 +Logger_Allocator_Buffer : [4 * Kilobyte]u8 log :: proc( msg : string, level := core_log.Level.Info, loc := #caller_location ) { - // temp_arena : Arena; arena_init(& temp_arena, Logger_Allocator_Buffer[:]) - // context.allocator = arena_allocator(& temp_arena) - // context.temp_allocator = arena_allocator(& temp_arena) + temp_arena : Arena; arena_init(& temp_arena, Logger_Allocator_Buffer[:]) + context.allocator = arena_allocator(& temp_arena) + context.temp_allocator = arena_allocator(& temp_arena) core_log.log( level, msg, location = loc ) } logf :: proc( fmt : string, args : ..any, level := core_log.Level.Info, loc := #caller_location ) { - // temp_arena : Arena; arena_init(& temp_arena, Logger_Allocator_Buffer[:]) - // context.allocator = arena_allocator(& temp_arena) - // context.temp_allocator = arena_allocator(& temp_arena) + temp_arena : Arena; arena_init(& temp_arena, Logger_Allocator_Buffer[:]) + context.allocator = arena_allocator(& temp_arena) + context.temp_allocator = arena_allocator(& temp_arena) core_log.logf( level, fmt, ..args, location = loc ) } @@ -73,7 +73,7 @@ reset_batch_codepoint_state :: #force_inline proc( ctx : ^Context ) { USE_F64_PRECISION_ON_X_FORM_OPS :: false -screenspace_x_form :: #force_inline proc "contextless" ( position, scale : ^Vec2, size : Vec2 ) +screenspace_x_form :: #force_inline proc "contextless" ( #no_alias position, scale : ^Vec2, size : Vec2 ) { when USE_F64_PRECISION_ON_X_FORM_OPS { @@ -101,7 +101,7 @@ screenspace_x_form :: #force_inline proc "contextless" ( position, scale : ^Vec2 } } -textspace_x_form :: #force_inline proc "contextless" ( position, scale : ^Vec2, size : Vec2 ) +textspace_x_form :: #force_inline proc "contextless" ( #no_alias position, scale : ^Vec2, size : Vec2 ) { when USE_F64_PRECISION_ON_X_FORM_OPS { @@ -123,7 +123,7 @@ textspace_x_form :: #force_inline proc "contextless" ( position, scale : ^Vec2, } } -USE_MANUAL_SIMD_FOR_BEZIER_OPS :: false +USE_MANUAL_SIMD_FOR_BEZIER_OPS :: true when ! USE_MANUAL_SIMD_FOR_BEZIER_OPS { diff --git a/code/font/vefontcache/parser.odin b/code/font/vefontcache/parser.odin index 2bb0b6f..3e2b0a0 100644 --- a/code/font/vefontcache/parser.odin +++ b/code/font/vefontcache/parser.odin @@ -113,6 +113,7 @@ parser_unload_font :: proc( font : ^Parser_Font_Info ) parser_find_glyph_index :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, codepoint : rune ) -> (glyph_index : Glyph) { + profile(#procedure) switch font.kind { case .Freetype: @@ -131,7 +132,7 @@ parser_find_glyph_index :: #force_inline proc "contextless" ( font : ^Parser_Fon return Glyph(-1) } -parser_free_shape :: proc( font : ^Parser_Font_Info, shape : Parser_Glyph_Shape ) +parser_free_shape :: #force_inline proc( font : ^Parser_Font_Info, shape : Parser_Glyph_Shape ) { switch font.kind { @@ -219,10 +220,12 @@ parser_get_font_vertical_metrics :: #force_inline proc "contextless" ( font : ^P return } -parser_get_glyph_box :: #force_inline proc ( font : ^Parser_Font_Info, glyph_index : Glyph ) -> (bounds_0, bounds_1 : Vec2i) +parser_get_glyph_box :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, glyph_index : Glyph ) -> (bounds_0, bounds_1 : Vec2i) { + profile(#procedure) switch font.kind { + case .Freetype: freetype.load_glyph( font.freetype_info, c.uint(glyph_index), { .No_Bitmap, .No_Hinting, .No_Scale } ) @@ -234,15 +237,15 @@ parser_get_glyph_box :: #force_inline proc ( font : ^Parser_Font_Info, glyph_ind case .STB_TrueType: x0, y0, x1, y1 : i32 success := cast(bool) stbtt.GetGlyphBox( & font.stbtt_info, i32(glyph_index), & x0, & y0, & x1, & y1 ) - assert( success ) + // assert( success ) - bounds_0 = { i32(x0), i32(y0) } - bounds_1 = { i32(x1), i32(y1) } + bounds_0 = { x0, y0 } + bounds_1 = { x1, y1 } } return } -parser_get_glyph_shape :: proc( font : ^Parser_Font_Info, glyph_index : Glyph ) -> (shape : Parser_Glyph_Shape, error : Allocator_Error) +parser_get_glyph_shape :: #force_inline proc ( font : ^Parser_Font_Info, glyph_index : Glyph ) -> (shape : Parser_Glyph_Shape, error : Allocator_Error) { switch font.kind { diff --git a/code/font/vefontcache/shaped_text.odin b/code/font/vefontcache/shaped_text.odin index 59ac8bb..3ffc110 100644 --- a/code/font/vefontcache/shaped_text.odin +++ b/code/font/vefontcache/shaped_text.odin @@ -1,10 +1,10 @@ package vefontcache Shaped_Text :: struct { - glyphs : [dynamic]Glyph, - positions : [dynamic]Vec2, - end_cursor_pos : Vec2, - size : Vec2, + glyphs : [dynamic]Glyph, + positions : [dynamic]Vec2, + end_cursor_pos : Vec2, + size : Vec2, } Shaped_Text_Cache :: struct { @@ -19,9 +19,11 @@ shape_lru_hash :: #force_inline proc "contextless" ( hash : ^u64, bytes : []byte } } -shape_text_cached :: proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : ^Entry ) -> ^Shaped_Text +ShapedTextUncachedProc :: #type proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : ^Entry, output : ^Shaped_Text ) + +shape_text_cached :: proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : ^Entry, shape_text_uncached : ShapedTextUncachedProc ) -> ^Shaped_Text #no_bounds_check { - // profile(#procedure) + profile(#procedure) font := font font_bytes := slice_ptr( transmute(^byte) & font, size_of(Font_ID) ) text_bytes := transmute( []byte) text_utf8 @@ -59,9 +61,9 @@ shape_text_cached :: proc( ctx : ^Context, font : Font_ID, text_utf8 : string, e return & shape_cache.storage[ shape_cache_idx ] } -shape_text_uncached :: proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : ^Entry, output : ^Shaped_Text ) +shape_text_uncached_advanced :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : ^Entry, output : ^Shaped_Text ) { - // profile(#procedure) + profile(#procedure) assert( ctx != nil ) assert( font >= 0 && int(font) < len(ctx.entries) ) @@ -74,58 +76,68 @@ shape_text_uncached :: proc( ctx : ^Context, font : Font_ID, text_utf8 : string, line_gap := f32(line_gap_i32) line_height := (ascent - descent + line_gap) * entry.size_scale - if ctx.use_advanced_shaper - { - shaper_shape_from_text( & ctx.shaper_ctx, & entry.shaper_info, output, text_utf8, ascent_i32, descent_i32, line_gap_i32, entry.size, entry.size_scale ) - return - } - else - { - // Note(Original Author): - // We use our own fallback dumbass text shaping. - // WARNING: PLEASE USE HARFBUZZ. GOOD TEXT SHAPING IS IMPORTANT FOR INTERNATIONALISATION. + shaper_shape_from_text( & ctx.shaper_ctx, & entry.shaper_info, output, text_utf8, ascent_i32, descent_i32, line_gap_i32, entry.size, entry.size_scale ) +} - line_count : int = 1 - max_line_width : f32 = 0 - position : Vec2 +shape_text_uncached_latin :: proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : ^Entry, output : ^Shaped_Text ) +{ + profile(#procedure) + assert( ctx != nil ) + assert( font >= 0 && int(font) < len(ctx.entries) ) - prev_codepoint : rune - for codepoint in text_utf8 + clear( & output.glyphs ) + clear( & output.positions ) + + ascent_i32, descent_i32, line_gap_i32 := parser_get_font_vertical_metrics( & entry.parser_info ) + ascent := f32(ascent_i32) + descent := f32(descent_i32) + line_gap := f32(line_gap_i32) + line_height := (ascent - descent + line_gap) * entry.size_scale + + line_count : int = 1 + max_line_width : f32 = 0 + position : Vec2 + + prev_codepoint : rune + for codepoint, index in text_utf8 + { + if prev_codepoint > 0 { + kern := parser_get_codepoint_kern_advance( & entry.parser_info, prev_codepoint, codepoint ) + position.x += f32(kern) * entry.size_scale + } + if codepoint == '\n' { - if prev_codepoint > 0 { - kern := parser_get_codepoint_kern_advance( & entry.parser_info, prev_codepoint, codepoint ) - position.x += f32(kern) * entry.size_scale - } - if codepoint == '\n' - { - line_count += 1 - max_line_width = max(max_line_width, position.x) - position.x = 0.0 - position.y -= line_height - position.y = position.y - prev_codepoint = rune(0) - continue - } - if abs( entry.size ) <= ctx.shaper_ctx.adv_snap_small_font_threshold { - position.x = ceil(position.x) - } - - append( & output.glyphs, parser_find_glyph_index( & entry.parser_info, codepoint )) - advance, _ := parser_get_codepoint_horizontal_metrics( & entry.parser_info, codepoint ) + line_count += 1 + max_line_width = max(max_line_width, position.x) + position.x = 0.0 + position.y -= line_height + position.y = position.y + prev_codepoint = rune(0) + continue + } + if abs( entry.size ) <= ctx.shaper_ctx.adv_snap_small_font_threshold { + position.x = ceil(position.x) + } + glyph_index := parser_find_glyph_index( & entry.parser_info, codepoint ) + is_empty := parser_is_glyph_empty( & entry.parser_info,glyph_index ) + if ! is_empty + { + append( & output.glyphs, glyph_index) append( & output.positions, Vec2 { floor(position.x), floor(position.y) }) - - position.x += f32(advance) * entry.size_scale - prev_codepoint = codepoint } - output.end_cursor_pos = position - max_line_width = max(max_line_width, position.x) - - output.size.x = max_line_width - output.size.y = f32(line_count) * line_height + advance, _ := parser_get_codepoint_horizontal_metrics( & entry.parser_info, codepoint ) + position.x += f32(advance) * entry.size_scale + prev_codepoint = codepoint } + + output.end_cursor_pos = position + max_line_width = max(max_line_width, position.x) + + output.size.x = max_line_width + output.size.y = f32(line_count) * line_height } diff --git a/code/font/vefontcache/shaper.odin b/code/font/vefontcache/shaper.odin index df61c8f..8e3826a 100644 --- a/code/font/vefontcache/shaper.odin +++ b/code/font/vefontcache/shaper.odin @@ -37,7 +37,7 @@ shaper_shutdown :: proc( ctx : ^Shaper_Context ) } } -shaper_load_font :: proc( ctx : ^Shaper_Context, label : string, data : []byte, user_data : rawptr ) -> (info : Shaper_Info) +shaper_load_font :: proc( ctx : ^Shaper_Context, label : string, data : []byte, user_data : rawptr = nil ) -> (info : Shaper_Info) { using info blob = harfbuzz.blob_create( raw_data(data), cast(c.uint) len(data), harfbuzz.Memory_Mode.READONLY, user_data, nil ) @@ -54,10 +54,10 @@ shaper_unload_font :: proc( ctx : ^Shaper_Info ) if blob != nil do harfbuzz.blob_destroy( blob ) } -shaper_shape_from_text :: proc( ctx : ^Shaper_Context, info : ^Shaper_Info, output :^Shaped_Text, text_utf8 : string, +shaper_shape_from_text :: #force_inline proc( ctx : ^Shaper_Context, info : ^Shaper_Info, output :^Shaped_Text, text_utf8 : string, ascent, descent, line_gap : i32, size, size_scale : f32 ) { - // profile(#procedure) + profile(#procedure) current_script := harfbuzz.Script.UNKNOWN hb_ucfunc := harfbuzz.unicode_funcs_get_default() harfbuzz.buffer_clear_contents( ctx.hb_buffer ) @@ -71,12 +71,13 @@ shaper_shape_from_text :: proc( ctx : ^Shaper_Context, info : ^Shaper_Info, outp line_count := 1 line_height := ((ascent - descent + line_gap) * size_scale) - position, vertical_position : f32 - shape_run :: proc( buffer : harfbuzz.Buffer, script : harfbuzz.Script, font : harfbuzz.Font, output : ^Shaped_Text, - position, vertical_position, max_line_width: ^f32, line_count: ^int, + position : Vec2 + shape_run :: #force_inline proc( buffer : harfbuzz.Buffer, script : harfbuzz.Script, font : harfbuzz.Font, output : ^Shaped_Text, + position : ^Vec2, max_line_width: ^f32, line_count: ^int, ascent, descent, line_gap, size, size_scale: f32, snap_shape_pos : b32, adv_snap_small_font_threshold : f32 ) { + profile(#procedure) // Set script and direction. We use the system's default langauge. // script = HB_SCRIPT_LATIN harfbuzz.buffer_set_script( buffer, script ) @@ -102,10 +103,10 @@ shaper_shape_from_text :: proc( ctx : ^Shaper_Context, info : ^Shaper_Info, outp if hb_glyph.cluster > 0 { - (max_line_width^) = max( max_line_width^, position^ ) - (position^) = 0.0 - (vertical_position^) -= line_height - (vertical_position^) = floor(vertical_position^) + (max_line_width^) = max( max_line_width^, position.x ) + position.x = 0.0 + position.y -= line_height + position.y = floor(position.y) (line_count^) += 1 continue } @@ -116,26 +117,24 @@ shaper_shape_from_text :: proc( ctx : ^Shaper_Context, info : ^Shaper_Info, outp append( & output.glyphs, glyph_id ) - pos := position^ - v_pos := vertical_position^ - offset_x := f32(hb_gposition.x_offset) * size_scale - offset_y := f32(hb_gposition.y_offset) * size_scale - pos += offset_x - v_pos += offset_y + glyph_pos := position^ + offset := Vec2 { f32(hb_gposition.x_offset), f32(hb_gposition.y_offset) } * size_scale + glyph_pos += offset if snap_shape_pos { - pos = ceil(pos) - v_pos = ceil(v_pos) + glyph_pos = ceil(glyph_pos) } - append( & output.positions, Vec2 {pos, v_pos}) + append( & output.positions, glyph_pos) - (position^) += f32(hb_gposition.x_advance) * size_scale - (vertical_position^) += f32(hb_gposition.y_advance) * size_scale - (max_line_width^) = max(max_line_width^, position^) + advance := Vec2 { + f32(hb_gposition.x_advance) * size_scale, + f32(hb_gposition.y_advance) * size_scale + } + (position^) += advance + (max_line_width^) = max(max_line_width^, position.x) } - output.end_cursor_pos.x = position^ - output.end_cursor_pos.y = vertical_position^ + output.end_cursor_pos = position^ harfbuzz.buffer_clear_contents( buffer ) } @@ -162,7 +161,7 @@ shaper_shape_from_text :: proc( ctx : ^Shaper_Context, info : ^Shaper_Info, outp // End current run since we've encountered a script change. shape_run( ctx.hb_buffer, current_script, info.font, output, - & position, & vertical_position, & max_line_width, & line_count, + & position, & max_line_width, & line_count, ascent, descent, line_gap, size, size_scale, ctx.snap_glyph_position, ctx.adv_snap_small_font_threshold ) @@ -173,7 +172,7 @@ shaper_shape_from_text :: proc( ctx : ^Shaper_Context, info : ^Shaper_Info, outp // End the last run if needed shape_run( ctx.hb_buffer, current_script, info.font, output, - & position, & vertical_position, & max_line_width, & line_count, + & position, & max_line_width, & line_count, ascent, descent, line_gap, size, size_scale, ctx.snap_glyph_position, ctx.adv_snap_small_font_threshold ) diff --git a/code/font/vefontcache/vefontcache.odin b/code/font/vefontcache/vefontcache.odin index 8ca238c..d7e35fd 100644 --- a/code/font/vefontcache/vefontcache.odin +++ b/code/font/vefontcache/vefontcache.odin @@ -7,7 +7,7 @@ package vefontcache import "base:runtime" -Font_ID :: distinct i64 +Font_ID :: distinct i32 Glyph :: distinct i32 Entry :: struct { @@ -37,7 +37,7 @@ Context :: struct { temp_path : [dynamic]Vertex, temp_codepoint_seen : map[u64]bool, - temp_codepoint_seen_num : int, + temp_codepoint_seen_num : i32, snap_width : f32, snap_height : f32, @@ -63,7 +63,6 @@ Context :: struct { debug_print_verbose : b32, } - Init_Atlas_Region_Params :: struct { width : u32, height : u32, @@ -113,7 +112,7 @@ Init_Glyph_Draw_Params :: struct { Init_Glyph_Draw_Params_Default :: Init_Glyph_Draw_Params { over_sample = Vec2 { 4, 4 }, - buffer_batch = 4, + buffer_batch = 8, draw_padding = Init_Atlas_Params_Default.glyph_padding, } @@ -135,8 +134,8 @@ Init_Shape_Cache_Params :: struct { } Init_Shape_Cache_Params_Default :: Init_Shape_Cache_Params { - capacity = 8 * 1024, - reserve_length = 256, + capacity = 10 * 1024, + reserve_length = 10 * 1024, } //#region("lifetime") @@ -148,10 +147,10 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType, glyph_draw_params := Init_Glyph_Draw_Params_Default, shape_cache_params := Init_Shape_Cache_Params_Default, shaper_params := Init_Shaper_Params_Default, - default_curve_quality : u32 = 3, - entires_reserve : u32 = 512, + default_curve_quality : u32 = 2, + entires_reserve : u32 = 256, temp_path_reserve : u32 = 1024, - temp_codepoint_seen_reserve : u32 = 2048, + temp_codepoint_seen_reserve : u32 = 1024, ) { assert( ctx != nil, "Must provide a valid context" ) @@ -179,13 +178,13 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType, temp_codepoint_seen, error = make( map[u64]bool, uint(temp_codepoint_seen_reserve) ) assert(error == .None, "VEFontCache.init : Failed to allocate temp_path") - draw_list.vertices, error = make( [dynamic]Vertex, len = 0, cap = 4 * Kilobyte ) + draw_list.vertices, error = make( [dynamic]Vertex, len = 0, cap = 1 * Kilobyte ) assert(error == .None, "VEFontCache.init : Failed to allocate draw_list.vertices") - draw_list.indices, error = make( [dynamic]u32, len = 0, cap = 8 * Kilobyte ) + draw_list.indices, error = make( [dynamic]u32, len = 0, cap = 2 * Kilobyte ) assert(error == .None, "VEFontCache.init : Failed to allocate draw_list.indices") - draw_list.calls, error = make( [dynamic]Draw_Call, len = 0, cap = 512 ) + draw_list.calls, error = make( [dynamic]Draw_Call, len = 0, cap = 128 ) assert(error == .None, "VEFontCache.init : Failed to allocate draw_list.calls") init_atlas_region :: proc( region : ^Atlas_Region, params : Init_Atlas_Params, region_params : Init_Atlas_Region_Params, factor : Vec2i, expected_cap : i32 ) @@ -215,7 +214,7 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType, atlas.width = i32(atlas_params.width) atlas.height = i32(atlas_params.height) - atlas.glyph_padding = i32(atlas_params.glyph_padding) + atlas.glyph_padding = f32(atlas_params.glyph_padding) atlas.glyph_over_scalar = atlas_params.glyph_over_scalar atlas.region_a.offset = {0, 0} @@ -394,7 +393,7 @@ load_font :: proc( ctx : ^Context, label : string, data : []byte, size_px : f32, used = true parser_info = parser_load_font( & parser_ctx, label, data ) - shaper_info = shaper_load_font( & shaper_ctx, label, data, transmute(rawptr) id ) + shaper_info = shaper_load_font( & shaper_ctx, label, data ) size = size_px size_scale = parser_scale( & parser_info, size ) @@ -442,9 +441,9 @@ configure_snap :: #force_inline proc( ctx : ^Context, snap_width, snap_height : get_cursor_pos :: #force_inline proc( ctx : ^Context ) -> Vec2 { assert(ctx != nil); return ctx.cursor_pos } set_colour :: #force_inline proc( ctx : ^Context, colour : Colour ) { assert(ctx != nil); ctx.colour = colour } -draw_text :: proc( ctx : ^Context, font : Font_ID, text_utf8 : string, position, scale : Vec2 ) -> b32 +draw_text :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, position, scale : Vec2 ) -> b32 { - // profile(#procedure) + profile(#procedure) assert( ctx != nil ) assert( font >= 0 && int(font) < len(ctx.entries) ) @@ -466,20 +465,50 @@ draw_text :: proc( ctx : ^Context, font : Font_ID, text_utf8 : string, position, text_chunk = transmute(string) text_utf8_bytes[ : ] if len(text_chunk) > 0 { - shaped := shape_text_cached( ctx, font, text_chunk, entry ) + shaped := shape_text_cached( ctx, font, text_chunk, entry, shape_text_uncached_advanced ) + ctx.cursor_pos = draw_text_shape( ctx, font, entry, shaped, position, scale, ctx.snap_width, ctx.snap_height ) + } + return true +} + +draw_text_mono_latin :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, position, scale : Vec2 ) -> b32 +{ + profile(#procedure) + assert( ctx != nil ) + assert( font >= 0 && int(font) < len(ctx.entries) ) + + ctx.cursor_pos = {} + + position := position + if ctx.snap_width > 0 do position.x = ceil(position.x * ctx.snap_width ) / ctx.snap_width + if ctx.snap_height > 0 do position.y = ceil(position.y * ctx.snap_height) / ctx.snap_height + + entry := & ctx.entries[ font ] + + ChunkType :: enum u32 { Visible, Formatting } + chunk_kind : ChunkType + chunk_start : int = 0 + chunk_end : int = 0 + + text_utf8_bytes := transmute([]u8) text_utf8 + text_chunk : string + + text_chunk = transmute(string) text_utf8_bytes[ : ] + if len(text_chunk) > 0 { + shaped := shape_text_cached( ctx, font, text_chunk, entry, shape_text_uncached_latin ) ctx.cursor_pos = draw_text_shape( ctx, font, entry, shaped, position, scale, ctx.snap_width, ctx.snap_height ) } return true } // ve_fontcache_Draw_List -get_draw_list :: proc( ctx : ^Context, optimize_before_returning := true ) -> ^Draw_List { +get_draw_list :: #force_inline proc( ctx : ^Context, optimize_before_returning := true ) -> ^Draw_List { assert( ctx != nil ) if optimize_before_returning do optimize_draw_list( & ctx.draw_list, 0 ) return & ctx.draw_list } -get_draw_list_layer :: proc( ctx : ^Context, optimize_before_returning := true ) -> (vertices : []Vertex, indices : []u32, calls : []Draw_Call) { +get_draw_list_layer :: #force_inline proc( ctx : ^Context, optimize_before_returning := true ) -> (vertices : []Vertex, indices : []u32, calls : []Draw_Call) { assert( ctx != nil ) if optimize_before_returning do optimize_draw_list( & ctx.draw_list, ctx.draw_layer.calls_offset ) vertices = ctx.draw_list.vertices[ ctx.draw_layer.vertices_offset : ] @@ -489,7 +518,7 @@ get_draw_list_layer :: proc( ctx : ^Context, optimize_before_returning := true ) } // ve_fontcache_flush_Draw_List -flush_draw_list :: proc( ctx : ^Context ) { +flush_draw_list :: #force_inline proc( ctx : ^Context ) { assert( ctx != nil ) using ctx clear_draw_list( & draw_list ) @@ -498,7 +527,7 @@ flush_draw_list :: proc( ctx : ^Context ) { draw_layer.calls_offset = 0 } -flush_draw_list_layer :: proc( ctx : ^Context ) { +flush_draw_list_layer :: #force_inline proc( ctx : ^Context ) { assert( ctx != nil ) using ctx draw_layer.vertices_offset = len(draw_list.vertices) @@ -510,14 +539,14 @@ flush_draw_list_layer :: proc( ctx : ^Context ) { //#region("metrics") -measure_text_size :: proc( ctx : ^Context, font : Font_ID, text_utf8 : string ) -> (measured : Vec2) +measure_text_size :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string ) -> (measured : Vec2) { // profile(#procedure) assert( ctx != nil ) assert( font >= 0 && int(font) < len(ctx.entries) ) entry := &ctx.entries[font] - shaped := shape_text_cached(ctx, font, text_utf8, entry) + shaped := shape_text_cached(ctx, font, text_utf8, entry, shape_text_uncached_advanced ) return shaped.size } diff --git a/code/grime/hashmap_chained.odin b/code/grime/hashmap_chained.odin index 0f10dce..340a5e9 100644 --- a/code/grime/hashmap_chained.odin +++ b/code/grime/hashmap_chained.odin @@ -248,7 +248,6 @@ hmap_chained_set :: proc( self : HMapChained($Type), key : u64, value : Type ) - block = slice_ptr(transmute([^]byte) raw_mem, slot_size) // block, error = pool_grab(pool, false) next := transmute( ^HMapChainedSlot(Type)) raw_data(block) - next^ = {} slot.next = next slot.next^ = {} diff --git a/code/grime/profiler.odin b/code/grime/profiler.odin index d0bec4e..3a3a27c 100644 --- a/code/grime/profiler.odin +++ b/code/grime/profiler.odin @@ -15,15 +15,19 @@ set_profiler_module_context :: #force_inline proc "contextless" ( ctx : ^SpallPr Module_Context = ctx } -@(deferred_none = profile_end) +DISABLE_PROFILING :: false + +@(deferred_none = profile_end, disabled = DISABLE_PROFILING) profile :: #force_inline proc "contextless" ( name : string, loc := #caller_location ) { spall._buffer_begin( & Module_Context.ctx, & Module_Context.buffer, name, "", loc ) } +@(disabled = DISABLE_PROFILING) profile_begin :: #force_inline proc "contextless" ( name : string, loc := #caller_location ) { spall._buffer_begin( & Module_Context.ctx, & Module_Context.buffer, name, "", loc ) } +@(disabled = DISABLE_PROFILING) profile_end :: #force_inline proc "contextless" () { spall._buffer_end( & Module_Context.ctx, & Module_Context.buffer) } diff --git a/code/sectr/engine/client_api.odin b/code/sectr/engine/client_api.odin index 2a5823c..b72a6ff 100644 --- a/code/sectr/engine/client_api.odin +++ b/code/sectr/engine/client_api.odin @@ -310,7 +310,7 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem { profile("screen ui") - ui_startup( & screen_ui.base, cache_allocator = persistent_slab_allocator() ) + ui_startup( & screen_ui.base, cache_table_size = 2 * Kilo, cache_allocator = persistent_slab_allocator() ) ui_floating_startup( & screen_ui.floating, 1 * Kilobyte, 1 * Kilobyte, persistent_slab_allocator(), "screen ui floating manager" ) using screen_ui @@ -348,7 +348,7 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem // } // Setup workspace UI state - ui_startup( & workspace.ui, cache_allocator = persistent_slab_allocator() ) + ui_startup( & workspace.ui, cache_table_size = 8 * Kilo, cache_allocator = persistent_slab_allocator() ) } // debug.path_lorem = str_fmt("C:/projects/SectrPrototype/examples/Lorem Ipsum (197).txt", allocator = persistent_slab_allocator()) diff --git a/code/sectr/engine/render.odin b/code/sectr/engine/render.odin index 36ea6a2..20e8510 100644 --- a/code/sectr/engine/render.odin +++ b/code/sectr/engine/render.odin @@ -106,8 +106,9 @@ render_mode_2d_workspace :: proc( screen_extent : Vec2, cam : Camera, input : In cam := cam when UI_Render_Method == .Layers { - render_list := array_to_slice( ui.render_list ) - render_ui_via_box_list( render_list, screen_extent, ve_ctx, ve_render, & cam ) + render_list_box := array_to_slice( ui.render_list_box ) + render_list_text := array_to_slice( ui.render_list_text ) + render_ui_via_box_list( render_list_box, render_list_text, screen_extent, ve_ctx, ve_render, & cam ) } when UI_Render_Method == .Depth_First { @@ -281,8 +282,10 @@ render_screen_ui :: proc( screen_extent : Extents2, ui : ^UI_State, ve_ctx : ^ve render_set_view_space(screen_extent) when UI_Render_Method == .Layers { - render_list := array_to_slice( ui.render_list ) - render_ui_via_box_list( render_list, screen_extent, ve_ctx, ve_render ) + render_list_box := array_to_slice( ui.render_list_box ) + render_list_text := array_to_slice( ui.render_list_text ) + render_ui_via_box_list( render_list_box, render_list_text, screen_extent, ve_ctx, ve_render ) + // render_ui_via_box_list( render_list, screen_extent, ve_ctx, ve_render ) } when UI_Render_Method == .Depth_First { @@ -468,7 +471,8 @@ render_ui_via_box_tree :: proc( ui : ^UI_State, screen_extent : Vec2, ve_ctx : ^ } previous_layer : i32 = 0 - for box := ui.root.first; box != nil; box = ui_box_tranverse_next_depth_first( box, bypass_intersection_test = false, ctx = ui ) + for box := ui_box_tranverse_next_depth_first( ui.root, bypass_intersection_test = true, ctx = ui ); box != nil; + box = ui_box_tranverse_next_depth_first( box, bypass_intersection_test = true, ctx = ui ) { if box.ancestors != previous_layer { if shape_enqueued do render_flush_gp() @@ -555,68 +559,102 @@ render_ui_via_box_tree :: proc( ui : ^UI_State, screen_extent : Vec2, ve_ctx : ^ if text_enqueued do render_text_layer( screen_extent, ve_ctx, ve_render ) } -render_ui_via_box_list :: proc( render_list : []UI_RenderBoxInfo, screen_extent : Vec2, ve_ctx : ^ve.Context, ve_render : VE_RenderData, cam : ^Camera = nil ) +render_ui_via_box_list :: proc( box_list : []UI_RenderBoxInfo, text_list : []UI_RenderTextInfo, screen_extent : Vec2, ve_ctx : ^ve.Context, ve_render : VE_RenderData, cam : ^Camera = nil ) { + profile(#procedure) debug := get_state().debug default_font := get_state().default_font - text_enqueued : b32 = false - shape_enqueued : b32 = false + cam_zoom_ratio := cam != nil ? 1.0 / cam.zoom : 1.0 + circle_radius := cam != nil ? cam_zoom_ratio * 3 : 3 - for entry, id in render_list + box_id : i32 = 0 + text_id : i32 = 0 + + layer_left : b32 = true + for layer_left { - already_passed_signal := id > 0 && render_list[ id - 1 ].layer_signal - if !already_passed_signal && entry.layer_signal + profile("layer") + shape_enqueued : b32 = false + box_layer_done : b32 = false + for box_id < cast(i32) len(box_list) && ! box_layer_done { - // profile("render ui layer") - render_flush_gp() - if text_enqueued do render_text_layer( screen_extent, ve_ctx, ve_render ) - continue - } - using entry + profile("GP_Render") + box_layer_done = b32(box_id > 0) && box_list[ box_id - 1 ].layer_signal - // profile("enqueue box") + entry := box_list[box_id] - GP_Render: - { - // profile("draw_shapes") - if style.bg_color.a != 0 + corner_radii_total : f32 = 0 + for radius in entry.corner_radii do corner_radii_total += radius + + if entry.bg_color.a != 0 { - render_set_color( style.bg_color ) - draw_rect( bounds ) + render_set_color( entry.bg_color ) + if corner_radii_total > 0 do draw_rect_rounded( entry.bounds, entry.corner_radii, 16 ) + else do draw_rect( entry.bounds) shape_enqueued = true } - if style.border_color.a != 0 && border_width > 0 { - render_set_color( style.border_color ) - draw_rect_border( bounds, border_width ) + if entry.border_color.a != 0 && entry.border_width > 0 + { + render_set_color( entry.border_color ) + + if corner_radii_total > 0 do draw_rect_rounded_border( entry.bounds, entry.corner_radii, entry.border_width, 16 ) + else do draw_rect_border( entry.bounds, entry.border_width ) shape_enqueued = true } if debug.draw_ui_box_bounds_points { render_set_color(Color_Red) - draw_filled_circle(bounds.min.x, bounds.min.y, 3, 24) + draw_filled_circle(entry.bounds.min.x, entry.bounds.min.y, circle_radius, 24) render_set_color(Color_Blue) - draw_filled_circle(bounds.max.x, bounds.max.y, 3, 24) + draw_filled_circle(entry.bounds.max.x, entry.bounds.max.y, circle_radius, 24) shape_enqueued = true } + + box_id += 1 } - if len(text.str) > 0 && style.font.key != 0 { - if cam != nil { - draw_text_string_pos_extent_zoomed( text.str, default_font, font_size, computed.text_pos, cam^, style.text_color ) - } - else { - draw_text_string_pos_extent( text.str, default_font, font_size, computed.text_pos, style.text_color ) - } - text_enqueued = true + if shape_enqueued { + profile("render ui box_layer") + render_flush_gp() + shape_enqueued = false } + + text_enqueued : b32 = false + text_layer_done : b32 = false + for text_id < cast(i32) len(text_list) && ! text_layer_done + { + profile("Text_Render") + text_layer_done = b32(text_id > 0) && text_list[ text_id - 1 ].layer_signal + + entry := text_list[text_id] + + font := entry.font.key != 0 ? entry.font : default_font + if len(entry.text) > 0 + { + if cam != nil { + draw_text_string_pos_extent_zoomed( entry.text, font, entry.font_size, entry.position, cam^, entry.color ) + } + else { + draw_text_string_pos_extent( entry.text, font, entry.font_size, entry.position, entry.color ) + } + text_enqueued = true + } + + text_id += 1 + } + + if text_enqueued { + profile("render ui text layer") + if text_enqueued do render_text_layer( screen_extent, ve_ctx, ve_render ) + text_enqueued = false + } + + layer_left = box_id < cast(i32) len(box_list) && text_id < cast(i32) len(text_list) } - - if shape_enqueued do render_flush_gp() - if text_enqueued do render_text_layer( screen_extent, ve_ctx, ve_render ) } #region("Helpers") diff --git a/code/sectr/font/provider.odin b/code/sectr/font/provider.odin index d7e7b61..4dea357 100644 --- a/code/sectr/font/provider.odin +++ b/code/sectr/font/provider.odin @@ -46,8 +46,6 @@ font_provider_startup :: proc( ctx : ^FontProviderContext ) ve.startup( & ve_ctx, .STB_TrueType, allocator = persistent_slab_allocator() ) ve_ctx.glyph_buffer.over_sample = { 4,4 } log("VEFontCached initialized") - // provider_data.ve_ctx.debug_print = true - // provider_data.ve_ctx.debug_print_verbose = true font_provider_setup_sokol_gfx_objects( & render, ve_ctx ) } diff --git a/code/sectr/ui/core/base.odin b/code/sectr/ui/core/base.odin index 82ab84e..7b03e2c 100644 --- a/code/sectr/ui/core/base.odin +++ b/code/sectr/ui/core/base.odin @@ -64,7 +64,7 @@ UI_Style_Stack_Size :: 512 UI_Parent_Stack_Size :: 512 // UI_Built_Boxes_Array_Size :: 8 UI_Built_Boxes_Array_Size :: 56 * Kilobyte -UI_BoxCache_TableSize :: 8 * Kilobyte +UI_BoxCache_TableSize :: 8 * Kilobyte UI_RenderEntry :: struct { info : UI_RenderBoxInfo, @@ -75,14 +75,32 @@ UI_RenderEntry :: struct { UI_RenderLayer :: DLL_NodeFL(UI_RenderEntry) +// UI_RenderBoxInfo :: struct { +// using computed : UI_Computed, +// using style : UI_Style, +// text : StrRunesPair, +// font_size : UI_Scalar, +// border_width : UI_Scalar, +// label : StrRunesPair, +// layer_signal : b32, +// } + UI_RenderBoxInfo :: struct { - using computed : UI_Computed, - using style : UI_Style, - text : StrRunesPair, - font_size : UI_Scalar, - border_width : UI_Scalar, - label : StrRunesPair, - layer_signal : b32, + bounds : Range2, + corner_radii : [Corner.Count]f32, + bg_color : RGBA8, + border_color : RGBA8, + border_width : UI_Scalar, + layer_signal : b8, +} + +UI_RenderTextInfo :: struct { + text : string, + position : Vec2, + color : RGBA8, + font : FontID, + font_size : f32, + layer_signal : b8 } UI_RenderMethod :: enum u32 { @@ -90,7 +108,7 @@ UI_RenderMethod :: enum u32 { Layers, } -UI_Render_Method :: UI_RenderMethod.Depth_First +UI_Render_Method :: UI_RenderMethod.Layers // TODO(Ed): Rename to UI_Context UI_State :: struct { @@ -104,10 +122,15 @@ UI_State :: struct { prev_cache : ^HMapChained( UI_Box ), curr_cache : ^HMapChained( UI_Box ), + // TODO(Ed): DO WE ACTUALLY NEED THIS? + spacial_indexing_method : UI_SpacialIndexingMethod, + // For rendering via a set of layers organized into a single command list // render_queue_builder : SubArena, - render_queue : Array(UI_RenderLayer), - render_list : Array(UI_RenderBoxInfo), + // render_queue : Array(UI_RenderLayer), + // render_list : Array(UI_RenderBoxInfo), + render_list_box : Array(UI_RenderBoxInfo), + render_list_text : Array(UI_RenderTextInfo), null_box : ^UI_Box, // This was used with the Linked list interface... root : ^UI_Box, @@ -138,13 +161,13 @@ UI_State :: struct { #region("Lifetime") -ui_startup :: proc( ui : ^ UI_State, cache_allocator : Allocator /* , cache_reserve_size : u64 */ ) +ui_startup :: proc( ui : ^ UI_State, spacial_indexing_method : UI_SpacialIndexingMethod = .QuadTree, cache_allocator : Allocator, cache_table_size : uint ) { ui := ui ui^ = {} for & cache in ui.caches { - box_cache, allocation_error := make( HMapChained(UI_Box), UI_BoxCache_TableSize, cache_allocator ) + box_cache, allocation_error := make( HMapChained(UI_Box), cache_table_size, cache_allocator ) verify( allocation_error == AllocatorError.None, "Failed to allocate box cache" ) cache = box_cache } @@ -153,11 +176,17 @@ ui_startup :: proc( ui : ^ UI_State, cache_allocator : Allocator /* , cache_rese allocation_error : AllocatorError - ui.render_queue, allocation_error = make( Array(UI_RenderLayer), 32, cache_allocator ) - verify( allocation_error == AllocatorError.None, "Failed to allcate render_queue") + // ui.render_queue, allocation_error = make( Array(UI_RenderLayer), 32, cache_allocator ) + // verify( allocation_error == AllocatorError.None, "Failed to allcate render_queue") - ui.render_list, allocation_error = make( Array(UI_RenderBoxInfo), UI_Built_Boxes_Array_Size, cache_allocator, fixed_cap = true ) - verify( allocation_error == AllocatorError.None, "Failed to allocate rener_list" ) + // ui.render_list, allocation_error = make( Array(UI_RenderBoxInfo), UI_Built_Boxes_Array_Size, cache_allocator, fixed_cap = true ) + // verify( allocation_error == AllocatorError.None, "Failed to allocate rener_list" ) + + ui.render_list_box, allocation_error = make( Array(UI_RenderBoxInfo), UI_Built_Boxes_Array_Size, cache_allocator, fixed_cap = true ) + verify( allocation_error == AllocatorError.None, "Failed to allocate rener_list_box" ) + + ui.render_list_text, allocation_error = make( Array(UI_RenderTextInfo), UI_Built_Boxes_Array_Size, cache_allocator, fixed_cap = true ) + verify( allocation_error == AllocatorError.None, "Failed to allocate rener_list_box" ) log("ui_startup completed") } @@ -168,8 +197,10 @@ ui_reload :: proc( ui : ^ UI_State, cache_allocator : Allocator ) for & cache in ui.caches { hmap_chained_reload( cache, cache_allocator) } - ui.render_queue.backing = cache_allocator - ui.render_list.backing = cache_allocator + // ui.render_queue.backing = cache_allocator + // ui.render_list.backing = cache_allocator + ui.render_list_box.backing = cache_allocator + ui.render_list_text.backing = cache_allocator } // TODO(Ed) : Is this even needed? @@ -186,8 +217,10 @@ ui_graph_build_begin :: proc( ui : ^ UI_State, bounds : Vec2 = {} ) stack_clear( & layout_combo_stack ) stack_clear( & style_combo_stack ) - array_clear( render_queue ) - array_clear( render_list ) + // array_clear( render_queue ) + // array_clear( render_list ) + array_clear( render_list_box ) + array_clear( render_list_text ) curr_cache, prev_cache = swap( curr_cache, prev_cache ) @@ -210,6 +243,7 @@ ui_graph_build_end :: proc( ui : ^UI_State ) state := get_state() ui_parent_pop() // Should be ui_context.root + assert(stack_peek(& ui.parent_stack) == nil) Post_Build_Graph_Traversal: { @@ -225,196 +259,68 @@ ui_graph_build_end :: proc( ui : ^UI_State ) computed.content = computed.bounds } + previous_layer : i32 = 1 + // Auto-layout and initial render_queue generation profile_begin("Auto-layout and render_queue generation") - render_queue := array_to_slice(ui.render_queue) - for current := root.first; current != nil; current = ui_box_traverse_next_breadth_first( current, bypass_intersection_test = true ) + // render_queue := array_to_slice(ui.render_queue) + for current := ui.root.first; current != nil; + current = ui_box_tranverse_next_depth_first( current, bypass_intersection_test = true, ctx = ui ) { if ! current.computed.fresh { ui_box_compute_layout( current ) } - when UI_Render_Method == .Layers - { + if ! intersects_range2(ui_view_bounds(ui), current.computed.bounds) { + continue + } - // TODO(Ed): Eventually put this into a sub-arena - entry, error := new(UI_RenderEntry) - (entry^) = UI_RenderEntry { - info = { - current.computed, - current.style, - current.text, - current.layout.font_size, - current.layout.border_width, - current.label, - false, - }, - layer_id = current.ancestors -1, - } + when true { + // entry : UI_RenderBoxInfo = { + // current.computed, + // current.style, + // current.text, + // current.layout.font_size, + // current.layout.border_width, + // current.label, + // false, + // } + entry_box := UI_RenderBoxInfo { + bounds = current.computed.bounds, + corner_radii = current.style.corner_radii, + bg_color = current.style.bg_color, + border_color = current.style.border_color, + border_width = current.layout.border_width, + } + entry_text := UI_RenderTextInfo { + text = current.text.str, + position = current.computed.text_pos, + color = current.style.text_color, + font = current.style.font, + font_size = current.layout.font_size, + } - if entry.layer_id >= i32(ui.render_queue.num) { - append( & ui.render_queue, UI_RenderLayer {}) - render_queue = array_to_slice(ui.render_queue) - } + if current.ancestors != previous_layer { + entry_box .layer_signal = true + entry_text.layer_signal = true + } - // else if layer.last == nil { - // layer.first.next = entry - // entry.prev = layer.first - // layer.last = entry - // } - - // push_back to next layer - layer := & render_queue[entry.layer_id] - if layer.first == nil { - layer.first = entry - layer.last = entry - } - else { - layer.last.next = entry - entry.prev = layer.last - layer.last = entry - } - // dll_full_push_back( layer, entry, nil ) - - // If there is a parent entry, give it a reference to the child entry - parent_entry : ^UI_RenderEntry - if entry.layer_id > 0 { - parent_layer := & render_queue[entry.layer_id - 1] - parent_entry = parent_layer.last - entry.parent = parent_entry - - if parent_entry.first == nil { - parent_entry.first = entry - parent_entry.last = entry - } - else { - parent_entry.last = entry - } - // dll_fl_append( parent_entry, entry ) - } + array_append(& ui.render_list_box, entry_box) + array_append(& ui.render_list_text, entry_text) + previous_layer = current.ancestors } } profile_end() - profile("render_list generation") - when UI_Render_Method == .Layers - { - // render_queue overlap corrections & render_list generation - render_queue = array_to_slice(ui.render_queue) - for layer_id : i32 = 0; layer_id < i32(ui.render_queue.num); layer_id += 1 - { - layer := & ui.render_queue.data[ layer_id ] - append( & ui.render_list, UI_RenderBoxInfo { layer_signal = true }) - - to_increment, error := make( Array(^UI_RenderEntry), 4 * Kilo ) - verify( error == .None, "Faied to make to_increment array.") - - to_inc_last_iterated : i32 = 0 - for entry := layer.first; entry != nil; entry = entry.next - { - for neighbor := entry.next; neighbor != nil; neighbor = neighbor.next - { - if ! overlap_range2( entry.info.computed.bounds, neighbor.info.computed.bounds) do continue - append( & to_increment, neighbor ) - } // for neighbor := entry.next; neighbor != nil; neighbor = neighbor.next - - if entry == to_increment.data[ to_inc_last_iterated ] { - to_inc_last_iterated += 1 - } - else { - // This entry stayed in this layer, we can append the value - array_append_value( & ui.render_list, entry.info ) - } - } // for entry := layer.first; entry != nil; entry = entry.next - - // Move overlaping entries & their children's by 1 layer - to_inc_slice := array_to_slice(to_increment) - for entry in to_inc_slice - { - pop_layer := render_queue[entry.layer_id] - entry.layer_id += 1 - if entry.layer_id >= i32(ui.render_queue.num) { - append( & ui.render_queue, UI_RenderLayer {} ) - render_queue = array_to_slice(ui.render_queue) - } - push_layer := render_queue[entry.layer_id] - - // pop entry from layer - prev := entry.prev - prev.next = entry.next - if entry == pop_layer.last { - pop_layer.last = prev - } - - // push entry to next layer - if push_layer.first == nil { - push_layer.first = entry - push_layer.last = entry - } - else { - push_layer.last.next = entry - entry.prev = push_layer.last - push_layer.last = entry - entry.next = nil - } - // else if push_layer.last == nil { - // push_layer.last = entry - // entry.prev = push_layer.first - // push_layer.first.next = entry - // entry.next = nil - // } - - // increment children's layers - if entry.first != nil - { - for child := entry.first; child != nil; child = ui_render_entry_tranverse( child ) - { - pop_layer := render_queue[child.layer_id] - child.layer_id += 1 - - if child.layer_id >= i32(ui.render_queue.num) { - append( & ui.render_queue, UI_RenderLayer {}) - render_queue = array_to_slice(ui.render_queue) - } - push_layer := render_queue[child.layer_id] - - // pop from current layer - if child == pop_layer.first { - pop_layer.first = nil - } - if child == pop_layer.last { - pop_layer.last = child.prev - } - - // push_back to next layer - if push_layer.first == nil { - push_layer.first = child - push_layer.last = child - } - else { - push_layer.last.next = child - child.prev = push_layer.last - push_layer.last = child - } - - // else if push_layer.last == nil { - // push_layer.first.next = child - // child.prev = push_layer.first - // push_layer.last = child - // } - - } // for child := neighbor.first; child != nil; child = ui_render_entry_traverse_depth( child ) - } // if entry.first != nil - } // for entry in to_inc_slice - } // for & layer in render_queue - } - - render_list := array_to_slice(ui.render_list) + // render_list := array_to_slice(ui.render_list) + render_list_box := array_to_slice(ui.render_list_box) + render_list_text := array_to_slice(ui.render_list_text) } get_state().ui_context = nil } +// TODO(Ed): Review usage if at all. ui_render_entry_tranverse :: proc( entry : ^UI_RenderEntry ) -> ^UI_RenderEntry { // using state := get_state() diff --git a/code/sectr/ui/core/collision.odin b/code/sectr/ui/core/collision.odin new file mode 100644 index 0000000..02c54cb --- /dev/null +++ b/code/sectr/ui/core/collision.odin @@ -0,0 +1,29 @@ +package sectr + +UI_SpacialIndexingMethod :: enum(i32) { + QuadTree, + SpacialHash, +} + +ui_collision_register :: proc( box : ^UI_Box ) +{ + +} + +ui_collision_query :: proc ( box : ^UI_Box ) -> DLL_NodePN(UI_Box) { + return {} +} + +QuadTree_Tile :: struct { + +} + +QuadTree :: struct +{ + boundary : Range2, + +} + +SpacialHashMap :: struct { + +} \ No newline at end of file diff --git a/code/sectr/ui/core/layout_compute.odin b/code/sectr/ui/core/layout_compute.odin index ed3ec27..3d2f7ae 100644 --- a/code/sectr/ui/core/layout_compute.odin +++ b/code/sectr/ui/core/layout_compute.odin @@ -216,11 +216,17 @@ ui_box_compute_layout :: proc( box : ^UI_Box, else if .Order_Children_Bottom_To_Top in layout.flags { ui_layout_children_vertically( box, .Bottom_To_Top ) } + + if computed.fresh { + ui_collision_register( box ) + } } ui_compute_children_overall_bounds :: proc ( box : ^UI_Box ) -> ( children_bounds : Range2 ) { - for current := box.first; current != nil && current.prev != box; current = ui_box_tranverse_next_depth_first( current, parent_limit = box ) + // for current := box.first; current != nil && current.prev != box; current = ui_box_tranverse_next_depth_first( current, parent_limit = box ) + for current := ui_box_tranverse_next_depth_first( box, parent_limit = box, bypass_intersection_test = false ); current != nil; + current = ui_box_tranverse_next_depth_first( current, parent_limit = box, bypass_intersection_test = false ) { if current == box do return if ! current.computed.fresh do ui_box_compute_layout( current ) @@ -236,7 +242,9 @@ ui_compute_children_overall_bounds :: proc ( box : ^UI_Box ) -> ( children_bound ui_box_compute_layout_children :: proc( box : ^UI_Box ) { // for current := box.first; current != nil && current.prev != box; current = ui_box_tranverse_next_depth_first( current, parent_limit = box ) - for current := box.first; current != nil && current.prev != box; current = ui_box_traverse_next_breadth_first( current ) + // for current := box.first; current != nil && current.prev != box; current = ui_box_traverse_next_breadth_first( current ) + for current := ui_box_tranverse_next_depth_first( box, parent_limit = box, bypass_intersection_test = false ); current != nil; + current = ui_box_tranverse_next_depth_first( current, parent_limit = box, bypass_intersection_test = false ) { if current == box do return if current.computed.fresh do continue diff --git a/code/sectr/ui/tests.odin b/code/sectr/ui/tests.odin index d785044..dca8562 100644 --- a/code/sectr/ui/tests.odin +++ b/code/sectr/ui/tests.odin @@ -202,6 +202,7 @@ test_whitespace_ast :: proc( default_layout : ^UI_Layout, frame_style_default : line_id += 1 continue } + profile("line") ui_layout( text_layout ) line_hbox := ui_widget(str_fmt( "line %v", line_id ), {.Mouse_Clickable}) diff --git a/code/sectr/ui/widgets.odin b/code/sectr/ui/widgets.odin index 0aba946..5d42c09 100644 --- a/code/sectr/ui/widgets.odin +++ b/code/sectr/ui/widgets.odin @@ -153,7 +153,7 @@ ui_hbox_begin :: proc( direction : UI_LayoutDirection_X, label : string, flags : ui_hbox_end :: proc( hbox : UI_HBox, width_ref : ^f32 = nil, compute_layout := false ) { // profile(#procedure) - if compute_layout do ui_box_compute_layout(hbox.box, dont_mark_fresh = true) + // if compute_layout do ui_box_compute_layout(hbox.box, dont_mark_fresh = true) // ui_layout_children_horizontally( hbox.box, hbox.direction, width_ref ) } @@ -773,7 +773,7 @@ ui_vbox_begin :: proc( direction : UI_LayoutDirection_Y, label : string, flags : vbox.direction = direction vbox.box = ui_box_make( flags, label ) vbox.signal = ui_signal_from_box( vbox.box ) - if compute_layout do ui_box_compute_layout(vbox, dont_mark_fresh = true) + // if compute_layout do ui_box_compute_layout(vbox, dont_mark_fresh = true) switch direction { case .Top_To_Bottom: vbox.layout.flags |= { .Order_Children_Top_To_Bottom } @@ -786,7 +786,7 @@ ui_vbox_begin :: proc( direction : UI_LayoutDirection_Y, label : string, flags : // Auto-layout children ui_vbox_end :: proc( vbox : UI_VBox, height_ref : ^f32 = nil, compute_layout := false ) { // profile(#procedure) - if compute_layout do ui_box_compute_layout(vbox, dont_mark_fresh = true) + // if compute_layout do ui_box_compute_layout(vbox, dont_mark_fresh = true) // ui_layout_children_vertically( vbox.box, vbox.direction, height_ref ) } diff --git a/scripts/build.ps1 b/scripts/build.ps1 index 9bb4e51..ec895c5 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -202,9 +202,9 @@ push-location $path_root $build_args += $flag_use_separate_modules $build_args += $flag_thread_count + $CoreCount_Physical # $build_args += $flag_optimize_none - $build_args += $flag_optimize_minimal + # $build_args += $flag_optimize_minimal # $build_args += $flag_optimize_speed - # $build_args += $falg_optimize_aggressive + $build_args += $falg_optimize_aggressive $build_args += $flag_debug $build_args += $flag_pdb_name + $pdb $build_args += $flag_subsystem + 'windows'