Got render_list proper setup for UI_State rendering...
Still need a spacial indexing method to prevent redundant enqueues to the drawlist (esp for text) I'm in the process of trying to see if I can keep getting vefontcache even more performant... Making it multi-threaded can help but there might be some single-thread per still possible..
This commit is contained in:
		| @@ -139,7 +139,7 @@ pool_list_erase :: proc( pool : ^Pool_List, iter : Pool_ListIter ) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| pool_list_move_to_front :: #force_inline proc( pool : ^Pool_List, iter : Pool_ListIter ) | ||||
| pool_list_move_to_front :: #force_inline proc "contextless" ( pool : ^Pool_List, iter : Pool_ListIter ) | ||||
| { | ||||
| 	using pool | ||||
|  | ||||
| @@ -156,7 +156,7 @@ pool_list_move_to_front :: #force_inline proc( pool : ^Pool_List, iter : Pool_Li | ||||
| 	front               = iter | ||||
| } | ||||
|  | ||||
| pool_list_peek_back :: #force_inline proc ( pool : ^Pool_List ) -> Pool_ListValue { | ||||
| pool_list_peek_back :: #force_inline proc ( pool : ^Pool_List ) -> Pool_ListValue #no_bounds_check { | ||||
| 	assert( pool.back != - 1 ) | ||||
| 	value := pool.items[ pool.back ].value | ||||
| 	return value | ||||
| @@ -217,7 +217,7 @@ lru_find :: #force_inline proc "contextless" ( cache : ^LRU_Cache, key : u64, mu | ||||
| 	return link, success | ||||
| } | ||||
|  | ||||
| lru_get :: #force_inline proc( cache: ^LRU_Cache, key : u64 ) -> i32 { | ||||
| lru_get :: #force_inline proc ( cache: ^LRU_Cache, key : u64 ) -> i32 #no_bounds_check { | ||||
| 	if link, ok := &cache.table[ key ]; ok { | ||||
| 			pool_list_move_to_front(&cache.key_queue, link.ptr) | ||||
| 			return link.value | ||||
| @@ -233,7 +233,7 @@ lru_get_next_evicted :: #force_inline proc ( cache : ^LRU_Cache ) -> u64 { | ||||
| 	return 0xFFFFFFFFFFFFFFFF | ||||
| } | ||||
|  | ||||
| lru_peek :: #force_inline proc ( cache : ^LRU_Cache, key : u64, must_find := false ) -> i32 { | ||||
| lru_peek :: #force_inline proc "contextless" ( cache : ^LRU_Cache, key : u64, must_find := false ) -> i32 { | ||||
| 	iter, success := lru_find( cache, key, must_find ) | ||||
| 	if success == false { | ||||
| 		return -1 | ||||
| @@ -243,6 +243,7 @@ lru_peek :: #force_inline proc ( cache : ^LRU_Cache, key : u64, must_find := fal | ||||
|  | ||||
| lru_put :: #force_inline proc( cache : ^LRU_Cache, key : u64, value : i32 ) -> u64 | ||||
| { | ||||
| 	profile(#procedure) | ||||
| 	if link, ok := & cache.table[ key ]; ok { | ||||
| 		pool_list_move_to_front( & cache.key_queue, link.ptr ) | ||||
| 		link.value = value | ||||
|   | ||||
| @@ -27,7 +27,7 @@ Atlas :: struct { | ||||
| 	width  : i32, | ||||
| 	height : i32, | ||||
|  | ||||
| 	glyph_padding     : i32, // Padding to add to bounds_<width/height>_scaled for choosing which atlas region. | ||||
| 	glyph_padding     : f32, // Padding to add to bounds_<width/height>_scaled for choosing which atlas region. | ||||
| 	glyph_over_scalar : f32, // Scalar to apply to bounds_<width/height>_scaled for choosing which atlas region. | ||||
|  | ||||
| 	region_a : Atlas_Region, | ||||
| @@ -36,8 +36,9 @@ Atlas :: struct { | ||||
| 	region_d : Atlas_Region, | ||||
| } | ||||
|  | ||||
| atlas_bbox :: proc( atlas : ^Atlas, region : Atlas_Region_Kind, local_idx : i32 ) -> (position, size: Vec2) | ||||
| atlas_bbox :: #force_inline proc "contextless" ( atlas : ^Atlas, region : Atlas_Region_Kind, local_idx : i32 ) -> (position, size: Vec2) | ||||
| { | ||||
| 	profile(#procedure) | ||||
| 	switch region | ||||
| 	{ | ||||
| 		case .A: | ||||
| @@ -85,22 +86,21 @@ atlas_bbox :: proc( atlas : ^Atlas, region : Atlas_Region_Kind, local_idx : i32 | ||||
| 	return | ||||
| } | ||||
|  | ||||
| decide_codepoint_region :: proc(ctx : ^Context, entry : ^Entry, glyph_index : Glyph ) -> (region_kind : Atlas_Region_Kind, region : ^Atlas_Region, over_sample : Vec2) | ||||
| decide_codepoint_region :: #force_inline proc (ctx : ^Context, entry : ^Entry, glyph_index : Glyph ) -> (region_kind : Atlas_Region_Kind, region : ^Atlas_Region, over_sample : Vec2) | ||||
| { | ||||
| 	profile(#procedure) | ||||
| 	if parser_is_glyph_empty(&entry.parser_info, glyph_index) { | ||||
| 		return .None, nil, {} | ||||
| 	} | ||||
|  | ||||
| 	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) | ||||
| 	bounds_size        := vec2(bounds_1) - vec2(bounds_0) | ||||
|  | ||||
| 	atlas         := & ctx.atlas | ||||
| 	glyph_buffer  := & ctx.glyph_buffer | ||||
| 	glyph_padding := f32( atlas.glyph_padding ) * 2 | ||||
| 	atlas             := & ctx.atlas | ||||
| 	glyph_buffer      := & ctx.glyph_buffer | ||||
| 	glyph_padding_dbl := atlas.glyph_padding * 2 | ||||
|  | ||||
| 	bounds_width_scaled  := i32(bounds_width  * entry.size_scale * atlas.glyph_over_scalar + glyph_padding) | ||||
| 	bounds_height_scaled := i32(bounds_height * entry.size_scale * atlas.glyph_over_scalar + glyph_padding) | ||||
| 	bounds_size_scaled := bounds_size * entry.size_scale * atlas.glyph_over_scalar + glyph_padding_dbl | ||||
|  | ||||
| 	// Use a lookup table for faster region selection | ||||
| 	region_lookup := [4]struct { kind: Atlas_Region_Kind, region: ^Atlas_Region } { | ||||
| @@ -110,15 +110,15 @@ decide_codepoint_region :: proc(ctx : ^Context, entry : ^Entry, glyph_index : Gl | ||||
| 		{ .D, & atlas.region_d }, | ||||
| 	} | ||||
|  | ||||
| 	for region in region_lookup do if bounds_width_scaled <= region.region.width && bounds_height_scaled <= region.region.height { | ||||
| 	for region in region_lookup do if bounds_size_scaled.x <= f32(region.region.width) && bounds_size_scaled.y <= f32(region.region.height) { | ||||
| 		return region.kind, region.region, glyph_buffer.over_sample | ||||
| 	} | ||||
|  | ||||
| 	if bounds_width_scaled  <= glyph_buffer.width \ | ||||
| 	&& bounds_height_scaled <= glyph_buffer.height { | ||||
| 	if bounds_size_scaled.x <= f32(glyph_buffer.width) \ | ||||
| 	&& bounds_size_scaled.y <= f32(glyph_buffer.height) { | ||||
| 		over_sample = \ | ||||
| 			bounds_width_scaled  <= glyph_buffer.width  / 2 && | ||||
| 			bounds_height_scaled <= glyph_buffer.height / 2 ? \ | ||||
| 			bounds_size_scaled.x <= f32(glyph_buffer.width  / 2) && | ||||
| 			bounds_size_scaled.y <= f32(glyph_buffer.height / 2) ? \ | ||||
| 			  {2.0, 2.0} \ | ||||
| 			: {1.0, 1.0} | ||||
| 		return .E, nil, over_sample | ||||
|   | ||||
| @@ -53,9 +53,9 @@ Glyph_Draw_Buffer :: struct { | ||||
| 	draw_list       : Draw_List, | ||||
| } | ||||
|  | ||||
| blit_quad :: proc( draw_list : ^Draw_List, p0 : Vec2 = {0, 0}, p1 : Vec2 = {1, 1}, uv0 : Vec2 = {0, 0}, uv1 : Vec2 = {1, 1} ) | ||||
| 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) | ||||
| 	profile(#procedure) | ||||
| 	// logf("Blitting: xy0: %0.2f, %0.2f xy1: %0.2f, %0.2f uv0: %0.2f, %0.2f uv1: %0.2f, %0.2f", | ||||
| 		// p0.x, p0.y, p1.x, p1.y, uv0.x, uv0.y, uv1.x, uv1.y); | ||||
| 	v_offset := cast(u32) len(draw_list.vertices) | ||||
| @@ -89,173 +89,175 @@ blit_quad :: proc( draw_list : ^Draw_List, p0 : Vec2 = {0, 0}, p1 : Vec2 = {1, 1 | ||||
| } | ||||
|  | ||||
| // TODO(Ed): glyph caching cannot be handled in a 'font parser' abstraction. Just going to have explicit procedures to grab info neatly... | ||||
| cache_glyph_freetype :: proc(ctx: ^Context, font: Font_ID, glyph_index: Glyph, entry: ^Entry, bounds_0, bounds_1: Vec2, scale, translate: Vec2) -> b32 | ||||
| { | ||||
| 	draw_filled_path_freetype :: proc( draw_list : ^Draw_List, outside_point : Vec2, path : []Vertex, | ||||
| 		scale     := Vec2 { 1, 1 }, | ||||
| 		translate := Vec2 { 0, 0 }, | ||||
| 		debug_print_verbose : b32 = false | ||||
| 	) | ||||
| 	{ | ||||
| 		if debug_print_verbose { | ||||
| 			log("outline_path:") | ||||
| 			for point in path { | ||||
| 				vec := point.pos * scale + translate | ||||
| 				logf(" %0.2f %0.2f", vec.x, vec.y ) | ||||
| 			} | ||||
| 		} | ||||
| // cache_glyph_freetype :: proc(ctx: ^Context, font: Font_ID, glyph_index: Glyph, entry: ^Entry, bounds_0, bounds_1: Vec2, scale, translate: Vec2) -> b32 | ||||
| // { | ||||
| // 	draw_filled_path_freetype :: proc( draw_list : ^Draw_List, outside_point : Vec2, path : []Vertex, | ||||
| // 		scale     := Vec2 { 1, 1 }, | ||||
| // 		translate := Vec2 { 0, 0 }, | ||||
| // 		debug_print_verbose : b32 = false | ||||
| // 	) | ||||
| // 	{ | ||||
| // 		if debug_print_verbose { | ||||
| // 			log("outline_path:") | ||||
| // 			for point in path { | ||||
| // 				vec := point.pos * scale + translate | ||||
| // 				logf(" %0.2f %0.2f", vec.x, vec.y ) | ||||
| // 			} | ||||
| // 		} | ||||
|  | ||||
| 		v_offset := cast(u32) len(draw_list.vertices) | ||||
| 		for point in path | ||||
| 		{ | ||||
| 			transformed_point := Vertex { | ||||
| 				pos = point.pos * scale + translate, | ||||
| 				u = 0, | ||||
| 				v = 0 | ||||
| 			} | ||||
| 			append( & draw_list.vertices, transformed_point ) | ||||
| 		} | ||||
| // 		v_offset := cast(u32) len(draw_list.vertices) | ||||
| // 		for point in path | ||||
| // 		{ | ||||
| // 			transformed_point := Vertex { | ||||
| // 				pos = point.pos * scale + translate, | ||||
| // 				u = 0, | ||||
| // 				v = 0 | ||||
| // 			} | ||||
| // 			append( & draw_list.vertices, transformed_point ) | ||||
| // 		} | ||||
|  | ||||
| 		if len(path) > 2 | ||||
| 		{ | ||||
| 			indices := & draw_list.indices | ||||
| 			for index : u32 = 1; index < cast(u32) len(path) - 1; index += 1 { | ||||
| 				to_add := [3]u32 { | ||||
| 					v_offset, | ||||
| 					v_offset + index, | ||||
| 					v_offset + index + 1 | ||||
| 				} | ||||
| 				append( indices, ..to_add[:] ) | ||||
| 			} | ||||
| // 		if len(path) > 2 | ||||
| // 		{ | ||||
| // 			indices := & draw_list.indices | ||||
| // 			for index : u32 = 1; index < cast(u32) len(path) - 1; index += 1 { | ||||
| // 				to_add := [3]u32 { | ||||
| // 					v_offset, | ||||
| // 					v_offset + index, | ||||
| // 					v_offset + index + 1 | ||||
| // 				} | ||||
| // 				append( indices, ..to_add[:] ) | ||||
| // 			} | ||||
|  | ||||
| 			// Close the path by connecting the last vertex to the first two | ||||
| 			to_add := [3]u32 { | ||||
| 				v_offset, | ||||
| 				v_offset + cast(u32)(len(path) - 1), | ||||
| 				v_offset + 1 | ||||
| 			} | ||||
| 			append( indices, ..to_add[:] ) | ||||
| 		} | ||||
| 	} | ||||
| // 			// Close the path by connecting the last vertex to the first two | ||||
| // 			to_add := [3]u32 { | ||||
| // 				v_offset, | ||||
| // 				v_offset + cast(u32)(len(path) - 1), | ||||
| // 				v_offset + 1 | ||||
| // 			} | ||||
| // 			append( indices, ..to_add[:] ) | ||||
| // 		} | ||||
| // 	} | ||||
|  | ||||
| 	if glyph_index == Glyph(0) { | ||||
| 		return false | ||||
| 	} | ||||
| // 	if glyph_index == Glyph(0) { | ||||
| // 		return false | ||||
| // 	} | ||||
|  | ||||
| 	face := entry.parser_info.freetype_info | ||||
| 	error := freetype.load_glyph(face, u32(glyph_index), {.No_Bitmap, .No_Scale}) | ||||
| 	if error != .Ok { | ||||
| 		return false | ||||
| 	} | ||||
| // 	face := entry.parser_info.freetype_info | ||||
| // 	error := freetype.load_glyph(face, u32(glyph_index), {.No_Bitmap, .No_Scale}) | ||||
| // 	if error != .Ok { | ||||
| // 		return false | ||||
| // 	} | ||||
|  | ||||
| 	glyph := face.glyph | ||||
| 	if glyph.format != .Outline { | ||||
| 		return false | ||||
| 	} | ||||
| // 	glyph := face.glyph | ||||
| // 	if glyph.format != .Outline { | ||||
| // 		return false | ||||
| // 	} | ||||
|  | ||||
| 	outline := &glyph.outline | ||||
| 	if outline.n_points == 0 { | ||||
| 		return false | ||||
| 	} | ||||
| // 	outline := &glyph.outline | ||||
| // 	if outline.n_points == 0 { | ||||
| // 		return false | ||||
| // 	} | ||||
|  | ||||
| 	draw            := Draw_Call_Default | ||||
| 	draw.pass        = Frame_Buffer_Pass.Glyph | ||||
| 	draw.start_index = cast(u32) len(ctx.draw_list.indices) | ||||
| // 	draw            := Draw_Call_Default | ||||
| // 	draw.pass        = Frame_Buffer_Pass.Glyph | ||||
| // 	draw.start_index = cast(u32) len(ctx.draw_list.indices) | ||||
|  | ||||
| 	contours := slice.from_ptr(cast( [^]i16)             outline.contours, int(outline.n_contours)) | ||||
| 	points   := slice.from_ptr(cast( [^]freetype.Vector) outline.points,   int(outline.n_points)) | ||||
| 	tags     := slice.from_ptr(cast( [^]u8)              outline.tags,     int(outline.n_points)) | ||||
| // 	contours := slice.from_ptr(cast( [^]i16)             outline.contours, int(outline.n_contours)) | ||||
| // 	points   := slice.from_ptr(cast( [^]freetype.Vector) outline.points,   int(outline.n_points)) | ||||
| // 	tags     := slice.from_ptr(cast( [^]u8)              outline.tags,     int(outline.n_points)) | ||||
|  | ||||
| 	path := &ctx.temp_path | ||||
| 	clear(path) | ||||
| // 	path := &ctx.temp_path | ||||
| // 	clear(path) | ||||
|  | ||||
| 	outside := Vec2{ bounds_0.x - 21, bounds_0.y - 33 } | ||||
| // 	outside := Vec2{ bounds_0.x - 21, bounds_0.y - 33 } | ||||
|  | ||||
| 	start_index: int = 0 | ||||
| 	for contour_index in 0 ..< int(outline.n_contours) | ||||
| 	{ | ||||
| 		end_index   := int(contours[contour_index]) + 1 | ||||
| 		prev_point  : Vec2 | ||||
| 		first_point : Vec2 | ||||
| // 	start_index: int = 0 | ||||
| // 	for contour_index in 0 ..< int(outline.n_contours) | ||||
| // 	{ | ||||
| // 		end_index   := int(contours[contour_index]) + 1 | ||||
| // 		prev_point  : Vec2 | ||||
| // 		first_point : Vec2 | ||||
|  | ||||
| 		for idx := start_index; idx < end_index; idx += 1 | ||||
| 		{ | ||||
| 			current_pos := Vec2 { f32( points[idx].x ), f32( points[idx].y ) } | ||||
| 			if ( tags[idx] & 1 ) == 0 | ||||
| 			{ | ||||
| 				// If current point is off-curve | ||||
| 				if (idx == start_index || (tags[ idx - 1 ] & 1) != 0) | ||||
| 				{ | ||||
| 					// current is the first or following an on-curve point | ||||
| 					prev_point = current_pos | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					// current and previous are off-curve, calculate midpoint | ||||
| 					midpoint := (prev_point + current_pos) * 0.5 | ||||
| 					append( path, Vertex { pos = midpoint } )  // Add midpoint as on-curve point | ||||
| 					if idx < end_index - 1 | ||||
| 					{ | ||||
| 						// perform interp from prev_point to current_pos via midpoint | ||||
| 						step := 1.0 / entry.curve_quality | ||||
| 						for alpha : f32 = 0.0; alpha <= 1.0; alpha += step | ||||
| 						{ | ||||
| 							bezier_point := eval_point_on_bezier3( prev_point, midpoint, current_pos, alpha ) | ||||
| 							append( path, Vertex{ pos = bezier_point } ) | ||||
| 						} | ||||
| 					} | ||||
| // 		for idx := start_index; idx < end_index; idx += 1 | ||||
| // 		{ | ||||
| // 			current_pos := Vec2 { f32( points[idx].x ), f32( points[idx].y ) } | ||||
| // 			if ( tags[idx] & 1 ) == 0 | ||||
| // 			{ | ||||
| // 				// If current point is off-curve | ||||
| // 				if (idx == start_index || (tags[ idx - 1 ] & 1) != 0) | ||||
| // 				{ | ||||
| // 					// current is the first or following an on-curve point | ||||
| // 					prev_point = current_pos | ||||
| // 				} | ||||
| // 				else | ||||
| // 				{ | ||||
| // 					// current and previous are off-curve, calculate midpoint | ||||
| // 					midpoint := (prev_point + current_pos) * 0.5 | ||||
| // 					append( path, Vertex { pos = midpoint } )  // Add midpoint as on-curve point | ||||
| // 					if idx < end_index - 1 | ||||
| // 					{ | ||||
| // 						// perform interp from prev_point to current_pos via midpoint | ||||
| // 						step := 1.0 / entry.curve_quality | ||||
| // 						for alpha : f32 = 0.0; alpha <= 1.0; alpha += step | ||||
| // 						{ | ||||
| // 							bezier_point := eval_point_on_bezier3( prev_point, midpoint, current_pos, alpha ) | ||||
| // 							append( path, Vertex{ pos = bezier_point } ) | ||||
| // 						} | ||||
| // 					} | ||||
|  | ||||
| 					prev_point = current_pos | ||||
| 				} | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				if idx == start_index { | ||||
| 					first_point = current_pos | ||||
| 				} | ||||
| 				if prev_point != (Vec2{}) { | ||||
| 					// there was an off-curve point before this | ||||
| 					append(path, Vertex{ pos = prev_point}) // Ensure previous off-curve is handled | ||||
| 				} | ||||
| 				append(path, Vertex{ pos = current_pos}) | ||||
| 				prev_point = {} | ||||
| 			} | ||||
| 		} | ||||
| // 					prev_point = current_pos | ||||
| // 				} | ||||
| // 			} | ||||
| // 			else | ||||
| // 			{ | ||||
| // 				if idx == start_index { | ||||
| // 					first_point = current_pos | ||||
| // 				} | ||||
| // 				if prev_point != (Vec2{}) { | ||||
| // 					// there was an off-curve point before this | ||||
| // 					append(path, Vertex{ pos = prev_point}) // Ensure previous off-curve is handled | ||||
| // 				} | ||||
| // 				append(path, Vertex{ pos = current_pos}) | ||||
| // 				prev_point = {} | ||||
| // 			} | ||||
| // 		} | ||||
|  | ||||
| 		// ensure the contour is closed | ||||
| 		if path[0].pos != path[ len(path) - 1 ].pos { | ||||
| 			append(path, Vertex{pos = path[0].pos}) | ||||
| 		} | ||||
| 		draw_filled_path(&ctx.draw_list, bounds_0, path[:], scale, translate, ctx.debug_print_verbose) | ||||
| 		clear(path) | ||||
| 		start_index = end_index | ||||
| 	} | ||||
| // 		// ensure the contour is closed | ||||
| // 		if path[0].pos != path[ len(path) - 1 ].pos { | ||||
| // 			append(path, Vertex{pos = path[0].pos}) | ||||
| // 		} | ||||
| // 		draw_filled_path(&ctx.draw_list, bounds_0, path[:], scale, translate) | ||||
| // 		// draw_filled_path(&ctx.draw_list, bounds_0, path[:], scale, translate, ctx.debug_print_verbose) | ||||
| // 		clear(path) | ||||
| // 		start_index = end_index | ||||
| // 	} | ||||
|  | ||||
| 	if len(path) > 0 { | ||||
| 		draw_filled_path(&ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose) | ||||
| 	} | ||||
| // 	if len(path) > 0 { | ||||
| // 		// draw_filled_path(&ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose) | ||||
| // 		draw_filled_path(&ctx.draw_list, outside, path[:], scale, translate) | ||||
| // 	} | ||||
|  | ||||
| 	draw.end_index = cast(u32) len(ctx.draw_list.indices) | ||||
| 	if draw.end_index > draw.start_index { | ||||
| 		append( & ctx.draw_list.calls, draw) | ||||
| 	} | ||||
| // 	draw.end_index = cast(u32) len(ctx.draw_list.indices) | ||||
| // 	if draw.end_index > draw.start_index { | ||||
| // 		append( & ctx.draw_list.calls, draw) | ||||
| // 	} | ||||
|  | ||||
| 	return true | ||||
| } | ||||
| // 	return true | ||||
| // } | ||||
|  | ||||
| // TODO(Ed): Is it better to cache the glyph vertices for when it must be re-drawn (directly or two atlas)? | ||||
| cache_glyph :: proc(ctx : ^Context, font : Font_ID, glyph_index : Glyph, entry : ^Entry, bounds_0, bounds_1 : Vec2, scale, translate : Vec2) -> b32 | ||||
| { | ||||
| 	// profile(#procedure) | ||||
| 	profile(#procedure) | ||||
| 	if glyph_index == Glyph(0) { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	// Glyph shape handling are not abstractable between freetype and stb_truetype | ||||
| 	if entry.parser_info.kind == .Freetype { | ||||
| 		result := cache_glyph_freetype( ctx, font, glyph_index, entry, bounds_0, bounds_1, scale, translate ) | ||||
| 		return result | ||||
| 	} | ||||
| 	// if entry.parser_info.kind == .Freetype { | ||||
| 	// 	result := cache_glyph_freetype( ctx, font, glyph_index, entry, bounds_0, bounds_1, scale, translate ) | ||||
| 	// 	return result | ||||
| 	// } | ||||
|  | ||||
| 	shape, error := parser_get_glyph_shape(&entry.parser_info, glyph_index) | ||||
| 	assert(error == .None) | ||||
| @@ -277,7 +279,8 @@ cache_glyph :: proc(ctx : ^Context, font : Font_ID, glyph_index : Glyph, entry : | ||||
| 	{ | ||||
| 		case .Move: | ||||
| 			if len(path) > 0 { | ||||
| 				draw_filled_path(&ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose) | ||||
| 				// draw_filled_path(&ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose) | ||||
| 				draw_filled_path(&ctx.draw_list, outside, path[:], scale, translate) | ||||
| 				clear(path) | ||||
| 			} | ||||
| 			fallthrough | ||||
| @@ -310,7 +313,8 @@ cache_glyph :: proc(ctx : ^Context, font : Font_ID, glyph_index : Glyph, entry : | ||||
| 	} | ||||
|  | ||||
| 	if len(path) > 0 { | ||||
| 		draw_filled_path(&ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose) | ||||
| 		// draw_filled_path(&ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose) | ||||
| 		draw_filled_path(&ctx.draw_list, outside, path[:], scale, translate) | ||||
| 	} | ||||
|  | ||||
| 	draw.end_index = u32(len(ctx.draw_list.indices)) | ||||
| @@ -337,7 +341,7 @@ cache_glyph_to_atlas :: proc( ctx : ^Context, | ||||
| 	region      : ^Atlas_Region, | ||||
| 	over_sample : Vec2 ) | ||||
| { | ||||
| 	// profile(#procedure) | ||||
| 	profile(#procedure) | ||||
|  | ||||
| 	// Get hb_font text metrics. These are unscaled! | ||||
| 	bounds_0, bounds_1 := parser_get_glyph_box( & entry.parser_info, glyph_index ) | ||||
| @@ -354,8 +358,8 @@ cache_glyph_to_atlas :: proc( ctx : ^Context, | ||||
| 	{ | ||||
| 		if region.next_idx < region.state.capacity | ||||
| 		{ | ||||
| 			evicted         := lru_put( & region.state, lru_code, i32(region.next_idx) ) | ||||
| 			atlas_index      = i32(region.next_idx) | ||||
| 			evicted         := lru_put( & region.state, lru_code, region.next_idx ) | ||||
| 			atlas_index      = region.next_idx | ||||
| 			region.next_idx += 1 | ||||
| 			assert( evicted == lru_code ) | ||||
| 		} | ||||
| @@ -460,20 +464,19 @@ check_glyph_in_atlas :: #force_inline proc( ctx : ^Context, font : Font_ID, entr | ||||
| 	over_sample : Vec2 | ||||
| ) -> b32 | ||||
| { | ||||
| 	// profile(#procedure) | ||||
| 	profile(#procedure) | ||||
| 	assert( glyph_index != -1 ) | ||||
|  | ||||
| 	// 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 | ||||
| 	// TODO(Ed): Why 1024? | ||||
| 	if region_kind == .E || region_kind == .None                       do return false | ||||
| 	if ctx.temp_codepoint_seen_num > i32(cap(ctx.temp_codepoint_seen)) do return false | ||||
|  | ||||
| 	if atlas_index == - 1 | ||||
| 	{ | ||||
| 		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_codepoint := lru_get_next_evicted( & region.state ) | ||||
| 			seen, success := ctx.temp_codepoint_seen[next_evict_codepoint] | ||||
| 			seen, success  := ctx.temp_codepoint_seen[next_evict_codepoint] | ||||
| 			assert(success != false) | ||||
|  | ||||
| 			if (seen) { | ||||
| @@ -503,7 +506,7 @@ directly_draw_massive_glyph :: proc( ctx : ^Context, | ||||
| 	bounds_size                  : Vec2, | ||||
| 	over_sample, position, scale : Vec2 ) | ||||
| { | ||||
| 	// profile(#procedure) | ||||
| 	profile(#procedure) | ||||
| 	flush_glyph_buffer_to_atlas( ctx ) | ||||
|  | ||||
| 	glyph_padding     := f32(ctx.atlas.glyph_padding) | ||||
| @@ -567,22 +570,23 @@ directly_draw_massive_glyph :: proc( ctx : ^Context, | ||||
| // ve_fontcache_draw_filled_path | ||||
| draw_filled_path :: proc( draw_list : ^Draw_List, outside_point : Vec2, path : []Vertex, | ||||
| 	scale     := Vec2 { 1, 1 }, | ||||
| 	translate := Vec2 { 0, 0 }, | ||||
| 	debug_print_verbose : b32 = false | ||||
| ) | ||||
| 	translate := Vec2 { 0, 0 } | ||||
| 	// debug_print_verbose : b32 = false | ||||
| ) #no_bounds_check | ||||
| { | ||||
| 	if debug_print_verbose | ||||
| 	{ | ||||
| 		log("outline_path:") | ||||
| 		for point in path { | ||||
| 			vec := point.pos * scale + translate | ||||
| 			logf(" %0.2f %0.2f", vec.x, vec.y ) | ||||
| 		} | ||||
| 	} | ||||
| 	profile(#procedure) | ||||
| 	// if debug_print_verbose | ||||
| 	// { | ||||
| 	// 	log("outline_path:") | ||||
| 	// 	for point in path { | ||||
| 	// 		vec := point.pos * scale + translate | ||||
| 	// 		logf(" %0.2f %0.2f", vec.x, vec.y ) | ||||
| 	// 	} | ||||
| 	// } | ||||
|  | ||||
| 	v_offset := cast(u32) len(draw_list.vertices) | ||||
| 	for point in path { | ||||
| 		point := point | ||||
| 		point    := point | ||||
| 		point.pos = point.pos * scale + translate | ||||
| 		append( & draw_list.vertices, point ) | ||||
| 	} | ||||
| @@ -613,14 +617,16 @@ draw_text_batch :: proc(ctx: ^Context, entry: ^Entry, shaped: ^Shaped_Text, | ||||
| 	position, scale                : Vec2, | ||||
| 	snap_width, snap_height        : f32 ) | ||||
| { | ||||
| 	profile(#procedure) | ||||
| 	flush_glyph_buffer_to_atlas(ctx) | ||||
|  | ||||
| 	atlas         := & ctx.atlas | ||||
| 	atlas_size    := Vec2{ f32(atlas.width), f32(atlas.height) } | ||||
| 	glyph_padding := f32(atlas.glyph_padding) | ||||
| 	glyph_padding := atlas.glyph_padding | ||||
|  | ||||
| 	for index := batch_start_idx; index < batch_end_idx; index += 1 | ||||
| 	{ | ||||
| 		profile("glyph") | ||||
| 		glyph_index := shaped.glyphs[index] | ||||
|  | ||||
| 		if glyph_index == 0 || parser_is_glyph_empty( & entry.parser_info, glyph_index) do continue | ||||
| @@ -645,39 +651,40 @@ draw_text_batch :: proc(ctx: ^Context, entry: ^Entry, shaped: ^Shaped_Text, | ||||
| 		} | ||||
| 		else if atlas_index != -1 | ||||
| 		{ | ||||
| 			profile("derive manual") | ||||
| 			call             := Draw_Call_Default | ||||
| 			call.pass         = .Target | ||||
| 			call.colour       = ctx.colour | ||||
| 			call.start_index  = u32(len(ctx.draw_list.indices)) | ||||
|  | ||||
| 			// Draw cached glyph | ||||
| 			slot_position, _ := atlas_bbox( atlas, region_kind, atlas_index ) | ||||
| 			glyph_scale      := bounds_size * entry.size_scale + glyph_padding | ||||
| 			bounds_0_scaled  := ceil(vbounds_0 * entry.size_scale - 0.5 ) | ||||
| 			dst              := glyph_translate + bounds_0_scaled * scale | ||||
| 			dst_scale        := glyph_scale * scale | ||||
|  | ||||
| 			textspace_x_form( & slot_position, & glyph_scale, atlas_size ) | ||||
|  | ||||
| 			call             := Draw_Call_Default | ||||
| 			call.pass         = .Target | ||||
| 			call.colour       = ctx.colour | ||||
| 			call.start_index  = u32(len(ctx.draw_list.indices)) | ||||
|  | ||||
| 			blit_quad(&ctx.draw_list, | ||||
| 				dst,           dst           + dst_scale, | ||||
| 				slot_position, slot_position + glyph_scale ) | ||||
|  | ||||
| 			call.end_index = u32(len(ctx.draw_list.indices)) | ||||
|  | ||||
| 			append(&ctx.draw_list.calls, call) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 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, | ||||
| draw_text_shape :: #force_inline proc( ctx : ^Context, | ||||
| 	font                    : Font_ID, | ||||
| 	entry                   : ^Entry, | ||||
| 	shaped                  : ^Shaped_Text, | ||||
| 	position,   scale       : Vec2, | ||||
| 	snap_width, snap_height : f32 | ||||
| ) -> (cursor_pos : Vec2) | ||||
| ) -> (cursor_pos : Vec2) #no_bounds_check | ||||
| { | ||||
| 	// profile(#procedure) | ||||
| 	profile(#procedure) | ||||
| 	batch_start_idx : i32 = 0 | ||||
| 	for index : i32 = 0; index < cast(i32) len(shaped.glyphs); index += 1 | ||||
| 	{ | ||||
| @@ -710,9 +717,51 @@ draw_text_shape :: proc( ctx : ^Context, | ||||
| 	return | ||||
| } | ||||
|  | ||||
| flush_glyph_buffer_to_atlas :: proc( ctx : ^Context ) | ||||
| // Helper for draw_text_latin_mono | ||||
| draw_text_mono_latin_batch :: #force_inline proc( ctx : ^Context, | ||||
| 	font                    : Font_ID, | ||||
| 	entry                   : ^Entry, | ||||
| 	shaped                  : ^Shaped_Text, | ||||
| 	position,   scale       : Vec2, | ||||
| 	snap_width, snap_height : f32 | ||||
| ) -> (cursor_pos : Vec2) #no_bounds_check | ||||
| { | ||||
| 	// profile(#procedure) | ||||
| 	profile(#procedure) | ||||
| 	batch_start_idx : i32 = 0 | ||||
| 	for index : i32 = 0; index < cast(i32) len(shaped.glyphs); index += 1 | ||||
| 	{ | ||||
| 		glyph_index := shaped.glyphs[ index ] | ||||
| 		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 check_glyph_in_atlas( ctx, font, entry, glyph_index, lru_code, atlas_index, region_kind, region, over_sample ) do continue | ||||
|  | ||||
| 		// We can no longer directly append the shape as it has missing glyphs in the atlas | ||||
|  | ||||
| 		// 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, 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, cast(i32) len(shaped.glyphs), position, scale, snap_width , snap_height ) | ||||
| 	reset_batch_codepoint_state( ctx ) | ||||
|  | ||||
| 	cursor_pos = position + shaped.end_cursor_pos * scale | ||||
| 	return | ||||
| } | ||||
|  | ||||
| flush_glyph_buffer_to_atlas :: #force_inline proc( ctx : ^Context ) | ||||
| { | ||||
| 	profile(#procedure) | ||||
| 	// Flush Draw_Calls to draw list | ||||
| 	merge_draw_list( & ctx.draw_list, & ctx.glyph_buffer.clear_draw_list ) | ||||
| 	merge_draw_list( & ctx.draw_list, & ctx.glyph_buffer.draw_list) | ||||
| @@ -733,9 +782,9 @@ flush_glyph_buffer_to_atlas :: proc( ctx : ^Context ) | ||||
| } | ||||
|  | ||||
| // ve_fontcache_merge_Draw_List | ||||
| merge_draw_list :: proc( dst, src : ^Draw_List ) | ||||
| merge_draw_list :: #force_inline proc ( #no_alias dst, src : ^Draw_List ) | ||||
| { | ||||
| 	// profile(#procedure) | ||||
| 	profile(#procedure) | ||||
| 	error : Allocator_Error | ||||
|  | ||||
| 	v_offset := cast(u32) len( dst.vertices ) | ||||
| @@ -744,13 +793,13 @@ merge_draw_list :: proc( dst, src : ^Draw_List ) | ||||
| 	assert( error == .None ) | ||||
|  | ||||
| 	i_offset := cast(u32) len(dst.indices) | ||||
| 	for index : int = 0; index < len(src.indices); index += 1 { | ||||
| 	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 : int = 0; index < len(src.calls); index += 1 { | ||||
| 	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 | ||||
| @@ -759,9 +808,9 @@ merge_draw_list :: proc( dst, src : ^Draw_List ) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| optimize_draw_list :: proc(draw_list: ^Draw_List, call_offset: int) | ||||
| optimize_draw_list :: proc (draw_list: ^Draw_List, call_offset: int) | ||||
| { | ||||
| 	// profile(#procedure) | ||||
| 	profile(#procedure) | ||||
| 	assert(draw_list != nil) | ||||
|  | ||||
| 	can_merge_draw_calls :: #force_inline proc "contextless" ( a, b : ^Draw_Call ) -> bool { | ||||
|   | ||||
| @@ -105,4 +105,23 @@ vec2_64 :: proc { | ||||
| 	vec2_64_from_vec2, | ||||
| } | ||||
|  | ||||
| import "../../grime" | ||||
|  | ||||
| DISABLE_PROFILING :: false | ||||
|  | ||||
| @(deferred_none = profile_end, disabled = DISABLE_PROFILING) | ||||
| profile :: #force_inline proc "contextless" ( name : string, loc := #caller_location ) { | ||||
| 	grime.profile_begin(name, loc) | ||||
| } | ||||
|  | ||||
| @(disabled = DISABLE_PROFILING) | ||||
| profile_begin :: #force_inline proc "contextless" ( name : string, loc := #caller_location ) { | ||||
| 	grime.profile_begin(name, loc) | ||||
| } | ||||
|  | ||||
| @(disabled = DISABLE_PROFILING) | ||||
| profile_end :: #force_inline proc "contextless" () { | ||||
| 	grime.profile_end() | ||||
| } | ||||
|  | ||||
| //#endregion("Proc overload mappings") | ||||
|   | ||||
| @@ -21,20 +21,20 @@ vec2i_from_vec2   :: #force_inline proc "contextless" ( v2     : Vec2  ) -> Vec2 | ||||
|  | ||||
| // This buffer is used below excluisvely to prevent any allocator recusion when verbose logging from allocators. | ||||
| // This means a single line is limited to 4k buffer | ||||
| // Logger_Allocator_Buffer : [4 * Kilobyte]u8 | ||||
| Logger_Allocator_Buffer : [4 * Kilobyte]u8 | ||||
|  | ||||
| log :: proc( msg : string, level := core_log.Level.Info, loc := #caller_location ) { | ||||
| 	// temp_arena : Arena; arena_init(& temp_arena, Logger_Allocator_Buffer[:]) | ||||
| 	// context.allocator      = arena_allocator(& temp_arena) | ||||
| 	// context.temp_allocator = arena_allocator(& temp_arena) | ||||
| 	temp_arena : Arena; arena_init(& temp_arena, Logger_Allocator_Buffer[:]) | ||||
| 	context.allocator      = arena_allocator(& temp_arena) | ||||
| 	context.temp_allocator = arena_allocator(& temp_arena) | ||||
|  | ||||
| 	core_log.log( level, msg, location = loc ) | ||||
| } | ||||
|  | ||||
| logf :: proc( fmt : string, args : ..any,  level := core_log.Level.Info, loc := #caller_location  ) { | ||||
| 	// temp_arena : Arena; arena_init(& temp_arena, Logger_Allocator_Buffer[:]) | ||||
| 	// context.allocator      = arena_allocator(& temp_arena) | ||||
| 	// context.temp_allocator = arena_allocator(& temp_arena) | ||||
| 	temp_arena : Arena; arena_init(& temp_arena, Logger_Allocator_Buffer[:]) | ||||
| 	context.allocator      = arena_allocator(& temp_arena) | ||||
| 	context.temp_allocator = arena_allocator(& temp_arena) | ||||
|  | ||||
| 	core_log.logf( level, fmt, ..args, location = loc ) | ||||
| } | ||||
| @@ -73,7 +73,7 @@ reset_batch_codepoint_state :: #force_inline proc( ctx : ^Context ) { | ||||
|  | ||||
| USE_F64_PRECISION_ON_X_FORM_OPS :: false | ||||
|  | ||||
| screenspace_x_form :: #force_inline proc "contextless" ( position, scale : ^Vec2, size : Vec2 ) | ||||
| screenspace_x_form :: #force_inline proc "contextless" ( #no_alias position, scale : ^Vec2, size : Vec2 ) | ||||
| { | ||||
| 	when USE_F64_PRECISION_ON_X_FORM_OPS | ||||
| 	{ | ||||
| @@ -101,7 +101,7 @@ screenspace_x_form :: #force_inline proc "contextless" ( position, scale : ^Vec2 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| textspace_x_form :: #force_inline proc "contextless" ( position, scale : ^Vec2, size : Vec2 ) | ||||
| textspace_x_form :: #force_inline proc "contextless" ( #no_alias position, scale : ^Vec2, size : Vec2 ) | ||||
| { | ||||
| 	when USE_F64_PRECISION_ON_X_FORM_OPS | ||||
| 	{ | ||||
| @@ -123,7 +123,7 @@ textspace_x_form :: #force_inline proc "contextless" ( position, scale : ^Vec2, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| USE_MANUAL_SIMD_FOR_BEZIER_OPS :: false | ||||
| USE_MANUAL_SIMD_FOR_BEZIER_OPS :: true | ||||
|  | ||||
| when ! USE_MANUAL_SIMD_FOR_BEZIER_OPS | ||||
| { | ||||
|   | ||||
| @@ -113,6 +113,7 @@ parser_unload_font :: proc( font : ^Parser_Font_Info ) | ||||
|  | ||||
| parser_find_glyph_index :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, codepoint : rune ) -> (glyph_index : Glyph) | ||||
| { | ||||
| 	profile(#procedure) | ||||
| 	switch font.kind | ||||
| 	{ | ||||
| 		case .Freetype: | ||||
| @@ -131,7 +132,7 @@ parser_find_glyph_index :: #force_inline proc "contextless" ( font : ^Parser_Fon | ||||
| 	return Glyph(-1) | ||||
| } | ||||
|  | ||||
| parser_free_shape :: proc( font : ^Parser_Font_Info, shape : Parser_Glyph_Shape ) | ||||
| parser_free_shape :: #force_inline proc( font : ^Parser_Font_Info, shape : Parser_Glyph_Shape ) | ||||
| { | ||||
| 	switch font.kind | ||||
| 	{ | ||||
| @@ -219,10 +220,12 @@ parser_get_font_vertical_metrics :: #force_inline proc "contextless" ( font : ^P | ||||
| 	return | ||||
| } | ||||
|  | ||||
| parser_get_glyph_box :: #force_inline proc ( font : ^Parser_Font_Info, glyph_index : Glyph ) -> (bounds_0, bounds_1 : Vec2i) | ||||
| parser_get_glyph_box :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, glyph_index : Glyph ) -> (bounds_0, bounds_1 : Vec2i) | ||||
| { | ||||
| 	profile(#procedure) | ||||
| 	switch font.kind | ||||
| 	{ | ||||
| 		 | ||||
| 		case .Freetype: | ||||
| 			freetype.load_glyph( font.freetype_info, c.uint(glyph_index), { .No_Bitmap, .No_Hinting, .No_Scale } ) | ||||
|  | ||||
| @@ -234,15 +237,15 @@ parser_get_glyph_box :: #force_inline proc ( font : ^Parser_Font_Info, glyph_ind | ||||
| 		case .STB_TrueType: | ||||
| 			x0, y0, x1, y1 : i32 | ||||
| 			success := cast(bool) stbtt.GetGlyphBox( & font.stbtt_info, i32(glyph_index), & x0, & y0, & x1, & y1 ) | ||||
| 			assert( success ) | ||||
| 			// assert( success ) | ||||
|  | ||||
| 			bounds_0 = { i32(x0), i32(y0) } | ||||
| 			bounds_1 = { i32(x1), i32(y1) } | ||||
| 			bounds_0 = { x0, y0 } | ||||
| 			bounds_1 = { x1, y1 } | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| parser_get_glyph_shape :: proc( font : ^Parser_Font_Info, glyph_index : Glyph ) -> (shape : Parser_Glyph_Shape, error : Allocator_Error) | ||||
| parser_get_glyph_shape :: #force_inline proc ( font : ^Parser_Font_Info, glyph_index : Glyph ) -> (shape : Parser_Glyph_Shape, error : Allocator_Error) | ||||
| { | ||||
| 	switch font.kind | ||||
| 	{ | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| package vefontcache | ||||
|  | ||||
| Shaped_Text :: struct { | ||||
| 	glyphs         : [dynamic]Glyph, | ||||
| 	positions      : [dynamic]Vec2, | ||||
| 	end_cursor_pos : Vec2, | ||||
| 	size           : Vec2, | ||||
| 	glyphs             : [dynamic]Glyph, | ||||
| 	positions          : [dynamic]Vec2, | ||||
| 	end_cursor_pos     : Vec2, | ||||
| 	size               : Vec2, | ||||
| } | ||||
|  | ||||
| Shaped_Text_Cache :: struct { | ||||
| @@ -19,9 +19,11 @@ shape_lru_hash :: #force_inline proc "contextless" ( hash : ^u64, bytes : []byte | ||||
| 	} | ||||
| } | ||||
|  | ||||
| shape_text_cached :: proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : ^Entry ) -> ^Shaped_Text | ||||
| ShapedTextUncachedProc :: #type proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : ^Entry, output : ^Shaped_Text ) | ||||
|  | ||||
| shape_text_cached :: proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : ^Entry, shape_text_uncached : ShapedTextUncachedProc ) -> ^Shaped_Text #no_bounds_check | ||||
| { | ||||
| 	// profile(#procedure) | ||||
| 	profile(#procedure) | ||||
| 	font        := font | ||||
| 	font_bytes  := slice_ptr( transmute(^byte) & font,  size_of(Font_ID) ) | ||||
| 	text_bytes  := transmute( []byte) text_utf8 | ||||
| @@ -59,9 +61,9 @@ shape_text_cached :: proc( ctx : ^Context, font : Font_ID, text_utf8 : string, e | ||||
| 	return & shape_cache.storage[ shape_cache_idx ] | ||||
| } | ||||
|  | ||||
| shape_text_uncached :: proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : ^Entry, output : ^Shaped_Text ) | ||||
| shape_text_uncached_advanced :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : ^Entry, output : ^Shaped_Text ) | ||||
| { | ||||
| 	// profile(#procedure) | ||||
| 	profile(#procedure) | ||||
| 	assert( ctx != nil ) | ||||
| 	assert( font >= 0 && int(font) < len(ctx.entries) ) | ||||
|  | ||||
| @@ -74,58 +76,68 @@ shape_text_uncached :: proc( ctx : ^Context, font : Font_ID, text_utf8 : string, | ||||
| 	line_gap    := f32(line_gap_i32) | ||||
| 	line_height := (ascent - descent + line_gap) * entry.size_scale | ||||
|  | ||||
| 	if ctx.use_advanced_shaper | ||||
| 	{ | ||||
| 		shaper_shape_from_text( & ctx.shaper_ctx, & entry.shaper_info, output, text_utf8, ascent_i32, descent_i32, line_gap_i32, entry.size, entry.size_scale ) | ||||
| 		return | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		// Note(Original Author): | ||||
| 		// We use our own fallback dumbass text shaping. | ||||
| 		// WARNING: PLEASE USE HARFBUZZ. GOOD TEXT SHAPING IS IMPORTANT FOR INTERNATIONALISATION. | ||||
| 	shaper_shape_from_text( & ctx.shaper_ctx, & entry.shaper_info, output, text_utf8, ascent_i32, descent_i32, line_gap_i32, entry.size, entry.size_scale ) | ||||
| } | ||||
|  | ||||
| 		line_count     : int = 1 | ||||
| 		max_line_width : f32 = 0 | ||||
| 		position       : Vec2 | ||||
| shape_text_uncached_latin :: proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : ^Entry, output : ^Shaped_Text ) | ||||
| {	 | ||||
| 	profile(#procedure) | ||||
| 	assert( ctx != nil ) | ||||
| 	assert( font >= 0 && int(font) < len(ctx.entries) ) | ||||
|  | ||||
| 		prev_codepoint : rune | ||||
| 		for codepoint in text_utf8 | ||||
| 	clear( & output.glyphs ) | ||||
| 	clear( & output.positions ) | ||||
|  | ||||
| 	ascent_i32, descent_i32, line_gap_i32 := parser_get_font_vertical_metrics( & entry.parser_info ) | ||||
| 	ascent      := f32(ascent_i32) | ||||
| 	descent     := f32(descent_i32) | ||||
| 	line_gap    := f32(line_gap_i32) | ||||
| 	line_height := (ascent - descent + line_gap) * entry.size_scale | ||||
|  | ||||
| 	line_count     : int = 1 | ||||
| 	max_line_width : f32 = 0 | ||||
| 	position       : Vec2 | ||||
|  | ||||
| 	prev_codepoint : rune | ||||
| 	for codepoint, index in text_utf8 | ||||
| 	{ | ||||
| 		if prev_codepoint > 0 { | ||||
| 			kern       := parser_get_codepoint_kern_advance( & entry.parser_info, prev_codepoint, codepoint ) | ||||
| 			position.x += f32(kern) * entry.size_scale | ||||
| 		} | ||||
| 		if codepoint == '\n' | ||||
| 		{ | ||||
| 			if prev_codepoint > 0 { | ||||
| 				kern       := parser_get_codepoint_kern_advance( & entry.parser_info, prev_codepoint, codepoint ) | ||||
| 				position.x += f32(kern) * entry.size_scale | ||||
| 			} | ||||
| 			if codepoint == '\n' | ||||
| 			{ | ||||
| 				line_count    += 1 | ||||
| 				max_line_width = max(max_line_width, position.x) | ||||
| 				position.x     = 0.0 | ||||
| 				position.y    -= line_height | ||||
| 				position.y     = position.y | ||||
| 				prev_codepoint = rune(0) | ||||
| 				continue | ||||
| 			} | ||||
| 			if abs( entry.size ) <= ctx.shaper_ctx.adv_snap_small_font_threshold { | ||||
| 				position.x = ceil(position.x) | ||||
| 			} | ||||
|  | ||||
| 			append( & output.glyphs, parser_find_glyph_index( & entry.parser_info, codepoint )) | ||||
| 			advance, _ := parser_get_codepoint_horizontal_metrics( & entry.parser_info, codepoint ) | ||||
| 			line_count    += 1 | ||||
| 			max_line_width = max(max_line_width, position.x) | ||||
| 			position.x     = 0.0 | ||||
| 			position.y    -= line_height | ||||
| 			position.y     = position.y | ||||
| 			prev_codepoint = rune(0) | ||||
| 			continue | ||||
| 		} | ||||
| 		if abs( entry.size ) <= ctx.shaper_ctx.adv_snap_small_font_threshold { | ||||
| 			position.x = ceil(position.x) | ||||
| 		} | ||||
|  | ||||
| 		glyph_index := parser_find_glyph_index( & entry.parser_info, codepoint ) | ||||
| 		is_empty    := parser_is_glyph_empty( & entry.parser_info,glyph_index ) | ||||
| 		if ! is_empty | ||||
| 		{ | ||||
| 			append( & output.glyphs, glyph_index) | ||||
| 			append( & output.positions, Vec2 { | ||||
| 				floor(position.x), | ||||
| 				floor(position.y) | ||||
| 			}) | ||||
|  | ||||
| 			position.x += f32(advance) * entry.size_scale | ||||
| 			prev_codepoint = codepoint | ||||
| 		} | ||||
|  | ||||
| 		output.end_cursor_pos = position | ||||
| 		max_line_width        = max(max_line_width, position.x) | ||||
|  | ||||
| 		output.size.x = max_line_width | ||||
| 		output.size.y = f32(line_count) * line_height | ||||
| 		advance, _ := parser_get_codepoint_horizontal_metrics( & entry.parser_info, codepoint ) | ||||
| 		position.x += f32(advance) * entry.size_scale | ||||
| 		prev_codepoint = codepoint | ||||
| 	} | ||||
|  | ||||
| 	output.end_cursor_pos = position | ||||
| 	max_line_width        = max(max_line_width, position.x) | ||||
|  | ||||
| 	output.size.x = max_line_width | ||||
| 	output.size.y = f32(line_count) * line_height | ||||
| } | ||||
|   | ||||
| @@ -37,7 +37,7 @@ shaper_shutdown :: proc( ctx : ^Shaper_Context ) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| shaper_load_font :: proc( ctx : ^Shaper_Context, label : string, data : []byte, user_data : rawptr ) -> (info : Shaper_Info) | ||||
| shaper_load_font :: proc( ctx : ^Shaper_Context, label : string, data : []byte, user_data : rawptr = nil ) -> (info : Shaper_Info) | ||||
| { | ||||
| 	using info | ||||
| 	blob = harfbuzz.blob_create( raw_data(data), cast(c.uint) len(data), harfbuzz.Memory_Mode.READONLY, user_data, nil ) | ||||
| @@ -54,10 +54,10 @@ shaper_unload_font :: proc( ctx : ^Shaper_Info ) | ||||
| 	if blob != nil do harfbuzz.blob_destroy( blob ) | ||||
| } | ||||
|  | ||||
| shaper_shape_from_text :: proc( ctx : ^Shaper_Context, info : ^Shaper_Info, output :^Shaped_Text, text_utf8 : string, | ||||
| shaper_shape_from_text :: #force_inline proc( ctx : ^Shaper_Context, info : ^Shaper_Info, output :^Shaped_Text, text_utf8 : string, | ||||
| 	ascent, descent, line_gap : i32, size, size_scale : f32 ) | ||||
| { | ||||
| 	// profile(#procedure) | ||||
| 	profile(#procedure) | ||||
| 	current_script := harfbuzz.Script.UNKNOWN | ||||
| 	hb_ucfunc      := harfbuzz.unicode_funcs_get_default() | ||||
| 	harfbuzz.buffer_clear_contents( ctx.hb_buffer ) | ||||
| @@ -71,12 +71,13 @@ shaper_shape_from_text :: proc( ctx : ^Shaper_Context, info : ^Shaper_Info, outp | ||||
| 	line_count     := 1 | ||||
| 	line_height    := ((ascent - descent + line_gap) * size_scale) | ||||
|  | ||||
| 	position, vertical_position : f32 | ||||
| 	shape_run :: proc( buffer : harfbuzz.Buffer, script : harfbuzz.Script, font : harfbuzz.Font, output : ^Shaped_Text, | ||||
| 		position, vertical_position, max_line_width: ^f32, line_count: ^int, | ||||
| 	position : Vec2 | ||||
| 	shape_run :: #force_inline proc( buffer : harfbuzz.Buffer, script : harfbuzz.Script, font : harfbuzz.Font, output : ^Shaped_Text, | ||||
| 		position : ^Vec2, max_line_width: ^f32, line_count: ^int, | ||||
| 		ascent, descent, line_gap, size, size_scale: f32, | ||||
| 		snap_shape_pos : b32, adv_snap_small_font_threshold : f32 ) | ||||
| 	{ | ||||
| 		profile(#procedure) | ||||
| 		// Set script and direction. We use the system's default langauge. | ||||
| 		// script = HB_SCRIPT_LATIN | ||||
| 		harfbuzz.buffer_set_script( buffer, script ) | ||||
| @@ -102,10 +103,10 @@ shaper_shape_from_text :: proc( ctx : ^Shaper_Context, info : ^Shaper_Info, outp | ||||
|  | ||||
| 			if hb_glyph.cluster > 0 | ||||
| 			{ | ||||
| 				(max_line_width^)     = max( max_line_width^, position^ ) | ||||
| 				(position^)           = 0.0 | ||||
| 				(vertical_position^) -= line_height | ||||
| 				(vertical_position^)  = floor(vertical_position^) | ||||
| 				(max_line_width^)     = max( max_line_width^, position.x ) | ||||
| 				position.x            = 0.0 | ||||
| 				position.y           -= line_height | ||||
| 				position.y            = floor(position.y) | ||||
| 				(line_count^)         += 1 | ||||
| 				continue | ||||
| 			} | ||||
| @@ -116,26 +117,24 @@ shaper_shape_from_text :: proc( ctx : ^Shaper_Context, info : ^Shaper_Info, outp | ||||
|  | ||||
| 			append( & output.glyphs, glyph_id ) | ||||
|  | ||||
| 			pos      := position^  | ||||
| 			v_pos    := vertical_position^ | ||||
| 			offset_x := f32(hb_gposition.x_offset) * size_scale | ||||
| 			offset_y := f32(hb_gposition.y_offset) * size_scale | ||||
| 			pos   += offset_x | ||||
| 			v_pos += offset_y | ||||
| 			glyph_pos := position^ | ||||
| 			offset    := Vec2 { f32(hb_gposition.x_offset), f32(hb_gposition.y_offset) } * size_scale | ||||
| 			glyph_pos += offset | ||||
|  | ||||
| 			if snap_shape_pos { | ||||
| 				pos   = ceil(pos) | ||||
| 				v_pos = ceil(v_pos) | ||||
| 				glyph_pos = ceil(glyph_pos) | ||||
| 			} | ||||
| 			append( & output.positions, Vec2 {pos, v_pos}) | ||||
| 			append( & output.positions, glyph_pos) | ||||
|  | ||||
| 			(position^)          += f32(hb_gposition.x_advance) * size_scale | ||||
| 			(vertical_position^) += f32(hb_gposition.y_advance) * size_scale | ||||
| 			(max_line_width^)     = max(max_line_width^, position^) | ||||
| 			advance := Vec2 {  | ||||
| 				f32(hb_gposition.x_advance) * size_scale,  | ||||
| 				f32(hb_gposition.y_advance) * size_scale | ||||
| 			} | ||||
| 			(position^)          += advance | ||||
| 			(max_line_width^)     = max(max_line_width^, position.x) | ||||
| 		} | ||||
|  | ||||
| 		output.end_cursor_pos.x = position^ | ||||
| 		output.end_cursor_pos.y = vertical_position^ | ||||
| 		output.end_cursor_pos = position^ | ||||
| 		harfbuzz.buffer_clear_contents( buffer ) | ||||
| 	} | ||||
|  | ||||
| @@ -162,7 +161,7 @@ shaper_shape_from_text :: proc( ctx : ^Shaper_Context, info : ^Shaper_Info, outp | ||||
| 		// End current run since we've encountered a script change. | ||||
| 		shape_run(  | ||||
| 			ctx.hb_buffer, current_script, info.font, output,  | ||||
| 			& position, & vertical_position, & max_line_width, & line_count,  | ||||
| 			& position, & max_line_width, & line_count,  | ||||
| 			ascent, descent, line_gap, size, size_scale,  | ||||
| 			ctx.snap_glyph_position, ctx.adv_snap_small_font_threshold | ||||
| 		) | ||||
| @@ -173,7 +172,7 @@ shaper_shape_from_text :: proc( ctx : ^Shaper_Context, info : ^Shaper_Info, outp | ||||
| 	// End the last run if needed | ||||
| 	shape_run(  | ||||
| 		ctx.hb_buffer, current_script, info.font, output,  | ||||
| 		& position, & vertical_position, & max_line_width, & line_count,  | ||||
| 		& position, & max_line_width, & line_count,  | ||||
| 		ascent, descent, line_gap, size, size_scale,  | ||||
| 		ctx.snap_glyph_position, ctx.adv_snap_small_font_threshold | ||||
| 	) | ||||
|   | ||||
| @@ -7,7 +7,7 @@ package vefontcache | ||||
|  | ||||
| import "base:runtime" | ||||
|  | ||||
| Font_ID :: distinct i64 | ||||
| Font_ID :: distinct i32 | ||||
| Glyph   :: distinct i32 | ||||
|  | ||||
| Entry :: struct { | ||||
| @@ -37,7 +37,7 @@ Context :: struct { | ||||
|  | ||||
| 	temp_path               : [dynamic]Vertex, | ||||
| 	temp_codepoint_seen     : map[u64]bool, | ||||
| 	temp_codepoint_seen_num : int, | ||||
| 	temp_codepoint_seen_num : i32, | ||||
|  | ||||
| 	snap_width  : f32, | ||||
| 	snap_height : f32, | ||||
| @@ -63,7 +63,6 @@ Context :: struct { | ||||
| 	debug_print_verbose : b32, | ||||
| } | ||||
|  | ||||
|  | ||||
| Init_Atlas_Region_Params :: struct { | ||||
| 	width  : u32, | ||||
| 	height : u32, | ||||
| @@ -113,7 +112,7 @@ Init_Glyph_Draw_Params :: struct { | ||||
|  | ||||
| Init_Glyph_Draw_Params_Default :: Init_Glyph_Draw_Params { | ||||
| 	over_sample   = Vec2 { 4, 4 }, | ||||
| 	buffer_batch  = 4, | ||||
| 	buffer_batch  = 8, | ||||
| 	draw_padding  = Init_Atlas_Params_Default.glyph_padding, | ||||
| } | ||||
|  | ||||
| @@ -135,8 +134,8 @@ Init_Shape_Cache_Params :: struct { | ||||
| } | ||||
|  | ||||
| Init_Shape_Cache_Params_Default :: Init_Shape_Cache_Params { | ||||
| 	capacity       = 8 * 1024, | ||||
| 	reserve_length = 256, | ||||
| 	capacity       = 10 * 1024, | ||||
| 	reserve_length = 10 * 1024, | ||||
| } | ||||
|  | ||||
| //#region("lifetime") | ||||
| @@ -148,10 +147,10 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType, | ||||
| 	glyph_draw_params           := Init_Glyph_Draw_Params_Default, | ||||
| 	shape_cache_params          := Init_Shape_Cache_Params_Default, | ||||
| 	shaper_params               := Init_Shaper_Params_Default, | ||||
| 	default_curve_quality       : u32 = 3, | ||||
| 	entires_reserve             : u32 = 512, | ||||
| 	default_curve_quality       : u32 = 2, | ||||
| 	entires_reserve             : u32 = 256, | ||||
| 	temp_path_reserve           : u32 = 1024, | ||||
| 	temp_codepoint_seen_reserve : u32 = 2048, | ||||
| 	temp_codepoint_seen_reserve : u32 = 1024, | ||||
| ) | ||||
| { | ||||
| 	assert( ctx != nil, "Must provide a valid context" ) | ||||
| @@ -179,13 +178,13 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType, | ||||
| 	temp_codepoint_seen, error = make( map[u64]bool, uint(temp_codepoint_seen_reserve) ) | ||||
| 	assert(error == .None, "VEFontCache.init : Failed to allocate temp_path") | ||||
|  | ||||
| 	draw_list.vertices, error = make( [dynamic]Vertex, len = 0, cap = 4 * Kilobyte ) | ||||
| 	draw_list.vertices, error = make( [dynamic]Vertex, len = 0, cap = 1 * Kilobyte ) | ||||
| 	assert(error == .None, "VEFontCache.init : Failed to allocate draw_list.vertices") | ||||
|  | ||||
| 	draw_list.indices, error = make( [dynamic]u32, len = 0, cap = 8 * Kilobyte ) | ||||
| 	draw_list.indices, error = make( [dynamic]u32, len = 0, cap = 2 * Kilobyte ) | ||||
| 	assert(error == .None, "VEFontCache.init : Failed to allocate draw_list.indices") | ||||
|  | ||||
| 	draw_list.calls, error = make( [dynamic]Draw_Call, len = 0, cap = 512 ) | ||||
| 	draw_list.calls, error = make( [dynamic]Draw_Call, len = 0, cap = 128 ) | ||||
| 	assert(error == .None, "VEFontCache.init : Failed to allocate draw_list.calls") | ||||
|  | ||||
| 	init_atlas_region :: proc( region : ^Atlas_Region, params : Init_Atlas_Params, region_params : Init_Atlas_Region_Params, factor : Vec2i, expected_cap : i32 ) | ||||
| @@ -215,7 +214,7 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType, | ||||
|  | ||||
| 	atlas.width             = i32(atlas_params.width) | ||||
| 	atlas.height            = i32(atlas_params.height) | ||||
| 	atlas.glyph_padding     = i32(atlas_params.glyph_padding) | ||||
| 	atlas.glyph_padding     = f32(atlas_params.glyph_padding) | ||||
| 	atlas.glyph_over_scalar = atlas_params.glyph_over_scalar | ||||
|  | ||||
| 	atlas.region_a.offset   = {0, 0} | ||||
| @@ -394,7 +393,7 @@ load_font :: proc( ctx : ^Context, label : string, data : []byte, size_px : f32, | ||||
| 		used = true | ||||
|  | ||||
| 		parser_info = parser_load_font( & parser_ctx, label, data ) | ||||
| 		shaper_info = shaper_load_font( & shaper_ctx, label, data, transmute(rawptr) id ) | ||||
| 		shaper_info = shaper_load_font( & shaper_ctx, label, data ) | ||||
|  | ||||
| 		size       = size_px | ||||
| 		size_scale = parser_scale( & parser_info, size ) | ||||
| @@ -442,9 +441,9 @@ configure_snap :: #force_inline proc( ctx : ^Context, snap_width, snap_height : | ||||
| get_cursor_pos :: #force_inline proc( ctx : ^Context                  ) -> Vec2 { assert(ctx != nil); return ctx.cursor_pos } | ||||
| set_colour     :: #force_inline proc( ctx : ^Context, colour : Colour )         { assert(ctx != nil); ctx.colour = colour } | ||||
|  | ||||
| draw_text :: proc( ctx : ^Context, font : Font_ID, text_utf8 : string, position, scale : Vec2 ) -> b32 | ||||
| draw_text :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, position, scale : Vec2 ) -> b32 | ||||
| { | ||||
| 	// profile(#procedure) | ||||
| 	profile(#procedure) | ||||
| 	assert( ctx != nil ) | ||||
| 	assert( font >= 0 && int(font) < len(ctx.entries) ) | ||||
|  | ||||
| @@ -466,20 +465,50 @@ draw_text :: proc( ctx : ^Context, font : Font_ID, text_utf8 : string, position, | ||||
|  | ||||
| 	text_chunk = transmute(string) text_utf8_bytes[ : ] | ||||
| 	if len(text_chunk) > 0 { | ||||
| 		shaped        := shape_text_cached( ctx, font, text_chunk, entry ) | ||||
| 		shaped        := shape_text_cached( ctx, font, text_chunk, entry, shape_text_uncached_advanced ) | ||||
| 		ctx.cursor_pos = draw_text_shape( ctx, font, entry, shaped, position, scale, ctx.snap_width, ctx.snap_height ) | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| draw_text_mono_latin :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string, position, scale : Vec2 ) -> b32 | ||||
| { | ||||
| 	profile(#procedure) | ||||
| 	assert( ctx != nil ) | ||||
| 	assert( font >= 0 && int(font) < len(ctx.entries) ) | ||||
|  | ||||
| 	ctx.cursor_pos = {} | ||||
|  | ||||
| 	position := position | ||||
| 	if ctx.snap_width  > 0 do position.x = ceil(position.x * ctx.snap_width ) / ctx.snap_width | ||||
| 	if ctx.snap_height > 0 do position.y = ceil(position.y * ctx.snap_height) / ctx.snap_height | ||||
|  | ||||
| 	entry := & ctx.entries[ font ] | ||||
|  | ||||
| 	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 | ||||
|  | ||||
| 	text_chunk = transmute(string) text_utf8_bytes[ : ] | ||||
| 	if len(text_chunk) > 0 { | ||||
| 		shaped        := shape_text_cached( ctx, font, text_chunk, entry, shape_text_uncached_latin ) | ||||
| 		ctx.cursor_pos = draw_text_shape( ctx, font, entry, shaped, position, scale, ctx.snap_width, ctx.snap_height ) | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| // ve_fontcache_Draw_List | ||||
| get_draw_list :: proc( ctx : ^Context, optimize_before_returning := true ) -> ^Draw_List { | ||||
| get_draw_list :: #force_inline proc( ctx : ^Context, optimize_before_returning := true ) -> ^Draw_List { | ||||
| 	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 : []Draw_Call) { | ||||
| get_draw_list_layer :: #force_inline proc( ctx : ^Context, optimize_before_returning := true ) -> (vertices : []Vertex, indices : []u32, calls : []Draw_Call) { | ||||
| 	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 : ] | ||||
| @@ -489,7 +518,7 @@ get_draw_list_layer :: proc( ctx : ^Context, optimize_before_returning := true ) | ||||
| } | ||||
|  | ||||
| // ve_fontcache_flush_Draw_List | ||||
| flush_draw_list :: proc( ctx : ^Context ) { | ||||
| flush_draw_list :: #force_inline proc( ctx : ^Context ) { | ||||
| 	assert( ctx != nil ) | ||||
| 	using ctx | ||||
| 	clear_draw_list( & draw_list ) | ||||
| @@ -498,7 +527,7 @@ flush_draw_list :: proc( ctx : ^Context ) { | ||||
| 	draw_layer.calls_offset    = 0 | ||||
| } | ||||
|  | ||||
| flush_draw_list_layer :: proc( ctx : ^Context ) { | ||||
| flush_draw_list_layer :: #force_inline proc( ctx : ^Context ) { | ||||
| 	assert( ctx != nil ) | ||||
| 	using ctx | ||||
| 	draw_layer.vertices_offset = len(draw_list.vertices) | ||||
| @@ -510,14 +539,14 @@ flush_draw_list_layer :: proc( ctx : ^Context ) { | ||||
|  | ||||
| //#region("metrics") | ||||
|  | ||||
| measure_text_size :: proc( ctx : ^Context, font : Font_ID, text_utf8 : string ) -> (measured : Vec2) | ||||
| measure_text_size :: #force_inline proc( ctx : ^Context, font : Font_ID, text_utf8 : string ) -> (measured : Vec2) | ||||
| { | ||||
| 	// profile(#procedure) | ||||
| 	assert( ctx != nil ) | ||||
| 	assert( font >= 0 && int(font) < len(ctx.entries) ) | ||||
|  | ||||
| 	entry  := &ctx.entries[font] | ||||
| 	shaped := shape_text_cached(ctx, font, text_utf8, entry) | ||||
| 	shaped := shape_text_cached(ctx, font, text_utf8, entry, shape_text_uncached_advanced ) | ||||
| 	return shaped.size | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -248,7 +248,6 @@ hmap_chained_set :: proc( self : HMapChained($Type), key : u64, value : Type ) - | ||||
| 			block          = slice_ptr(transmute([^]byte) raw_mem, slot_size) | ||||
| 			// block, error = pool_grab(pool, false) | ||||
| 			next        := transmute( ^HMapChainedSlot(Type)) raw_data(block) | ||||
| 			next^ = {} | ||||
|  | ||||
| 			slot.next      = next | ||||
| 			slot.next^     = {} | ||||
|   | ||||
| @@ -15,15 +15,19 @@ set_profiler_module_context :: #force_inline proc "contextless" ( ctx : ^SpallPr | ||||
| 	Module_Context = ctx | ||||
| } | ||||
|  | ||||
| @(deferred_none = profile_end) | ||||
| DISABLE_PROFILING :: false | ||||
|  | ||||
| @(deferred_none = profile_end, disabled = DISABLE_PROFILING) | ||||
| profile :: #force_inline proc "contextless" ( name : string, loc := #caller_location ) { | ||||
| 	spall._buffer_begin( & Module_Context.ctx, & Module_Context.buffer, name, "", loc ) | ||||
| } | ||||
|  | ||||
| @(disabled = DISABLE_PROFILING) | ||||
| profile_begin :: #force_inline proc "contextless" ( name : string, loc := #caller_location ) { | ||||
| 	spall._buffer_begin( & Module_Context.ctx, & Module_Context.buffer, name, "", loc ) | ||||
| } | ||||
|  | ||||
| @(disabled = DISABLE_PROFILING) | ||||
| profile_end :: #force_inline proc "contextless" () { | ||||
| 	spall._buffer_end( & Module_Context.ctx, & Module_Context.buffer) | ||||
| } | ||||
|   | ||||
| @@ -310,7 +310,7 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem | ||||
| 	{ | ||||
| 		profile("screen ui") | ||||
|  | ||||
| 		ui_startup( & screen_ui.base, cache_allocator = persistent_slab_allocator() ) | ||||
| 		ui_startup( & screen_ui.base, cache_table_size = 2 * Kilo, cache_allocator = persistent_slab_allocator() ) | ||||
| 		ui_floating_startup( & screen_ui.floating, 1 * Kilobyte, 1 * Kilobyte, persistent_slab_allocator(), "screen ui floating manager" ) | ||||
|  | ||||
| 		using screen_ui | ||||
| @@ -348,7 +348,7 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem | ||||
| 			// } | ||||
|  | ||||
| 			// Setup workspace UI state | ||||
| 			ui_startup( & workspace.ui, cache_allocator =  persistent_slab_allocator() ) | ||||
| 			ui_startup( & workspace.ui, cache_table_size = 8 * Kilo, cache_allocator =  persistent_slab_allocator() ) | ||||
| 		} | ||||
|  | ||||
| 		// debug.path_lorem = str_fmt("C:/projects/SectrPrototype/examples/Lorem Ipsum (197).txt", allocator = persistent_slab_allocator()) | ||||
|   | ||||
| @@ -106,8 +106,9 @@ render_mode_2d_workspace :: proc( screen_extent : Vec2, cam : Camera, input : In | ||||
|  | ||||
| 	cam := cam | ||||
| 	when UI_Render_Method == .Layers { | ||||
| 		render_list := array_to_slice( ui.render_list ) | ||||
| 		render_ui_via_box_list( render_list, screen_extent, ve_ctx, ve_render, & cam ) | ||||
| 		render_list_box  := array_to_slice( ui.render_list_box ) | ||||
| 		render_list_text := array_to_slice( ui.render_list_text ) | ||||
| 		render_ui_via_box_list( render_list_box, render_list_text, screen_extent, ve_ctx, ve_render, & cam ) | ||||
| 	} | ||||
| 	when UI_Render_Method == .Depth_First | ||||
| 	{ | ||||
| @@ -281,8 +282,10 @@ render_screen_ui :: proc( screen_extent : Extents2, ui : ^UI_State, ve_ctx : ^ve | ||||
| 	render_set_view_space(screen_extent) | ||||
|  | ||||
| 	when UI_Render_Method == .Layers { | ||||
| 		render_list := array_to_slice( ui.render_list ) | ||||
| 		render_ui_via_box_list( render_list, screen_extent, ve_ctx, ve_render ) | ||||
| 		render_list_box  := array_to_slice( ui.render_list_box ) | ||||
| 		render_list_text := array_to_slice( ui.render_list_text ) | ||||
| 		render_ui_via_box_list( render_list_box, render_list_text, screen_extent, ve_ctx, ve_render ) | ||||
| 		// render_ui_via_box_list( render_list, screen_extent, ve_ctx, ve_render ) | ||||
| 	} | ||||
| 	when UI_Render_Method == .Depth_First | ||||
| 	{ | ||||
| @@ -468,7 +471,8 @@ render_ui_via_box_tree :: proc( ui : ^UI_State, screen_extent : Vec2, ve_ctx : ^ | ||||
| 	} | ||||
|  | ||||
| 	previous_layer : i32 = 0 | ||||
| 	for box := ui.root.first; box != nil; box = ui_box_tranverse_next_depth_first( box, bypass_intersection_test = false, ctx = ui ) | ||||
| 	for box := ui_box_tranverse_next_depth_first( ui.root, bypass_intersection_test = true, ctx = ui ); box != nil;  | ||||
| 	    box  = ui_box_tranverse_next_depth_first( box,     bypass_intersection_test = true, ctx = ui ) | ||||
| 	{ | ||||
| 		if box.ancestors != previous_layer { | ||||
| 			if shape_enqueued do render_flush_gp() | ||||
| @@ -555,68 +559,102 @@ render_ui_via_box_tree :: proc( ui : ^UI_State, screen_extent : Vec2, ve_ctx : ^ | ||||
| 	if text_enqueued  do render_text_layer( screen_extent, ve_ctx, ve_render ) | ||||
| } | ||||
|  | ||||
| render_ui_via_box_list :: proc( render_list : []UI_RenderBoxInfo, screen_extent : Vec2, ve_ctx : ^ve.Context, ve_render : VE_RenderData, cam : ^Camera = nil ) | ||||
| render_ui_via_box_list :: proc( box_list : []UI_RenderBoxInfo, text_list : []UI_RenderTextInfo, screen_extent : Vec2, ve_ctx : ^ve.Context, ve_render : VE_RenderData, cam : ^Camera = nil ) | ||||
| { | ||||
| 	profile(#procedure) | ||||
| 	debug        := get_state().debug | ||||
| 	default_font := get_state().default_font | ||||
|  | ||||
| 	text_enqueued  : b32 = false | ||||
| 	shape_enqueued : b32 = false | ||||
| 	cam_zoom_ratio := cam != nil ? 1.0 / cam.zoom     : 1.0 | ||||
| 	circle_radius  := cam != nil ? cam_zoom_ratio * 3 : 3 | ||||
|  | ||||
| 	for entry, id in render_list | ||||
| 	box_id  : i32 = 0 | ||||
| 	text_id : i32 = 0 | ||||
|  | ||||
| 	layer_left : b32 = true | ||||
| 	for layer_left | ||||
| 	{ | ||||
| 		already_passed_signal := id > 0 && render_list[ id - 1 ].layer_signal | ||||
| 		if !already_passed_signal && entry.layer_signal | ||||
| 		profile("layer") | ||||
| 		shape_enqueued : b32 = false | ||||
| 		box_layer_done : b32 = false | ||||
| 		for box_id < cast(i32) len(box_list) && ! box_layer_done | ||||
| 		{ | ||||
| 			// profile("render ui layer") | ||||
| 			render_flush_gp() | ||||
| 			if text_enqueued do render_text_layer( screen_extent, ve_ctx, ve_render ) | ||||
| 			continue | ||||
| 		} | ||||
| 		using entry | ||||
| 			profile("GP_Render") | ||||
| 			box_layer_done = b32(box_id > 0) && box_list[ box_id - 1 ].layer_signal | ||||
|  | ||||
| 		// profile("enqueue box") | ||||
| 			entry := box_list[box_id] | ||||
|  | ||||
| 		GP_Render: | ||||
| 		{ | ||||
| 			// profile("draw_shapes") | ||||
| 			if style.bg_color.a != 0 | ||||
| 			corner_radii_total : f32 = 0 | ||||
| 			for radius in entry.corner_radii do corner_radii_total += radius | ||||
|  | ||||
| 			if entry.bg_color.a != 0 | ||||
| 			{ | ||||
| 				render_set_color( style.bg_color ) | ||||
| 				draw_rect( bounds ) | ||||
| 				render_set_color( entry.bg_color ) | ||||
| 				if corner_radii_total > 0 do draw_rect_rounded( entry.bounds, entry.corner_radii, 16 ) | ||||
| 				else                      do draw_rect( entry.bounds) | ||||
| 				shape_enqueued = true | ||||
| 			} | ||||
|  | ||||
| 			if style.border_color.a != 0 && border_width > 0 { | ||||
| 				render_set_color( style.border_color ) | ||||
| 				draw_rect_border( bounds, border_width ) | ||||
| 			if entry.border_color.a != 0 && entry.border_width > 0 | ||||
| 			{ | ||||
| 				render_set_color( entry.border_color ) | ||||
|  | ||||
| 				if corner_radii_total > 0 do draw_rect_rounded_border( entry.bounds, entry.corner_radii, entry.border_width, 16 ) | ||||
| 				else                      do draw_rect_border( entry.bounds, entry.border_width ) | ||||
| 				shape_enqueued = true | ||||
| 			} | ||||
|  | ||||
| 			if debug.draw_ui_box_bounds_points | ||||
| 			{ | ||||
| 				render_set_color(Color_Red) | ||||
| 				draw_filled_circle(bounds.min.x, bounds.min.y, 3, 24) | ||||
| 				draw_filled_circle(entry.bounds.min.x, entry.bounds.min.y, circle_radius, 24) | ||||
|  | ||||
| 				render_set_color(Color_Blue) | ||||
| 				draw_filled_circle(bounds.max.x, bounds.max.y, 3, 24) | ||||
| 				draw_filled_circle(entry.bounds.max.x, entry.bounds.max.y, circle_radius, 24) | ||||
| 				shape_enqueued = true | ||||
| 			} | ||||
|  | ||||
| 			box_id += 1 | ||||
| 		} | ||||
|  | ||||
| 		if len(text.str) > 0 && style.font.key != 0 { | ||||
| 			if cam != nil { | ||||
| 				draw_text_string_pos_extent_zoomed( text.str, default_font, font_size, computed.text_pos, cam^, style.text_color ) | ||||
| 			} | ||||
| 			else { | ||||
| 				draw_text_string_pos_extent( text.str, default_font, font_size, computed.text_pos, style.text_color ) | ||||
| 			} | ||||
| 			text_enqueued = true | ||||
| 		if shape_enqueued { | ||||
| 			profile("render ui box_layer") | ||||
| 			render_flush_gp() | ||||
| 			shape_enqueued = false | ||||
| 		} | ||||
| 	 | ||||
| 		text_enqueued   : b32 = false | ||||
| 		text_layer_done : b32 = false | ||||
| 		for text_id < cast(i32) len(text_list) && ! text_layer_done | ||||
| 		{ | ||||
| 			profile("Text_Render") | ||||
| 			text_layer_done = b32(text_id > 0) && text_list[ text_id - 1 ].layer_signal | ||||
|  | ||||
| 			entry := text_list[text_id] | ||||
|  | ||||
| 			font := entry.font.key != 0 ? entry.font : default_font | ||||
| 			if len(entry.text) > 0 | ||||
| 			{ | ||||
| 				if cam != nil { | ||||
| 					draw_text_string_pos_extent_zoomed( entry.text, font, entry.font_size, entry.position, cam^, entry.color ) | ||||
| 				} | ||||
| 				else { | ||||
| 					draw_text_string_pos_extent( entry.text, font, entry.font_size, entry.position, entry.color ) | ||||
| 				} | ||||
| 				text_enqueued = true | ||||
| 			} | ||||
|  | ||||
| 			text_id += 1 | ||||
| 		} | ||||
|  | ||||
| 		if text_enqueued { | ||||
| 			profile("render ui text layer") | ||||
| 			if text_enqueued do render_text_layer( screen_extent, ve_ctx, ve_render ) | ||||
| 			text_enqueued  = false | ||||
| 		} | ||||
|  | ||||
| 		layer_left = box_id < cast(i32) len(box_list) && text_id < cast(i32) len(text_list) | ||||
| 	} | ||||
|  | ||||
| 	if shape_enqueued do render_flush_gp() | ||||
| 	if text_enqueued  do render_text_layer( screen_extent, ve_ctx, ve_render ) | ||||
| } | ||||
|  | ||||
| #region("Helpers") | ||||
|   | ||||
| @@ -46,8 +46,6 @@ font_provider_startup :: proc( ctx : ^FontProviderContext ) | ||||
| 	ve.startup( & ve_ctx, .STB_TrueType, allocator = persistent_slab_allocator() ) | ||||
| 	ve_ctx.glyph_buffer.over_sample = { 4,4 } | ||||
| 	log("VEFontCached initialized") | ||||
| 	// provider_data.ve_ctx.debug_print = true | ||||
| 	// provider_data.ve_ctx.debug_print_verbose = true | ||||
| 	font_provider_setup_sokol_gfx_objects( & render, ve_ctx ) | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -64,7 +64,7 @@ UI_Style_Stack_Size       :: 512 | ||||
| UI_Parent_Stack_Size      :: 512 | ||||
| // UI_Built_Boxes_Array_Size :: 8 | ||||
| UI_Built_Boxes_Array_Size :: 56 * Kilobyte | ||||
| UI_BoxCache_TableSize :: 8 * Kilobyte | ||||
| UI_BoxCache_TableSize     :: 8 * Kilobyte | ||||
|  | ||||
| UI_RenderEntry :: struct { | ||||
| 	info        : UI_RenderBoxInfo, | ||||
| @@ -75,14 +75,32 @@ UI_RenderEntry :: struct { | ||||
|  | ||||
| UI_RenderLayer :: DLL_NodeFL(UI_RenderEntry) | ||||
|  | ||||
| // UI_RenderBoxInfo :: struct { | ||||
| // 	using computed : UI_Computed, | ||||
| // 	using style    : UI_Style, | ||||
| // 	text           : StrRunesPair, | ||||
| // 	font_size      : UI_Scalar, | ||||
| // 	border_width   : UI_Scalar, | ||||
| // 	label          : StrRunesPair, | ||||
| // 	layer_signal   : b32, | ||||
| // } | ||||
|  | ||||
| UI_RenderBoxInfo :: struct { | ||||
| 	using computed : UI_Computed, | ||||
| 	using style    : UI_Style, | ||||
| 	text           : StrRunesPair, | ||||
| 	font_size      : UI_Scalar, | ||||
| 	border_width   : UI_Scalar, | ||||
| 	label          : StrRunesPair, | ||||
| 	layer_signal   : b32, | ||||
| 	bounds       : Range2, | ||||
| 	corner_radii : [Corner.Count]f32, | ||||
| 	bg_color     : RGBA8, | ||||
| 	border_color : RGBA8, | ||||
| 	border_width : UI_Scalar, | ||||
| 	layer_signal : b8, | ||||
| } | ||||
|  | ||||
| UI_RenderTextInfo :: struct { | ||||
| 	text         : string, | ||||
| 	position     : Vec2, | ||||
| 	color        : RGBA8, | ||||
| 	font         : FontID, | ||||
| 	font_size    : f32, | ||||
| 	layer_signal : b8 | ||||
| } | ||||
|  | ||||
| UI_RenderMethod :: enum u32 { | ||||
| @@ -90,7 +108,7 @@ UI_RenderMethod :: enum u32 { | ||||
| 	Layers, | ||||
| } | ||||
|  | ||||
| UI_Render_Method :: UI_RenderMethod.Depth_First | ||||
| UI_Render_Method :: UI_RenderMethod.Layers | ||||
|  | ||||
| // TODO(Ed): Rename to UI_Context | ||||
| UI_State :: struct { | ||||
| @@ -104,10 +122,15 @@ UI_State :: struct { | ||||
| 	prev_cache : ^HMapChained( UI_Box ), | ||||
| 	curr_cache : ^HMapChained( UI_Box ), | ||||
|  | ||||
| 	// TODO(Ed): DO WE ACTUALLY NEED THIS? | ||||
| 	spacial_indexing_method : UI_SpacialIndexingMethod, | ||||
|  | ||||
| 	// For rendering via a set of layers organized into a single command list | ||||
| 	// render_queue_builder : SubArena, | ||||
| 	render_queue         : Array(UI_RenderLayer), | ||||
| 	render_list          : Array(UI_RenderBoxInfo), | ||||
| 	// render_queue         : Array(UI_RenderLayer), | ||||
| 	// render_list          : Array(UI_RenderBoxInfo), | ||||
| 	render_list_box      : Array(UI_RenderBoxInfo), | ||||
| 	render_list_text     : Array(UI_RenderTextInfo), | ||||
|  | ||||
| 	null_box : ^UI_Box, // This was used with the Linked list interface... | ||||
| 	root     : ^UI_Box, | ||||
| @@ -138,13 +161,13 @@ UI_State :: struct { | ||||
|  | ||||
| #region("Lifetime") | ||||
|  | ||||
| ui_startup :: proc( ui : ^ UI_State, cache_allocator : Allocator /* , cache_reserve_size : u64 */ ) | ||||
| ui_startup :: proc( ui : ^ UI_State, spacial_indexing_method : UI_SpacialIndexingMethod = .QuadTree, cache_allocator : Allocator, cache_table_size : uint ) | ||||
| { | ||||
| 	ui := ui | ||||
| 	ui^ = {} | ||||
|  | ||||
| 	for & cache in ui.caches { | ||||
| 		box_cache, allocation_error := make( HMapChained(UI_Box), UI_BoxCache_TableSize, cache_allocator ) | ||||
| 		box_cache, allocation_error := make( HMapChained(UI_Box), cache_table_size, cache_allocator ) | ||||
| 		verify( allocation_error == AllocatorError.None, "Failed to allocate box cache" ) | ||||
| 		cache = box_cache | ||||
| 	} | ||||
| @@ -153,11 +176,17 @@ ui_startup :: proc( ui : ^ UI_State, cache_allocator : Allocator /* , cache_rese | ||||
|  | ||||
| 	allocation_error : AllocatorError | ||||
|  | ||||
| 	ui.render_queue, allocation_error = make( Array(UI_RenderLayer), 32, cache_allocator ) | ||||
| 	verify( allocation_error == AllocatorError.None, "Failed to allcate render_queue") | ||||
| 	// ui.render_queue, allocation_error = make( Array(UI_RenderLayer), 32, cache_allocator ) | ||||
| 	// verify( allocation_error == AllocatorError.None, "Failed to allcate render_queue") | ||||
|  | ||||
| 	ui.render_list, allocation_error = make( Array(UI_RenderBoxInfo), UI_Built_Boxes_Array_Size, cache_allocator, fixed_cap = true ) | ||||
| 	verify( allocation_error == AllocatorError.None, "Failed to allocate rener_list" ) | ||||
| 	// ui.render_list, allocation_error = make( Array(UI_RenderBoxInfo), UI_Built_Boxes_Array_Size, cache_allocator, fixed_cap = true ) | ||||
| 	// verify( allocation_error == AllocatorError.None, "Failed to allocate rener_list" ) | ||||
|  | ||||
| 	ui.render_list_box, allocation_error = make( Array(UI_RenderBoxInfo), UI_Built_Boxes_Array_Size, cache_allocator, fixed_cap = true ) | ||||
| 	verify( allocation_error == AllocatorError.None, "Failed to allocate rener_list_box" ) | ||||
|  | ||||
| 	ui.render_list_text, allocation_error = make( Array(UI_RenderTextInfo), UI_Built_Boxes_Array_Size, cache_allocator, fixed_cap = true ) | ||||
| 	verify( allocation_error == AllocatorError.None, "Failed to allocate rener_list_box" ) | ||||
|  | ||||
| 	log("ui_startup completed") | ||||
| } | ||||
| @@ -168,8 +197,10 @@ ui_reload :: proc( ui : ^ UI_State, cache_allocator : Allocator ) | ||||
| 	for & cache in ui.caches { | ||||
| 		hmap_chained_reload( cache, cache_allocator) | ||||
| 	} | ||||
| 	ui.render_queue.backing = cache_allocator | ||||
| 	ui.render_list.backing  = cache_allocator | ||||
| 	// ui.render_queue.backing = cache_allocator | ||||
| 	// ui.render_list.backing  = cache_allocator | ||||
| 	ui.render_list_box.backing  = cache_allocator | ||||
| 	ui.render_list_text.backing = cache_allocator | ||||
| } | ||||
|  | ||||
| // TODO(Ed) : Is this even needed? | ||||
| @@ -186,8 +217,10 @@ ui_graph_build_begin :: proc( ui : ^ UI_State, bounds : Vec2 = {} ) | ||||
|  | ||||
| 	stack_clear( & layout_combo_stack ) | ||||
| 	stack_clear( & style_combo_stack ) | ||||
| 	array_clear( render_queue ) | ||||
| 	array_clear( render_list ) | ||||
| 	// array_clear( render_queue ) | ||||
| 	// array_clear( render_list ) | ||||
| 	array_clear( render_list_box ) | ||||
| 	array_clear( render_list_text ) | ||||
|  | ||||
| 	curr_cache, prev_cache = swap( curr_cache, prev_cache ) | ||||
|  | ||||
| @@ -210,6 +243,7 @@ ui_graph_build_end :: proc( ui : ^UI_State ) | ||||
| 	state := get_state() | ||||
|  | ||||
| 	ui_parent_pop() // Should be ui_context.root | ||||
| 	assert(stack_peek(& ui.parent_stack) == nil) | ||||
|  | ||||
| 	Post_Build_Graph_Traversal: | ||||
| 	{ | ||||
| @@ -225,196 +259,68 @@ ui_graph_build_end :: proc( ui : ^UI_State ) | ||||
| 			computed.content    = computed.bounds | ||||
| 		} | ||||
|  | ||||
| 		previous_layer : i32 = 1 | ||||
|  | ||||
| 		// Auto-layout and initial render_queue generation | ||||
| 		profile_begin("Auto-layout and render_queue generation") | ||||
| 		render_queue := array_to_slice(ui.render_queue) | ||||
| 		for current := root.first; current != nil; current = ui_box_traverse_next_breadth_first( current, bypass_intersection_test = true ) | ||||
| 		// render_queue := array_to_slice(ui.render_queue) | ||||
| 		for current := ui.root.first; current != nil;  | ||||
| 				current  = ui_box_tranverse_next_depth_first( current, bypass_intersection_test = true, ctx = ui ) | ||||
| 		{ | ||||
| 			if ! current.computed.fresh { | ||||
| 				ui_box_compute_layout( current ) | ||||
| 			} | ||||
|  | ||||
| 			when UI_Render_Method == .Layers | ||||
| 			{ | ||||
| 			if ! intersects_range2(ui_view_bounds(ui), current.computed.bounds) { | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 				// TODO(Ed): Eventually put this into a sub-arena | ||||
| 				entry, error := new(UI_RenderEntry) | ||||
| 				(entry^) = UI_RenderEntry { | ||||
| 					info = { | ||||
| 						current.computed, | ||||
| 						current.style, | ||||
| 						current.text, | ||||
| 						current.layout.font_size, | ||||
| 						current.layout.border_width, | ||||
| 						current.label, | ||||
| 						false, | ||||
| 					}, | ||||
| 					layer_id = current.ancestors -1, | ||||
| 				} | ||||
| 			when true { | ||||
| 			// entry : UI_RenderBoxInfo = { | ||||
| 			// 	current.computed, | ||||
| 			// 	current.style, | ||||
| 			// 	current.text, | ||||
| 			// 	current.layout.font_size, | ||||
| 			// 	current.layout.border_width, | ||||
| 			// 	current.label, | ||||
| 			// 	false, | ||||
| 			// } | ||||
| 			entry_box := UI_RenderBoxInfo { | ||||
| 				bounds        = current.computed.bounds, | ||||
| 				corner_radii  = current.style.corner_radii, | ||||
| 				bg_color      = current.style.bg_color, | ||||
| 				border_color  = current.style.border_color, | ||||
| 				border_width  = current.layout.border_width, | ||||
| 			} | ||||
| 			entry_text := UI_RenderTextInfo { | ||||
| 				text      = current.text.str, | ||||
| 				position  = current.computed.text_pos, | ||||
| 				color     = current.style.text_color, | ||||
| 				font      = current.style.font, | ||||
| 				font_size = current.layout.font_size, | ||||
| 			} | ||||
|  | ||||
| 				if entry.layer_id >= i32(ui.render_queue.num) { | ||||
| 					append( & ui.render_queue, UI_RenderLayer {}) | ||||
| 					render_queue = array_to_slice(ui.render_queue) | ||||
| 				} | ||||
| 			if current.ancestors != previous_layer { | ||||
| 				entry_box .layer_signal = true | ||||
| 				entry_text.layer_signal = true | ||||
| 			} | ||||
|  | ||||
| 				// else if layer.last == nil { | ||||
| 				// 	layer.first.next = entry | ||||
| 				// 	entry.prev       = layer.first | ||||
| 				// 	layer.last       = entry | ||||
| 				// } | ||||
|  | ||||
| 				// push_back to next layer | ||||
| 				layer := & render_queue[entry.layer_id] | ||||
| 				if layer.first == nil { | ||||
| 					layer.first = entry | ||||
| 					layer.last  = entry | ||||
| 				} | ||||
| 				else { | ||||
| 					layer.last.next = entry | ||||
| 					entry.prev      = layer.last | ||||
| 					layer.last      = entry | ||||
| 				} | ||||
| 				// dll_full_push_back( layer, entry, nil ) | ||||
|  | ||||
| 				// If there is a parent entry, give it a reference to the child entry | ||||
| 				parent_entry  : ^UI_RenderEntry | ||||
| 				if entry.layer_id > 0 { | ||||
| 					parent_layer := & render_queue[entry.layer_id - 1] | ||||
| 					parent_entry  = parent_layer.last | ||||
| 					entry.parent  = parent_entry | ||||
|  | ||||
| 					if parent_entry.first == nil { | ||||
| 						parent_entry.first = entry | ||||
| 						parent_entry.last  = entry | ||||
| 					} | ||||
| 					else { | ||||
| 						parent_entry.last = entry | ||||
| 					} | ||||
| 					// dll_fl_append( parent_entry, entry ) | ||||
| 				} | ||||
| 			array_append(& ui.render_list_box,  entry_box) | ||||
| 			array_append(& ui.render_list_text, entry_text) | ||||
| 			previous_layer = current.ancestors | ||||
| 			} | ||||
| 		} | ||||
| 		profile_end() | ||||
|  | ||||
| 		profile("render_list generation") | ||||
| 		when UI_Render_Method == .Layers | ||||
| 		{ | ||||
| 			// render_queue overlap corrections & render_list generation | ||||
| 			render_queue = array_to_slice(ui.render_queue) | ||||
| 			for layer_id : i32 = 0; layer_id < i32(ui.render_queue.num); layer_id += 1 | ||||
| 			{ | ||||
| 				layer := & ui.render_queue.data[ layer_id ] | ||||
| 				append( & ui.render_list, UI_RenderBoxInfo { layer_signal = true }) | ||||
|  | ||||
| 				to_increment, error := make( Array(^UI_RenderEntry), 4 * Kilo ) | ||||
| 				verify( error == .None, "Faied to make to_increment array.") | ||||
|  | ||||
| 				to_inc_last_iterated : i32 = 0 | ||||
| 				for entry := layer.first; entry != nil; entry = entry.next | ||||
| 				{ | ||||
| 					for neighbor := entry.next; neighbor != nil; neighbor = neighbor.next | ||||
| 					{ | ||||
| 						if ! overlap_range2( entry.info.computed.bounds, neighbor.info.computed.bounds) do continue | ||||
| 						append( & to_increment, neighbor ) | ||||
| 					} // for neighbor := entry.next; neighbor != nil; neighbor = neighbor.next | ||||
|  | ||||
| 					if entry == to_increment.data[ to_inc_last_iterated ] { | ||||
| 						to_inc_last_iterated += 1 | ||||
| 					} | ||||
| 					else { | ||||
| 						// This entry stayed in this layer, we can append the value | ||||
| 						array_append_value( & ui.render_list, entry.info ) | ||||
| 					} | ||||
| 				} // for entry := layer.first; entry != nil; entry = entry.next | ||||
|  | ||||
| 				// Move overlaping entries & their children's by 1 layer | ||||
| 				to_inc_slice := array_to_slice(to_increment) | ||||
| 				for entry in to_inc_slice | ||||
| 				{ | ||||
| 					pop_layer      := render_queue[entry.layer_id] | ||||
| 					entry.layer_id += 1 | ||||
| 					if entry.layer_id >= i32(ui.render_queue.num) { | ||||
| 						append( & ui.render_queue, UI_RenderLayer {} ) | ||||
| 						render_queue = array_to_slice(ui.render_queue) | ||||
| 					} | ||||
| 					push_layer := render_queue[entry.layer_id] | ||||
|  | ||||
| 					// pop entry from layer | ||||
| 					prev      := entry.prev | ||||
| 					prev.next  = entry.next | ||||
| 					if entry == pop_layer.last { | ||||
| 						pop_layer.last = prev | ||||
| 					} | ||||
|  | ||||
| 					// push entry to next layer | ||||
| 					if push_layer.first == nil { | ||||
| 						push_layer.first = entry | ||||
| 						push_layer.last  = entry | ||||
| 					} | ||||
| 					else { | ||||
| 						push_layer.last.next = entry | ||||
| 						entry.prev           = push_layer.last | ||||
| 						push_layer.last      = entry | ||||
| 						entry.next           = nil | ||||
| 					} | ||||
| 					// else if push_layer.last == nil { | ||||
| 					// 	push_layer.last       = entry | ||||
| 					// 	entry.prev            = push_layer.first | ||||
| 					// 	push_layer.first.next = entry | ||||
| 					// 	entry.next            = nil | ||||
| 					// } | ||||
|  | ||||
| 					// increment children's layers | ||||
| 					if entry.first != nil | ||||
| 					{ | ||||
| 						for child := entry.first; child != nil; child = ui_render_entry_tranverse( child ) | ||||
| 						{ | ||||
| 							pop_layer      := render_queue[child.layer_id] | ||||
| 							child.layer_id += 1 | ||||
|  | ||||
| 							if child.layer_id >= i32(ui.render_queue.num) { | ||||
| 								append( & ui.render_queue, UI_RenderLayer {}) | ||||
| 								render_queue = array_to_slice(ui.render_queue) | ||||
| 							} | ||||
| 							push_layer := render_queue[child.layer_id] | ||||
|  | ||||
| 							// pop from current layer | ||||
| 							if child == pop_layer.first { | ||||
| 								pop_layer.first = nil | ||||
| 							} | ||||
| 							if child == pop_layer.last { | ||||
| 								pop_layer.last = child.prev | ||||
| 							} | ||||
|  | ||||
| 							// push_back to next layer | ||||
| 							if push_layer.first == nil { | ||||
| 								push_layer.first = child | ||||
| 								push_layer.last  = child | ||||
| 							} | ||||
| 							else { | ||||
| 								push_layer.last.next = child | ||||
| 								child.prev           = push_layer.last | ||||
| 								push_layer.last      = child | ||||
| 							} | ||||
|  | ||||
| 							// else if push_layer.last == nil { | ||||
| 							// 	push_layer.first.next = child | ||||
| 							// 	child.prev            = push_layer.first | ||||
| 							// 	push_layer.last       = child | ||||
| 							// } | ||||
|  | ||||
| 						} // for child := neighbor.first; child != nil; child = ui_render_entry_traverse_depth( child ) | ||||
| 					} // 	if entry.first != nil | ||||
| 				} // for entry in to_inc_slice | ||||
| 			} // for & layer in render_queue | ||||
| 		} | ||||
|  | ||||
| 		render_list  := array_to_slice(ui.render_list) | ||||
| 		// render_list  := array_to_slice(ui.render_list) | ||||
| 		render_list_box   := array_to_slice(ui.render_list_box) | ||||
| 		render_list_text  := array_to_slice(ui.render_list_text) | ||||
| 	} | ||||
|  | ||||
| 	get_state().ui_context = nil | ||||
| } | ||||
|  | ||||
| // TODO(Ed): Review usage if at all. | ||||
| ui_render_entry_tranverse :: proc( entry : ^UI_RenderEntry ) -> ^UI_RenderEntry | ||||
| { | ||||
| 	// using state := get_state() | ||||
|   | ||||
							
								
								
									
										29
									
								
								code/sectr/ui/core/collision.odin
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								code/sectr/ui/core/collision.odin
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| package sectr | ||||
|  | ||||
| UI_SpacialIndexingMethod :: enum(i32) { | ||||
| 	QuadTree, | ||||
| 	SpacialHash, | ||||
| } | ||||
|  | ||||
| ui_collision_register :: proc( box : ^UI_Box ) | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| ui_collision_query :: proc ( box : ^UI_Box ) -> DLL_NodePN(UI_Box) { | ||||
| 	return {} | ||||
| } | ||||
|  | ||||
| QuadTree_Tile :: struct { | ||||
|  | ||||
| } | ||||
|  | ||||
| QuadTree :: struct | ||||
| { | ||||
| 	boundary : Range2, | ||||
| 	 | ||||
| } | ||||
|  | ||||
| SpacialHashMap :: struct { | ||||
| 	 | ||||
| } | ||||
| @@ -216,11 +216,17 @@ ui_box_compute_layout :: proc( box : ^UI_Box, | ||||
| 	else if .Order_Children_Bottom_To_Top in layout.flags { | ||||
| 		ui_layout_children_vertically( box, .Bottom_To_Top ) | ||||
| 	} | ||||
|  | ||||
| 	if computed.fresh { | ||||
| 		ui_collision_register( box ) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| ui_compute_children_overall_bounds :: proc ( box : ^UI_Box ) -> ( children_bounds : Range2 ) | ||||
| { | ||||
| 	for current := box.first; current != nil && current.prev != box; current = ui_box_tranverse_next_depth_first( current, parent_limit = box ) | ||||
| 	// for current := box.first; current != nil && current.prev != box; current = ui_box_tranverse_next_depth_first( current, parent_limit = box ) | ||||
| 		for current := ui_box_tranverse_next_depth_first( box,     parent_limit = box, bypass_intersection_test = false ); current != nil;  | ||||
| 				current  = ui_box_tranverse_next_depth_first( current, parent_limit = box, bypass_intersection_test = false ) | ||||
| 	{ | ||||
| 		if current == box do return | ||||
| 		if ! current.computed.fresh do  ui_box_compute_layout( current ) | ||||
| @@ -236,7 +242,9 @@ ui_compute_children_overall_bounds :: proc ( box : ^UI_Box ) -> ( children_bound | ||||
| ui_box_compute_layout_children :: proc( box : ^UI_Box ) | ||||
| { | ||||
| 	// for current := box.first; current != nil && current.prev != box; current = ui_box_tranverse_next_depth_first( current, parent_limit = box ) | ||||
| 	for current := box.first; current != nil && current.prev != box; current = ui_box_traverse_next_breadth_first( current ) | ||||
| 	// for current := box.first; current != nil && current.prev != box; current = ui_box_traverse_next_breadth_first( current ) | ||||
| 	for current := ui_box_tranverse_next_depth_first( box,     parent_limit = box, bypass_intersection_test = false ); current != nil;  | ||||
| 			current  = ui_box_tranverse_next_depth_first( current, parent_limit = box, bypass_intersection_test = false ) | ||||
| 	{ | ||||
| 		if current == box do return | ||||
| 		if current.computed.fresh do continue | ||||
|   | ||||
| @@ -202,6 +202,7 @@ test_whitespace_ast :: proc( default_layout : ^UI_Layout, frame_style_default : | ||||
| 			line_id += 1 | ||||
| 			continue | ||||
| 		} | ||||
| 		profile("line") | ||||
|  | ||||
| 		ui_layout( text_layout ) | ||||
| 		line_hbox := ui_widget(str_fmt( "line %v", line_id ), {.Mouse_Clickable}) | ||||
|   | ||||
| @@ -153,7 +153,7 @@ ui_hbox_begin :: proc( direction : UI_LayoutDirection_X, label : string, flags : | ||||
| ui_hbox_end :: proc( hbox : UI_HBox, width_ref : ^f32 = nil, compute_layout := false ) | ||||
| { | ||||
| 	// profile(#procedure) | ||||
| 	if compute_layout do ui_box_compute_layout(hbox.box, dont_mark_fresh = true) | ||||
| 	// if compute_layout do ui_box_compute_layout(hbox.box, dont_mark_fresh = true) | ||||
| 	// ui_layout_children_horizontally( hbox.box, hbox.direction, width_ref ) | ||||
| } | ||||
|  | ||||
| @@ -773,7 +773,7 @@ ui_vbox_begin :: proc( direction : UI_LayoutDirection_Y, label : string, flags : | ||||
| 	vbox.direction = direction | ||||
| 	vbox.box       = ui_box_make( flags, label ) | ||||
| 	vbox.signal    = ui_signal_from_box( vbox.box ) | ||||
| 	if compute_layout do ui_box_compute_layout(vbox, dont_mark_fresh = true) | ||||
| 	// if compute_layout do ui_box_compute_layout(vbox, dont_mark_fresh = true) | ||||
| 	switch direction { | ||||
| 		case .Top_To_Bottom: | ||||
| 			vbox.layout.flags |= { .Order_Children_Top_To_Bottom } | ||||
| @@ -786,7 +786,7 @@ ui_vbox_begin :: proc( direction : UI_LayoutDirection_Y, label : string, flags : | ||||
| // Auto-layout children | ||||
| ui_vbox_end :: proc( vbox : UI_VBox, height_ref : ^f32 = nil, compute_layout := false ) { | ||||
| 	// profile(#procedure) | ||||
| 	if compute_layout do ui_box_compute_layout(vbox, dont_mark_fresh = true) | ||||
| 	// if compute_layout do ui_box_compute_layout(vbox, dont_mark_fresh = true) | ||||
| 	// ui_layout_children_vertically( vbox.box, vbox.direction, height_ref ) | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -202,9 +202,9 @@ push-location $path_root | ||||
| 			$build_args += $flag_use_separate_modules | ||||
| 			$build_args += $flag_thread_count + $CoreCount_Physical | ||||
| 			# $build_args += $flag_optimize_none | ||||
| 			$build_args += $flag_optimize_minimal | ||||
| 			# $build_args += $flag_optimize_minimal | ||||
| 			# $build_args += $flag_optimize_speed | ||||
| 			# $build_args += $falg_optimize_aggressive | ||||
| 			$build_args += $falg_optimize_aggressive | ||||
| 			$build_args += $flag_debug | ||||
| 			$build_args += $flag_pdb_name + $pdb | ||||
| 			$build_args += $flag_subsystem + 'windows' | ||||
|   | ||||
		Reference in New Issue
	
	Block a user