mirror of
https://github.com/Ed94/VEFontCache-Odin.git
synced 2025-08-06 06:52:44 -07:00
I need to review the convention I'm using for the "view" or at least how I interpret these coordinate spaces so its inutitive for the interface. At the end of the day, the draw_list should be in normalized space, however how it gets digested to that state needs to be better documented or made more explicit in its transformation from the usual user calls.
725 lines
29 KiB
Odin
725 lines
29 KiB
Odin
package sokol_demo
|
||
|
||
import "base:runtime"
|
||
import "core:path/filepath"
|
||
file_name_from_path :: filepath.short_stem
|
||
import "core:fmt"
|
||
import "core:math"
|
||
import "core:math/rand"
|
||
import "core:mem"
|
||
import "core:os"
|
||
import "core:strings"
|
||
|
||
import ve "../../vefontcache"
|
||
import ve_sokol "backend:sokol"
|
||
import app "thirdparty:sokol/app"
|
||
import gfx "thirdparty:sokol/gfx"
|
||
import glue "thirdparty:sokol/glue"
|
||
import slog "thirdparty:sokol/log"
|
||
|
||
Vec2 :: ve.Vec2
|
||
|
||
RGBA8 :: struct { r, g, b, a : u8 }
|
||
RGBAN :: [4]f32
|
||
|
||
normalize_rgba8 :: #force_inline proc( color : RGBA8 ) -> RGBAN {
|
||
quotient : f32 = 1.0 / 255
|
||
|
||
result := RGBAN {
|
||
f32(color.r) * quotient,
|
||
f32(color.g) * quotient,
|
||
f32(color.b) * quotient,
|
||
f32(color.a) * quotient,
|
||
}
|
||
return result
|
||
}
|
||
|
||
COLOR_BLUE :: RGBA8 { 90, 90, 230, 255 }
|
||
COLOR_RED :: RGBA8 { 230, 90, 90, 255 }
|
||
COLOR_WHITE :: RGBA8 { 255, 255, 255, 255 }
|
||
|
||
FONT_LARGEST_PIXEL_SIZE :: 400
|
||
FONT_SIZE_INTERVAL :: 2
|
||
|
||
FONT_DEFAULT :: Font_ID { "" }
|
||
FONT_DEFAULT_SIZE :: 12.0
|
||
|
||
FONT_LOAD_USE_DEFAULT_SIZE :: -1
|
||
FONT_LOAD_GEN_ID :: ""
|
||
|
||
// Working directory assumed to be the build folder
|
||
PATH_FONTS :: "../fonts/"
|
||
|
||
OVER_SAMPLE_ZOOM : f32 : 2.0 // Adjust this value as needed, used by draw_text_zoomed_norm
|
||
|
||
Font_ID :: struct {
|
||
label : string,
|
||
}
|
||
|
||
Font_Entry :: struct {
|
||
path_file : string,
|
||
default_size : i32,
|
||
ve_id : ve.Font_ID,
|
||
}
|
||
|
||
Demo_Context :: struct {
|
||
ve_ctx : ve.Context,
|
||
render_ctx : ve_sokol.Context,
|
||
font_ids : map[string]Font_Entry,
|
||
|
||
// Values between 1, & -1 on Y axis
|
||
mouse_scroll : Vec2,
|
||
|
||
font_firacode : Font_ID,
|
||
font_logo : Font_ID,
|
||
font_title : Font_ID,
|
||
font_print : Font_ID,
|
||
font_mono : Font_ID,
|
||
font_small : Font_ID,
|
||
font_demo_sans : Font_ID,
|
||
font_demo_serif : Font_ID,
|
||
font_demo_script : Font_ID,
|
||
font_demo_mono : Font_ID,
|
||
font_demo_chinese : Font_ID,
|
||
font_demo_japanese : Font_ID,
|
||
font_demo_korean : Font_ID,
|
||
font_demo_thai : Font_ID,
|
||
font_demo_arabic : Font_ID,
|
||
font_demo_hebrew : Font_ID,
|
||
font_demo_raincode : Font_ID,
|
||
font_demo_grid2 : Font_ID,
|
||
font_demo_grid3 : Font_ID,
|
||
|
||
screen_size : [2]f32,
|
||
}
|
||
|
||
demo_ctx : Demo_Context
|
||
|
||
font_load :: proc(path_file : string,
|
||
default_size : i32 = FONT_LOAD_USE_DEFAULT_SIZE,
|
||
desired_id : string = FONT_LOAD_GEN_ID,
|
||
curve_quality : u32 = 3,
|
||
) -> Font_ID
|
||
{
|
||
msg := fmt.println("Loading font: %v", path_file)
|
||
|
||
font_data, read_succeded : = os.read_entire_file( path_file )
|
||
assert( bool(read_succeded), fmt.tprintf("Failed to read font file for: %v", path_file) )
|
||
font_data_size := cast(i32) len(font_data)
|
||
font_firacode : Font_ID
|
||
|
||
|
||
desired_id := desired_id
|
||
if len(desired_id) == 0 {
|
||
fmt.println("desired_key not provided, using file name. Give it a proper name!")
|
||
desired_id = file_name_from_path(path_file)
|
||
}
|
||
|
||
demo_ctx.font_ids[desired_id] = Font_Entry {}
|
||
def := & demo_ctx.font_ids[desired_id]
|
||
|
||
default_size := default_size
|
||
if default_size < 0 {
|
||
default_size = FONT_DEFAULT_SIZE
|
||
}
|
||
|
||
error : ve.Load_Font_Error
|
||
def.path_file = path_file
|
||
def.default_size = default_size
|
||
def.ve_id, error = ve.load_font( & demo_ctx.ve_ctx, desired_id, font_data, curve_quality )
|
||
assert(error == .None)
|
||
|
||
fid := Font_ID { desired_id }
|
||
return fid
|
||
}
|
||
|
||
Font_Use_Default_Size :: f32(0.0)
|
||
|
||
measure_text_size :: proc( text : string, font : Font_ID, font_size := Font_Use_Default_Size, spacing : f32 ) -> Vec2
|
||
{
|
||
def := demo_ctx.font_ids[ font.label ]
|
||
measured := ve.measure_text_size( & demo_ctx.ve_ctx, def.ve_id, font_size, text )
|
||
return measured
|
||
}
|
||
|
||
get_font_vertical_metrics :: #force_inline proc ( font : Font_ID, font_size := Font_Use_Default_Size ) -> ( ascent, descent, line_gap : f32 )
|
||
{
|
||
def := demo_ctx.font_ids[ font.label ]
|
||
ascent, descent, line_gap = ve.get_font_vertical_metrics( & demo_ctx.ve_ctx, def.ve_id, font_size )
|
||
return
|
||
}
|
||
|
||
// Draw text using a string and normalized render coordinates
|
||
draw_text_string_pos_norm :: proc( content : string, font : Font_ID, size : f32, pos : Vec2, color := COLOR_WHITE, scale : f32 = 1.0 )
|
||
{
|
||
color_norm := normalize_rgba8(color)
|
||
def := demo_ctx.font_ids[ font.label ]
|
||
size := size > 2.0 ? size : f32(def.default_size)
|
||
|
||
ve.draw_text_normalized_space( & demo_ctx.ve_ctx,
|
||
def.ve_id,
|
||
size,
|
||
color_norm,
|
||
demo_ctx.screen_size,
|
||
pos,
|
||
scale,
|
||
1.0,
|
||
content
|
||
)
|
||
return
|
||
}
|
||
|
||
// Adapt the draw_text_string_pos_extent_zoomed procedure
|
||
draw_text_zoomed_norm :: proc(content : string, font : Font_ID, size : f32, pos : Vec2, zoom : f32, color := COLOR_WHITE)
|
||
{
|
||
screen_size := demo_ctx.screen_size
|
||
screen_scale := 1 / screen_size
|
||
zoom_adjust_size := size * zoom
|
||
|
||
resolved_size := size
|
||
|
||
text_scale := screen_scale
|
||
{
|
||
diff_scalar := 1 + (zoom_adjust_size - resolved_size) / resolved_size
|
||
text_scale = diff_scalar * screen_scale
|
||
text_scale.x = clamp(text_scale.x, 0, 1)
|
||
text_scale.y = clamp(text_scale.y, 0, 1)
|
||
}
|
||
|
||
color_norm := normalize_rgba8(color)
|
||
def := demo_ctx.font_ids[ font.label ]
|
||
|
||
ve.draw_text_normalized_space(& demo_ctx.ve_ctx,
|
||
def.ve_id,
|
||
resolved_size,
|
||
color_norm,
|
||
screen_size,
|
||
pos,
|
||
text_scale,
|
||
1.0,
|
||
content
|
||
)
|
||
}
|
||
|
||
sokol_app_alloc :: proc "c" ( size : uint, user_data : rawptr ) -> rawptr {
|
||
context = runtime.default_context()
|
||
block, error := mem.alloc( int(size), allocator = context.allocator )
|
||
assert(error == .None, "sokol_app allocation failed")
|
||
return block
|
||
}
|
||
|
||
sokol_app_free :: proc "c" ( data : rawptr, user_data : rawptr ) {
|
||
context = runtime.default_context()
|
||
free(data, allocator = context.allocator)
|
||
}
|
||
|
||
sokol_gfx_alloc :: proc "c" ( size : uint, user_data : rawptr ) -> rawptr {
|
||
context = runtime.default_context()
|
||
block, error := mem.alloc( int(size), allocator = context.allocator )
|
||
assert(error == .None, "sokol_gfx allocation failed")
|
||
return block
|
||
}
|
||
|
||
sokol_gfx_free :: proc "c" ( data : rawptr, user_data : rawptr ) {
|
||
context = runtime.default_context()
|
||
free(data, allocator = context.allocator )
|
||
}
|
||
|
||
|
||
init :: proc "c" ()
|
||
{
|
||
context = runtime.default_context()
|
||
desc := gfx.Desc {
|
||
buffer_pool_size = 128,
|
||
image_pool_size = 128,
|
||
sampler_pool_size = 64,
|
||
shader_pool_size = 32,
|
||
pipeline_pool_size = 64,
|
||
attachments_pool_size = 16,
|
||
uniform_buffer_size = 4 * mem.Megabyte,
|
||
max_commit_listeners = 1024,
|
||
allocator = { sokol_gfx_alloc, sokol_gfx_free, nil },
|
||
logger = { func = slog.func },
|
||
environment = glue.environment(),
|
||
}
|
||
gfx.setup(desc)
|
||
|
||
// just some debug output what backend we're running on
|
||
switch gfx.query_backend() {
|
||
case .D3D11 : fmt.println(">> using D3D11 backend")
|
||
case .GLCORE, .GLES3: fmt.println(">> using GL backend")
|
||
|
||
case .METAL_MACOS, .METAL_IOS, .METAL_SIMULATOR:
|
||
fmt.println(">> using Metal backend")
|
||
|
||
case .WGPU : fmt.println(">> using WebGPU backend")
|
||
case .DUMMY: fmt.println(">> using dummy backend")
|
||
}
|
||
|
||
glyph_draw_opts := ve.Init_Glyph_Draw_Params_Default
|
||
glyph_draw_opts.snap_glyph_height = false
|
||
|
||
shaper_opts := ve.Init_Shaper_Params_Default
|
||
shaper_opts.snap_glyph_position = false
|
||
|
||
ve.startup( & demo_ctx.ve_ctx, .STB_TrueType, allocator = context.allocator,
|
||
glyph_draw_params = glyph_draw_opts,
|
||
shaper_params = shaper_opts,
|
||
px_scalar = 1.5,
|
||
alpha_sharpen = 0.1,
|
||
)
|
||
ve_sokol.setup_gfx_objects( & demo_ctx.render_ctx, & demo_ctx.ve_ctx, vert_cap = 1024 * 1024, index_cap = 1024 * 1024 )
|
||
|
||
error : mem.Allocator_Error
|
||
demo_ctx.font_ids, error = make( map[string]Font_Entry, 256 )
|
||
assert( error == .None, "Failed to allocate demo_ctx.font_ids" )
|
||
|
||
path_sawarabi_mincho := strings.concatenate({ PATH_FONTS, "SawarabiMincho-Regular.ttf" })
|
||
path_open_sans := strings.concatenate({ PATH_FONTS, "OpenSans-Regular.ttf" })
|
||
path_noto_sans_jp := strings.concatenate({ PATH_FONTS, "NotoSansJP-Light.otf" })
|
||
path_ubuntu_mono := strings.concatenate({ PATH_FONTS, "UbuntuMono-Regular.ttf" })
|
||
path_roboto := strings.concatenate({ PATH_FONTS, "Roboto-Regular.ttf" })
|
||
path_bitter := strings.concatenate({ PATH_FONTS, "Bitter-Regular.ttf" })
|
||
path_dancing_script := strings.concatenate({ PATH_FONTS, "DancingScript-Regular.ttf" })
|
||
path_nova_mono := strings.concatenate({ PATH_FONTS, "NovaMono-Regular.ttf" })
|
||
path_noto_serif_sc := strings.concatenate({ PATH_FONTS, "NotoSerifSC-Regular.otf" })
|
||
path_nanum_pen_script := strings.concatenate({ PATH_FONTS, "NanumPenScript-Regular.ttf" })
|
||
path_krub := strings.concatenate({ PATH_FONTS, "Krub-Regular.ttf" })
|
||
path_tajawal := strings.concatenate({ PATH_FONTS, "Tajawal-Regular.ttf" })
|
||
path_david_libre := strings.concatenate({ PATH_FONTS, "DavidLibre-Regular.ttf" })
|
||
path_noto_sans_jp_reg := strings.concatenate({ PATH_FONTS, "NotoSansJP-Regular.otf" })
|
||
path_firacode := strings.concatenate({ PATH_FONTS, "FiraCode-Regular.ttf" })
|
||
|
||
demo_ctx.font_logo = font_load(path_sawarabi_mincho, 330.0, "SawarabiMincho", 6 )
|
||
demo_ctx.font_title = font_load(path_open_sans, 92.0, "OpenSans", 6 )
|
||
demo_ctx.font_print = font_load(path_noto_sans_jp, 19.0, "NotoSansJP")
|
||
demo_ctx.font_mono = font_load(path_ubuntu_mono, 21.0, "UbuntuMono")
|
||
demo_ctx.font_small = font_load(path_roboto, 10.0, "Roboto")
|
||
demo_ctx.font_demo_sans = font_load(path_open_sans, 18.0, "OpenSans")
|
||
demo_ctx.font_demo_serif = font_load(path_bitter, 18.0, "Bitter")
|
||
demo_ctx.font_demo_script = font_load(path_dancing_script, 22.0, "DancingScript")
|
||
demo_ctx.font_demo_mono = font_load(path_nova_mono, 18.0, "NovaMono")
|
||
demo_ctx.font_demo_chinese = font_load(path_noto_serif_sc, 24.0, "NotoSerifSC")
|
||
demo_ctx.font_demo_japanese = font_load(path_sawarabi_mincho, 24.0, "SawarabiMincho")
|
||
demo_ctx.font_demo_korean = font_load(path_nanum_pen_script, 36.0, "NanumPenScript")
|
||
demo_ctx.font_demo_thai = font_load(path_krub, 24.0, "Krub")
|
||
demo_ctx.font_demo_arabic = font_load(path_tajawal, 24.0, "Tajawal")
|
||
demo_ctx.font_demo_hebrew = font_load(path_david_libre, 22.0, "DavidLibre")
|
||
demo_ctx.font_demo_raincode = font_load(path_noto_sans_jp_reg, 20.0, "NotoSansJPRegular")
|
||
demo_ctx.font_demo_grid2 = font_load(path_noto_serif_sc, 54.0, "NotoSerifSC")
|
||
demo_ctx.font_demo_grid3 = font_load(path_bitter, 44.0, "Bitter")
|
||
demo_ctx.font_firacode = font_load(path_firacode, 16.0, "FiraCode", 3 )
|
||
}
|
||
|
||
event :: proc "c" (sokol_event : ^app.Event)
|
||
{
|
||
#partial switch sokol_event.type {
|
||
case .MOUSE_SCROLL:
|
||
demo_ctx.mouse_scroll = clamp(sokol_event.scroll_y, -1, 1) * -1
|
||
}
|
||
}
|
||
|
||
frame :: proc "c" ()
|
||
{
|
||
context = runtime.default_context()
|
||
|
||
demo_ctx.screen_size = { app.widthf(), app.heightf() }
|
||
|
||
pass_action : gfx.Pass_Action;
|
||
pass_action.colors[0] = { load_action = .CLEAR, clear_value = { 0.18, 0.204, 0.251, 1.0 } }
|
||
gfx.begin_pass({ action = pass_action, swapchain = glue.swapchain() })
|
||
gfx.end_pass()
|
||
{
|
||
// ve.configure_snap( & demo_ctx.ve_ctx, u32(demo_ctx.screen_size.x), u32(demo_ctx.screen_size.y) )
|
||
// ve.set_colour( & demo_ctx.ve_ctx, ve.Colour { 1.0, 1.0, 1.0, 1.0 })
|
||
|
||
// Smooth scrolling implementation
|
||
@static demo_autoscroll := false
|
||
@static current_scroll : f32 = 0.0
|
||
@static mouse_down_pos : f32 = -1.0
|
||
@static mouse_down_scroll : f32 = -1.0
|
||
@static mouse_prev_pos : f32 = 0.0
|
||
@static scroll_velocity : f32 = 0.0
|
||
|
||
frame_duration := cast(f32) app.frame_duration()
|
||
|
||
scroll_velocity += demo_ctx.mouse_scroll.y * 0.05
|
||
mouse_down_pos = -1.0
|
||
substep_dt := frame_duration / 4.0
|
||
for _ in 0 ..< 4 {
|
||
scroll_velocity *= math.exp(-3.0 * substep_dt)
|
||
current_scroll += scroll_velocity * substep_dt * 10.0
|
||
}
|
||
if demo_autoscroll {
|
||
current_scroll += 0.05 * frame_duration
|
||
}
|
||
demo_ctx.mouse_scroll = {} // Reset mouse scroll
|
||
|
||
// Clamp scroll value if needed
|
||
current_scroll = clamp(current_scroll, 0, 6.1) // Adjust max value as needed
|
||
|
||
// Frametime display
|
||
frametime_text := fmt.tprintf("Frametime %v", frame_duration)
|
||
draw_text_string_pos_norm(frametime_text, demo_ctx.font_title, 0, {0.0, 0.0}, COLOR_WHITE)
|
||
|
||
if current_scroll < 1.5 {
|
||
intro := `Ça va! Everything here is rendered using VE Font Cache, a single header-only library designed for game engines.
|
||
It aims to:
|
||
• Be fast and simple to integrate.
|
||
• Take advantage of modern GPU power.
|
||
• Be backend agnostic and easy to port to any API such as Vulkan, DirectX, OpenGL.
|
||
• Load TTF & OTF file formats directly.
|
||
• Use only runtime cache with no offline calculation.
|
||
• Render glyphs at reasonable quality at a wide range of hb_font sizes.
|
||
• Support a good amount of internationalisation. そうですね!
|
||
• Support cached text shaping with HarfBuzz with simple Latin-style fallback.
|
||
• Load and unload fonts at any time.`
|
||
|
||
draw_text_string_pos_norm("ゑ", demo_ctx.font_logo, 330, { 0.4, current_scroll }, COLOR_WHITE)
|
||
draw_text_string_pos_norm("VEFontCache Demo", demo_ctx.font_title, 92, { 0.2, current_scroll - 0.1 }, COLOR_WHITE)
|
||
draw_text_string_pos_norm(intro, demo_ctx.font_print, 19, { 0.2, current_scroll - 0.14 }, COLOR_WHITE)
|
||
}
|
||
|
||
section_start : f32 = 0.42
|
||
section_end : f32 = 2.32
|
||
if current_scroll > section_start && current_scroll < section_end {
|
||
how_it_works := `Glyphs are GPU rasterised with 16x supersampling. This method is a simplification of "Easy Scalable Text Rendering on the GPU",
|
||
by Evan Wallace, making use of XOR blending. Bézier curves are handled via brute force triangle tessellation; even 6 triangles per
|
||
curve only generates < 300 triangles, which is nothing for modern GPUs! This avoids complex frag shader for reasonable quality.
|
||
|
||
Texture atlas caching uses naïve grid placement; this wastes a lot of space but ensures interchangeable cache slots allowing for
|
||
LRU ( Least Recently Used ) caching scheme to be employed.
|
||
The hb_font atlas is a single 4k x 2k R8 texture divided into 4 regions:`
|
||
|
||
caching_strategy := ` 2k
|
||
--------------------
|
||
| | |
|
||
| A | |
|
||
| | | 2
|
||
|---------| C | k
|
||
| | |
|
||
1k | B | |
|
||
| | |
|
||
--------------------
|
||
| |
|
||
| |
|
||
| | 2
|
||
| D | k
|
||
| |
|
||
| |
|
||
| |
|
||
--------------------
|
||
|
||
Region A = 32x32 caches, 1024 glyphs
|
||
Region B = 32x64 caches, 512 glyphs
|
||
Region C = 64x64 caches, 512 glyphs
|
||
Region D = 128x128 caches, 256 glyphs`
|
||
|
||
how_it_works2 := `Region A is designed for small glyphs, Region B is for tall glyphs, Region C is for large glyphs, and Region D for huge glyphs.
|
||
Glyphs are first rendered to an intermediate 2k x 512px R8 texture. This allows for minimum 4 Region D glyphs supersampled at
|
||
4 x 4 = 16x supersampling, and 8 Region C glyphs similarly. A simple 16-tap box downsample shader is then used to blit from this
|
||
intermediate texture to the final atlas location.`
|
||
|
||
draw_text_string_pos_norm("How it works", demo_ctx.font_title, 92, { 0.2, current_scroll - (section_start + 0.06) }, COLOR_WHITE)
|
||
draw_text_string_pos_norm(how_it_works, demo_ctx.font_print, 19, { 0.2, current_scroll - (section_start + 0.1) }, COLOR_WHITE)
|
||
draw_text_string_pos_norm(caching_strategy, demo_ctx.font_mono, 21, { 0.28, current_scroll - (section_start + 0.32) }, COLOR_WHITE)
|
||
draw_text_string_pos_norm(how_it_works2, demo_ctx.font_print, 19, { 0.2, current_scroll - (section_start + 0.82) }, COLOR_WHITE)
|
||
}
|
||
|
||
// Showcase section
|
||
section_start, section_end = 1.2, 3.2
|
||
if current_scroll > section_start && current_scroll < section_end
|
||
{
|
||
font_family_test := `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
|
||
incididunt ut labore et dolore magna aliqua. Est ullamcorper eget nulla facilisi
|
||
etiam dignissim diam quis enim. Convallis convallis tellus id interdum.`
|
||
|
||
draw_text_string_pos_norm("Showcase", demo_ctx.font_title, 92, { 0.2, current_scroll - (section_start + 0.2) }, COLOR_WHITE)
|
||
draw_text_string_pos_norm("This is a showcase demonstrating different hb_font categories and languages.", demo_ctx.font_print, 19, { 0.2, current_scroll - (section_start + 0.24) }, COLOR_WHITE)
|
||
|
||
draw_text_string_pos_norm("Sans serif", demo_ctx.font_print, 19, { 0.2, current_scroll - (section_start + 0.28) }, COLOR_WHITE)
|
||
draw_text_string_pos_norm(font_family_test, demo_ctx.font_demo_sans, 18, { 0.3, current_scroll - (section_start + 0.28) }, COLOR_WHITE)
|
||
|
||
draw_text_string_pos_norm("Serif", demo_ctx.font_print, 19, { 0.2, current_scroll - (section_start + 0.36) }, COLOR_WHITE)
|
||
draw_text_string_pos_norm(font_family_test, demo_ctx.font_demo_serif, 18, { 0.3, current_scroll - (section_start + 0.36) }, COLOR_WHITE)
|
||
|
||
draw_text_string_pos_norm("Script", demo_ctx.font_print, 19, { 0.2, current_scroll - (section_start + 0.44) }, COLOR_WHITE)
|
||
draw_text_string_pos_norm(font_family_test, demo_ctx.font_demo_script, 22, { 0.3, current_scroll - (section_start + 0.44) }, COLOR_WHITE)
|
||
|
||
draw_text_string_pos_norm("Monospace", demo_ctx.font_print, 19, { 0.2, current_scroll - (section_start + 0.52) }, COLOR_WHITE)
|
||
draw_text_string_pos_norm(font_family_test, demo_ctx.font_demo_mono, 18, { 0.3, current_scroll - (section_start + 0.52) }, COLOR_WHITE)
|
||
|
||
draw_text_string_pos_norm("Small", demo_ctx.font_print, 19, { 0.2, current_scroll - (section_start + 0.60) }, COLOR_WHITE)
|
||
draw_text_string_pos_norm(font_family_test, demo_ctx.font_small, 10, { 0.3, current_scroll - (section_start + 0.60) }, COLOR_WHITE)
|
||
|
||
draw_text_string_pos_norm("Greek", demo_ctx.font_print, 19, { 0.2, current_scroll - (section_start + 0.72) }, COLOR_WHITE)
|
||
draw_text_string_pos_norm("Ήταν απλώς θέμα χρόνου.", demo_ctx.font_demo_sans, 18, { 0.3, current_scroll - (section_start + 0.72) }, COLOR_WHITE)
|
||
|
||
draw_text_string_pos_norm("Vietnamese", demo_ctx.font_print, 19, { 0.2, current_scroll - (section_start + 0.76) }, COLOR_WHITE)
|
||
draw_text_string_pos_norm("Bầu trời trong xanh thăm thẳm, không một gợn mây.", demo_ctx.font_demo_sans, 18, { 0.3, current_scroll - (section_start + 0.76) }, COLOR_WHITE)
|
||
|
||
draw_text_string_pos_norm("Thai", demo_ctx.font_print, 19, {0.2, current_scroll - (section_start + 0.80)}, COLOR_WHITE)
|
||
draw_text_string_pos_norm("การเดินทางขากลับคงจะเหงา", demo_ctx.font_demo_thai, 24, {0.3, current_scroll - (section_start + 0.80)}, COLOR_WHITE)
|
||
|
||
draw_text_string_pos_norm("Chinese", demo_ctx.font_print, 19, {0.2, current_scroll - (section_start + 0.84)}, COLOR_WHITE)
|
||
draw_text_string_pos_norm("床前明月光 疑是地上霜 举头望明月 低头思故乡", demo_ctx.font_demo_chinese, 24, {0.3, current_scroll - (section_start + 0.84)}, COLOR_WHITE)
|
||
|
||
draw_text_string_pos_norm("Japanese", demo_ctx.font_print, 19, {0.2, current_scroll - (section_start + 0.88)}, COLOR_WHITE)
|
||
draw_text_string_pos_norm("ぎょしょうとナレズシの研究 モンスーン・アジアの食事文化", demo_ctx.font_demo_japanese, 24, {0.3, current_scroll - (section_start + 0.88)}, COLOR_WHITE)
|
||
|
||
draw_text_string_pos_norm("Korean", demo_ctx.font_print, 19, {0.2, current_scroll - (section_start + 0.92)}, COLOR_WHITE)
|
||
draw_text_string_pos_norm("그들의 장비와 기구는 모두 살아 있다.", demo_ctx.font_demo_korean, 36, {0.3, current_scroll - (section_start + 0.92)}, COLOR_WHITE)
|
||
|
||
draw_text_string_pos_norm("Needs harfbuzz to work:", demo_ctx.font_print, 14, {0.2, current_scroll - (section_start + 0.96)}, COLOR_WHITE)
|
||
|
||
draw_text_string_pos_norm("Arabic", demo_ctx.font_print, 19, {0.2, current_scroll - (section_start + 1.00)}, COLOR_WHITE)
|
||
draw_text_string_pos_norm("حب السماء لا تمطر غير الأحلام.", demo_ctx.font_demo_arabic, 24, {0.3, current_scroll - (section_start + 1.00)}, COLOR_WHITE)
|
||
|
||
draw_text_string_pos_norm("Hebrew", demo_ctx.font_print, 19, {0.2, current_scroll - (section_start + 1.04)}, COLOR_WHITE)
|
||
draw_text_string_pos_norm("אז הגיע הלילה של כוכב השביט הראשון.", demo_ctx.font_demo_hebrew, 22, {0.3, current_scroll - (section_start + 1.04)}, COLOR_WHITE)
|
||
}
|
||
|
||
// Zoom Test
|
||
section_start = 2.3
|
||
section_end = section_start + 2.23
|
||
if current_scroll > section_start && current_scroll < section_end
|
||
{
|
||
zoom_text := `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
|
||
incididunt ut labore et dolore magna aliqua. Est ullamcorper eget nulla facilisi
|
||
etiam dignissim diam quis enim. Convallis convallis tellus id interdum.`
|
||
|
||
@static zoom_time: f32 = 0
|
||
zoom_time += frame_duration
|
||
zoom_duration :: 10.0 // Time for one complete zoom cycle in seconds
|
||
|
||
modified_linear_zoom :: proc( delta : f32) -> f32
|
||
{
|
||
// Adjust these values to control the time spent at min/max zoom
|
||
min_threshold :: 0.05
|
||
max_threshold :: 0.95
|
||
|
||
if delta < min_threshold do return 0
|
||
else if delta > max_threshold do return 1
|
||
else do return (delta - min_threshold) / (max_threshold - min_threshold)
|
||
}
|
||
|
||
// Calculate the current zoom
|
||
delta := (math.sin(2 * math.PI * zoom_time / zoom_duration) + 1) / 2 // Normalize sine wave to 0-1
|
||
zoom_t := modified_linear_zoom(delta)
|
||
current_zoom := math.lerp(f32(1.0), f32(20.0), zoom_t) // Zoom range from 0.5x to 50x
|
||
|
||
// Calculate positions with reduced gaps
|
||
scroll_offset := current_scroll - section_start
|
||
title_y := current_scroll - (section_start + 0.05)
|
||
zoom_info_y := current_scroll - (section_start + 0.10)
|
||
zoomed_text_y := current_scroll - (section_start + 0.30) + math.sin(zoom_time) * 0.02
|
||
|
||
draw_text_string_pos_norm("Zoom Test", demo_ctx.font_title, 92, {0.2, title_y}, COLOR_WHITE)
|
||
|
||
zoomed_text_base_size : f32 = 12.0
|
||
zoom_adjust_size := zoomed_text_base_size * current_zoom
|
||
// ve_id, resolved_size := font_resolve_draw_id( font_firacode, zoom_adjust_size * OVER_SAMPLE_ZOOM )
|
||
resolved_size := zoom_adjust_size
|
||
current_zoom_text := fmt.tprintf("Current Zoom : %.2f x\nCurrent Resolved Size: %v px", current_zoom, resolved_size )
|
||
draw_text_string_pos_norm(current_zoom_text, demo_ctx.font_firacode, 19, {0.2, zoom_info_y}, COLOR_WHITE)
|
||
|
||
// ve.configure_snap( & demo_ctx.ve_ctx, u32(0), u32(0) )
|
||
|
||
size := measure_text_size( zoom_text, demo_ctx.font_firacode, zoomed_text_base_size, 0 ) * current_zoom
|
||
x_offset := (size.x / demo_ctx.screen_size.x) * 0.5
|
||
zoomed_text_pos := Vec2 { 0.5 - x_offset, zoomed_text_y }
|
||
draw_text_zoomed_norm(zoom_text, demo_ctx.font_firacode, zoomed_text_base_size, zoomed_text_pos, current_zoom, COLOR_WHITE)
|
||
}
|
||
|
||
// Raincode Demo
|
||
section_start = 3.6
|
||
section_end = 5.4
|
||
if current_scroll > section_start && current_scroll < section_end
|
||
{
|
||
GRID_W :: 80
|
||
GRID_H :: 50
|
||
NUM_RAINDROPS :: GRID_W / 3
|
||
|
||
@static init_grid := false
|
||
@static grid : [ GRID_W * GRID_H ]int
|
||
@static grid_age : [ GRID_W * GRID_H ]f32
|
||
@static raindropsX : [ NUM_RAINDROPS ]int
|
||
@static raindropsY : [ NUM_RAINDROPS ]int
|
||
@static code_colour : RGBA8
|
||
|
||
@static codes := [?]string {
|
||
" ", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "Z", "T", "H", "E", "|", "¦", "日",
|
||
"ハ", "ミ", "ヒ", "ー", "ウ", "シ", "ナ", "モ", "ニ", "サ", "ワ", "ツ", "オ", "リ", "ア", "ホ", "テ", "マ",
|
||
"ケ", "メ", "エ", "カ", "キ", "ム", "ユ", "ラ", "セ", "ネ", "ス", "ツ", "タ", "ヌ", "ヘ", ":", "・", ".",
|
||
"\"", "=", "*", "+", "-", "<", ">", "ç", "リ", "ク", "コ", "チ", "ヤ", "ル", "ン", "C", "O", "D"
|
||
}
|
||
|
||
if !init_grid {
|
||
for idx in 0..<NUM_RAINDROPS do raindropsY[idx] = GRID_H
|
||
init_grid = true
|
||
}
|
||
|
||
@static fixed_timestep_passed : f32 = 0.0
|
||
fixed_timestep : f32 = (1.0 / 30.0)
|
||
fixed_timestep_passed += frame_duration
|
||
for fixed_timestep_passed > fixed_timestep
|
||
{
|
||
for idx in 0 ..< (GRID_W * GRID_H) do grid_age[idx] += frame_duration
|
||
for idx in 0 ..< NUM_RAINDROPS {
|
||
raindropsY[idx] += 1
|
||
if raindropsY[idx] < 0 do continue
|
||
if raindropsY[idx] >= GRID_H {
|
||
raindropsY[idx] = -5 - rand.int_max(40)
|
||
raindropsX[idx] = rand.int_max(GRID_W)
|
||
continue
|
||
}
|
||
grid [ raindropsY[idx] * GRID_W + raindropsX[idx] ] = rand.int_max(len(codes))
|
||
grid_age[ raindropsY[idx] * GRID_W + raindropsX[idx] ] = 0.0
|
||
}
|
||
fixed_timestep_passed = 0
|
||
}
|
||
|
||
// Draw grid
|
||
draw_text_string_pos_norm("Raincode demo", demo_ctx.font_title, 92, { 0.2, current_scroll - (section_start + 0.2) }, COLOR_WHITE)
|
||
for y in 0 ..< GRID_H do for x in 0 ..< GRID_W
|
||
{
|
||
pos_x := 0.2 + f32(x) * 0.007
|
||
pos_y := current_scroll - (section_start + 0.24 + f32(y) * 0.018)
|
||
age := grid_age[y * GRID_W + x]
|
||
|
||
code_colour = {255, 255, 255, 255}
|
||
if age > 0.0 {
|
||
code_colour = {
|
||
51 + 30,
|
||
77 + 30,
|
||
102 + 30,
|
||
u8(clamp((1.0 - age) * 255, 0, 255) ) }
|
||
if code_colour.a == 0 do continue
|
||
}
|
||
|
||
draw_text_string_pos_norm(codes[grid[y * GRID_W + x]], demo_ctx.font_demo_raincode, 20, {pos_x, pos_y}, code_colour)
|
||
}
|
||
|
||
// ve.set_colour(&ve_ctx, {1.0, 1.0, 1.0, 1.0})
|
||
}
|
||
|
||
// Cache pressure test
|
||
section_start = 5.3
|
||
section_end = 6.2
|
||
if current_scroll > section_start && current_scroll < section_end && true
|
||
{
|
||
GRID_W :: 30
|
||
GRID_H :: 15
|
||
GRID2_W :: 8
|
||
GRID2_H :: 2
|
||
GRID3_W :: 16
|
||
GRID3_H :: 4
|
||
|
||
@static grid : [GRID_W * GRID_H ]int
|
||
@static grid2 : [GRID2_W * GRID2_H]int
|
||
@static grid3 : [GRID3_W * GRID3_H]int
|
||
|
||
@static rotate_current : int = 0
|
||
@static fixed_timestep_passed : f32 = 0.0
|
||
|
||
fixed_timestep_passed += frame_duration
|
||
fixed_timestep := f32(1.0 / 30.0)
|
||
for fixed_timestep_passed > fixed_timestep
|
||
{
|
||
rotate_current = (rotate_current + 1) % 4
|
||
rotate_idx := 0
|
||
for & g in grid
|
||
{
|
||
if (rotate_idx % 4) != rotate_current {
|
||
rotate_idx += 1
|
||
continue
|
||
}
|
||
g = 0x4E00 + rand.int_max(0x9FFF - 0x4E00)
|
||
rotate_idx += 1
|
||
}
|
||
for & g in grid2 do g = 0x4E00 + rand.int_max(0x9FFF - 0x4E00)
|
||
for & g in grid3 do g = rand.int_max(128)
|
||
fixed_timestep_passed -= fixed_timestep
|
||
}
|
||
|
||
codepoint_to_utf8 :: proc(c: []u8, chr: int) {
|
||
if chr == 0 {
|
||
return
|
||
}
|
||
else if (0xffffff80 & chr) == 0 {
|
||
c[0] = u8(chr)
|
||
}
|
||
else if (0xfffff800 & chr) == 0 {
|
||
c[0] = 0xc0 | u8(chr >> 6)
|
||
c[1] = 0x80 | u8(chr & 0x3f)
|
||
}
|
||
else if (0xffff0000 & chr) == 0 {
|
||
c[0] = 0xe0 | u8(chr >> 12)
|
||
c[1] = 0x80 | u8((chr >> 6) & 0x3f)
|
||
c[2] = 0x80 | u8(chr & 0x3f)
|
||
}
|
||
else {
|
||
c[0] = 0xf0 | u8(chr >> 18)
|
||
c[1] = 0x80 | u8((chr >> 12) & 0x3f)
|
||
c[2] = 0x80 | u8((chr >> 6) & 0x3f)
|
||
c[3] = 0x80 | u8(chr & 0x3f)
|
||
}
|
||
}
|
||
|
||
// Draw grid
|
||
draw_text_string_pos_norm("Cache pressure test", demo_ctx.font_title, 92, {0.2, current_scroll - (section_start + 0.2)}, COLOR_WHITE)
|
||
for y in 0..< GRID_H do for x in 0 ..< GRID_W
|
||
{
|
||
posx := 0.2 + f32(x) * 0.02
|
||
posy := current_scroll - (section_start + 0.24 + f32(y) * 0.025)
|
||
c := [5]u8{}
|
||
codepoint_to_utf8(c[:], grid[ y * GRID_W + x ])
|
||
draw_text_string_pos_norm(string( c[:] ), demo_ctx.font_demo_chinese, 24, {posx, posy}, COLOR_WHITE)
|
||
}
|
||
for y in 0 ..< GRID2_H do for x in 0 ..< GRID2_W {
|
||
posx := 0.2 + f32(x) * 0.03
|
||
posy := current_scroll - (section_start + 0.66 + f32(y) * 0.052)
|
||
c := [5]u8{}
|
||
codepoint_to_utf8(c[:], grid2[ y * GRID2_W + x ])
|
||
draw_text_string_pos_norm(string( c[:] ), demo_ctx.font_demo_grid2, 54, {posx, posy}, COLOR_WHITE)
|
||
}
|
||
for y in 0 ..< GRID3_H do for x in 0 ..< GRID3_W {
|
||
posx := 0.45 + f32(x) * 0.02
|
||
posy := current_scroll - (section_start + 0.64 + f32(y) * 0.034)
|
||
c := [5]u8{}
|
||
codepoint_to_utf8( c[:], grid3[ y * GRID3_W + x ])
|
||
draw_text_string_pos_norm(string( c[:] ), demo_ctx.font_demo_grid3, 44, {posx, posy}, COLOR_WHITE)
|
||
}
|
||
}
|
||
|
||
ve_sokol.render_text_layer(demo_ctx.screen_size * 0.5, & demo_ctx.ve_ctx, demo_ctx.render_ctx)
|
||
}
|
||
|
||
gfx.commit()
|
||
ve.flush_draw_list( & demo_ctx.ve_ctx )
|
||
}
|
||
|
||
cleanup :: proc "c" () {
|
||
context = runtime.default_context()
|
||
ve.shutdown( & demo_ctx.ve_ctx )
|
||
gfx.shutdown()
|
||
}
|
||
|
||
main :: proc()
|
||
{
|
||
demo_ctx.screen_size = Vec2 { 1920, 1080 }
|
||
|
||
app.run({
|
||
init_cb = init,
|
||
event_cb = event,
|
||
frame_cb = frame,
|
||
cleanup_cb = cleanup,
|
||
width = i32(demo_ctx.screen_size.x),
|
||
height = i32(demo_ctx.screen_size.y),
|
||
window_title = "VEFonCache: Sokol Backend Demo",
|
||
icon = { sokol_default = true },
|
||
logger = { func = slog.func },
|
||
allocator = { sokol_app_alloc, sokol_app_free, nil },
|
||
})
|
||
}
|