Minor lifting in the render side, getting ready to just hunker down and take notes on whats next
* VEFontCache needs to get fixed up (possibly bring back ELFHash) * Problably going to do conversion early to odin's array and map usage * Need get the quad tree setup for the ui so that I can do fast and efficient traversal for the layer based rendering.
This commit is contained in:
		| @@ -325,7 +325,7 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem | ||||
| 			ui_startup( & workspace.ui, cache_allocator =  persistent_slab_allocator() ) | ||||
| 		} | ||||
|  | ||||
| 		debug.path_lorem = str_fmt("C:/projects/SectrPrototype/examples/Lorem Ipsum.txt", allocator = persistent_slab_allocator()) | ||||
| 		debug.path_lorem = str_fmt("C:/projects/SectrPrototype/examples/Lorem_Ispsum (197).txt", allocator = persistent_slab_allocator()) | ||||
|  | ||||
| 		alloc_error : AllocatorError; success : bool | ||||
| 		debug.lorem_content, success = os.read_entire_file( debug.path_lorem, persistent_slab_allocator() ) | ||||
|   | ||||
| @@ -19,133 +19,6 @@ RenderState :: struct { | ||||
| 	pass_actions : PassActions, | ||||
| } | ||||
|  | ||||
| #region("Helpers") | ||||
|  | ||||
| draw_filled_circle :: proc(x, y, radius: f32, edges: int) | ||||
| { | ||||
| 	if edges < 3 do return // Need at least 3 edges to form a shape | ||||
|  | ||||
| 	triangles     := make([]gp.Triangle, edges) | ||||
| 	center        := gp.Point{x, y} | ||||
| 	edge_quotient := 1 / f32(edges) | ||||
| 	angle_factor  := 2 * math.PI * edge_quotient | ||||
| 	for edge_id in 0..< edges | ||||
| 	{ | ||||
| 			angle1 := f32(edge_id   ) * angle_factor | ||||
| 			angle2 := f32(edge_id +1) * angle_factor | ||||
|  | ||||
| 			p1 := gp.Point{ | ||||
| 					x + radius * math.cos(angle1), | ||||
| 					y + radius * math.sin(angle1), | ||||
| 			} | ||||
| 			p2 := gp.Point{ | ||||
| 					x + radius * math.cos(angle2), | ||||
| 					y + radius * math.sin(angle2), | ||||
| 			} | ||||
| 			triangles[edge_id] = gp.Triangle{center, p1, p2} | ||||
| 	} | ||||
|  | ||||
| 	gp.draw_filled_triangles(raw_data(triangles), u32(len(triangles))) | ||||
| } | ||||
|  | ||||
| draw_rect :: proc( rect : Range2, color : RGBA8 ) { | ||||
| 	using rect | ||||
| 	render_set_color( color ) | ||||
|  | ||||
| 	size     := max - min | ||||
| 	position := min | ||||
| 	gp.draw_filled_rect( position.x, position.y, size.x, size.y ) | ||||
| } | ||||
|  | ||||
| draw_rect_border :: proc( rect : Range2, border_width: f32) | ||||
| { | ||||
| 	rect_size    := rect.max - rect.min | ||||
| 	border_width := lalg.min(border_width, min(rect_size.x, rect_size.y) * 0.5) | ||||
|  | ||||
| 	top    := gp.Rect{ rect.min.x,                rect.min.y,                rect_size.x,                    border_width } | ||||
| 	bottom := gp.Rect{ rect.min.x,                rect.max.y - border_width, rect_size.x,                    border_width } | ||||
| 	left   := gp.Rect{ rect.min.x,                rect.min.y + border_width, border_width, rect_size.y - 2 * border_width } | ||||
| 	right  := gp.Rect{ rect.max.x - border_width, rect.min.y + border_width, border_width, rect_size.y - 2 * border_width } | ||||
|  | ||||
| 	borders := []gp.Rect{ top, bottom, left, right } | ||||
| 	gp.draw_filled_rects( raw_data(borders), u32(len(borders)) ) | ||||
| } | ||||
|  | ||||
| // Draw text using a string and normalized render coordinates | ||||
| draw_text_string_pos_norm :: proc( content : string, id : FontID, size : f32, pos : Vec2, color := Color_White, scale : f32 = 1.0 ) | ||||
| { | ||||
| 	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, size ) | ||||
| 	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} * scale ) | ||||
| 	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 ) | ||||
| { | ||||
| 	// profile(#procedure) | ||||
| 	state          := get_state(); using state | ||||
| 	screen_size    := app_window.extent * 2 | ||||
| 	render_pos     := screen_to_render_pos(pos) | ||||
| 	normalized_pos := render_pos * (1.0 / screen_size) | ||||
| 	draw_text_string_pos_norm( content, id, size, normalized_pos, color ) | ||||
| } | ||||
|  | ||||
| draw_text_string_pos_extent_zoomed :: proc( content : string, id : FontID, size : f32, pos : Vec2, cam : Camera, color := Color_White ) | ||||
| { | ||||
| 	// profile(#procedure) | ||||
| 	cam_offset := Vec2 { | ||||
| 		cam.position.x, | ||||
| 		cam.position.y, | ||||
| 	} | ||||
|  | ||||
| 	pos_offset := pos + cam_offset * cam.zoom | ||||
|  | ||||
| 	cam_zoom_ratio := 1 / cam.zoom | ||||
|  | ||||
| 	state          := get_state(); using state | ||||
| 	screen_size    := app_window.extent * 2 | ||||
| 	render_pos     := screen_to_render_pos(pos_offset) | ||||
| 	normalized_pos := render_pos * (1.0 / screen_size) | ||||
| 	draw_text_string_pos_norm( content, id, size, normalized_pos, color, cam.zoom ) | ||||
| } | ||||
|  | ||||
| // TODO(Ed): Eventually the workspace will need a viewport for drawing text | ||||
|  | ||||
| render_flush_gp :: #force_inline proc() | ||||
| { | ||||
| 	gfx.begin_pass( gfx.Pass { action = get_state().render_data.pass_actions.empty_action, swapchain = sokol_glue.swapchain() }) | ||||
| 	gp.flush() | ||||
| 	gfx.end_pass() | ||||
| } | ||||
|  | ||||
| @(deferred_none=gp.reset_transform) | ||||
| render_set_camera :: #force_inline proc( cam : Camera ) | ||||
| { | ||||
| 	gp.translate( cam.position.x * cam.zoom, cam.position.y * cam.zoom ) | ||||
| 	gp.scale( cam.zoom, cam.zoom ) | ||||
| } | ||||
|  | ||||
| render_set_color :: #force_inline proc( color : RGBA8 ) { | ||||
| 	color := normalize_rgba8(color); | ||||
| 	gp.set_color( color.r, color.g, color.b, color.a ) | ||||
| } | ||||
|  | ||||
| render_set_view_space :: #force_inline proc( extent : Extents2 ) | ||||
| { | ||||
| 	size := extent * 2 | ||||
| 	gp.viewport(0, 0, i32(size.x), i32(size.y)) | ||||
| 	gp.project( -extent.x, extent.x, extent.y, -extent.y ) | ||||
| } | ||||
|  | ||||
| #endregion("Helpers") | ||||
|  | ||||
| render :: proc() | ||||
| { | ||||
| 	profile(#procedure) | ||||
| @@ -224,59 +97,15 @@ render_mode_2d_workspace :: proc() | ||||
| 	render_set_camera(cam) | ||||
|  | ||||
| 	ui := & project.workspace.ui | ||||
| 	render_list := array_to_slice( ui.render_list ) | ||||
|  | ||||
| 	text_enqueued  : b32 = false | ||||
| 	shape_enqueued : b32 = false | ||||
|  | ||||
| 	for entry, id in render_list | ||||
| 	{ | ||||
| 		already_passed_signal := id > 0 && render_list[ id - 1 ].layer_signal | ||||
| 		if !already_passed_signal && entry.layer_signal | ||||
| 		{ | ||||
| 			profile("render ui layer") | ||||
| 			render_flush_gp() | ||||
| 			if text_enqueued do render_text_layer() | ||||
| 			continue | ||||
| 		} | ||||
| 		using entry | ||||
|  | ||||
| 		profile("enqueue box") | ||||
|  | ||||
| 		GP_Render: | ||||
| 		{ | ||||
| 			// profile("draw_shapes") | ||||
| 			if style.bg_color.a != 0 | ||||
| 			{ | ||||
| 				draw_rect( bounds, style.bg_color ) | ||||
| 				shape_enqueued = true | ||||
| 			} | ||||
|  | ||||
| 			if style.border_color.a != 0 && border_width > 0 { | ||||
| 				render_set_color( style.border_color ) | ||||
| 				draw_rect_border( bounds, 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) | ||||
|  | ||||
| 				render_set_color(Color_Blue) | ||||
| 				draw_filled_circle(bounds.max.x, bounds.max.y, 3, 24) | ||||
| 				shape_enqueued = true | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if len(text.str) > 0 && style.font.key != 0 { | ||||
| 			draw_text_string_pos_extent_zoomed( text.str, default_font, font_size, computed.text_pos, cam, style.text_color ) | ||||
| 			text_enqueued = true | ||||
| 		} | ||||
| 	when UI_Render_Method == .Layers { | ||||
| 		render_list := array_to_slice( ui.render_list ) | ||||
| 		render_ui_via_box_list( render_list, & cam ) | ||||
| 	} | ||||
| 	when UI_Render_Method == .Depth_First | ||||
| 	{ | ||||
| 		render_ui_via_box_tree( ui.root, & cam ) | ||||
| 	} | ||||
|  | ||||
| 	if shape_enqueued do render_flush_gp() | ||||
| 	if text_enqueued  do render_text_layer() | ||||
| } | ||||
|  | ||||
| render_mode_screenspace :: proc() | ||||
| @@ -441,69 +270,19 @@ render_screen_ui :: proc() | ||||
| 	text_enqueued  : b32 = false | ||||
| 	shape_enqueued : b32 = false | ||||
|  | ||||
| 	render_list := array_to_slice( ui.render_list ) | ||||
| 	for entry, id in render_list | ||||
| 	{ | ||||
| 		if entry.layer_signal | ||||
| 		{ | ||||
| 			profile("render ui layer") | ||||
| 			render_flush_gp() | ||||
| 			if text_enqueued do render_text_layer() | ||||
| 			continue | ||||
| 		} | ||||
| 		using entry | ||||
|  | ||||
| 		profile("enqueue box") | ||||
|  | ||||
| 		GP_Render: | ||||
| 		{ | ||||
| 			profile("draw_shapes") | ||||
|  | ||||
| 			draw_rect :: proc( rect : Range2, color : RGBA8 ) { | ||||
| 				using rect | ||||
| 				render_set_color( color ) | ||||
|  | ||||
| 				size     := max - min | ||||
| 				position := min | ||||
| 				gp.draw_filled_rect( position.x, position.y, size.x, size.y ) | ||||
| 			} | ||||
|  | ||||
| 			if style.bg_color.a != 0 | ||||
| 			{ | ||||
| 				draw_rect( bounds, style.bg_color ) | ||||
| 				shape_enqueued = true | ||||
| 			} | ||||
|  | ||||
| 			if style.border_color.a != 0 && border_width > 0 { | ||||
| 				render_set_color( style.border_color ) | ||||
| 				draw_rect_border( bounds, 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) | ||||
|  | ||||
| 				render_set_color(Color_Blue) | ||||
| 				draw_filled_circle(bounds.max.x, bounds.max.y, 3, 24) | ||||
| 				shape_enqueued = true | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if len(text.str) > 0 && style.font.key != 0 { | ||||
| 			draw_text_string_pos_extent( text.str, default_font, font_size, computed.text_pos, style.text_color ) | ||||
| 			text_enqueued = true | ||||
| 		} | ||||
| 	when UI_Render_Method == .Layers { | ||||
| 		render_list := array_to_slice( ui.render_list ) | ||||
| 		render_ui_via_box_list( render_list ) | ||||
| 	} | ||||
| 	when UI_Render_Method == .Depth_First | ||||
| 	{ | ||||
| 		render_ui_via_box_tree( ui.root ) | ||||
| 	} | ||||
|  | ||||
| 	if shape_enqueued do render_flush_gp() | ||||
| 	if text_enqueued  do render_text_layer() | ||||
| } | ||||
|  | ||||
| render_text_layer :: proc() | ||||
| { | ||||
| 	profile("VEFontCache: render text layer") | ||||
| 	// profile("VEFontCache: render text layer") | ||||
|  | ||||
| 	Bindings    :: gfx.Bindings | ||||
| 	Range       :: gfx.Range | ||||
| @@ -681,3 +460,254 @@ render_text_layer :: proc() | ||||
| 		gfx.end_pass() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| render_ui_via_box_tree :: proc( root : ^UI_Box, cam : ^Camera = nil ) | ||||
| { | ||||
| 	debug        := get_state().debug | ||||
| 	default_font := get_state().default_font | ||||
|  | ||||
| 	for box := root.first; box != nil; box = ui_box_tranverse_next_depth_based( box ) | ||||
| 	{ | ||||
| 		text_enqueued  : b32 = false | ||||
| 		shape_enqueued : b32 = false | ||||
|  | ||||
| 		border_width := box.layout.border_width | ||||
| 		computed     := box.computed | ||||
| 		font_size    := box.layout.font_size | ||||
| 		style        := box.style | ||||
| 		text         := box.text | ||||
|  | ||||
| 		using computed | ||||
|  | ||||
| 		// profile("enqueue box") | ||||
|  | ||||
| 		GP_Render: | ||||
| 		{ | ||||
| 			// profile("draw_shapes") | ||||
| 			if style.bg_color.a != 0 | ||||
| 			{ | ||||
| 				draw_rect( bounds, style.bg_color ) | ||||
| 				shape_enqueued = true | ||||
| 			} | ||||
|  | ||||
| 			if style.border_color.a != 0 && border_width > 0 { | ||||
| 				render_set_color( style.border_color ) | ||||
| 				draw_rect_border( bounds, 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) | ||||
|  | ||||
| 				render_set_color(Color_Blue) | ||||
| 				draw_filled_circle(bounds.max.x, bounds.max.y, 3, 24) | ||||
| 				shape_enqueued = true | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		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 do render_flush_gp() | ||||
| 		if text_enqueued  do render_text_layer() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| render_ui_via_box_list :: proc( render_list : []UI_RenderBoxInfo, cam : ^Camera = nil ) | ||||
| { | ||||
| 	debug        := get_state().debug | ||||
| 	default_font := get_state().default_font | ||||
|  | ||||
| 	text_enqueued  : b32 = false | ||||
| 	shape_enqueued : b32 = false | ||||
|  | ||||
| 	for entry, id in render_list | ||||
| 	{ | ||||
| 		already_passed_signal := id > 0 && render_list[ id - 1 ].layer_signal | ||||
| 		if !already_passed_signal && entry.layer_signal | ||||
| 		{ | ||||
| 			profile("render ui layer") | ||||
| 			render_flush_gp() | ||||
| 			if text_enqueued do render_text_layer() | ||||
| 			continue | ||||
| 		} | ||||
| 		using entry | ||||
|  | ||||
| 		profile("enqueue box") | ||||
|  | ||||
| 		GP_Render: | ||||
| 		{ | ||||
| 			// profile("draw_shapes") | ||||
| 			if style.bg_color.a != 0 | ||||
| 			{ | ||||
| 				draw_rect( bounds, style.bg_color ) | ||||
| 				shape_enqueued = true | ||||
| 			} | ||||
|  | ||||
| 			if style.border_color.a != 0 && border_width > 0 { | ||||
| 				render_set_color( style.border_color ) | ||||
| 				draw_rect_border( bounds, 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) | ||||
|  | ||||
| 				render_set_color(Color_Blue) | ||||
| 				draw_filled_circle(bounds.max.x, bounds.max.y, 3, 24) | ||||
| 				shape_enqueued = true | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		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 do render_flush_gp() | ||||
| 	if text_enqueued  do render_text_layer() | ||||
| } | ||||
|  | ||||
| #region("Helpers") | ||||
|  | ||||
| draw_filled_circle :: proc(x, y, radius: f32, edges: int) | ||||
| { | ||||
| 	if edges < 3 do return // Need at least 3 edges to form a shape | ||||
|  | ||||
| 	triangles     := make([]gp.Triangle, edges) | ||||
| 	center        := gp.Point{x, y} | ||||
| 	edge_quotient := 1 / f32(edges) | ||||
| 	angle_factor  := 2 * math.PI * edge_quotient | ||||
| 	for edge_id in 0..< edges | ||||
| 	{ | ||||
| 			angle1 := f32(edge_id   ) * angle_factor | ||||
| 			angle2 := f32(edge_id +1) * angle_factor | ||||
|  | ||||
| 			p1 := gp.Point{ | ||||
| 					x + radius * math.cos(angle1), | ||||
| 					y + radius * math.sin(angle1), | ||||
| 			} | ||||
| 			p2 := gp.Point{ | ||||
| 					x + radius * math.cos(angle2), | ||||
| 					y + radius * math.sin(angle2), | ||||
| 			} | ||||
| 			triangles[edge_id] = gp.Triangle{center, p1, p2} | ||||
| 	} | ||||
|  | ||||
| 	gp.draw_filled_triangles(raw_data(triangles), u32(len(triangles))) | ||||
| } | ||||
|  | ||||
| draw_rect :: proc( rect : Range2, color : RGBA8 ) { | ||||
| 	using rect | ||||
| 	render_set_color( color ) | ||||
|  | ||||
| 	size     := max - min | ||||
| 	position := min | ||||
| 	gp.draw_filled_rect( position.x, position.y, size.x, size.y ) | ||||
| } | ||||
|  | ||||
| draw_rect_border :: proc( rect : Range2, border_width: f32) | ||||
| { | ||||
| 	rect_size    := rect.max - rect.min | ||||
| 	border_width := lalg.min(border_width, min(rect_size.x, rect_size.y) * 0.5) | ||||
|  | ||||
| 	top    := gp.Rect{ rect.min.x,                rect.min.y,                rect_size.x,                    border_width } | ||||
| 	bottom := gp.Rect{ rect.min.x,                rect.max.y - border_width, rect_size.x,                    border_width } | ||||
| 	left   := gp.Rect{ rect.min.x,                rect.min.y + border_width, border_width, rect_size.y - 2 * border_width } | ||||
| 	right  := gp.Rect{ rect.max.x - border_width, rect.min.y + border_width, border_width, rect_size.y - 2 * border_width } | ||||
|  | ||||
| 	borders := []gp.Rect{ top, bottom, left, right } | ||||
| 	gp.draw_filled_rects( raw_data(borders), u32(len(borders)) ) | ||||
| } | ||||
|  | ||||
| // Draw text using a string and normalized render coordinates | ||||
| draw_text_string_pos_norm :: proc( content : string, id : FontID, size : f32, pos : Vec2, color := Color_White, scale : f32 = 1.0 ) | ||||
| { | ||||
| 	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, size ) | ||||
| 	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} * scale ) | ||||
| 	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 ) | ||||
| { | ||||
| 	// profile(#procedure) | ||||
| 	state          := get_state(); using state | ||||
| 	screen_size    := app_window.extent * 2 | ||||
| 	render_pos     := screen_to_render_pos(pos) | ||||
| 	normalized_pos := render_pos * (1.0 / screen_size) | ||||
| 	draw_text_string_pos_norm( content, id, size, normalized_pos, color ) | ||||
| } | ||||
|  | ||||
| draw_text_string_pos_extent_zoomed :: proc( content : string, id : FontID, size : f32, pos : Vec2, cam : Camera, color := Color_White ) | ||||
| { | ||||
| 	// profile(#procedure) | ||||
| 	cam_offset := Vec2 { | ||||
| 		cam.position.x, | ||||
| 		cam.position.y, | ||||
| 	} | ||||
|  | ||||
| 	pos_offset := pos + cam_offset * cam.zoom | ||||
|  | ||||
| 	cam_zoom_ratio := 1 / cam.zoom | ||||
|  | ||||
| 	state          := get_state(); using state | ||||
| 	screen_size    := app_window.extent * 2 | ||||
| 	render_pos     := screen_to_render_pos(pos_offset) | ||||
| 	normalized_pos := render_pos * (1.0 / screen_size) | ||||
| 	draw_text_string_pos_norm( content, id, size * cam.zoom, normalized_pos, color, cam.zoom ) | ||||
| } | ||||
|  | ||||
| // TODO(Ed): Eventually the workspace will need a viewport for drawing text | ||||
|  | ||||
| render_flush_gp :: #force_inline proc() | ||||
| { | ||||
| 	gfx.begin_pass( gfx.Pass { action = get_state().render_data.pass_actions.empty_action, swapchain = sokol_glue.swapchain() }) | ||||
| 	gp.flush() | ||||
| 	gfx.end_pass() | ||||
| } | ||||
|  | ||||
| @(deferred_none=gp.reset_transform) | ||||
| render_set_camera :: #force_inline proc( cam : Camera ) | ||||
| { | ||||
| 	gp.translate( cam.position.x * cam.zoom, cam.position.y * cam.zoom ) | ||||
| 	gp.scale( cam.zoom, cam.zoom ) | ||||
| } | ||||
|  | ||||
| render_set_color :: #force_inline proc( color : RGBA8 ) { | ||||
| 	color := normalize_rgba8(color); | ||||
| 	gp.set_color( color.r, color.g, color.b, color.a ) | ||||
| } | ||||
|  | ||||
| render_set_view_space :: #force_inline proc( extent : Extents2 ) | ||||
| { | ||||
| 	size := extent * 2 | ||||
| 	gp.viewport(0, 0, i32(size.x), i32(size.y)) | ||||
| 	gp.project( -extent.x, extent.x, extent.y, -extent.y ) | ||||
| } | ||||
|  | ||||
| #endregion("Helpers") | ||||
|   | ||||
| @@ -167,6 +167,7 @@ update :: proc( delta_time : f64 ) -> b32 | ||||
| 		config.cam_max_zoom = 30 | ||||
| 		config.cam_zoom_sensitivity_digital = 0.04 | ||||
| 		// config.cam_min_zoom = 0.04 | ||||
| 		config.cam_zoom_sensitivity_smooth = 0.02 | ||||
| 		config.cam_zoom_mode = .Smooth | ||||
| 		switch config.cam_zoom_mode | ||||
| 		{ | ||||
|   | ||||
| @@ -8,7 +8,7 @@ import sokol_glue "thirdparty:sokol/glue" | ||||
|  | ||||
|  | ||||
| Font_Provider_Use_Freetype :: false | ||||
| Font_Largest_Px_Size       :: 72 | ||||
| Font_Largest_Px_Size       :: 132 | ||||
| Font_Size_Interval         :: 2 | ||||
|  | ||||
| Font_Default            :: FontID { 0, "" } | ||||
| @@ -217,8 +217,8 @@ font_provider_startup :: proc() | ||||
| 			}) | ||||
|  | ||||
| 			glyph_rt_sampler = sokol_gfx.make_sampler( SamplerDescription { | ||||
| 				min_filter    = Filter.NEAREST, | ||||
| 				mag_filter    = Filter.NEAREST, | ||||
| 				min_filter    = Filter.LINEAR, | ||||
| 				mag_filter    = Filter.LINEAR, | ||||
| 				mipmap_filter = Filter.NONE, | ||||
| 				wrap_u        = .CLAMP_TO_EDGE, | ||||
| 				wrap_v        = .CLAMP_TO_EDGE, | ||||
| @@ -354,8 +354,8 @@ font_provider_startup :: proc() | ||||
| 			verify( sokol_gfx.query_image_state(atlas_rt_depth) < ResourceState.FAILED, "Failed to make atlas_rt_depth") | ||||
|  | ||||
| 			atlas_rt_sampler = sokol_gfx.make_sampler( SamplerDescription { | ||||
| 				min_filter    = Filter.NEAREST, | ||||
| 				mag_filter    = Filter.NEAREST, | ||||
| 				min_filter    = Filter.LINEAR, | ||||
| 				mag_filter    = Filter.LINEAR, | ||||
| 				mipmap_filter = Filter.NONE, | ||||
| 				wrap_u        = .CLAMP_TO_EDGE, | ||||
| 				wrap_v        = .CLAMP_TO_EDGE, | ||||
|   | ||||
| @@ -84,6 +84,13 @@ UI_RenderBoxInfo :: struct { | ||||
| 	layer_signal   : b32, | ||||
| } | ||||
|  | ||||
| UI_RenderMethod :: enum u32 { | ||||
| 	Depth_First, | ||||
| 	Layers, | ||||
| } | ||||
|  | ||||
| UI_Render_Method :: UI_RenderMethod.Depth_First | ||||
|  | ||||
| // TODO(Ed): Rename to UI_Context | ||||
| UI_State :: struct { | ||||
| 	// TODO(Ed) : Use these? | ||||
| @@ -96,6 +103,7 @@ UI_State :: struct { | ||||
| 	prev_cache : ^HMapZPL( UI_Box ), | ||||
| 	curr_cache : ^HMapZPL( UI_Box ), | ||||
|  | ||||
| 	// 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), | ||||
| @@ -214,186 +222,191 @@ ui_graph_build_end :: proc( ui : ^UI_State ) | ||||
| 			computed.content    = computed.bounds | ||||
| 		} | ||||
|  | ||||
|  | ||||
| 		// Auto-layout and initial render_queue generation | ||||
| 		render_queue := array_to_slice(ui.render_queue) | ||||
| 		for current := root.first; current != nil; current = ui_box_tranverse_next( current ) | ||||
| 		for current := root.first; current != nil; current = ui_box_tranverse_next_depth_based( current ) | ||||
| 		{ | ||||
| 			if ! current.computed.fresh { | ||||
| 				ui_box_compute_layout( current ) | ||||
| 			} | ||||
|  | ||||
| 			// 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 UI_Render_Method == .Layers | ||||
| 			{ | ||||
|  | ||||
| 			if entry.layer_id >= i32(ui.render_queue.num) { | ||||
| 				append( & ui.render_queue, UI_RenderLayer {}) | ||||
| 				render_queue = array_to_slice(ui.render_queue) | ||||
| 			} | ||||
| 				// 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, | ||||
| 				} | ||||
|  | ||||
| 			// else if layer.last == nil { | ||||
| 			// 	layer.first.next = entry | ||||
| 			// 	entry.prev       = layer.first | ||||
| 			// 	layer.last       = entry | ||||
| 			// } | ||||
| 				if entry.layer_id >= i32(ui.render_queue.num) { | ||||
| 					append( & ui.render_queue, UI_RenderLayer {}) | ||||
| 					render_queue = array_to_slice(ui.render_queue) | ||||
| 				} | ||||
|  | ||||
| 			// 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 ) | ||||
| 				// else if layer.last == nil { | ||||
| 				// 	layer.first.next = entry | ||||
| 				// 	entry.prev       = layer.first | ||||
| 				// 	layer.last       = entry | ||||
| 				// } | ||||
|  | ||||
| 			// 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 | ||||
| 				// push_back to next layer | ||||
| 				layer := & render_queue[entry.layer_id] | ||||
| 				if layer.first == nil { | ||||
| 					layer.first = entry | ||||
| 					layer.last  = entry | ||||
| 				} | ||||
| 				else { | ||||
| 					parent_entry.last = entry | ||||
| 					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 ) | ||||
| 				} | ||||
| 				// dll_fl_append( parent_entry, entry ) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// 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 | ||||
| 		when UI_Render_Method == .Layers | ||||
| 		{ | ||||
| 			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 | ||||
| 			// 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 | ||||
| 			{ | ||||
| 				for neighbor := entry.next; neighbor != nil; neighbor = neighbor.next | ||||
| 				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 | ||||
| 				{ | ||||
| 					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 ) | ||||
| 					for neighbor := entry.next; neighbor != nil; neighbor = neighbor.next | ||||
| 					{ | ||||
| 						pop_layer      := render_queue[child.layer_id] | ||||
| 						child.layer_id += 1 | ||||
| 						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 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] | ||||
| 					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 | ||||
|  | ||||
| 						// pop from current layer | ||||
| 						if child == pop_layer.first { | ||||
| 							pop_layer.first = nil | ||||
| 						} | ||||
| 						if child == pop_layer.last { | ||||
| 							pop_layer.last = child.prev | ||||
| 						} | ||||
| 				// 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] | ||||
|  | ||||
| 						// 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 | ||||
| 						} | ||||
| 					// pop entry from layer | ||||
| 					prev      := entry.prev | ||||
| 					prev.next  = entry.next | ||||
| 					if entry == pop_layer.last { | ||||
| 						pop_layer.last = prev | ||||
| 					} | ||||
|  | ||||
| 						// else if push_layer.last == nil { | ||||
| 						// 	push_layer.first.next = child | ||||
| 						// 	child.prev            = push_layer.first | ||||
| 						// 	push_layer.last       = child | ||||
| 						// } | ||||
| 					// 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 | ||||
| 					// } | ||||
|  | ||||
| 					} // 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 | ||||
| 					// 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_queue := array_to_slice(ui.render_queue) | ||||
| 	render_list  := array_to_slice(ui.render_list) | ||||
|  | ||||
| 	get_state().ui_context = nil | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -118,7 +118,7 @@ ui_prev_cached_box :: #force_inline proc( box : ^UI_Box ) -> ^UI_Box { return hm | ||||
|  | ||||
| // TODO(Ed): Rename to ui_box_tranverse_view_next | ||||
| // Traveral pritorizes immeidate children | ||||
| ui_box_tranverse_next :: proc "contextless" ( box : ^ UI_Box, bypass_intersection_test := false ) -> (^ UI_Box) | ||||
| ui_box_tranverse_next_depth_based :: proc "contextless" ( box : ^ UI_Box, bypass_intersection_test := false ) -> (^ UI_Box) | ||||
| { | ||||
| 	using state := get_state() | ||||
| 	// If current has children, do them first | ||||
|   | ||||
| @@ -181,7 +181,7 @@ ui_compute_children_bounding_area :: proc ( box : ^UI_Box ) | ||||
|  | ||||
| ui_box_compute_layout_children :: proc( box : ^UI_Box ) | ||||
| { | ||||
| 	for current := box.first; current != nil && current.prev != box; current = ui_box_tranverse_next( current ) | ||||
| 	for current := box.first; current != nil && current.prev != box; current = ui_box_tranverse_next_depth_based( current ) | ||||
| 	{ | ||||
| 		if current == box do return | ||||
| 		if current.computed.fresh do continue | ||||
|   | ||||
		Reference in New Issue
	
	Block a user