diff --git a/code/font/VEFontCache/LRU.odin b/code/font/VEFontCache/LRU.odin index ad8e906..6b00752 100644 --- a/code/font/VEFontCache/LRU.odin +++ b/code/font/VEFontCache/LRU.odin @@ -55,7 +55,7 @@ pool_list_init :: proc( pool : ^PoolList, capacity : u32, dbg_name : string = "" pool_list_free :: proc( pool : ^PoolList ) { - + // TODO(Ed): Implement } pool_list_reload :: proc( pool : ^PoolList, allocator : Allocator ) @@ -163,7 +163,7 @@ LRU_init :: proc( cache : ^LRU_Cache, capacity : u32, dbg_name : string = "" ) { LRU_free :: proc( cache : ^LRU_Cache ) { - + // TODO(Ed): Implement } LRU_reload :: #force_inline proc( cache : ^LRU_Cache, allocator : Allocator ) diff --git a/code/font/VEFontCache/Readme.md b/code/font/VEFontCache/Readme.md index e469042..d9e78e8 100644 --- a/code/font/VEFontCache/Readme.md +++ b/code/font/VEFontCache/Readme.md @@ -2,6 +2,8 @@ This is a port of the library base on [fork](https://github.com/hypernewbie/VEFontCache) +Its original purpose was for use in game engines, however its rendeirng quality and performance is more than adequate for many other applications. + TODO (Making it a more idiomatic library): * Use Odin's builtin dynamic arrays diff --git a/code/font/VEFontCache/VEFontCache.odin b/code/font/VEFontCache/VEFontCache.odin index 65660d4..5b1f7a8 100644 --- a/code/font/VEFontCache/VEFontCache.odin +++ b/code/font/VEFontCache/VEFontCache.odin @@ -28,11 +28,6 @@ vec2_64_from_vec2 :: #force_inline proc( v2 : Vec2 ) -> Vec2_64 { return { f FontID :: distinct i64 Glyph :: distinct i32 -Vertex :: struct { - pos : Vec2, - u, v : f32, -} - Entry :: struct { parser_info : ParserFontInfo, shaper_info : ShaperInfo, @@ -68,6 +63,14 @@ Context :: struct { colour : Colour, cursor_pos : Vec2, + // draw_cursor_pos : Vec2, + + draw_layer : struct { + vertices_offset : int, + indices_offset : int, + calls_offset : int, + }, + draw_list : DrawList, atlas : Atlas, shape_cache : ShapedTextCache, @@ -130,7 +133,6 @@ InitGlyphDrawParams_Default :: InitGlyphDrawParams { over_sample = { 4, 4 }, buffer_batch = 4, draw_padding = InitAtlasParams_Default.glyph_padding, - // draw_padding = InitAtlasParams_Default.glyph_padding, } InitShapeCacheParams :: struct { @@ -139,12 +141,12 @@ InitShapeCacheParams :: struct { } InitShapeCacheParams_Default :: InitShapeCacheParams { - capacity = 256, - reserve_length = 64, + capacity = 1024, + reserve_length = 1024, } // ve_fontcache_init -init :: proc( ctx : ^Context, parser_kind : ParserKind, +startup :: proc( ctx : ^Context, parser_kind : ParserKind, allocator := context.allocator, atlas_params := InitAtlasParams_Default, glyph_draw_params := InitGlyphDrawParams_Default, @@ -272,9 +274,9 @@ init :: proc( ctx : ^Context, parser_kind : ParserKind, hot_reload :: proc( ctx : ^Context, allocator : Allocator ) { + assert( ctx != nil ) ctx.backing = allocator context.allocator = ctx.backing - using ctx reload_array( & entries, allocator ) @@ -323,15 +325,8 @@ shutdown :: proc( ctx : ^Context ) } shaper_shutdown( & shaper_ctx ) -} -#endregion("lifetime") - -// ve_fontcache_configure_snap -configure_snap :: #force_inline proc( ctx : ^Context, snap_width, snap_height : u32 ) { - assert( ctx != nil ) - ctx.snap_width = snap_width - ctx.snap_height = snap_height + // TODO(Ed): Finish implementing, there is quite a few resource not released here. } // ve_fontcache_load @@ -395,271 +390,185 @@ unload_font :: proc( ctx : ^Context, font : FontID ) shaper_unload_font( & entry.shaper_info ) } -cache_glyph :: proc( ctx : ^Context, font : FontID, glyph_index : Glyph, scale, translate : Vec2 ) -> b32 -{ - // profile(#procedure) +#endregion("lifetime") + +#region("drawing") + +// ve_fontcache_configure_snap +configure_snap :: #force_inline proc( ctx : ^Context, snap_width, snap_height : u32 ) { assert( ctx != nil ) - assert( font >= 0 && int(font) < len(ctx.entries) ) - entry := & ctx.entries[ font ] - if glyph_index == Glyph(0) { - // Note(Original Author): Glyph not in current hb_font - return false - } - - // No shpae to retrieve - if parser_is_glyph_empty( & entry.parser_info, glyph_index ) do return true - - // Retrieve the shape definition from the parser. - shape, error := parser_get_glyph_shape( & entry.parser_info, glyph_index ) - assert( error == .None ) - if len(shape) == 0 { - return false - } - - if ctx.debug_print_verbose - { - log( "shape:") - for vertex in shape - { - if vertex.type == .Move { - logf("move_to %d %d", vertex.x, vertex.y ) - } - else if vertex.type == .Line { - logf("line_to %d %d", vertex.x, vertex.y ) - } - else if vertex.type == .Curve { - logf("curve_to %d %d through %d %d", vertex.x, vertex.y, vertex.contour_x0, vertex.contour_y0 ) - } - else if vertex.type == .Cubic { - logf("cubic_to %d %d through %d %d and %d %d", - vertex.x, vertex.y, - vertex.contour_x0, vertex.contour_y0, - vertex.contour_x1, vertex.contour_y1 ) - } - } - } - - /* - Note(Original Author): - We need a random point that is outside our shape. We simply pick something diagonally across from top-left bound corner. - Note that this outside point is scaled alongside the glyph in ve_fontcache_draw_filled_path, so we don't need to handle that here. - */ - bounds_0, bounds_1 := parser_get_glyph_box( & entry.parser_info, glyph_index ) - - outside := Vec2 { - f32(bounds_0.x) - 21, - f32(bounds_0.y) - 33, - } - - // Note(Original Author): Figure out scaling so it fits within our box. - draw := DrawCall_Default - draw.pass = FrameBufferPass.Glyph - draw.start_index = u32(len(ctx.draw_list.indices)) - - // Note(Original Author); - // Draw the path using simplified version of https://medium.com/@evanwallace/easy-scalable-text-rendering-on-the-gpu-c3f4d782c5ac. - // Instead of involving fragment shader code we simply make use of modern GPU ability to crunch triangles and brute force curve definitions. - path := ctx.temp_path - clear( & path) - for edge in shape do switch edge.type - { - case .Move: - if len(path) > 0 { - draw_filled_path( & ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose ) - } - clear( & path) - fallthrough - - case .Line: - append_elem( & path, Vec2{ f32(edge.x), f32(edge.y) }) - - case .Curve: - assert( len(path) > 0 ) - p0 := path[ len(path) - 1 ] - p1 := Vec2{ f32(edge.contour_x0), f32(edge.contour_y0) } - p2 := Vec2{ f32(edge.x), f32(edge.y) } - - step := 1.0 / f32(ctx.curve_quality) - alpha := step - for index := i32(0); index < i32(ctx.curve_quality); index += 1 { - append_elem( & path, eval_point_on_bezier3( p0, p1, p2, alpha )) - alpha += step - } - - case .Cubic: - assert( len(path) > 0 ) - p0 := path[ len(path) - 1] - p1 := Vec2{ f32(edge.contour_x0), f32(edge.contour_y0) } - p2 := Vec2{ f32(edge.contour_x1), f32(edge.contour_y1) } - p3 := Vec2{ f32(edge.x), f32(edge.y) } - - step := 1.0 / f32(ctx.curve_quality) - alpha := step - for index := i32(0); index < i32(ctx.curve_quality); index += 1 { - append_elem( & path, eval_point_on_bezier4( p0, p1, p2, p3, alpha )) - alpha += step - } - - case .None: - assert(false, "Unknown edge type or invalid") - } - if len(path) > 0 { - draw_filled_path( & ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose ) - } - - // Note(Original Author): Apend the draw call - draw.end_index = cast(u32) len(ctx.draw_list.indices) - if draw.end_index > draw.start_index { - append(& ctx.draw_list.calls, draw) - } - - parser_free_shape( & entry.parser_info, shape ) - return true -} - -cache_glyph_to_atlas :: proc( ctx : ^Context, font : FontID, glyph_index : Glyph ) -{ - // profile(#procedure) - assert( ctx != nil ) - assert( font >= 0 && int(font) < len(ctx.entries) ) - entry := & ctx.entries[ font ] - - if glyph_index == 0 do return - if parser_is_glyph_empty( & entry.parser_info, glyph_index ) do return - - // Get hb_font text metrics. These are unscaled! - 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) - - region_kind, region, over_sample := decide_codepoint_region( ctx, entry, glyph_index ) - - // E region is special case and not cached to atlas. - if region_kind == .None || region_kind == .E do return - - // Grab an atlas LRU cache slot. - lru_code := font_glyph_lru_code( font, glyph_index ) - atlas_index := LRU_get( & region.state, lru_code ) - if atlas_index == -1 - { - if region.next_idx < region.state.capacity - { - evicted := LRU_put( & region.state, lru_code, i32(region.next_idx) ) - atlas_index = i32(region.next_idx) - region.next_idx += 1 - assert( evicted == lru_code ) - } - else - { - next_evict_codepoint := LRU_get_next_evicted( & region.state ) - assert( next_evict_codepoint != 0xFFFFFFFFFFFFFFFF ) - - atlas_index = LRU_peek( & region.state, next_evict_codepoint, must_find = true ) - assert( atlas_index != -1 ) - - evicted := LRU_put( & region.state, lru_code, atlas_index ) - assert( evicted == next_evict_codepoint ) - } - - assert( LRU_get( & region.state, lru_code ) != - 1 ) - } - - atlas := & ctx.atlas - atlas_width := f32(atlas.width) - atlas_height := f32(atlas.height) - glyph_buffer_width := f32(atlas.buffer_width) - glyph_buffer_height := f32(atlas.buffer_height) - glyph_padding := cast(f32) atlas.glyph_padding - - if ctx.debug_print - { - @static debug_total_cached : i32 = 0 - logf("glyph %v%v( %v ) caching to atlas region %v at idx %d. %d total glyphs cached.\n", i32(glyph_index), rune(glyph_index), cast(rune) region_kind, atlas_index, debug_total_cached) - debug_total_cached += 1 - } - - // Draw oversized glyph to update FBO - glyph_draw_scale := over_sample * entry.size_scale - glyph_draw_translate := -1 * Vec2 { f32(bounds_0.x), f32(bounds_0.y) } * glyph_draw_scale + vec2( glyph_padding ) - glyph_draw_translate.x = cast(f32) (i32(glyph_draw_translate.x + 0.9999999)) - glyph_draw_translate.y = cast(f32) (i32(glyph_draw_translate.y + 0.9999999)) - - // Allocate a glyph_update_FBO region - gwidth_scaled_px := i32( bounds_width * glyph_draw_scale.x + 1.0 ) + i32(over_sample.x * glyph_padding) - if i32(atlas.update_batch_x + gwidth_scaled_px) >= i32(atlas.buffer_width) { - flush_glyph_buffer_to_atlas( ctx ) - } - - // Calculate the src and destination regions - dst_position, dst_width, dst_height := atlas_bbox( atlas, region_kind, atlas_index ) - dst_glyph_position := dst_position - dst_glyph_width := bounds_width * entry.size_scale - dst_glyph_height := bounds_height * entry.size_scale - dst_glyph_width += glyph_padding - dst_glyph_height += glyph_padding - - dst_size := Vec2 { dst_width, dst_height } - dst_glyph_size := Vec2 { dst_glyph_width, dst_glyph_height } - screenspace_x_form( & dst_glyph_position, & dst_glyph_size, atlas_width, atlas_height ) - screenspace_x_form( & dst_position, & dst_size, atlas_width, atlas_height ) - - src_position := Vec2 { f32(atlas.update_batch_x), 0 } - src_size := Vec2 { - bounds_width * glyph_draw_scale.x, - bounds_height * glyph_draw_scale.y, - } - src_size += over_sample * glyph_padding - textspace_x_form( & src_position, & src_size, glyph_buffer_width, glyph_buffer_height ) - - // Advance glyph_update_batch_x and calculate final glyph drawing transform - glyph_draw_translate.x += f32(atlas.update_batch_x) - atlas.update_batch_x += gwidth_scaled_px - screenspace_x_form( & glyph_draw_translate, & glyph_draw_scale, glyph_buffer_width, glyph_buffer_height ) - - call : DrawCall - { - // Queue up clear on target region on atlas - using call - pass = .Atlas - region = .Ignore - start_index = cast(u32) len(atlas.clear_draw_list.indices) - blit_quad( & atlas.clear_draw_list, dst_position, dst_position + dst_size, { 1.0, 1.0 }, { 1.0, 1.0 } ) - end_index = cast(u32) len(atlas.clear_draw_list.indices) - append( & atlas.clear_draw_list.calls, call ) - - // Queue up a blit from glyph_update_FBO to the atlas - region = .None - start_index = cast(u32) len(atlas.draw_list.indices) - blit_quad( & atlas.draw_list, dst_glyph_position, dst_position + dst_glyph_size, src_position, src_position + src_size ) - end_index = cast(u32) len(atlas.draw_list.indices) - append( & atlas.draw_list.calls, call ) - } - - // Render glyph to glyph_update_FBO - cache_glyph( ctx, font, glyph_index, glyph_draw_scale, glyph_draw_translate ) + ctx.snap_width = snap_width + ctx.snap_height = snap_height } get_cursor_pos :: #force_inline proc "contextless" ( ctx : ^Context ) -> Vec2 { return ctx.cursor_pos } set_colour :: #force_inline proc "contextless" ( ctx : ^Context, colour : Colour ) { ctx.colour = colour } -is_empty :: #force_inline proc ( ctx : ^Context, entry : ^Entry, glyph_index : Glyph ) -> b32 +// TODO(Ed): Change this to be whitespace aware so that we can optimize the caching of shpaes properly. +// Right now the entire text provided to this call is considered a "shape" this is really bad as basically it invalidates caching for large chunks of text +// Instead we should be aware of whitespace tokens and the chunks between them (the whitespace lexer could be abused for this). +// From there we should maek a 'draw text shape' that breaks up the batch text draws for each of the shapes. +draw_text :: proc( ctx : ^Context, font : FontID, text_utf8 : string, position : Vec2, scale : Vec2 ) -> b32 { - if glyph_index == 0 do return true - if parser_is_glyph_empty( & entry.parser_info, glyph_index ) do return true - return false + // profile(#procedure) + assert( ctx != nil ) + assert( font >= 0 && int(font) < len(ctx.entries) ) + + ctx.cursor_pos = {} + + position := position + snap_width := f32(ctx.snap_width) + snap_height := f32(ctx.snap_height) + 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[ font ] + + 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, entry ) + ctx.cursor_pos = draw_text_shape( ctx, font, entry, shaped, position, scale, snap_width, snap_height ) + 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, entry ) + ctx.cursor_pos += draw_text_shape( ctx, font, entry, shaped, position, scale, snap_width, snap_height ) + 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, entry ) + ctx.cursor_pos += draw_text_shape( ctx, font, entry, shaped, position, scale, snap_width, snap_height ) + 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, entry ) + ctx.cursor_pos += draw_text_shape( ctx, font, entry, shaped, position, scale, snap_width, snap_height ) + last_shaped = shaped + } + + chunk_start = byte_offset + chunk_end = chunk_start + chunk_kind = .Visible + + last_byte_offset = byte_offset + } + return true } +// ve_fontcache_drawlist +get_draw_list :: proc( ctx : ^Context, optimize_before_returning := true ) -> ^DrawList { + 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 : []DrawCall) { + 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 : ] + indices = ctx.draw_list.indices [ ctx.draw_layer.indices_offset : ] + calls = ctx.draw_list.calls [ ctx.draw_layer.calls_offset : ] + return +} + +// ve_fontcache_flush_drawlist +flush_draw_list :: proc( ctx : ^Context ) { + assert( ctx != nil ) + using ctx + clear_draw_list( & draw_list ) + draw_layer.vertices_offset = 0 + draw_layer.indices_offset = 0 + draw_layer.calls_offset = 0 +} + +flush_draw_list_layer :: proc( ctx : ^Context ) { + assert( ctx != nil ) + using ctx + draw_layer.vertices_offset = len(draw_list.vertices) + draw_layer.indices_offset = len(draw_list.indices) + draw_layer.calls_offset = len(draw_list.calls) +} + +#endregion("drawing") + +#region("metrics") + measure_text_size :: proc( ctx : ^Context, font : FontID, text_utf8 : string ) -> (measured : Vec2) { // profile(#procedure) assert( ctx != nil ) assert( font >= 0 && int(font) < len(ctx.entries) ) - context.allocator = ctx.backing - atlas := ctx.atlas - shaped := shape_text_cached( ctx, font, text_utf8 ) entry := & ctx.entries[ font ] + shaped := shape_text_cached( ctx, font, text_utf8, entry ) padding := cast(f32) atlas.glyph_padding for index : i32 = 0; index < i32(len(shaped.glyphs)); index += 1 @@ -676,3 +585,15 @@ measure_text_size :: proc( ctx : ^Context, font : FontID, text_utf8 : string ) - measured.x = shaped.end_cursor_pos.x return measured } + +get_font_vertical_metrics :: #force_inline proc ( ctx : ^Context, font : FontID ) -> ( ascent, descent, line_gap : i32 ) +{ + assert( ctx != nil ) + assert( font >= 0 && int(font) < len(ctx.entries) ) + + entry := & ctx.entries[ font ] + ascent, descent, line_gap = parser_get_font_vertical_metrics( & entry.parser_info ) + return +} + +#endregion("metrics") diff --git a/code/font/VEFontCache/atlas.odin b/code/font/VEFontCache/atlas.odin index 3c48193..89abfa6 100644 --- a/code/font/VEFontCache/atlas.odin +++ b/code/font/VEFontCache/atlas.odin @@ -88,25 +88,22 @@ atlas_bbox :: proc( atlas : ^Atlas, region : AtlasRegionKind, local_idx : i32 ) return } -can_batch_glyph :: #force_inline proc( ctx : ^Context, font : FontID, entry : ^Entry, glyph_index : Glyph ) -> b32 +can_batch_glyph :: #force_inline proc( ctx : ^Context, font : FontID, entry : ^Entry, glyph_index : Glyph, + lru_code : u64, + atlas_index : i32, + region_kind : AtlasRegionKind, + region : ^AtlasRegion, + over_sample : Vec2 +) -> b32 { // profile(#procedure) - assert( ctx != nil ) - assert( entry.id == font ) - - // Decide which atlas to target assert( glyph_index != -1 ) - region_kind, region, over_sample := decide_codepoint_region( ctx, entry, glyph_index ) // 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 - // Note(Ed): Why 1024? + // TODO(Ed): Why 1024? - // Is this glyph cached? - // lru_code := u64(glyph_index) + ( ( 0x100000000 * u64(font) ) & 0xFFFFFFFF00000000 ) - lru_code := font_glyph_lru_code(font, glyph_index) - atlas_index := LRU_get( & region.state, lru_code ) if atlas_index == - 1 { if region.next_idx > u32( region.state.capacity) { @@ -120,12 +117,11 @@ can_batch_glyph :: #force_inline proc( ctx : ^Context, font : FontID, entry : ^E } } - cache_glyph_to_atlas( ctx, font, glyph_index ) + cache_glyph_to_atlas( ctx, font, glyph_index, lru_code, atlas_index, entry, region_kind, region, over_sample ) } assert( LRU_get( & region.state, lru_code ) != -1 ) - ctx.temp_codepoint_seen[lru_code] = true - ctx.temp_codepoint_seen_num += 1 + mark_batch_codepoint_seen( ctx, lru_code) return true } diff --git a/code/font/VEFontCache/docs/Readme.md b/code/font/VEFontCache/docs/Readme.md index 8abed21..9a9f4aa 100644 --- a/code/font/VEFontCache/docs/Readme.md +++ b/code/font/VEFontCache/docs/Readme.md @@ -1,7 +1,73 @@ -# Documentation - -Work in progress... - +# Interface +Notes +--- + +Freetype implementation supports specifying a FT_Memory handle which is a pointer to a FT_MemoryRect. This can be used to define an allocator for the parser. Currently this library does not wrap this interface (yet). If using freetype its recommend to update `parser_init` with the necessary changes to wrap the context's backing allocator for freetype to utilize. + +```c + struct FT_MemoryRec_ + { + void* user; + FT_Alloc_Func alloc; + FT_Free_Func free; + FT_Realloc_Func realloc; + }; + ``` + +### startup + +Initializes a provided context. + +There are a large amount of parameters to tune the library instance to the user's preference. By default, keep in mind the library defaults to utilize stb_truetype as the font parser and harfbuzz (soon...) for the shaper. + +Much of the data structures within the context struct are not fixed-capacity allocations so make sure that the backing allocator utilized can handle it. + +### hot_reload + +The library supports being used in a dynamically loaded module. If this occurs simply make sure to call this procedure with a reference to the backing allocator provided during startup as all dynamic containers tend to lose a proper reference to the allocator's procedure. + +### shutdown + +Release resources from the context. + +### configure_snap + +You'll find this used immediately in draw_text it acts as a way to snap the position of the text to the nearest pixel for the width and height specified. + +If snapping is not desired, set the snap_width and height before calling draw_text to 0. + +## get_cursor_pos + +Will provide the current cursor_pos for the resulting text drawn. + +## set_color + +Sets the color to utilize on `DrawCall`s for FrameBuffer.Target or .Target_Uncached passes + +### get_draw_list + +Get the enqueded draw_list (vertices, indices, and draw call arrays) in its entirety. +By default, if get_draw_list is called, it will first call `optimize_draw_list` to optimize the draw list's calls for the user. If this is undesired, make sure to pass `optimize_before_returning = false` in the arguments. + +### get_draw_list_layer + +Get the enqueued draw_list for the current "layer". +A layer is considered the slice of the drawlist's content from the last call to `flush_draw_list_layer` onward. +By default, if get_draw_list_layer is called, it will first call `optimize_draw_list` for the user to optimize the slice (exlusively) of the draw list's draw calls. If this is undesired, make sure to pass `optimize_before_returning = false` in the arguments. + +The draw layer offsets are cleared with `flush_draw_list` + +### flush_draw_list + +Will clear the draw list and draw layer offsets. + +### flush_draw_list_layer + +Will update the draw list layer with the latest offset based on the current lenght of the draw list vertices, indices, and calls arrays. + +### measure_text_size + +Provides a Vec2 the width and height occupied by the provided text string. The y is measured to be the the largest glyph box bounds height of the text. The width is derived from the `end_cursor_pos` field from a `ShapedText` entry. diff --git a/code/font/VEFontCache/docs/draw_text_codepaths.pur b/code/font/VEFontCache/docs/draw_text_codepaths.pur new file mode 100644 index 0000000..061c9b9 Binary files /dev/null and b/code/font/VEFontCache/docs/draw_text_codepaths.pur differ diff --git a/code/font/VEFontCache/draw.odin b/code/font/VEFontCache/draw.odin index e493dd2..6305a48 100644 --- a/code/font/VEFontCache/draw.odin +++ b/code/font/VEFontCache/draw.odin @@ -2,6 +2,11 @@ package VEFontCache import "core:math" +Vertex :: struct { + pos : Vec2, + u, v : f32, +} + DrawCall :: struct { pass : FrameBufferPass, start_index : u32, @@ -89,6 +94,241 @@ blit_quad :: proc( draw_list : ^DrawList, p0 : Vec2 = {0, 0}, p1 : Vec2 = {1, 1} return } +cache_glyph :: proc( ctx : ^Context, font : FontID, glyph_index : Glyph, entry : ^Entry, bounds_0, bounds_1 : Vec2i, scale, translate : Vec2 ) -> b32 +{ + // profile(#procedure) + if glyph_index == Glyph(0) { + // Note(Original Author): Glyph not in current hb_font + return false + } + + // No shpae to retrieve + // if parser_is_glyph_empty( & entry.parser_info, glyph_index ) do return true + + // Retrieve the shape definition from the parser. + shape, error := parser_get_glyph_shape( & entry.parser_info, glyph_index ) + assert( error == .None ) + if len(shape) == 0 { + return false + } + + if ctx.debug_print_verbose + { + log( "shape:") + for vertex in shape + { + if vertex.type == .Move { + logf("move_to %d %d", vertex.x, vertex.y ) + } + else if vertex.type == .Line { + logf("line_to %d %d", vertex.x, vertex.y ) + } + else if vertex.type == .Curve { + logf("curve_to %d %d through %d %d", vertex.x, vertex.y, vertex.contour_x0, vertex.contour_y0 ) + } + else if vertex.type == .Cubic { + logf("cubic_to %d %d through %d %d and %d %d", + vertex.x, vertex.y, + vertex.contour_x0, vertex.contour_y0, + vertex.contour_x1, vertex.contour_y1 ) + } + } + } + + /* + Note(Original Author): + We need a random point that is outside our shape. We simply pick something diagonally across from top-left bound corner. + Note that this outside point is scaled alongside the glyph in ve_fontcache_draw_filled_path, so we don't need to handle that here. + */ + outside := Vec2 { + f32(bounds_0.x) - 21, + f32(bounds_0.y) - 33, + } + + // Note(Original Author): Figure out scaling so it fits within our box. + draw := DrawCall_Default + draw.pass = FrameBufferPass.Glyph + draw.start_index = u32(len(ctx.draw_list.indices)) + + // Note(Original Author); + // Draw the path using simplified version of https://medium.com/@evanwallace/easy-scalable-text-rendering-on-the-gpu-c3f4d782c5ac. + // Instead of involving fragment shader code we simply make use of modern GPU ability to crunch triangles and brute force curve definitions. + path := ctx.temp_path + clear( & path) + for edge in shape do switch edge.type + { + case .Move: + if len(path) > 0 { + draw_filled_path( & ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose ) + } + clear( & path) + fallthrough + + case .Line: + append_elem( & path, Vec2{ f32(edge.x), f32(edge.y) }) + + case .Curve: + assert( len(path) > 0 ) + p0 := path[ len(path) - 1 ] + p1 := Vec2{ f32(edge.contour_x0), f32(edge.contour_y0) } + p2 := Vec2{ f32(edge.x), f32(edge.y) } + + step := 1.0 / f32(ctx.curve_quality) + alpha := step + for index := i32(0); index < i32(ctx.curve_quality); index += 1 { + append_elem( & path, eval_point_on_bezier3( p0, p1, p2, alpha )) + alpha += step + } + + case .Cubic: + assert( len(path) > 0 ) + p0 := path[ len(path) - 1] + p1 := Vec2{ f32(edge.contour_x0), f32(edge.contour_y0) } + p2 := Vec2{ f32(edge.contour_x1), f32(edge.contour_y1) } + p3 := Vec2{ f32(edge.x), f32(edge.y) } + + step := 1.0 / f32(ctx.curve_quality) + alpha := step + for index := i32(0); index < i32(ctx.curve_quality); index += 1 { + append_elem( & path, eval_point_on_bezier4( p0, p1, p2, p3, alpha )) + alpha += step + } + + case .None: + assert(false, "Unknown edge type or invalid") + } + if len(path) > 0 { + draw_filled_path( & ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose ) + } + + // Note(Original Author): Apend the draw call + draw.end_index = cast(u32) len(ctx.draw_list.indices) + if draw.end_index > draw.start_index { + append(& ctx.draw_list.calls, draw) + } + + parser_free_shape( & entry.parser_info, shape ) + return true +} + +/* + Called by: + * can_batch_glyph : If it determines that the glyph was not detected and we haven't reached capacity in the atlas + * draw_text_shape : Glyph +*/ +cache_glyph_to_atlas :: proc( ctx : ^Context, font : FontID, glyph_index : Glyph, lru_code : u64, atlas_index : i32, entry : ^Entry, region_kind : AtlasRegionKind, region : ^AtlasRegion, over_sample : Vec2 ) +{ + // profile(#procedure) + + // Get hb_font text metrics. These are unscaled! + 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) + + // E region is special case and not cached to atlas. + if region_kind == .None || region_kind == .E do return + + // Grab an atlas LRU cache slot. + atlas_index := atlas_index + if atlas_index == -1 + { + if region.next_idx < region.state.capacity + { + evicted := LRU_put( & region.state, lru_code, i32(region.next_idx) ) + atlas_index = i32(region.next_idx) + region.next_idx += 1 + assert( evicted == lru_code ) + } + else + { + next_evict_codepoint := LRU_get_next_evicted( & region.state ) + assert( next_evict_codepoint != 0xFFFFFFFFFFFFFFFF ) + + atlas_index = LRU_peek( & region.state, next_evict_codepoint, must_find = true ) + assert( atlas_index != -1 ) + + evicted := LRU_put( & region.state, lru_code, atlas_index ) + assert( evicted == next_evict_codepoint ) + } + + assert( LRU_get( & region.state, lru_code ) != - 1 ) + } + + atlas := & ctx.atlas + atlas_width := f32(atlas.width) + atlas_height := f32(atlas.height) + glyph_buffer_width := f32(atlas.buffer_width) + glyph_buffer_height := f32(atlas.buffer_height) + glyph_padding := cast(f32) atlas.glyph_padding + + if ctx.debug_print + { + @static debug_total_cached : i32 = 0 + logf("glyph %v%v( %v ) caching to atlas region %v at idx %d. %d total glyphs cached.\n", i32(glyph_index), rune(glyph_index), cast(rune) region_kind, atlas_index, debug_total_cached) + debug_total_cached += 1 + } + + // Draw oversized glyph to update FBO + glyph_draw_scale := over_sample * entry.size_scale + glyph_draw_translate := -1 * Vec2 { f32(bounds_0.x), f32(bounds_0.y) } * glyph_draw_scale + vec2( glyph_padding ) + glyph_draw_translate.x = cast(f32) (i32(glyph_draw_translate.x + 0.9999999)) + glyph_draw_translate.y = cast(f32) (i32(glyph_draw_translate.y + 0.9999999)) + + // Allocate a glyph_update_FBO region + gwidth_scaled_px := i32( bounds_width * glyph_draw_scale.x + 1.0 ) + i32(over_sample.x * glyph_padding) + if i32(atlas.update_batch_x + gwidth_scaled_px) >= i32(atlas.buffer_width) { + flush_glyph_buffer_to_atlas( ctx ) + } + + // Calculate the src and destination regions + dst_position, dst_width, dst_height := atlas_bbox( atlas, region_kind, atlas_index ) + dst_glyph_position := dst_position + dst_glyph_width := bounds_width * entry.size_scale + dst_glyph_height := bounds_height * entry.size_scale + dst_glyph_width += glyph_padding + dst_glyph_height += glyph_padding + + dst_size := Vec2 { dst_width, dst_height } + dst_glyph_size := Vec2 { dst_glyph_width, dst_glyph_height } + screenspace_x_form( & dst_glyph_position, & dst_glyph_size, atlas_width, atlas_height ) + screenspace_x_form( & dst_position, & dst_size, atlas_width, atlas_height ) + + src_position := Vec2 { f32(atlas.update_batch_x), 0 } + src_size := Vec2 { + bounds_width * glyph_draw_scale.x, + bounds_height * glyph_draw_scale.y, + } + src_size += over_sample * glyph_padding + textspace_x_form( & src_position, & src_size, glyph_buffer_width, glyph_buffer_height ) + + // Advance glyph_update_batch_x and calculate final glyph drawing transform + glyph_draw_translate.x += f32(atlas.update_batch_x) + atlas.update_batch_x += gwidth_scaled_px + screenspace_x_form( & glyph_draw_translate, & glyph_draw_scale, glyph_buffer_width, glyph_buffer_height ) + + call : DrawCall + { + // Queue up clear on target region on atlas + using call + pass = .Atlas + region = .Ignore + start_index = cast(u32) len(atlas.clear_draw_list.indices) + blit_quad( & atlas.clear_draw_list, dst_position, dst_position + dst_size, { 1.0, 1.0 }, { 1.0, 1.0 } ) + end_index = cast(u32) len(atlas.clear_draw_list.indices) + append( & atlas.clear_draw_list.calls, call ) + + // Queue up a blit from glyph_update_FBO to the atlas + region = .None + start_index = cast(u32) len(atlas.draw_list.indices) + blit_quad( & atlas.draw_list, dst_glyph_position, dst_position + dst_glyph_size, src_position, src_position + src_size ) + end_index = cast(u32) len(atlas.draw_list.indices) + append( & atlas.draw_list.calls, call ) + } + + // Render glyph to glyph_update_FBO + cache_glyph( ctx, font, glyph_index, entry, bounds_0, bounds_1, glyph_draw_scale, glyph_draw_translate ) +} + // ve_fontcache_clear_drawlist clear_draw_list :: #force_inline proc ( draw_list : ^DrawList ) { clear( & draw_list.calls ) @@ -96,35 +336,42 @@ clear_draw_list :: #force_inline proc ( draw_list : ^DrawList ) { clear( & draw_list.vertices ) } -directly_draw_massive_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph : Glyph, bounds_0 : Vec2i, bounds_width, bounds_height : i32, over_sample, position, scale : Vec2 ) +directly_draw_massive_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph : Glyph, bounds_0, bounds_1 : Vec2i, bounds_width, bounds_height : f32, over_sample, position, scale : Vec2 ) { // profile(#procedure) flush_glyph_buffer_to_atlas( ctx ) + glyph_padding := f32(ctx.atlas.glyph_padding) + glyph_buffer_width := f32(ctx.atlas.buffer_width) + glyph_buffer_height := f32(ctx.atlas.buffer_height) + // Draw un-antialiased glyph to update FBO. glyph_draw_scale := over_sample * entry.size_scale - glyph_draw_translate := Vec2{ -f32(bounds_0.x), -f32(bounds_0.y)} * glyph_draw_scale + Vec2{ f32(ctx.atlas.glyph_padding), f32(ctx.atlas.glyph_padding) } - screenspace_x_form( & glyph_draw_translate, & glyph_draw_scale, f32(ctx.atlas.buffer_width), f32(ctx.atlas.buffer_height) ) + glyph_draw_translate := -1 * Vec2{ f32(bounds_0.x), f32(bounds_0.y) } * glyph_draw_scale + vec2_from_scalar(glyph_padding) + screenspace_x_form( & glyph_draw_translate, & glyph_draw_scale, glyph_buffer_width, glyph_buffer_height ) - cache_glyph( ctx, entry.id, glyph, glyph_draw_scale, glyph_draw_translate ) + cache_glyph( ctx, entry.id, glyph, entry, bounds_0, bounds_1, glyph_draw_scale, glyph_draw_translate ) + + glyph_padding_dbl := glyph_padding * 2 + bounds_scaled := Vec2 { bounds_width, bounds_height } * entry.size_scale // Figure out the source rect. glyph_position := Vec2 {} - glyph_width := f32(bounds_width) * entry.size_scale * over_sample.x - glyph_height := f32(bounds_height) * entry.size_scale * over_sample.y - glyph_dst_width := f32(bounds_width) * entry.size_scale - glyph_dst_height := f32(bounds_height) * entry.size_scale - glyph_width += f32(2 * ctx.atlas.glyph_padding) - glyph_height += f32(2 * ctx.atlas.glyph_padding) - glyph_dst_width += f32(2 * ctx.atlas.glyph_padding) - glyph_dst_height += f32(2 * ctx.atlas.glyph_padding) + glyph_width := bounds_scaled.x * over_sample.x + glyph_height := bounds_scaled.y * over_sample.y + glyph_dst_width := bounds_scaled.x + glyph_dst_height := bounds_scaled.y + glyph_height += glyph_padding_dbl + glyph_width += glyph_padding_dbl + glyph_dst_width += glyph_padding_dbl + glyph_dst_height += glyph_padding_dbl // Figure out the destination rect. - bounds_scaled := Vec2 { + bounds_0_scaled := Vec2 { cast(f32) i32(f32(bounds_0.x) * entry.size_scale - 0.5), cast(f32) i32(f32(bounds_0.y) * entry.size_scale - 0.5), } - dst := position + scale * bounds_scaled + dst := position + scale * bounds_0_scaled dst_width := scale.x * glyph_dst_width dst_height := scale.y * glyph_dst_height dst.x -= scale.x * f32(ctx.atlas.draw_padding) @@ -132,7 +379,7 @@ directly_draw_massive_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph : Gly dst_size := Vec2{ dst_width, dst_height } glyph_size := Vec2 { glyph_width, glyph_height } - textspace_x_form( & glyph_position, & glyph_size, f32(ctx.atlas.buffer_width), f32(ctx.atlas.buffer_height) ) + textspace_x_form( & glyph_position, & glyph_size, glyph_buffer_width, glyph_buffer_height ) // Add the glyph drawcall. call : DrawCall @@ -154,32 +401,25 @@ directly_draw_massive_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph : Gly append( & ctx.draw_list.calls, call ) } -draw_cached_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph_index : Glyph, position, scale : Vec2 ) -> b32 +draw_cached_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph_index : Glyph, + lru_code : u64, atlas_index : i32, + bounds_0, bounds_1 : Vec2i, + region_kind : AtlasRegionKind, region : ^AtlasRegion, over_sample : Vec2, + position, scale : Vec2 +) -> b32 { // profile(#procedure) - // Glyph not in current font - if glyph_index == 0 do return true - if parser_is_glyph_empty( & entry.parser_info, glyph_index ) do return true - - 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) - // Decide which atlas to target - region_kind, region, over_sample := decide_codepoint_region( ctx, entry, glyph_index ) - // E region is special case and not cached to atlas if region_kind == .E { - directly_draw_massive_glyph( ctx, entry, glyph_index, bounds_0, cast(i32) bounds_width, cast(i32) bounds_height, over_sample, position, scale ) + directly_draw_massive_glyph( ctx, entry, glyph_index, bounds_0, bounds_1, bounds_width, bounds_height, over_sample, position, scale ) return true } // Is this codepoint cached? - // lru_code := u64(glyph_index) + ( ( 0x100000000 * u64(entry.id) ) & 0xFFFFFFFF00000000 ) - lru_code := font_glyph_lru_code(entry.id, glyph_index) - atlas_index := LRU_get( & region.state, lru_code ) if atlas_index == - 1 { return false } @@ -192,8 +432,8 @@ draw_cached_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph_index : Glyph, // Figure out the source bounding box in the atlas texture glyph_atlas_position, glyph_atlas_width, glyph_atlas_height := atlas_bbox( atlas, region_kind, atlas_index ) - glyph_width := bounds_width * entry.size_scale - glyph_height := bounds_height * entry.size_scale + glyph_width := bounds_width * entry.size_scale + glyph_height := bounds_height * entry.size_scale glyph_width += glyph_padding glyph_height += glyph_padding @@ -204,14 +444,10 @@ draw_cached_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph_index : Glyph, math.ceil(bounds_0_scaled.x), math.ceil(bounds_0_scaled.y), } - // dst := position * scale * bounds_0_scaled - dst := Vec2 { - position.x + bounds_0_scaled.x * scale.x, - position.y + bounds_0_scaled.y * scale.y, - } + dst := position + bounds_0_scaled * scale + dst -= scale * glyph_padding dst_width := scale.x * glyph_width dst_height := scale.y * glyph_height - dst -= scale * glyph_padding dst_scale := Vec2 { dst_width, dst_height } textspace_x_form( & glyph_atlas_position, & glyph_scale, atlas_width, atlas_height ) @@ -280,187 +516,74 @@ draw_filled_path :: proc( draw_list : ^DrawList, outside_point : Vec2, path : [] } } -// TODO(Ed): Change this to be whitespace aware so that we can optimize the caching of shpaes properly. -// Right now the entire text provided to this call is considered a "shape" this is really bad as basically it invalidates caching for large chunks of text -// Instead we should be aware of whitespace tokens and the chunks between them (the whitespace lexer could be abused for this). -// From there we should maek a 'draw text shape' that breaks up the batch text draws for each of the shapes. -draw_text :: proc( ctx : ^Context, font : FontID, text_utf8 : string, position : Vec2, scale : Vec2 ) -> b32 +draw_text_batch :: proc( ctx : ^Context, entry : ^Entry, shaped : ^ShapedText, batch_start_idx, batch_end_idx : i32, position, scale : Vec2, snap_width, snap_height : f32 ) { - // profile(#procedure) - assert( ctx != nil ) - assert( font >= 0 && int(font) < len(ctx.entries) ) - - context.allocator = ctx.backing - - position := position - snap_width := f32(ctx.snap_width) - snap_height := f32(ctx.snap_height) - 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[ font ] - - // entry.size_scale = parser_scale( & entry.parser_info, entry.size ) - - 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 -} - -draw_text_batch :: proc( ctx : ^Context, entry : ^Entry, shaped : ^ShapedText, batch_start_idx, batch_end_idx : i32, position, scale : Vec2 ) -{ - flush_glyph_buffer_to_atlas( ctx ) + // flush_glyph_buffer_to_atlas( ctx ) for index := batch_start_idx; index < batch_end_idx; index += 1 { // profile(#procedure) - glyph_index := shaped.glyphs[ index ] - shaped_position := shaped.positions[index] - glyph_translate := position + shaped_position * scale - glyph_cached := draw_cached_glyph( ctx, entry, glyph_index, glyph_translate, scale) + glyph_index := shaped.glyphs[ index ] + + if glyph_index == 0 do continue + if parser_is_glyph_empty( & entry.parser_info, 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 ) + bounds_0, bounds_1 := parser_get_glyph_box( & entry.parser_info, glyph_index ) + + shaped_position := shaped.positions[index] + glyph_translate := position + shaped_position * scale + + glyph_cached := draw_cached_glyph( ctx, + entry, glyph_index, + lru_code, atlas_index, + bounds_0, bounds_1, + region_kind, region, over_sample, + glyph_translate, scale) assert( glyph_cached == 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) { + // position := position //+ ctx.cursor_pos * scale // profile(#procedure) batch_start_idx : i32 = 0 for index : i32 = 0; index < i32(len(shaped.glyphs)); index += 1 { glyph_index := shaped.glyphs[ index ] - if is_empty( ctx, entry, glyph_index ) do continue - if can_batch_glyph( ctx, font, entry, glyph_index ) do continue + 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 can_batch_glyph( ctx, font, entry, glyph_index, lru_code, atlas_index, region_kind, region, over_sample ) do continue // Glyph has not been catched, needs to be directly drawn. - draw_text_batch( ctx, entry, shaped, batch_start_idx, index, position, scale ) + + // 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 := font_glyph_lru_code(font, glyph_index) - ctx.temp_codepoint_seen[lru_code] = true - ctx.temp_codepoint_seen_num += 1 - + 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, i32(len(shaped.glyphs)), position, scale ) + flush_glyph_buffer_to_atlas(ctx) + draw_text_batch( ctx, entry, shaped, batch_start_idx, i32(len(shaped.glyphs)), position, scale, snap_width , snap_height ) reset_batch_codepoint_state( ctx ) - cursor_pos = position + shaped.end_cursor_pos * scale + cursor_pos = shaped.end_cursor_pos return } -// ve_fontcache_flush_drawlist -flush_draw_list :: proc( ctx : ^Context ) { - assert( ctx != nil ) - clear_draw_list( & ctx.draw_list ) -} - flush_glyph_buffer_to_atlas :: proc( ctx : ^Context ) { // profile(#procedure) @@ -484,17 +607,6 @@ flush_glyph_buffer_to_atlas :: proc( ctx : ^Context ) } } -// ve_fontcache_drawlist -get_draw_list :: proc( ctx : ^Context ) -> ^DrawList { - assert( ctx != nil ) - 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 ) { @@ -526,13 +638,13 @@ merge_draw_list :: proc( dst, src : ^DrawList ) } } -optimize_draw_list :: proc( draw_list : ^DrawList, call_offset : u64 ) +optimize_draw_list :: proc( draw_list : ^DrawList, call_offset : int ) { // profile(#procedure) assert( draw_list != nil ) - write_index : u64 = call_offset - for index : u64 = 1 + call_offset; index < cast(u64) len(draw_list.calls); index += 1 + write_index : int = call_offset + for index : int = 1 + call_offset; index < len(draw_list.calls); index += 1 { assert( write_index <= index ) draw_0 := & draw_list.calls[ write_index ] diff --git a/code/font/VEFontCache/misc.odin b/code/font/VEFontCache/misc.odin index d3f5f5f..860c57a 100644 --- a/code/font/VEFontCache/misc.odin +++ b/code/font/VEFontCache/misc.odin @@ -72,6 +72,19 @@ eval_point_on_bezier4 :: #force_inline proc "contextless" ( p0, p1, p2, p3 : Vec return { f32(point.x), f32(point.y) } } +is_empty :: #force_inline proc ( ctx : ^Context, entry : ^Entry, glyph_index : Glyph ) -> b32 +{ + if glyph_index == 0 do return true + if parser_is_glyph_empty( & entry.parser_info, glyph_index ) do return true + return false +} + +mark_batch_codepoint_seen :: #force_inline proc ( ctx : ^Context, lru_code : u64 ) +{ + ctx.temp_codepoint_seen[lru_code] = true + ctx.temp_codepoint_seen_num += 1 +} + reset_batch_codepoint_state :: #force_inline proc( ctx : ^Context ) { clear_map( & ctx.temp_codepoint_seen ) ctx.temp_codepoint_seen_num = 0 diff --git a/code/font/VEFontCache/shaped_text.odin b/code/font/VEFontCache/shaped_text.odin index 79cdf5c..cf9e2fe 100644 --- a/code/font/VEFontCache/shaped_text.odin +++ b/code/font/VEFontCache/shaped_text.odin @@ -14,22 +14,24 @@ ShapedTextCache :: struct { next_cache_id : i32, } -shape_text_cached :: proc( ctx : ^Context, font : FontID, text_utf8 : string ) -> ^ShapedText +shape_text_cached :: proc( ctx : ^Context, font : FontID, text_utf8 : string, entry : ^Entry ) -> ^ShapedText { // profile(#procedure) @static buffer : [64 * Kilobyte]byte - font := font + font := font + text_size := len(text_utf8) + sice_end_offset := size_of(FontID) + len(text_utf8) buffer_slice := buffer[:] font_bytes := slice_ptr( transmute(^byte) & font, size_of(FontID) ) copy( buffer_slice, font_bytes ) text_bytes := transmute( []byte) text_utf8 - buffer_slice_post_font := buffer[size_of(FontID) : size_of(FontID) + len(text_utf8) ] + buffer_slice_post_font := buffer[ size_of(FontID) : sice_end_offset ] copy( buffer_slice_post_font, text_bytes ) - hash := shape_lru_hash( transmute(string) buffer[: size_of(FontID) + len(text_utf8)] ) + hash := shape_lru_hash( transmute(string) buffer[: sice_end_offset ] ) shape_cache := & ctx.shape_cache state := & ctx.shape_cache.state @@ -54,20 +56,19 @@ shape_text_cached :: proc( ctx : ^Context, font : FontID, text_utf8 : string ) - LRU_put( state, hash, shape_cache_idx ) } - shape_text_uncached( ctx, font, & shape_cache.storage[ shape_cache_idx ], text_utf8 ) + shape_text_uncached( ctx, font, text_utf8, entry, & shape_cache.storage[ shape_cache_idx ] ) } return & shape_cache.storage[ shape_cache_idx ] } -shape_text_uncached :: proc( ctx : ^Context, font : FontID, output : ^ShapedText, text_utf8 : string ) +shape_text_uncached :: proc( ctx : ^Context, font : FontID, text_utf8 : string, entry : ^Entry, output : ^ShapedText ) { // profile(#procedure) assert( ctx != nil ) assert( font >= 0 && int(font) < len(ctx.entries) ) use_full_text_shape := ctx.text_shape_adv - entry := & ctx.entries[ font ] clear( & output.glyphs ) clear( & output.positions )