package vefontcache

/*
	Note(Ed): This may be seperated in the future into another file dedending on how much is involved with supportin ear-clipping triangulation.
*/

// import "thirdparty:freetype"

Glyph_Trianglation_Method :: enum(i32) {
	Ear_Clipping,
	Triangle_Fanning,
}

Vertex :: struct {
	pos  : Vec2,
	u, v : f32,
}

Glyph_Bounds_Mat :: matrix[2, 2] f32

Glyph_Draw_Quad :: struct {
	dst_pos   : Vec2,
	dst_scale : Vec2,
	src_pos   : Vec2, 
	src_scale : Vec2,
}

// This is used by generate_shape_draw_list & batch_generate_glyphs_draw_list 
// to track relevant glyph data in soa format for pipelined processing
Glyph_Pack_Entry :: struct #packed {
	position           : Vec2,
	atlas_index        : i32,
	region_pos         : Vec2,
	region_size        : Vec2,
	over_sample        : Vec2, // Only used for oversized glyphs
	shape              : Parser_Glyph_Shape,
	draw_transform     : Transform,
	draw_quad          : Glyph_Draw_Quad,
	buffer_x           : f32,
	flush_glyph_buffer : b8,
}

Draw_Call :: struct {
	pass              : Frame_Buffer_Pass,
	start_index       : u32,
	end_index         : u32,
	clear_before_draw : b32,
	region            : Atlas_Region_Kind,
	colour            : RGBAN,
}

Draw_Call_Default :: Draw_Call {
	pass              = .None,
	start_index       = 0,
	end_index         = 0,
	clear_before_draw = false,
	region            = .A,
	colour            = { 1.0, 1.0, 1.0, 1.0 }
}

Draw_List :: struct {
	vertices : [dynamic]Vertex,
	indices  : [dynamic]u32,
	calls    : [dynamic]Draw_Call,
}

Frame_Buffer_Pass :: enum u32 {
	None            = 0,
	Glyph           = 1, // Operations on glyph buffer render target
	Atlas           = 2, // Operations on atlas render target
	Target          = 3, // Operations on user's end-destination render target using atlas
	Target_Uncached = 4, // Operations on user's end-destination render target using glyph buffer
}

Glyph_Batch_Cache :: struct {
	table : map[Atlas_Key]b8,
	num   : i32,
	cap   : i32,
}

// The general tracker for a generator pipeline 
Glyph_Draw_Buffer :: struct{
	over_sample       : Vec2,
	size              : Vec2i,
	draw_padding      : f32,
	snap_glyph_height : f32,
	snap_glyph_width  : f32,

	allocated_x     : i32, // Space used (horizontally) within the glyph buffer
	clear_draw_list : Draw_List,
	draw_list       : Draw_List,

	batch_cache       : Glyph_Batch_Cache,
	shape_gen_scratch : [dynamic]Vertex, // Used during triangulating a glyph into a mesh.

	glyph_pack : #soa[dynamic]Glyph_Pack_Entry,
	oversized  : [dynamic]i32,
	to_cache   : [dynamic]i32,
	cached     : [dynamic]i32,
}

// Contructs a quad mesh for bliting a texture from source render target (src uv0 & 1) to the destination render target (p0, p1)
@(optimization_mode="favor_size")
blit_quad :: #force_inline proc ( draw_list : ^Draw_List, 
	p0  : Vec2 = {0, 0}, 
	p1  : Vec2 = {1, 1}, 
	uv0 : Vec2 = {0, 0}, 
	uv1 : Vec2 = {1, 1} 
)
{
	// profile(#procedure)
	v_offset := cast(u32) len(draw_list.vertices)

	quadv : [4]Vertex = {
		{
			{p0.x, p0.y},
			uv0.x, uv0.y
		},
		{
			{p0.x, p1.y},
			uv0.x, uv1.y
		},
		{
			{p1.x, p0.y},
			uv1.x, uv0.y
		},
		{
			{p1.x, p1.y},
			uv1.x, uv1.y
		}
	}
	append( & draw_list.vertices, ..quadv[:] )

	quad_indices : []u32 = {
		0 + v_offset, 1 + v_offset, 2 + v_offset,
		2 + v_offset, 1 + v_offset, 3 + v_offset
	}
	append( & draw_list.indices, ..quad_indices[:] )
	return
}

// Constructs a triangle fan mesh to fill a shape using the provided path outside_point represents the center point of the fan.
@(optimization_mode="favor_size")
fill_path_via_fan_triangulation :: proc( draw_list : ^Draw_List, 
	outside_point : Vec2, 
	path          : []Vertex,
	scale         := Vec2 { 1, 1 },
	translate     := Vec2 { 0, 0 }
) #no_bounds_check
{
	// profile(#procedure)
	v_offset := cast(u32) len(draw_list.vertices)
	for point in path {
		point    := point
		point.pos = point.pos * scale + translate
		append( & draw_list.vertices, point )
	}

	outside_vertex := cast(u32) len(draw_list.vertices)
	{
		vertex := Vertex {
			pos = outside_point * scale + translate,
			u = 0,
			v = 0,
		}
		append( & draw_list.vertices, vertex )
	}

	for index : u32 = 1; index < cast(u32) len(path); index += 1 {
		indices := & draw_list.indices
		to_add := [3]u32 {
			outside_vertex,
			v_offset + index - 1,
			v_offset + index
		}
		append( indices, ..to_add[:] )
	}
}

// Glyph triangulation generator
@(optimization_mode="favor_size")
generate_glyph_pass_draw_list :: proc(draw_list : ^Draw_List, path : ^[dynamic]Vertex,
	glyph_shape      : Parser_Glyph_Shape, 
	curve_quality    : f32, 
	bounds           : Range2, 
	translate, scale : Vec2
) #no_bounds_check
{
	profile(#procedure)
	outside := Vec2{bounds.p0.x - 21, bounds.p0.y - 33}

	draw            := Draw_Call_Default
	draw.pass        = Frame_Buffer_Pass.Glyph
	draw.start_index = u32(len(draw_list.indices))

	clear(path)

	step := 1.0 / curve_quality
	for edge, index in glyph_shape do #partial switch edge.type
	{
		case .Move:
			if len(path) > 0 {
				fill_path_via_fan_triangulation( draw_list, outside, path[:], scale, translate)
				clear(path)
			}
			fallthrough

		case .Line:
			append( path, Vertex { pos = Vec2 { f32(edge.x), f32(edge.y)} } )

		case .Curve:
			assert(len(path) > 0)
			p0 := path[ len(path) - 1].pos
			p1 := Vec2{ f32(edge.contour_x0), f32(edge.contour_y0) }
			p2 := Vec2{ f32(edge.x), f32(edge.y) }

			for index : f32 = 1; index <= curve_quality; index += 1 {
				alpha := index * step
				append( path, Vertex { pos = eval_point_on_bezier3(p0, p1, p2, alpha) } )
			}

		case .Cubic:
			assert( len(path) > 0)
			p0 := path[ len(path) - 1].pos
			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) }

			for index : f32 = 1; index <= curve_quality; index += 1 {
				alpha := index * step
				append( path, Vertex { pos = eval_point_on_bezier4(p0, p1, p2, p3, alpha) } )
			}
	}

	if len(path) > 0 {
		fill_path_via_fan_triangulation(draw_list, outside, path[:], scale, translate)
	}

	draw.end_index = u32(len(draw_list.indices))
	if draw.end_index > draw.start_index {
		append( & draw_list.calls, draw)
	}
}

// Just a warpper of generate_shape_draw_list for handling an array of shapes
generate_shapes_draw_list :: #force_inline proc ( ctx : ^Context, 
	font       : Font_ID, 
	colour     : RGBAN, 
	entry      : Entry, 
	px_size    : f32, 
	font_scale : f32, 
	position   : Vec2, 
	scale      : Vec2, 
	shapes     : []Shaped_Text
)
{
	assert(len(shapes) > 0)
	for shape in shapes {
		ctx.cursor_pos = {}
		ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer, ctx.px_scalar,
			colour, 
			entry,
			px_size,
			font_scale, 
			position,
			scale, 
		)
	}
}

/* Generator pipeline for shapes
	This procedure has no awareness of layers. That should be handled by a higher-order codepath. 

	Pipleine order:
	* Resolve the glyph's position offset from the target position
	* Segregate the glyphs into three slices: oversized, to_cache, cached. 
		* If oversized is not necessary for your use case and your hitting a bottleneck, omit it with setting ENABLE_OVERSIZED_GLYPHS to false.
		* The segregation will not allow slices to exceed the batch_cache capacity of the glyph_buffer (configurable within startup params)
		* When The capacity is reached batch_generate_glyphs_draw_list will be called which will do futher compute and then finally draw_list generation.
	* This may perform better with smaller shapes vs larger shapes, but having more shapes has a cache lookup penatly (if done per frame) so keep that in mind.
*/
generate_shape_draw_list :: proc( draw_list : ^Draw_List, shape : Shaped_Text,
	atlas        : ^Atlas,
	glyph_buffer : ^Glyph_Draw_Buffer,
	px_scalar    : f32,

	colour     : RGBAN,
	entry      : Entry,
	px_size    : f32,
	font_scale : f32,

	target_position : Vec2,
	target_scale    : Vec2,
) -> (cursor_pos : Vec2) #no_bounds_check
{
	profile(#procedure)

	mark_glyph_seen :: #force_inline proc "contextless" ( cache : ^Glyph_Batch_Cache, lru_code : Atlas_Key ) {
		cache.table[lru_code] = true
		cache.num            += 1
	}
	reset_batch :: #force_inline proc( cache : ^Glyph_Batch_Cache ) {
		clear_map( & cache.table )
		cache.num = 0
	}

	atlas_glyph_pad   := atlas.glyph_padding
	atlas_size        := vec2(atlas.size)
	glyph_buffer_size := vec2(glyph_buffer.size)

	// Make sure the packs are large enough for the shape
	glyph_pack := & glyph_buffer.glyph_pack
	oversized  := & glyph_buffer.oversized
	to_cache   := & glyph_buffer.to_cache
	cached     := & glyph_buffer.cached
	resize_soa_non_zero(glyph_pack, len(shape.visible))
	
	profile_begin("batching & segregating glyphs")
	// We do any reservation up front as appending to the array's will not check.
	reserve(oversized, len(shape.visible))
	reserve(to_cache,  len(shape.visible))
	reserve(cached,    len(shape.visible))
	clear(oversized)
	clear(to_cache)
	clear(cached)
	reset_batch( & glyph_buffer.batch_cache)

	append_sub_pack :: #force_inline proc ( pack : ^[dynamic]i32, entry : i32 )
	{
		raw := cast(^Raw_Dynamic_Array) pack
		raw.len            += 1
		pack[len(pack) - 1] = entry
	}
	sub_slice :: #force_inline proc "contextless" ( pack : ^[dynamic]i32) -> []i32 { return pack[:] }

	profile_begin("translate")
	for & glyph, index in glyph_pack {
		// Throughout the draw list generation vis_id will need to be used over index as 
		// not all glyphs or positions for the shape are visibly rendered.
		vis_id        := shape.visible[index]
		glyph.position = target_position + (shape.position[vis_id]) * target_scale
	}
	profile_end()

	for & glyph, index in glyph_pack
	{
		// atlas_lru_code, region_kind, and bounds are all 1:1 with shape.visible
		atlas_key          := shape.atlas_lru_code[index]
		region_kind        := shape.region_kind[index]
		bounds             := shape.bounds[index]
		bounds_size_scaled := size(bounds) * font_scale

		assert(region_kind != .None, "FAILED TO ASSGIN REGION")
		when ENABLE_OVERSIZED_GLYPHS
		{
			if region_kind == .E
			{
				glyph.over_sample = \
					bounds_size_scaled.x <= glyph_buffer_size.x / 2 &&
					bounds_size_scaled.y <= glyph_buffer_size.y / 2 ? \
						{2.0, 2.0} \
					: {1.0, 1.0}
				append_sub_pack(oversized, cast(i32) index)
				continue
			}
		}
		
		glyph.over_sample = glyph_buffer.over_sample
		region           := atlas.regions[region_kind]
		glyph.atlas_index =  lru_get( & region.state, atlas_key )

		// Glyphs are prepared in batches based on the capacity of the batch cache.
		Prepare_For_Batch:
		{
			pack := cached

			found_take_slow_path : b8
			success : bool

			// Determine if we hit the limit for this batch.
			if glyph.atlas_index == - 1
			{
				// Check to see if we reached capacity for the atlas
				if region.next_idx > region.state.capacity
				{
					// We will evict LRU. We must predict which LRU will get evicted, and if it's something we've seen then we need to take slowpath and flush batch.
					next_evict_glyph              := lru_get_next_evicted( region.state )
					found_take_slow_path, success  = glyph_buffer.batch_cache.table[next_evict_glyph]
					assert(success != false)
					// TODO(Ed): This might not be needed with the new pipeline/batching 
					if (found_take_slow_path) {
						break Prepare_For_Batch
					}
				}
				// profile_begin("glyph needs caching")
				glyph.atlas_index = atlas_reserve_slot(region, atlas_key)
				pack              = to_cache
				// profile_end()
			}
			// profile("append cached")
			glyph.region_pos, glyph.region_size = atlas_region_bbox(region ^, glyph.atlas_index)
			mark_glyph_seen(& glyph_buffer.batch_cache, atlas_key)
			append_sub_pack(pack, cast(i32) index)
			// TODO(Ed): This might not be needed with the new pipeline/batching 
			// if (found_take_slow_path) {
				// break Prepare_For_Batch
			// }
			if glyph_buffer.batch_cache.num >= glyph_buffer.batch_cache.cap do break Prepare_For_Batch
			continue
		}

		// Batch has been prepared for a set of glyphs time to generate glyphs.
		batch_generate_glyphs_draw_list( draw_list, shape, glyph_pack, sub_slice(cached), sub_slice(to_cache), sub_slice(oversized),
			atlas, 
			glyph_buffer, 
			atlas_size, 
			glyph_buffer_size, 
			entry, 
			colour, 
			font_scale, 
			target_scale
		)

		reset_batch( & glyph_buffer.batch_cache)
		clear(oversized)
		clear(to_cache)
		clear(cached)
	}
	profile_end()

	if len(oversized) > 0 || glyph_buffer.batch_cache.num > 0
	{
		// Last batch pass
		batch_generate_glyphs_draw_list( draw_list, shape, glyph_pack, sub_slice(cached), sub_slice(to_cache), sub_slice(oversized),
			atlas, 
			glyph_buffer, 
			atlas_size, 
			glyph_buffer_size, 
			entry, 
			colour, 
			font_scale, 
			target_scale,
		)
	}

	cursor_pos = target_position + shape.end_cursor_pos * target_scale
	return
}

/*
	The glyphs types have been segregated by this point into a batch slice of indices to the glyph_pack
	The transform and draw quads are computed first (getting the math done in one spot as possible)
	Some of the math from to_cache pass for glyph generation was not moved over (it could be but I'm not sure its worth it)

	Order    : Oversized first, then to_cache, then cached.
	Important: These slices store ids for glyph_pack which matches shape.visible in index. 
	shape.position and shape.glyph DO NOT.

	There are only two places this matters for: getting glyph shapes when doing glyph pass generation for oversized and to_cache iterations.

	Oversized and to_cache will both enqueue operations for rendering glyphs to the glyph buffer render target.
	The compute section will have operations regarding how many glyphs they may alloate before a flush must occur.
	A flush will force one of the following:
	  * Oversized will have a draw call setup to blit directly from the glyph buffer to the target.
		* to_cache will blit the glyphs rendered from the buffer to the atlas.
*/
@(optimization_mode="favor_size")
batch_generate_glyphs_draw_list :: proc ( draw_list : ^Draw_List,
	shape      : Shaped_Text,
	glyph_pack : ^#soa[dynamic]Glyph_Pack_Entry,
	cached     : []i32, 
	to_cache   : []i32, 
	oversized  : []i32,

	atlas             : ^Atlas,
	glyph_buffer      : ^Glyph_Draw_Buffer,
	atlas_size        : Vec2,
	glyph_buffer_size : Vec2,

	entry        : Entry,
	colour       : RGBAN,
	font_scale   : Vec2,
	target_scale : Vec2,
) #no_bounds_check
{
	profile(#procedure)
	colour := colour

	profile_begin("glyph transform & draw quads compute")
	for id, index in cached
	{
		// Quad to for drawing atlas slot to target
		glyph         := & glyph_pack[id]
		bounds        := shape.bounds[id]
		bounds_scaled := mul(bounds, font_scale)
		glyph_scale   := size(bounds_scaled) + atlas.glyph_padding

		quad  := & glyph.draw_quad
		quad.dst_pos   = glyph.position + (bounds_scaled.p0) * target_scale
		quad.dst_scale =                  (glyph_scale)      * target_scale
		quad.src_scale =                  (glyph_scale)
		quad.src_pos   = (glyph.region_pos) 
		to_target_space( & quad.src_pos, & quad.src_scale, atlas_size )
	}
	for id, index in to_cache
	{
		glyph         := & glyph_pack[id]
		bounds        := shape.bounds[id]
		bounds_scaled := mul(bounds, font_scale)
		glyph_scale   := size(bounds_scaled) + glyph_buffer.draw_padding

		f32_allocated_x := cast(f32) glyph_buffer.allocated_x

		// Resolve how much space this glyph will allocate in the buffer
		buffer_size   := glyph_scale * glyph_buffer.over_sample
		// Allocate a glyph glyph render target region (FBO)
		to_allocate_x := buffer_size.x + 4.0

		// If allocation would exceed buffer's bounds the buffer must be flush before this glyph can be rendered.
		glyph.flush_glyph_buffer = i32(f32_allocated_x + to_allocate_x) >= i32(glyph_buffer_size.x)
		glyph.buffer_x           = f32_allocated_x * f32( i32( ! glyph.flush_glyph_buffer ) )

		// The glyph buffer space transform for generate_glyph_pass_draw_list
		draw_transform       := & glyph.draw_transform
		draw_transform.scale  = font_scale * glyph_buffer.over_sample
		draw_transform.pos    = -1 * (bounds.p0) * draw_transform.scale + glyph_buffer.draw_padding
		draw_transform.pos.x += glyph.buffer_x
		to_glyph_buffer_space( & draw_transform.pos, & draw_transform.scale, glyph_buffer_size )

		// Allocate the space
		glyph_buffer.allocated_x += i32(to_allocate_x)

		// Quad to for drawing atlas slot to target (used in generate_cached_draw_list)
		draw_quad := & glyph.draw_quad

		// Destination  (draw_list's target image)
		draw_quad.dst_pos   = glyph.position + (bounds_scaled.p0) * target_scale
		draw_quad.dst_scale =                  (glyph_scale)      * target_scale

		// UV Coordinates for sampling the atlas
		draw_quad.src_scale = (glyph_scale)
		draw_quad.src_pos   = (glyph.region_pos)
		to_target_space( & draw_quad.src_pos, & draw_quad.src_scale, atlas_size )
	}
	when ENABLE_OVERSIZED_GLYPHS do for id, index in oversized
	{
		glyph_padding      := vec2(glyph_buffer.draw_padding)
		glyph              := & glyph_pack[id]
		bounds             := shape.bounds[id]
		bounds_scaled      := mul(bounds, font_scale)
		bounds_size_scaled := size(bounds_scaled)

		f32_allocated_x := cast(f32) glyph_buffer.allocated_x
		// Resolve how much space this glyph will allocate in the buffer
		buffer_size     := (bounds_size_scaled + glyph_padding) * glyph.over_sample

		// Allocate a glyph glyph render target region (FBO)
		to_allocate_x            := buffer_size.x + 2.0
		glyph_buffer.allocated_x += i32(to_allocate_x)

		// If allocation would exceed buffer's bounds the buffer must be flush before this glyph can be rendered.
		glyph.flush_glyph_buffer = i32(f32_allocated_x + to_allocate_x) >= i32(glyph_buffer_size.x)
		glyph.buffer_x           = f32_allocated_x * f32( i32( ! glyph.flush_glyph_buffer ) )

		// Quad to for drawing atlas slot to target
		draw_quad := & glyph.draw_quad

		// Target position (draw_list's target image)
		draw_quad.dst_pos   = glyph.position + (bounds_scaled.p0   - glyph_padding) * target_scale
		draw_quad.dst_scale =                  (bounds_size_scaled + glyph_padding) * target_scale
		
		// The glyph buffer space transform for generate_glyph_pass_draw_list
		draw_transform       := & glyph.draw_transform
		draw_transform.scale  = font_scale * glyph.over_sample 
		draw_transform.pos    = -1 * bounds.p0 * draw_transform.scale + vec2(atlas.glyph_padding)
		draw_transform.pos.x += glyph.buffer_x
		to_glyph_buffer_space( & draw_transform.pos, & draw_transform.scale, glyph_buffer_size )


		draw_quad.src_pos   = Vec2 { glyph.buffer_x, 0 }
		draw_quad.src_scale = bounds_size_scaled * glyph.over_sample + glyph_padding
		to_target_space( & draw_quad.src_pos, & draw_quad.src_scale, glyph_buffer_size )
	}
	profile_end()

	profile_begin("gen oversized glyphs draw_list")
	when ENABLE_OVERSIZED_GLYPHS do if len(oversized) > 0
	{
		when ENABLE_DRAW_TYPE_VISUALIZATION {
			colour.r = 1.0
			colour.g = 1.0
			colour.b = 0.0
		}
		for pack_id, index in oversized {
			vis_id := shape.visible[pack_id]
			error : Allocator_Error
			glyph_pack[pack_id].shape, error = parser_get_glyph_shape(entry.parser_info, shape.glyph[vis_id])
			assert(error == .None)
			assert(glyph_pack[pack_id].shape != nil)
		}
		for id, index in oversized
		{
			glyph  := & glyph_pack[id]
			bounds := shape.bounds[id]
			if glyph.flush_glyph_buffer do flush_glyph_buffer_draw_list(draw_list, 
				& glyph_buffer.draw_list, 
				& glyph_buffer.clear_draw_list, 
				& glyph_buffer.allocated_x
			)

			generate_glyph_pass_draw_list( draw_list, & glyph_buffer.shape_gen_scratch,
				glyph_pack[id].shape, 
				entry.curve_quality, 
				bounds, 
				glyph_pack[id].draw_transform.pos,
				glyph_pack[id].draw_transform.scale
			)

			target_quad := & glyph_pack[id].draw_quad

			draw_to_target : Draw_Call
			{
				draw_to_target.pass        = .Target_Uncached
				draw_to_target.colour      = colour
				draw_to_target.start_index = u32(len(draw_list.indices))

				blit_quad( draw_list,
					target_quad.dst_pos, target_quad.dst_pos + target_quad.dst_scale,
					target_quad.src_pos, target_quad.src_pos + target_quad.src_scale )

				draw_to_target.end_index = u32(len(draw_list.indices))
			}
			append( & draw_list.calls, draw_to_target )
		}

		flush_glyph_buffer_draw_list(draw_list, & glyph_buffer.draw_list, & glyph_buffer.clear_draw_list, & glyph_buffer.allocated_x)
		for pack_id, index in oversized {
			assert(glyph_pack[pack_id].shape != nil)
			parser_free_shape(entry.parser_info, glyph_pack[pack_id].shape)
		}
	}
	profile_end()

	@(optimization_mode="favor_size")
	generate_blit_from_atlas_draw_list :: #force_inline proc  (draw_list : ^Draw_List, glyph_pack : #soa[]Glyph_Pack_Entry, sub_pack : []i32, colour : RGBAN )
	{
		profile(#procedure)
		call       := Draw_Call_Default
		call.pass   = .Target
		call.colour = colour
		for id, index in sub_pack
		{
			// profile("glyph")
			call.start_index = u32(len(draw_list.indices))

			quad := glyph_pack[id].draw_quad
			blit_quad(draw_list,
				quad.dst_pos, quad.dst_pos + quad.dst_scale,
				quad.src_pos, quad.src_pos + quad.src_scale
			)
			call.end_index = u32(len(draw_list.indices))
			append(& draw_list.calls, call)
		}
	}

	profile_begin("to_cache: caching to atlas")
	if len(to_cache) > 0
	{
		for pack_id, index in to_cache {
			vis_id := shape.visible[pack_id]
			error : Allocator_Error
			glyph_pack[pack_id].shape, error = parser_get_glyph_shape(entry.parser_info, shape.glyph[vis_id])
			assert(error == .None)
			assert(glyph_pack[pack_id].shape != nil)
		}

		for id, index in to_cache
		{
			// profile("glyph")
			glyph              := & glyph_pack[id]
			bounds             := shape.bounds[id]
			bounds_scaled      := mul(bounds, font_scale)
			bounds_size_scaled := size(bounds_scaled)

			if glyph.flush_glyph_buffer do flush_glyph_buffer_draw_list( draw_list, 
				& glyph_buffer.draw_list,
				& glyph_buffer.clear_draw_list,
				& glyph_buffer.allocated_x
			)
	
			dst_region_pos    := glyph.region_pos
			dst_region_size   := glyph.region_size
			to_glyph_buffer_space( & dst_region_pos, & dst_region_size, atlas_size )
		
			clear_target_region : Draw_Call
			clear_target_region.pass        = .Atlas
			clear_target_region.region      = .Ignore
			clear_target_region.start_index = cast(u32) len(glyph_buffer.clear_draw_list.indices)
			blit_quad( & glyph_buffer.clear_draw_list,
				dst_region_pos, dst_region_pos + dst_region_size,
				{ 1.0, 1.0 },  { 1.0, 1.0 }
			)
			clear_target_region.end_index = cast(u32) len(glyph_buffer.clear_draw_list.indices)
			
			dst_glyph_pos    := glyph.region_pos
			dst_glyph_size   := bounds_size_scaled + atlas.glyph_padding
			dst_glyph_size.x  = max(dst_glyph_size.x, ceil(dst_glyph_size.x) * glyph_buffer.snap_glyph_width)   // Note(Ed): Can (in specific cases, rare.) improve hinting
			dst_glyph_size.y  = max(dst_glyph_size.y, ceil(dst_glyph_size.y) * glyph_buffer.snap_glyph_height)  // Note(Ed): Seems to improve hinting
			to_glyph_buffer_space( & dst_glyph_pos, & dst_glyph_size, atlas_size )
	
			src_position  := Vec2 { glyph.buffer_x, 0 }
			src_size      := (bounds_size_scaled + atlas.glyph_padding) * glyph_buffer.over_sample
			src_size.x     = max(src_size.x, ceil(src_size.x) * glyph_buffer.snap_glyph_width)  // Note(Ed): Can (in specific cases, rare.) improve hinting
			src_size.y     = max(src_size.y, ceil(src_size.y) * glyph_buffer.snap_glyph_height) // Note(Ed): Seems to improve hinting
			to_target_space( & src_position, & src_size, glyph_buffer_size )
			
			blit_to_atlas : Draw_Call
			blit_to_atlas.pass        = .Atlas
			blit_to_atlas.region      = .None
			blit_to_atlas.start_index = cast(u32) len(glyph_buffer.draw_list.indices)
			blit_quad( & glyph_buffer.draw_list,
				dst_glyph_pos, dst_glyph_pos + dst_glyph_size,
				src_position,  src_position  + src_size )
			blit_to_atlas.end_index = cast(u32) len(glyph_buffer.draw_list.indices)
	
			append( & glyph_buffer.clear_draw_list.calls, clear_target_region )
			append( & glyph_buffer.draw_list.calls,       blit_to_atlas )

			// Render glyph to glyph render target (FBO)
			generate_glyph_pass_draw_list( draw_list, & glyph_buffer.shape_gen_scratch, 
				glyph.shape, 
				entry.curve_quality, 
				bounds, 
				glyph.draw_transform.pos, 
				glyph.draw_transform.scale 
			)
		}

		flush_glyph_buffer_draw_list(draw_list, & glyph_buffer.draw_list, & glyph_buffer.clear_draw_list, & glyph_buffer.allocated_x)
		for pack_id, index in to_cache {
			assert(glyph_pack[pack_id].shape != nil)
			parser_free_shape(entry.parser_info, glyph_pack[pack_id].shape)
		} 

		profile_begin("gen_cached_draw_list: to_cache")
		when ENABLE_DRAW_TYPE_VISUALIZATION {
			colour.r = 1.0
			colour.g = 0.0
			colour.b = 0.0
		}
		generate_blit_from_atlas_draw_list( draw_list, glyph_pack[:], to_cache, colour )
		profile_end()
	}
	profile_end()

	profile_begin("gen_cached_draw_list: cached")
	when ENABLE_DRAW_TYPE_VISUALIZATION {
		colour.r = 0.5
		colour.g = 0.5
		colour.b = 0.5
	}
	generate_blit_from_atlas_draw_list( draw_list, glyph_pack[:], cached, colour )
	profile_end()
}

// Flush the content of the glyph_buffers draw lists to the main draw list
flush_glyph_buffer_draw_list :: proc( #no_alias draw_list, glyph_buffer_draw_list,  glyph_buffer_clear_draw_list : ^Draw_List, allocated_x : ^i32 )
{
	profile(#procedure)
	merge_draw_list( draw_list, glyph_buffer_clear_draw_list )
	merge_draw_list( draw_list, glyph_buffer_draw_list)
	clear_draw_list( glyph_buffer_draw_list )
	clear_draw_list( glyph_buffer_clear_draw_list )

	call := Draw_Call_Default
	call.pass              = .Glyph
	call.start_index       = 0
	call.end_index         = 0
	call.clear_before_draw = true
	append( & draw_list.calls, call )
	(allocated_x ^) = 0
}

@(optimization_mode="favor_size")
clear_draw_list :: #force_inline proc ( draw_list : ^Draw_List ) {
	clear( & draw_list.calls )
	clear( & draw_list.indices )
	clear( & draw_list.vertices )
}

// Helper used by flush_glyph_buffer_draw_list. Used to append all the content from the src draw list o the destination.
@(optimization_mode="favor_size")
merge_draw_list :: proc ( #no_alias dst, src : ^Draw_List ) #no_bounds_check
{
	profile(#procedure)
	error : Allocator_Error

	v_offset := cast(u32) len( dst.vertices )
	num_appended : int
	num_appended, error = append( & dst.vertices, ..src.vertices[:] )
	assert( error == .None )

	i_offset := cast(u32) len(dst.indices)
	for index : i32 = 0; index < cast(i32) len(src.indices); index += 1 {
		ignored : int
		ignored, error = append( & dst.indices, src.indices[index] + v_offset )
		assert( error == .None )
	}

	for index : i32 = 0; index < cast(i32) len(src.calls); index += 1 {
		src_call             := src.calls[ index ]
		src_call.start_index += i_offset
		src_call.end_index   += i_offset
		append( & dst.calls, src_call )
		assert( error == .None )
	}
}

// Naive implmentation to merge passes that are equivalent and the following to be merged (b for can_merge_draw_calls) doesn't have a clear todo.
// Its intended for optimiztion passes to occur on a per-layer basis.
optimize_draw_list :: proc (draw_list: ^Draw_List, call_offset: int)  #no_bounds_check
{
	profile(#procedure)
	assert(draw_list != nil)

	can_merge_draw_calls :: #force_inline proc "contextless" ( a, b : ^Draw_Call ) -> bool {
		result := \
		a.pass      == b.pass        &&
		a.end_index == b.start_index &&
		a.region    == b.region      &&
		a.colour    == b.colour      &&
		! b.clear_before_draw
		return result
	}

	write_index := call_offset
	for read_index := call_offset + 1; read_index < len(draw_list.calls); read_index += 1
	{
		draw_current := & draw_list.calls[write_index]
		draw_next    := & draw_list.calls[read_index]

		if can_merge_draw_calls(draw_current, draw_next) {
			draw_current.end_index = draw_next.end_index
		}
		else {
			// Move to the next write position and copy the draw call
			write_index += 1
			if write_index != read_index {
				draw_list.calls[write_index] = (draw_next^)
			}
		}
	}

	resize( & draw_list.calls, write_index + 1)
}
