From ce1d31f0d4f79bc499a88b688fe337474f5ac936 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sun, 23 Jun 2024 02:47:13 -0400 Subject: [PATCH] Progress on fleshing out rendering (Getting ui ready to render in "layer batches") --- code/font/VEFontCache/LRU.odin | 3 +- code/font/VEFontCache/VEFontCache.odin | 4 +- code/font/VEFontCache/mappings.odin | 1 + code/grime/hashmap_chained.odin | 13 +- code/sectr/colors.odin | 2 + code/sectr/engine/client_api.odin | 8 +- code/sectr/engine/render.odin | 111 +++++++++++- code/sectr/engine/update.odin | 6 +- code/sectr/ui/core/base.odin | 231 ++++++++++++++++++------- code/sectr/ui/core/box.odin | 16 +- code/sectr/ui/widgets.odin | 2 +- 11 files changed, 317 insertions(+), 80 deletions(-) diff --git a/code/font/VEFontCache/LRU.odin b/code/font/VEFontCache/LRU.odin index cbd0012..3a18901 100644 --- a/code/font/VEFontCache/LRU.odin +++ b/code/font/VEFontCache/LRU.odin @@ -172,7 +172,8 @@ LRU_reload :: proc( cache : ^LRU_Cache, allocator : Allocator ) LRU_hash_key :: #force_inline proc( key : u64 ) -> ( hash : u64 ) { bytes := transmute( [8]byte ) key - hash = fnv64a( bytes[:] ) + // hash = fnv64a( bytes[:] ) + hash = cast(u64) crc64( bytes[:] ) return } diff --git a/code/font/VEFontCache/VEFontCache.odin b/code/font/VEFontCache/VEFontCache.odin index 167f014..b1e36e2 100644 --- a/code/font/VEFontCache/VEFontCache.odin +++ b/code/font/VEFontCache/VEFontCache.odin @@ -754,8 +754,8 @@ reset_batch_codepoint_state :: proc( ctx : ^Context ) { shape_text_cached :: proc( ctx : ^Context, font : FontID, text_utf8 : string ) -> ^ShapedText { font := font - // hash := cast(u64) crc32( transmute([]u8) text_utf8 ) - hash := label_hash( text_utf8 ) + hash := cast(u64) crc32( transmute([]u8) text_utf8 ) + // hash := label_hash( text_utf8 ) shape_cache := & ctx.shape_cache state := & ctx.shape_cache.state diff --git a/code/font/VEFontCache/mappings.odin b/code/font/VEFontCache/mappings.odin index e1a3a92..e7971e6 100644 --- a/code/font/VEFontCache/mappings.odin +++ b/code/font/VEFontCache/mappings.odin @@ -1,6 +1,7 @@ package VEFontCache import "core:hash" + crc64 :: hash.crc64_xz crc32 :: hash.crc32 fnv64a :: hash.fnv64a diff --git a/code/grime/hashmap_chained.odin b/code/grime/hashmap_chained.odin index 3594933..62bb1dc 100644 --- a/code/grime/hashmap_chained.odin +++ b/code/grime/hashmap_chained.odin @@ -64,7 +64,7 @@ hmap_chained_init :: proc( $HMapChainedType : typeid/HMapChained($Type), lookup_ ) -> (table : HMapChained(Type), error : AllocatorError) { header_size := size_of(HMapChainedHeader(Type)) - size := header_size + int(lookup_capacity) * size_of( ^HMapChainedSlot(Type)) + size := header_size + int(lookup_capacity) * size_of( ^HMapChainedSlot(Type)) + size_of(int) raw_mem : rawptr raw_mem, error = alloc( size, allocator = allocator ) @@ -72,7 +72,7 @@ hmap_chained_init :: proc( $HMapChainedType : typeid/HMapChained($Type), lookup_ pool_bucket_cap := pool_bucket_cap if pool_bucket_cap == 0 { - pool_bucket_cap = cast(uint) int(lookup_capacity) * size_of( HMapChainedSlot(Type)) + pool_bucket_cap = cast(uint) int(lookup_capacity) * size_of( HMapChainedSlot(Type)) * 2 } table.header = cast( ^HMapChainedHeader(Type)) raw_mem @@ -205,7 +205,9 @@ hmap_chained_set :: proc( self : HMapChained($Type), key : u64, value : Type ) - slot_size := size_of(HMapChainedSlot(Type)) if surface_slot == nil { - block, error := pool_grab(pool, false) + raw_mem, error := alloc( size_of(HMapChainedSlot(Type)), allocator = pool.backing) + block := slice_ptr(transmute([^]byte) raw_mem, slot_size) + // block, error := pool_grab(pool, false) surface_slot := transmute( ^HMapChainedSlot(Type)) raw_data(block) surface_slot^ = {} @@ -240,8 +242,11 @@ hmap_chained_set :: proc( self : HMapChained($Type), key : u64, value : Type ) - error : AllocatorError if slot.next == nil { + raw_mem : rawptr block : []byte - block, error = pool_grab(pool, false) + raw_mem, error = alloc( size_of(HMapChainedSlot(Type)), allocator = pool.backing) + block = slice_ptr(transmute([^]byte) raw_mem, slot_size) + // block, error = pool_grab(pool, false) next := transmute( ^HMapChainedSlot(Type)) raw_data(block) slot.next = next diff --git a/code/sectr/colors.odin b/code/sectr/colors.odin index a45953a..ef01176 100644 --- a/code/sectr/colors.odin +++ b/code/sectr/colors.odin @@ -19,6 +19,8 @@ Color_Blue :: RGBA8 { 90, 90, 230, 255 } Color_Red :: RGBA8 { 230, 90, 90, 255 } Color_White :: RGBA8 { 255, 255, 255, 255 } +Color_Screen_Center_Dot :: RGBA8 { 180, 180, 180, 10} + Color_Transparent :: RGBA8 { 0, 0, 0, 0 } Color_BG :: RGBA8 { 55, 55, 60, 255 } Color_BG_TextBox :: RGBA8 { 32, 32, 32, 180 } diff --git a/code/sectr/engine/client_api.odin b/code/sectr/engine/client_api.odin index dd02443..9c0c14a 100644 --- a/code/sectr/engine/client_api.odin +++ b/code/sectr/engine/client_api.odin @@ -241,6 +241,10 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem load_action = .CLEAR, clear_value = { 0, 0, 0, 1 } } + render_data.pass_actions.empty_action.colors[0] = sokol_gfx.Color_Attachment_Action { + load_action = .LOAD, + clear_value = { 0, 0, 0, 1 } + } } // Setup sokol_gp @@ -277,7 +281,7 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem } // Setup the screen ui state - if false + if true { ui_startup( & screen_ui.base, cache_allocator = persistent_slab_allocator() ) ui_floating_startup( & screen_ui.floating, 1 * Kilobyte, 1 * Kilobyte, persistent_slab_allocator(), "screen ui floating manager" ) @@ -292,7 +296,7 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem // Demo project setup // TODO(Ed): This will eventually have to occur when the user either creates or loads a workspace. I don't know - if false + if true { using project path = str_intern("./") diff --git a/code/sectr/engine/render.odin b/code/sectr/engine/render.odin index cd9cc9e..8d8d193 100644 --- a/code/sectr/engine/render.odin +++ b/code/sectr/engine/render.odin @@ -11,13 +11,45 @@ import gp "thirdparty:sokol/gp" PassActions :: struct { bg_clear_black : gfx.Pass_Action, - + empty_action : gfx.Pass_Action, } RenderState :: struct { pass_actions : PassActions, } +gp_set_color :: #force_inline proc( color : RGBA8 ) { + color := normalize_rgba8(color); + gp.set_color( color.r, color.g, color.b, color.a ) +} + +draw_filled_circle :: proc(x, y, radius: f32, edges: int) { + if edges < 3 do return // Need at least 3 edges to form a shape + + triangles := make([]gp.Triangle, edges) + // defer delete(triangles) + + center := gp.Point{x, y} + + for i in 0.. b32 config.cam_max_zoom = 30 config.cam_zoom_sensitivity_digital = 0.04 - config.cam_min_zoom = 0.04 - config.cam_zoom_mode = .Digital + // config.cam_min_zoom = 0.04 + config.cam_zoom_mode = .Smooth switch config.cam_zoom_mode { case .Smooth: @@ -196,7 +196,7 @@ update :: proc( delta_time : f64 ) -> b32 { if is_within_screenspace(input.mouse.pos) { pan_velocity := input.mouse.delta * vec2(1, -1) * ( 1 / cam.zoom ) - cam.position -= pan_velocity + cam.position += pan_velocity } } } diff --git a/code/sectr/ui/core/base.odin b/code/sectr/ui/core/base.odin index 5093287..d587cb0 100644 --- a/code/sectr/ui/core/base.odin +++ b/code/sectr/ui/core/base.odin @@ -65,11 +65,15 @@ UI_Parent_Stack_Size :: 512 // UI_Built_Boxes_Array_Size :: 8 UI_Built_Boxes_Array_Size :: 128 * Kilobyte -UI_RenderQueueEntry :: struct { - using links : DLL_NodePN(UI_RenderQueueEntry), - box : ^UI_Box, +UI_RenderEntry :: struct { + info : UI_RenderBoxInfo, + using links : DLL_NodeFull(UI_RenderEntry), + parent : ^UI_RenderEntry, + layer_id : i32, } +UI_RenderLayer :: DLL_NodeFL(UI_RenderEntry) + // TODO(Ed): Rename to UI_Context UI_State :: struct { // TODO(Ed) : Use these? @@ -83,7 +87,7 @@ UI_State :: struct { curr_cache : ^HMapZPL( UI_Box ), // render_queue_builder : SubArena, - render_queue : DLL_NodeFL(UI_RenderQueueEntry), + render_queue : Array(UI_RenderLayer), render_list : Array(UI_RenderBoxInfo), null_box : ^UI_Box, // This was used with the Linked list interface... @@ -146,7 +150,6 @@ ui_reload :: proc( ui : ^ UI_State, cache_allocator : Allocator ) ui_shutdown :: proc() { } - ui_graph_build_begin :: proc( ui : ^ UI_State, bounds : Vec2 = {} ) { profile(#procedure) @@ -195,76 +198,186 @@ ui_graph_build_end :: proc( ui : ^UI_State ) computed.content = computed.bounds } + // Auto-layout and initial render_queue generation + render_queue := array_to_slice(ui.render_queue) for current := root.first; current != nil; current = ui_box_tranverse_next( current ) { if ! current.computed.fresh { ui_box_compute_layout( current ) } + + // 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, + }, + layer_id = current.ancestors -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_back to next layer + layer := & render_queue[entry.layer_id] + if layer.first == nil { + layer.first = entry + } + else if layer.last == nil { + layer.first.next = entry + entry.prev = layer.first + 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 + + // dll_fl_append( parent_entry, entry ) + + if parent_entry.first == nil { + parent_entry.first = entry + } + else { + parent_entry.last = entry + } + } } - + // render_queue overlap corrections & render_list generation + render_queue = array_to_slice(ui.render_queue) + for & layer in render_queue + { + to_increment, error := make( Array(^UI_RenderEntry), 4 * Kilo ) + verify( error == .None, "Faied to make to_increment array.") - // Render order is "layer-based" and thus needs a new traversal done - // ui.render_queue.first = new(UI_RenderQueueEntry) - // ui.render_queue.first.box = root.first - // for current := root.first.next; current != nil; current = ui_box_traverse_next_layer_based( current ) - // { - // entry := new(UI_RenderQueueEntry) - // entry.box = current - // // Enqueue box to render - // // Would be best to use a linked list tied to a sub-arena - // dll_push_back( & ui.render_queue.first, entry ) - // ui.render_queue.last = entry - // } + for entry := layer.first; entry != nil; entry = entry.next + { + for neighbor := entry.next; neighbor != nil; neighbor = neighbor.next + { + if ! intersects_range2( entry.info.computed.bounds, neighbor.info.computed.bounds) do continue - // enqueued_id : i32 = 0 - // for enqueued := ui.render_queue.first; enqueued != nil; enqueued = enqueued.next - // { - // /* - // For each enqueue box: - // 1. Check to see if any overlap (traverse all Queued Entries and check to see if their bounds overlap) - // 2. If they do, the box & its children must be popped off the current ancestry layer and moved to the start of the next - // (This could problably be an iteration where we just pop and push_back until we reach the adjacent box of the same layer) - // */ - // // other_id : i32 = 0 - // for other := enqueued.next; other != nil; other = other.next - // { - // if intersects_range2( enqueued.box.computed.bounds, other.box.computed.bounds) - // { - // // Intersection occured, other's box is on top, it and its children must be deferred to the next layer - // next_layer_start := other.next - // for ; next_layer_start != nil; next_layer_start = next_layer_start.next - // { - // if next_layer_start.box == other.box.first do break - // } + append( & to_increment, neighbor ) + } // for neighbor := entry.next; neighbor != nil; neighbor = neighbor.next + } // for entry := layer.first; entry != nil; entry = entry.next - // other.next = next_layer_start - // other.prev = next_layer_start.prev - // next_layer_start.prev = other - // enqueued.next = other.prev - // break - // } - // } - // } + 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] - // Queue should be optimized now to generate the final draw list - // for enqueued := ui.render_queue.first; enqueued != nil; enqueued = enqueued.next - // { - // using enqueued.box - // // Enqueue for rendering - // array_append( & ui.render_list, UI_RenderBoxInfo { - // computed, - // style, - // text, - // layout.font_size, - // layout.border_width, - // }) - // } + + // 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 + } + else if push_layer.last == nil { + push_layer.last = entry + entry.prev = push_layer.first + push_layer.first.next = entry + entry.next = nil + } + else { + push_layer.last.next = entry + entry.prev = push_layer.last + push_layer.last = 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 + } + else if push_layer.last == nil { + push_layer.first.next = child + child.prev = push_layer.first + push_layer.last = child + } + else { + push_layer.last.next = child + child.prev = push_layer.last + 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 } get_state().ui_context = nil } +ui_render_entry_tranverse :: proc( entry : ^UI_RenderEntry ) -> ^UI_RenderEntry +{ + // using state := get_state() + parent := entry.parent + if parent != nil + { + if parent.last != entry + { + if entry.next != nil do return entry.next + } + } + + // If there any children, do them next. + if entry.first != nil { + return entry.first + } + + // Iteration exhausted + return nil +} + @(deferred_in = ui_graph_build_end) ui_graph_build :: #force_inline proc( ui : ^ UI_State ) { ui_graph_build_begin( ui ) } diff --git a/code/sectr/ui/core/box.odin b/code/sectr/ui/core/box.odin index abeeb32..f7e08c4 100644 --- a/code/sectr/ui/core/box.odin +++ b/code/sectr/ui/core/box.odin @@ -124,15 +124,18 @@ ui_box_make :: proc( flags : UI_BoxFlags, label : string ) -> (^ UI_Box) ui_prev_cached_box :: #force_inline proc( box : ^UI_Box ) -> ^UI_Box { return hmap_zpl_get( ui_context().prev_cache, cast(u64) box.key ) } +// TODO(Ed): Rename to ui_box_tranverse_view_next // Traveral pritorizes immeidate children -ui_box_tranverse_next :: proc "contextless" ( box : ^ UI_Box ) -> (^ UI_Box) +ui_box_tranverse_next :: proc "contextless" ( box : ^ UI_Box, bypass_intersection_test := false ) -> (^ UI_Box) { using state := get_state() // If current has children, do them first if box.first != nil { // Check to make sure parent is present on the screen, if its not don't bother. - is_app_ui := ui_context == & screen_ui + if bypass_intersection_test { + return box.first + } if intersects_range2( ui_view_bounds(), box.computed.bounds) { return box.first } @@ -156,7 +159,7 @@ ui_box_tranverse_next :: proc "contextless" ( box : ^ UI_Box ) -> (^ UI_Box) } // Traveral pritorizes traversing a "anestry layer" -ui_box_traverse_next_layer_based :: proc "contextless" ( box : ^UI_Box, skip_intersection_test := false ) -> (^UI_Box) +ui_box_traverse_next_layer_based :: proc "contextless" ( box : ^UI_Box, bypass_intersection_test := false ) -> (^UI_Box) { using state := get_state() @@ -166,8 +169,8 @@ ui_box_traverse_next_layer_based :: proc "contextless" ( box : ^UI_Box, skip_int if parent.last != box { if box.next != nil do return box.next - // There are no more adjacent nodes } + // There are no more adjacent nodes } // Either is root OR @@ -176,7 +179,10 @@ ui_box_traverse_next_layer_based :: proc "contextless" ( box : ^UI_Box, skip_int if box.first != nil { // Check to make sure parent is present on the screen, if its not don't bother. - if ! skip_intersection_test && intersects_range2( ui_view_bounds(), box.computed.bounds) { + if bypass_intersection_test { + return box.first + } + if intersects_range2( ui_view_bounds(), box.computed.bounds) { return box.first } } diff --git a/code/sectr/ui/widgets.odin b/code/sectr/ui/widgets.odin index ba23fe7..ce71cf6 100644 --- a/code/sectr/ui/widgets.odin +++ b/code/sectr/ui/widgets.odin @@ -4,7 +4,7 @@ import "base:runtime" import lalg "core:math/linalg" // Problably cursed way to setup a 'scope' for a widget -ui_build :: #force_inline proc( captures : $Type, $maker : #type proc(captures : Type) -> $ReturnType ) -> ReturnType { return maker(captures) } +// ui_build :: #force_inline proc( captures : $Type, $maker : #type proc(captures : Type) -> $ReturnType ) -> ReturnType { return maker(captures) } UI_Widget :: struct { using box : ^UI_Box,