fix hot-reload, starting to setup proper rendering again.
* Added hot_reload, measure_text_size to VEFontCache
This commit is contained in:
		| @@ -49,6 +49,17 @@ pool_list_init :: proc( pool : ^PoolList, capacity : u32 ) | ||||
| 	back  = -1 | ||||
| } | ||||
|  | ||||
| pool_list_free :: proc( pool : ^PoolList ) | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| pool_list_reload :: proc( pool : ^PoolList, allocator : Allocator ) | ||||
| { | ||||
| 	pool.items.backing     = allocator | ||||
| 	pool.free_list.backing = allocator | ||||
| } | ||||
|  | ||||
| pool_list_push_front :: proc( pool : ^PoolList, value : PoolListValue ) | ||||
| { | ||||
| 	using pool | ||||
| @@ -134,6 +145,17 @@ LRU_init :: proc( cache : ^LRU_Cache, capacity : u32 ) { | ||||
| 	pool_list_init( & cache.key_queue, capacity ) | ||||
| } | ||||
|  | ||||
| LRU_free :: proc( cache : ^LRU_Cache ) | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| LRU_reload :: proc( cache : ^LRU_Cache, allocator : Allocator ) | ||||
| { | ||||
| 	hmap_zpl_reload( & cache.table, allocator ) | ||||
| 	pool_list_reload( & cache.key_queue, allocator ) | ||||
| } | ||||
|  | ||||
| LRU_hash_key :: #force_inline proc( key : u64 ) -> ( hash : u64 ) { | ||||
| 	bytes := transmute( [8]byte ) key | ||||
| 	hash   = fnv64a( bytes[:] ) | ||||
| @@ -172,7 +194,8 @@ LRU_peek :: proc( cache : ^LRU_Cache, key : u64 ) -> i32 { | ||||
| 	return iter.value | ||||
| } | ||||
|  | ||||
| LRU_put :: proc( cache : ^LRU_Cache, key : u64,  value : i32 ) -> u64 { | ||||
| LRU_put :: proc( cache : ^LRU_Cache, key : u64,  value : i32 ) -> u64 | ||||
| { | ||||
| 	hash_key := LRU_hash_key( key ) | ||||
| 	iter     := get( & cache.table, hash_key ) | ||||
| 	if iter != nil { | ||||
|   | ||||
| @@ -355,6 +355,43 @@ init :: proc( ctx : ^Context, parser_kind : ParserKind, | ||||
| 	shaper_init( & shaper_ctx ) | ||||
| } | ||||
|  | ||||
| hot_reload :: proc( ctx : ^Context, allocator : Allocator ) | ||||
| { | ||||
| 	ctx.backing       = allocator | ||||
| 	context.allocator = ctx.backing | ||||
|  | ||||
| 	using ctx | ||||
|  | ||||
| 	entries.backing   = allocator | ||||
| 	temp_path.backing = allocator | ||||
| 	hmap_zpl_reload( & temp_codepoint_seen, allocator ) | ||||
| 	draw_list.vertices.backing  = allocator | ||||
| 	draw_list.indices.backing   = allocator | ||||
| 	draw_list.calls.backing     = allocator | ||||
|  | ||||
| 	LRU_reload( & atlas.region_a.state, allocator) | ||||
| 	LRU_reload( & atlas.region_b.state, allocator) | ||||
| 	LRU_reload( & atlas.region_c.state, allocator) | ||||
| 	LRU_reload( & atlas.region_d.state, allocator) | ||||
|  | ||||
| 	LRU_reload( & shape_cache.state, allocator ) | ||||
| 	for idx : u32 = 0; idx < u32(shape_cache.storage.capacity); idx += 1 { | ||||
| 		stroage_entry := & shape_cache.storage.data[idx] | ||||
| 		using stroage_entry | ||||
|  | ||||
| 		glyphs.backing    = allocator | ||||
| 		positions.backing = allocator | ||||
| 	} | ||||
|  | ||||
| 	atlas.draw_list.calls.backing    = allocator | ||||
| 	atlas.draw_list.indices.backing  = allocator | ||||
| 	atlas.draw_list.vertices.backing = allocator | ||||
|  | ||||
| 	atlas.clear_draw_list.calls.backing    = allocator | ||||
| 	atlas.clear_draw_list.indices.backing  = allocator | ||||
| 	atlas.clear_draw_list.vertices.backing = allocator | ||||
| } | ||||
|  | ||||
| // ve_foncache_shutdown | ||||
| shutdown :: proc( ctx : ^Context ) | ||||
| { | ||||
| @@ -679,6 +716,37 @@ is_empty :: proc( ctx : ^Context, entry : ^Entry, glyph_index : Glyph ) -> b32 | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| measure_text_size :: proc( ctx : ^Context, font : FontID, text_utf8 : string ) -> (measured : Vec2) | ||||
| { | ||||
| 	assert( ctx != nil ) | ||||
| 	assert( font >= 0 && font < FontID(ctx.entries.num) ) | ||||
|  | ||||
| 	atlas := ctx.atlas | ||||
|  | ||||
| 	shaped := shape_text_cached( ctx, font, text_utf8 ) | ||||
|  | ||||
| 	entry := & ctx.entries.data[ font ] | ||||
|  | ||||
| 	batch_start_idx : i32 = 0 | ||||
| 	for index : i32 = 0; index < i32(shaped.glyphs.num); index += 1 | ||||
| 	{ | ||||
| 		glyph_index := shaped.glyphs.data[ index ] | ||||
| 		if is_empty( ctx, entry, glyph_index ) do continue | ||||
|  | ||||
| 		bounds_0, bounds_1 := parser_get_glyph_box( & entry.parser_info, glyph_index ) | ||||
| 		bounds_width  := bounds_1.x - bounds_0.x | ||||
| 		bounds_height := bounds_1.y - bounds_0.y | ||||
|  | ||||
| 		bounds := Vec2 { | ||||
| 			cast(f32) cast(u32) (f32(bounds_width)  * entry.size_scale - f32(atlas.glyph_padding)), | ||||
| 			cast(f32) cast(u32) (f32(bounds_height) * entry.size_scale - f32(atlas.glyph_padding)), | ||||
| 		} | ||||
| 		measured += bounds | ||||
| 	} | ||||
|  | ||||
| 	return measured | ||||
| } | ||||
|  | ||||
| reset_batch_codepoint_state :: proc( ctx : ^Context ) { | ||||
| 	clear( & ctx.temp_codepoint_seen ) | ||||
| 	ctx.temp_codepoint_seen_num = 0 | ||||
|   | ||||
| @@ -289,6 +289,8 @@ draw_text :: proc( ctx : ^Context, font : FontID, text_utf8 : string, position : | ||||
| 	assert( ctx != nil ) | ||||
| 	assert( font >= 0 && font < FontID(ctx.entries.num) ) | ||||
|  | ||||
| 	context.allocator = ctx.backing | ||||
|  | ||||
| 	shaped := shape_text_cached( ctx, font, text_utf8 ) | ||||
|  | ||||
| 	snap_width  := f32(ctx.snap_width) | ||||
|   | ||||
| @@ -108,6 +108,10 @@ make :: proc { | ||||
| 	hmap_zpl_init, | ||||
| } | ||||
|  | ||||
| // reload :: proc { | ||||
|  | ||||
| // } | ||||
|  | ||||
| remove_at :: proc { | ||||
| 	array_remove_at, | ||||
| } | ||||
|   | ||||
| @@ -2,17 +2,17 @@ package grime | ||||
|  | ||||
| import "base:runtime" | ||||
|  | ||||
| reload_array :: proc( self : [dynamic]$Type, allocator : Allocator ) { | ||||
| reload_array :: proc( self : ^[dynamic]$Type, allocator : Allocator ) { | ||||
| 	raw          := transmute(runtime.Raw_Dynamic_Array) self | ||||
| 	raw.allocator = allocator | ||||
| } | ||||
|  | ||||
| reload_queue :: proc( self : Queue($Type), allocator : Allocator ) { | ||||
| reload_queue :: proc( self : ^Queue($Type), allocator : Allocator ) { | ||||
| 	raw_array          := transmute(runtime.Raw_Dynamic_Array) self.data | ||||
| 	raw_array.allocator = allocator | ||||
| } | ||||
|  | ||||
| reload_map :: proc( self : map [$KeyType] $EntryType, allocator : Allocator ) { | ||||
| reload_map :: proc( self : ^map [$KeyType] $EntryType, allocator : Allocator ) { | ||||
| 	raw          := transmute(runtime.Raw_Map) self | ||||
| 	raw.allocator = allocator | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,18 @@ | ||||
| package sectr | ||||
|  | ||||
| RGBA8       :: struct { r, g, b, a : u8 } | ||||
| RGBA8 :: struct { r, g, b, a : u8 } | ||||
| RGBAN :: [4]f32 | ||||
|  | ||||
| normalize_rgba8 :: #force_inline proc( color : RGBA8 ) -> RGBAN { | ||||
| 	result := RGBAN { | ||||
| 		1.0 / f32(color.r), | ||||
| 		1.0 / f32(color.g), | ||||
| 		1.0 / f32(color.b), | ||||
| 		1.0 / f32(color.a), | ||||
| 	} | ||||
| 	return result | ||||
| } | ||||
|  | ||||
| Color_Blue  :: RGBA8 {  90,  90, 230, 255 } | ||||
| Color_Red   :: RGBA8 { 230,  90,  90, 255 } | ||||
| Color_White :: RGBA8 { 255, 255, 255, 255 } | ||||
|   | ||||
| @@ -43,8 +43,6 @@ render :: proc() | ||||
| 	do_nothing : bool | ||||
| 	do_nothing = false | ||||
| 
 | ||||
| 	time.sleep(10000) | ||||
| 
 | ||||
| 	// The below are most likely limited to a "depth layer" and so | ||||
| 	// different depth layers need different draw pass combos (of the 3 constructive passes) | ||||
| 	// Will need to profile how expensive it is for batching with the UI box rendering | ||||
| @@ -112,19 +112,19 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem | ||||
| 		using input_events | ||||
|  | ||||
| 		error : AllocatorError | ||||
| 		events, error  = make( Queue(InputEvent), 4 * Kilo, persistent_allocator() ) | ||||
| 		events, error  = make( Queue(InputEvent), 4 * Kilo, persistent_slab_allocator() ) | ||||
| 		ensure(error == AllocatorError.None, "Failed to allocate input.events array") | ||||
|  | ||||
| 		key_events, error = make( Queue(InputKeyEvent), Kilo, persistent_allocator() ) | ||||
| 		key_events, error = make( Queue(InputKeyEvent), Kilo, persistent_slab_allocator() ) | ||||
| 		ensure(error == AllocatorError.None, "Failed to allocate key_events array") | ||||
|  | ||||
| 		mouse_events, error = make( Queue(InputMouseEvent), 2 * Kilo, persistent_allocator() ) | ||||
| 		mouse_events, error = make( Queue(InputMouseEvent), 2 * Kilo, persistent_slab_allocator() ) | ||||
| 		ensure(error == AllocatorError.None, "Failed to allocate mouse_events array") | ||||
|  | ||||
| 		codes_pressed, error = make( Array(rune), Kilo, persistent_allocator() ) | ||||
| 		codes_pressed, error = make( Array(rune), Kilo, persistent_slab_allocator() ) | ||||
| 		ensure(error == AllocatorError.None, "Failed to allocate codes_pressed array") | ||||
|  | ||||
| 		staged_input_events, error = make( Array(InputEvent), 8 * Kilo, persistent_allocator() ) | ||||
| 		staged_input_events, error = make( Array(InputEvent), 8 * Kilo, persistent_slab_allocator() ) | ||||
| 		ensure(error == AllocatorError.None, "Failed to allocate input_staged_events array") | ||||
| 	} | ||||
|  | ||||
| @@ -416,9 +416,16 @@ hot_reload :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_ | ||||
| 	slab_reload( persistent_slab, persistent_allocator() ) | ||||
|  | ||||
| 	// input_reload() | ||||
| 	{ | ||||
| 		using input_events | ||||
| 		reload( & events, persistent_slab_allocator()) | ||||
| 		reload( & key_events, persistent_slab_allocator()) | ||||
| 		reload( & mouse_events, persistent_slab_allocator()) | ||||
| 		codes_pressed.backing = persistent_slab_allocator() | ||||
| 		staged_input_events.backing = persistent_slab_allocator() | ||||
| 	} | ||||
|  | ||||
| 	// font_provider_reload() | ||||
| 	hmap_chained_reload( font_provider_data.font_cache, persistent_allocator()) | ||||
| 	font_provider_reload() | ||||
|  | ||||
| 	str_cache_reload( & string_cache, persistent_allocator(), persistent_allocator() ) | ||||
| 	str_cache_set_module_ctx( & string_cache ) | ||||
| @@ -512,7 +519,7 @@ tick_work_frame :: #force_inline proc( host_delta_time_ms : f64 ) -> b32 | ||||
| } | ||||
|  | ||||
| // Lifted out of tick so that sokol_app_frame_callback can do it as well. | ||||
| tick_frametime :: #force_inline proc( client_tick : ^time.Tick, host_delta_time_ms : f64, host_delta_ns : Duration ) | ||||
| tick_frametime :: #force_inline proc( client_tick : ^time.Tick, host_delta_time_ms : f64, host_delta_ns : Duration, can_sleep := true ) | ||||
| { | ||||
| 	profile(#procedure) | ||||
| 	state := get_state(); using state | ||||
| @@ -536,7 +543,7 @@ tick_frametime :: #force_inline proc( client_tick : ^time.Tick, host_delta_time_ | ||||
| 		sleep_ms       := frametime_target_ms - frametime_elapsed_ms | ||||
| 		pre_sleep_tick := time.tick_now() | ||||
|  | ||||
| 		if sleep_ms > 0 { | ||||
| 		if can_sleep && sleep_ms > 0 { | ||||
| 			thread_sleep( cast(Duration) sleep_ms * MS_To_NS ) | ||||
| 			// thread__highres_wait( sleep_ms ) | ||||
| 		} | ||||
|   | ||||
| @@ -45,7 +45,7 @@ sokol_app_frame_callback :: proc "c" () | ||||
| 	should_close |= tick_work_frame( sokol_delta_ms ) | ||||
| 	profile_end() | ||||
|  | ||||
| 	tick_frametime( & client_tick, sokol_delta_ms, sokol_delta_ns ) | ||||
| 	tick_frametime( & client_tick, sokol_delta_ms, sokol_delta_ns, can_sleep = false ) | ||||
| 	window.resized = false | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										336
									
								
								code/sectr/engine/render.odin
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										336
									
								
								code/sectr/engine/render.odin
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,336 @@ | ||||
| package sectr | ||||
|  | ||||
| import "core:math" | ||||
| import "core:time" | ||||
|  | ||||
| import ve         "codebase:font/VEFontCache" | ||||
| import sokol_app  "thirdparty:sokol/app" | ||||
| import gfx        "thirdparty:sokol/gfx" | ||||
| import sokol_glue "thirdparty:sokol/glue" | ||||
| import gp         "thirdparty:sokol/gp" | ||||
|  | ||||
| PassActions :: struct { | ||||
| 	bg_clear_black : gfx.Pass_Action, | ||||
|  | ||||
| } | ||||
|  | ||||
| RenderState :: struct { | ||||
| 	pass_actions : PassActions, | ||||
| } | ||||
|  | ||||
| // Draw text using a string and normalized screen coordinates | ||||
| draw_text_string_pos_norm :: proc( content : string, id : FontID, size : f32, pos : Vec2, color := Color_White ) | ||||
| { | ||||
| 	state := get_state(); using state | ||||
| 	width  := app_window.extent.x * 2 | ||||
| 	height := app_window.extent.y * 2 | ||||
|  | ||||
| 	ve_id      := font_provider_resolve_draw_id( id ) | ||||
| 	color_norm := normalize_rgba8(color) | ||||
|  | ||||
| 	ve.set_colour( & font_provider_data.ve_font_cache, color_norm ) | ||||
| 	ve.draw_text( & font_provider_data.ve_font_cache, ve_id, content, pos, Vec2{1 / width, 1 / height} ) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Draw text using a string and extent-based screen coordinates | ||||
| draw_text_string_pos_extent :: proc( content : string, id : FontID, size : f32, pos : Vec2, color := Color_White ) | ||||
| { | ||||
| 	state := get_state(); using state | ||||
| 	extent := app_window.extent | ||||
|  | ||||
| 	normalized_pos := pos / extent | ||||
| 	draw_text_string_pos_norm( content, id, size, normalized_pos, color ) | ||||
| } | ||||
|  | ||||
| render :: proc() | ||||
| { | ||||
| 	profile(#procedure) | ||||
| 	state := get_state(); using state // TODO(Ed): Prefer passing static context to through the callstack | ||||
|  | ||||
|  | ||||
| 	// TODO(Ed): Eventually we want to only update when state is dirty/user has done an action | ||||
| 	gfx.begin_pass(gfx.Pass { action = render_data.pass_actions.bg_clear_black, swapchain = sokol_glue.swapchain() }) | ||||
| 	gfx.end_pass(); | ||||
|  | ||||
| 	// render_mode_3d() | ||||
|  | ||||
| 	render_mode_2d_workspace() | ||||
| 	render_mode_screenspace() | ||||
|  | ||||
| 	gfx.commit() | ||||
| 	ve.flush_draw_list( & font_provider_data.ve_font_cache ) | ||||
| } | ||||
|  | ||||
| // TODO(Ed): Eventually this needs to become a 'viewport within a UI' | ||||
| // This would allow the user to have more than one workspace open at the same time | ||||
| render_mode_2d_workspace :: proc() | ||||
| { | ||||
| 	profile(#procedure) | ||||
| 	state  := get_state(); using state // TODO(Ed): Prefer passing static context to through the callstack | ||||
| 	cam    := & project.workspace.cam | ||||
| } | ||||
|  | ||||
| render_mode_screenspace :: proc() | ||||
| { | ||||
| 	profile(#procedure) | ||||
| 	state := get_state(); using state // TODO(Ed): Prefer passing static context to through the callstack | ||||
| 	replay := & Memory_App.replay | ||||
| 	cam    := & project.workspace.cam | ||||
| 	win_extent := state.app_window.extent | ||||
|  | ||||
| 	ve.configure_snap( & font_provider_data.ve_font_cache, u32(state.app_window.extent.x * 2.0), u32(state.app_window.extent.y * 2.0) ) | ||||
|  | ||||
| 	render_screen_ui() | ||||
|  | ||||
| 	debug_draw_text :: proc( content : string, pos : Vec2, size : f32, color := Color_White, font : FontID = Font_Default ) | ||||
| 	{ | ||||
| 		state := get_state(); using state | ||||
|  | ||||
| 		if len( content ) == 0 { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		font := font | ||||
| 		if font.key == Font_Default.key { | ||||
| 			font = default_font | ||||
| 		} | ||||
| 		// pos := screen_to_render_pos(pos) | ||||
|  | ||||
| 		draw_text_string_pos_extent( content, font, size, pos, color ) | ||||
| 	} | ||||
|  | ||||
| 	debug_text :: proc( format : string, args : ..any ) | ||||
| 	{ | ||||
| 		@static draw_text_scratch : [Kilobyte * 8]u8 | ||||
|  | ||||
| 		state := get_state(); using state | ||||
| 		if debug.draw_debug_text_y > 800 { | ||||
| 			debug.draw_debug_text_y = 0 | ||||
| 		} | ||||
|  | ||||
| 		cam            := & project.workspace.cam | ||||
| 		screen_corners := screen_get_corners() | ||||
|  | ||||
| 		position   := screen_corners.top_right | ||||
| 		position.x -= app_window.extent.x | ||||
| 		position.y -= debug.draw_debug_text_y | ||||
|  | ||||
| 		content := str_fmt_buffer( draw_text_scratch[:], format, ..args ) | ||||
| 		debug_draw_text( content, position, 12.0 ) | ||||
|  | ||||
| 		debug.draw_debug_text_y += 14 | ||||
| 	} | ||||
|  | ||||
| 	// "Draw text" using immediate mode api | ||||
| 	{ | ||||
| 		font_provider := & state.font_provider_data | ||||
| 		using font_provider | ||||
|  | ||||
| 		@static index : i32 | ||||
| 		text_test_str := str_fmt("frametime       : %0.6f\nframetime(sokol): %0.2f\nframe id   : %d\nsokol_frame: %d", frametime_delta_ms, sokol_app.frame_delta() * S_To_MS, frame, sokol_app.frame_count() ) | ||||
| 		// log(text_test_str) | ||||
| 		// text_test_str := str_fmt("HELLO VE FONT CACHE!") | ||||
| 		// text_test_str := str_fmt("C") | ||||
|  | ||||
| 		// font_provider := & state.font_provider_data | ||||
| 		// fdef := hmap_chained_get( font_cache, default_font.key ) | ||||
|  | ||||
| 		width  := app_window.extent.x * 2 | ||||
| 		height := app_window.extent.y * 2 | ||||
|  | ||||
| 		ve.set_colour( & ve_font_cache,  { 1.0, 1.0, 1.0, 1.0 } ) | ||||
| 		ve.configure_snap( & ve_font_cache, u32(state.app_window.extent.x * 2.0), u32(state.app_window.extent.y * 2.0) ) | ||||
|  | ||||
| 		ve.draw_text( & ve_font_cache, font_provider_resolve_draw_id(default_font), text_test_str, {0.0, 0.975}, Vec2{1 / width, 1 / height} ) | ||||
| 	} | ||||
|  | ||||
| 	debug.debug_text_vis = true | ||||
| 	if debug.debug_text_vis | ||||
| 	{ | ||||
| 		fps_msg       := str_fmt( "FPS: %f", fps_avg) | ||||
| 		fps_msg_width := cast(f32) u32(measure_text_size( fps_msg, default_font, 12.0, 0.0 ).x) + 0.5 | ||||
| 		fps_msg_pos   := screen_get_corners().top_right - { fps_msg_width, 0 } - { 5, 5 } | ||||
| 		debug_draw_text( fps_msg, fps_msg_pos, 12.0, color = Color_White ) | ||||
| 		// debug_draw_text( fps_msg, {}, 12.0, color = Color_White ) | ||||
|  | ||||
| 		render_text_layer() | ||||
| 	} | ||||
|  | ||||
| 	debug.draw_debug_text_y = 14 | ||||
| } | ||||
|  | ||||
| render_screen_ui :: proc() | ||||
| { | ||||
| 	profile(#procedure) | ||||
| 	state  := get_state(); using state // TODO(Ed): Prefer passing static context to through the callstack | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
| render_text_layer :: proc() | ||||
| { | ||||
| 	profile("VEFontCache: render frame") | ||||
|  | ||||
| 	Bindings    :: gfx.Bindings | ||||
| 	Range       :: gfx.Range | ||||
| 	ShaderStage :: gfx.Shader_Stage | ||||
|  | ||||
| 	state := get_state(); using state | ||||
| 	font_provider := state.font_provider_data | ||||
| 	using font_provider | ||||
|  | ||||
| 	ve.optimize_draw_list( & ve_font_cache ) | ||||
| 	draw_list := ve.get_draw_list( & ve_font_cache ) | ||||
|  | ||||
| 	draw_list_vert_slice  := array_to_slice(draw_list.vertices) | ||||
| 	draw_list_index_slice := array_to_slice(draw_list.indices) | ||||
|  | ||||
| 	gfx.update_buffer( draw_list_vbuf, Range{ draw_list.vertices.data, draw_list.vertices.num * size_of(ve.Vertex) }) | ||||
| 	gfx.update_buffer( draw_list_ibuf, Range{ draw_list.indices.data,  draw_list.indices.num  * size_of(u32)  }) | ||||
|  | ||||
| 	draw_list_call_slice := array_to_slice(draw_list.calls) | ||||
| 	for & draw_call in array_to_slice(draw_list.calls) | ||||
| 	{ | ||||
| 		watch := draw_call | ||||
| 		// profile("VEFontCache: draw call") | ||||
|  | ||||
| 		switch draw_call.pass | ||||
| 		{ | ||||
| 			// 1. Do the glyph rendering pass | ||||
| 			// Glyphs are first rendered to an intermediate 2k x 512px R8 texture | ||||
| 			case .Glyph: | ||||
| 				// profile("VEFontCache: draw call: glyph") | ||||
| 				if (draw_call.end_index - draw_call.start_index) == 0 && ! draw_call.clear_before_draw { | ||||
| 					continue | ||||
| 				} | ||||
|  | ||||
| 				width  := ve_font_cache.atlas.buffer_width | ||||
| 				height := ve_font_cache.atlas.buffer_height | ||||
|  | ||||
| 				pass := glyph_pass | ||||
| 				if draw_call.clear_before_draw { | ||||
| 					pass.action.colors[0].load_action   = .CLEAR | ||||
| 					pass.action.colors[0].clear_value.a = 1.0 | ||||
| 				} | ||||
| 				gfx.begin_pass( pass ) | ||||
|  | ||||
| 				// sokol_gfx.apply_viewport( 0,0, width, height, origin_top_left = true ) | ||||
| 				// sokol_gfx.apply_scissor_rect( 0,0, width, height, origin_top_left = true ) | ||||
|  | ||||
| 				gfx.apply_pipeline( glyph_pipeline ) | ||||
|  | ||||
| 				bindings := Bindings { | ||||
| 					vertex_buffers = { | ||||
| 						0 = draw_list_vbuf, | ||||
| 					}, | ||||
| 					vertex_buffer_offsets = { | ||||
| 						0 = 0, | ||||
| 					}, | ||||
| 					index_buffer        = draw_list_ibuf, | ||||
| 					index_buffer_offset = 0,//i32(draw_call.start_index) * size_of(u32), | ||||
| 					fs = {}, | ||||
| 				} | ||||
| 				gfx.apply_bindings( bindings ) | ||||
|  | ||||
| 			// 2. Do the atlas rendering pass | ||||
| 			// A simple 16-tap box downsample shader is then used to blit from this intermediate texture to the final atlas location | ||||
| 			case .Atlas: | ||||
| 				// profile("VEFontCache: draw call: atlas") | ||||
| 				if (draw_call.end_index - draw_call.start_index) == 0 && ! draw_call.clear_before_draw { | ||||
| 					continue | ||||
| 				} | ||||
|  | ||||
| 				width  := ve_font_cache.atlas.width | ||||
| 				height := ve_font_cache.atlas.height | ||||
|  | ||||
| 				pass := atlas_pass | ||||
| 				if draw_call.clear_before_draw { | ||||
| 					pass.action.colors[0].load_action   = .CLEAR | ||||
| 					pass.action.colors[0].clear_value.a = 1.0 | ||||
| 				} | ||||
| 				gfx.begin_pass( pass ) | ||||
|  | ||||
| 				// sokol_gfx.apply_viewport( 0, 0, width, height, origin_top_left = true ) | ||||
| 				// sokol_gfx.apply_scissor_rect( 0, 0, width, height, origin_top_left = true ) | ||||
|  | ||||
| 				gfx.apply_pipeline( atlas_pipeline ) | ||||
|  | ||||
| 				fs_uniform := Ve_Blit_Atlas_Fs_Params { region = cast(i32) draw_call.region } | ||||
| 				gfx.apply_uniforms( ShaderStage.FS, SLOT_ve_blit_atlas_fs_params, Range { & fs_uniform, size_of(Ve_Blit_Atlas_Fs_Params) }) | ||||
|  | ||||
| 				gfx.apply_bindings(Bindings { | ||||
| 					vertex_buffers = { | ||||
| 						0 = draw_list_vbuf, | ||||
| 					}, | ||||
| 					vertex_buffer_offsets = { | ||||
| 						0 = 0, | ||||
| 					}, | ||||
| 					index_buffer        = draw_list_ibuf, | ||||
| 					index_buffer_offset = 0,//i32(draw_call.start_index) * size_of(u32), | ||||
| 					fs = { | ||||
| 						images   = { SLOT_ve_blit_atlas_src_texture = glyph_rt_color, }, | ||||
| 						samplers = { SLOT_ve_blit_atlas_src_sampler = glyph_rt_sampler, }, | ||||
| 					}, | ||||
| 				}) | ||||
|  | ||||
| 			// 3. Use the atlas to then render the text. | ||||
| 			case .None: fallthrough | ||||
| 			case .Target: fallthrough | ||||
| 			case .Target_Uncached: | ||||
| 				if (draw_call.end_index - draw_call.start_index) == 0 && ! draw_call.clear_before_draw { | ||||
| 					continue | ||||
| 				} | ||||
|  | ||||
| 				// profile("VEFontCache: draw call: target") | ||||
| 				width  := u32(app_window.extent.x * 2) | ||||
| 				height := u32(app_window.extent.y * 2) | ||||
|  | ||||
| 				pass := screen_pass | ||||
| 				pass.swapchain = sokol_glue.swapchain() | ||||
| 				gfx.begin_pass( pass ) | ||||
|  | ||||
| 				// sokol_gfx.apply_viewport( 0, 0, width, height, origin_top_left = true ) | ||||
| 				// sokol_gfx.apply_scissor_rect( 0, 0, width, height, origin_top_left = true ) | ||||
|  | ||||
| 				gfx.apply_pipeline( screen_pipeline ) | ||||
|  | ||||
| 				src_rt      := atlas_rt_color | ||||
| 				src_sampler := atlas_rt_sampler | ||||
|  | ||||
| 				fs_target_uniform := Ve_Draw_Text_Fs_Params { | ||||
| 					down_sample = 0, | ||||
| 					colour = {1.0, 1.0, 1.0, 1}, | ||||
| 				} | ||||
|  | ||||
| 				if draw_call.pass == .Target_Uncached { | ||||
| 					fs_target_uniform.down_sample = 1 | ||||
| 					src_rt      = glyph_rt_color | ||||
| 					src_sampler = glyph_rt_sampler | ||||
| 				} | ||||
| 				gfx.apply_uniforms( ShaderStage.FS, SLOT_ve_draw_text_fs_params, Range { & fs_target_uniform, size_of(Ve_Draw_Text_Fs_Params) }) | ||||
|  | ||||
| 				gfx.apply_bindings(Bindings { | ||||
| 					vertex_buffers = { | ||||
| 						0 = draw_list_vbuf, | ||||
| 					}, | ||||
| 					vertex_buffer_offsets = { | ||||
| 						0 = 0, | ||||
| 					}, | ||||
| 					index_buffer        = draw_list_ibuf, | ||||
| 					index_buffer_offset = 0,//i32(draw_call.start_index) * size_of(u32), | ||||
| 					fs = { | ||||
| 						images   = { SLOT_ve_draw_text_src_texture = src_rt, }, | ||||
| 						samplers = { SLOT_ve_draw_text_src_sampler = src_sampler, }, | ||||
| 					}, | ||||
| 				}) | ||||
| 		} | ||||
|  | ||||
| 		if (draw_call.end_index - draw_call.start_index) != 0 { | ||||
| 			num_indices := draw_call.end_index - draw_call.start_index | ||||
| 			gfx.draw( draw_call.start_index, num_indices, 1 ) | ||||
| 		} | ||||
|  | ||||
| 		gfx.end_pass() | ||||
| 	} | ||||
| } | ||||
| @@ -1,5 +1,6 @@ | ||||
| package sectr | ||||
|  | ||||
| import "core:math" | ||||
| import "core:os" | ||||
| import ve         "codebase:font/VEFontCache" | ||||
| import sokol_gfx  "thirdparty:sokol/gfx" | ||||
| @@ -22,8 +23,10 @@ FontID  :: struct { | ||||
| } | ||||
|  | ||||
| FontDef :: struct { | ||||
| 	path_file : string, | ||||
| 	ve_id     : ve.FontID, | ||||
| 	path_file    : string, | ||||
| 	default_size : i32, | ||||
| 	size_table   : [Font_Largest_Px_Size / Font_Size_Interval] ve.FontID, | ||||
| 	// ve_id     : ve.FontID, | ||||
| } | ||||
|  | ||||
| FontProviderData :: struct | ||||
| @@ -150,7 +153,7 @@ font_provider_startup :: proc() | ||||
| 				pixel_format = .R8, | ||||
| 				write_mask   = .RGBA, | ||||
| 				blend = BlendState { | ||||
| 					enabled = true, | ||||
| 					enabled          = true, | ||||
| 					src_factor_rgb   = .ONE_MINUS_DST_COLOR, | ||||
| 					dst_factor_rgb   = .ONE_MINUS_SRC_COLOR, | ||||
| 					op_rgb           = BlendOp.ADD, | ||||
| @@ -169,11 +172,11 @@ font_provider_startup :: proc() | ||||
| 				}, | ||||
| 				color_count  = 1, | ||||
| 				depth = { | ||||
| 					pixel_format = .DEPTH, | ||||
| 					compare = .ALWAYS, | ||||
| 					pixel_format  = .DEPTH, | ||||
| 					compare       = .ALWAYS, | ||||
| 					write_enabled = false, | ||||
| 				}, | ||||
| 				cull_mode = .NONE, | ||||
| 				cull_mode    = .NONE, | ||||
| 				sample_count = 1, | ||||
| 				// label = | ||||
| 			}) | ||||
| @@ -213,18 +216,17 @@ font_provider_startup :: proc() | ||||
| 				min_filter    = Filter.NEAREST, | ||||
| 				mag_filter    = Filter.NEAREST, | ||||
| 				mipmap_filter = Filter.NONE, | ||||
| 				wrap_u = .CLAMP_TO_EDGE, | ||||
| 				wrap_v = .CLAMP_TO_EDGE, | ||||
| 				min_lod = -1000.0, | ||||
| 				max_lod =  1000.0, | ||||
| 				wrap_u        = .CLAMP_TO_EDGE, | ||||
| 				wrap_v        = .CLAMP_TO_EDGE, | ||||
| 				min_lod       = -1000.0, | ||||
| 				max_lod       =  1000.0, | ||||
| 				border_color  = BorderColor.OPAQUE_BLACK, | ||||
| 				compare = .NEVER | ||||
| 				compare       = .NEVER | ||||
| 			}) | ||||
| 			verify( sokol_gfx.query_sampler_state( glyph_rt_sampler) < ResourceState.FAILED, "Failed to make atlas_rt_sampler" ) | ||||
|  | ||||
| 			color_attach := AttachmentDesc { | ||||
| 				image     = glyph_rt_color, | ||||
| 				// mip_level = 1, | ||||
| 				image = glyph_rt_color, | ||||
| 			} | ||||
|  | ||||
| 			glyph_attachments := sokol_gfx.make_attachments({ | ||||
| @@ -242,19 +244,18 @@ font_provider_startup :: proc() | ||||
| 					0 = { | ||||
| 						load_action  = .LOAD, | ||||
| 						store_action = .STORE, | ||||
| 						// clear_value  = {0.01,0.01,0.01,1}, | ||||
| 						clear_value  = {0.00, 0.00, 0.00, 1.00}, | ||||
| 					} | ||||
| 				}, | ||||
| 				depth = { | ||||
| 					load_action = .DONTCARE, | ||||
| 					load_action  = .DONTCARE, | ||||
| 					store_action = .DONTCARE, | ||||
| 					clear_value = 0.0, | ||||
| 					clear_value  = 0.0, | ||||
| 				}, | ||||
| 				stencil = { | ||||
| 					load_action = .DONTCARE, | ||||
| 					load_action  = .DONTCARE, | ||||
| 					store_action = .DONTCARE, | ||||
| 					clear_value = 0, | ||||
| 					clear_value  = 0, | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| @@ -290,7 +291,7 @@ font_provider_startup :: proc() | ||||
| 				pixel_format = .R8, | ||||
| 				write_mask   = .RGBA, | ||||
| 				blend = BlendState { | ||||
| 					enabled = true, | ||||
| 					enabled          = true, | ||||
| 					src_factor_rgb   = .SRC_ALPHA, | ||||
| 					dst_factor_rgb   = .ONE_MINUS_SRC_ALPHA, | ||||
| 					op_rgb           = BlendOp.ADD, | ||||
| @@ -309,11 +310,11 @@ font_provider_startup :: proc() | ||||
| 				}, | ||||
| 				color_count  = 1, | ||||
| 				depth = { | ||||
| 					pixel_format = .DEPTH, | ||||
| 					compare = .ALWAYS, | ||||
| 					pixel_format  = .DEPTH, | ||||
| 					compare       = .ALWAYS, | ||||
| 					write_enabled = false, | ||||
| 				}, | ||||
| 				cull_mode = .NONE, | ||||
| 				cull_mode    = .NONE, | ||||
| 				sample_count = 1, | ||||
| 			}) | ||||
| 		} | ||||
| @@ -352,12 +353,12 @@ font_provider_startup :: proc() | ||||
| 				min_filter    = Filter.NEAREST, | ||||
| 				mag_filter    = Filter.NEAREST, | ||||
| 				mipmap_filter = Filter.NONE, | ||||
| 				wrap_u = .CLAMP_TO_EDGE, | ||||
| 				wrap_v = .CLAMP_TO_EDGE, | ||||
| 				min_lod = -1000.0, | ||||
| 				max_lod =  1000.0, | ||||
| 				wrap_u        = .CLAMP_TO_EDGE, | ||||
| 				wrap_v        = .CLAMP_TO_EDGE, | ||||
| 				min_lod       = -1000.0, | ||||
| 				max_lod       =  1000.0, | ||||
| 				border_color  = BorderColor.OPAQUE_BLACK, | ||||
| 				compare = .NEVER | ||||
| 				compare       = .NEVER | ||||
| 			}) | ||||
| 			verify( sokol_gfx.query_sampler_state( atlas_rt_sampler) < ResourceState.FAILED, "Failed to make atlas_rt_sampler" ) | ||||
|  | ||||
| @@ -448,8 +449,8 @@ font_provider_startup :: proc() | ||||
| 				color_count  = 1, | ||||
| 				sample_count = 1, | ||||
| 				depth = { | ||||
| 					pixel_format = app_env.defaults.depth_format, | ||||
| 					compare = .ALWAYS, | ||||
| 					pixel_format  = app_env.defaults.depth_format, | ||||
| 					compare       = .ALWAYS, | ||||
| 					write_enabled = false, | ||||
| 				}, | ||||
| 				cull_mode = .NONE, | ||||
| @@ -478,14 +479,14 @@ font_provider_startup :: proc() | ||||
| 					} | ||||
| 				}, | ||||
| 				depth = { | ||||
| 					load_action = .DONTCARE, | ||||
| 					load_action  = .DONTCARE, | ||||
| 					store_action = .DONTCARE, | ||||
| 					clear_value = 0.0, | ||||
| 					clear_value  = 0.0, | ||||
| 				}, | ||||
| 				stencil = { | ||||
| 					load_action = .DONTCARE, | ||||
| 					load_action  = .DONTCARE, | ||||
| 					store_action = .DONTCARE, | ||||
| 					clear_value = 0, | ||||
| 					clear_value  = 0, | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| @@ -502,7 +503,10 @@ font_provider_reload :: proc() | ||||
| 	state         := get_state() | ||||
| 	provider_data := & state.font_provider_data | ||||
|  | ||||
| 	ve.configure_snap( & provider_data.ve_font_cache, u32(state.app_window.extent.x * 2.0), u32(state.app_window.extent.y * 2.0) ) | ||||
| 	hmap_chained_reload( provider_data.font_cache, persistent_allocator()) | ||||
|  | ||||
| 	// ve.configure_snap( & provider_data.ve_font_cache, u32(state.app_window.extent.x * 2.0), u32(state.app_window.extent.y * 2.0) ) | ||||
| 	ve.hot_reload( & provider_data.ve_font_cache, persistent_slab_allocator() ) | ||||
| } | ||||
|  | ||||
| font_provider_shutdown :: proc() | ||||
| @@ -514,13 +518,14 @@ font_provider_shutdown :: proc() | ||||
| } | ||||
|  | ||||
| font_load :: proc(path_file : string, | ||||
| 	default_size : f32    = Font_Load_Use_Default_Size, | ||||
| 	default_size : i32    = Font_Load_Use_Default_Size, | ||||
| 	desired_id   : string = Font_Load_Gen_ID | ||||
| ) -> FontID | ||||
| { | ||||
| 	profile(#procedure) | ||||
| 	msg := str_fmt_tmp("Loading font: %v", path_file) | ||||
| 	profile(msg) | ||||
| 	log(msg) | ||||
|  | ||||
| 	logf("Loading font: %v", path_file) | ||||
| 	provider_data := & get_state().font_provider_data; using provider_data | ||||
|  | ||||
| 	font_data, read_succeded : = os.read_entire_file( path_file, persistent_allocator() ) | ||||
| @@ -542,11 +547,47 @@ font_load :: proc(path_file : string, | ||||
| 	def, set_error := hmap_chained_set(font_cache, key, FontDef{}) | ||||
| 	verify( set_error == AllocatorError.None, "Failed to add new font entry to cache" ) | ||||
|  | ||||
| 	def.path_file = path_file | ||||
| 	def.path_file    = path_file | ||||
| 	def.default_size = default_size | ||||
|  | ||||
| 	// TODO(Ed): Load even sizes from 8px to upper bound. | ||||
| 	def.ve_id = ve.load_font( & provider_data.ve_font_cache, desired_id, font_data, 18.0 ) | ||||
| 	for font_size : i32 = Font_Size_Interval; font_size <= Font_Largest_Px_Size; font_size += Font_Size_Interval | ||||
| 	{ | ||||
| 		id    := (font_size / Font_Size_Interval) + (font_size % Font_Size_Interval) | ||||
| 		ve_id := & def.size_table[id - 1] | ||||
| 		ve_id^ = ve.load_font( & provider_data.ve_font_cache, desired_id, font_data, 14.0 ) | ||||
| 	} | ||||
|  | ||||
| 	fid := FontID { key, desired_id } | ||||
| 	return fid | ||||
| } | ||||
|  | ||||
| Font_Use_Default_Size :: f32(0.0) | ||||
|  | ||||
| font_provider_resolve_draw_id :: proc( id : FontID, size := Font_Use_Default_Size ) -> ve.FontID | ||||
| { | ||||
| 	state := get_state(); using state | ||||
|  | ||||
| 	even_size := math.round(size * (1.0 / f32(Font_Size_Interval))) * f32(Font_Size_Interval) | ||||
| 	size      := clamp( i32( even_size), 4, Font_Largest_Px_Size ) | ||||
| 	def       := hmap_chained_get( font_provider_data.font_cache, id.key ) | ||||
| 	size       = size if size != i32(Font_Use_Default_Size) else def.default_size | ||||
|  | ||||
| 	id    := (size / Font_Size_Interval) + (size % Font_Size_Interval) | ||||
| 	ve_id := def.size_table[ id - 1 ] | ||||
|  | ||||
| 	width  := app_window.extent.x * 2 | ||||
| 	height := app_window.extent.y * 2 | ||||
| 	return ve_id | ||||
| } | ||||
|  | ||||
| measure_text_size :: proc( text : string, font : FontID, font_size := Font_Use_Default_Size, spacing : f32 ) -> Vec2 | ||||
| { | ||||
| 	state := get_state(); using state | ||||
|  | ||||
| 	// profile(#procedure) | ||||
| 	px_size := math.round( font_size ) | ||||
| 	ve_id   := font_provider_resolve_draw_id( font, font_size ) | ||||
|  | ||||
| 	measured := ve.measure_text_size( & font_provider_data.ve_font_cache, ve_id, text ) | ||||
| 	return measured | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user