From 0d9623c3404cceaeb56acc69fcd26ea44ecc9929 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Mon, 24 Jun 2024 11:29:44 -0400 Subject: [PATCH] layer text rendering works, ui has issues, there is perf problems * Added todos for VEFontCache * Going be remaking the direct box traversal rendering again, I'll keep both layer based and it as a option to switch between * Need to implement a quad tree for the ui boxes to help with collision test traversal --- code/font/VEFontCache/Readme.md | 22 +- code/font/VEFontCache/VEFontCache.odin | 13 +- code/font/VEFontCache/draw.odin | 157 ++++++++++--- code/font/VEFontCache/parser.odin | 17 +- code/sectr/engine/client_api.odin | 6 +- code/sectr/engine/render.odin | 309 +++++++++++++++---------- code/sectr/engine/update.odin | 2 +- code/sectr/math/space.odin | 2 +- 8 files changed, 366 insertions(+), 162 deletions(-) diff --git a/code/font/VEFontCache/Readme.md b/code/font/VEFontCache/Readme.md index 005edba..b2de176 100644 --- a/code/font/VEFontCache/Readme.md +++ b/code/font/VEFontCache/Readme.md @@ -1,10 +1,30 @@ # VE Font Cache : Odin Port -This is a port of the library base on my [fork](https://github.com/Ed94/VEFontCache) +This is a port of the library base on [fork](https://github.com/hypernewbie/VEFontCache) TODO (Making it a more idiomatic library): * Use Odin's builtin dynamic arrays * Use Odin's builtin map type * Setup freetype, harfbuzz, depedency management within the library + +TODO Documentation: + +* Pureref outline of draw_text exectuion +* Markdown general documentation + +TODO Content: + * Port over the original demo utilizing sokol libraries instead +* Provide a sokol_gfx backend package + +TODO Additional Features: + +* Support for freetype +* Support for harfbuzz +* Ability to set a draw transform, viewport and projection + * By default the library's position is in unsigned normalized render space + +TODO Optimizations: + +* Support more granular handling of shapes by chunking any text from draw_text into visible and whitespace/formatting diff --git a/code/font/VEFontCache/VEFontCache.odin b/code/font/VEFontCache/VEFontCache.odin index 5a3d79a..20a0e4d 100644 --- a/code/font/VEFontCache/VEFontCache.odin +++ b/code/font/VEFontCache/VEFontCache.odin @@ -4,10 +4,7 @@ A port of (https://github.com/hypernewbie/VEFontCache) to Odin. Status: This port is heavily tied to the grime package in SectrPrototype. -TODO(Ed): Make an idiomatic port of this for Odin (or just dupe the data structures...) - Changes: -- Support for freetype(WIP) - Font Parser & Glyph Shaper are abstracted to their own interface - Font Face parser info stored separately from entries - ve_fontcache_loadfile not ported (just use odin's core:os or os2), then call load_font @@ -759,9 +756,15 @@ reset_batch_codepoint_state :: proc( ctx : ^Context ) { shape_text_cached :: proc( ctx : ^Context, font : FontID, text_utf8 : string ) -> ^ShapedText { + @static buffer : [64 * Kilobyte]byte + font := font - hash := cast(u64) crc64( transmute([]u8) text_utf8 ) - // hash := label_hash( text_utf8 ) + + font_bytes := slice_ptr( transmute(^byte) & font, size_of(FontID) ) + copy( buffer[:], font_bytes ) + copy( buffer[: size_of(FontID) + len(text_utf8) ], transmute( []byte) text_utf8 ) + + hash := label_hash( transmute(string) buffer[: size_of(FontID) + len(text_utf8)] ) shape_cache := & ctx.shape_cache state := & ctx.shape_cache.state diff --git a/code/font/VEFontCache/draw.odin b/code/font/VEFontCache/draw.odin index 8c17539..c6e4b8d 100644 --- a/code/font/VEFontCache/draw.odin +++ b/code/font/VEFontCache/draw.odin @@ -219,15 +219,6 @@ draw_cached_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph_index : Glyph, end_index = cast(u32) ctx.draw_list.indices.num } append( & ctx.draw_list.calls, call ) - - // NOTE(Ed): Never done in the original - // Clear glyph_update_FBO - // call.pass = .Glyph - // call.start_index = 0 - // call.end_index = 0 - // call.clear_before_draw = true - // append( & ctx.draw_list.calls, call ) - return true } @@ -291,17 +282,125 @@ draw_text :: proc( ctx : ^Context, font : FontID, text_utf8 : string, position : context.allocator = ctx.backing - shaped := shape_text_cached( ctx, font, text_utf8 ) - + position := position snap_width := f32(ctx.snap_width) snap_height := f32(ctx.snap_height) - - position := position if ctx.snap_width > 0 do position.x = cast(f32) cast(u32) (position.x * snap_width + 0.5) / snap_width if ctx.snap_height > 0 do position.y = cast(f32) cast(u32) (position.y * snap_height + 0.5) / snap_height - entry := & ctx.entries.data[ font ] + entry := & ctx.entries.data[ font ] + post_shapes_draw_cursor_pos : Vec2 + last_shaped : ^ShapedText + + 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 + + when true { + text_chunk = transmute(string) text_utf8_bytes[ : ] + if len(text_chunk) > 0 { + shaped := shape_text_cached( ctx, font, text_chunk ) + post_shapes_draw_cursor_pos += draw_text_shape( ctx, font, entry, shaped, position, scale, snap_width, snap_height ) + ctx.cursor_pos = post_shapes_draw_cursor_pos + position += shaped.end_cursor_pos + last_shaped = shaped + } + } + else { + last_byte_offset : int = 0 + byte_offset : int = 0 + for codepoint, offset in text_utf8 + { + Rune_Space :: ' ' + Rune_Tab :: '\t' + Rune_Carriage_Return :: '\r' + Rune_Line_Feed :: '\n' + // Rune_Tab_Vertical :: '\v' + + byte_offset = offset + + switch codepoint + { + case Rune_Space: fallthrough + case Rune_Tab: fallthrough + case Rune_Line_Feed: fallthrough + case Rune_Carriage_Return: + if chunk_kind == .Formatting { + chunk_end = byte_offset + last_byte_offset = byte_offset + } + else + { + text_chunk = transmute(string) text_utf8_bytes[ chunk_start : byte_offset] + if len(text_chunk) > 0 { + shaped := shape_text_cached( ctx, font, text_chunk ) + post_shapes_draw_cursor_pos += draw_text_shape( ctx, font, entry, shaped, position, scale, snap_width, snap_height ) + ctx.cursor_pos = post_shapes_draw_cursor_pos + position += shaped.end_cursor_pos + last_shaped = shaped + } + + chunk_start = byte_offset + chunk_end = chunk_start + chunk_kind = .Formatting + + last_byte_offset = byte_offset + continue + } + } + + // Visible Chunk + if chunk_kind == .Visible { + chunk_end = byte_offset + last_byte_offset = byte_offset + } + else + { + text_chunk = transmute(string) text_utf8_bytes[ chunk_start : byte_offset ] + if len(text_chunk) > 0 { + shaped := shape_text_cached( ctx, font, text_chunk ) + post_shapes_draw_cursor_pos += draw_text_shape( ctx, font, entry, shaped, position, scale, snap_width, snap_height ) + ctx.cursor_pos = post_shapes_draw_cursor_pos + position += shaped.end_cursor_pos + last_shaped = shaped + } + + chunk_start = byte_offset + chunk_end = chunk_start + chunk_kind = .Visible + + last_byte_offset = byte_offset + } + } + + text_chunk = transmute(string) text_utf8_bytes[ chunk_start : byte_offset ] + if len(text_chunk) > 0 { + shaped := shape_text_cached( ctx, font, text_chunk ) + post_shapes_draw_cursor_pos += draw_text_shape( ctx, font, entry, shaped, position, scale, snap_width, snap_height ) + ctx.cursor_pos = post_shapes_draw_cursor_pos + position += shaped.end_cursor_pos + last_shaped = shaped + } + + chunk_start = byte_offset + chunk_end = chunk_start + chunk_kind = .Visible + + last_byte_offset = byte_offset + + ctx.cursor_pos = post_shapes_draw_cursor_pos + } + return true +} + +// 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, font : FontID, entry : ^Entry, shaped : ^ShapedText, position, scale : Vec2, snap_width, snap_height : f32 ) -> (cursor_pos : Vec2) +{ batch_start_idx : i32 = 0 for index : i32 = 0; index < i32(shaped.glyphs.num); index += 1 { @@ -309,6 +408,7 @@ draw_text :: proc( ctx : ^Context, font : FontID, text_utf8 : string, position : if is_empty( ctx, entry, glyph_index ) do continue if can_batch_glyph( ctx, font, entry, glyph_index ) do continue + // Glyph has not been catched, needs to be directly drawn. draw_text_batch( ctx, entry, shaped, batch_start_idx, index, position, scale ) reset_batch_codepoint_state( ctx ) @@ -323,9 +423,8 @@ draw_text :: proc( ctx : ^Context, font : FontID, text_utf8 : string, position : draw_text_batch( ctx, entry, shaped, batch_start_idx, i32(shaped.glyphs.num), position, scale ) reset_batch_codepoint_state( ctx ) - ctx.cursor_pos = position + shaped.end_cursor_pos * scale - - return true + cursor_pos = position + shaped.end_cursor_pos * scale + return } draw_text_batch :: proc( ctx : ^Context, entry : ^Entry, shaped : ^ShapedText, batch_start_idx, batch_end_idx : i32, position, scale : Vec2 ) @@ -375,6 +474,11 @@ get_draw_list :: proc( ctx : ^Context ) -> ^DrawList { return & ctx.draw_list } +// TODO(Ed): See render.odin's render_text_layer, should provide the ability to get a slice of the draw list to render the latest layer +DrawListLayer :: struct {} +get_draw_list_layer :: proc() -> DrawListLayer { return {} } +flush_layer :: proc( draw_list : ^DrawList ) {} + // ve_fontcache_merge_drawlist merge_draw_list :: proc( dst, src : ^DrawList ) { @@ -403,19 +507,18 @@ merge_draw_list :: proc( dst, src : ^DrawList ) } } -// Call during a full depth layer pass to optimize the final draw list when necessary -optimize_draw_list :: proc( ctx : ^Context ) +optimize_draw_list :: proc( draw_list : ^DrawList, call_offset : u64 ) { - assert( ctx != nil ) + assert( draw_list != nil ) - calls := array_to_slice(ctx.draw_list.calls) + calls := array_to_slice(draw_list.calls) - write_index : i32 = 0 - for index : i32 = 1; index < i32(ctx.draw_list.calls.num); index += 1 + write_index : u64 = call_offset + for index : u64 = 1 + call_offset; index < u64(draw_list.calls.num); index += 1 { assert( write_index <= index ) - draw_0 := & ctx.draw_list.calls.data[ write_index ] - draw_1 := & ctx.draw_list.calls.data[ index ] + draw_0 := & draw_list.calls.data[ write_index ] + draw_1 := & draw_list.calls.data[ index ] merge : b32 = true if draw_0.pass != draw_1.pass do merge = false @@ -436,11 +539,11 @@ optimize_draw_list :: proc( ctx : ^Context ) // logf("can't merge %v : %v %v", draw_0.pass, write_index, index ) write_index += 1 if write_index != index { - draw_2 := & ctx.draw_list.calls.data[ write_index ] + draw_2 := & draw_list.calls.data[ write_index ] draw_2^ = draw_1^ } } } - resize( & ctx.draw_list.calls, u64(write_index + 1) ) + resize( & draw_list.calls, u64(write_index + 1) ) } diff --git a/code/font/VEFontCache/parser.odin b/code/font/VEFontCache/parser.odin index 1eaf99b..2e175f8 100644 --- a/code/font/VEFontCache/parser.odin +++ b/code/font/VEFontCache/parser.odin @@ -25,7 +25,8 @@ ParserFontInfo :: struct { using _ : struct #raw_union { stbtt_info : stbtt.fontinfo, freetype_info : freetype.Face - } + }, + data : []byte, } GlyphVertType :: enum u8 { @@ -96,6 +97,7 @@ parser_load_font :: proc( ctx : ^ParserContext, label : string, data : []byte ) } font.label = label + font.data = data return } @@ -209,7 +211,7 @@ parser_get_glyph_box :: proc( font : ^ParserFontInfo, glyph_index : Glyph ) -> ( case .STB_TrueType: x0, y0, x1, y1 : i32 // { - // success := stbtt.InitFont( & font.stbtt_info, raw_data(data), 0 ) + // success := stbtt.InitFont( & font.stbtt_info, raw_data(font.data), 0 ) // } success := cast(bool) stbtt.GetGlyphBox( & font.stbtt_info, i32(glyph_index), & x0, & y0, & x1, & y1 ) assert( success ) @@ -394,7 +396,16 @@ parser_is_glyph_empty :: proc( font : ^ParserFontInfo, glyph_index : Glyph ) -> return false } -parser_scale_for_pixel_height :: #force_inline proc( font : ^ParserFontInfo, size : f32 ) -> f32 +parser_scale :: #force_inline proc( font : ^ParserFontInfo, size : f32 ) -> f32 +{ + size_scale := size < 0.0 ? \ + parser_scale_for_pixel_height( font, -size ) \ + : parser_scale_for_mapping_em_to_pixels( font, size ) + // size_scale = 1.0 + return size_scale +} + +parser_scale_for_pixel_height :: proc( font : ^ParserFontInfo, size : f32 ) -> f32 { switch font.kind { case .Freetype: diff --git a/code/sectr/engine/client_api.odin b/code/sectr/engine/client_api.odin index c7acabf..5c18acd 100644 --- a/code/sectr/engine/client_api.odin +++ b/code/sectr/engine/client_api.odin @@ -503,8 +503,8 @@ tick_work_frame :: #force_inline proc( host_delta_time_ms : f64 ) -> b32 // config.engine_refresh_hz = 165 - config.color_theme = App_Thm_Light - // config.color_theme = App_Thm_Dusk + // config.color_theme = App_Thm_Light + config.color_theme = App_Thm_Dusk // config.color_theme = App_Thm_Dark sokol_width := sokol_app.widthf() @@ -551,7 +551,7 @@ tick_frametime :: #force_inline proc( client_tick : ^time.Tick, host_delta_time_ pre_sleep_tick := time.tick_now() if can_sleep && sleep_ms > 0 { - thread_sleep( cast(Duration) sleep_ms * MS_To_NS ) + // thread_sleep( cast(Duration) sleep_ms * MS_To_NS ) // thread__highres_wait( sleep_ms ) } diff --git a/code/sectr/engine/render.odin b/code/sectr/engine/render.odin index b535ba2..b42fa03 100644 --- a/code/sectr/engine/render.odin +++ b/code/sectr/engine/render.odin @@ -19,12 +19,7 @@ RenderState :: struct { pass_actions : PassActions, } -#region("Draw Helpers") - -gp_set_color :: #force_inline proc( color : RGBA8 ) { - color := normalize_rgba8(color); - gp.set_color( color.r, color.g, color.b, color.a ) -} +#region("Helpers") draw_filled_circle :: proc(x, y, radius: f32, edges: int) { @@ -53,6 +48,15 @@ draw_filled_circle :: proc(x, y, radius: f32, edges: int) gp.draw_filled_triangles(raw_data(triangles), u32(len(triangles))) } +draw_rect :: proc( rect : Range2, color : RGBA8 ) { + using rect + render_set_color( color ) + + size := max - min + position := min + gp.draw_filled_rect( position.x, position.y, size.x, size.y ) +} + draw_rect_border :: proc( rect : Range2, border_width: f32) { rect_size := rect.max - rect.min @@ -68,7 +72,7 @@ draw_rect_border :: proc( rect : Range2, border_width: f32) } // Draw text using a string and normalized render coordinates -draw_text_string_pos_norm :: proc( content : string, id : FontID, size : f32, pos : Vec2, color := Color_White ) +draw_text_string_pos_norm :: proc( content : string, id : FontID, size : f32, pos : Vec2, color := Color_White, scale : f32 = 1.0 ) { state := get_state(); using state width := app_window.extent.x * 2 @@ -78,14 +82,14 @@ draw_text_string_pos_norm :: proc( content : string, id : FontID, size : f32, po 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} ) + ve.draw_text( & font_provider_data.ve_font_cache, ve_id, content, pos, Vec2{1 / width, 1 / height} * scale ) 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 ) { - profile(#procedure) + // profile(#procedure) state := get_state(); using state screen_size := app_window.extent * 2 render_pos := screen_to_render_pos(pos) @@ -93,37 +97,80 @@ draw_text_string_pos_extent :: proc( content : string, id : FontID, size : f32, draw_text_string_pos_norm( content, id, size, normalized_pos, color ) } -#endregion("Draw Helpers") +draw_text_string_pos_extent_zoomed :: proc( content : string, id : FontID, size : f32, pos : Vec2, cam : Camera, color := Color_White ) +{ + // profile(#procedure) + cam_offset := Vec2 { + cam.position.x, + cam.position.y, + } + + pos_offset := pos + cam_offset * cam.zoom + + cam_zoom_ratio := 1 / cam.zoom + + state := get_state(); using state + screen_size := app_window.extent * 2 + render_pos := screen_to_render_pos(pos_offset) + normalized_pos := render_pos * (1.0 / screen_size) + draw_text_string_pos_norm( content, id, size, normalized_pos, color, cam.zoom ) +} + +// TODO(Ed): Eventually the workspace will need a viewport for drawing text + +render_flush_gp :: #force_inline proc() +{ + gfx.begin_pass( gfx.Pass { action = get_state().render_data.pass_actions.empty_action, swapchain = sokol_glue.swapchain() }) + gp.flush() + gfx.end_pass() +} + +@(deferred_none=gp.reset_transform) +render_set_camera :: #force_inline proc( cam : Camera ) +{ + gp.translate( cam.position.x * cam.zoom, cam.position.y * cam.zoom ) + gp.scale( cam.zoom, cam.zoom ) +} + +render_set_color :: #force_inline proc( color : RGBA8 ) { + color := normalize_rgba8(color); + gp.set_color( color.r, color.g, color.b, color.a ) +} + +render_set_view_space :: #force_inline proc( extent : Extents2 ) +{ + size := extent * 2 + gp.viewport(0, 0, i32(size.x), i32(size.y)) + gp.project( -extent.x, extent.x, extent.y, -extent.y ) +} + +#endregion("Helpers") render :: proc() { profile(#procedure) state := get_state(); using state // TODO(Ed): Prefer passing static context to through the callstack - clear_pass := gfx.Pass { action = render_data.pass_actions.bg_clear_black, swapchain = sokol_glue.swapchain() } - clear_pass.action.colors[0].clear_value = transmute(gfx.Color) normalize_rgba8( config.color_theme.bg ) - screen_extent := app_window.extent screen_size := app_window.extent * 2 screen_ratio := screen_size.x * ( 1.0 / screen_size.y ) gp.begin( i32(screen_size.x), i32(screen_size.y) ) - gp.viewport(0, 0, i32(screen_size.x), i32(screen_size.y)) - gp.project( -screen_extent.x, screen_extent.x, screen_extent.y, -screen_extent.y ) - gp.set_blend_mode( .BLEND ) - gp_set_color(config.color_theme.bg) - gp.clear() - - gfx.begin_pass(clear_pass) - gp.flush() - gfx.end_pass(); - - render_mode_2d_workspace() - render_mode_screenspace() + // Clear the gp surface + { + render_set_view_space(screen_extent) + render_set_color(config.color_theme.bg) + gp.clear() + render_flush_gp() + } + // Workspace and screen rendering passes + { + render_mode_2d_workspace() + render_mode_screenspace() + } gp.end() - gfx.commit() ve.flush_draw_list( & font_provider_data.ve_font_cache ) font_provider_data.vbuf_layer_offset = 0 @@ -137,7 +184,7 @@ 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 + cam := project.workspace.cam screen_extent := app_window.extent screen_size := app_window.extent * 2 @@ -145,16 +192,16 @@ render_mode_2d_workspace :: proc() cam_zoom_ratio := 1.0 / cam.zoom - Render_Reference_Dots: + // TODO(Ed): Eventually will be the viewport extents + 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_Debug: { profile("render_reference_dots (workspace)") - gp.viewport(0, 0, i32(screen_size.x), i32(screen_size.y)) - gp.project( -screen_extent.x, screen_extent.x, screen_extent.y, -screen_extent.y ) + render_set_view_space(screen_extent) + render_set_camera(cam) - gp.translate( cam.position.x * cam.zoom, cam.position.y * cam.zoom ) - gp.scale( cam.zoom, cam.zoom ) - - gp_set_color(Color_White) + render_set_color(Color_White) draw_filled_circle(0, 0, 2 * cam_zoom_ratio, 24) if false @@ -166,19 +213,70 @@ render_mode_2d_workspace :: proc() gp.draw_filled_rect(50, 50, 100, 100 ) } - Mouse_Position: + mouse_pos := input.mouse.pos * cam_zoom_ratio - cam.position + render_set_color( Color_GreyRed ) + draw_filled_circle( mouse_pos.x, mouse_pos.y, 4, 24 ) + + render_flush_gp() + } + + render_set_view_space(screen_extent) + render_set_camera(cam) + + ui := & project.workspace.ui + render_list := array_to_slice( ui.render_list ) + + text_enqueued : b32 = false + shape_enqueued : b32 = false + + for entry, id in render_list + { + already_passed_signal := id > 0 && render_list[ id - 1 ].layer_signal + if !already_passed_signal && entry.layer_signal { - mouse_pos := (input.mouse.pos) * cam_zoom_ratio - cam.position - gp_set_color( Color_GreyRed ) - draw_filled_circle( mouse_pos.x, mouse_pos.y, 4, 24 ) + profile("render ui layer") + render_flush_gp() + if text_enqueued do render_text_layer() + continue + } + using entry + + profile("enqueue box") + + GP_Render: + { + // profile("draw_shapes") + if style.bg_color.a != 0 + { + draw_rect( bounds, style.bg_color ) + shape_enqueued = true + } + + if style.border_color.a != 0 && border_width > 0 { + render_set_color( style.border_color ) + draw_rect_border( bounds, 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) + + render_set_color(Color_Blue) + draw_filled_circle(bounds.max.x, bounds.max.y, 3, 24) + shape_enqueued = true + } } - gp.reset_transform() - - gfx.begin_pass( gfx.Pass { action = render_data.pass_actions.empty_action, swapchain = sokol_glue.swapchain() }) - gp.flush() - gfx.end_pass() + if len(text.str) > 0 && style.font.key != 0 { + draw_text_string_pos_extent_zoomed( text.str, default_font, font_size, computed.text_pos, cam, style.text_color ) + text_enqueued = true + } } + + if shape_enqueued do render_flush_gp() + if text_enqueued do render_text_layer() } render_mode_screenspace :: proc() @@ -200,67 +298,51 @@ render_mode_screenspace :: proc() Render_Reference_Dots: { profile("render_reference_dots (screen)") - gp.viewport(0, 0, i32(screen_size.x), i32(screen_size.y)) - gp.project( -screen_extent.x, screen_extent.x, screen_extent.y, -screen_extent.y ) + render_set_view_space(screen_extent) - gp_set_color(Color_Screen_Center_Dot) + render_set_color(Color_Screen_Center_Dot) draw_filled_circle(0, 0, 2, 24) Mouse_Position: { mouse_pos := input.mouse.pos - gp_set_color( Color_White_A125 ) + render_set_color( Color_White_A125 ) draw_filled_circle( mouse_pos.x, mouse_pos.y, 4, 24 ) } - gfx.begin_pass( gfx.Pass { action = render_data.pass_actions.empty_action, swapchain = sokol_glue.swapchain() }) - gp.flush() - gfx.end_pass() - } - - 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_left - position.y -= debug.draw_debug_text_y - - content := str_fmt_buffer( draw_text_scratch[:], format, ..args ) - - text_size := measure_text_size( content, default_font, 14.0, 0.0 ) - debug_draw_text( content, position, 14.0 ) - - debug.draw_debug_text_y += text_size.y + 4 + render_flush_gp() } debug.debug_text_vis = true if debug.debug_text_vis { + 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 do return + + font := font + if font.key == Font_Default.key do font = default_font + draw_text_string_pos_extent( content, font, size, pos, color ) + } + + debug_text :: proc( format : string, args : ..any ) + { + state := get_state(); using state + if debug.draw_debug_text_y > 800 do debug.draw_debug_text_y = 0 + + cam := & project.workspace.cam + screen_corners := screen_get_corners() + + position := screen_corners.top_left + position.y -= debug.draw_debug_text_y + + content := str_fmt( format, ..args ) + text_size := measure_text_size( content, default_font, 14.0, 0.0 ) + debug_draw_text( content, position, 14.0 ) + debug.draw_debug_text_y += text_size.y + 4 + } + profile("debug_text_vis") fps_size : f32 = 14.0 fps_msg := str_fmt( "FPS: %0.2f", fps_avg) @@ -281,7 +363,7 @@ render_mode_screenspace :: proc() } debug_text("Zoom Target: %v", project.workspace.zoom_target) - if false + if true { using input_events @@ -302,11 +384,9 @@ render_mode_screenspace :: proc() debug_text("Mouse Position (Render) : %v", input.mouse.raw_pos ) debug_text("Mouse Position (Screen) : %v", input.mouse.pos ) debug_text("Mouse Position (Workspace View): %v", screen_to_ws_view_pos(input.mouse.pos) ) - // rl.DrawCircleV( input.mouse.raw_pos, 10, Color_White_A125 ) - // rl.DrawCircleV( screen_to_render_pos(input.mouse.pos), 2, Color_BG ) } - if false + if true { ui := & project.workspace.ui @@ -323,7 +403,7 @@ render_mode_screenspace :: proc() } } - if false + if true { ui := & screen_ui @@ -354,29 +434,21 @@ render_screen_ui :: proc() screen_extent := app_window.extent screen_size := app_window.extent * 2 screen_ratio := screen_size.x * ( 1.0 / screen_size.y ) + render_set_view_space(screen_extent) ui := & screen_ui - render_list := array_to_slice( ui.render_list ) - - gp.viewport(0, 0, i32(screen_size.x), i32(screen_size.y)) - gp.project( -screen_extent.x, screen_extent.x, screen_extent.y, -screen_extent.y ) - text_enqueued : b32 = false shape_enqueued : b32 = false + render_list := array_to_slice( ui.render_list ) for entry, id in render_list { if entry.layer_signal { profile("render ui layer") - gfx.begin_pass( gfx.Pass { action = render_data.pass_actions.empty_action, swapchain = sokol_glue.swapchain() }) - gp.flush() - gfx.end_pass() - - if text_enqueued { - render_text_layer() - } + render_flush_gp() + if text_enqueued do render_text_layer() continue } using entry @@ -389,7 +461,7 @@ render_screen_ui :: proc() draw_rect :: proc( rect : Range2, color : RGBA8 ) { using rect - gp_set_color( color ) + render_set_color( color ) size := max - min position := min @@ -403,17 +475,17 @@ render_screen_ui :: proc() } if style.border_color.a != 0 && border_width > 0 { - gp_set_color( style.border_color ) + render_set_color( style.border_color ) draw_rect_border( bounds, border_width ) shape_enqueued = true } if debug.draw_ui_box_bounds_points { - gp_set_color(Color_Red) + render_set_color(Color_Red) draw_filled_circle(bounds.min.x, bounds.min.y, 3, 24) - gp_set_color(Color_Blue) + render_set_color(Color_Blue) draw_filled_circle(bounds.max.x, bounds.max.y, 3, 24) shape_enqueued = true } @@ -425,15 +497,8 @@ render_screen_ui :: proc() } } - if shape_enqueued { - gfx.begin_pass( gfx.Pass { action = render_data.pass_actions.empty_action, swapchain = sokol_glue.swapchain() }) - gp.flush() - gfx.end_pass() - } - - if text_enqueued { - render_text_layer() - } + if shape_enqueued do render_flush_gp() + if text_enqueued do render_text_layer() } render_text_layer :: proc() @@ -448,7 +513,9 @@ render_text_layer :: proc() font_provider := & state.font_provider_data using font_provider - // ve.optimize_draw_list( & ve_font_cache ) + // TODO(Ed): All this functionality for being able to segregate rendering of the drawlist incrementally should be lifted to the library itself (VEFontCache) + + ve.optimize_draw_list( & ve_font_cache.draw_list, calls_layer_offset ) draw_list := ve.get_draw_list( & ve_font_cache ) draw_list_vert_slice := array_to_slice(draw_list.vertices) diff --git a/code/sectr/engine/update.odin b/code/sectr/engine/update.odin index 1bec6fb..d791ffd 100644 --- a/code/sectr/engine/update.odin +++ b/code/sectr/engine/update.odin @@ -244,7 +244,7 @@ update :: proc( delta_time : f64 ) -> b32 scope( frame_style ) config.ui_resize_border_width = 2.5 - test_hover_n_click() + // test_hover_n_click() // test_draggable() // test_text_box() // test_parenting( & default_layout, & frame_style_default ) diff --git a/code/sectr/math/space.odin b/code/sectr/math/space.odin index e972701..09c89be 100644 --- a/code/sectr/math/space.odin +++ b/code/sectr/math/space.odin @@ -202,7 +202,7 @@ render_to_ws_view_pos :: #force_inline proc "contextless" (pos : Vec2) -> Vec2 { screen_to_ws_view_pos :: #force_inline proc "contextless" (pos: Vec2) -> Vec2 { state := get_state(); using state cam := & project.workspace.cam - result := Vec2 { cam.position.x, -cam.position.y} + Vec2 { pos.x, pos.y } * (1 / cam.zoom) + result := Vec2 { cam.position.x, -cam.position.y} + Vec2 { pos.x, pos.y } * (1 / cam.zoom) return result }