VEFontCache: Codepath simplificiation & optimization
This commit is contained in:
		| @@ -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 ) | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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") | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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. | ||||
|  | ||||
|  | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								code/font/VEFontCache/docs/draw_text_codepaths.pur
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								code/font/VEFontCache/docs/draw_text_codepaths.pur
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -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 ] | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 ) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user