improvements while working Sectr Prototype

This commit is contained in:
2024-06-28 07:52:48 -04:00
parent 7e34131884
commit d454778fd6
9 changed files with 668 additions and 434 deletions

432
draw.odin
View File

@@ -56,151 +56,109 @@ blit_quad :: proc( draw_list : ^DrawList, p0 : Vec2 = {0, 0}, p1 : Vec2 = {1, 1}
// p0.x, p0.y, p1.x, p1.y, uv0.x, uv0.y, uv1.x, uv1.y);
v_offset := cast(u32) len(draw_list.vertices)
vertex := Vertex {
{p0.x, p0.y},
uv0.x, uv0.y
quadv : [4]Vertex = {
{
{p0.x, p0.y},
uv0.x, uv0.y
},
{
{p0.x, p1.y},
uv0.x, uv1.y
},
{
{p1.x, p0.y},
uv1.x, uv0.y
},
{
{p1.x, p1.y},
uv1.x, uv1.y
}
}
append_elem( & draw_list.vertices, vertex )
vertex = Vertex {
{p0.x, p1.y},
uv0.x, uv1.y
}
append_elem( & draw_list.vertices, vertex )
vertex = Vertex {
{p1.x, p0.y},
uv1.x, uv0.y
}
append_elem( & draw_list.vertices, vertex )
vertex = Vertex {
{p1.x, p1.y},
uv1.x, uv1.y
}
append_elem( & draw_list.vertices, vertex )
append( & draw_list.vertices, ..quadv[:] )
quad_indices : []u32 = {
0, 1, 2,
2, 1, 3
}
for index : i32 = 0; index < 6; index += 1 {
append( & draw_list.indices, v_offset + quad_indices[ index ] )
0 + v_offset, 1 + v_offset, 2 + v_offset,
2 + v_offset, 1 + v_offset, 3 + v_offset
}
append( & draw_list.indices, ..quad_indices[:] )
return
}
cache_glyph :: proc( ctx : ^Context, font : FontID, glyph_index : Glyph, entry : ^Entry, bounds_0, bounds_1 : Vec2, scale, translate : Vec2 ) -> b32
cache_glyph :: proc(ctx : ^Context, font : FontID, glyph_index : Glyph, entry : ^Entry, bounds_0, bounds_1 : Vec2, scale, translate : Vec2) -> b32
{
// profile(#procedure)
if glyph_index == Glyph(0) {
// Note(Original Author): Glyph not in current hb_font
return false
}
// Retrieve the shape definition from the parser.
shape, error := parser_get_glyph_shape( & entry.parser_info, glyph_index )
assert( error == .None )
shape, error := parser_get_glyph_shape(&entry.parser_info, glyph_index)
assert(error == .None)
if len(shape) == 0 {
return false
}
if ctx.debug_print_verbose
{
log( "shape:")
for vertex in shape
{
if vertex.type == .Move {
logf("move_to %d %d", vertex.x, vertex.y )
}
else if vertex.type == .Line {
logf("line_to %d %d", vertex.x, vertex.y )
}
else if vertex.type == .Curve {
logf("curve_to %d %d through %d %d", vertex.x, vertex.y, vertex.contour_x0, vertex.contour_y0 )
}
else if vertex.type == .Cubic {
logf("cubic_to %d %d through %d %d and %d %d",
vertex.x, vertex.y,
vertex.contour_x0, vertex.contour_y0,
vertex.contour_x1, vertex.contour_y1 )
}
}
}
outside := Vec2{bounds_0.x - 21, bounds_0.y - 33}
/*
Note(Original Author):
We need a random point that is outside our shape. We simply pick something diagonally across from top-left bound corner.
Note that this outside point is scaled alongside the glyph in ve_fontcache_draw_filled_path, so we don't need to handle that here.
*/
outside := Vec2 {
bounds_0.x - 21,
bounds_0.y - 33,
}
// Note(Original Author): Figure out scaling so it fits within our box.
draw := DrawCall_Default
draw := DrawCall_Default
draw.pass = FrameBufferPass.Glyph
draw.start_index = u32(len(ctx.draw_list.indices))
// Note(Original Author);
// Draw the path using simplified version of https://medium.com/@evanwallace/easy-scalable-text-rendering-on-the-gpu-c3f4d782c5ac.
// Instead of involving fragment shader code we simply make use of modern GPU ability to crunch triangles and brute force curve definitions.
path := ctx.temp_path
clear( & path)
for edge in shape do switch edge.type
{
path := &ctx.temp_path
clear(path)
append_bezier_curve :: #force_inline proc(path: ^[dynamic]Vertex, p0, p1, p2: Vec2, quality: u32) {
step := 1.0 / f32(quality)
for index := u32(1); index <= quality; index += 1 {
alpha := f32(index) * step
append( path, Vertex { pos = eval_point_on_bezier3(p0, p1, p2, alpha) } )
}
}
append_bezier_curve_cubic :: #force_inline proc(path: ^[dynamic]Vertex, p0, p1, p2, p3: Vec2, quality: u32) {
step := 1.0 / f32(quality)
for index := u32(1); index <= quality; index += 1 {
alpha := f32(index) * step
append( path, Vertex { pos = eval_point_on_bezier4(p0, p1, p2, p3, alpha) } )
}
}
for edge in shape do #partial switch edge.type {
case .Move:
if len(path) > 0 {
draw_filled_path( & ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose )
draw_filled_path(&ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose)
clear(path)
}
clear( & path)
fallthrough
case .Line:
append( & path, Vec2{ f32(edge.x), f32(edge.y) })
append( path, Vertex { pos = Vec2 { f32(edge.x), f32(edge.y)} } )
case .Curve:
assert( len(path) > 0 )
p0 := path[ len(path) - 1 ]
assert(len(path) > 0)
p0 := path[ len(path) - 1].pos
p1 := Vec2{ f32(edge.contour_x0), f32(edge.contour_y0) }
p2 := Vec2{ f32(edge.x), f32(edge.y) }
step := 1.0 / f32(ctx.curve_quality)
alpha := step
for index := i32(0); index < i32(ctx.curve_quality); index += 1 {
append( & path, eval_point_on_bezier3( p0, p1, p2, alpha ))
alpha += step
}
append_bezier_curve( path, p0, p1, p2, ctx.curve_quality )
case .Cubic:
assert( len(path) > 0 )
p0 := path[ len(path) - 1]
assert( len(path) > 0)
p0 := path[ len(path) - 1].pos
p1 := Vec2{ f32(edge.contour_x0), f32(edge.contour_y0) }
p2 := Vec2{ f32(edge.contour_x1), f32(edge.contour_y1) }
p3 := Vec2{ f32(edge.x), f32(edge.y) }
step := 1.0 / f32(ctx.curve_quality)
alpha := step
for index := i32(0); index < i32(ctx.curve_quality); index += 1 {
append( & path, eval_point_on_bezier4( p0, p1, p2, p3, alpha ))
alpha += step
}
case .None:
assert(false, "Unknown edge type or invalid")
append_bezier_curve_cubic( path, p0, p1, p2, p3, ctx.curve_quality )
}
if len(path) > 0 {
draw_filled_path( & ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose )
draw_filled_path(&ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose)
}
// Note(Original Author): Apend the draw call
draw.end_index = cast(u32) len(ctx.draw_list.indices)
draw.end_index = u32(len(ctx.draw_list.indices))
if draw.end_index > draw.start_index {
append(& ctx.draw_list.calls, draw)
append(&ctx.draw_list.calls, draw)
}
parser_free_shape( & entry.parser_info, shape )
parser_free_shape(&entry.parser_info, shape)
return true
}
@@ -301,10 +259,9 @@ cache_glyph_to_atlas :: proc( ctx : ^Context,
glyph_buffer.batch_x += i32(gwidth_scaled_px)
screenspace_x_form( & glyph_draw_translate, & glyph_draw_scale, glyph_buffer_size )
call : DrawCall
clear_target_region : DrawCall
{
// Queue up clear on target region on atlas
using call
using clear_target_region
pass = .Atlas
region = .Ignore
start_index = cast(u32) len(glyph_buffer.clear_draw_list.indices)
@@ -314,9 +271,12 @@ cache_glyph_to_atlas :: proc( ctx : ^Context,
{ 1.0, 1.0 }, { 1.0, 1.0 } )
end_index = cast(u32) len(glyph_buffer.clear_draw_list.indices)
append( & glyph_buffer.clear_draw_list.calls, call )
}
// Queue up a blit from glyph_update_FBO to the atlas
blit_to_atlas : DrawCall
{
using blit_to_atlas
pass = .Atlas
region = .None
start_index = cast(u32) len(glyph_buffer.draw_list.indices)
@@ -325,14 +285,17 @@ cache_glyph_to_atlas :: proc( ctx : ^Context,
src_position, src_position + src_size )
end_index = cast(u32) len(glyph_buffer.draw_list.indices)
append( & glyph_buffer.draw_list.calls, call )
}
append( & glyph_buffer.clear_draw_list.calls, clear_target_region )
append( & glyph_buffer.draw_list.calls, blit_to_atlas )
// Render glyph to glyph_update_FBO
cache_glyph( ctx, font, glyph_index, entry, vec2(bounds_0), vec2(bounds_1), glyph_draw_scale, glyph_draw_translate )
}
can_batch_glyph :: #force_inline proc( ctx : ^Context, font : FontID, entry : ^Entry, glyph_index : Glyph,
// If the glyuph is found in the atlas, nothing occurs, otherwise, the glyph call is setup to catch it to the atlas
check_glyph_in_atlas :: #force_inline proc( ctx : ^Context, font : FontID, entry : ^Entry, glyph_index : Glyph,
lru_code : u64,
atlas_index : i32,
region_kind : AtlasRegionKind,
@@ -402,7 +365,7 @@ directly_draw_massive_glyph :: proc( ctx : ^Context,
// Figure out the source rect.
glyph_position := Vec2 {}
glyph_size := vec2(glyph_padding_dbl)
glyph_dst_size := glyph_size + bounds_scaled
glyph_dst_size := glyph_size + bounds_scaled
glyph_size += bounds_scaled * over_sample
// Figure out the destination rect.
@@ -415,9 +378,11 @@ directly_draw_massive_glyph :: proc( ctx : ^Context,
textspace_x_form( & glyph_position, & glyph_size, glyph_buffer_size )
// Add the glyph drawcall.
call : DrawCall
calls : [2]DrawCall
draw_to_target := & calls[0]
{
using call
using draw_to_target
pass = .Target_Uncached
colour = ctx.colour
start_index = u32(len(ctx.draw_list.indices))
@@ -427,18 +392,20 @@ directly_draw_massive_glyph :: proc( ctx : ^Context,
glyph_position, glyph_position + glyph_size )
end_index = u32(len(ctx.draw_list.indices))
append( & ctx.draw_list.calls, call )
}
// Clear glyph_update_FBO.
call.pass = .Glyph
call.start_index = 0
call.end_index = 0
call.clear_before_draw = true
append( & ctx.draw_list.calls, call )
clear_glyph_update := & calls[1]
{
// Clear glyph_update_FBO.
clear_glyph_update.pass = .Glyph
clear_glyph_update.start_index = 0
clear_glyph_update.end_index = 0
clear_glyph_update.clear_before_draw = true
}
append( & ctx.draw_list.calls, ..calls[:] )
}
draw_cached_glyph :: proc( ctx : ^Context,
draw_cached_glyph :: proc( ctx : ^Context, shaped : ^ShapedText,
entry : ^Entry,
glyph_index : Glyph,
lru_code : u64,
@@ -480,26 +447,45 @@ draw_cached_glyph :: proc( ctx : ^Context,
bounds_0_scaled := bounds_0 * entry.size_scale //- { 0.5, 0.5 }
bounds_0_scaled = ceil(bounds_0_scaled)
dst := position + bounds_0_scaled * scale
dst -= glyph_padding * scale
dst_scale := glyph_scale * scale
dst := position + (bounds_0_scaled - glyph_padding) * scale
dst_scale := glyph_scale * scale
textspace_x_form( & slot_position, & glyph_scale, atlas_size )
// Add the glyph drawcall
call := DrawCall_Default
// Shape call setup
when false
{
using call
pass = .Target
colour = ctx.colour
start_index = cast(u32) len(ctx.draw_list.indices)
call := DrawCall_Default
{
using call
pass = .Target
colour = ctx.colour
start_index = cast(u32) len(shaped.draw_list.indices)
blit_quad( & ctx.draw_list,
dst, dst + dst_scale,
slot_position, slot_position + glyph_scale )
end_index = cast(u32) len(ctx.draw_list.indices)
blit_quad( & shaped.draw_list,
dst, dst + dst_scale,
slot_position, slot_position + glyph_scale )
end_index = cast(u32) len(shaped.draw_list.indices)
}
append( & shaped.draw_list.calls, call )
}
else
{
// Add the glyph drawcall
call := DrawCall_Default
{
using call
pass = .Target
colour = ctx.colour
start_index = cast(u32) len(ctx.draw_list.indices)
blit_quad( & ctx.draw_list,
dst, dst + dst_scale,
slot_position, slot_position + glyph_scale )
end_index = cast(u32) len(ctx.draw_list.indices)
}
append( & ctx.draw_list.calls, call )
}
append( & ctx.draw_list.calls, call )
return true
}
@@ -509,7 +495,7 @@ draw_cached_glyph :: proc( ctx : ^Context,
// Note(Original Author):
// WARNING: doesn't actually append drawcall; caller is responsible for actually appending the drawcall.
// ve_fontcache_draw_filled_path
draw_filled_path :: proc( draw_list : ^DrawList, outside_point : Vec2, path : []Vec2,
draw_filled_path :: proc( draw_list : ^DrawList, outside_point : Vec2, path : []Vertex,
scale := Vec2 { 1, 1 },
translate := Vec2 { 0, 0 },
debug_print_verbose : b32 = false
@@ -519,19 +505,16 @@ draw_filled_path :: proc( draw_list : ^DrawList, outside_point : Vec2, path : []
{
log("outline_path:")
for point in path {
vec := point * scale + translate
vec := point.pos * scale + translate
logf(" %0.2f %0.2f", vec.x, vec.y )
}
}
v_offset := cast(u32) len(draw_list.vertices)
for point in path {
vertex := Vertex {
pos = point * scale + translate,
u = 0,
v = 0,
}
append( & draw_list.vertices, vertex )
point := point
point.pos = point.pos * scale + translate
append( & draw_list.vertices, point )
}
outside_vertex := cast(u32) len(draw_list.vertices)
@@ -546,42 +529,71 @@ draw_filled_path :: proc( draw_list : ^DrawList, outside_point : Vec2, path : []
for index : u32 = 1; index < cast(u32) len(path); index += 1 {
indices := & draw_list.indices
append( indices, outside_vertex )
append( indices, v_offset + index - 1 )
append( indices, v_offset + index )
to_add := [3]u32 {
outside_vertex,
v_offset + index - 1,
v_offset + index
}
append( indices, ..to_add[:] )
}
}
draw_text_batch :: proc( ctx : ^Context, entry : ^Entry, shaped : ^ShapedText,
draw_text_batch :: proc(ctx: ^Context, entry: ^Entry, shaped: ^ShapedText,
batch_start_idx, batch_end_idx : i32,
position, scale : Vec2,
snap_width, snap_height : f32 )
position, scale : Vec2,
snap_width, snap_height : f32 )
{
flush_glyph_buffer_to_atlas( ctx )
flush_glyph_buffer_to_atlas(ctx)
atlas := & ctx.atlas
atlas_size := Vec2{ f32(atlas.width), f32(atlas.height) }
glyph_padding := f32(atlas.glyph_padding)
for index := batch_start_idx; index < batch_end_idx; index += 1
{
glyph_index := shaped.glyphs[ index ]
glyph_index := shaped.glyphs[index]
if glyph_index == 0 do continue
if parser_is_glyph_empty( & entry.parser_info, glyph_index ) do continue
if glyph_index == 0 || parser_is_glyph_empty( & entry.parser_info, glyph_index) do continue
region_kind, region, over_sample := decide_codepoint_region( ctx, entry, glyph_index )
lru_code := font_glyph_lru_code(entry.id, glyph_index)
atlas_index := cast(i32) -1
region_kind, region, over_sample := decide_codepoint_region( ctx, entry, glyph_index )
lru_code := font_glyph_lru_code( entry.id, glyph_index )
atlas_index := region_kind != .E ? LRU_get( & region.state, lru_code ) : -1
bounds_0, bounds_1 := parser_get_glyph_box( & entry.parser_info, glyph_index )
vbounds_0 := vec2(bounds_0)
vbounds_1 := vec2(bounds_1)
bounds_size := Vec2 { vbounds_1.x - vbounds_0.x, vbounds_1.y - vbounds_0.y }
if region_kind != .E do atlas_index = LRU_get( & region.state, lru_code )
bounds_0, bounds_1 := parser_get_glyph_box( & entry.parser_info, glyph_index )
shaped_position := shaped.positions[index]
glyph_translate := position + shaped_position * scale
shaped_position := shaped.positions[index]
glyph_translate := position + shaped_position * scale
if region_kind == .E
{
directly_draw_massive_glyph(ctx, entry, glyph_index,
vbounds_0, vbounds_1,
bounds_size,
over_sample, glyph_translate, scale )
}
else if atlas_index != -1
{
slot_position, _ := atlas_bbox( atlas, region_kind, atlas_index )
glyph_scale := bounds_size * entry.size_scale + glyph_padding
bounds_0_scaled := ceil( vbounds_0 * entry.size_scale )
dst := glyph_translate + (bounds_0_scaled - glyph_padding) * scale
dst_scale := glyph_scale * scale
textspace_x_form( & slot_position, & glyph_scale, atlas_size )
glyph_cached := draw_cached_glyph( ctx,
entry, glyph_index,
lru_code, atlas_index,
vec2(bounds_0), vec2(bounds_1),
region_kind, region, over_sample,
glyph_translate, scale)
assert( glyph_cached == true )
call := DrawCall_Default
call.pass = .Target
call.colour = ctx.colour
call.start_index = u32(len(ctx.draw_list.indices))
blit_quad(&ctx.draw_list,
dst, dst + dst_scale,
slot_position, slot_position + glyph_scale )
call.end_index = u32(len(ctx.draw_list.indices))
append(&ctx.draw_list.calls, call)
}
}
}
@@ -594,7 +606,6 @@ draw_text_shape :: proc( ctx : ^Context,
snap_width, snap_height : f32
) -> (cursor_pos : Vec2)
{
// position := position //+ ctx.cursor_pos * scale
// profile(#procedure)
batch_start_idx : i32 = 0
for index : i32 = 0; index < cast(i32) len(shaped.glyphs); index += 1
@@ -607,9 +618,9 @@ draw_text_shape :: proc( ctx : ^Context,
atlas_index := cast(i32) -1
if region_kind != .E do atlas_index = LRU_get( & region.state, lru_code )
if can_batch_glyph( ctx, font, entry, glyph_index, lru_code, atlas_index, region_kind, region, over_sample ) do continue
if check_glyph_in_atlas( ctx, font, entry, glyph_index, lru_code, atlas_index, region_kind, region, over_sample ) do continue
// Glyph has not been catched, needs to be directly drawn.
// We can no longer directly append the shape as it has missing glyphs in the atlas
// First batch the other cached glyphs
// flush_glyph_buffer_to_atlas(ctx)
@@ -621,10 +632,10 @@ draw_text_shape :: proc( ctx : ^Context,
batch_start_idx = index
}
// flush_glyph_buffer_to_atlas(ctx)
draw_text_batch( ctx, entry, shaped, batch_start_idx, cast(i32) len(shaped.glyphs), position, scale, snap_width , snap_height )
reset_batch_codepoint_state( ctx )
cursor_pos = shaped.end_cursor_pos
cursor_pos = position + shaped.end_cursor_pos * scale
return
}
@@ -650,6 +661,34 @@ flush_glyph_buffer_to_atlas :: proc( ctx : ^Context )
}
}
// flush_glyph_buffer_to_atlas :: proc( ctx : ^Context )
// {
// // profile(#procedure)
// // Flush drawcalls to draw list
// if len(ctx.glyph_buffer.clear_draw_list.calls) > 0 {
// merge_draw_list( & ctx.draw_list, & ctx.glyph_buffer.clear_draw_list)
// clear_draw_list( & ctx.glyph_buffer.clear_draw_list)
// }
// if len(ctx.glyph_buffer.draw_list.calls) > 0 {
// merge_draw_list( & ctx.draw_list, & ctx.glyph_buffer.draw_list)
// clear_draw_list( & ctx.glyph_buffer.draw_list)
// }
// // Clear glyph_update_FBO
// if ctx.glyph_buffer.batch_x != 0
// {
// call := DrawCall {
// pass = .Glyph,
// start_index = 0,
// end_index = 0,
// clear_before_draw = true,
// }
// append( & ctx.draw_list.calls, call)
// ctx.glyph_buffer.batch_x = 0
// }
// }
// ve_fontcache_merge_drawlist
merge_draw_list :: proc( dst, src : ^DrawList )
{
@@ -677,42 +716,37 @@ merge_draw_list :: proc( dst, src : ^DrawList )
}
}
optimize_draw_list :: proc( draw_list : ^DrawList, call_offset : int )
{
optimize_draw_list :: proc(draw_list: ^DrawList, call_offset: int) {
// profile(#procedure)
assert( draw_list != nil )
assert(draw_list != nil)
write_index : int = call_offset
for index : int = 1 + call_offset; index < len(draw_list.calls); index += 1
can_merge_draw_calls :: #force_inline proc "contextless" ( a, b : ^DrawCall ) -> bool {
result := \
a.pass == b.pass &&
a.end_index == b.start_index &&
a.region == b.region &&
a.colour == b.colour &&
! b.clear_before_draw
return result
}
write_index := call_offset
for read_index := call_offset + 1; read_index < len(draw_list.calls); read_index += 1
{
assert( write_index <= index )
draw_0 := & draw_list.calls[ write_index ]
draw_1 := & draw_list.calls[ index ]
draw_current := & draw_list.calls[write_index]
draw_next := & draw_list.calls[read_index]
merge : b32 = true
if draw_0.pass != draw_1.pass do merge = false
if draw_0.end_index != draw_1.start_index do merge = false
if draw_0.region != draw_1.region do merge = false
if draw_1.clear_before_draw do merge = false
if draw_0.colour != draw_1.colour do merge = false
if merge
{
// logf("merging %v : %v %v", draw_0.pass, write_index, index )
draw_0.end_index = draw_1.end_index
draw_1.start_index = 0
draw_1.end_index = 0
if can_merge_draw_calls(draw_current, draw_next) {
draw_current.end_index = draw_next.end_index
}
else
{
// logf("can't merge %v : %v %v", draw_0.pass, write_index, index )
else {
// Move to the next write position and copy the draw call
write_index += 1
if write_index != index {
draw_2 := & draw_list.calls[ write_index ]
draw_2^ = draw_1^
if write_index != read_index {
draw_list.calls[write_index] = (draw_next^)
}
}
}
resize( & draw_list.calls, write_index + 1 )
resize( & draw_list.calls, write_index + 1)
}