wip : trying to get layered text rendering working

This commit is contained in:
2024-06-23 20:22:36 -04:00
parent 55b80da8e5
commit 7d41fcc335
13 changed files with 311 additions and 113 deletions

View File

@ -276,13 +276,15 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem
path_arial_unicode_ms := strings.concatenate( { Path_Assets, "Arial Unicode MS.ttf" } )
font_arial_unicode_ms = font_load( path_arial_unicode_ms, 24.0, "Arial_Unicode_MS" )
default_font = font_firacode
default_font = font_rec_mono_semicasual_reg
log( "Default font loaded" )
}
// Setup the screen ui state
if true
{
profile("screen ui")
ui_startup( & screen_ui.base, cache_allocator = persistent_slab_allocator() )
ui_floating_startup( & screen_ui.floating, 1 * Kilobyte, 1 * Kilobyte, persistent_slab_allocator(), "screen ui floating manager" )
@ -298,6 +300,7 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem
// TODO(Ed): This will eventually have to occur when the user either creates or loads a workspace. I don't know
if true
{
profile("project setup")
using project
path = str_intern("./")
name = str_intern( "First Project" )
@ -437,7 +440,7 @@ hot_reload :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_
slab_reload( frame_slab, frame_allocator())
slab_reload( transient_slab, transient_allocator())
// ui_reload( & get_state().project.workspace.ui, cache_allocator = persistent_slab_allocator() )
ui_reload( & get_state().project.workspace.ui, cache_allocator = persistent_slab_allocator() )
log("Module reloaded")
}

View File

@ -1,6 +1,7 @@
package sectr
import "core:math"
import lalg "core:math/linalg"
import "core:time"
import ve "codebase:font/VEFontCache"
@ -18,6 +19,8 @@ RenderState :: struct {
pass_actions : PassActions,
}
#region("Draw Helpers")
gp_set_color :: #force_inline proc( color : RGBA8 ) {
color := normalize_rgba8(color);
gp.set_color( color.r, color.g, color.b, color.a )
@ -26,14 +29,14 @@ gp_set_color :: #force_inline proc( color : RGBA8 ) {
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)
// defer delete(triangles)
center := gp.Point{x, y}
for i in 0..<edges {
angle1 := 2 * math.PI * f32(i) / f32(edges)
angle2 := 2 * math.PI * f32(i+1) / f32(edges)
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),
@ -43,21 +46,34 @@ draw_filled_circle :: proc(x, y, radius: f32, edges: int) {
x + radius * math.cos(angle2),
y + radius * math.sin(angle2),
}
triangles[i] = gp.Triangle{center, p1, p2}
triangles[edge_id] = gp.Triangle{center, p1, p2}
}
gp.draw_filled_triangles(raw_data(triangles), u32(len(triangles)))
}
// Draw text using a string and normalized screen coordinates
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 )
{
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 )
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 )
@ -68,18 +84,26 @@ draw_text_string_pos_norm :: proc( content : string, id : FontID, size : f32, po
// 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
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 )
}
#endregion("Draw Helpers")
render :: proc()
{
profile(#procedure)
state := get_state(); using state // TODO(Ed): Prefer passing static context to through the callstack
ve.flush_draw_list( & font_provider_data.ve_font_cache )
font_provider_data.vbuf_layer_offset = 0
font_provider_data.ibuf_layer_offset = 0
font_provider_data.calls_layer_offset = 0
clear_pass := gfx.Pass { action = render_data.pass_actions.bg_clear_black, swapchain = sokol_glue.swapchain() }
clear_pass.action.colors[0].clear_value = transmute(gfx.Color) normalize_rgba8( config.color_theme.bg )
@ -91,7 +115,6 @@ render :: proc()
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'
@ -108,21 +131,25 @@ render_mode_2d_workspace :: proc()
cam_zoom_ratio := 1.0 / cam.zoom
gp.begin( i32(screen_size.x), i32(screen_size.y) )
gp.viewport(0, 0, i32(screen_size.x), i32(screen_size.y))
gp.project( -screen_extent.x, screen_extent.x, screen_extent.y, -screen_extent.y )
Render_Reference_Dots:
{
profile("render_reference_dots (workspace)")
gp.begin( i32(screen_size.x), i32(screen_size.y) )
gp.viewport(0, 0, i32(screen_size.x), i32(screen_size.y))
gp.project( -screen_extent.x, screen_extent.x, screen_extent.y, -screen_extent.y )
gp.translate( cam.position.x * cam.zoom, cam.position.y * cam.zoom )
gp.scale( cam.zoom, cam.zoom )
gp.translate( cam.position.x * cam.zoom, cam.position.y * cam.zoom )
gp.scale( cam.zoom, cam.zoom )
gp_set_color(Color_White)
draw_filled_circle(0, 0, 4, 24)
gp_set_color(Color_White)
draw_filled_circle(0, 0, 4, 24)
// Enqueue gp pass to sokol gfx
gfx.begin_pass( gfx.Pass { action = render_data.pass_actions.empty_action, swapchain = sokol_glue.swapchain() })
gp.flush()
gp.end()
gfx.end_pass()
// Enqueue gp pass to sokol gfx
gfx.begin_pass( gfx.Pass { action = render_data.pass_actions.empty_action, swapchain = sokol_glue.swapchain() })
gp.flush()
gp.end()
gfx.end_pass()
}
}
render_mode_screenspace :: proc()
@ -143,6 +170,7 @@ render_mode_screenspace :: proc()
Render_Reference_Dots:
{
profile("render_reference_dots (screen)")
gp.begin( i32(screen_size.x), i32(screen_size.y) )
gp.viewport(0, 0, i32(screen_size.x), i32(screen_size.y))
gp.project( -screen_extent.x, screen_extent.x, screen_extent.y, -screen_extent.y )
@ -163,7 +191,6 @@ render_mode_screenspace :: proc()
gfx.end_pass()
}
debug_draw_text :: proc( content : string, pos : Vec2, size : f32, color := Color_White, font : FontID = Font_Default )
{
state := get_state(); using state
@ -194,27 +221,30 @@ render_mode_screenspace :: proc()
screen_corners := screen_get_corners()
position := screen_corners.top_left
position.x = 0
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 += 12
text_size := measure_text_size( content, default_font, 14.0, 0.0 )
debug_draw_text( content, position, 14.0 )
debug.draw_debug_text_y += text_size.y + 4
}
debug.debug_text_vis = true
if debug.debug_text_vis
{
profile("debug_text_vis")
fps_size : f32 = 14.0
fps_msg := str_fmt( "FPS: %0.2f", fps_avg)
fps_msg_width := measure_text_size( fps_msg, default_font, 12.0, 0.0 ).x
fps_msg_pos := screen_get_corners().top_right - { fps_msg_width, 0 } - { 5, 5 }
debug_draw_text( fps_msg, fps_msg_pos, 38.0, color = Color_Red )
fps_msg_size := measure_text_size( fps_msg, default_font, fps_size, 0.0 )
fps_msg_pos := screen_get_corners().top_right - { fps_msg_size.x * 2, fps_msg_size.y }
debug_draw_text( fps_msg, fps_msg_pos, fps_size, color = Color_Red )
debug_text( "Screen Width : %v", screen_size.x )
debug_text( "Screen Height: %v", screen_size.y )
debug_text( "frametime_target_ms : %f ms", frametime_target_ms )
debug_text( "frametime : %0.3f ms", frametime_delta32() )
debug_text( "frametime (work) : %0.3f ms", frametime_delta_ms )
debug_text( "frametime_last_elapsed_ms : %f ms", frametime_elapsed_ms )
if replay.mode == ReplayMode.Record {
debug_text( "Recording Input")
@ -224,7 +254,7 @@ render_mode_screenspace :: proc()
}
debug_text("Zoom Target: %v", project.workspace.zoom_target)
if true
if false
{
using input_events
@ -294,12 +324,99 @@ render_screen_ui :: proc()
profile(#procedure)
state := get_state(); using state // TODO(Ed): Prefer passing static context to through the callstack
screen_extent := app_window.extent
screen_size := app_window.extent * 2
screen_ratio := screen_size.x * ( 1.0 / screen_size.y )
ui := & screen_ui
render_list := array_to_slice( ui.render_list )
gp.begin( i32(screen_size.x), i32(screen_size.y) )
gp.viewport(0, 0, i32(screen_size.x), i32(screen_size.y))
gp.project( -screen_extent.x, screen_extent.x, screen_extent.y, -screen_extent.y )
text_enqueued : b32 = false
shape_enqueued : b32 = false
for entry, id in render_list
{
if entry.layer_signal
{
profile("render ui layer")
gfx.begin_pass( gfx.Pass { action = render_data.pass_actions.empty_action, swapchain = sokol_glue.swapchain() })
gp.flush()
gp.end()
gfx.end_pass()
if text_enqueued {
render_text_layer()
}
gp.begin( i32(screen_size.x), i32(screen_size.y) )
gp.viewport(0, 0, i32(screen_size.x), i32(screen_size.y))
gp.project( -screen_extent.x, screen_extent.x, screen_extent.y, -screen_extent.y )
continue
}
using entry
profile("enqueue box")
GP_Render:
{
profile("draw_shapes")
draw_rect :: proc( rect : Range2, color : RGBA8 ) {
using rect
gp_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 {
gp_set_color( style.border_color )
draw_rect_border( bounds, border_width )
shape_enqueued = true
}
gp_set_color(Color_Red)
draw_filled_circle(bounds.min.x, bounds.min.y, 3, 24)
shape_enqueued = true
gp_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
}
}
if shape_enqueued {
gfx.begin_pass( gfx.Pass { action = render_data.pass_actions.empty_action, swapchain = sokol_glue.swapchain() })
gp.flush()
gp.end()
gfx.end_pass()
}
if text_enqueued {
render_text_layer()
}
}
render_text_layer :: proc()
{
profile("VEFontCache: render frame")
profile("VEFontCache: render text layer")
Bindings :: gfx.Bindings
Range :: gfx.Range
@ -309,28 +426,38 @@ render_text_layer :: proc()
font_provider := state.font_provider_data
using font_provider
ve.optimize_draw_list( & ve_font_cache )
// 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)
draw_list_calls_slice := array_to_slice(draw_list.calls)
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) })
vbuf_layer_slice := draw_list_vert_slice [ vbuf_layer_offset : ]
ibuf_layer_slice := draw_list_index_slice[ ibuf_layer_offset : ]
calls_layer_slice := draw_list_calls_slice[ calls_layer_offset : ]
draw_list_call_slice := array_to_slice(draw_list.calls)
for & draw_call in array_to_slice(draw_list.calls)
gfx.append_buffer( draw_list_vbuf, Range{ raw_data(vbuf_layer_slice), cast(u64) len(vbuf_layer_slice) * size_of(ve.Vertex) })
gfx.append_buffer( draw_list_ibuf, Range{ raw_data(ibuf_layer_slice), cast(u64) len(ibuf_layer_slice) * size_of(u32) })
vbuf_layer_offset = cast(u64) len(draw_list_vert_slice)
ibuf_layer_offset = cast(u64) len(draw_list_index_slice)
calls_layer_offset = cast(u64) len(draw_list_calls_slice)
for & draw_call in calls_layer_slice
{
watch := draw_call
// profile("VEFontCache: draw call")
num_indices := draw_call.end_index - draw_call.start_index
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 {
if num_indices == 0 && ! draw_call.clear_before_draw {
continue
}
@ -366,7 +493,7 @@ render_text_layer :: proc()
// 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 {
if num_indices == 0 && ! draw_call.clear_before_draw {
continue
}
@ -407,7 +534,7 @@ render_text_layer :: proc()
case .None: fallthrough
case .Target: fallthrough
case .Target_Uncached:
if (draw_call.end_index - draw_call.start_index) == 0 && ! draw_call.clear_before_draw {
if num_indices == 0 && ! draw_call.clear_before_draw {
continue
}
@ -455,8 +582,7 @@ render_text_layer :: proc()
})
}
if (draw_call.end_index - draw_call.start_index) != 0 {
num_indices := draw_call.end_index - draw_call.start_index
if num_indices != 0 {
gfx.draw( draw_call.start_index, num_indices, 1 )
}

View File

@ -195,7 +195,7 @@ update :: proc( delta_time : f64 ) -> b32
if debug_actions.cam_mouse_pan
{
if is_within_screenspace(input.mouse.pos) {
pan_velocity := input.mouse.delta * vec2(1, -1) * ( 1 / cam.zoom )
pan_velocity := input.mouse.delta * ( 1 / cam.zoom )
cam.position += pan_velocity
}
}
@ -204,7 +204,7 @@ update :: proc( delta_time : f64 ) -> b32
// TODO(Ed): We need input buffer so that we can consume input actions based on the UI with priority
// ui_screen_tick()
ui_screen_tick()
//region WorkspaceImgui Tick
if false