WIP: attempt to improve text rendering

This commit is contained in:
2024-12-29 10:20:06 -05:00
parent 292d1b58b5
commit 7eab6f9a7f
16 changed files with 145 additions and 69 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+4 -3
View File
@@ -27,7 +27,8 @@ Atlas :: struct {
width : i32,
height : i32,
glyph_padding : i32,
glyph_padding : i32, // Padding to add to bounds_<width/height>_scaled for choosing which atlas region.
glyph_over_scalar : f32, // Scalar to apply to bounds_<width/height>_scaled for choosing which atlas region.
region_a : Atlas_Region,
region_b : Atlas_Region,
@@ -98,8 +99,8 @@ decide_codepoint_region :: proc(ctx : ^Context, entry : ^Entry, glyph_index : Gl
glyph_buffer := & ctx.glyph_buffer
glyph_padding := f32( atlas.glyph_padding ) * 2
bounds_width_scaled := i32(bounds_width * entry.size_scale + glyph_padding)
bounds_height_scaled := i32(bounds_height * entry.size_scale + glyph_padding)
bounds_width_scaled := i32(bounds_width * entry.size_scale * atlas.glyph_over_scalar + glyph_padding)
bounds_height_scaled := i32(bounds_height * entry.size_scale * atlas.glyph_over_scalar + glyph_padding)
// Use a lookup table for faster region selection
region_lookup := [4]struct { kind: Atlas_Region_Kind, region: ^Atlas_Region } {
+5 -4
View File
@@ -174,9 +174,9 @@ cache_glyph_freetype :: proc(ctx: ^Context, font: Font_ID, glyph_index: Glyph, e
start_index: int = 0
for contour_index in 0 ..< int(outline.n_contours)
{
end_index := int(contours[contour_index]) + 1
prev_point: Vec2
first_point: Vec2
end_index := int(contours[contour_index]) + 1
prev_point : Vec2
first_point : Vec2
for idx := start_index; idx < end_index; idx += 1
{
@@ -766,7 +766,8 @@ merge_draw_list :: proc( dst, src : ^Draw_List )
}
}
optimize_draw_list :: proc(draw_list: ^Draw_List, call_offset: int) {
optimize_draw_list :: proc(draw_list: ^Draw_List, call_offset: int)
{
// profile(#procedure)
assert(draw_list != nil)
+4 -7
View File
@@ -76,7 +76,7 @@ shape_text_uncached :: proc( ctx : ^Context, font : Font_ID, text_utf8 : string,
if ctx.text_shape_adv
{
shaper_shape_from_text( & ctx.shaper_ctx, & entry.shaper_info, output, text_utf8, ascent_i32, descent_i32, line_gap_i32, entry.size, entry.size_scale )
shaper_shape_from_text( & ctx.shaper_ctx, ctx.snap_shape_pos, & entry.shaper_info, output, text_utf8, ascent_i32, descent_i32, line_gap_i32, entry.size, entry.size_scale )
return
}
else
@@ -107,21 +107,18 @@ shape_text_uncached :: proc( ctx : ^Context, font : Font_ID, text_utf8 : string,
continue
}
if abs( entry.size ) <= ADVANCE_SNAP_SMALLFONT_SIZE {
position.x = position.x
position.x = ceil(position.x)
}
append( & output.glyphs, parser_find_glyph_index( & entry.parser_info, codepoint ))
advance, _ := parser_get_codepoint_horizontal_metrics( & entry.parser_info, codepoint )
if ctx.snap_shape_pos do position.x = ceil(position.x)
append( & output.positions, Vec2 {
position.x,
position.y
floor(position.x),
floor(position.y)
})
position.x += f32(advance) * entry.size_scale
if ctx.snap_shape_pos do position.x = ceil(position.x)
prev_codepoint = codepoint
}
+18 -9
View File
@@ -13,6 +13,9 @@ Shaper_Kind :: enum {
Shaper_Context :: struct {
hb_buffer : harfbuzz.Buffer,
snap_glyph_pos : b32,
adv_snap_small_font_threshold : u32,
}
Shaper_Info :: struct {
@@ -51,7 +54,7 @@ shaper_unload_font :: proc( ctx : ^Shaper_Info )
if blob != nil do harfbuzz.blob_destroy( blob )
}
shaper_shape_from_text :: proc( ctx : ^Shaper_Context, info : ^Shaper_Info, output :^Shaped_Text, text_utf8 : string,
shaper_shape_from_text :: proc( ctx : ^Shaper_Context, snap_shape_pos : b32, info : ^Shaper_Info, output :^Shaped_Text, text_utf8 : string,
ascent, descent, line_gap : i32, size, size_scale : f32 )
{
// profile(#procedure)
@@ -71,7 +74,8 @@ shaper_shape_from_text :: proc( ctx : ^Shaper_Context, info : ^Shaper_Info, outp
position, vertical_position : f32
shape_run :: proc( buffer : harfbuzz.Buffer, script : harfbuzz.Script, font : harfbuzz.Font, output : ^Shaped_Text,
position, vertical_position, max_line_width: ^f32, line_count: ^int,
ascent, descent, line_gap, size, size_scale: f32 )
ascent, descent, line_gap, size, size_scale: f32,
snap_shape_pos : b32 )
{
// Set script and direction. We use the system's default langauge.
// script = HB_SCRIPT_LATIN
@@ -101,7 +105,7 @@ shaper_shape_from_text :: proc( ctx : ^Shaper_Context, info : ^Shaper_Info, outp
(max_line_width^) = max( max_line_width^, position^ )
(position^) = 0.0
(vertical_position^) -= line_height
(vertical_position^) = cast(f32) i32(vertical_position^ + 0.5)
(vertical_position^) = vertical_position^
(line_count^) += 1
continue
}
@@ -112,13 +116,18 @@ shaper_shape_from_text :: proc( ctx : ^Shaper_Context, info : ^Shaper_Info, outp
append( & output.glyphs, glyph_id )
pos := position^
pos := position^
v_pos := vertical_position^
offset_x := f32(hb_gposition.x_offset) * size_scale
offset_y := f32(hb_gposition.y_offset) * size_scale
append( & output.positions, Vec2 { cast(f32) i32( pos + offset_x + 0.5 ),
v_pos + offset_y,
})
pos += offset_x
v_pos += offset_y
if snap_shape_pos {
pos = floor(pos)
v_pos = floor(v_pos)
}
append( & output.positions, Vec2 {pos, v_pos})
(position^) += f32(hb_gposition.x_advance) * size_scale
(vertical_position^) += f32(hb_gposition.y_advance) * size_scale
@@ -151,13 +160,13 @@ shaper_shape_from_text :: proc( ctx : ^Shaper_Context, info : ^Shaper_Info, outp
}
// End current run since we've encountered a script change.
shape_run( ctx.hb_buffer, current_script, info.font, output, & position, & vertical_position, & max_line_width, & line_count, ascent, descent, line_gap, size, size_scale )
shape_run( ctx.hb_buffer, current_script, info.font, output, & position, & vertical_position, & max_line_width, & line_count, ascent, descent, line_gap, size, size_scale, snap_shape_pos )
harfbuzz.buffer_add( ctx.hb_buffer, hb_codepoint, codepoint == '\n' ? 1 : 0 )
current_script = script
}
// End the last run if needed
shape_run( ctx.hb_buffer, current_script, info.font, output, & position, & vertical_position, & max_line_width, & line_count, ascent, descent, line_gap, size, size_scale )
shape_run( ctx.hb_buffer, current_script, info.font, output, & position, & vertical_position, & max_line_width, & line_count, ascent, descent, line_gap, size, size_scale, snap_shape_pos )
// Set the final size
output.size.x = max_line_width
+17 -17
View File
@@ -7,7 +7,7 @@ package vefontcache
import "base:runtime"
ADVANCE_SNAP_SMALLFONT_SIZE :: 0
ADVANCE_SNAP_SMALLFONT_SIZE :: 10
Font_ID :: distinct i64
Glyph :: distinct i32
@@ -44,7 +44,6 @@ Context :: struct {
snap_width : f32,
snap_height : f32,
colour : Colour,
cursor_pos : Vec2,
@@ -75,9 +74,10 @@ Init_Atlas_Region_Params :: struct {
}
Init_Atlas_Params :: struct {
width : u32,
height : u32,
glyph_padding : u32,
width : u32,
height : u32,
glyph_padding : u32, // Padding to add to bounds_<width/height>_scaled for choosing which atlas region.
glyph_over_scalar : f32, // Scalar to apply to bounds_<width/height>_scaled for choosing which atlas region.
region_a : Init_Atlas_Region_Params,
region_b : Init_Atlas_Region_Params,
@@ -86,9 +86,10 @@ Init_Atlas_Params :: struct {
}
Init_Atlas_Params_Default :: Init_Atlas_Params {
width = 4096,
height = 2048,
glyph_padding = 4,
width = 4096,
height = 2048,
glyph_padding = 1,
glyph_over_scalar = 1,
region_a = {
width = 32,
@@ -115,7 +116,7 @@ Init_Glyph_Draw_Params :: struct {
}
Init_Glyph_Draw_Params_Default :: Init_Glyph_Draw_Params {
over_sample = { 16, 16 },
over_sample = { 4, 4 },
buffer_batch = 4,
draw_padding = Init_Atlas_Params_Default.glyph_padding,
}
@@ -138,7 +139,7 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType,
shape_cache_params := Init_Shape_Cache_Params_Default,
use_advanced_text_shaper : b32 = true,
snap_shape_position : b32 = true,
default_curve_quality : u32 = 6,
default_curve_quality : u32 = 3,
entires_reserve : u32 = 512,
temp_path_reserve : u32 = 1024,
temp_codepoint_seen_reserve : u32 = 2048,
@@ -202,9 +203,10 @@ startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType,
init_atlas_region( & atlas.region_c, atlas_params, atlas_params.region_c, { 4, 1}, 512 )
init_atlas_region( & atlas.region_d, atlas_params, atlas_params.region_d, { 2, 1}, 256 )
atlas.width = i32(atlas_params.width)
atlas.height = i32(atlas_params.height)
atlas.glyph_padding = i32(atlas_params.glyph_padding)
atlas.width = i32(atlas_params.width)
atlas.height = i32(atlas_params.height)
atlas.glyph_padding = i32(atlas_params.glyph_padding)
atlas.glyph_over_scalar = atlas_params.glyph_over_scalar
atlas.region_a.offset = {0, 0}
atlas.region_b.offset.x = 0
@@ -385,10 +387,8 @@ load_font :: proc( ctx : ^Context, label : string, data : []byte, size_px : f32,
parser_info = parser_load_font( & parser_ctx, label, data )
shaper_info = shaper_load_font( & shaper_ctx, label, data, transmute(rawptr) id )
size = size_px
size_scale = size_px < 0.0 ? \
parser_scale_for_pixel_height( & parser_info, -size_px ) \
: parser_scale_for_mapping_em_to_pixels( & parser_info, size_px )
size = size_px
size_scale = parser_scale( & parser_info, size )
if glyph_curve_quality == 0 {
curve_quality = f32(ctx.default_curve_quality)
+60 -12
View File
@@ -18,6 +18,7 @@ UI_ScreenState :: struct
using widget : UI_Widget,
}
},
settings_menu : struct
{
container : UI_Widget,
@@ -29,11 +30,12 @@ UI_ScreenState :: struct
zoom_digital_sensitivity_input : UI_TextInputBox,
zoom_scroll_delta_scale_input : UI_TextInputBox,
font_size_canvas_scalar_input : UI_TextInputBox,
cfg_drop_down : UI_DropDown,
zoom_mode_drop_down : UI_DropDown,
pos, size, min_size : Vec2,
is_open : b32,
is_maximized : b32,
font_size_screen_scalar_input : UI_TextInputBox,
cfg_drop_down : UI_DropDown,
zoom_mode_drop_down : UI_DropDown,
pos, size, min_size : Vec2,
is_open : b32,
is_maximized : b32,
},
}
@@ -166,23 +168,39 @@ ui_screen_settings_menu :: proc( captures : rawptr = nil ) -> ( should_raise : b
Construct_Container:
{
scope(theme_window_panel)
container = ui_widget("Settings Menu", {}); {
container = ui_widget("Settings Menu", {});
if ! settings_menu.is_maximized {
using container
layout.flags = { .Fixed_Width, .Fixed_Height, .Fixed_Position_X, .Fixed_Position_Y, .Origin_At_Anchor_Center }
layout.flags = {
// .Size_To_Content,
.Fixed_Width, .Fixed_Height,
.Fixed_Position_X, .Fixed_Position_Y,
.Origin_At_Anchor_Center
}
layout.pos = pos
layout.size = range2( size, {})
}
if settings_menu.is_maximized {
else {
using container
layout.flags = {.Origin_At_Anchor_Center }
layout.pos = {}
}
old_vbox := ui_box_from_key(prev_cache, ui_key_from_string("Settings Menu: VBox"))
if old_vbox != nil {
vbox_size := size_range2(old_vbox.computed.bounds)
larger_than_size := vbox_size.x > size.x || vbox_size.y > size.y
if ! settings_menu.is_maximized && larger_than_size
{
size = vbox_size
container.layout.size = range2(size, {})
}
}
should_raise |= ui_resizable_handles( & container, & pos, & size)
}
ui_parent(container)
ui_parent_push(container)
vbox := ui_vbox_begin( .Top_To_Bottom, "Settings Menu: VBox", {.Mouse_Clickable}, compute_layout = true)
vbox := ui_vbox_begin( .Top_To_Bottom, "Settings Menu: VBox", {.Mouse_Clickable}, compute_layout = true )
{
should_raise |= b32(vbox.active)
ui_parent(vbox)
@@ -261,7 +279,6 @@ ui_screen_settings_menu :: proc( captures : rawptr = nil ) -> ( should_raise : b
}
}
Engine_Refresh_Hz:
{
scope(theme_table_row(is_even = false))
@@ -559,7 +576,7 @@ ui_screen_settings_menu :: proc( captures : rawptr = nil ) -> ( should_raise : b
Cam_Zoom_Scroll_Delta_Scale:
{
ui_settings_entry_inputbox( & zoom_scroll_delta_scale_input, false, "settings_menu.cam_zoom_scroll_delta_scale", str_intern("Camera: Zoom Scroll Delta Scale"),
ui_settings_entry_inputbox( & zoom_scroll_delta_scale_input, true, "settings_menu.cam_zoom_scroll_delta_scale", str_intern("Camera: Zoom Scroll Delta Scale"),
UI_TextInput_Policy {
digits_only = true,
disallow_leading_zeros = false,
@@ -586,6 +603,35 @@ ui_screen_settings_menu :: proc( captures : rawptr = nil ) -> ( should_raise : b
}
}
Font_Size_Screen_Scalar:
{
ui_settings_entry_inputbox( & font_size_screen_scalar_input, false, "settings_menu.font_size_screen_scalar", str_intern("Font: Size Screen Scalar"),
UI_TextInput_Policy {
digits_only = true,
disallow_leading_zeros = false,
disallow_decimal = false,
digit_min = 0.01,
digit_max = 9999,
max_length = 5,
}
)
using font_size_screen_scalar_input
if was_active
{
value, success := parse_f32(to_string(array_to_slice(input_str)))
if success {
value = clamp(value, 0.001, 9999.0)
config.font_size_screen_scalar = value
}
}
else
{
clear( input_str )
append( & input_str, to_runes(str_fmt("%v", config.font_size_screen_scalar)))
}
}
Font_Size_Canvas_Scalar:
{
ui_settings_entry_inputbox( & font_size_canvas_scalar_input, false, "settings_menu.font_size_canvas_scalar", str_intern("Font: Size Canvas Scalar"),
@@ -617,5 +663,7 @@ ui_screen_settings_menu :: proc( captures : rawptr = nil ) -> ( should_raise : b
}
}
ui_vbox_end(vbox, compute_layout = false )
ui_parent_pop() // container
return
}
+6 -1
View File
@@ -161,6 +161,7 @@ AppConfig :: struct {
color_theme : AppColorTheme,
font_size_screen_scalar : f32,
font_size_canvas_scalar : f32,
}
@@ -202,7 +203,7 @@ State :: struct {
default_slab_policy : SlabPolicy,
persistent_slab : Slab,
frame_slab : Slab,
transient_slab : Slab, // TODO(Ed): This needs to be recreated per transient wipe
transient_slab : Slab,
transinet_clear_lock : b32, // Pravents auto-free of transient at designated intervals
transient_clear_time : f32, // Time in seconds for the usual period to clear transient
transient_clear_elapsed : f32, // Time since last clear
@@ -249,8 +250,12 @@ State :: struct {
font_arial_unicode_ms : FontID,
font_firacode : FontID,
font_fira_cousine : FontID,
font_noto_sans : FontID,
font_open_sans : FontID,
font_neodgm_code : FontID,
font_rec_mono_linear : FontID,
font_roboto_regular : FontID,
font_squidgy_slimes : FontID,
font_rec_mono_semicasual_reg : FontID,
default_font : FontID,
+21 -8
View File
@@ -88,9 +88,8 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem
push( policy_ptr, SlabSizeClass { 16 * Megabyte, 16 * Megabyte, alignment })
push( policy_ptr, SlabSizeClass { 32 * Megabyte, 32 * Megabyte, alignment })
push( policy_ptr, SlabSizeClass { 64 * Megabyte, 64 * Megabyte, alignment })
push( policy_ptr, SlabSizeClass { 128 * Megabyte, 128 * Megabyte, alignment })
push( policy_ptr, SlabSizeClass { 256 * Megabyte, 256 * Megabyte, alignment })
push( policy_ptr, SlabSizeClass { 512 * Megabyte, 512 * Megabyte, alignment })
push( policy_ptr, SlabSizeClass { 128 * Megabyte, 128 * Megabyte, alignment })
// Anything above 128 meg needs to have its own setup looked into.
alloc_error : AllocatorError
persistent_slab, alloc_error = slab_init( policy_ptr, allocator = persistent_allocator(), dbg_name = Persistent_Slab_DBG_Name, enable_mem_tracking = false )
@@ -153,6 +152,7 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem
color_theme = App_Thm_Dusk
font_size_screen_scalar = 1.0
font_size_canvas_scalar = 1.0
}
@@ -271,8 +271,11 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem
// path_squidgy_slimes := strings.concatenate( { Path_Assets, "Squidgy Slimes.ttf" } )
// font_squidgy_slimes = font_load( path_squidgy_slimes, 32.0, "Squidgy_Slime" )
path_firacode := strings.concatenate( { Path_Assets, "FiraCode-Regular.ttf" } )
font_firacode = font_load( path_firacode, 16.0, "FiraCode" )
// path_firacode := strings.concatenate( { Path_Assets, "FiraCode-Regular.ttf" } )
// font_firacode = font_load( path_firacode, 16.0, "FiraCode" )
path_fira_cousine := strings.concatenate( { Path_Assets, "FiraCousine-Regular.ttf" } )
font_fira_cousine = font_load( path_fira_cousine, 16.0, "Fira Cousine" )
// path_open_sans := strings.concatenate( { Path_Assets, "OpenSans-Regular.ttf" } )
// font_open_sans = font_load( path_open_sans, 16.0, "OpenSans" )
@@ -280,10 +283,22 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem
// path_noto_sans := strings.concatenate( { Path_Assets, "NotoSans-Regular.ttf" } )
// font_noto_sans = font_load( path_noto_sans, 16.0, "NotoSans" )
// path_neodgm_code := strings.concatenate( { Path_Assets, "neodgm_code.ttf"} )
// font_neodgm_code = font_load( path_neodgm_code, 32.0, "NeoDunggeunmo Code" )
// path_rec_mono_linear := strings.concatenate( { Path_Assets, "RecMonoLinear-Regular-1.084.ttf" })
// font_rec_mono_linear = font_load( path_rec_mono_linear, 16.0, "RecMonoLinear Regular" )
// path_roboto_regular := strings.concatenate( { Path_Assets, "Roboto-Regular.ttf"} )
// font_roboto_regular = font_load( path_roboto_regular, 32.0, "Roboto Regular" )
// path_arial_unicode_ms := strings.concatenate( { Path_Assets, "Arial Unicode MS.ttf" } )
// font_arial_unicode_ms = font_load( path_arial_unicode_ms, 16.0, "Arial_Unicode_MS" )
default_font = font_firacode
// path_arial_unicode_ms := strings.concatenate( { Path_Assets, "Arial Unicode MS.ttf" } )
// font_arial_unicode_ms = font_load( path_arial_unicode_ms, 16.0, "Arial_Unicode_MS" )
default_font = font_fira_cousine
log( "Default font loaded" )
}
@@ -545,8 +560,6 @@ tick_frametime :: #force_inline proc( client_tick : ^time.Tick, host_delta_time_
// profile("Client tick timing processing")
// config.engine_refresh_hz = uint(monitor_refresh_hz)
// config.engine_refresh_hz = 10
frametime_target_ms = 1.0 / f64(config.engine_refresh_hz) * S_To_MS
sub_ms_granularity_required := frametime_target_ms <= Frametime_High_Perf_Threshold_MS
+6 -4
View File
@@ -57,8 +57,8 @@ render_mode_2d_workspace :: proc( screen_extent : Vec2, cam : Camera, input : In
screen_size := screen_extent * 2
// TODO(Ed): Eventually will be the viewport extents
// ve.configure_snap( ve_ctx, u32(screen_size.x), u32(screen_size.y) )
ve.configure_snap( ve_ctx, 0, 0 )
ve.configure_snap( ve_ctx, u32(screen_size.x), u32(screen_size.y) )
// ve.configure_snap( ve_ctx, 0, 0 )
Render_Debug:
{
@@ -801,11 +801,13 @@ draw_text_string_pos_norm :: proc( content : string, id : FontID, size : f32, po
width := app_window.extent.x * 2
height := app_window.extent.y * 2
ve_id, resolved_size := font_provider_resolve_draw_id( id, size )
// TODO(Ed): Review doing double scaling on the text...
ve_id, resolved_size := font_provider_resolve_draw_id( id, size * config.font_size_screen_scalar )
color_norm := normalize_rgba8(color)
ve.set_colour( & font_provider_ctx.ve_ctx, color_norm )
ve.draw_text( & font_provider_ctx.ve_ctx, ve_id, content, pos, Vec2{1 / width, 1 / height} * scale )
ve.draw_text( & font_provider_ctx.ve_ctx, ve_id, content, pos, Vec2{1 / width, 1 / height} * scale * (1/config.font_size_screen_scalar) )
return
}
+2 -2
View File
@@ -778,8 +778,8 @@ ui_vbox_end_pop_parent :: proc( vbox : UI_VBox ) {
}
@(deferred_out = ui_vbox_end_pop_parent)
ui_vbox :: #force_inline proc( direction : UI_LayoutDirectionY, label : string, flags : UI_BoxFlags = {} ) -> (vbox : UI_VBox) {
vbox = ui_vbox_begin(direction, label, flags)
ui_vbox :: #force_inline proc( direction : UI_LayoutDirectionY, label : string, flags : UI_BoxFlags = {}, compute_layout := false ) -> (vbox : UI_VBox) {
vbox = ui_vbox_begin(direction, label, flags, compute_layout )
ui_parent_push(vbox.widget)
return
}
+2 -2
View File
@@ -278,9 +278,9 @@ push-location $path_root
$build_args += $flag_use_separate_modules
$build_args += $flag_thread_count + $CoreCount_Physical
# $build_args += $flag_optimize_none
# $build_args += $flag_optimize_minimal
$build_args += $flag_optimize_minimal
# $build_args += $flag_optimize_speed
$build_args += $falg_optimize_aggressive
# $build_args += $falg_optimize_aggressive
$build_args += $flag_debug
$build_args += $flag_pdb_name + $pdb
$build_args += $flag_subsystem + 'windows'