From 59ed4d9dd67405ced0f1fcd2325b2f27c1f69fe5 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Wed, 19 Jun 2024 02:34:02 -0400 Subject: [PATCH] fix hot-reload, starting to setup proper rendering again. * Added hot_reload, measure_text_size to VEFontCache --- code/font/VEFontCache/LRU.odin | 25 +- code/font/VEFontCache/VEFontCache.odin | 68 ++++ code/font/VEFontCache/draw.odin | 2 + code/font/VEFontCache/mappings.odin | 4 + code/grime/hot_reload.odin | 6 +- code/sectr/colors.odin | 14 +- ...nder_testing.odin => .render_testing.odin} | 2 - code/sectr/engine/client_api.odin | 25 +- .../engine/client_api_sokol_callbacks.odin | 2 +- code/sectr/engine/render.odin | 336 ++++++++++++++++++ code/sectr/font/provider_VEFontCache.odin | 121 ++++--- scripts/build.ps1 | 2 +- 12 files changed, 549 insertions(+), 58 deletions(-) rename code/sectr/engine/{render_testing.odin => .render_testing.odin} (99%) create mode 100644 code/sectr/engine/render.odin diff --git a/code/font/VEFontCache/LRU.odin b/code/font/VEFontCache/LRU.odin index b815cdd..9518bd3 100644 --- a/code/font/VEFontCache/LRU.odin +++ b/code/font/VEFontCache/LRU.odin @@ -49,6 +49,17 @@ pool_list_init :: proc( pool : ^PoolList, capacity : u32 ) back = -1 } +pool_list_free :: proc( pool : ^PoolList ) +{ + +} + +pool_list_reload :: proc( pool : ^PoolList, allocator : Allocator ) +{ + pool.items.backing = allocator + pool.free_list.backing = allocator +} + pool_list_push_front :: proc( pool : ^PoolList, value : PoolListValue ) { using pool @@ -134,6 +145,17 @@ LRU_init :: proc( cache : ^LRU_Cache, capacity : u32 ) { pool_list_init( & cache.key_queue, capacity ) } +LRU_free :: proc( cache : ^LRU_Cache ) +{ + +} + +LRU_reload :: proc( cache : ^LRU_Cache, allocator : Allocator ) +{ + hmap_zpl_reload( & cache.table, allocator ) + pool_list_reload( & cache.key_queue, allocator ) +} + LRU_hash_key :: #force_inline proc( key : u64 ) -> ( hash : u64 ) { bytes := transmute( [8]byte ) key hash = fnv64a( bytes[:] ) @@ -172,7 +194,8 @@ LRU_peek :: proc( cache : ^LRU_Cache, key : u64 ) -> i32 { return iter.value } -LRU_put :: proc( cache : ^LRU_Cache, key : u64, value : i32 ) -> u64 { +LRU_put :: proc( cache : ^LRU_Cache, key : u64, value : i32 ) -> u64 +{ hash_key := LRU_hash_key( key ) iter := get( & cache.table, hash_key ) if iter != nil { diff --git a/code/font/VEFontCache/VEFontCache.odin b/code/font/VEFontCache/VEFontCache.odin index f12e3e0..1edc9cb 100644 --- a/code/font/VEFontCache/VEFontCache.odin +++ b/code/font/VEFontCache/VEFontCache.odin @@ -355,6 +355,43 @@ init :: proc( ctx : ^Context, parser_kind : ParserKind, shaper_init( & shaper_ctx ) } +hot_reload :: proc( ctx : ^Context, allocator : Allocator ) +{ + ctx.backing = allocator + context.allocator = ctx.backing + + using ctx + + entries.backing = allocator + temp_path.backing = allocator + hmap_zpl_reload( & temp_codepoint_seen, allocator ) + draw_list.vertices.backing = allocator + draw_list.indices.backing = allocator + draw_list.calls.backing = allocator + + LRU_reload( & atlas.region_a.state, allocator) + LRU_reload( & atlas.region_b.state, allocator) + LRU_reload( & atlas.region_c.state, allocator) + LRU_reload( & atlas.region_d.state, allocator) + + LRU_reload( & shape_cache.state, allocator ) + for idx : u32 = 0; idx < u32(shape_cache.storage.capacity); idx += 1 { + stroage_entry := & shape_cache.storage.data[idx] + using stroage_entry + + glyphs.backing = allocator + positions.backing = allocator + } + + atlas.draw_list.calls.backing = allocator + atlas.draw_list.indices.backing = allocator + atlas.draw_list.vertices.backing = allocator + + atlas.clear_draw_list.calls.backing = allocator + atlas.clear_draw_list.indices.backing = allocator + atlas.clear_draw_list.vertices.backing = allocator +} + // ve_foncache_shutdown shutdown :: proc( ctx : ^Context ) { @@ -679,6 +716,37 @@ is_empty :: proc( ctx : ^Context, entry : ^Entry, glyph_index : Glyph ) -> b32 return false } +measure_text_size :: proc( ctx : ^Context, font : FontID, text_utf8 : string ) -> (measured : Vec2) +{ + assert( ctx != nil ) + assert( font >= 0 && font < FontID(ctx.entries.num) ) + + atlas := ctx.atlas + + shaped := shape_text_cached( ctx, font, text_utf8 ) + + entry := & ctx.entries.data[ font ] + + batch_start_idx : i32 = 0 + for index : i32 = 0; index < i32(shaped.glyphs.num); index += 1 + { + glyph_index := shaped.glyphs.data[ index ] + if is_empty( ctx, entry, glyph_index ) do continue + + bounds_0, bounds_1 := parser_get_glyph_box( & entry.parser_info, glyph_index ) + bounds_width := bounds_1.x - bounds_0.x + bounds_height := bounds_1.y - bounds_0.y + + bounds := Vec2 { + cast(f32) cast(u32) (f32(bounds_width) * entry.size_scale - f32(atlas.glyph_padding)), + cast(f32) cast(u32) (f32(bounds_height) * entry.size_scale - f32(atlas.glyph_padding)), + } + measured += bounds + } + + return measured +} + reset_batch_codepoint_state :: proc( ctx : ^Context ) { clear( & ctx.temp_codepoint_seen ) ctx.temp_codepoint_seen_num = 0 diff --git a/code/font/VEFontCache/draw.odin b/code/font/VEFontCache/draw.odin index d264dc2..8c17539 100644 --- a/code/font/VEFontCache/draw.odin +++ b/code/font/VEFontCache/draw.odin @@ -289,6 +289,8 @@ draw_text :: proc( ctx : ^Context, font : FontID, text_utf8 : string, position : assert( ctx != nil ) assert( font >= 0 && font < FontID(ctx.entries.num) ) + context.allocator = ctx.backing + shaped := shape_text_cached( ctx, font, text_utf8 ) snap_width := f32(ctx.snap_width) diff --git a/code/font/VEFontCache/mappings.odin b/code/font/VEFontCache/mappings.odin index 0ba7ac6..fb31d57 100644 --- a/code/font/VEFontCache/mappings.odin +++ b/code/font/VEFontCache/mappings.odin @@ -108,6 +108,10 @@ make :: proc { hmap_zpl_init, } +// reload :: proc { + +// } + remove_at :: proc { array_remove_at, } diff --git a/code/grime/hot_reload.odin b/code/grime/hot_reload.odin index f0af0a8..bd84cd0 100644 --- a/code/grime/hot_reload.odin +++ b/code/grime/hot_reload.odin @@ -2,17 +2,17 @@ package grime import "base:runtime" -reload_array :: proc( self : [dynamic]$Type, allocator : Allocator ) { +reload_array :: proc( self : ^[dynamic]$Type, allocator : Allocator ) { raw := transmute(runtime.Raw_Dynamic_Array) self raw.allocator = allocator } -reload_queue :: proc( self : Queue($Type), allocator : Allocator ) { +reload_queue :: proc( self : ^Queue($Type), allocator : Allocator ) { raw_array := transmute(runtime.Raw_Dynamic_Array) self.data raw_array.allocator = allocator } -reload_map :: proc( self : map [$KeyType] $EntryType, allocator : Allocator ) { +reload_map :: proc( self : ^map [$KeyType] $EntryType, allocator : Allocator ) { raw := transmute(runtime.Raw_Map) self raw.allocator = allocator } diff --git a/code/sectr/colors.odin b/code/sectr/colors.odin index 622e71b..07a0907 100644 --- a/code/sectr/colors.odin +++ b/code/sectr/colors.odin @@ -1,6 +1,18 @@ package sectr -RGBA8 :: struct { r, g, b, a : u8 } +RGBA8 :: struct { r, g, b, a : u8 } +RGBAN :: [4]f32 + +normalize_rgba8 :: #force_inline proc( color : RGBA8 ) -> RGBAN { + result := RGBAN { + 1.0 / f32(color.r), + 1.0 / f32(color.g), + 1.0 / f32(color.b), + 1.0 / f32(color.a), + } + return result +} + Color_Blue :: RGBA8 { 90, 90, 230, 255 } Color_Red :: RGBA8 { 230, 90, 90, 255 } Color_White :: RGBA8 { 255, 255, 255, 255 } diff --git a/code/sectr/engine/render_testing.odin b/code/sectr/engine/.render_testing.odin similarity index 99% rename from code/sectr/engine/render_testing.odin rename to code/sectr/engine/.render_testing.odin index 27bb7bd..14d0d5c 100644 --- a/code/sectr/engine/render_testing.odin +++ b/code/sectr/engine/.render_testing.odin @@ -43,8 +43,6 @@ render :: proc() do_nothing : bool do_nothing = false - time.sleep(10000) - // The below are most likely limited to a "depth layer" and so // different depth layers need different draw pass combos (of the 3 constructive passes) // Will need to profile how expensive it is for batching with the UI box rendering diff --git a/code/sectr/engine/client_api.odin b/code/sectr/engine/client_api.odin index 25f1911..c529df2 100644 --- a/code/sectr/engine/client_api.odin +++ b/code/sectr/engine/client_api.odin @@ -112,19 +112,19 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem using input_events error : AllocatorError - events, error = make( Queue(InputEvent), 4 * Kilo, persistent_allocator() ) + events, error = make( Queue(InputEvent), 4 * Kilo, persistent_slab_allocator() ) ensure(error == AllocatorError.None, "Failed to allocate input.events array") - key_events, error = make( Queue(InputKeyEvent), Kilo, persistent_allocator() ) + key_events, error = make( Queue(InputKeyEvent), Kilo, persistent_slab_allocator() ) ensure(error == AllocatorError.None, "Failed to allocate key_events array") - mouse_events, error = make( Queue(InputMouseEvent), 2 * Kilo, persistent_allocator() ) + mouse_events, error = make( Queue(InputMouseEvent), 2 * Kilo, persistent_slab_allocator() ) ensure(error == AllocatorError.None, "Failed to allocate mouse_events array") - codes_pressed, error = make( Array(rune), Kilo, persistent_allocator() ) + codes_pressed, error = make( Array(rune), Kilo, persistent_slab_allocator() ) ensure(error == AllocatorError.None, "Failed to allocate codes_pressed array") - staged_input_events, error = make( Array(InputEvent), 8 * Kilo, persistent_allocator() ) + staged_input_events, error = make( Array(InputEvent), 8 * Kilo, persistent_slab_allocator() ) ensure(error == AllocatorError.None, "Failed to allocate input_staged_events array") } @@ -416,9 +416,16 @@ hot_reload :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_ slab_reload( persistent_slab, persistent_allocator() ) // input_reload() + { + using input_events + reload( & events, persistent_slab_allocator()) + reload( & key_events, persistent_slab_allocator()) + reload( & mouse_events, persistent_slab_allocator()) + codes_pressed.backing = persistent_slab_allocator() + staged_input_events.backing = persistent_slab_allocator() + } - // font_provider_reload() - hmap_chained_reload( font_provider_data.font_cache, persistent_allocator()) + font_provider_reload() str_cache_reload( & string_cache, persistent_allocator(), persistent_allocator() ) str_cache_set_module_ctx( & string_cache ) @@ -512,7 +519,7 @@ tick_work_frame :: #force_inline proc( host_delta_time_ms : f64 ) -> b32 } // Lifted out of tick so that sokol_app_frame_callback can do it as well. -tick_frametime :: #force_inline proc( client_tick : ^time.Tick, host_delta_time_ms : f64, host_delta_ns : Duration ) +tick_frametime :: #force_inline proc( client_tick : ^time.Tick, host_delta_time_ms : f64, host_delta_ns : Duration, can_sleep := true ) { profile(#procedure) state := get_state(); using state @@ -536,7 +543,7 @@ tick_frametime :: #force_inline proc( client_tick : ^time.Tick, host_delta_time_ sleep_ms := frametime_target_ms - frametime_elapsed_ms pre_sleep_tick := time.tick_now() - if sleep_ms > 0 { + if can_sleep && sleep_ms > 0 { thread_sleep( cast(Duration) sleep_ms * MS_To_NS ) // thread__highres_wait( sleep_ms ) } diff --git a/code/sectr/engine/client_api_sokol_callbacks.odin b/code/sectr/engine/client_api_sokol_callbacks.odin index 68ef894..3334bb9 100644 --- a/code/sectr/engine/client_api_sokol_callbacks.odin +++ b/code/sectr/engine/client_api_sokol_callbacks.odin @@ -45,7 +45,7 @@ sokol_app_frame_callback :: proc "c" () should_close |= tick_work_frame( sokol_delta_ms ) profile_end() - tick_frametime( & client_tick, sokol_delta_ms, sokol_delta_ns ) + tick_frametime( & client_tick, sokol_delta_ms, sokol_delta_ns, can_sleep = false ) window.resized = false } diff --git a/code/sectr/engine/render.odin b/code/sectr/engine/render.odin new file mode 100644 index 0000000..7e20c28 --- /dev/null +++ b/code/sectr/engine/render.odin @@ -0,0 +1,336 @@ +package sectr + +import "core:math" +import "core:time" + +import ve "codebase:font/VEFontCache" +import sokol_app "thirdparty:sokol/app" +import gfx "thirdparty:sokol/gfx" +import sokol_glue "thirdparty:sokol/glue" +import gp "thirdparty:sokol/gp" + +PassActions :: struct { + bg_clear_black : gfx.Pass_Action, + +} + +RenderState :: struct { + pass_actions : PassActions, +} + +// Draw text using a string and normalized screen coordinates +draw_text_string_pos_norm :: proc( content : string, id : FontID, size : f32, pos : Vec2, color := Color_White ) +{ + state := get_state(); using state + width := app_window.extent.x * 2 + height := app_window.extent.y * 2 + + ve_id := font_provider_resolve_draw_id( id ) + color_norm := normalize_rgba8(color) + + ve.set_colour( & font_provider_data.ve_font_cache, color_norm ) + ve.draw_text( & font_provider_data.ve_font_cache, ve_id, content, pos, Vec2{1 / width, 1 / height} ) + return +} + +// Draw text using a string and extent-based screen coordinates +draw_text_string_pos_extent :: proc( content : string, id : FontID, size : f32, pos : Vec2, color := Color_White ) +{ + state := get_state(); using state + extent := app_window.extent + + normalized_pos := pos / extent + draw_text_string_pos_norm( content, id, size, normalized_pos, color ) +} + +render :: proc() +{ + profile(#procedure) + state := get_state(); using state // TODO(Ed): Prefer passing static context to through the callstack + + + // TODO(Ed): Eventually we want to only update when state is dirty/user has done an action + gfx.begin_pass(gfx.Pass { action = render_data.pass_actions.bg_clear_black, swapchain = sokol_glue.swapchain() }) + gfx.end_pass(); + + // render_mode_3d() + + render_mode_2d_workspace() + render_mode_screenspace() + + gfx.commit() + ve.flush_draw_list( & font_provider_data.ve_font_cache ) +} + +// TODO(Ed): Eventually this needs to become a 'viewport within a UI' +// This would allow the user to have more than one workspace open at the same time +render_mode_2d_workspace :: proc() +{ + profile(#procedure) + state := get_state(); using state // TODO(Ed): Prefer passing static context to through the callstack + cam := & project.workspace.cam +} + +render_mode_screenspace :: proc() +{ + profile(#procedure) + state := get_state(); using state // TODO(Ed): Prefer passing static context to through the callstack + replay := & Memory_App.replay + cam := & project.workspace.cam + win_extent := state.app_window.extent + + ve.configure_snap( & font_provider_data.ve_font_cache, u32(state.app_window.extent.x * 2.0), u32(state.app_window.extent.y * 2.0) ) + + render_screen_ui() + + debug_draw_text :: proc( content : string, pos : Vec2, size : f32, color := Color_White, font : FontID = Font_Default ) + { + state := get_state(); using state + + if len( content ) == 0 { + return + } + + font := font + if font.key == Font_Default.key { + font = default_font + } + // pos := screen_to_render_pos(pos) + + draw_text_string_pos_extent( content, font, size, pos, color ) + } + + debug_text :: proc( format : string, args : ..any ) + { + @static draw_text_scratch : [Kilobyte * 8]u8 + + state := get_state(); using state + if debug.draw_debug_text_y > 800 { + debug.draw_debug_text_y = 0 + } + + cam := & project.workspace.cam + screen_corners := screen_get_corners() + + position := screen_corners.top_right + position.x -= app_window.extent.x + position.y -= debug.draw_debug_text_y + + content := str_fmt_buffer( draw_text_scratch[:], format, ..args ) + debug_draw_text( content, position, 12.0 ) + + debug.draw_debug_text_y += 14 + } + + // "Draw text" using immediate mode api + { + font_provider := & state.font_provider_data + using font_provider + + @static index : i32 + text_test_str := str_fmt("frametime : %0.6f\nframetime(sokol): %0.2f\nframe id : %d\nsokol_frame: %d", frametime_delta_ms, sokol_app.frame_delta() * S_To_MS, frame, sokol_app.frame_count() ) + // log(text_test_str) + // text_test_str := str_fmt("HELLO VE FONT CACHE!") + // text_test_str := str_fmt("C") + + // font_provider := & state.font_provider_data + // fdef := hmap_chained_get( font_cache, default_font.key ) + + width := app_window.extent.x * 2 + height := app_window.extent.y * 2 + + ve.set_colour( & ve_font_cache, { 1.0, 1.0, 1.0, 1.0 } ) + ve.configure_snap( & ve_font_cache, u32(state.app_window.extent.x * 2.0), u32(state.app_window.extent.y * 2.0) ) + + ve.draw_text( & ve_font_cache, font_provider_resolve_draw_id(default_font), text_test_str, {0.0, 0.975}, Vec2{1 / width, 1 / height} ) + } + + debug.debug_text_vis = true + if debug.debug_text_vis + { + fps_msg := str_fmt( "FPS: %f", fps_avg) + fps_msg_width := cast(f32) u32(measure_text_size( fps_msg, default_font, 12.0, 0.0 ).x) + 0.5 + fps_msg_pos := screen_get_corners().top_right - { fps_msg_width, 0 } - { 5, 5 } + debug_draw_text( fps_msg, fps_msg_pos, 12.0, color = Color_White ) + // debug_draw_text( fps_msg, {}, 12.0, color = Color_White ) + + render_text_layer() + } + + debug.draw_debug_text_y = 14 +} + +render_screen_ui :: proc() +{ + profile(#procedure) + state := get_state(); using state // TODO(Ed): Prefer passing static context to through the callstack + + +} + +render_text_layer :: proc() +{ + profile("VEFontCache: render frame") + + Bindings :: gfx.Bindings + Range :: gfx.Range + ShaderStage :: gfx.Shader_Stage + + state := get_state(); using state + font_provider := state.font_provider_data + using font_provider + + ve.optimize_draw_list( & ve_font_cache ) + draw_list := ve.get_draw_list( & ve_font_cache ) + + draw_list_vert_slice := array_to_slice(draw_list.vertices) + draw_list_index_slice := array_to_slice(draw_list.indices) + + gfx.update_buffer( draw_list_vbuf, Range{ draw_list.vertices.data, draw_list.vertices.num * size_of(ve.Vertex) }) + gfx.update_buffer( draw_list_ibuf, Range{ draw_list.indices.data, draw_list.indices.num * size_of(u32) }) + + draw_list_call_slice := array_to_slice(draw_list.calls) + for & draw_call in array_to_slice(draw_list.calls) + { + watch := draw_call + // profile("VEFontCache: draw call") + + switch draw_call.pass + { + // 1. Do the glyph rendering pass + // Glyphs are first rendered to an intermediate 2k x 512px R8 texture + case .Glyph: + // profile("VEFontCache: draw call: glyph") + if (draw_call.end_index - draw_call.start_index) == 0 && ! draw_call.clear_before_draw { + continue + } + + width := ve_font_cache.atlas.buffer_width + height := ve_font_cache.atlas.buffer_height + + pass := glyph_pass + if draw_call.clear_before_draw { + pass.action.colors[0].load_action = .CLEAR + pass.action.colors[0].clear_value.a = 1.0 + } + gfx.begin_pass( pass ) + + // sokol_gfx.apply_viewport( 0,0, width, height, origin_top_left = true ) + // sokol_gfx.apply_scissor_rect( 0,0, width, height, origin_top_left = true ) + + gfx.apply_pipeline( glyph_pipeline ) + + bindings := Bindings { + vertex_buffers = { + 0 = draw_list_vbuf, + }, + vertex_buffer_offsets = { + 0 = 0, + }, + index_buffer = draw_list_ibuf, + index_buffer_offset = 0,//i32(draw_call.start_index) * size_of(u32), + fs = {}, + } + gfx.apply_bindings( bindings ) + + // 2. Do the atlas rendering pass + // A simple 16-tap box downsample shader is then used to blit from this intermediate texture to the final atlas location + case .Atlas: + // profile("VEFontCache: draw call: atlas") + if (draw_call.end_index - draw_call.start_index) == 0 && ! draw_call.clear_before_draw { + continue + } + + width := ve_font_cache.atlas.width + height := ve_font_cache.atlas.height + + pass := atlas_pass + if draw_call.clear_before_draw { + pass.action.colors[0].load_action = .CLEAR + pass.action.colors[0].clear_value.a = 1.0 + } + gfx.begin_pass( pass ) + + // sokol_gfx.apply_viewport( 0, 0, width, height, origin_top_left = true ) + // sokol_gfx.apply_scissor_rect( 0, 0, width, height, origin_top_left = true ) + + gfx.apply_pipeline( atlas_pipeline ) + + fs_uniform := Ve_Blit_Atlas_Fs_Params { region = cast(i32) draw_call.region } + gfx.apply_uniforms( ShaderStage.FS, SLOT_ve_blit_atlas_fs_params, Range { & fs_uniform, size_of(Ve_Blit_Atlas_Fs_Params) }) + + gfx.apply_bindings(Bindings { + vertex_buffers = { + 0 = draw_list_vbuf, + }, + vertex_buffer_offsets = { + 0 = 0, + }, + index_buffer = draw_list_ibuf, + index_buffer_offset = 0,//i32(draw_call.start_index) * size_of(u32), + fs = { + images = { SLOT_ve_blit_atlas_src_texture = glyph_rt_color, }, + samplers = { SLOT_ve_blit_atlas_src_sampler = glyph_rt_sampler, }, + }, + }) + + // 3. Use the atlas to then render the text. + case .None: fallthrough + case .Target: fallthrough + case .Target_Uncached: + if (draw_call.end_index - draw_call.start_index) == 0 && ! draw_call.clear_before_draw { + continue + } + + // profile("VEFontCache: draw call: target") + width := u32(app_window.extent.x * 2) + height := u32(app_window.extent.y * 2) + + pass := screen_pass + pass.swapchain = sokol_glue.swapchain() + gfx.begin_pass( pass ) + + // sokol_gfx.apply_viewport( 0, 0, width, height, origin_top_left = true ) + // sokol_gfx.apply_scissor_rect( 0, 0, width, height, origin_top_left = true ) + + gfx.apply_pipeline( screen_pipeline ) + + src_rt := atlas_rt_color + src_sampler := atlas_rt_sampler + + fs_target_uniform := Ve_Draw_Text_Fs_Params { + down_sample = 0, + colour = {1.0, 1.0, 1.0, 1}, + } + + if draw_call.pass == .Target_Uncached { + fs_target_uniform.down_sample = 1 + src_rt = glyph_rt_color + src_sampler = glyph_rt_sampler + } + gfx.apply_uniforms( ShaderStage.FS, SLOT_ve_draw_text_fs_params, Range { & fs_target_uniform, size_of(Ve_Draw_Text_Fs_Params) }) + + gfx.apply_bindings(Bindings { + vertex_buffers = { + 0 = draw_list_vbuf, + }, + vertex_buffer_offsets = { + 0 = 0, + }, + index_buffer = draw_list_ibuf, + index_buffer_offset = 0,//i32(draw_call.start_index) * size_of(u32), + fs = { + images = { SLOT_ve_draw_text_src_texture = src_rt, }, + samplers = { SLOT_ve_draw_text_src_sampler = src_sampler, }, + }, + }) + } + + if (draw_call.end_index - draw_call.start_index) != 0 { + num_indices := draw_call.end_index - draw_call.start_index + gfx.draw( draw_call.start_index, num_indices, 1 ) + } + + gfx.end_pass() + } +} diff --git a/code/sectr/font/provider_VEFontCache.odin b/code/sectr/font/provider_VEFontCache.odin index 90fe472..6405dd1 100644 --- a/code/sectr/font/provider_VEFontCache.odin +++ b/code/sectr/font/provider_VEFontCache.odin @@ -1,5 +1,6 @@ package sectr +import "core:math" import "core:os" import ve "codebase:font/VEFontCache" import sokol_gfx "thirdparty:sokol/gfx" @@ -22,8 +23,10 @@ FontID :: struct { } FontDef :: struct { - path_file : string, - ve_id : ve.FontID, + path_file : string, + default_size : i32, + size_table : [Font_Largest_Px_Size / Font_Size_Interval] ve.FontID, + // ve_id : ve.FontID, } FontProviderData :: struct @@ -150,7 +153,7 @@ font_provider_startup :: proc() pixel_format = .R8, write_mask = .RGBA, blend = BlendState { - enabled = true, + enabled = true, src_factor_rgb = .ONE_MINUS_DST_COLOR, dst_factor_rgb = .ONE_MINUS_SRC_COLOR, op_rgb = BlendOp.ADD, @@ -169,11 +172,11 @@ font_provider_startup :: proc() }, color_count = 1, depth = { - pixel_format = .DEPTH, - compare = .ALWAYS, + pixel_format = .DEPTH, + compare = .ALWAYS, write_enabled = false, }, - cull_mode = .NONE, + cull_mode = .NONE, sample_count = 1, // label = }) @@ -213,18 +216,17 @@ font_provider_startup :: proc() min_filter = Filter.NEAREST, mag_filter = Filter.NEAREST, mipmap_filter = Filter.NONE, - wrap_u = .CLAMP_TO_EDGE, - wrap_v = .CLAMP_TO_EDGE, - min_lod = -1000.0, - max_lod = 1000.0, + wrap_u = .CLAMP_TO_EDGE, + wrap_v = .CLAMP_TO_EDGE, + min_lod = -1000.0, + max_lod = 1000.0, border_color = BorderColor.OPAQUE_BLACK, - compare = .NEVER + compare = .NEVER }) verify( sokol_gfx.query_sampler_state( glyph_rt_sampler) < ResourceState.FAILED, "Failed to make atlas_rt_sampler" ) color_attach := AttachmentDesc { - image = glyph_rt_color, - // mip_level = 1, + image = glyph_rt_color, } glyph_attachments := sokol_gfx.make_attachments({ @@ -242,19 +244,18 @@ font_provider_startup :: proc() 0 = { load_action = .LOAD, store_action = .STORE, - // clear_value = {0.01,0.01,0.01,1}, clear_value = {0.00, 0.00, 0.00, 1.00}, } }, depth = { - load_action = .DONTCARE, + load_action = .DONTCARE, store_action = .DONTCARE, - clear_value = 0.0, + clear_value = 0.0, }, stencil = { - load_action = .DONTCARE, + load_action = .DONTCARE, store_action = .DONTCARE, - clear_value = 0, + clear_value = 0, } } @@ -290,7 +291,7 @@ font_provider_startup :: proc() pixel_format = .R8, write_mask = .RGBA, blend = BlendState { - enabled = true, + enabled = true, src_factor_rgb = .SRC_ALPHA, dst_factor_rgb = .ONE_MINUS_SRC_ALPHA, op_rgb = BlendOp.ADD, @@ -309,11 +310,11 @@ font_provider_startup :: proc() }, color_count = 1, depth = { - pixel_format = .DEPTH, - compare = .ALWAYS, + pixel_format = .DEPTH, + compare = .ALWAYS, write_enabled = false, }, - cull_mode = .NONE, + cull_mode = .NONE, sample_count = 1, }) } @@ -352,12 +353,12 @@ font_provider_startup :: proc() min_filter = Filter.NEAREST, mag_filter = Filter.NEAREST, mipmap_filter = Filter.NONE, - wrap_u = .CLAMP_TO_EDGE, - wrap_v = .CLAMP_TO_EDGE, - min_lod = -1000.0, - max_lod = 1000.0, + wrap_u = .CLAMP_TO_EDGE, + wrap_v = .CLAMP_TO_EDGE, + min_lod = -1000.0, + max_lod = 1000.0, border_color = BorderColor.OPAQUE_BLACK, - compare = .NEVER + compare = .NEVER }) verify( sokol_gfx.query_sampler_state( atlas_rt_sampler) < ResourceState.FAILED, "Failed to make atlas_rt_sampler" ) @@ -448,8 +449,8 @@ font_provider_startup :: proc() color_count = 1, sample_count = 1, depth = { - pixel_format = app_env.defaults.depth_format, - compare = .ALWAYS, + pixel_format = app_env.defaults.depth_format, + compare = .ALWAYS, write_enabled = false, }, cull_mode = .NONE, @@ -478,14 +479,14 @@ font_provider_startup :: proc() } }, depth = { - load_action = .DONTCARE, + load_action = .DONTCARE, store_action = .DONTCARE, - clear_value = 0.0, + clear_value = 0.0, }, stencil = { - load_action = .DONTCARE, + load_action = .DONTCARE, store_action = .DONTCARE, - clear_value = 0, + clear_value = 0, } } @@ -502,7 +503,10 @@ font_provider_reload :: proc() state := get_state() provider_data := & state.font_provider_data - ve.configure_snap( & provider_data.ve_font_cache, u32(state.app_window.extent.x * 2.0), u32(state.app_window.extent.y * 2.0) ) + hmap_chained_reload( provider_data.font_cache, persistent_allocator()) + + // ve.configure_snap( & provider_data.ve_font_cache, u32(state.app_window.extent.x * 2.0), u32(state.app_window.extent.y * 2.0) ) + ve.hot_reload( & provider_data.ve_font_cache, persistent_slab_allocator() ) } font_provider_shutdown :: proc() @@ -514,13 +518,14 @@ font_provider_shutdown :: proc() } font_load :: proc(path_file : string, - default_size : f32 = Font_Load_Use_Default_Size, + default_size : i32 = Font_Load_Use_Default_Size, desired_id : string = Font_Load_Gen_ID ) -> FontID { - profile(#procedure) + msg := str_fmt_tmp("Loading font: %v", path_file) + profile(msg) + log(msg) - logf("Loading font: %v", path_file) provider_data := & get_state().font_provider_data; using provider_data font_data, read_succeded : = os.read_entire_file( path_file, persistent_allocator() ) @@ -542,11 +547,47 @@ font_load :: proc(path_file : string, def, set_error := hmap_chained_set(font_cache, key, FontDef{}) verify( set_error == AllocatorError.None, "Failed to add new font entry to cache" ) - def.path_file = path_file + def.path_file = path_file + def.default_size = default_size - // TODO(Ed): Load even sizes from 8px to upper bound. - def.ve_id = ve.load_font( & provider_data.ve_font_cache, desired_id, font_data, 18.0 ) + for font_size : i32 = Font_Size_Interval; font_size <= Font_Largest_Px_Size; font_size += Font_Size_Interval + { + id := (font_size / Font_Size_Interval) + (font_size % Font_Size_Interval) + ve_id := & def.size_table[id - 1] + ve_id^ = ve.load_font( & provider_data.ve_font_cache, desired_id, font_data, 14.0 ) + } fid := FontID { key, desired_id } return fid } + +Font_Use_Default_Size :: f32(0.0) + +font_provider_resolve_draw_id :: proc( id : FontID, size := Font_Use_Default_Size ) -> ve.FontID +{ + state := get_state(); using state + + even_size := math.round(size * (1.0 / f32(Font_Size_Interval))) * f32(Font_Size_Interval) + size := clamp( i32( even_size), 4, Font_Largest_Px_Size ) + def := hmap_chained_get( font_provider_data.font_cache, id.key ) + size = size if size != i32(Font_Use_Default_Size) else def.default_size + + id := (size / Font_Size_Interval) + (size % Font_Size_Interval) + ve_id := def.size_table[ id - 1 ] + + width := app_window.extent.x * 2 + height := app_window.extent.y * 2 + return ve_id +} + +measure_text_size :: proc( text : string, font : FontID, font_size := Font_Use_Default_Size, spacing : f32 ) -> Vec2 +{ + state := get_state(); using state + + // profile(#procedure) + px_size := math.round( font_size ) + ve_id := font_provider_resolve_draw_id( font, font_size ) + + measured := ve.measure_text_size( & font_provider_data.ve_font_cache, ve_id, text ) + return measured +} diff --git a/scripts/build.ps1 b/scripts/build.ps1 index 91533b0..1fd84b2 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -208,7 +208,7 @@ push-location $path_root # $build_args += $flag_optimize_minimal # $build_args += $flag_optimize_speed # $build_args += $falg_optimize_aggressive - # $build_args += $flag_debug + $build_args += $flag_debug $build_args += $flag_pdb_name + $pdb $build_args += $flag_subsystem + 'windows' # $build_args += $flag_show_system_calls