wip : trying to get layered text rendering working
This commit is contained in:
@ -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")
|
||||
}
|
||||
|
@ -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 )
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user