mirror of
https://github.com/Ed94/VEFontCache-Odin.git
synced 2025-08-04 22:22:43 -07:00
77
.github/workflows/linux_build.yaml
vendored
Normal file
77
.github/workflows/linux_build.yaml
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
name: Linux Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
- '**'
|
||||
pull_request:
|
||||
branches:
|
||||
- '**'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Odin
|
||||
uses: laytan/setup-odin@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up environment
|
||||
run: |
|
||||
if ! command -v git &> /dev/null; then
|
||||
echo "Git not found. Installing Git..."
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y git
|
||||
else
|
||||
echo "Git is already installed."
|
||||
fi
|
||||
git --version
|
||||
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
build-essential \
|
||||
bash \
|
||||
libfreetype6-dev \
|
||||
libharfbuzz-dev \
|
||||
libgl1-mesa-dev \
|
||||
libx11-dev \
|
||||
libxcursor-dev \
|
||||
libxrandr-dev \
|
||||
libxinerama-dev \
|
||||
libxi-dev \
|
||||
libglew-dev \
|
||||
libxtst-dev \
|
||||
libasound-dev
|
||||
|
||||
make -C "/home/runner/odin/vendor/stb/src"
|
||||
|
||||
- name: Run build script
|
||||
run: |
|
||||
echo "Setting execute permissions on specific .sh files"
|
||||
chmod +x ./scripts/build_sokol_demo.sh
|
||||
chmod +x ./scripts/build_sokol_library.sh
|
||||
chmod +x ./scripts/compile_sokol_shaders.sh
|
||||
chmod +x ./scripts/clean.sh
|
||||
chmod +x ./scripts/helpers/misc.sh
|
||||
chmod +x ./scripts/helpers/odin_compiler_defs.sh
|
||||
|
||||
echo "Running build_sokol_demo.sh"
|
||||
./scripts/build_sokol_demo.sh
|
||||
shell: bash
|
||||
|
||||
- name: List build artifacts
|
||||
run: ls -R ./build || echo "build directory not found"
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: linux-build
|
||||
path: ./build/
|
||||
if-no-files-found: warn
|
2
.github/workflows/macos_build.yaml
vendored
2
.github/workflows/macos_build.yaml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
brew install harfbuzz
|
||||
brew install odin
|
||||
|
||||
make -C "/opt/homebrew/Cellar/odin/2024-10/libexec/vendor/stb/src"
|
||||
make -C "/opt/homebrew/Cellar/odin/2024-12/libexec/vendor/stb/src"
|
||||
|
||||
- name: Run build script
|
||||
run: |
|
||||
|
13
.gitignore
vendored
13
.gitignore
vendored
@@ -1,6 +1,7 @@
|
||||
build
|
||||
thirdparty
|
||||
.vscode
|
||||
# backend/sokol/render_glyph.odin
|
||||
# backend/sokol/blit_atlas.odin
|
||||
# backend/sokol/draw_text.odin
|
||||
.vscode
|
||||
ols.json
|
||||
build
|
||||
thirdparty/freetype
|
||||
thirdparty/harfbuzz
|
||||
thirdparty/sokol
|
||||
thirdparty/sokol-tools
|
||||
|
@@ -1,4 +1,4 @@
|
||||
VEFontCache Odin Port
|
||||
VEFontCache Odin
|
||||
Copyright 2024 Edward R. Gonzalez
|
||||
|
||||
This project is based on Vertex Engine GPU Font Cache
|
||||
|
75
Readme.md
75
Readme.md
@@ -1,28 +1,73 @@
|
||||
# VE Font Cache : Odin Port
|
||||
# VE Font Cache
|
||||
|
||||
https://github.com/user-attachments/assets/b74f1ec1-f980-45df-b604-d6b7d87d40ff
|
||||
Vertex Engine GPU Font Cache: A text shaping & rendering library.
|
||||
|
||||
This is a port of the [VEFontCache](https://github.com/hypernewbie/VEFontCache) library.
|
||||
This project started as a port of the [VEFontCache](https://github.com/hypernewbie/VEFontCache) library to the Odin programming language.
|
||||
While originally intended for game engines, its rendering quality and performance make it suitable for many other applications.
|
||||
|
||||
Its original purpose was for use in game engines, however its rendeirng quality and performance is more than adequate for many other applications.
|
||||
Since then, the library has been overhauled to offer higher performance, improved visual fidelity, additional features, and quality of life improvements.
|
||||
|
||||
See: [docs/Readme.md](docs/Readme.md) for the library's interface.
|
||||
Features:
|
||||
|
||||
* Simple and well documented
|
||||
* Load and unload fonts at any time
|
||||
* Almost entirely configurable and tunable at runtime
|
||||
* Full support for hot-reload
|
||||
* Clear the caches at any time
|
||||
* Robust quality of life features:
|
||||
* Snap positioning to view for better hinting
|
||||
* Tracks text layers
|
||||
* Enforce even-only font sizing (useful for linear zoom)
|
||||
* Push and pop stack for font, font_size, color, view, position, scale, and zoom
|
||||
* Basic (latin) or advanced (harfbuzz) text shaping
|
||||
* All rendering is real-time, with triangulation on the CPU, vertex rendering and texture blitting on the GPU
|
||||
* Can handle thousands of draw text calls with very large or small shapes
|
||||
* 4-Level Regioned Texture Atlas for caching rendered glyphs
|
||||
* Text shape caching
|
||||
* Glyph texture buffer for rendering text with super-sampling to downsample to the atlas or direct to target screen
|
||||
* Super-sample by a font size scalar for sharper glyphs
|
||||
* All caching backed by an optimized 32-bit LRU indexing cache
|
||||
* Provides a backend-agnostic draw list (see [backend](./backend) for usage example)
|
||||
|
||||
Upcoming:
|
||||
|
||||
* Support choosing between top-left or bottom-left coordinate convention (currently bottom-left)
|
||||
* Support for better triangulation
|
||||
* Support for triangulation method selection on a per-font basis
|
||||
* [Reference paper](https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf)
|
||||
* Better support for tuning glyph render sampling
|
||||
* Support for sub-pixel AA
|
||||
* Ability to decide AA method & degree on a per-font basis
|
||||
* Multi-threading supported job queue
|
||||
* Lift heavy-lifting portion of the library's context into a thread context
|
||||
* Synchronize threads by merging their generated layered draw list into a finished draw list for processing on the user's render thread
|
||||
* User defines how thread contexts are distributed for drawing (a basic quadrant-based selector procedure will be provided)
|
||||
|
||||
## Documentation
|
||||
|
||||
* [docs/Readme.md](docs/Readme.md) for the library's interface
|
||||
* [docs/guide_backend.md](docs/guide_backend.md) for information on implementing your own backend
|
||||
* [docs/guide_architecture.md](docs/guide_architecture.md) for an in-depth breakdown of significant design decisions and code-paths
|
||||
|
||||
For learning about text shaping & rendering see: [notes](https://github.com/Ed94/TextRendering_Notes)
|
||||
|
||||
## Building
|
||||
|
||||
See [scripts/Readme.md](scripts/Readme.md) for building examples or utilizing the provided backends.
|
||||
|
||||
Currently the scripts provided & the library itself were developed & tested on Windows. There are bash scripts for building on linux & mac.
|
||||
Currently, the scripts provided & the library itself were developed & tested on Windows. There are bash scripts for building on Linux (they build on WSL but need additional testing).
|
||||
|
||||
The library depends on freetype, harfbuzz, & stb_truetype to build.
|
||||
Note: freetype and harfbuzz could technically be gutted if the user removes their definitions, however they have not been made into a conditional compilation option (yet).
|
||||
The library depends on harfbuzz & stb_truetype to build.
|
||||
Note: harfbuzz could technically be removed if the user removes their definitions, however this hasn't been made into a conditional compilation option yet.
|
||||
|
||||
## Changes from orignal
|
||||
**NOTICE: All library dependency packages are in the "thirdparty" collection of this repository. For their codebase, the user soley has to modify that collection specification for where they would like to put these external vendor package not provided by the offical Odin vendor collections.**
|
||||
|
||||
* Font Parser & Glyph shaper are abstracted to their own warpper interface
|
||||
* ve_fontcache_loadfile not ported (ust use core:os or os2, then call load_font)
|
||||
* Macro defines have been coverted (mostly) to runtime parameters
|
||||
* Support for hot_reloading
|
||||
* Curve quality step interpolation for glyph rendering can be set on a per font basis.
|
||||
## Gallery
|
||||
|
||||

|
||||

|
||||
|
||||
https://github.com/user-attachments/assets/db8c7725-84dd-48df-9a3f-65605d3ab444
|
||||
|
||||
https://github.com/user-attachments/assets/40030308-37db-492d-a196-f830e8a39f3c
|
||||
|
||||
https://github.com/user-attachments/assets/0985246b-74f8-4d1c-82d8-053414c44aec
|
||||
|
@@ -12,12 +12,12 @@ Context :: struct {
|
||||
atlas_shader : gfx.Shader,
|
||||
screen_shader : gfx.Shader,
|
||||
|
||||
// 2k x 512, R8
|
||||
// ve.glyph_buffer.(width, height), R8
|
||||
glyph_rt_color : gfx.Image,
|
||||
glyph_rt_depth : gfx.Image,
|
||||
glyph_rt_sampler : gfx.Sampler,
|
||||
|
||||
// 4k x 2k, R8
|
||||
// ve.atlas.(width, height), R8
|
||||
atlas_rt_color : gfx.Image,
|
||||
atlas_rt_depth : gfx.Image,
|
||||
atlas_rt_sampler : gfx.Sampler,
|
||||
@@ -33,7 +33,6 @@ Context :: struct {
|
||||
|
||||
setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index_cap : u64 )
|
||||
{
|
||||
using ctx
|
||||
Attachment_Desc :: gfx.Attachment_Desc
|
||||
Blend_Factor :: gfx.Blend_Factor
|
||||
Blend_Op :: gfx.Blend_Op
|
||||
@@ -60,23 +59,23 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index
|
||||
backend := gfx.query_backend()
|
||||
app_env := glue.environment()
|
||||
|
||||
glyph_shader = gfx.make_shader(render_glyph_shader_desc(backend) )
|
||||
atlas_shader = gfx.make_shader(blit_atlas_shader_desc(backend) )
|
||||
screen_shader = gfx.make_shader(draw_text_shader_desc(backend) )
|
||||
ctx.glyph_shader = gfx.make_shader(render_glyph_shader_desc(backend) )
|
||||
ctx.atlas_shader = gfx.make_shader(ve_blit_atlas_shader_desc(backend) )
|
||||
ctx.screen_shader = gfx.make_shader(ve_draw_text_shader_desc(backend) )
|
||||
|
||||
draw_list_vbuf = gfx.make_buffer( Buffer_Desciption {
|
||||
ctx.draw_list_vbuf = gfx.make_buffer( Buffer_Desciption {
|
||||
size = cast(uint)(size_of([4]f32) * vert_cap),
|
||||
usage = Buffer_Usage.STREAM,
|
||||
type = Buffer_Type.VERTEXBUFFER,
|
||||
})
|
||||
assert( gfx.query_buffer_state( draw_list_vbuf) < Resource_State.FAILED, "Failed to make draw_list_vbuf" )
|
||||
assert( gfx.query_buffer_state( ctx.draw_list_vbuf) < Resource_State.FAILED, "Failed to make draw_list_vbuf" )
|
||||
|
||||
draw_list_ibuf = gfx.make_buffer( Buffer_Desciption {
|
||||
ctx.draw_list_ibuf = gfx.make_buffer( Buffer_Desciption {
|
||||
size = cast(uint)(size_of(u32) * index_cap),
|
||||
usage = Buffer_Usage.STREAM,
|
||||
type = Buffer_Type.INDEXBUFFER,
|
||||
})
|
||||
assert( gfx.query_buffer_state( draw_list_ibuf) < Resource_State.FAILED, "Failed to make draw_list_iubuf" )
|
||||
assert( gfx.query_buffer_state( ctx.draw_list_ibuf) < Resource_State.FAILED, "Failed to make draw_list_iubuf" )
|
||||
|
||||
Image_Filter := Filter.LINEAR
|
||||
|
||||
@@ -84,18 +83,17 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index
|
||||
{
|
||||
vs_layout : Vertex_Layout_State
|
||||
{
|
||||
using vs_layout
|
||||
attrs[ATTR_render_glyph_v_position] = Vertex_Attribute_State {
|
||||
vs_layout.attrs[ATTR_render_glyph_v_position] = Vertex_Attribute_State {
|
||||
format = Vertex_Format.FLOAT2,
|
||||
offset = 0,
|
||||
buffer_index = 0,
|
||||
}
|
||||
attrs[ATTR_render_glyph_v_texture] = Vertex_Attribute_State {
|
||||
vs_layout.attrs[ATTR_render_glyph_v_texture] = Vertex_Attribute_State {
|
||||
format = Vertex_Format.FLOAT2,
|
||||
offset = size_of(ve.Vec2),
|
||||
buffer_index = 0,
|
||||
}
|
||||
buffers[0] = Vertex_Buffer_Layout_State {
|
||||
vs_layout.buffers[0] = Vertex_Buffer_Layout_State {
|
||||
stride = size_of([4]f32),
|
||||
step_func = Vertex_Step.PER_VERTEX
|
||||
}
|
||||
@@ -115,8 +113,8 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index
|
||||
},
|
||||
}
|
||||
|
||||
glyph_pipeline = gfx.make_pipeline({
|
||||
shader = glyph_shader,
|
||||
ctx.glyph_pipeline = gfx.make_pipeline({
|
||||
shader = ctx.glyph_shader,
|
||||
layout = vs_layout,
|
||||
index_type = Vertex_Index_Type.UINT32,
|
||||
colors = {
|
||||
@@ -131,29 +129,29 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index
|
||||
cull_mode = .NONE,
|
||||
sample_count = 1,
|
||||
})
|
||||
assert( gfx.query_pipeline_state(glyph_pipeline) < Resource_State.FAILED, "Failed to make glyph_pipeline" )
|
||||
assert( gfx.query_pipeline_state(ctx.glyph_pipeline) < Resource_State.FAILED, "Failed to make glyph_pipeline" )
|
||||
}
|
||||
|
||||
// glyph_pass
|
||||
{
|
||||
glyph_rt_color = gfx.make_image( Image_Desc {
|
||||
ctx.glyph_rt_color = gfx.make_image( Image_Desc {
|
||||
type = ._2D,
|
||||
render_target = true,
|
||||
width = i32(ve_ctx.glyph_buffer.width),
|
||||
height = i32(ve_ctx.glyph_buffer.height),
|
||||
width = i32(ve_ctx.glyph_buffer.size.x),
|
||||
height = i32(ve_ctx.glyph_buffer.size.y),
|
||||
num_slices = 1,
|
||||
num_mipmaps = 1,
|
||||
usage = .IMMUTABLE,
|
||||
pixel_format = .R8,
|
||||
sample_count = 1,
|
||||
})
|
||||
assert( gfx.query_image_state(glyph_rt_color) < Resource_State.FAILED, "Failed to make glyph_pipeline" )
|
||||
assert( gfx.query_image_state(ctx.glyph_rt_color) < Resource_State.FAILED, "Failed to make glyph_pipeline" )
|
||||
|
||||
glyph_rt_depth = gfx.make_image( Image_Desc {
|
||||
ctx.glyph_rt_depth = gfx.make_image( Image_Desc {
|
||||
type = ._2D,
|
||||
render_target = true,
|
||||
width = i32(ve_ctx.glyph_buffer.width),
|
||||
height = i32(ve_ctx.glyph_buffer.height),
|
||||
width = i32(ve_ctx.glyph_buffer.size.x),
|
||||
height = i32(ve_ctx.glyph_buffer.size.y),
|
||||
num_slices = 1,
|
||||
num_mipmaps = 1,
|
||||
usage = .IMMUTABLE,
|
||||
@@ -161,22 +159,22 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index
|
||||
sample_count = 1,
|
||||
})
|
||||
|
||||
glyph_rt_sampler = gfx.make_sampler( Sampler_Description {
|
||||
ctx.glyph_rt_sampler = gfx.make_sampler( Sampler_Description {
|
||||
min_filter = Image_Filter,
|
||||
mag_filter = Image_Filter,
|
||||
mipmap_filter = Filter.NEAREST,
|
||||
wrap_u = .CLAMP_TO_EDGE,
|
||||
wrap_v = .CLAMP_TO_EDGE,
|
||||
min_lod = -1000.0,
|
||||
max_lod = 1000.0,
|
||||
min_lod = -1.0,
|
||||
max_lod = 1.0,
|
||||
border_color = Border_Color.OPAQUE_BLACK,
|
||||
compare = .NEVER,
|
||||
max_anisotropy = 1,
|
||||
})
|
||||
assert( gfx.query_sampler_state( glyph_rt_sampler) < Resource_State.FAILED, "Failed to make atlas_rt_sampler" )
|
||||
assert( gfx.query_sampler_state( ctx.glyph_rt_sampler) < Resource_State.FAILED, "Failed to make atlas_rt_sampler" )
|
||||
|
||||
color_attach := Attachment_Desc {
|
||||
image = glyph_rt_color,
|
||||
image = ctx.glyph_rt_color,
|
||||
}
|
||||
|
||||
glyph_attachments := gfx.make_attachments({
|
||||
@@ -184,7 +182,7 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index
|
||||
0 = color_attach,
|
||||
},
|
||||
depth_stencil = {
|
||||
image = glyph_rt_depth,
|
||||
image = ctx.glyph_rt_depth,
|
||||
},
|
||||
})
|
||||
assert( gfx.query_attachments_state(glyph_attachments) < Resource_State.FAILED, "Failed to make glyph_attachments" )
|
||||
@@ -209,7 +207,7 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index
|
||||
}
|
||||
}
|
||||
|
||||
glyph_pass = gfx.Pass {
|
||||
ctx.glyph_pass = gfx.Pass {
|
||||
action = glyph_action,
|
||||
attachments = glyph_attachments,
|
||||
// label =
|
||||
@@ -220,18 +218,17 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index
|
||||
{
|
||||
vs_layout : Vertex_Layout_State
|
||||
{
|
||||
using vs_layout
|
||||
attrs[ATTR_blit_atlas_v_position] = Vertex_Attribute_State {
|
||||
vs_layout.attrs[ATTR_ve_blit_atlas_v_position] = Vertex_Attribute_State {
|
||||
format = Vertex_Format.FLOAT2,
|
||||
offset = 0,
|
||||
buffer_index = 0,
|
||||
}
|
||||
attrs[ATTR_blit_atlas_v_texture] = Vertex_Attribute_State {
|
||||
vs_layout.attrs[ATTR_ve_blit_atlas_v_texture] = Vertex_Attribute_State {
|
||||
format = Vertex_Format.FLOAT2,
|
||||
offset = size_of(ve.Vec2),
|
||||
buffer_index = 0,
|
||||
}
|
||||
buffers[0] = Vertex_Buffer_Layout_State {
|
||||
vs_layout.buffers[0] = Vertex_Buffer_Layout_State {
|
||||
stride = size_of([4]f32),
|
||||
step_func = Vertex_Step.PER_VERTEX
|
||||
}
|
||||
@@ -251,8 +248,8 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index
|
||||
},
|
||||
}
|
||||
|
||||
atlas_pipeline = gfx.make_pipeline({
|
||||
shader = atlas_shader,
|
||||
ctx.atlas_pipeline = gfx.make_pipeline({
|
||||
shader = ctx.atlas_shader,
|
||||
layout = vs_layout,
|
||||
index_type = Vertex_Index_Type.UINT32,
|
||||
colors = {
|
||||
@@ -271,11 +268,11 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index
|
||||
|
||||
// atlas_pass
|
||||
{
|
||||
atlas_rt_color = gfx.make_image( Image_Desc {
|
||||
ctx.atlas_rt_color = gfx.make_image( Image_Desc {
|
||||
type = ._2D,
|
||||
render_target = true,
|
||||
width = i32(ve_ctx.atlas.width),
|
||||
height = i32(ve_ctx.atlas.height),
|
||||
width = i32(ve_ctx.atlas.size.x),
|
||||
height = i32(ve_ctx.atlas.size.y),
|
||||
num_slices = 1,
|
||||
num_mipmaps = 1,
|
||||
usage = .IMMUTABLE,
|
||||
@@ -284,37 +281,37 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index
|
||||
// TODO(Ed): Setup labels for debug tracing/logging
|
||||
// label =
|
||||
})
|
||||
assert( gfx.query_image_state(atlas_rt_color) < Resource_State.FAILED, "Failed to make atlas_rt_color")
|
||||
assert( gfx.query_image_state(ctx.atlas_rt_color) < Resource_State.FAILED, "Failed to make atlas_rt_color")
|
||||
|
||||
atlas_rt_depth = gfx.make_image( Image_Desc {
|
||||
ctx.atlas_rt_depth = gfx.make_image( Image_Desc {
|
||||
type = ._2D,
|
||||
render_target = true,
|
||||
width = i32(ve_ctx.atlas.width),
|
||||
height = i32(ve_ctx.atlas.height),
|
||||
width = i32(ve_ctx.atlas.size.x),
|
||||
height = i32(ve_ctx.atlas.size.y),
|
||||
num_slices = 1,
|
||||
num_mipmaps = 1,
|
||||
usage = .IMMUTABLE,
|
||||
pixel_format = .DEPTH,
|
||||
sample_count = 1,
|
||||
})
|
||||
assert( gfx.query_image_state(atlas_rt_depth) < Resource_State.FAILED, "Failed to make atlas_rt_depth")
|
||||
assert( gfx.query_image_state(ctx.atlas_rt_depth) < Resource_State.FAILED, "Failed to make atlas_rt_depth")
|
||||
|
||||
atlas_rt_sampler = gfx.make_sampler( Sampler_Description {
|
||||
ctx.atlas_rt_sampler = gfx.make_sampler( Sampler_Description {
|
||||
min_filter = Image_Filter,
|
||||
mag_filter = Image_Filter,
|
||||
mipmap_filter = Filter.NEAREST,
|
||||
wrap_u = .CLAMP_TO_EDGE,
|
||||
wrap_v = .CLAMP_TO_EDGE,
|
||||
min_lod = -1000.0,
|
||||
max_lod = 1000.0,
|
||||
min_lod = -1.0,
|
||||
max_lod = 1.0,
|
||||
border_color = Border_Color.OPAQUE_BLACK,
|
||||
compare = .NEVER,
|
||||
max_anisotropy = 1,
|
||||
})
|
||||
assert( gfx.query_sampler_state( atlas_rt_sampler) < Resource_State.FAILED, "Failed to make atlas_rt_sampler" )
|
||||
assert( gfx.query_sampler_state( ctx.atlas_rt_sampler) < Resource_State.FAILED, "Failed to make atlas_rt_sampler" )
|
||||
|
||||
color_attach := Attachment_Desc {
|
||||
image = atlas_rt_color,
|
||||
image = ctx.atlas_rt_color,
|
||||
}
|
||||
|
||||
atlas_attachments := gfx.make_attachments({
|
||||
@@ -322,7 +319,7 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index
|
||||
0 = color_attach,
|
||||
},
|
||||
depth_stencil = {
|
||||
image = atlas_rt_depth,
|
||||
image = ctx.atlas_rt_depth,
|
||||
},
|
||||
})
|
||||
assert( gfx.query_attachments_state(atlas_attachments) < Resource_State.FAILED, "Failed to make atlas_attachments")
|
||||
@@ -347,7 +344,7 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index
|
||||
}
|
||||
}
|
||||
|
||||
atlas_pass = gfx.Pass {
|
||||
ctx.atlas_pass = gfx.Pass {
|
||||
action = atlas_action,
|
||||
attachments = atlas_attachments,
|
||||
}
|
||||
@@ -357,18 +354,17 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index
|
||||
{
|
||||
vs_layout : Vertex_Layout_State
|
||||
{
|
||||
using vs_layout
|
||||
attrs[ATTR_draw_text_v_position] = Vertex_Attribute_State {
|
||||
vs_layout.attrs[ATTR_ve_draw_text_v_position] = Vertex_Attribute_State {
|
||||
format = Vertex_Format.FLOAT2,
|
||||
offset = 0,
|
||||
buffer_index = 0,
|
||||
}
|
||||
attrs[ATTR_draw_text_v_texture] = Vertex_Attribute_State {
|
||||
vs_layout.attrs[ATTR_ve_draw_text_v_texture] = Vertex_Attribute_State {
|
||||
format = Vertex_Format.FLOAT2,
|
||||
offset = size_of(ve.Vec2),
|
||||
buffer_index = 0,
|
||||
}
|
||||
buffers[0] = Vertex_Buffer_Layout_State {
|
||||
vs_layout.buffers[0] = Vertex_Buffer_Layout_State {
|
||||
stride = size_of([4]f32),
|
||||
step_func = Vertex_Step.PER_VERTEX
|
||||
}
|
||||
@@ -388,8 +384,8 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index
|
||||
},
|
||||
}
|
||||
|
||||
screen_pipeline = gfx.make_pipeline({
|
||||
shader = screen_shader,
|
||||
ctx.screen_pipeline = gfx.make_pipeline({
|
||||
shader = ctx.screen_shader,
|
||||
layout = vs_layout,
|
||||
index_type = Vertex_Index_Type.UINT32,
|
||||
colors = {
|
||||
@@ -404,7 +400,7 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index
|
||||
},
|
||||
cull_mode = .NONE,
|
||||
})
|
||||
assert( gfx.query_pipeline_state(screen_pipeline) < Resource_State.FAILED, "Failed to make screen_pipeline" )
|
||||
assert( gfx.query_pipeline_state(ctx.screen_pipeline) < Resource_State.FAILED, "Failed to make screen_pipeline" )
|
||||
}
|
||||
|
||||
// screen_pass
|
||||
@@ -439,7 +435,7 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index
|
||||
}
|
||||
}
|
||||
|
||||
screen_pass = gfx.Pass {
|
||||
ctx.screen_pass = gfx.Pass {
|
||||
action = screen_action,
|
||||
}
|
||||
}
|
||||
@@ -448,8 +444,6 @@ setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index
|
||||
render_text_layer :: proc( screen_extent : ve.Vec2, ve_ctx : ^ve.Context, ctx : Context )
|
||||
{
|
||||
// profile("VEFontCache: render text layer")
|
||||
using ctx
|
||||
|
||||
Bindings :: gfx.Bindings
|
||||
Range :: gfx.Range
|
||||
Shader_Stage :: gfx.Shader_Stage
|
||||
@@ -459,8 +453,8 @@ render_text_layer :: proc( screen_extent : ve.Vec2, ve_ctx : ^ve.Context, ctx :
|
||||
vbuf_ve_range := Range{ raw_data(vbuf_layer_slice), cast(uint) len(vbuf_layer_slice) * size_of(ve.Vertex) }
|
||||
ibuf_ve_range := Range{ raw_data(ibuf_layer_slice), cast(uint) len(ibuf_layer_slice) * size_of(u32) }
|
||||
|
||||
gfx.append_buffer( draw_list_vbuf, vbuf_ve_range )
|
||||
gfx.append_buffer( draw_list_ibuf, ibuf_ve_range )
|
||||
gfx.append_buffer( ctx.draw_list_vbuf, vbuf_ve_range )
|
||||
gfx.append_buffer( ctx.draw_list_ibuf, ibuf_ve_range )
|
||||
|
||||
ve.flush_draw_list_layer( ve_ctx )
|
||||
|
||||
@@ -484,10 +478,10 @@ render_text_layer :: proc( screen_extent : ve.Vec2, ve_ctx : ^ve.Context, ctx :
|
||||
continue
|
||||
}
|
||||
|
||||
width := ve_ctx.glyph_buffer.width
|
||||
height := ve_ctx.glyph_buffer.height
|
||||
width := ve_ctx.glyph_buffer.size.x
|
||||
height := ve_ctx.glyph_buffer.size.y
|
||||
|
||||
pass := glyph_pass
|
||||
pass := ctx.glyph_pass
|
||||
if draw_call.clear_before_draw {
|
||||
pass.action.colors[0].load_action = .CLEAR
|
||||
pass.action.colors[0].clear_value.a = 1.0
|
||||
@@ -497,16 +491,16 @@ render_text_layer :: proc( screen_extent : ve.Vec2, ve_ctx : ^ve.Context, ctx :
|
||||
gfx.apply_viewport( 0,0, width, height, origin_top_left = true )
|
||||
gfx.apply_scissor_rect( 0,0, width, height, origin_top_left = true )
|
||||
|
||||
gfx.apply_pipeline( glyph_pipeline )
|
||||
gfx.apply_pipeline( ctx.glyph_pipeline )
|
||||
|
||||
bindings := Bindings {
|
||||
vertex_buffers = {
|
||||
0 = draw_list_vbuf,
|
||||
0 = ctx.draw_list_vbuf,
|
||||
},
|
||||
vertex_buffer_offsets = {
|
||||
0 = 0,
|
||||
},
|
||||
index_buffer = draw_list_ibuf,
|
||||
index_buffer = ctx.draw_list_ibuf,
|
||||
index_buffer_offset = 0,
|
||||
}
|
||||
gfx.apply_bindings( bindings )
|
||||
@@ -519,10 +513,10 @@ render_text_layer :: proc( screen_extent : ve.Vec2, ve_ctx : ^ve.Context, ctx :
|
||||
continue
|
||||
}
|
||||
|
||||
width := ve_ctx.atlas.width
|
||||
height := ve_ctx.atlas.height
|
||||
width := ve_ctx.atlas.size.x
|
||||
height := ve_ctx.atlas.size.y
|
||||
|
||||
pass := atlas_pass
|
||||
pass := ctx.atlas_pass
|
||||
if draw_call.clear_before_draw {
|
||||
pass.action.colors[0].load_action = .CLEAR
|
||||
pass.action.colors[0].clear_value.a = 1.0
|
||||
@@ -532,25 +526,29 @@ render_text_layer :: proc( screen_extent : ve.Vec2, ve_ctx : ^ve.Context, ctx :
|
||||
gfx.apply_viewport( 0, 0, width, height, origin_top_left = true )
|
||||
gfx.apply_scissor_rect( 0, 0, width, height, origin_top_left = true )
|
||||
|
||||
gfx.apply_pipeline( atlas_pipeline )
|
||||
gfx.apply_pipeline( ctx.atlas_pipeline )
|
||||
|
||||
fs_uniform := Blit_Atlas_Fs_Params { region = cast(i32) draw_call.region }
|
||||
gfx.apply_uniforms( UB_blit_atlas_fs_params, Range { & fs_uniform, size_of(Blit_Atlas_Fs_Params) })
|
||||
fs_uniform := Ve_Blit_Atlas_Fs_Params {
|
||||
glyph_buffer_size = ve.vec2(ve_ctx.glyph_buffer.size),
|
||||
over_sample = ve_ctx.glyph_buffer.over_sample.x,
|
||||
region = cast(i32) draw_call.region,
|
||||
}
|
||||
gfx.apply_uniforms( UB_ve_blit_atlas_fs_params, Range { & fs_uniform, size_of(Ve_Blit_Atlas_Fs_Params) })
|
||||
|
||||
gfx.apply_bindings(Bindings {
|
||||
vertex_buffers = {
|
||||
0 = draw_list_vbuf,
|
||||
0 = ctx.draw_list_vbuf,
|
||||
},
|
||||
vertex_buffer_offsets = {
|
||||
0 = 0,
|
||||
},
|
||||
index_buffer = draw_list_ibuf,
|
||||
index_buffer = ctx.draw_list_ibuf,
|
||||
index_buffer_offset = 0,
|
||||
images = { IMG_blit_atlas_src_texture = glyph_rt_color, },
|
||||
samplers = { SMP_blit_atlas_src_sampler = glyph_rt_sampler, },
|
||||
images = { IMG_ve_blit_atlas_src_texture = ctx.glyph_rt_color, },
|
||||
samplers = { SMP_ve_blit_atlas_src_sampler = ctx.glyph_rt_sampler, },
|
||||
})
|
||||
|
||||
// 3. Use the atlas to then render the text.
|
||||
// 3. Use the atlas (.Target) or the glyph buffer (.Target_Unchached) to then render the text.
|
||||
case .None, .Target, .Target_Uncached:
|
||||
if num_indices == 0 && ! draw_call.clear_before_draw {
|
||||
continue
|
||||
@@ -558,41 +556,42 @@ render_text_layer :: proc( screen_extent : ve.Vec2, ve_ctx : ^ve.Context, ctx :
|
||||
|
||||
// profile("VEFontCache: draw call: target")
|
||||
|
||||
pass := screen_pass
|
||||
pass := ctx.screen_pass
|
||||
pass.swapchain = glue.swapchain()
|
||||
gfx.begin_pass( pass )
|
||||
|
||||
gfx.apply_viewport( 0, 0, screen_width, screen_height, origin_top_left = true )
|
||||
gfx.apply_scissor_rect( 0, 0, screen_width, screen_height, origin_top_left = true )
|
||||
|
||||
gfx.apply_pipeline( screen_pipeline )
|
||||
gfx.apply_pipeline( ctx.screen_pipeline )
|
||||
|
||||
src_rt := atlas_rt_color
|
||||
src_sampler := atlas_rt_sampler
|
||||
src_rt := ctx.atlas_rt_color
|
||||
src_sampler := ctx.atlas_rt_sampler
|
||||
|
||||
fs_target_uniform := Draw_Text_Fs_Params {
|
||||
down_sample = 0,
|
||||
colour = draw_call.colour,
|
||||
fs_target_uniform := Ve_Draw_Text_Fs_Params {
|
||||
// glyph_buffer_size = glyph_buf_size,
|
||||
over_sample = ve_ctx.glyph_buffer.over_sample.x,
|
||||
colour = draw_call.colour,
|
||||
}
|
||||
|
||||
if draw_call.pass == .Target_Uncached {
|
||||
fs_target_uniform.down_sample = 1
|
||||
src_rt = glyph_rt_color
|
||||
src_sampler = glyph_rt_sampler
|
||||
// fs_target_uniform.over_sample = 1.0
|
||||
src_rt = ctx.glyph_rt_color
|
||||
src_sampler = ctx.glyph_rt_sampler
|
||||
}
|
||||
gfx.apply_uniforms( UB_draw_text_fs_params, Range { & fs_target_uniform, size_of(Draw_Text_Fs_Params) })
|
||||
gfx.apply_uniforms( UB_ve_draw_text_fs_params, Range { & fs_target_uniform, size_of(Ve_Draw_Text_Fs_Params) })
|
||||
|
||||
gfx.apply_bindings(Bindings {
|
||||
vertex_buffers = {
|
||||
0 = draw_list_vbuf,
|
||||
0 = ctx.draw_list_vbuf,
|
||||
},
|
||||
vertex_buffer_offsets = {
|
||||
0 = 0,
|
||||
},
|
||||
index_buffer = draw_list_ibuf,
|
||||
index_buffer = ctx.draw_list_ibuf,
|
||||
index_buffer_offset = 0,
|
||||
images = { IMG_draw_text_src_texture = src_rt, },
|
||||
samplers = { SMP_draw_text_src_sampler = src_sampler, },
|
||||
images = { IMG_ve_draw_text_src_texture = src_rt, },
|
||||
samplers = { SMP_ve_draw_text_src_sampler = src_sampler, },
|
||||
})
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,48 +1,50 @@
|
||||
@module blit_atlas
|
||||
@module ve_blit_atlas
|
||||
|
||||
@header package ve_sokol
|
||||
@header import sg "thirdparty:sokol/gfx"
|
||||
|
||||
@vs blit_atlas_vs
|
||||
@vs ve_blit_atlas_vs
|
||||
@include ./source_shared.shdc.glsl
|
||||
@end
|
||||
|
||||
@fs blit_atlas_fs
|
||||
@fs ve_blit_atlas_fs
|
||||
in vec2 uv;
|
||||
out vec4 frag_color;
|
||||
|
||||
layout(binding = 0) uniform texture2D blit_atlas_src_texture;
|
||||
layout(binding = 0) uniform sampler blit_atlas_src_sampler;
|
||||
layout(binding = 0) uniform texture2D ve_blit_atlas_src_texture;
|
||||
layout(binding = 0) uniform sampler ve_blit_atlas_src_sampler;
|
||||
|
||||
layout(binding = 0) uniform blit_atlas_fs_params {
|
||||
int region;
|
||||
layout(binding = 0) uniform ve_blit_atlas_fs_params {
|
||||
vec2 glyph_buffer_size;
|
||||
float over_sample;
|
||||
int region;
|
||||
};
|
||||
|
||||
float down_sample( vec2 uv, vec2 texture_size )
|
||||
float down_sample_to_texture( vec2 uv, vec2 texture_size )
|
||||
{
|
||||
float down_sample_scale = 1.0f / 4.0f;
|
||||
float down_sample = 1.0f / over_sample;
|
||||
|
||||
float value =
|
||||
texture(sampler2D( blit_atlas_src_texture, blit_atlas_src_sampler ), uv + vec2( 0.0f, 0.0f ) * texture_size ).x * down_sample_scale
|
||||
+ texture(sampler2D( blit_atlas_src_texture, blit_atlas_src_sampler ), uv + vec2( 0.0f, 1.0f ) * texture_size ).x * down_sample_scale
|
||||
+ texture(sampler2D( blit_atlas_src_texture, blit_atlas_src_sampler ), uv + vec2( 1.0f, 0.0f ) * texture_size ).x * down_sample_scale
|
||||
+ texture(sampler2D( blit_atlas_src_texture, blit_atlas_src_sampler ), uv + vec2( 1.0f, 1.0f ) * texture_size ).x * down_sample_scale;
|
||||
texture(sampler2D( ve_blit_atlas_src_texture, ve_blit_atlas_src_sampler ), uv + vec2( 0.0f, 0.0f ) * texture_size ).x * down_sample
|
||||
+ texture(sampler2D( ve_blit_atlas_src_texture, ve_blit_atlas_src_sampler ), uv + vec2( 0.0f, 1.0f ) * texture_size ).x * down_sample
|
||||
+ texture(sampler2D( ve_blit_atlas_src_texture, ve_blit_atlas_src_sampler ), uv + vec2( 1.0f, 0.0f ) * texture_size ).x * down_sample
|
||||
+ texture(sampler2D( ve_blit_atlas_src_texture, ve_blit_atlas_src_sampler ), uv + vec2( 1.0f, 1.0f ) * texture_size ).x * down_sample;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
// TODO(Ed): The original author made these consts, I want to instead expose as uniforms...
|
||||
const vec2 texture_size = 1.0f / vec2( 2048.0f, 512.0f ); // VEFontCache.Context.buffer_width/buffer_height
|
||||
if ( region == 0 || region == 1 || region == 2 )
|
||||
const vec2 texture_size = 1.0f / glyph_buffer_size;
|
||||
if ( region == 0 || region == 1 || region == 2 || region == 4 )
|
||||
{
|
||||
float down_sample_scale = 1.0f / 4.0f;
|
||||
float down_sample = 1.0f / over_sample;
|
||||
|
||||
float alpha =
|
||||
down_sample( uv + vec2( -1.0f, -1.5f ) * texture_size, texture_size ) * down_sample_scale
|
||||
+ down_sample( uv + vec2( 0.5f, -1.5f ) * texture_size, texture_size ) * down_sample_scale
|
||||
+ down_sample( uv + vec2( -1.5f, 0.5f ) * texture_size, texture_size ) * down_sample_scale
|
||||
+ down_sample( uv + vec2( 0.5f, 0.5f ) * texture_size, texture_size ) * down_sample_scale;
|
||||
down_sample_to_texture( uv + vec2( -1.0f, -1.5f ) * texture_size, texture_size ) * down_sample
|
||||
+ down_sample_to_texture( uv + vec2( 0.5f, -1.5f ) * texture_size, texture_size ) * down_sample
|
||||
+ down_sample_to_texture( uv + vec2( -1.5f, 0.5f ) * texture_size, texture_size ) * down_sample
|
||||
+ down_sample_to_texture( uv + vec2( 0.5f, 0.5f ) * texture_size, texture_size ) * down_sample;
|
||||
frag_color = vec4( 1.0f, 1.0f, 1.0f, alpha );
|
||||
}
|
||||
else
|
||||
@@ -52,4 +54,4 @@ void main()
|
||||
}
|
||||
@end
|
||||
|
||||
@program blit_atlas blit_atlas_vs blit_atlas_fs
|
||||
@program ve_blit_atlas ve_blit_atlas_vs ve_blit_atlas_fs
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,9 @@
|
||||
@module draw_text
|
||||
@module ve_draw_text
|
||||
|
||||
@header package ve_sokol
|
||||
@header import sg "thirdparty:sokol/gfx"
|
||||
|
||||
@vs draw_text_vs
|
||||
@vs ve_draw_text_vs
|
||||
in vec2 v_position;
|
||||
in vec2 v_texture;
|
||||
out vec2 uv;
|
||||
@@ -19,33 +19,33 @@ void main()
|
||||
}
|
||||
@end
|
||||
|
||||
@fs draw_text_fs
|
||||
@fs ve_draw_text_fs
|
||||
in vec2 uv;
|
||||
out vec4 frag_color;
|
||||
|
||||
layout(binding = 0) uniform texture2D draw_text_src_texture;
|
||||
layout(binding = 0) uniform sampler draw_text_src_sampler;
|
||||
layout(binding = 0) uniform texture2D ve_draw_text_src_texture;
|
||||
layout(binding = 0) uniform sampler ve_draw_text_src_sampler;
|
||||
|
||||
layout(binding = 0) uniform draw_text_fs_params {
|
||||
int down_sample;
|
||||
vec4 colour;
|
||||
layout(binding = 0) uniform ve_draw_text_fs_params {
|
||||
vec2 glyph_buffer_size;
|
||||
float over_sample;
|
||||
vec4 colour;
|
||||
};
|
||||
|
||||
void main()
|
||||
{
|
||||
float alpha = texture(sampler2D( draw_text_src_texture, draw_text_src_sampler ), uv ).x;
|
||||
if ( down_sample == 1 )
|
||||
{
|
||||
// TODO(Ed): The original author made these consts, I want to instead expose as uniforms...
|
||||
const vec2 texture_size = 1.0f / vec2( 2048.0f, 512.0f ); // VEFontCache.Context.buffer_width/buffer_height
|
||||
alpha =
|
||||
(texture(sampler2D( draw_text_src_texture, draw_text_src_sampler), uv + vec2( -0.5f, -0.5f) * texture_size ).x * 0.25f)
|
||||
+ (texture(sampler2D( draw_text_src_texture, draw_text_src_sampler), uv + vec2( -0.5f, 0.5f) * texture_size ).x * 0.25f)
|
||||
+ (texture(sampler2D( draw_text_src_texture, draw_text_src_sampler), uv + vec2( 0.5f, -0.5f) * texture_size ).x * 0.25f)
|
||||
+ (texture(sampler2D( draw_text_src_texture, draw_text_src_sampler), uv + vec2( 0.5f, 0.5f) * texture_size ).x * 0.25f);
|
||||
}
|
||||
float alpha = texture(sampler2D( ve_draw_text_src_texture, ve_draw_text_src_sampler ), uv ).x;
|
||||
|
||||
const vec2 texture_size = glyph_buffer_size;
|
||||
const float down_sample = 1.0f / over_sample;
|
||||
|
||||
alpha =
|
||||
(texture(sampler2D( ve_draw_text_src_texture, ve_draw_text_src_sampler), uv + vec2( -0.5f, -0.5f) * texture_size ).x * down_sample)
|
||||
+ (texture(sampler2D( ve_draw_text_src_texture, ve_draw_text_src_sampler), uv + vec2( -0.5f, 0.5f) * texture_size ).x * down_sample)
|
||||
+ (texture(sampler2D( ve_draw_text_src_texture, ve_draw_text_src_sampler), uv + vec2( 0.5f, -0.5f) * texture_size ).x * down_sample)
|
||||
+ (texture(sampler2D( ve_draw_text_src_texture, ve_draw_text_src_sampler), uv + vec2( 0.5f, 0.5f) * texture_size ).x * down_sample);
|
||||
frag_color = vec4( colour.xyz, colour.a * alpha );
|
||||
}
|
||||
@end
|
||||
|
||||
@program draw_text draw_text_vs draw_text_fs
|
||||
@program ve_draw_text ve_draw_text_vs ve_draw_text_fs
|
||||
|
@@ -1,6 +1,5 @@
|
||||
in vec2 v_position;
|
||||
in vec2 v_texture;
|
||||
// in vec4 v_elem;
|
||||
out vec2 uv;
|
||||
|
||||
void main()
|
||||
|
161
docs/Readme.md
161
docs/Readme.md
@@ -1,23 +1,6 @@
|
||||
# Interface
|
||||
|
||||
Notes
|
||||
---
|
||||
|
||||
The freetype setup is not finished. Specifically due to cache_glyph_freetype not parsing the glyph outline data structure properly.
|
||||
|
||||
Freetype supports specifying a FT_Memory handle which is a pointer to a FT_MemoryRect. This can be used to define an allocator for the parser. Currently this library does not wrap this interface (yet). If using freetype its recommend to update `parser_init` with the necessary changes to wrap the context's backing allocator for freetype to utilize.
|
||||
|
||||
```c
|
||||
struct FT_MemoryRec_
|
||||
{
|
||||
void* user;
|
||||
FT_Alloc_Func alloc;
|
||||
FT_Free_Func free;
|
||||
FT_Realloc_Func realloc;
|
||||
};
|
||||
```
|
||||
|
||||
This library (seems) to perform best if the text commands are fed in 'whitespace aware chunks', where instead of feeding it entire blobs of text, the user identfies "words" in the text and feeding the visible and whitespce chunks derived from this to draw_text as separate calls. It improves the caching of the text shapes. The downside is there has to be a time where the text is parsed into tokens beforehand so that the this iteration does not have to occur continously.
|
||||
## Lifetime
|
||||
|
||||
### startup
|
||||
|
||||
@@ -31,32 +14,68 @@ Much of the data structures within the context struct are not fixed-capacity all
|
||||
|
||||
The library supports being used in a dynamically loaded module. If its hot-reloaded simply make sure to call this procedure with a reference to the backing allocator provided during startup as all dynamic containers tend to lose a proper reference to the allocator's procedure.
|
||||
|
||||
Call clear_atlas_region_caches & clear_shape_cache to reset the library's shape and glyph cache state if doing tuning of the library.
|
||||
Call `clear_atlas_region_caches` & `clear_shape_cache` to reset the library's shape and glyph cache state to force a re-render.
|
||||
|
||||
### shutdown
|
||||
|
||||
Release resources from the context.
|
||||
|
||||
### configure_snap
|
||||
### clear_atlas_region_caches
|
||||
|
||||
You'll find this used immediately in draw_text it acts as a way to snap the position of the text to the nearest pixel for the width and height specified.
|
||||
Clears the LRU caches of regions A-D of the Atlas & sets their next_idx to 0. Effectively will force a re-cache of all previously rendered glyphs. Shape configuration for the glyph will remain unchanged unless clear_shape_cache is also called.
|
||||
|
||||
If snapping is not desired, set the snap_width and height before calling draw_text to 0.
|
||||
### clear_shape_cache
|
||||
|
||||
### get_cursor_pos
|
||||
Clears the LRU cache of the shaper along with clearing all existing storage entries. Effectively will force a re-cache of previously cached text shapes (Does not recache their rendered glyphs).
|
||||
|
||||
Will provide the current cursor_pos for the resulting text drawn.
|
||||
### load_font
|
||||
|
||||
### set_color
|
||||
Will load an instance of a font. The user needs to load the file's bytes themselves, the font entry (Entry :: struct) will by tracked by the library. The user will be given a font_id which is a direct index for the entry in the tracked array.
|
||||
|
||||
Sets the color to utilize on `Draw_Call`s for FrameBuffer.Target or .Target_Uncached passes
|
||||
### unload_font
|
||||
|
||||
Will free an entry, (parser and shaper resources also freed)
|
||||
|
||||
## Shaping
|
||||
|
||||
Ideally the user should track the shapes themselves in a time-scale beyond the per-frame draw call. This avoids having to do caching/lookups of the shope.
|
||||
|
||||
### shape_text
|
||||
|
||||
Will shape the text using the `shaper_proc` arugment (user overloadable). Shape will be cached by the library.
|
||||
|
||||
### shape_text_uncached
|
||||
|
||||
Will shape the text using the `shaper_proc` arugment (user overloadable).
|
||||
Shape will NOT be cached by the library. Use this if you want to roll your own solution for tracking shapes.
|
||||
|
||||
## Draw list generation
|
||||
|
||||
### draw_text procedures
|
||||
|
||||
There a total of six procedures, 3 for shapes, 3 for text:
|
||||
|
||||
* `draw_shape_normalized_space`
|
||||
* `draw_shape_view_space`
|
||||
* `draw_shape`
|
||||
* `draw_text_normalized_space`
|
||||
* `draw_text_view_space`
|
||||
* `draw_text`
|
||||
|
||||
The normalized space procedures are the `baseline` interface draw procedures. They expec the position, and scale provided to operate with an unsigned normalized space where the bottom left is 0.0, 0.0 and the top right is 1.0, 1.0.
|
||||
|
||||
The view space will normalize the position and scale for the user based on the provided view and zoom. The coordinate system is still unsigned just scaled to the view's size.
|
||||
|
||||
The non-suffix named procedures use the scope stack to derive the position and scale the user provides a relative position and scale for the text that will be adjusted to the scope's view, position, scale, & zoom.
|
||||
|
||||
See the comment above each of the procedures for diagrams.
|
||||
|
||||
### get_draw_list
|
||||
|
||||
Get the enqueded draw_list (vertices, indices, and draw call arrays) in its entirety.
|
||||
By default, if get_draw_list is called, it will first call `optimize_draw_list` to optimize the draw list's calls for the user. If this is undesired, make sure to pass `optimize_before_returning = false` in the arguments.
|
||||
|
||||
### get_draw_list_layer
|
||||
### get_draw_list_layer
|
||||
|
||||
Get the enqueued draw_list for the current "layer".
|
||||
A layer is considered the slice of the `Draw_List`'s content from the last call to `flush_draw_list_layer` onward.
|
||||
@@ -72,6 +91,12 @@ Will clear the draw list and draw layer offsets.
|
||||
|
||||
Will update the draw list layer with the latest offset based on the current lenght of the draw list vertices, indices, and calls arrays.
|
||||
|
||||
## Metrics
|
||||
|
||||
### measure_shape_size
|
||||
|
||||
This provide's the shape size scaled down by the ctx.px_scale to get intended usage size. Size is equivalent to `measure_text_size`.
|
||||
|
||||
### measure_text_size
|
||||
|
||||
Provides a Vec2 the width and height occupied by the provided text string. The y is measured to be the the largest glyph box bounds height of the text. The width is derived from the `end_cursor_pos` field from a `Shaped_Text` entry.
|
||||
@@ -80,10 +105,84 @@ Provides a Vec2 the width and height occupied by the provided text string. The y
|
||||
|
||||
A wrapper for `parser_get_font_vertical_metrics`. Will provide the ascent, descent, and line_gap for a font entry.
|
||||
|
||||
### clear_atlas_region_caches
|
||||
## Miscellaneous
|
||||
|
||||
Clears the LRU caches of regions A-D of the Atlas & sets their next_idx to 0. Effectively will force a re-cache of all previously rendered glyphs. Shape configuration for the glyph will remain unchanged unless clear_shape_cache is also called.
|
||||
Stuff used by the draw list generation interface or just getters and setters.
|
||||
|
||||
### clear_shape_cache
|
||||
### get_cursor_pos
|
||||
|
||||
Clears the LRU cache of the shaper along with clearing all existing storage entries. Effectively will force a re-cache of previously cached text shapes (Does not recache their rendered glyphs).
|
||||
Will provide the current cursor_pos for the resulting text drawn.
|
||||
|
||||
### get_normalized_position_scale
|
||||
|
||||
Will normalize the value of the position and scale based on the provided view.
|
||||
Position will also be snapped to the nearest pixel via ceil.
|
||||
Does nothing if view is 1 or 0
|
||||
|
||||
This is used by draw via view relative space procedures to normalize it to the intended space for the render pass.
|
||||
|
||||
### resolve_draw_px_size
|
||||
|
||||
Used to constrain the px_size used in `resolve_zoom_size_scale`.
|
||||
|
||||
The view relative space and scoping stack-based procedures support zoom. When utilizing zoom their is a nasty jitter that will occur if the user smoothly goes across different font sizes because the spacing can drastically change between even and odd font-sizes. This is applied to enforce the font sticks to a specific interval.
|
||||
|
||||
The library uses the context's zoom_px_interval as the reference interval in the draw procedures. It can be set with `set_zoom_px_interval` and the default value is 2.
|
||||
|
||||
### resolve_zoom_size_scale
|
||||
|
||||
Provides a way to get a "zoom" on the font size and scale, similar conceptually to a canvas UX zoom
|
||||
Does nothing when zoom is 1.0
|
||||
|
||||
Uses `resolve_draw_px_size` to constrain which font size is used for the zoom.
|
||||
|
||||
### set_alpha_scalar
|
||||
|
||||
This is an artifact feature of the current shader, it *may* be removed in the future... Increasing the alpha of the colour draw with above 1.0 increases the edge contrast of the glyph shape.
|
||||
|
||||
For the value to be added to the colour, the alph of the text must already be at 1.0 or greater.
|
||||
|
||||
### set_px_scalar
|
||||
|
||||
This another "super-scalar" applied to rendering glyphs. In each draw procedure the following is computed before passing the values to the shaper and draw list generation passes:
|
||||
|
||||
```go
|
||||
target_px_size := px_size * ctx.px_scalar
|
||||
target_scale := scale * (1 / ctx.px_scalar)
|
||||
target_font_scale := parser_scale( entry.parser_info, target_px_size )
|
||||
```
|
||||
|
||||
Essentially, `ctx.px_scalar` is used to upscale the px_size by its value and then downscale the render target scale back the indended size. Doing so provides better shape positioning and futher improves text hinting. The downside is that small text tends to become more jagged (as its really hitting the limits of of how well the shader can blend those edges at that resolution).
|
||||
|
||||
This will most likely be preserved with future shader upgrades, however it will most likely not be as necessary as it is right now to achieve crisp text.
|
||||
|
||||
### set_zoom_px_interval
|
||||
|
||||
Used with by draw procedures with `resolve_draw_px_size` & `resolve_zoom_size_scale`. Provides the interval to use when constraining the px_size to a specific set of values when using zoom scaling.
|
||||
|
||||
### set_snap_glyph_shape_position
|
||||
|
||||
During the shaping pass, the position of each glyph can be rounded up to the integer to (ussually) allow better hinting.
|
||||
|
||||
### set_snap_glyph_render_height
|
||||
|
||||
During the draw list generation pass, the position of each glyph when blitting to atlas can have teh quad size rounded up to the integer.
|
||||
Can yield better hinting but may significantly stretch the glyphs at small scales.
|
||||
|
||||
## Scope Stack
|
||||
|
||||
These are a set of push & pop pairs of functions that operator ont he context's stack containers. They are used with the draw_shape and draw_text procedures. This mainly for quick scratch usage where the user wants to directly compose a large amount of text without having a UI framework directly handle the text backend.
|
||||
|
||||
* font
|
||||
* font_size
|
||||
* colour: Linear colour.
|
||||
* view: Width and height of the 2D area the text will be drawn within.
|
||||
* position: Uses relative positioning will offset the incoming position by the given amount.
|
||||
* scale: Uses relative scaling, will scale the procedures incoming scale by the given amount.
|
||||
* zoom: Affects scaling, will scale the procedure's incoming font size & scale based on an *UX canvas camera's* notion of it.
|
||||
|
||||
Procedure types:
|
||||
|
||||
* `scope_<stack_option>`: push with a defer pop
|
||||
* `push_<stack_option>`
|
||||
* `pop_<stack_option>`
|
||||
|
234
docs/guide_architecture.md
Normal file
234
docs/guide_architecture.md
Normal file
@@ -0,0 +1,234 @@
|
||||
# Guide: Architecture
|
||||
|
||||
Overview of the package design and code-path layout.
|
||||
|
||||
---
|
||||
|
||||
The purpose of this library is to alleviate four key challenges with one encapsulating package:
|
||||
|
||||
* Font parsing
|
||||
* Text codepoint shaping
|
||||
* Glyph shape triangulation
|
||||
* Glyph draw-list generation
|
||||
|
||||
Shaping text, getting metrics for glyphs, triangulating glyphs, and anti-aliasing their render are expensive operations to perform per frame. Therefore, any compute operations that can be cached, will be.
|
||||
|
||||
There are two cache types used:
|
||||
|
||||
* Shape cache (`Shaped_Text_Cache.state`)
|
||||
* Atlas region cache (`Atlas_Region.state`)
|
||||
|
||||
The shape cache stores all data for a piece of text that will be utilized in a draw call that is not dependent on a specific position & scale (and is faster to lookup vs compute per draw call).
|
||||
The atlas region cache tracks what slots have glyphs rendered to the texture atlas. This essentially caches triangulation and super-sampling computations.
|
||||
|
||||
All caching uses [LRU.odin](../vefontcache/LRU.odin)
|
||||
|
||||
## Code Paths
|
||||
|
||||
### Lifetime
|
||||
|
||||
The library lifetime is straightforward: you have a startup procedure that should be called during your usual app initialization. From there you may either choose to manually shut it down or let the OS clean it up.
|
||||
|
||||
If hot-reload is desired, you just need to call hot_reload with the context's backing allocator to refresh the procedure references. After the DLL has been reloaded, these should be the only aspects that have been scrambled.
|
||||
Usually when hot-reloading the library for tuning or major changes, you'd also want to clear the caches. Simply call `clear_atlas_region_caches` & `clear_shape_cache` right after.
|
||||
|
||||
Ideally, there should be zero dynamic allocation on a per-frame basis as long as the reserves for the dynamic containers are never exceeded. It's acceptable if they do exceed as their memory locality is so large their distance in the pages to load into CPU cache won't matter - it just needs to be a low incidence.
|
||||
|
||||
### Shaping Pass
|
||||
|
||||
If using the library's cache, `shaper_shape_text_cached` handles the hashing and lookup. As long as a shape is found, it will not enter the uncached code path. By default, this library uses `shaper_shape_harfbuzz` as the `shape_text_uncached` procedure.
|
||||
|
||||
Shapes are cached using the following parameters to hash a key:
|
||||
|
||||
* font: Font_ID
|
||||
* font_size: f32
|
||||
* the text itself: string
|
||||
|
||||
All shapers fulfill the following interface:
|
||||
|
||||
```odin
|
||||
Shaper_Shape_Text_Uncached_Proc :: #type proc( ctx : ^Shaper_Context,
|
||||
atlas : Atlas,
|
||||
glyph_buffer_size : Vec2,
|
||||
font : Font_ID,
|
||||
entry : Entry,
|
||||
font_px_Size : f32,
|
||||
font_scale : f32,
|
||||
text_utf8 : string,
|
||||
output : ^Shaped_Text
|
||||
)
|
||||
```
|
||||
|
||||
Which will resolve the output `Shaped_Text`. It has the following definition:
|
||||
|
||||
```odin
|
||||
Shaped_Text :: struct #packed {
|
||||
glyph : [dynamic]Glyph,
|
||||
position : [dynamic]Vec2,
|
||||
visible : [dynamic]i16,
|
||||
atlas_lru_code : [dynamic]Atlas_Key,
|
||||
region_kind : [dynamic]Atlas_Region_Kind,
|
||||
bounds : [dynamic]Range2,
|
||||
end_cursor_pos : Vec2,
|
||||
size : Vec2,
|
||||
font : Font_ID,
|
||||
px_size : f32,
|
||||
}
|
||||
```
|
||||
|
||||
The result of the shaping process is the glyphs and their positions for the the shape; historically resembling whats known as a *Slug* of prepared text for printing. The end position of where the user's "cursor" would be is also recorded which provided the end position of the shape. The size of the shape is also resolved here, which if using px_scalar must be downscaled. `measure_shape_size` does the downscaling for the user.
|
||||
|
||||
`visible` tracks which of the glyphs will actually be relevant for the draw_list pass. This is to avoid a conditional jump during the draw list gen pass. When accessing glyph or position during the draw_list gen, they will use visible's relative index.
|
||||
|
||||
The font and px_size is tracked here as well so they user does not need to provide it to the library's interface and related.
|
||||
|
||||
As stated under the main heading of this guide, the the following are within shaped text so that they may be resolved outside of the draw list generation (see: `generate_shape_draw_list`):
|
||||
|
||||
* atlas_lru_code
|
||||
* region_kind
|
||||
* bounds
|
||||
|
||||
These are the same length as the `visible` array, so indexing those will not need to use visibile's relative index.
|
||||
|
||||
`shaper_shape_text_latin` does naive shaping by utilizing the codepoint's kern_advance and detecting newlines.
|
||||
`shaper_shape_harfbuzz` is an actual shaping *engine*. Here is the general idea of how the library utilizes it for shaping:
|
||||
|
||||
1. Reset the state of the hb_buffer
|
||||
2. Determine the line height
|
||||
3. Go through the codepoints: (for each)
|
||||
1. Determine the codepoint's script
|
||||
2. If the script is netural (Uknown, Inherited, or of Common type), the script has not changed, or this is the first codepoint of the shape we can add the codepoint to the buffer.
|
||||
3. Otherwise we will have to start a shaping run if we do encounter a significant script change. After, we can add the codepoint to the post-run-cleared hb_buffer.
|
||||
4. This continues until all codepoints have been processed.
|
||||
4. We do a final shape run after iterating to make sure all codepoints have been processed.
|
||||
5. Set the size of the shape: X is max line width, Y is line height multiplied by the line count.
|
||||
6. Resolve the atlas_lru_code, region_kind, and bounds for all visible glyphs
|
||||
7. Store the font and px_size information.
|
||||
|
||||
The `shape_run` procedure within does the following:
|
||||
|
||||
1. Setup the buffer for the batch
|
||||
2. Have harfbuzz shape the buffer
|
||||
3. Extract glyph infos and positions from the buffer.
|
||||
4. Iterate through all glyphs
|
||||
1. If the hb_glyph cluster is > 0, we need to treat it as the indication of a newline glyph. ***(We update position and skip)***
|
||||
2. Update positioning and other metrics and append output shape's glyph and position.
|
||||
3. If the glyph is visible we append it to shape's visible (harfbuzz must specify it as not .nodef, and parser must identify it as non-empty)
|
||||
5. We update the output.end_cursor_pos with the last position processed by the iteration
|
||||
6. Clear the hb_buffer's contents to prepare for a possible upcoming shape run.
|
||||
|
||||
**Note on shape_run.4: The iteration doesn't preserve tracking the clusters, so that information is lost.**
|
||||
*In the future cluster tracking may be added if its found to be important for high level text features beyond rendering.*
|
||||
|
||||
**Note on shape_run.4.1: Don't know if the glyph signifiying newline should be preserved**
|
||||
|
||||
See [Harfbuzz documentation](https://harfbuzz.github.io) for additional information.
|
||||
|
||||
There are other shapers out there:
|
||||
|
||||
* [hamza](https://github.com/saidwho12/hamza): A notable C library that could be setup with bindings.
|
||||
|
||||
***Note: Monospace fonts may have a much more trivial shaper (however for fonts with ligatures this may not be the case)***
|
||||
***They should only need the kern advance of a single glyph as they're all the same. ligatures (I believe) should preserve this kern advance.***
|
||||
|
||||
### Draw List Generation
|
||||
|
||||
All interface draw text procedures will ultimately call `generate_shape_draw_list`. If the draw procedure is given text, it will call `shaper_shape_text_cached` the text immediately before calling it.
|
||||
|
||||
Its implementation uses a batched-pipeline approach where its goal is to populate three arrays behavings as queues:
|
||||
|
||||
* oversized: For drawing oversized glyphs
|
||||
* to_cache: For glyphs that need triangulation & rendering to glyph buffer then blitting to atlas.
|
||||
* cache: For glyphs that are already cached in the atlas and just need to be blit to the render target.
|
||||
|
||||
And then sent those off to `batch_generate_glyphs_draw_list` for further actual generation to be done. The size of a batch is determined by the capacity of the glyph_buffer's `batch_cache`. This can be set in `glyph_draw_params` for startup.
|
||||
|
||||
`glyph_buffer.glyph_pack` is utilized by both `generate_shape_draw_list` and `batch_generate_glyphs_draw_list` to various computed data in an SOA data structure for the glyphs.
|
||||
|
||||
generate_shape_draw_list outline:
|
||||
|
||||
1. Prepare glyph_pack, oversized, to_cache, cached, and reset the batch cache
|
||||
* `glyph_pack` is resized to to the length of `shape.visible`
|
||||
* The other arrays populated have their reserved set to that length as well (they will not bounds check capacity on append)
|
||||
2. Iterate through the shape.visible and resolve glyph_pack's positions.
|
||||
3. Iterate through shape.visible this time for final region resolution and segregation of glyphs to their appropriate queue.
|
||||
1. If the glyphs assigned region is `.E` its oversized. The `oversample` used for rendering to render target will either be 2x or 1x depending on how huge it is.
|
||||
2. The following glyphs are checked to see if their assigned region has the glyph `cached`.
|
||||
1. If it does, its just appended to cached and marked as seen in the `batch_cache`.
|
||||
2. If its doesn't then a slot is reserved for within the atlas's region and the glyph is appended to `to_cache`.
|
||||
3. For either case the atlas_region_bbox is computed.
|
||||
3. After a batch has been resolved, `batch_generate_glyphs_draw_list` is called.
|
||||
4. If there is an partially filled batch (the usual case), batch_generate_glyphs_draw_list will be called for it.
|
||||
5. The cursor_pos is updated with the shape's end cursor position adjusted for the target space.
|
||||
|
||||
batch_generate_glyphs_draw_list outline:
|
||||
|
||||
The batch is organized into three major stages:
|
||||
|
||||
1. glyph transform & draw quads compute
|
||||
2. glyph_buffer draw list generation (`oversized` & `to_cache`)
|
||||
3. blit-from-atlas to render target draw list generation (`to_cache` & `cached`)
|
||||
|
||||
Glyph transform & draw quads compute does an iteration for each of the 3 arrays.
|
||||
Nearly all the math for all three is done there *except* for `to_cache`, which does its blitting compute in its glyph_buffer draw-list gen pass.
|
||||
|
||||
glyph_buffer draw list generation paths for `oversized` and `to_cache` are unique to each.
|
||||
|
||||
For `oversized`:
|
||||
|
||||
1. Allocate glyph shapes
|
||||
2. Iterate oversized:
|
||||
1. Flush the glyph buffer if flagged todo so (reached glyph allocation limit)
|
||||
2. Call `generate_glyph_pass_draw_list` for trianglation and rendering to buffer.
|
||||
3. blit quad.
|
||||
3. flush the glyph buffer's draw list.
|
||||
4. free glyph shapes
|
||||
|
||||
For `to_cached`:
|
||||
|
||||
1. Allocate glyph shapes
|
||||
2. Iterate to_cache:
|
||||
1. Flush the glyph buffer if flagged todo so (reached glyph allocation limit)
|
||||
2. Compute & blit quads for clearing the atlas region and blitting from the buffer to the atlas.
|
||||
3. Call `generate_glyph_pass_draw_list` for trianglation and rendering to buffer.
|
||||
3. flush the glyph buffer's draw list.
|
||||
4. free glyph shapes
|
||||
5. Do blits from atlas to draw list.
|
||||
|
||||
`cached` only needs to blit from the atlas to the render target.
|
||||
|
||||
`generate_glyph_pass_draw_list`: sets up the draw call for glyph to the glyph buffer. Currently it also handles triangulation as well. For now the shape triangulation is rudimentary and uses triangle fanning. Eventually it would be nice to offer alternative modes that can be specified on a per-font basis.
|
||||
|
||||
`flush_glyph_buffer_draw_list`: Will merge the draw_lists contents of the glyph buffer over to the library's general draw_list, the clear the buffer's draw lists.
|
||||
|
||||
### On Layering
|
||||
|
||||
The base draw list generation pippline provided by the library allows the user to batch whatever they want into a single "layer".
|
||||
However, the user most likely would want take into consideration: font instances, font size, colors; these are things that may benefit from having shared locality during a layer batch. Overlaping text benefits from the user to handle the ordering via layers.
|
||||
|
||||
Layers (so far) are just a set of offssets tracked by the library's `Context.draw_layer` struct. When `flush_draw_list_layer` is called, the offsets are set to the current length of the draw list. This allows the rendering backend to retrieve the latest set of vertices, indices, and calls to render on a per-layer basis with: `get_draw_list_layer`.
|
||||
|
||||
Importantly, this leads to the following pattern when enuquing a layer to render:
|
||||
|
||||
1. Begin render pass
|
||||
2. For codepath that will deal with text layers
|
||||
1. Process user-level code-path that calls the draw text interface, populating the draw list layer (usually a for loop)
|
||||
2. After iteration on the layer is complete, render the text layer
|
||||
1. grab the draw list layer
|
||||
2. flush the layer so the draw list offsets are reset
|
||||
3. Repeat until all layers for the codepath are exhausted.
|
||||
|
||||
There is consideration to instead explicitly have a draw list with more contextual information of the start and end of each layer. So that batching can be orchestrated in an isolated section of their pipeline.
|
||||
|
||||
This would involve just tracking *slices* of thier draw-list that represents layers:
|
||||
|
||||
```odin
|
||||
Draw_List_Layer :: struct {
|
||||
vertices : []Vertex,
|
||||
indices : []u32,
|
||||
calls : []Draw_Call,
|
||||
}
|
||||
```
|
||||
|
||||
Eventually the library may provide this since adding that feature is relatively cheap and and a low line-count addition to the interface.
|
||||
There should be little to no perfomrance loss from doing so as the iteration size is two large of a surface area to matter (so its just pipeline ergonomics)
|
68
docs/guide_backend.md
Normal file
68
docs/guide_backend.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# Guide: Backend
|
||||
|
||||
The end-user needs to adapt this library to hook into their own codebase. For reference, they can check the [examples](../examples/) and [backend](../backend/) directories for working code that demonstrates what this guide covers.
|
||||
|
||||
When rendering text, users need to handle two main aspects: the text to draw and its "layering". Similar to UIs, text should be drawn in layer batches, where each layer can represent a pass with arbitrary distinctions from other layers.
|
||||
|
||||
The following components are required:
|
||||
|
||||
* Vertex and Index Buffers for glyph meshes
|
||||
* Glyph shader for rendering glyphs to the glyph buffer
|
||||
* Atlas shader for blitting upscaled glyph quads from the glyph buffer to an atlas region slot (downsampled)
|
||||
* "Screen or Target" shader for blitting glyph quads from the atlas to a render target or swapchain
|
||||
* The glyph, atlas, and target image buffers
|
||||
|
||||
Currently, the library doesn't support sub-pixel AA, so we're only rendering to R8 images.
|
||||
|
||||
## Rendering Passes
|
||||
|
||||
There are four passes that need to be handled when rendering a draw list:
|
||||
|
||||
* Glyph: Rendering a glyph mesh to the glyph buffer
|
||||
* Atlas: Blitting a glyph quad from the glyph buffer to an atlas slot
|
||||
* Target: Blitting from the atlas image to the target image
|
||||
* Target_Uncached: Blitting from the glyph buffer image to the target image
|
||||
|
||||
The Target & Target_Uncached passes can technically be handled in the same case. The user just needs to swap between using the atlas image and the glyph buffer image. This is how the backend_soko.odin's `render_text_layer` has these passes set up.
|
||||
|
||||
## Vertex Buffer Layout
|
||||
|
||||
The vertex buffer has the following layout for all passes:
|
||||
|
||||
* `[2]f32` for positions
|
||||
* `[2]f32` for texture coords (Offset is naturally `[2]f32`)
|
||||
* Total stride: `[4]f32`
|
||||
|
||||
---
|
||||
|
||||
The index buffer is a simple u32 stream.
|
||||
|
||||
For quad mesh layout details, see `blit_quad` in [draw.odin](../vefontcache/draw.odin).
|
||||
|
||||
For glyph shape triangulation meshes, the library currently only uses a triangle fanning technique, implemented in `fill_path_via_fan_triangulation` within [draw.odin](../vefontcache/draw.odin). Eventually, the library will support other modes on a per-font basis.
|
||||
|
||||
## UV Coordinate Conventions (GLSL vs HLSL)
|
||||
|
||||
DirectX, Metal, and Vulkan consider the top-left corner as (0, 0), where the Y axis increases downward (traditional screenspace). This library follows OpenGL's convention, where (0, 0) is at the bottom-left (Y goes up).
|
||||
|
||||
Adjust the UV coordinates in your shader accordingly:
|
||||
|
||||
```c
|
||||
#if !OpenGL
|
||||
uv = vec2(v_texture.x, 1.0 - v_texture.y);
|
||||
#else
|
||||
uv = vec2(v_texture.x, v_texture.y);
|
||||
#endif
|
||||
```
|
||||
|
||||
Eventually, the library will support both conventions as a comp-time conditional.
|
||||
|
||||
## Retrieving & Processing the layer
|
||||
|
||||
`get_draw_list_layer` will provide the layer's vertex, index, and draw call slices. Unless the default is overwritten, it will call `optimize_draw_list` before returning the slices (profile to see whats better for your use case).
|
||||
Once those are retrived, call `flush_draw_list_layer` to update the layer offsets tracked by the library's `Context`.
|
||||
|
||||
The vertex and index slices just needed to be appended to your backend's vertex and index buffers.
|
||||
The draw calls need to be iterated with a switch statement for the aforementioned pass types. Within the case you can construct the enqueue the passes.
|
||||
|
||||
---
|
@@ -42,7 +42,7 @@ FONT_LARGEST_PIXEL_SIZE :: 400
|
||||
FONT_SIZE_INTERVAL :: 2
|
||||
|
||||
FONT_DEFAULT :: Font_ID { "" }
|
||||
FONT_DEFAULT_SIZEZ :: 12.0
|
||||
FONT_DEFAULT_SIZE :: 12.0
|
||||
|
||||
FONT_LOAD_USE_DEFAULT_SIZE :: -1
|
||||
FONT_LOAD_GEN_ID :: ""
|
||||
@@ -56,16 +56,17 @@ Font_ID :: struct {
|
||||
label : string,
|
||||
}
|
||||
|
||||
FontDef :: struct {
|
||||
Font_Entry :: struct {
|
||||
path_file : string,
|
||||
data : []byte,
|
||||
default_size : i32,
|
||||
size_table : [FONT_LARGEST_PIXEL_SIZE / FONT_SIZE_INTERVAL] ve.Font_ID,
|
||||
ve_id : ve.Font_ID,
|
||||
}
|
||||
|
||||
Demo_Context :: struct {
|
||||
ve_ctx : ve.Context,
|
||||
render_ctx : ve_sokol.Context,
|
||||
font_ids : map[string]FontDef,
|
||||
font_ids : map[string]Font_Entry,
|
||||
|
||||
// Values between 1, & -1 on Y axis
|
||||
mouse_scroll : Vec2,
|
||||
@@ -106,7 +107,7 @@ font_load :: proc(path_file : string,
|
||||
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
|
||||
font_firacode : Font_ID
|
||||
|
||||
|
||||
desired_id := desired_id
|
||||
@@ -115,24 +116,20 @@ font_firacode : Font_ID
|
||||
desired_id = file_name_from_path(path_file)
|
||||
}
|
||||
|
||||
demo_ctx.font_ids[desired_id] = FontDef {}
|
||||
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_SIZEZ
|
||||
default_size = FONT_DEFAULT_SIZE
|
||||
}
|
||||
|
||||
error : ve.Load_Font_Error
|
||||
def.path_file = path_file
|
||||
def.default_size = default_size
|
||||
|
||||
for font_size : i32 = clamp( FONT_SIZE_INTERVAL, 2, FONT_SIZE_INTERVAL ); font_size <= FONT_LARGEST_PIXEL_SIZE; font_size += FONT_SIZE_INTERVAL
|
||||
{
|
||||
id := (font_size / FONT_SIZE_INTERVAL) + (font_size % FONT_SIZE_INTERVAL)
|
||||
ve_id := & def.size_table[id - 1]
|
||||
ve_ret_id := ve.load_font( & demo_ctx.ve_ctx, desired_id, font_data, f32(font_size), curve_quality )
|
||||
(ve_id^) = ve_ret_id
|
||||
}
|
||||
def.data = font_data
|
||||
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
|
||||
@@ -140,81 +137,39 @@ font_firacode : Font_ID
|
||||
|
||||
Font_Use_Default_Size :: f32(0.0)
|
||||
|
||||
font_resolve_draw_id :: proc( id : Font_ID, size := Font_Use_Default_Size ) -> ( ve_id : ve.Font_ID, resolved_size : i32 )
|
||||
{
|
||||
def := demo_ctx.font_ids[ id.label ]
|
||||
size := size == 0.0 ? f32(def.default_size) : size
|
||||
even_size := math.round(size * (1.0 / f32(FONT_SIZE_INTERVAL))) * f32(FONT_SIZE_INTERVAL)
|
||||
resolved_size = clamp( i32( even_size), 2, FONT_LARGEST_PIXEL_SIZE )
|
||||
|
||||
id := (resolved_size / FONT_SIZE_INTERVAL) + (resolved_size % FONT_SIZE_INTERVAL)
|
||||
ve_id = def.size_table[ id - 1 ]
|
||||
return
|
||||
}
|
||||
|
||||
measure_text_size :: proc( text : string, font : Font_ID, font_size := Font_Use_Default_Size, spacing : f32 ) -> Vec2
|
||||
{
|
||||
ve_id, size := font_resolve_draw_id( font, font_size )
|
||||
measured := ve.measure_text_size( & demo_ctx.ve_ctx, ve_id, text )
|
||||
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 )
|
||||
{
|
||||
ve_id, size := font_resolve_draw_id( font, font_size )
|
||||
ascent, descent, line_gap = ve.get_font_vertical_metrics( & demo_ctx.ve_ctx, ve_id )
|
||||
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, id : Font_ID, size : f32, pos : Vec2, color := COLOR_WHITE, scale : f32 = 1.0 )
|
||||
draw_text :: proc( content : string, font : Font_ID, pos : Vec2, size : f32 = 0.0, color := COLOR_WHITE, scale : f32 = 1.0, zoom : f32 = 1.0 )
|
||||
{
|
||||
width := demo_ctx.screen_size.x
|
||||
height := demo_ctx.screen_size.y
|
||||
|
||||
ve_id, resolved_size := font_resolve_draw_id( id, size )
|
||||
color_norm := normalize_rgba8(color)
|
||||
|
||||
ve.set_colour( & demo_ctx.ve_ctx, color_norm )
|
||||
ve.draw_text( & demo_ctx.ve_ctx, ve_id, content, pos, Vec2{1 / width, 1 / height} * scale )
|
||||
return
|
||||
}
|
||||
|
||||
// Draw text using a string and extent-based screen coordinates
|
||||
draw_text_string_pos_extent :: proc( content : string, id : Font_ID, size : f32, pos : Vec2, color := COLOR_WHITE ) {
|
||||
render_pos := pos + demo_ctx.screen_size * 0.5
|
||||
normalized_pos := render_pos * (1.0 / demo_ctx.screen_size)
|
||||
draw_text_string_pos_norm( content, id, size, normalized_pos, color )
|
||||
}
|
||||
|
||||
// Adapt the draw_text_string_pos_extent_zoomed procedure
|
||||
draw_text_zoomed_norm :: proc(content : string, id : Font_ID, size : f32, pos : Vec2, zoom : f32, color := COLOR_WHITE)
|
||||
{
|
||||
screen_size := demo_ctx.screen_size
|
||||
screen_scale := Vec2{1.0 / screen_size.x, 1.0 / screen_size.y}
|
||||
zoom_adjust_size := size * zoom
|
||||
|
||||
// Over-sample font-size
|
||||
|
||||
zoom_adjust_size *= OVER_SAMPLE_ZOOM
|
||||
|
||||
ve_id, resolved_size := font_resolve_draw_id(id, zoom_adjust_size)
|
||||
|
||||
text_scale := screen_scale
|
||||
{
|
||||
f32_resolved_size := f32(resolved_size)
|
||||
diff_scalar := 1 + (zoom_adjust_size - f32_resolved_size) / f32_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)
|
||||
}
|
||||
|
||||
// Down-sample back
|
||||
text_scale /= OVER_SAMPLE_ZOOM
|
||||
|
||||
color_norm := normalize_rgba8(color)
|
||||
ve.set_colour(&demo_ctx.ve_ctx, color_norm)
|
||||
ve.draw_text(&demo_ctx.ve_ctx, ve_id, content, pos, text_scale)
|
||||
def := demo_ctx.font_ids[ font.label ]
|
||||
size := size >= 2.0 ? size : f32(def.default_size)
|
||||
|
||||
resolved_size, zoom_scale := ve.resolve_zoom_size_scale( zoom, size, scale, 2, 2, 999.0, demo_ctx.screen_size )
|
||||
snapped_pos := ve.snap_normalized_position_to_view( pos, demo_ctx.screen_size )
|
||||
norm_scale := zoom_scale * (1 / demo_ctx.screen_size)
|
||||
ve.draw_text_normalized_space( & demo_ctx.ve_ctx,
|
||||
def.ve_id,
|
||||
resolved_size,
|
||||
color_norm,
|
||||
snapped_pos,
|
||||
norm_scale,
|
||||
content
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
sokol_app_alloc :: proc "c" ( size : uint, user_data : rawptr ) -> rawptr {
|
||||
@@ -271,11 +226,22 @@ init :: proc "c" ()
|
||||
case .DUMMY: fmt.println(">> using dummy backend")
|
||||
}
|
||||
|
||||
ve.startup( & demo_ctx.ve_ctx, .STB_TrueType, allocator = context.allocator )
|
||||
ve_sokol.setup_gfx_objects( & demo_ctx.render_ctx, & demo_ctx.ve_ctx, vert_cap = 1024 * 1024, index_cap = 1024 * 1024 )
|
||||
glyph_draw_opts := ve.Init_Glyph_Draw_Params_Default
|
||||
glyph_draw_opts.snap_glyph_height = true
|
||||
|
||||
shaper_opts := ve.Init_Shaper_Params_Default
|
||||
shaper_opts.snap_glyph_position = true
|
||||
|
||||
ve.startup( & demo_ctx.ve_ctx, .STB_TrueType, allocator = context.allocator,
|
||||
glyph_draw_params = glyph_draw_opts,
|
||||
shaper_params = shaper_opts,
|
||||
px_scalar = 1.6,
|
||||
alpha_sharpen = 0.35,
|
||||
)
|
||||
ve_sokol.setup_gfx_objects( & demo_ctx.render_ctx, & demo_ctx.ve_ctx, vert_cap = 256 * 1024, index_cap = 512 * 1024 )
|
||||
|
||||
error : mem.Allocator_Error
|
||||
demo_ctx.font_ids, error = make( map[string]FontDef, 256 )
|
||||
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" })
|
||||
@@ -294,26 +260,25 @@ init :: proc "c" ()
|
||||
path_noto_sans_jp_reg := strings.concatenate({ PATH_FONTS, "NotoSansJP-Regular.otf" })
|
||||
path_firacode := strings.concatenate({ PATH_FONTS, "FiraCode-Regular.ttf" })
|
||||
|
||||
using demo_ctx
|
||||
font_logo = font_load(path_sawarabi_mincho, 330.0, "SawarabiMincho", 6 )
|
||||
font_title = font_load(path_open_sans, 92.0, "OpenSans", 12 )
|
||||
font_print = font_load(path_noto_sans_jp, 19.0, "NotoSansJP")
|
||||
font_mono = font_load(path_ubuntu_mono, 21.0, "UbuntuMono")
|
||||
font_small = font_load(path_roboto, 10.0, "Roboto")
|
||||
font_demo_sans = font_load(path_open_sans, 18.0, "OpenSans")
|
||||
font_demo_serif = font_load(path_bitter, 18.0, "Bitter")
|
||||
font_demo_script = font_load(path_dancing_script, 22.0, "DancingScript")
|
||||
font_demo_mono = font_load(path_nova_mono, 18.0, "NovaMono")
|
||||
font_demo_chinese = font_load(path_noto_serif_sc, 24.0, "NotoSerifSC")
|
||||
font_demo_japanese = font_load(path_sawarabi_mincho, 24.0, "SawarabiMincho")
|
||||
font_demo_korean = font_load(path_nanum_pen_script, 36.0, "NanumPenScript")
|
||||
font_demo_thai = font_load(path_krub, 24.0, "Krub")
|
||||
font_demo_arabic = font_load(path_tajawal, 24.0, "Tajawal")
|
||||
font_demo_hebrew = font_load(path_david_libre, 22.0, "DavidLibre")
|
||||
font_demo_raincode = font_load(path_noto_sans_jp_reg, 20.0, "NotoSansJPRegular")
|
||||
font_demo_grid2 = font_load(path_noto_serif_sc, 54.0, "NotoSerifSC")
|
||||
font_demo_grid3 = font_load(path_bitter, 44.0, "Bitter")
|
||||
font_firacode = font_load(path_firacode, 16.0, "FiraCode", 12 )
|
||||
demo_ctx.font_logo = font_load(path_sawarabi_mincho, 150.0, "SawarabiMincho", 18 )
|
||||
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", 6)
|
||||
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_title = demo_ctx.font_demo_sans
|
||||
demo_ctx.font_demo_grid2 = demo_ctx.font_demo_chinese
|
||||
demo_ctx.font_demo_grid3 = demo_ctx.font_demo_serif
|
||||
demo_ctx.font_firacode = font_load(path_firacode, 16.0, "FiraCode", 12 )
|
||||
}
|
||||
|
||||
event :: proc "c" (sokol_event : ^app.Event)
|
||||
@@ -335,10 +300,8 @@ frame :: proc "c" ()
|
||||
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 })
|
||||
|
||||
using demo_ctx
|
||||
// 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
|
||||
@@ -350,7 +313,7 @@ frame :: proc "c" ()
|
||||
|
||||
frame_duration := cast(f32) app.frame_duration()
|
||||
|
||||
scroll_velocity += mouse_scroll.y * 0.05
|
||||
scroll_velocity += demo_ctx.mouse_scroll.y * 0.035
|
||||
mouse_down_pos = -1.0
|
||||
substep_dt := frame_duration / 4.0
|
||||
for _ in 0 ..< 4 {
|
||||
@@ -360,14 +323,16 @@ frame :: proc "c" ()
|
||||
if demo_autoscroll {
|
||||
current_scroll += 0.05 * frame_duration
|
||||
}
|
||||
mouse_scroll = {} // Reset mouse scroll
|
||||
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, font_title, 0, {0.0, 0.0}, COLOR_WHITE)
|
||||
draw_text(frametime_text, demo_ctx.font_title, {0.0, 0.0}, size = 30)
|
||||
|
||||
// Below is content based on the original demo from the C++ library.
|
||||
|
||||
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.
|
||||
@@ -382,9 +347,9 @@ It aims to:
|
||||
• Support cached text shaping with HarfBuzz with simple Latin-style fallback.
|
||||
• Load and unload fonts at any time.`
|
||||
|
||||
draw_text_string_pos_norm("ゑ", font_logo, 330, {0.4, current_scroll}, COLOR_WHITE)
|
||||
draw_text_string_pos_norm("VEFontCache Demo", font_title, 92, {0.2, current_scroll - 0.1}, COLOR_WHITE)
|
||||
draw_text_string_pos_norm(intro, font_print, 19, {0.2, current_scroll - 0.14}, COLOR_WHITE)
|
||||
draw_text("ゑ", demo_ctx.font_logo, { 0.4, current_scroll }, size = 200, scale = 2.0)
|
||||
draw_text("VEFontCache Demo", demo_ctx.font_title, { 0.2, current_scroll - 0.1 }, size = 92)
|
||||
draw_text(intro, demo_ctx.font_print, { 0.2, current_scroll - 0.14 }, size = 19)
|
||||
}
|
||||
|
||||
section_start : f32 = 0.42
|
||||
@@ -427,10 +392,10 @@ Glyphs are first rendered to an intermediate 2k x 512px R8 texture. This allows
|
||||
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", font_title, 92, {0.2, current_scroll - (section_start + 0.06)}, COLOR_WHITE)
|
||||
draw_text_string_pos_norm(how_it_works, 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, font_print, 19, {0.2, current_scroll - (section_start + 0.82)}, COLOR_WHITE)
|
||||
draw_text("How it works", demo_ctx.font_title, { 0.2, current_scroll - (section_start + 0.06) }, size = 92)
|
||||
draw_text(how_it_works, demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.1) }, size = 19)
|
||||
draw_text(caching_strategy, demo_ctx.font_mono, { 0.28, current_scroll - (section_start + 0.32) }, size = 21)
|
||||
draw_text(how_it_works2, demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.82) }, size = 19)
|
||||
}
|
||||
|
||||
// Showcase section
|
||||
@@ -441,49 +406,49 @@ intermediate texture to the final atlas location.`
|
||||
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", 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.", font_print, 19, {0.2, current_scroll - (section_start + 0.24)}, COLOR_WHITE)
|
||||
draw_text("Showcase", demo_ctx.font_title, { 0.2, current_scroll - (section_start + 0.2) }, size = 92)
|
||||
draw_text("This is a showcase demonstrating different hb_font categories and languages.", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.24) }, size = 19)
|
||||
|
||||
draw_text_string_pos_norm("Sans serif", font_print, 19, {0.2, current_scroll - (section_start + 0.28)}, COLOR_WHITE)
|
||||
draw_text_string_pos_norm(font_family_test, font_demo_sans, 18, {0.3, current_scroll - (section_start + 0.28)}, COLOR_WHITE)
|
||||
draw_text("Sans serif", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.28) }, size = 19)
|
||||
draw_text(font_family_test, demo_ctx.font_demo_sans, { 0.3, current_scroll - (section_start + 0.28) }, size = 18)
|
||||
|
||||
draw_text_string_pos_norm("Serif", font_print, 19, {0.2, current_scroll - (section_start + 0.36)}, COLOR_WHITE)
|
||||
draw_text_string_pos_norm(font_family_test, font_demo_serif, 18, {0.3, current_scroll - (section_start + 0.36)}, COLOR_WHITE)
|
||||
draw_text("Serif", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.36) }, size = 19)
|
||||
draw_text(font_family_test, demo_ctx.font_demo_serif, { 0.3, current_scroll - (section_start + 0.36) }, size = 18)
|
||||
|
||||
draw_text_string_pos_norm("Script", font_print, 19, {0.2, current_scroll - (section_start + 0.44)}, COLOR_WHITE)
|
||||
draw_text_string_pos_norm(font_family_test, font_demo_script, 22, {0.3, current_scroll - (section_start + 0.44)}, COLOR_WHITE)
|
||||
draw_text("Script", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.44) }, size = 19)
|
||||
draw_text(font_family_test, demo_ctx.font_demo_script, { 0.3, current_scroll - (section_start + 0.44) }, size = 22)
|
||||
|
||||
draw_text_string_pos_norm("Monospace", font_print, 19, {0.2, current_scroll - (section_start + 0.52)}, COLOR_WHITE)
|
||||
draw_text_string_pos_norm(font_family_test, font_demo_mono, 18, {0.3, current_scroll - (section_start + 0.52)}, COLOR_WHITE)
|
||||
draw_text("Monospace", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.52) }, size = 19)
|
||||
draw_text(font_family_test, demo_ctx.font_demo_mono, { 0.3, current_scroll - (section_start + 0.52) }, size = 22)
|
||||
|
||||
draw_text_string_pos_norm("Small", font_print, 19, {0.2, current_scroll - (section_start + 0.60)}, COLOR_WHITE)
|
||||
draw_text_string_pos_norm(font_family_test, font_small, 10, {0.3, current_scroll - (section_start + 0.60)}, COLOR_WHITE)
|
||||
draw_text("Small", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.60) }, size = 19)
|
||||
draw_text(font_family_test, demo_ctx.font_small, { 0.3, current_scroll - (section_start + 0.60) }, size = 10)
|
||||
|
||||
draw_text_string_pos_norm("Greek", font_print, 19, {0.2, current_scroll - (section_start + 0.72)}, COLOR_WHITE)
|
||||
draw_text_string_pos_norm("Ήταν απλώς θέμα χρόνου.", font_demo_sans, 18, {0.3, current_scroll - (section_start + 0.72)}, COLOR_WHITE)
|
||||
draw_text("Greek", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.72) }, size = 19)
|
||||
draw_text("Ήταν απλώς θέμα χρόνου.", demo_ctx.font_demo_sans, { 0.3, current_scroll - (section_start + 0.72) }, size = 18)
|
||||
|
||||
draw_text_string_pos_norm("Vietnamese", 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.", font_demo_sans, 18, {0.3, current_scroll - (section_start + 0.76)}, COLOR_WHITE)
|
||||
draw_text("Vietnamese", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.76) }, size = 19)
|
||||
draw_text("Bầu trời trong xanh thăm thẳm, không một gợn mây.", demo_ctx.font_demo_sans, { 0.3, current_scroll - (section_start + 0.76) }, size = 18)
|
||||
|
||||
draw_text_string_pos_norm("Thai", font_print, 19, {0.2, current_scroll - (section_start + 0.80)}, COLOR_WHITE)
|
||||
draw_text_string_pos_norm("การเดินทางขากลับคงจะเหงา", font_demo_thai, 24, {0.3, current_scroll - (section_start + 0.80)}, COLOR_WHITE)
|
||||
draw_text("Thai", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.80) }, size = 19)
|
||||
draw_text("การเดินทางขากลับคงจะเหงา", demo_ctx.font_demo_thai, { 0.3, current_scroll - (section_start + 0.80) }, size = 24)
|
||||
|
||||
draw_text_string_pos_norm("Chinese", font_print, 19, {0.2, current_scroll - (section_start + 0.84)}, COLOR_WHITE)
|
||||
draw_text_string_pos_norm("床前明月光 疑是地上霜 举头望明月 低头思故乡", font_demo_chinese, 24, {0.3, current_scroll - (section_start + 0.84)}, COLOR_WHITE)
|
||||
draw_text("Chinese", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.84) }, size = 19)
|
||||
draw_text("床前明月光 疑是地上霜 举头望明月 低头思故乡", demo_ctx.font_demo_chinese, {0.3, current_scroll - (section_start + 0.84) }, size = 24)
|
||||
|
||||
draw_text_string_pos_norm("Japanese", font_print, 19, {0.2, current_scroll - (section_start + 0.88)}, COLOR_WHITE)
|
||||
draw_text_string_pos_norm("ぎょしょうとナレズシの研究 モンスーン・アジアの食事文化", font_demo_japanese, 24, {0.3, current_scroll - (section_start + 0.88)}, COLOR_WHITE)
|
||||
draw_text("Japanese", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.88) }, size = 19)
|
||||
draw_text("ぎょしょうとナレズシの研究 モンスーン・アジアの食事文化", demo_ctx.font_demo_japanese, { 0.3, current_scroll - (section_start + 0.88) }, size = 24)
|
||||
|
||||
draw_text_string_pos_norm("Korean", font_print, 19, {0.2, current_scroll - (section_start + 0.92)}, COLOR_WHITE)
|
||||
draw_text_string_pos_norm("그들의 장비와 기구는 모두 살아 있다.", font_demo_korean, 36, {0.3, current_scroll - (section_start + 0.92)}, COLOR_WHITE)
|
||||
draw_text("Korean", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.92) }, size = 19)
|
||||
draw_text("그들의 장비와 기구는 모두 살아 있다.", demo_ctx.font_demo_korean, { 0.3, current_scroll - (section_start + 0.92) }, size = 36)
|
||||
|
||||
draw_text_string_pos_norm("Needs harfbuzz to work:", font_print, 14, {0.2, current_scroll - (section_start + 0.96)}, COLOR_WHITE)
|
||||
draw_text("Needs harfbuzz to work:", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 0.96)}, size = 14)
|
||||
|
||||
draw_text_string_pos_norm("Arabic", font_print, 19, {0.2, current_scroll - (section_start + 1.00)}, COLOR_WHITE)
|
||||
draw_text_string_pos_norm("حب السماء لا تمطر غير الأحلام.", font_demo_arabic, 24, {0.3, current_scroll - (section_start + 1.00)}, COLOR_WHITE)
|
||||
draw_text("Arabic", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 1.00) }, size = 19)
|
||||
draw_text("حب السماء لا تمطر غير الأحلام.", demo_ctx.font_demo_arabic, { 0.3, current_scroll - (section_start + 1.00) }, size = 24)
|
||||
|
||||
draw_text_string_pos_norm("Hebrew", font_print, 19, {0.2, current_scroll - (section_start + 1.04)}, COLOR_WHITE)
|
||||
draw_text_string_pos_norm("אז הגיע הלילה של כוכב השביט הראשון.", font_demo_hebrew, 22, {0.3, current_scroll - (section_start + 1.04)}, COLOR_WHITE)
|
||||
draw_text("Hebrew", demo_ctx.font_print, { 0.2, current_scroll - (section_start + 1.04) }, size = 19)
|
||||
draw_text("אז הגיע הלילה של כוכב השביט הראשון.", demo_ctx.font_demo_hebrew, { 0.3, current_scroll - (section_start + 1.04) }, size = 22)
|
||||
}
|
||||
|
||||
// Zoom Test
|
||||
@@ -521,20 +486,18 @@ etiam dignissim diam quis enim. Convallis convallis tellus id interdum.`
|
||||
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", font_title, 92, {0.2, title_y}, COLOR_WHITE)
|
||||
draw_text("Zoom Test", demo_ctx.font_title, { 0.2, title_y }, size = 92)
|
||||
|
||||
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, _ := ve.resolve_zoom_size_scale(current_zoom, zoomed_text_base_size, 1.0, 2, 2, 999.0, demo_ctx.screen_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, font_firacode, 19, {0.2, zoom_info_y}, COLOR_WHITE)
|
||||
draw_text(current_zoom_text, demo_ctx.font_firacode, { 0.2, zoom_info_y })
|
||||
|
||||
ve.configure_snap( & demo_ctx.ve_ctx, u32(0), u32(0) )
|
||||
|
||||
size := measure_text_size( zoom_text, font_firacode, zoomed_text_base_size, 0 ) * current_zoom
|
||||
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, font_firacode, zoomed_text_base_size, zoomed_text_pos, current_zoom, COLOR_WHITE)
|
||||
draw_text(zoom_text, demo_ctx.font_firacode, zoomed_text_pos, size = zoomed_text_base_size, zoom = current_zoom)
|
||||
}
|
||||
|
||||
// Raincode Demo
|
||||
@@ -543,7 +506,7 @@ etiam dignissim diam quis enim. Convallis convallis tellus id interdum.`
|
||||
if current_scroll > section_start && current_scroll < section_end
|
||||
{
|
||||
GRID_W :: 80
|
||||
GRID_H :: 50
|
||||
GRID_H :: 45
|
||||
NUM_RAINDROPS :: GRID_W / 3
|
||||
|
||||
@static init_grid := false
|
||||
@@ -557,7 +520,7 @@ etiam dignissim diam quis enim. Convallis convallis tellus id interdum.`
|
||||
" ", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "Z", "T", "H", "E", "|", "¦", "日",
|
||||
"ハ", "ミ", "ヒ", "ー", "ウ", "シ", "ナ", "モ", "ニ", "サ", "ワ", "ツ", "オ", "リ", "ア", "ホ", "テ", "マ",
|
||||
"ケ", "メ", "エ", "カ", "キ", "ム", "ユ", "ラ", "セ", "ネ", "ス", "ツ", "タ", "ヌ", "ヘ", ":", "・", ".",
|
||||
"\"", "=", "*", "+", "-", "<", ">", "ç", "リ", "ク", "コ", "チ", "ヤ", "ル", "ン", "C", "O", "D"
|
||||
"\"", "=", "*", "+", "-", "<", ">", "ç", "リ", "ク", "コ", "チ", "ヤ", "ル", "ン", "C", "O", "D",
|
||||
}
|
||||
|
||||
if !init_grid {
|
||||
@@ -586,7 +549,7 @@ etiam dignissim diam quis enim. Convallis convallis tellus id interdum.`
|
||||
}
|
||||
|
||||
// Draw grid
|
||||
draw_text_string_pos_norm("Raincode demo", font_title, 92, { 0.2, current_scroll - (section_start + 0.2) }, COLOR_WHITE)
|
||||
draw_text("Raincode demo", demo_ctx.font_title, { 0.2, current_scroll - (section_start + 0.2) }, size = 92)
|
||||
for y in 0 ..< GRID_H do for x in 0 ..< GRID_W
|
||||
{
|
||||
pos_x := 0.2 + f32(x) * 0.007
|
||||
@@ -603,16 +566,14 @@ etiam dignissim diam quis enim. Convallis convallis tellus id interdum.`
|
||||
if code_colour.a == 0 do continue
|
||||
}
|
||||
|
||||
draw_text_string_pos_norm(codes[grid[y * GRID_W + x]], font_demo_raincode, 20, {pos_x, pos_y}, code_colour)
|
||||
draw_text(codes[grid[y * GRID_W + x]], demo_ctx.font_demo_raincode, { pos_x, pos_y }, size = 20, color = 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
|
||||
if current_scroll > section_start && current_scroll < section_end
|
||||
{
|
||||
GRID_W :: 30
|
||||
GRID_H :: 15
|
||||
@@ -629,7 +590,7 @@ etiam dignissim diam quis enim. Convallis convallis tellus id interdum.`
|
||||
@static fixed_timestep_passed : f32 = 0.0
|
||||
|
||||
fixed_timestep_passed += frame_duration
|
||||
fixed_timestep := f32(1.0 / 30.0)
|
||||
fixed_timestep := f32(1.0 / 120.0)
|
||||
for fixed_timestep_passed > fixed_timestep
|
||||
{
|
||||
rotate_current = (rotate_current + 1) % 4
|
||||
@@ -673,28 +634,28 @@ etiam dignissim diam quis enim. Convallis convallis tellus id interdum.`
|
||||
}
|
||||
|
||||
// Draw grid
|
||||
draw_text_string_pos_norm("Cache pressure test", font_title, 92, {0.2, current_scroll - (section_start + 0.2)}, COLOR_WHITE)
|
||||
draw_text("Cache pressure test (throttled to 120 hz)", demo_ctx.font_title, { 0.2, current_scroll - (section_start + 0.2) }, size = 72)
|
||||
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[:] ), font_demo_chinese, 24, {posx, posy}, COLOR_WHITE)
|
||||
draw_text(string( c[:] ), demo_ctx.font_demo_chinese, { posx, posy }, size = 24)
|
||||
}
|
||||
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[:] ), font_demo_grid2, 54, {posx, posy}, COLOR_WHITE)
|
||||
draw_text(string( c[:] ), demo_ctx.font_demo_grid2, { posx, posy }, size = 54)
|
||||
}
|
||||
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)
|
||||
posy := current_scroll - (section_start + 0.64 + f32(y) * 0.04)
|
||||
c := [5]u8{}
|
||||
codepoint_to_utf8( c[:], grid3[ y * GRID3_W + x ])
|
||||
draw_text_string_pos_norm(string( c[:] ), font_demo_grid3, 44, {posx, posy}, COLOR_WHITE)
|
||||
draw_text(string( c[:] ), demo_ctx.font_demo_grid3, { posx, posy }, size = 44)
|
||||
}
|
||||
}
|
||||
|
||||
|
31
ols.json
31
ols.json
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/DanielGavin/ols/master/misc/ols.schema.json",
|
||||
"odin_command": "odin.exe",
|
||||
"collections": [
|
||||
{
|
||||
"name": "backend",
|
||||
"path": "./backend"
|
||||
},
|
||||
{
|
||||
"name": "examples",
|
||||
"path": "./sectr"
|
||||
},
|
||||
{
|
||||
"name": "thirdparty",
|
||||
"path": "./thirdparty"
|
||||
}
|
||||
],
|
||||
"enable_document_symbols": true,
|
||||
"enable_fake_methods": true,
|
||||
"enable_format": false,
|
||||
"enable_hover": true,
|
||||
"enable_semantic_tokens": false,
|
||||
"enable_snippets": false,
|
||||
"enable_references": true,
|
||||
"thread_pool_count": 10,
|
||||
"enable_inlay_hints": true,
|
||||
"enable_procedure_context": true,
|
||||
"enable_procedure_snippet": false,
|
||||
"verbose": true,
|
||||
"disable_parser_errors": true
|
||||
}
|
@@ -12,7 +12,12 @@ Its assumed the user has Odin installed and exposed to the OS enviornment's PATH
|
||||
|
||||
#### Note on dependency packages
|
||||
|
||||
All dependencies are cloned directly into a created thirdparty directory.
|
||||
A custom version of the vendor:stb/truetype is maintained by this library:
|
||||
|
||||
* Added ability to set the stb_truetype allocator for STBTT_MALLOC and STBTT_FREE.
|
||||
* Changed procedure signatures to pass the font_info struct by immutable ptr (#by_ptr) when the C equivalent has their parameter as `const*`.
|
||||
|
||||
All other dependencies are cloned directly into a created thirdparty directory.
|
||||
|
||||
[harfbuzz](https://github.com/Ed94/odin_harfbuzz) is configured to pull & build the C++ library, it will use the MSVC toolchain (you can change it to use meson instead of preferred).
|
||||
[freetype](https://github.com/Ed94/odin-freetype) package has pre-built .lib files for windows (debug/release).
|
||||
|
@@ -67,6 +67,15 @@ push-location $path_thirdparty
|
||||
}
|
||||
pop-location
|
||||
|
||||
$path_stb_truetype = join-path $path_thirdparty 'stb\src'
|
||||
|
||||
push-location $path_stb_truetype
|
||||
$devshell = Join-Path $PSScriptRoot 'helpers/devshell.ps1'
|
||||
. $devshell -arch amd64
|
||||
|
||||
& .\build.bat
|
||||
pop-location
|
||||
|
||||
$odin_compiler_defs = join-path $PSScriptRoot 'helpers/odin_compiler_defs.ps1'
|
||||
. $odin_compiler_defs
|
||||
|
||||
@@ -96,8 +105,8 @@ function build-SokolBackendDemo
|
||||
$build_args += $flag_thread_count + $CoreCount_Physical
|
||||
# $build_args += $flag_optimize_none
|
||||
# $build_args += $flag_optimize_minimal
|
||||
# $build_args += $flag_optimize_speed
|
||||
$build_args += $falg_optimize_aggressive
|
||||
$build_args += $flag_optimize_speed
|
||||
# $build_args += $falg_optimize_aggressive
|
||||
# $build_args += $flag_debug
|
||||
$build_args += $flag_pdb_name + $pdb
|
||||
$build_args += $flag_subsystem + 'windows'
|
||||
@@ -111,6 +120,8 @@ function build-SokolBackendDemo
|
||||
# $build_args += $flag_sanitize_address
|
||||
# $build_args += $flag_sanitize_memory
|
||||
|
||||
Write-Host $build_args
|
||||
|
||||
Invoke-WithColorCodedOutput { & $odin_compiler $build_args }
|
||||
}
|
||||
build-SokolBackendDemo
|
||||
|
@@ -81,6 +81,12 @@ pushd "$path_thirdparty" > /dev/null
|
||||
fi
|
||||
popd > /dev/null
|
||||
|
||||
path_stb_truetype="$path_thirdparty/stb/src"
|
||||
|
||||
pushd "$path_stb_truetype" > /dev/null
|
||||
make
|
||||
popd > /dev/null
|
||||
|
||||
source "$(dirname "$0")/helpers/odin_compiler_defs.sh"
|
||||
|
||||
pkg_collection_backend="backend=$path_backend"
|
||||
|
@@ -6,4 +6,4 @@ path_scripts="$path_root/scripts"
|
||||
path_thirdparty="$path_root/thirdparty"
|
||||
|
||||
if [ -d "$path_build" ]; then rm -rf "$path_build"; fi
|
||||
# if [ -d "$path_thirdparty" ]; then rm -rf "$path_thirdparty"; fi
|
||||
# if [ -d "$path_thirdparty" ]; then rm -rf "$path_thirdparty"; fi
|
||||
|
0
thirdparty/stb/lib/.gitkeep
vendored
Normal file
0
thirdparty/stb/lib/.gitkeep
vendored
Normal file
BIN
thirdparty/stb/lib/darwin/stb_truetype.a
vendored
Normal file
BIN
thirdparty/stb/lib/darwin/stb_truetype.a
vendored
Normal file
Binary file not shown.
BIN
thirdparty/stb/lib/stb_truetype.a
vendored
Normal file
BIN
thirdparty/stb/lib/stb_truetype.a
vendored
Normal file
Binary file not shown.
BIN
thirdparty/stb/lib/stb_truetype.lib
vendored
Normal file
BIN
thirdparty/stb/lib/stb_truetype.lib
vendored
Normal file
Binary file not shown.
BIN
thirdparty/stb/lib/stb_truetype_wasm.o
vendored
Normal file
BIN
thirdparty/stb/lib/stb_truetype_wasm.o
vendored
Normal file
Binary file not shown.
27
thirdparty/stb/src/Makefile
vendored
Normal file
27
thirdparty/stb/src/Makefile
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
OS=$(shell uname)
|
||||
|
||||
ifeq ($(OS), Darwin)
|
||||
all: darwin
|
||||
else
|
||||
all: unix
|
||||
endif
|
||||
|
||||
$(info Current OS is: $(OS))
|
||||
|
||||
wasm:
|
||||
mkdir -p ../lib
|
||||
$(CC) -c -Os --target=wasm32 --sysroot=$(shell odin root)/vendor/libc stb_truetype.c -o ../lib/stb_truetype_wasm.o
|
||||
|
||||
unix:
|
||||
mkdir -p ../lib
|
||||
$(CC) -c -O2 -Os -fPIC stb_truetype.c
|
||||
$(AR) rcs ../lib/stb_truetype.a stb_truetype.o
|
||||
#$(CC) -fPIC -shared -Wl,-soname=stb_truetype.so -o ../lib/stb_truetype.so stb_image_truetype.o
|
||||
rm *.o
|
||||
|
||||
darwin:
|
||||
mkdir -p ../lib
|
||||
$(CC) -arch x86_64 -c -O2 -Os -fPIC stb_truetype.c -o stb_truetype-x86_64.o -mmacosx-version-min=10.12
|
||||
$(CC) -arch arm64 -c -O2 -Os -fPIC stb_truetype.c -o stb_truetype-arm64.o -mmacosx-version-min=10.12
|
||||
lipo -create stb_truetype-x86_64.o stb_truetype-arm64.o -output ../lib/darwin/stb_truetype.a
|
||||
rm *.o
|
8
thirdparty/stb/src/build.bat
vendored
Normal file
8
thirdparty/stb/src/build.bat
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
@echo off
|
||||
|
||||
if not exist "..\lib" mkdir ..\lib
|
||||
|
||||
cl -nologo -MT -TC -O2 -c stb_image.c stb_truetype.c
|
||||
lib -nologo stb_truetype.obj -out:..\lib\stb_truetype.lib
|
||||
|
||||
del *.obj
|
2
thirdparty/stb/src/stb_truetype.c
vendored
Normal file
2
thirdparty/stb/src/stb_truetype.c
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
#define STB_TRUETYPE_IMPLEMENTATION
|
||||
#include "stb_truetype.h"
|
5114
thirdparty/stb/src/stb_truetype.h
vendored
Normal file
5114
thirdparty/stb/src/stb_truetype.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
19055
thirdparty/stb/src/zpl/zpl.h
vendored
Normal file
19055
thirdparty/stb/src/zpl/zpl.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
660
thirdparty/stb/truetype/stb_truetype.odin
vendored
Normal file
660
thirdparty/stb/truetype/stb_truetype.odin
vendored
Normal file
@@ -0,0 +1,660 @@
|
||||
package stb_truetype
|
||||
|
||||
import c "core:c"
|
||||
import stbrp "vendor:stb/rect_pack"
|
||||
|
||||
@(private)
|
||||
LIB :: (
|
||||
"../lib/stb_truetype.lib" when ODIN_OS == .Windows
|
||||
else "../lib/stb_truetype.a" when ODIN_OS == .Linux
|
||||
else "../lib/darwin/stb_truetype.a" when ODIN_OS == .Darwin
|
||||
else "../lib/stb_truetype_wasm.o" when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32
|
||||
else ""
|
||||
)
|
||||
|
||||
when LIB != "" {
|
||||
when !#exists(LIB) {
|
||||
#panic("Could not find the compiled STB libraries, they can be compiled by running `make -C \"" + ODIN_ROOT + "vendor/stb/src\"`")
|
||||
}
|
||||
}
|
||||
|
||||
when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 {
|
||||
foreign import stbtt "../lib/stb_truetype_wasm.o"
|
||||
} else when LIB != "" {
|
||||
foreign import stbtt { LIB }
|
||||
} else {
|
||||
foreign import stbtt "system:stb_truetype"
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
////
|
||||
//// INTERFACE
|
||||
////
|
||||
////
|
||||
|
||||
#assert(size_of(c.int) == size_of(rune))
|
||||
#assert(size_of(c.int) == size_of(b32))
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// CUSTOM: ODIN COMPATIBLE ALLOCATOR
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
zpl_allocator_type :: enum(i32) {
|
||||
Alloc,
|
||||
Free,
|
||||
FreeAll,
|
||||
Resize,
|
||||
}
|
||||
|
||||
zpl_allocator_proc :: #type proc(allocator_data: rawptr, type: zpl_allocator_type,
|
||||
size: c.ssize_t, alignment: c.ssize_t,
|
||||
old_memory: rawptr, old_size: c.ssize_t,
|
||||
flags : c.ulonglong
|
||||
) -> rawptr
|
||||
|
||||
zpl_allocator :: struct {
|
||||
procedure: zpl_allocator_proc,
|
||||
data: rawptr,
|
||||
}
|
||||
|
||||
@(default_calling_convention="c", link_prefix="stbtt_")
|
||||
foreign stbtt {
|
||||
SetAllocator :: proc(allocator : zpl_allocator) ---
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// END CUSTOM: ODIN COMPATIBLE ALLOCATOR
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TEXTURE BAKING API
|
||||
//
|
||||
// If you use this API, you only have to call two functions ever.
|
||||
//
|
||||
|
||||
bakedchar :: struct {
|
||||
x0, y0, x1, y1: u16, // coordinates of bbox in bitmap
|
||||
xoff, yoff, xadvance: f32,
|
||||
}
|
||||
|
||||
aligned_quad :: struct {
|
||||
x0, y0, s0, t0: f32, // top-left
|
||||
x1, y1, s1, t1: f32, // bottom-right
|
||||
}
|
||||
|
||||
|
||||
|
||||
// bindings
|
||||
@(default_calling_convention="c", link_prefix="stbtt_")
|
||||
foreign stbtt {
|
||||
// if return is positive, the first unused row of the bitmap
|
||||
// if return is negative, returns the negative of the number of characters that fit
|
||||
// if return is 0, no characters fit and no rows were used
|
||||
// This uses a very crappy packing.
|
||||
BakeFontBitmap :: proc(data: [^]byte, offset: c.int, // font location (use offset=0 for plain .ttf)
|
||||
pixel_height: f32, // height of font in pixels
|
||||
pixels: [^]byte, pw, ph: c.int, // bitmap to be filled in
|
||||
first_char, num_chars: c.int, // characters to bake
|
||||
chardata: [^]bakedchar, // you allocate this, it's num_chars long
|
||||
) -> c.int ---
|
||||
|
||||
// Call GetBakedQuad with char_index = 'character - first_char', and it
|
||||
// creates the quad you need to draw and advances the current position.
|
||||
//
|
||||
// The coordinate system used assumes y increases downwards.
|
||||
//
|
||||
// Characters will extend both above and below the current position;
|
||||
// see discussion of "BASELINE" above.
|
||||
//
|
||||
// It's inefficient; you might want to c&p it and optimize it.
|
||||
GetBakedQuad :: proc(chardata: ^bakedchar, pw, ph: c.int, // same data as above
|
||||
char_index: c.int, // character to display
|
||||
xpos, ypos: ^f32, // pointers to current position in screen pixel space
|
||||
q: ^aligned_quad, // output: quad to draw
|
||||
opengl_fillrule: b32, // true if opengl fill rule; false if DX9 or earlier
|
||||
) ---
|
||||
|
||||
// Query the font vertical metrics without having to create a font first.
|
||||
GetScaledFontVMetrics :: proc(fontdata: [^]byte, index: c.int, size: f32, ascent, descent, lineGap: ^f32) ---
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// NEW TEXTURE BAKING API
|
||||
//
|
||||
// This provides options for packing multiple fonts into one atlas, not
|
||||
// perfectly but better than nothing.
|
||||
|
||||
packedchar :: struct {
|
||||
x0, y0, x1, y1: u16,
|
||||
xoff, yoff, xadvance: f32,
|
||||
xoff2, yoff2: f32,
|
||||
}
|
||||
|
||||
pack_range :: struct {
|
||||
font_size: f32,
|
||||
first_unicode_codepoint_in_range: c.int,
|
||||
array_of_unicode_codepoints: [^]rune,
|
||||
num_chars: c.int,
|
||||
chardata_for_range: ^packedchar,
|
||||
_, _: u8, // used internally to store oversample info
|
||||
}
|
||||
|
||||
pack_context :: struct {
|
||||
user_allocator_context, pack_info: rawptr,
|
||||
width, height, stride_in_bytes, padding: c.int,
|
||||
skip_missing: b32,
|
||||
h_oversample, v_oversample: u32,
|
||||
pixels: [^]byte,
|
||||
nodes: rawptr,
|
||||
}
|
||||
|
||||
POINT_SIZE :: #force_inline proc(x: $T) -> T { return -x } // @NOTE: this was a macro
|
||||
|
||||
// bindings
|
||||
@(default_calling_convention="c", link_prefix="stbtt_")
|
||||
foreign stbtt {
|
||||
// Initializes a packing context stored in the passed-in stbtt_pack_context.
|
||||
// Future calls using this context will pack characters into the bitmap passed
|
||||
// in here: a 1-channel bitmap that is width * height. stride_in_bytes is
|
||||
// the distance from one row to the next (or 0 to mean they are packed tightly
|
||||
// together). "padding" is the amount of padding to leave between each
|
||||
// character (normally you want '1' for bitmaps you'll use as textures with
|
||||
// bilinear filtering).
|
||||
//
|
||||
// Returns 0 on failure, 1 on success.
|
||||
PackBegin :: proc(spc: ^pack_context, pixels: [^]byte, width, height, stride_in_bytes, padding: c.int, alloc_context: rawptr) -> c.int ---
|
||||
|
||||
// Cleans up the packing context and frees all memory.
|
||||
PackEnd :: proc(spc: ^pack_context) ---
|
||||
|
||||
// Creates character bitmaps from the font_index'th font found in fontdata (use
|
||||
// font_index=0 if you don't know what that is). It creates num_chars_in_range
|
||||
// bitmaps for characters with unicode values starting at first_unicode_char_in_range
|
||||
// and increasing. Data for how to render them is stored in chardata_for_range;
|
||||
// pass these to stbtt_GetPackedQuad to get back renderable quads.
|
||||
//
|
||||
// font_size is the full height of the character from ascender to descender,
|
||||
// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed
|
||||
// by stbtt_ScaleForMappingEmToPixels, wrap the point size in POINT_SIZE()
|
||||
// and pass that result as 'font_size':
|
||||
// ..., 20 , ... // font max minus min y is 20 pixels tall
|
||||
// ..., POINT_SIZE(20), ... // 'M' is 20 pixels tall
|
||||
PackFontRange :: proc(spc: ^pack_context, fontdata: [^]byte, font_index: c.int, font_size: f32, first_unicode_char_in_range, num_chars_in_range: c.int, chardata_for_range: ^packedchar) -> c.int ---
|
||||
|
||||
// Creates character bitmaps from multiple ranges of characters stored in
|
||||
// ranges. This will usually create a better-packed bitmap than multiple
|
||||
// calls to stbtt_PackFontRange. Note that you can call this multiple
|
||||
// times within a single PackBegin/PackEnd.
|
||||
PackFontRanges :: proc(spc: ^pack_context, fontdata: [^]byte, font_index: c.int, ranges: [^]pack_range, num_ranges: c.int) -> c.int ---
|
||||
|
||||
// Oversampling a font increases the quality by allowing higher-quality subpixel
|
||||
// positioning, and is especially valuable at smaller text sizes.
|
||||
//
|
||||
// This function sets the amount of oversampling for all following calls to
|
||||
// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given
|
||||
// pack context. The default (no oversampling) is achieved by h_oversample=1
|
||||
// and v_oversample=1. The total number of pixels required is
|
||||
// h_oversample*v_oversample larger than the default; for example, 2x2
|
||||
// oversampling requires 4x the storage of 1x1. For best results, render
|
||||
// oversampled textures with bilinear filtering. Look at the readme in
|
||||
// stb/tests/oversample for information about oversampled fonts
|
||||
//
|
||||
// To use with PackFontRangesGather etc., you must set it before calls
|
||||
// call to PackFontRangesGatherRects.
|
||||
PackSetOversampling :: proc(spc: ^pack_context, h_oversample, v_oversample: c.uint) ---
|
||||
|
||||
// If skip != false, this tells stb_truetype to skip any codepoints for which
|
||||
// there is no corresponding glyph. If skip=false, which is the default, then
|
||||
// codepoints without a glyph recived the font's "missing character" glyph,
|
||||
// typically an empty box by convention.
|
||||
PackSetSkipMissingCodepoints :: proc(spc: ^pack_context, skip: b32) ---
|
||||
|
||||
GetPackedQuad :: proc(chardata: ^packedchar, pw, ph: c.int, // same data as above
|
||||
char_index: c.int, // character to display
|
||||
xpos, ypos: ^f32, // pointers to current position in screen pixel space
|
||||
q: ^aligned_quad, // output: quad to draw
|
||||
align_to_integer: b32,
|
||||
) ---
|
||||
|
||||
// Calling these functions in sequence is roughly equivalent to calling
|
||||
// stbtt_PackFontRanges(). If you more control over the packing of multiple
|
||||
// fonts, or if you want to pack custom data into a font texture, take a look
|
||||
// at the source to of stbtt_PackFontRanges() and create a custom version
|
||||
// using these functions, e.g. call GatherRects multiple times,
|
||||
// building up a single array of rects, then call PackRects once,
|
||||
// then call RenderIntoRects repeatedly. This may result in a
|
||||
// better packing than calling PackFontRanges multiple times
|
||||
// (or it may not).
|
||||
PackFontRangesGatherRects :: proc(spc: ^pack_context, #by_ptr info: fontinfo, ranges: ^pack_range, num_ranges: c.int, rects: [^]stbrp.Rect) -> c.int ---
|
||||
PackFontRangesPackRects :: proc(spc: ^pack_context, rects: [^]stbrp.Rect, num_rects: c.int) ---
|
||||
PackFontRangesRenderIntoRects :: proc(spc: ^pack_context, #by_ptr info: fontinfo, ranges: ^pack_range, num_ranges: c.int, rects: [^]stbrp.Rect) -> c.int ---
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// FONT LOADING
|
||||
//
|
||||
//
|
||||
|
||||
fontinfo :: struct {
|
||||
userdata: rawptr,
|
||||
data: [^]byte,
|
||||
fontstart: c.int,
|
||||
|
||||
numGlyphs: c.int,
|
||||
|
||||
loca, head, glyf, hhea, hmtx, kern, gpos, svg: c.int,
|
||||
index_map: c.int,
|
||||
indexToLocFormat: c.int,
|
||||
|
||||
cff: _buf,
|
||||
charstrings: _buf,
|
||||
gsubrs: _buf,
|
||||
subrs: _buf,
|
||||
fontdicts: _buf,
|
||||
fdselect: _buf,
|
||||
}
|
||||
|
||||
@(default_calling_convention="c", link_prefix="stbtt_")
|
||||
foreign stbtt {
|
||||
// Given an offset into the file that defines a font, this function builds
|
||||
// the necessary cached info for the rest of the system. You must allocate
|
||||
// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't
|
||||
// need to do anything special to free it, because the contents are pure
|
||||
// value data with no additional data structures. Returns 0 on failure.
|
||||
InitFont :: proc(info: ^fontinfo, data: [^]byte, offset: c.int) -> b32 ---
|
||||
|
||||
// This function will determine the number of fonts in a font file. TrueType
|
||||
// collection (.ttc) files may contain multiple fonts, while TrueType font
|
||||
// (.ttf) files only contain one font. The number of fonts can be used for
|
||||
// indexing with the previous function where the index is between zero and one
|
||||
// less than the total fonts. If an error occurs, -1 is returned.
|
||||
GetNumberOfFonts :: proc(data: [^]byte) -> c.int ---
|
||||
|
||||
// Each .ttf/.ttc file may have more than one font. Each font has a sequential
|
||||
// index number starting from 0. Call this function to get the font offset for
|
||||
// a given index; it returns -1 if the index is out of range. A regular .ttf
|
||||
// file will only define one font and it always be at offset 0, so it will
|
||||
// return '0' for index 0, and -1 for all other indices.
|
||||
GetFontOffsetForIndex :: proc(data: [^]byte, index: c.int) -> c.int ---
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// CHARACTER TO GLYPH-INDEX CONVERSION
|
||||
|
||||
@(default_calling_convention="c", link_prefix="stbtt_")
|
||||
foreign stbtt {
|
||||
// If you're going to perform multiple operations on the same character
|
||||
// and you want a speed-up, call this function with the character you're
|
||||
// going to process, then use glyph-based functions instead of the
|
||||
// codepoint-based functions.
|
||||
// Returns 0 if the character codepoint is not defined in the font.
|
||||
FindGlyphIndex :: proc(#by_ptr info: fontinfo, unicode_codepoint: rune) -> c.int ---
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// CHARACTER PROPERTIES
|
||||
//
|
||||
|
||||
@(default_calling_convention="c", link_prefix="stbtt_")
|
||||
foreign stbtt {
|
||||
// computes a scale factor to produce a font whose "height" is 'pixels' tall.
|
||||
// Height is measured as the distance from the highest ascender to the lowest
|
||||
// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics
|
||||
// and computing:
|
||||
// scale = pixels / (ascent - descent)
|
||||
// so if you prefer to measure height by the ascent only, use a similar calculation.
|
||||
ScaleForPixelHeight :: proc(#by_ptr info: fontinfo, pixels: f32) -> f32 ---
|
||||
|
||||
// computes a scale factor to produce a font whose EM size is mapped to
|
||||
// 'pixels' tall. This is probably what traditional APIs compute, but
|
||||
// I'm not positive.
|
||||
ScaleForMappingEmToPixels :: proc(#by_ptr info: fontinfo, pixels: f32) -> f32 ---
|
||||
|
||||
// ascent is the coordinate above the baseline the font extends; descent
|
||||
// is the coordinate below the baseline the font extends (i.e. it is typically negative)
|
||||
// lineGap is the spacing between one row's descent and the next row's ascent...
|
||||
// so you should advance the vertical position by "*ascent - *descent + *lineGap"
|
||||
// these are expressed in unscaled coordinates, so you must multiply by
|
||||
// the scale factor for a given size
|
||||
GetFontVMetrics :: proc(#by_ptr info: fontinfo, ascent, descent, lineGap: ^c.int) ---
|
||||
|
||||
// analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2
|
||||
// table (specific to MS/Windows TTF files).
|
||||
//
|
||||
// Returns 1 on success (table present), 0 on failure.
|
||||
GetFontVMetricsOS2 :: proc(#by_ptr info: fontinfo, typoAscent, typoDescent, typoLineGap: ^c.int) -> b32 ---
|
||||
|
||||
// the bounding box around all possible characters
|
||||
GetFontBoundingBox :: proc(#by_ptr info: fontinfo, x0, y0, x1, y1: ^c.int) ---
|
||||
|
||||
// leftSideBearing is the offset from the current horizontal position to the left edge of the character
|
||||
// advanceWidth is the offset from the current horizontal position to the next horizontal position
|
||||
// these are expressed in unscaled coordinates
|
||||
GetCodepointHMetrics :: proc(#by_ptr info: fontinfo, codepoint: rune, advanceWidth, leftSideBearing: ^c.int) ---
|
||||
|
||||
// an additional amount to add to the 'advance' value between ch1 and ch2
|
||||
GetCodepointKernAdvance :: proc(#by_ptr info: fontinfo, ch1, ch2: rune) -> (advance: c.int) ---
|
||||
|
||||
// Gets the bounding box of the visible part of the glyph, in unscaled coordinates
|
||||
GetCodepointBox :: proc(#by_ptr info: fontinfo, codepoint: rune, x0, y0, x1, y1: ^c.int) -> c.int ---
|
||||
|
||||
// as above, but takes one or more glyph indices for greater efficiency
|
||||
GetGlyphHMetrics :: proc(#by_ptr info: fontinfo, glyph_index: c.int, advanceWidth, leftSideBearing: ^c.int) ---
|
||||
GetGlyphKernAdvance :: proc(#by_ptr info: fontinfo, glyph1, glyph2: c.int) -> c.int ---
|
||||
GetGlyphBox :: proc(#by_ptr info : fontinfo, glyph_index: c.int, x0, y0, x1, y1: ^c.int) -> c.int ---
|
||||
}
|
||||
|
||||
kerningentry :: struct {
|
||||
glyph1: rune, // use FindGlyphIndex
|
||||
glyph2: rune,
|
||||
advance: c.int,
|
||||
}
|
||||
|
||||
@(default_calling_convention="c", link_prefix="stbtt_")
|
||||
foreign stbtt {
|
||||
// Retrieves a complete list of all of the kerning pairs provided by the font
|
||||
// stbtt_GetKerningTable never writes more than table_length entries and returns how many entries it did write.
|
||||
// The table will be sorted by (a.glyph1 == b.glyph1)?(a.glyph2 < b.glyph2):(a.glyph1 < b.glyph1)
|
||||
GetKerningTableLength :: proc(#by_ptr info: fontinfo) -> c.int ---
|
||||
GetKerningTable :: proc(#by_ptr info: fontinfo, table: [^]kerningentry, table_length: c.int) -> c.int ---
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// GLYPH SHAPES (you probably don't need these, but they have to go before
|
||||
// the bitmaps for C declaration-order reasons)
|
||||
//
|
||||
|
||||
vmove :: enum c.int {
|
||||
none,
|
||||
vmove=1,
|
||||
vline,
|
||||
vcurve,
|
||||
vcubic,
|
||||
}
|
||||
|
||||
vertex_type :: distinct c.short // can't use stbtt_int16 because that's not visible in the header file
|
||||
vertex :: struct {
|
||||
x, y, cx, cy, cx1, cy1: vertex_type,
|
||||
type, padding: byte,
|
||||
}
|
||||
|
||||
@(default_calling_convention="c", link_prefix="stbtt_")
|
||||
foreign stbtt {
|
||||
// returns true if nothing is drawn for this glyph
|
||||
IsGlyphEmpty :: proc(#by_ptr info: fontinfo, glyph_index: c.int) -> b32 ---
|
||||
|
||||
// returns # of vertices and fills *vertices with the pointer to them
|
||||
// these are expressed in "unscaled" coordinates
|
||||
//
|
||||
// The shape is a series of contours. Each one starts with
|
||||
// a STBTT_moveto, then consists of a series of mixed
|
||||
// STBTT_lineto and STBTT_curveto segments. A lineto
|
||||
// draws a line from previous endpoint to its x,y; a curveto
|
||||
// draws a quadratic bezier from previous endpoint to
|
||||
// its x,y, using cx,cy as the bezier control point.
|
||||
GetCodepointShape :: proc(#by_ptr info: fontinfo, unicode_codepoint: rune, vertices: ^[^]vertex) -> c.int ---
|
||||
GetGlyphShape :: proc(#by_ptr info: fontinfo, glyph_index: c.int, vertices: ^[^]vertex) -> c.int ---
|
||||
|
||||
// frees the data allocated above
|
||||
FreeShape :: proc(#by_ptr info: fontinfo, vertices: [^]vertex) ---
|
||||
|
||||
// fills svg with the character's SVG data.
|
||||
// returns data size or 0 if SVG not found.
|
||||
FindSVGDoc :: proc(#by_ptr info: fontinfo, gl: b32) -> [^]byte ---
|
||||
GetCodepointSVG :: proc(#by_ptr info: fontinfo, unicode_codepoint: rune, svg: ^cstring) -> c.int ---
|
||||
GetGlyphSVG :: proc(#by_ptr info: fontinfo, gl: b32, svg: ^cstring) -> c.int ---
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// BITMAP RENDERING
|
||||
//
|
||||
|
||||
_bitmap :: struct {
|
||||
w, h, stride: c.int,
|
||||
pixels: [^]byte,
|
||||
}
|
||||
|
||||
@(default_calling_convention="c", link_prefix="stbtt_")
|
||||
foreign stbtt {
|
||||
// frees the bitmap allocated below
|
||||
FreeBitmap :: proc(bitmap: [^]byte, userdata: rawptr) ---
|
||||
|
||||
// allocates a large-enough single-channel 8bpp bitmap and renders the
|
||||
// specified character/glyph at the specified scale into it, with
|
||||
// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque).
|
||||
// *width & *height are filled out with the width & height of the bitmap,
|
||||
// which is stored left-to-right, top-to-bottom.
|
||||
//
|
||||
// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap
|
||||
GetCodepointBitmap :: proc(#by_ptr info: fontinfo, scale_x, scale_y: f32, codepoint: rune, width, height, xoff, yoff: ^c.int) -> [^]byte ---
|
||||
|
||||
// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel
|
||||
// shift for the character
|
||||
GetCodepointBitmapSubpixel :: proc(#by_ptr info: fontinfo, scale_x, scale_y, shift_x, shift_y: f32, codepoint: rune, width, height, xoff, yoff: ^c.int) -> [^]byte ---
|
||||
|
||||
// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap
|
||||
// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap
|
||||
// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the
|
||||
// width and height and positioning info for it first.
|
||||
MakeCodepointBitmap :: proc(#by_ptr info: fontinfo, output: [^]byte, out_w, out_h, out_stride: c.int, scale_x, scale_y: f32, codepoint: rune) ---
|
||||
|
||||
// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel
|
||||
// shift for the character
|
||||
MakeCodepointBitmapSubpixel :: proc(#by_ptr info: fontinfo, output: [^]byte, out_w, out_h, out_stride: c.int, scale_x, scale_y, shift_x, shift_y: f32, codepoint: rune) ---
|
||||
|
||||
// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering
|
||||
// is performed (see stbtt_PackSetOversampling)
|
||||
MakeCodepointBitmapSubpixelPrefilter :: proc(#by_ptr info: fontinfo, output: [^]byte, out_w, out_h, out_stride: c.int, scale_x, scale_y, shift_x, shift_y: f32, oversample_x, oversample_y: b32, sub_x, sub_y: ^f32, codepoint: rune) ---
|
||||
|
||||
// get the bbox of the bitmap centered around the glyph origin; so the
|
||||
// bitmap width is ix1-ix0, height is iy1-iy0, and location to place
|
||||
// the bitmap top left is (leftSideBearing*scale,iy0).
|
||||
// (Note that the bitmap uses y-increases-down, but the shape uses
|
||||
// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.)
|
||||
GetCodepointBitmapBox :: proc(#by_ptr font: fontinfo, codepoint: rune, scale_x, scale_y: f32, ix0, iy0, ix1, iy1: ^c.int) ---
|
||||
|
||||
// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel
|
||||
// shift for the character
|
||||
GetCodepointBitmapBoxSubpixel :: proc(#by_ptr font: fontinfo, codepoint: rune, scale_x, scale_y, shift_x, shift_y: f32, ix0, iy0, ix1, iy1: ^c.int) ---
|
||||
|
||||
// the following functions are equivalent to the above functions, but operate
|
||||
// on glyph indices instead of Unicode codepoints (for efficiency)
|
||||
GetGlyphBitmap :: proc(#by_ptr info: fontinfo, scale_x, scale_y: f32, glyph: c.int, width, height, xoff, yoff: ^c.int) -> [^]byte ---
|
||||
GetGlyphBitmapSubpixel :: proc(#by_ptr info: fontinfo, scale_x, scale_y, shift_x, shift_y: f32, glyph: c.int, width, height, xoff, yoff: ^c.int) -> [^]byte ---
|
||||
MakeGlyphBitmap :: proc(#by_ptr info: fontinfo, output: [^]byte, out_w, out_h, out_stride: c.int, scale_x, scale_y: f32, glyph: c.int) ---
|
||||
MakeGlyphBitmapSubpixel :: proc(#by_ptr info: fontinfo, output: [^]byte, out_w, out_h, out_stride: c.int, scale_x, scale_y, shift_x, shift_y: f32, glyph: c.int) ---
|
||||
MakeGlyphBitmapSubpixelPrefilter :: proc(#by_ptr info: fontinfo, output: [^]byte, out_w, out_h, out_stride: c.int, scale_x, scale_y, shift_x, shift_y: f32, oversample_x, oversample_y: c.int, sub_x, sub_y: ^f32, glyph: c.int) ---
|
||||
GetGlyphBitmapBox :: proc(#by_ptr font: fontinfo, glyph: c.int, scale_x, scale_y: f32, ix0, iy0, ix1, iy1: ^c.int) ---
|
||||
GetGlyphBitmapBoxSubpixel :: proc(#by_ptr font: fontinfo, glyph: c.int, scale_x, scale_y, shift_x, shift_y: f32, ix0, iy0, ix1, iy1: ^c.int) ---
|
||||
|
||||
// rasterize a shape with quadratic beziers into a bitmap
|
||||
Rasterize :: proc(result: ^_bitmap, // 1-channel bitmap to draw into
|
||||
flatness_in_pixels: f32, // allowable error of curve in pixels
|
||||
vertices: [^]vertex, // array of vertices defining shape
|
||||
num_verts: c.int, // number of vertices in above array
|
||||
scale_x, scale_y: f32, // scale applied to input vertices
|
||||
shift_x, shift_y: f32, // translation applied to input vertices
|
||||
x_off, y_off: c.int, // another translation applied to input
|
||||
invert: b32, // if non-zero, vertically flip shape
|
||||
userdata: rawptr, // context for to STBTT_MALLOC
|
||||
) ---
|
||||
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Signed Distance Function (or Field) rendering
|
||||
//
|
||||
|
||||
@(default_calling_convention="c", link_prefix="stbtt_")
|
||||
foreign stbtt {
|
||||
// frees the SDF bitmap allocated below
|
||||
FreeSDF :: proc(bitmap: [^]byte, userdata: rawptr) ---
|
||||
|
||||
// These functions compute a discretized SDF field for a single character, suitable for storing
|
||||
// in a single-channel texture, sampling with bilinear filtering, and testing against
|
||||
// larger than some threshold to produce scalable fonts.
|
||||
// info -- the font
|
||||
// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap
|
||||
// glyph/codepoint -- the character to generate the SDF for
|
||||
// padding -- extra "pixels" around the character which are filled with the distance to the character (not 0),
|
||||
// which allows effects like bit outlines
|
||||
// onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character)
|
||||
// pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale)
|
||||
// if positive, > onedge_value is inside; if negative, < onedge_value is inside
|
||||
// width,height -- output height & width of the SDF bitmap (including padding)
|
||||
// xoff,yoff -- output origin of the character
|
||||
// return value -- a 2D array of bytes 0..255, width*height in size
|
||||
//
|
||||
// pixel_dist_scale & onedge_value are a scale & bias that allows you to make
|
||||
// optimal use of the limited 0..255 for your application, trading off precision
|
||||
// and special effects. SDF values outside the range 0..255 are clamped to 0..255.
|
||||
//
|
||||
// Example:
|
||||
// scale = stbtt_ScaleForPixelHeight(22)
|
||||
// padding = 5
|
||||
// onedge_value = 180
|
||||
// pixel_dist_scale = 180/5.0 = 36.0
|
||||
//
|
||||
// This will create an SDF bitmap in which the character is about 22 pixels
|
||||
// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled
|
||||
// shape, sample the SDF at each pixel and fill the pixel if the SDF value
|
||||
// is greater than or equal to 180/255. (You'll actually want to antialias,
|
||||
// which is beyond the scope of this example.) Additionally, you can compute
|
||||
// offset outlines (e.g. to stroke the character border inside & outside,
|
||||
// or only outside). For example, to fill outside the character up to 3 SDF
|
||||
// pixels, you would compare against (180-36.0*3)/255 = 72/255. The above
|
||||
// choice of variables maps a range from 5 pixels outside the shape to
|
||||
// 2 pixels inside the shape to 0..255; this is intended primarily for apply
|
||||
// outside effects only (the interior range is needed to allow proper
|
||||
// antialiasing of the font at *smaller* sizes)
|
||||
//
|
||||
// The function computes the SDF analytically at each SDF pixel, not by e.g.
|
||||
// building a higher-res bitmap and approximating it. In theory the quality
|
||||
// should be as high as possible for an SDF of this size & representation, but
|
||||
// unclear if this is true in practice (perhaps building a higher-res bitmap
|
||||
// and computing from that can allow drop-out prevention).
|
||||
//
|
||||
// The algorithm has not been optimized at all, so expect it to be slow
|
||||
// if computing lots of characters or very large sizes.
|
||||
|
||||
GetGlyphSDF :: proc(#by_ptr info: fontinfo, scale: f32, glyph, padding: c.int, onedge_value: u8, pixel_dist_scale: f32, width, height, xoff, yoff: ^c.int) -> [^]byte ---
|
||||
GetCodepointSDF :: proc(#by_ptr info: fontinfo, scale: f32, codepoint, padding: c.int, onedge_value: u8, pixel_dist_scale: f32, width, height, xoff, yoff: ^c.int) -> [^]byte ---
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Finding the right font...
|
||||
//
|
||||
// You should really just solve this offline, keep your own tables
|
||||
// of what font is what, and don't try to get it out of the .ttf file.
|
||||
// That's because getting it out of the .ttf file is really hard, because
|
||||
// the names in the file can appear in many possible encodings, in many
|
||||
// possible languages, and e.g. if you need a case-insensitive comparison,
|
||||
// the details of that depend on the encoding & language in a complex way
|
||||
// (actually underspecified in truetype, but also gigantic).
|
||||
//
|
||||
// But you can use the provided functions in two possible ways:
|
||||
// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on
|
||||
// unicode-encoded names to try to find the font you want;
|
||||
// you can run this before calling stbtt_InitFont()
|
||||
//
|
||||
// stbtt_GetFontNameString() lets you get any of the various strings
|
||||
// from the file yourself and do your own comparisons on them.
|
||||
// You have to have called stbtt_InitFont() first.
|
||||
|
||||
MACSTYLE_DONTCARE :: 0
|
||||
MACSTYLE_BOLD :: 1
|
||||
MACSTYLE_ITALIC :: 2
|
||||
MACSTYLE_UNDERSCORE :: 4
|
||||
MACSTYLE_NONE :: 8 // <= not same as 0, this makes us check the bitfield is 0
|
||||
|
||||
@(default_calling_convention="c", link_prefix="stbtt_")
|
||||
foreign stbtt {
|
||||
// returns the offset (not index) of the font that matches, or -1 if none
|
||||
// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold".
|
||||
// if you use any other flag, use a font name like "Arial"; this checks
|
||||
// the 'macStyle' header field; i don't know if fonts set this consistently
|
||||
FindMatchingFont :: proc(fontdata: [^]byte, name: cstring, flags: c.int) -> c.int ---
|
||||
|
||||
// returns 1/0 whether the first string interpreted as utf8 is identical to
|
||||
// the second string interpreted as big-endian utf16... useful for strings from next func
|
||||
CompareUTF8toUTF16_bigendian :: proc(s1: cstring, len1: c.int, s2: cstring, len2: c.int) -> c.int ---
|
||||
|
||||
// returns the string (which may be big-endian double byte, e.g. for unicode)
|
||||
// and puts the length in bytes in *length.
|
||||
//
|
||||
// some of the values for the IDs are below; for more see the truetype spec:
|
||||
// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html
|
||||
// http://www.microsoft.com/typography/otspec/name.htm
|
||||
GetFontNameString :: proc(#by_ptr font: fontinfo, length: ^c.int, platformID: PLATFORM_ID, encodingID, languageID, nameID: c.int) -> cstring ---
|
||||
}
|
||||
|
||||
|
||||
PLATFORM_ID :: enum c.int { // platformID
|
||||
PLATFORM_ID_UNICODE = 0,
|
||||
PLATFORM_ID_MAC = 1,
|
||||
PLATFORM_ID_ISO = 2,
|
||||
PLATFORM_ID_MICROSOFT = 3,
|
||||
}
|
||||
|
||||
// encodingID for PLATFORM_ID_UNICODE
|
||||
UNICODE_EID_UNICODE_1_0 :: 0
|
||||
UNICODE_EID_UNICODE_1_1 :: 1
|
||||
UNICODE_EID_ISO_10646 :: 2
|
||||
UNICODE_EID_UNICODE_2_0_BMP :: 3
|
||||
UNICODE_EID_UNICODE_2_0_FULL :: 4
|
||||
|
||||
// encodingID for PLATFORM_ID_MICROSOFT
|
||||
MS_EID_SYMBOL :: 0
|
||||
MS_EID_UNICODE_BMP :: 1
|
||||
MS_EID_SHIFTJIS :: 2
|
||||
MS_EID_UNICODE_FULL :: 10
|
||||
|
||||
|
||||
// encodingID for PLATFORM_ID_MAC; same as Script Manager codes
|
||||
MAC_EID_ROMAN, MAC_EID_ARABIC :: 0, 4
|
||||
MAC_EID_JAPANESE, MAC_EID_HEBREW :: 1, 5
|
||||
MAC_EID_CHINESE_TRAD, MAC_EID_GREEK :: 2, 6
|
||||
MAC_EID_KOREAN, MAC_EID_RUSSIAN :: 3, 7
|
||||
|
||||
// languageID for PLATFORM_ID_MICROSOFT; same as LCID...
|
||||
// problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs
|
||||
MS_LANG_ENGLISH, MS_LANG_ITALIAN :: 0x0409, 0x0410
|
||||
MS_LANG_CHINESE, MS_LANG_JAPANESE :: 0x0804, 0x0411
|
||||
MS_LANG_DUTCH, MS_LANG_KOREAN :: 0x0413, 0x0412
|
||||
MS_LANG_FRENCH, MS_LANG_RUSSIAN :: 0x040c, 0x0419
|
||||
MS_LANG_GERMAN, MS_LANG_SPANISH :: 0x0407, 0x0409
|
||||
MS_LANG_HEBREW, MS_LANG_SWEDISH :: 0x040d, 0x041D
|
||||
|
||||
|
||||
// languageID for PLATFORM_ID_MAC
|
||||
MAC_LANG_ENGLISH, MAC_LANG_JAPANESE :: 0, 11
|
||||
MAC_LANG_ARABIC, MAC_LANG_KOREAN :: 12, 23
|
||||
MAC_LANG_DUTCH, MAC_LANG_RUSSIAN :: 4, 32
|
||||
MAC_LANG_FRENCH, MAC_LANG_SPANISH :: 1, 6
|
||||
MAC_LANG_GERMAN, MAC_LANG_SWEDISH :: 2, 5
|
||||
MAC_LANG_HEBREW, MAC_LANG_CHINESE_SIMPLIFIED :: 10, 33
|
||||
MAC_LANG_ITALIAN, MAC_LANG_CHINESE_TRAD :: 3, 19
|
||||
|
||||
// private structure
|
||||
_buf :: struct {
|
||||
data: [^]byte,
|
||||
cursor: c.int,
|
||||
size: c.int,
|
||||
}
|
4
thirdparty/stb/truetype/stb_truetype_wasm.odin
vendored
Normal file
4
thirdparty/stb/truetype/stb_truetype_wasm.odin
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
#+build wasm32, wasm64p32
|
||||
package stb_truetype
|
||||
|
||||
@(require) import _ "vendor:libc"
|
163
vefontcache/.freetype_wip.odin
Normal file
163
vefontcache/.freetype_wip.odin
Normal file
@@ -0,0 +1,163 @@
|
||||
package vefontcache
|
||||
|
||||
when false {
|
||||
// TODO(Ed): Freetype support
|
||||
|
||||
// TODO(Ed): glyph triangulation cannot be handled in a 'font parser' abstraction. Just going to have explicit procedures to grab info neatly...
|
||||
cache_glyph_freetype :: proc(ctx: ^Context, font: Font_ID, glyph_index: Glyph, entry: ^Entry, bounds_0, bounds_1: Vec2, scale, translate: Vec2) -> b32
|
||||
{
|
||||
draw_filled_path_freetype :: proc( draw_list : ^Draw_List, outside_point : Vec2, path : []Vertex,
|
||||
scale := Vec2 { 1, 1 },
|
||||
translate := Vec2 { 0, 0 },
|
||||
debug_print_verbose : b32 = false
|
||||
)
|
||||
{
|
||||
if debug_print_verbose {
|
||||
log("outline_path:")
|
||||
for point in path {
|
||||
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
|
||||
{
|
||||
transformed_point := Vertex {
|
||||
pos = point.pos * scale + translate,
|
||||
u = 0,
|
||||
v = 0
|
||||
}
|
||||
append( & draw_list.vertices, transformed_point )
|
||||
}
|
||||
|
||||
if len(path) > 2
|
||||
{
|
||||
indices := & draw_list.indices
|
||||
for index : u32 = 1; index < cast(u32) len(path) - 1; index += 1 {
|
||||
to_add := [3]u32 {
|
||||
v_offset,
|
||||
v_offset + index,
|
||||
v_offset + index + 1
|
||||
}
|
||||
append( indices, ..to_add[:] )
|
||||
}
|
||||
|
||||
// Close the path by connecting the last vertex to the first two
|
||||
to_add := [3]u32 {
|
||||
v_offset,
|
||||
v_offset + cast(u32)(len(path) - 1),
|
||||
v_offset + 1
|
||||
}
|
||||
append( indices, ..to_add[:] )
|
||||
}
|
||||
}
|
||||
|
||||
if glyph_index == Glyph(0) {
|
||||
return false
|
||||
}
|
||||
|
||||
face := entry.parser_info.freetype_info
|
||||
error := freetype.load_glyph(face, u32(glyph_index), {.No_Bitmap, .No_Scale})
|
||||
if error != .Ok {
|
||||
return false
|
||||
}
|
||||
|
||||
glyph := face.glyph
|
||||
if glyph.format != .Outline {
|
||||
return false
|
||||
}
|
||||
|
||||
outline := &glyph.outline
|
||||
if outline.n_points == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
draw := Draw_Call_Default
|
||||
draw.pass = Frame_Buffer_Pass.Glyph
|
||||
draw.start_index = cast(u32) len(ctx.draw_list.indices)
|
||||
|
||||
contours := slice.from_ptr(cast( [^]i16) outline.contours, int(outline.n_contours))
|
||||
points := slice.from_ptr(cast( [^]freetype.Vector) outline.points, int(outline.n_points))
|
||||
tags := slice.from_ptr(cast( [^]u8) outline.tags, int(outline.n_points))
|
||||
|
||||
path := &ctx.temp_path
|
||||
clear(path)
|
||||
|
||||
outside := Vec2{ bounds_0.x - 21, bounds_0.y - 33 }
|
||||
|
||||
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
|
||||
|
||||
for idx := start_index; idx < end_index; idx += 1
|
||||
{
|
||||
current_pos := Vec2 { f32( points[idx].x ), f32( points[idx].y ) }
|
||||
if ( tags[idx] & 1 ) == 0
|
||||
{
|
||||
// If current point is off-curve
|
||||
if (idx == start_index || (tags[ idx - 1 ] & 1) != 0)
|
||||
{
|
||||
// current is the first or following an on-curve point
|
||||
prev_point = current_pos
|
||||
}
|
||||
else
|
||||
{
|
||||
// current and previous are off-curve, calculate midpoint
|
||||
midpoint := (prev_point + current_pos) * 0.5
|
||||
append( path, Vertex { pos = midpoint } ) // Add midpoint as on-curve point
|
||||
if idx < end_index - 1
|
||||
{
|
||||
// perform interp from prev_point to current_pos via midpoint
|
||||
step := 1.0 / entry.curve_quality
|
||||
for alpha : f32 = 0.0; alpha <= 1.0; alpha += step
|
||||
{
|
||||
bezier_point := eval_point_on_bezier3( prev_point, midpoint, current_pos, alpha )
|
||||
append( path, Vertex{ pos = bezier_point } )
|
||||
}
|
||||
}
|
||||
|
||||
prev_point = current_pos
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if idx == start_index {
|
||||
first_point = current_pos
|
||||
}
|
||||
if prev_point != (Vec2{}) {
|
||||
// there was an off-curve point before this
|
||||
append(path, Vertex{ pos = prev_point}) // Ensure previous off-curve is handled
|
||||
}
|
||||
append(path, Vertex{ pos = current_pos})
|
||||
prev_point = {}
|
||||
}
|
||||
}
|
||||
|
||||
// ensure the contour is closed
|
||||
if path[0].pos != path[ len(path) - 1 ].pos {
|
||||
append(path, Vertex{pos = path[0].pos})
|
||||
}
|
||||
draw_filled_path(&ctx.draw_list, bounds_0, path[:], scale, translate)
|
||||
// draw_filled_path(&ctx.draw_list, bounds_0, path[:], scale, translate, ctx.debug_print_verbose)
|
||||
clear(path)
|
||||
start_index = end_index
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
draw.end_index = cast(u32) len(ctx.draw_list.indices)
|
||||
if draw.end_index > draw.start_index {
|
||||
append( & ctx.draw_list.calls, draw)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
@@ -1,22 +1,34 @@
|
||||
package vefontcache
|
||||
|
||||
/*
|
||||
The choice was made to keep the LRU cache implementation as close to the original as possible.
|
||||
/* Note(Ed):
|
||||
Original implementation has been changed moderately.
|
||||
Notably the LRU is now type generic for its key value.
|
||||
This was done to profile between using u64, u32, and u16.
|
||||
|
||||
What ended up happening was using u32 for both the atlas and the shape cache
|
||||
yielded a several ms save for processing thousands of draw text calls.
|
||||
|
||||
There was an attempt at an optimization pass but the directives done here (other than force_inline)
|
||||
are marginal changes at best.
|
||||
*/
|
||||
|
||||
import "base:runtime"
|
||||
// 16-bit hashing was attempted, however it seems to get collisions with djb8_hash_16
|
||||
|
||||
LRU_Fail_Mask_16 :: 0xFFFF
|
||||
LRU_Fail_Mask_32 :: 0xFFFFFFFF
|
||||
LRU_Fail_Mask_64 :: 0xFFFFFFFFFFFFFFFF
|
||||
|
||||
Pool_ListIter :: i32
|
||||
Pool_ListValue :: u64
|
||||
|
||||
Pool_List_Item :: struct {
|
||||
Pool_List_Item :: struct( $V_Type : typeid ) #packed {
|
||||
// Pool_List_Item :: struct( $V_Type : typeid ) {
|
||||
prev : Pool_ListIter,
|
||||
next : Pool_ListIter,
|
||||
value : Pool_ListValue,
|
||||
value : V_Type,
|
||||
}
|
||||
|
||||
Pool_List :: struct {
|
||||
items : [dynamic]Pool_List_Item,
|
||||
Pool_List :: struct( $V_Type : typeid) {
|
||||
items : [dynamic]Pool_List_Item(V_Type),
|
||||
free_list : [dynamic]Pool_ListIter,
|
||||
front : Pool_ListIter,
|
||||
back : Pool_ListIter,
|
||||
@@ -25,144 +37,144 @@ Pool_List :: struct {
|
||||
dbg_name : string,
|
||||
}
|
||||
|
||||
pool_list_init :: proc( pool : ^Pool_List, capacity : i32, dbg_name : string = "" )
|
||||
pool_list_init :: proc( pool : ^Pool_List($V_Type), capacity : i32, dbg_name : string = "" )
|
||||
{
|
||||
error : Allocator_Error
|
||||
pool.items, error = make( [dynamic]Pool_List_Item, int(capacity) )
|
||||
assert( error == .None, "VEFontCache.pool_list_init : Failed to allocate items array")
|
||||
pool.items, error = make( [dynamic]Pool_List_Item(V_Type), int(capacity) )
|
||||
assert( error == .None, "VEFontCache.pool_list_inits: Failed to allocate items array")
|
||||
resize( & pool.items, capacity )
|
||||
|
||||
pool.free_list, error = make( [dynamic]Pool_ListIter, len = 0, cap = int(capacity) )
|
||||
assert( error == .None, "VEFontCache.pool_list_init : Failed to allocate free_list array")
|
||||
assert( error == .None, "VEFontCache.pool_list_init: Failed to allocate free_list array")
|
||||
resize( & pool.free_list, capacity )
|
||||
|
||||
pool.capacity = capacity
|
||||
|
||||
pool.dbg_name = dbg_name
|
||||
using pool
|
||||
|
||||
for id in 0 ..< capacity {
|
||||
free_list[id] = i32(id)
|
||||
items[id] = {
|
||||
for id in 0 ..< pool.capacity {
|
||||
pool.free_list[id] = Pool_ListIter(id)
|
||||
pool.items[id] = {
|
||||
prev = -1,
|
||||
next = -1,
|
||||
}
|
||||
}
|
||||
|
||||
front = -1
|
||||
back = -1
|
||||
pool.front = -1
|
||||
pool.back = -1
|
||||
}
|
||||
|
||||
pool_list_free :: proc( pool : ^Pool_List ) {
|
||||
pool_list_free :: proc( pool : ^Pool_List($V_Type) ) {
|
||||
delete( pool.items)
|
||||
delete( pool.free_list)
|
||||
}
|
||||
|
||||
pool_list_reload :: proc( pool : ^Pool_List, allocator : Allocator ) {
|
||||
pool_list_reload :: proc( pool : ^Pool_List($V_Type), allocator : Allocator ) {
|
||||
reload_array( & pool.items, allocator )
|
||||
reload_array( & pool.free_list, allocator )
|
||||
}
|
||||
|
||||
pool_list_clear :: proc( pool: ^Pool_List ) {
|
||||
using pool
|
||||
clear(& items)
|
||||
clear(& free_list)
|
||||
pool_list_clear :: proc( pool: ^Pool_List($V_Type) )
|
||||
{
|
||||
clear(& pool.items)
|
||||
clear(& pool.free_list)
|
||||
resize( & pool.items, cap(pool.items) )
|
||||
resize( & pool.free_list, cap(pool.free_list) )
|
||||
|
||||
for id in 0 ..< capacity {
|
||||
free_list[id] = i32(id)
|
||||
items[id] = {
|
||||
for id in 0 ..< pool.capacity {
|
||||
pool.free_list[id] = Pool_ListIter(id)
|
||||
pool.items[id] = {
|
||||
prev = -1,
|
||||
next = -1,
|
||||
}
|
||||
}
|
||||
|
||||
front = -1
|
||||
back = -1
|
||||
size = 0
|
||||
pool.front = -1
|
||||
pool.back = -1
|
||||
pool.size = 0
|
||||
}
|
||||
|
||||
pool_list_push_front :: proc( pool : ^Pool_List, value : Pool_ListValue )
|
||||
@(optimization_mode="favor_size")
|
||||
pool_list_push_front :: proc( pool : ^Pool_List($V_Type), value : V_Type ) #no_bounds_check
|
||||
{
|
||||
using pool
|
||||
if size >= capacity do return
|
||||
if pool.size >= pool.capacity do return
|
||||
|
||||
length := len(free_list)
|
||||
length := len(pool.free_list)
|
||||
assert( length > 0 )
|
||||
assert( length == int(capacity - size) )
|
||||
assert( length == int(pool.capacity - pool.size) )
|
||||
|
||||
id := free_list[ len(free_list) - 1 ]
|
||||
if pool.dbg_name != "" {
|
||||
logf("pool_list: back %v", id)
|
||||
}
|
||||
pop( & free_list )
|
||||
items[ id ].prev = -1
|
||||
items[ id ].next = front
|
||||
items[ id ].value = value
|
||||
if pool.dbg_name != "" {
|
||||
logf("pool_list: pushed %v into id %v", value, id)
|
||||
}
|
||||
id := pool.free_list[ len(pool.free_list) - 1 ]
|
||||
// if pool.dbg_name != "" {
|
||||
// logf("pool_list: back %v", id)
|
||||
// }
|
||||
pop( & pool.free_list )
|
||||
pool.items[ id ].prev = -1
|
||||
pool.items[ id ].next = pool.front
|
||||
pool.items[ id ].value = value
|
||||
// if pool.dbg_name != "" {
|
||||
// logf("pool_list: pushed %v into id %v", value, id)
|
||||
// }
|
||||
|
||||
if front != -1 do items[ front ].prev = id
|
||||
if back == -1 do back = id
|
||||
front = id
|
||||
size += 1
|
||||
if pool.front != -1 do pool.items[ pool.front ].prev = id
|
||||
if pool.back == -1 do pool.back = id
|
||||
pool.front = id
|
||||
pool.size += 1
|
||||
}
|
||||
|
||||
pool_list_erase :: proc( pool : ^Pool_List, iter : Pool_ListIter )
|
||||
@(optimization_mode="favor_size")
|
||||
pool_list_erase :: proc( pool : ^Pool_List($V_Type), iter : Pool_ListIter ) #no_bounds_check
|
||||
{
|
||||
using pool
|
||||
if size <= 0 do return
|
||||
assert( iter >= 0 && iter < i32(capacity) )
|
||||
assert( len(free_list) == int(capacity - size) )
|
||||
if pool.size <= 0 do return
|
||||
assert( iter >= 0 && iter < Pool_ListIter(pool.capacity) )
|
||||
assert( len(pool.free_list) == int(pool.capacity - pool.size) )
|
||||
|
||||
iter_node := & items[ iter ]
|
||||
iter_node := & pool.items[ iter ]
|
||||
prev := iter_node.prev
|
||||
next := iter_node.next
|
||||
|
||||
if iter_node.prev != -1 do items[ prev ].next = iter_node.next
|
||||
if iter_node.next != -1 do items[ next ].prev = iter_node.prev
|
||||
if iter_node.prev != -1 do pool.items[ prev ].next = iter_node.next
|
||||
if iter_node.next != -1 do pool.items[ next ].prev = iter_node.prev
|
||||
|
||||
if front == iter do front = iter_node.next
|
||||
if back == iter do back = iter_node.prev
|
||||
if pool.front == iter do pool.front = iter_node.next
|
||||
if pool.back == iter do pool.back = iter_node.prev
|
||||
|
||||
iter_node.prev = -1
|
||||
iter_node.next = -1
|
||||
iter_node.value = 0
|
||||
append( & free_list, iter )
|
||||
append( & pool.free_list, iter )
|
||||
|
||||
size -= 1
|
||||
if size == 0 {
|
||||
back = -1
|
||||
front = -1
|
||||
pool.size -= 1
|
||||
if pool.size == 0 {
|
||||
pool.back = -1
|
||||
pool.front = -1
|
||||
}
|
||||
}
|
||||
|
||||
pool_list_move_to_front :: #force_inline proc( pool : ^Pool_List, iter : Pool_ListIter )
|
||||
@(optimization_mode="favor_size")
|
||||
pool_list_move_to_front :: proc "contextless" ( pool : ^Pool_List($V_Type), iter : Pool_ListIter ) #no_bounds_check
|
||||
{
|
||||
using pool
|
||||
if pool.front == iter do return
|
||||
|
||||
if front == iter do return
|
||||
item := & pool.items[iter]
|
||||
if item.prev != -1 do pool.items[ item.prev ].next = item.next
|
||||
if item.next != -1 do pool.items[ item.next ].prev = item.prev
|
||||
if pool.back == iter do pool.back = item.prev
|
||||
|
||||
item := & items[iter]
|
||||
if item.prev != -1 do items[ item.prev ].next = item.next
|
||||
if item.next != -1 do items[ item.next ].prev = item.prev
|
||||
if back == iter do back = item.prev
|
||||
|
||||
item.prev = -1
|
||||
item.next = front
|
||||
items[ front ].prev = iter
|
||||
front = iter
|
||||
item.prev = -1
|
||||
item.next = pool.front
|
||||
pool.items[ pool.front ].prev = iter
|
||||
pool.front = iter
|
||||
}
|
||||
|
||||
pool_list_peek_back :: #force_inline proc ( pool : ^Pool_List ) -> Pool_ListValue {
|
||||
@(optimization_mode="favor_size")
|
||||
pool_list_peek_back :: #force_inline proc ( pool : Pool_List($V_Type) ) -> V_Type #no_bounds_check {
|
||||
assert( pool.back != - 1 )
|
||||
value := pool.items[ pool.back ].value
|
||||
return value
|
||||
}
|
||||
|
||||
pool_list_pop_back :: #force_inline proc( pool : ^Pool_List ) -> Pool_ListValue {
|
||||
@(optimization_mode="favor_size")
|
||||
pool_list_pop_back :: #force_inline proc( pool : ^Pool_List($V_Type) ) -> V_Type #no_bounds_check {
|
||||
if pool.size <= 0 do return 0
|
||||
assert( pool.back != -1 )
|
||||
|
||||
@@ -171,69 +183,69 @@ pool_list_pop_back :: #force_inline proc( pool : ^Pool_List ) -> Pool_ListValue
|
||||
return value
|
||||
}
|
||||
|
||||
LRU_Link :: struct {
|
||||
pad_top : u64,
|
||||
|
||||
LRU_Link :: struct #packed {
|
||||
value : i32,
|
||||
ptr : Pool_ListIter,
|
||||
|
||||
pad_bottom : u64,
|
||||
}
|
||||
|
||||
LRU_Cache :: struct {
|
||||
LRU_Cache :: struct( $Key_Type : typeid ) {
|
||||
capacity : i32,
|
||||
num : i32,
|
||||
table : map[u64]LRU_Link,
|
||||
key_queue : Pool_List,
|
||||
table : map[Key_Type]LRU_Link,
|
||||
key_queue : Pool_List(Key_Type),
|
||||
}
|
||||
|
||||
lru_init :: proc( cache : ^LRU_Cache, capacity : i32, dbg_name : string = "" ) {
|
||||
lru_init :: proc( cache : ^LRU_Cache($Key_Type), capacity : i32, dbg_name : string = "" ) {
|
||||
error : Allocator_Error
|
||||
cache.capacity = capacity
|
||||
cache.table, error = make( map[u64]LRU_Link, uint(capacity) )
|
||||
cache.table, error = make( map[Key_Type]LRU_Link, uint(capacity) )
|
||||
assert( error == .None, "VEFontCache.lru_init : Failed to allocate cache's table")
|
||||
|
||||
pool_list_init( & cache.key_queue, capacity, dbg_name = dbg_name )
|
||||
}
|
||||
}
|
||||
|
||||
lru_free :: proc( cache : ^LRU_Cache ) {
|
||||
lru_free :: proc( cache : ^LRU_Cache($Key_Type) ) {
|
||||
pool_list_free( & cache.key_queue )
|
||||
delete( cache.table )
|
||||
}
|
||||
|
||||
lru_reload :: #force_inline proc( cache : ^LRU_Cache, allocator : Allocator ) {
|
||||
lru_reload :: #force_inline proc( cache : ^LRU_Cache($Key_Type), allocator : Allocator ) {
|
||||
reload_map( & cache.table, allocator )
|
||||
pool_list_reload( & cache.key_queue, allocator )
|
||||
}
|
||||
|
||||
lru_clear :: proc ( cache : ^LRU_Cache ) {
|
||||
lru_clear :: proc ( cache : ^LRU_Cache($Key_Type) ) {
|
||||
pool_list_clear( & cache.key_queue )
|
||||
clear(& cache.table)
|
||||
cache.num = 0
|
||||
}
|
||||
|
||||
lru_find :: #force_inline proc "contextless" ( cache : ^LRU_Cache, key : u64, must_find := false ) -> (LRU_Link, bool) {
|
||||
@(optimization_mode="favor_size")
|
||||
lru_find :: #force_inline proc "contextless" ( cache : LRU_Cache($Key_Type), key : Key_Type, must_find := false ) -> (LRU_Link, bool) #no_bounds_check {
|
||||
link, success := cache.table[key]
|
||||
return link, success
|
||||
}
|
||||
|
||||
lru_get :: #force_inline proc( cache: ^LRU_Cache, key : u64 ) -> i32 {
|
||||
@(optimization_mode="favor_size")
|
||||
lru_get :: #force_inline proc ( cache: ^LRU_Cache($Key_Type), key : Key_Type ) -> i32 #no_bounds_check {
|
||||
if link, ok := &cache.table[ key ]; ok {
|
||||
pool_list_move_to_front(&cache.key_queue, link.ptr)
|
||||
return link.value
|
||||
pool_list_move_to_front(&cache.key_queue, link.ptr)
|
||||
return link.value
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
lru_get_next_evicted :: #force_inline proc ( cache : ^LRU_Cache ) -> u64 {
|
||||
@(optimization_mode="favor_size")
|
||||
lru_get_next_evicted :: #force_inline proc ( cache : LRU_Cache($Key_Type) ) -> Key_Type #no_bounds_check {
|
||||
if cache.key_queue.size >= cache.capacity {
|
||||
evict := pool_list_peek_back( & cache.key_queue )
|
||||
evict := pool_list_peek_back( cache.key_queue )
|
||||
return evict
|
||||
}
|
||||
return 0xFFFFFFFFFFFFFFFF
|
||||
return ~Key_Type(0)
|
||||
}
|
||||
|
||||
lru_peek :: #force_inline proc ( cache : ^LRU_Cache, key : u64, must_find := false ) -> i32 {
|
||||
@(optimization_mode="favor_size")
|
||||
lru_peek :: #force_inline proc "contextless" ( cache : LRU_Cache($Key_Type), key : Key_Type, must_find := false ) -> i32 #no_bounds_check {
|
||||
iter, success := lru_find( cache, key, must_find )
|
||||
if success == false {
|
||||
return -1
|
||||
@@ -241,8 +253,10 @@ lru_peek :: #force_inline proc ( cache : ^LRU_Cache, key : u64, must_find := fal
|
||||
return iter.value
|
||||
}
|
||||
|
||||
lru_put :: #force_inline proc( cache : ^LRU_Cache, key : u64, value : i32 ) -> u64
|
||||
@(optimization_mode="favor_size")
|
||||
lru_put :: proc( cache : ^LRU_Cache($Key_Type), key : Key_Type, value : i32 ) -> Key_Type #no_bounds_check
|
||||
{
|
||||
// profile(#procedure)
|
||||
if link, ok := & cache.table[ key ]; ok {
|
||||
pool_list_move_to_front( & cache.key_queue, link.ptr )
|
||||
link.value = value
|
||||
@@ -265,8 +279,8 @@ lru_put :: #force_inline proc( cache : ^LRU_Cache, key : u64, value : i32 ) -> u
|
||||
return evict
|
||||
}
|
||||
|
||||
lru_refresh :: proc( cache : ^LRU_Cache, key : u64 ) {
|
||||
link, success := lru_find( cache, key )
|
||||
lru_refresh :: proc( cache : ^LRU_Cache($Key_Type), key : Key_Type ) {
|
||||
link, success := lru_find( cache ^, key )
|
||||
pool_list_erase( & cache.key_queue, link.ptr )
|
||||
pool_list_push_front( & cache.key_queue, key )
|
||||
link.ptr = cache.key_queue.front
|
||||
|
@@ -1,127 +1,126 @@
|
||||
package vefontcache
|
||||
|
||||
// There are only 4 actual regions of the atlas. E represents the atlas_decide_region detecting an oversized glyph.
|
||||
// Note(Ed): None should never really occur anymore. So its safe to most likely add an assert when its detected.
|
||||
Atlas_Region_Kind :: enum u8 {
|
||||
None = 0x00,
|
||||
A = 0x41,
|
||||
B = 0x42,
|
||||
C = 0x43,
|
||||
D = 0x44,
|
||||
E = 0x45,
|
||||
A = 0x01,
|
||||
B = 0x02,
|
||||
C = 0x03,
|
||||
D = 0x04,
|
||||
E = 0x05,
|
||||
Ignore = 0xFF, // ve_fontcache_cache_glyph_to_atlas uses a -1 value in clear draw call
|
||||
}
|
||||
|
||||
Atlas_Region :: struct {
|
||||
state : LRU_Cache,
|
||||
Atlas_Key :: u32
|
||||
|
||||
width : i32,
|
||||
height : i32,
|
||||
// TODO(Ed) It might perform better with a tailored made hashtable implementation for the LRU_Cache or dedicated array struct/procs for the Atlas.
|
||||
/* Essentially a sub-atlas of the atlas. There is a state cache per region that tracks the glyph inventory (what slot they occupy).
|
||||
Unlike the shape cache this one's fixed capacity (natrually) and the next avail slot is tracked.
|
||||
*/
|
||||
Atlas_Region :: struct {
|
||||
state : LRU_Cache(Atlas_Key),
|
||||
|
||||
size : Vec2i,
|
||||
capacity : Vec2i,
|
||||
offset : Vec2i,
|
||||
|
||||
slot_size : Vec2i,
|
||||
|
||||
next_idx : i32,
|
||||
}
|
||||
|
||||
/* There are four regions each succeeding region holds larger sized slots.
|
||||
The generator pipeline for draw lists utilizes the regions array for info lookup.
|
||||
|
||||
Note(Ed):
|
||||
Padding can techncially be larger than 1, however recently I haven't had any artififact issues...
|
||||
size_multiplier usage isn't fully resolved. Intent was to further setup over_sampling or just having
|
||||
a more massive cache for content that used more than the usual common glyphs.
|
||||
*/
|
||||
Atlas :: struct {
|
||||
width : i32,
|
||||
height : 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,
|
||||
region_c : Atlas_Region,
|
||||
region_d : Atlas_Region,
|
||||
|
||||
regions : [5] ^Atlas_Region,
|
||||
|
||||
glyph_padding : f32, // Padding to add to bounds_<width/height>_scaled for choosing which atlas region.
|
||||
size_multiplier : f32, // Grows all text by this multiple.
|
||||
|
||||
size : Vec2i,
|
||||
}
|
||||
|
||||
atlas_bbox :: proc( atlas : ^Atlas, region : Atlas_Region_Kind, local_idx : i32 ) -> (position, size: Vec2)
|
||||
{
|
||||
switch region
|
||||
{
|
||||
case .A:
|
||||
size.x = f32(atlas.region_a.width)
|
||||
size.y = f32(atlas.region_a.height)
|
||||
|
||||
position.x = cast(f32) (( local_idx % atlas.region_a.capacity.x ) * atlas.region_a.width)
|
||||
position.y = cast(f32) (( local_idx / atlas.region_a.capacity.x ) * atlas.region_a.height)
|
||||
|
||||
position.x += f32(atlas.region_a.offset.x)
|
||||
position.y += f32(atlas.region_a.offset.y)
|
||||
|
||||
case .B:
|
||||
size.x = f32(atlas.region_b.width)
|
||||
size.y = f32(atlas.region_b.height)
|
||||
|
||||
position.x = cast(f32) (( local_idx % atlas.region_b.capacity.x ) * atlas.region_b.width)
|
||||
position.y = cast(f32) (( local_idx / atlas.region_b.capacity.x ) * atlas.region_b.height)
|
||||
|
||||
position.x += f32(atlas.region_b.offset.x)
|
||||
position.y += f32(atlas.region_b.offset.y)
|
||||
|
||||
case .C:
|
||||
size.x = f32(atlas.region_c.width)
|
||||
size.y = f32(atlas.region_c.height)
|
||||
|
||||
position.x = cast(f32) (( local_idx % atlas.region_c.capacity.x ) * atlas.region_c.width)
|
||||
position.y = cast(f32) (( local_idx / atlas.region_c.capacity.x ) * atlas.region_c.height)
|
||||
|
||||
position.x += f32(atlas.region_c.offset.x)
|
||||
position.y += f32(atlas.region_c.offset.y)
|
||||
|
||||
case .D:
|
||||
size.x = f32(atlas.region_d.width)
|
||||
size.y = f32(atlas.region_d.height)
|
||||
|
||||
position.x = cast(f32) (( local_idx % atlas.region_d.capacity.x ) * atlas.region_d.width)
|
||||
position.y = cast(f32) (( local_idx / atlas.region_d.capacity.x ) * atlas.region_d.height)
|
||||
|
||||
position.x += f32(atlas.region_d.offset.x)
|
||||
position.y += f32(atlas.region_d.offset.y)
|
||||
|
||||
case .Ignore, .None, .E:
|
||||
}
|
||||
// Hahser for the atlas.
|
||||
@(optimization_mode="favor_size")
|
||||
atlas_glyph_lru_code :: #force_inline proc "contextless" ( font : Font_ID, px_size : f32, glyph_index : Glyph ) -> (lru_code : Atlas_Key) {
|
||||
// lru_code = u32(glyph_index) + ( ( 0x10000 * u32(font) ) & 0xFFFF0000 )
|
||||
font := font
|
||||
glyph_index := glyph_index
|
||||
px_size := px_size
|
||||
djb8_hash( & lru_code, to_bytes( & font) )
|
||||
djb8_hash( & lru_code, to_bytes( & glyph_index ) )
|
||||
djb8_hash( & lru_code, to_bytes( & px_size ) )
|
||||
return
|
||||
}
|
||||
|
||||
decide_codepoint_region :: proc(ctx : ^Context, entry : ^Entry, glyph_index : Glyph ) -> (region_kind : Atlas_Region_Kind, region : ^Atlas_Region, over_sample : Vec2)
|
||||
@(optimization_mode="favor_size")
|
||||
atlas_region_bbox :: #force_inline proc( region : Atlas_Region, local_idx : i32 ) -> (position, size: Vec2)
|
||||
{
|
||||
if parser_is_glyph_empty(&entry.parser_info, glyph_index) {
|
||||
return .None, nil, {}
|
||||
}
|
||||
size = vec2(region.slot_size)
|
||||
|
||||
bounds_0, bounds_1 := parser_get_glyph_box(&entry.parser_info, glyph_index)
|
||||
bounds_width := f32(bounds_1.x - bounds_0.x)
|
||||
bounds_height := f32(bounds_1.y - bounds_0.y)
|
||||
position.x = cast(f32) (( local_idx % region.capacity.x ) * region.slot_size.x)
|
||||
position.y = cast(f32) (( local_idx / region.capacity.x ) * region.slot_size.y)
|
||||
|
||||
atlas := & ctx.atlas
|
||||
glyph_buffer := & ctx.glyph_buffer
|
||||
glyph_padding := f32( atlas.glyph_padding ) * 2
|
||||
|
||||
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 } {
|
||||
{ .A, & atlas.region_a },
|
||||
{ .B, & atlas.region_b },
|
||||
{ .C, & atlas.region_c },
|
||||
{ .D, & atlas.region_d },
|
||||
}
|
||||
|
||||
for region in region_lookup do if bounds_width_scaled <= region.region.width && bounds_height_scaled <= region.region.height {
|
||||
return region.kind, region.region, glyph_buffer.over_sample
|
||||
}
|
||||
|
||||
if bounds_width_scaled <= glyph_buffer.width \
|
||||
&& bounds_height_scaled <= glyph_buffer.height {
|
||||
over_sample = \
|
||||
bounds_width_scaled <= glyph_buffer.width / 2 &&
|
||||
bounds_height_scaled <= glyph_buffer.height / 2 ? \
|
||||
{2.0, 2.0} \
|
||||
: {1.0, 1.0}
|
||||
return .E, nil, over_sample
|
||||
}
|
||||
return .None, nil, {}
|
||||
position.x += f32(region.offset.x)
|
||||
position.y += f32(region.offset.y)
|
||||
return
|
||||
}
|
||||
|
||||
@(optimization_mode="favor_size")
|
||||
atlas_decide_region :: #force_inline proc "contextless" (atlas : Atlas, glyph_buffer_size : Vec2, bounds_size_scaled : Vec2 ) -> (region_kind : Atlas_Region_Kind)
|
||||
{
|
||||
// profile(#procedure)
|
||||
glyph_padding_dbl := atlas.glyph_padding * 2
|
||||
padded_bounds := bounds_size_scaled + glyph_padding_dbl
|
||||
|
||||
for kind in 1 ..= 4 do if
|
||||
padded_bounds.x <= f32(atlas.regions[kind].slot_size.x) &&
|
||||
padded_bounds.y <= f32(atlas.regions[kind].slot_size.y)
|
||||
{
|
||||
return cast(Atlas_Region_Kind) kind
|
||||
}
|
||||
|
||||
if padded_bounds.x <= glyph_buffer_size.x && padded_bounds.y <= glyph_buffer_size.y{
|
||||
return .E
|
||||
}
|
||||
return .None
|
||||
}
|
||||
|
||||
// Grab an atlas LRU cache slot.
|
||||
@(optimization_mode="favor_size")
|
||||
atlas_reserve_slot :: #force_inline proc ( region : ^Atlas_Region, lru_code : Atlas_Key ) -> (atlas_index : i32)
|
||||
{
|
||||
if region.next_idx < region.state.capacity
|
||||
{
|
||||
evicted := lru_put( & region.state, lru_code, region.next_idx )
|
||||
atlas_index = region.next_idx
|
||||
region.next_idx += 1
|
||||
assert( evicted == lru_code )
|
||||
}
|
||||
else
|
||||
{
|
||||
next_evict_codepoint := lru_get_next_evicted( region.state )
|
||||
assert( next_evict_codepoint != LRU_Fail_Mask_16)
|
||||
|
||||
atlas_index = lru_peek( region.state, next_evict_codepoint, must_find = true )
|
||||
assert( atlas_index != -1 )
|
||||
|
||||
evicted := lru_put( & region.state, lru_code, atlas_index )
|
||||
assert( evicted == next_evict_codepoint )
|
||||
}
|
||||
|
||||
assert( lru_get( & region.state, lru_code ) != - 1 )
|
||||
return
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,16 +1,56 @@
|
||||
package vefontcache
|
||||
|
||||
import "base:runtime"
|
||||
/*
|
||||
Didn't want to splinter this into more files..
|
||||
Just a bunch of utilities.
|
||||
*/
|
||||
|
||||
import "core:simd"
|
||||
import "core:math"
|
||||
|
||||
import core_log "core:log"
|
||||
|
||||
Colour :: [4]f32
|
||||
peek_array :: #force_inline proc "contextless" ( self : [dynamic]$Type ) -> Type {
|
||||
return self[ len(self) - 1 ]
|
||||
}
|
||||
|
||||
reload_array :: #force_inline proc( self : ^[dynamic]$Type, allocator : Allocator ) {
|
||||
raw := transmute( ^Raw_Dynamic_Array) self
|
||||
raw.allocator = allocator
|
||||
}
|
||||
|
||||
reload_array_soa :: #force_inline proc( self : ^#soa[dynamic]$Type, allocator : Allocator ) {
|
||||
raw := raw_soa_footer(self)
|
||||
raw.allocator = allocator
|
||||
}
|
||||
|
||||
reload_map :: #force_inline proc( self : ^map [$KeyType] $EntryType, allocator : Allocator ) {
|
||||
raw := transmute( ^Raw_Map) self
|
||||
raw.allocator = allocator
|
||||
}
|
||||
|
||||
to_bytes :: #force_inline proc "contextless" ( typed_data : ^$Type ) -> []byte { return slice_ptr( transmute(^byte) typed_data, size_of(Type) ) }
|
||||
|
||||
@(optimization_mode="favor_size")
|
||||
djb8_hash :: #force_inline proc "contextless" ( hash : ^$Type, bytes : []byte ) { for value in bytes do (hash^) = (( (hash^) << 8) + (hash^) ) + Type(value) }
|
||||
|
||||
RGBA8 :: [4]u8
|
||||
RGBAN :: [4]f32
|
||||
Vec2 :: [2]f32
|
||||
Vec2i :: [2]i32
|
||||
Vec2_64 :: [2]f64
|
||||
|
||||
Transform :: struct {
|
||||
pos : Vec2,
|
||||
scale : Vec2,
|
||||
}
|
||||
|
||||
Range2 :: struct {
|
||||
p0, p1 : Vec2,
|
||||
}
|
||||
|
||||
mul_range2_vec2 :: #force_inline proc "contextless" ( range : Range2, v : Vec2 ) -> Range2 { return { range.p0 * v, range.p1 * v } }
|
||||
size_range2 :: #force_inline proc "contextless" ( range : Range2 ) -> Vec2 { return range.p1 - range.p0 }
|
||||
|
||||
vec2_from_scalar :: #force_inline proc "contextless" ( scalar : f32 ) -> Vec2 { return { scalar, scalar }}
|
||||
vec2_64_from_vec2 :: #force_inline proc "contextless" ( v2 : Vec2 ) -> Vec2_64 { return { f64(v2.x), f64(v2.y) }}
|
||||
vec2_from_vec2i :: #force_inline proc "contextless" ( v2i : Vec2i ) -> Vec2 { return { f32(v2i.x), f32(v2i.y) }}
|
||||
@@ -19,7 +59,7 @@ vec2i_from_vec2 :: #force_inline proc "contextless" ( v2 : Vec2 ) -> Vec2
|
||||
@(require_results) ceil_vec2 :: proc "contextless" ( v : Vec2 ) -> Vec2 { return { ceil_f32(v.x), ceil_f32(v.y) } }
|
||||
@(require_results) floor_vec2 :: proc "contextless" ( v : Vec2 ) -> Vec2 { return { floor_f32(v.x), floor_f32(v.y) } }
|
||||
|
||||
// This buffer is used below excluisvely to prevent any allocator recusion when verbose logging from allocators.
|
||||
// This buffer is used below excluisvely to prevent any allocator recursion when verbose logging from allocators.
|
||||
// This means a single line is limited to 4k buffer
|
||||
// Logger_Allocator_Buffer : [4 * Kilobyte]u8
|
||||
|
||||
@@ -39,91 +79,29 @@ logf :: proc( fmt : string, args : ..any, level := core_log.Level.Info, loc :=
|
||||
core_log.logf( level, fmt, ..args, location = loc )
|
||||
}
|
||||
|
||||
reload_array :: proc( self : ^[dynamic]$Type, allocator : Allocator ) {
|
||||
raw := transmute( ^runtime.Raw_Dynamic_Array) self
|
||||
raw.allocator = allocator
|
||||
}
|
||||
|
||||
reload_map :: proc( self : ^map [$KeyType] $EntryType, allocator : Allocator ) {
|
||||
raw := transmute( ^runtime.Raw_Map) self
|
||||
raw.allocator = allocator
|
||||
}
|
||||
|
||||
font_glyph_lru_code :: #force_inline proc "contextless" ( font : Font_ID, glyph_index : Glyph ) -> (lru_code : u64) {
|
||||
lru_code = u64(glyph_index) + ( ( 0x100000000 * u64(font) ) & 0xFFFFFFFF00000000 )
|
||||
return
|
||||
}
|
||||
|
||||
is_empty :: #force_inline proc ( ctx : ^Context, entry : ^Entry, glyph_index : Glyph ) -> b32
|
||||
@(optimization_mode="favor_size")
|
||||
to_glyph_buffer_space :: #force_inline proc "contextless" ( #no_alias position, scale : ^Vec2, size : Vec2 )
|
||||
{
|
||||
if glyph_index == 0 do return true
|
||||
if parser_is_glyph_empty( & entry.parser_info, glyph_index ) do return true
|
||||
return false
|
||||
pos := position^
|
||||
scale_32 := scale^
|
||||
|
||||
quotient : Vec2 = 1.0 / size
|
||||
pos = pos * quotient * 2.0 - 1.0
|
||||
scale_32 = scale_32 * quotient * 2.0
|
||||
|
||||
(position^) = pos
|
||||
(scale^) = scale_32
|
||||
}
|
||||
|
||||
mark_batch_codepoint_seen :: #force_inline proc ( ctx : ^Context, lru_code : u64 ) {
|
||||
ctx.temp_codepoint_seen[lru_code] = true
|
||||
ctx.temp_codepoint_seen_num += 1
|
||||
}
|
||||
|
||||
reset_batch_codepoint_state :: #force_inline proc( ctx : ^Context ) {
|
||||
clear_map( & ctx.temp_codepoint_seen )
|
||||
ctx.temp_codepoint_seen_num = 0
|
||||
}
|
||||
|
||||
USE_F64_PRECISION_ON_X_FORM_OPS :: false
|
||||
|
||||
screenspace_x_form :: #force_inline proc "contextless" ( position, scale : ^Vec2, size : Vec2 )
|
||||
@(optimization_mode="favor_size")
|
||||
to_target_space :: #force_inline proc "contextless" ( #no_alias position, scale : ^Vec2, size : Vec2 )
|
||||
{
|
||||
when USE_F64_PRECISION_ON_X_FORM_OPS
|
||||
{
|
||||
pos_64 := vec2_64_from_vec2(position^)
|
||||
scale_64 := vec2_64_from_vec2(scale^)
|
||||
|
||||
quotient : Vec2_64 = 1.0 / vec2_64(size)
|
||||
pos_64 = pos_64 * quotient * 2.0 - 1.0
|
||||
scale_64 = scale_64 * quotient * 2.0
|
||||
|
||||
(position^) = { f32(pos_64.x), f32(pos_64.y) }
|
||||
(scale^) = { f32(scale_64.x), f32(scale_64.y) }
|
||||
}
|
||||
else
|
||||
{
|
||||
pos := position^
|
||||
scale_32 := scale^
|
||||
|
||||
quotient : Vec2 = 1.0 / size
|
||||
pos = pos * quotient * 2.0 - 1.0
|
||||
scale_32 = scale_32 * quotient * 2.0
|
||||
|
||||
(position^) = pos
|
||||
(scale^) = scale_32
|
||||
}
|
||||
quotient : Vec2 = 1.0 / size
|
||||
(position^) *= quotient
|
||||
(scale^) *= quotient
|
||||
}
|
||||
|
||||
textspace_x_form :: #force_inline proc "contextless" ( position, scale : ^Vec2, size : Vec2 )
|
||||
{
|
||||
when USE_F64_PRECISION_ON_X_FORM_OPS
|
||||
{
|
||||
pos_64 := vec2_64_from_vec2(position^)
|
||||
scale_64 := vec2_64_from_vec2(scale^)
|
||||
|
||||
quotient : Vec2_64 = 1.0 / vec2_64(size)
|
||||
pos_64 *= quotient
|
||||
scale_64 *= quotient
|
||||
|
||||
(position^) = { f32(pos_64.x), f32(pos_64.y) }
|
||||
(scale^) = { f32(scale_64.x), f32(scale_64.y) }
|
||||
}
|
||||
else
|
||||
{
|
||||
quotient : Vec2 = 1.0 / size
|
||||
(position^) *= quotient
|
||||
(scale^) *= quotient
|
||||
}
|
||||
}
|
||||
|
||||
USE_MANUAL_SIMD_FOR_BEZIER_OPS :: false
|
||||
USE_MANUAL_SIMD_FOR_BEZIER_OPS :: true
|
||||
|
||||
when ! USE_MANUAL_SIMD_FOR_BEZIER_OPS
|
||||
{
|
||||
@@ -132,11 +110,6 @@ when ! USE_MANUAL_SIMD_FOR_BEZIER_OPS
|
||||
// ve_fontcache_eval_bezier (quadratic)
|
||||
eval_point_on_bezier3 :: #force_inline proc "contextless" ( p0, p1, p2 : Vec2, alpha : f32 ) -> Vec2
|
||||
{
|
||||
// p0 := vec2_64(p0)
|
||||
// p1 := vec2_64(p1)
|
||||
// p2 := vec2_64(p2)
|
||||
// alpha := f64(alpha)
|
||||
|
||||
weight_start := (1 - alpha) * (1 - alpha)
|
||||
weight_control := 2.0 * (1 - alpha) * alpha
|
||||
weight_end := alpha * alpha
|
||||
@@ -154,12 +127,6 @@ when ! USE_MANUAL_SIMD_FOR_BEZIER_OPS
|
||||
// ve_fontcache_eval_bezier (cubic)
|
||||
eval_point_on_bezier4 :: #force_inline proc "contextless" ( p0, p1, p2, p3 : Vec2, alpha : f32 ) -> Vec2
|
||||
{
|
||||
// p0 := vec2_64(p0)
|
||||
// p1 := vec2_64(p1)
|
||||
// p2 := vec2_64(p2)
|
||||
// p3 := vec2_64(p3)
|
||||
// alpha := f64(alpha)
|
||||
|
||||
weight_start := (1 - alpha) * (1 - alpha) * (1 - alpha)
|
||||
weight_c_a := 3 * (1 - alpha) * (1 - alpha) * alpha
|
||||
weight_c_b := 3 * (1 - alpha) * alpha * alpha
|
||||
@@ -178,14 +145,17 @@ else
|
||||
{
|
||||
Vec2_SIMD :: simd.f32x4
|
||||
|
||||
@(optimization_mode="favor_size")
|
||||
vec2_to_simd :: #force_inline proc "contextless" (v: Vec2) -> Vec2_SIMD {
|
||||
return Vec2_SIMD{v.x, v.y, 0, 0}
|
||||
}
|
||||
|
||||
@(optimization_mode="favor_size")
|
||||
simd_to_vec2 :: #force_inline proc "contextless" (v: Vec2_SIMD) -> Vec2 {
|
||||
return Vec2{ simd.extract(v, 0), simd.extract(v, 1) }
|
||||
}
|
||||
|
||||
@(optimization_mode="favor_size")
|
||||
eval_point_on_bezier3 :: #force_inline proc "contextless" (p0, p1, p2: Vec2, alpha: f32) -> Vec2
|
||||
{
|
||||
simd_p0 := vec2_to_simd(p0)
|
||||
@@ -209,6 +179,7 @@ else
|
||||
return simd_to_vec2(result)
|
||||
}
|
||||
|
||||
@(optimization_mode="favor_size")
|
||||
eval_point_on_bezier4 :: #force_inline proc "contextless" (p0, p1, p2, p3: Vec2, alpha: f32) -> Vec2
|
||||
{
|
||||
simd_p0 := vec2_to_simd(p0)
|
||||
|
@@ -2,31 +2,34 @@ package vefontcache
|
||||
|
||||
/*
|
||||
Notes:
|
||||
This is a minimal wrapper I originally did incase a font parser other than stb_truetype is introduced in the future.
|
||||
Otherwise, its essentially 1:1 with it.
|
||||
|
||||
Freetype will do memory allocations and has an interface the user can implement.
|
||||
That interface is not exposed from this parser but could be added to parser_init.
|
||||
Freetype isn't really supported and its not a high priority.
|
||||
~~Freetype will do memory allocations and has an interface the user can implement.~~
|
||||
~~That interface is not exposed from this parser but could be added to parser_init.~~
|
||||
|
||||
STB_Truetype has macros for its allocation unfortuantely
|
||||
STB_Truetype:
|
||||
* Added ability to set the stb_truetype allocator for STBTT_MALLOC and STBTT_FREE.
|
||||
* Changed procedure signatures to pass the font_info struct by immutable ptr (#by_ptr)
|
||||
when the C equivalent has their parameter as `const*`.
|
||||
*/
|
||||
|
||||
import "base:runtime"
|
||||
import "core:c"
|
||||
import "core:math"
|
||||
import "core:slice"
|
||||
import stbtt "vendor:stb/truetype"
|
||||
import freetype "thirdparty:freetype"
|
||||
import stbtt "thirdparty:stb/truetype"
|
||||
// import freetype "thirdparty:freetype"
|
||||
|
||||
Parser_Kind :: enum u32 {
|
||||
STB_TrueType,
|
||||
Freetype,
|
||||
Freetype, // Currently not implemented.
|
||||
}
|
||||
|
||||
Parser_Font_Info :: struct {
|
||||
label : string,
|
||||
kind : Parser_Kind,
|
||||
using _ : struct #raw_union {
|
||||
stbtt_info : stbtt.fontinfo,
|
||||
freetype_info : freetype.Face
|
||||
stbtt_info : stbtt.fontinfo,
|
||||
// freetype_info : freetype.Face
|
||||
},
|
||||
data : []byte,
|
||||
}
|
||||
@@ -51,47 +54,55 @@ Parser_Glyph_Vertex :: struct {
|
||||
Parser_Glyph_Shape :: [dynamic]Parser_Glyph_Vertex
|
||||
|
||||
Parser_Context :: struct {
|
||||
kind : Parser_Kind,
|
||||
ft_library : freetype.Library,
|
||||
lib_backing : Allocator,
|
||||
kind : Parser_Kind,
|
||||
// ft_library : freetype.Library,
|
||||
}
|
||||
|
||||
parser_init :: proc( ctx : ^Parser_Context, kind : Parser_Kind )
|
||||
parser_stbtt_allocator_proc :: proc(
|
||||
allocator_data : rawptr,
|
||||
type : stbtt.zpl_allocator_type,
|
||||
size : c.ssize_t,
|
||||
alignment : c.ssize_t,
|
||||
old_memory : rawptr,
|
||||
old_size : c.ssize_t,
|
||||
flags : c.ulonglong
|
||||
) -> rawptr
|
||||
{
|
||||
switch kind
|
||||
{
|
||||
case .Freetype:
|
||||
result := freetype.init_free_type( & ctx.ft_library )
|
||||
assert( result == freetype.Error.Ok, "VEFontCache.parser_init: Failed to initialize freetype" )
|
||||
allocator := transmute(^Allocator) allocator_data
|
||||
result, error := allocator.procedure( allocator.data, cast(Allocator_Mode) type, cast(int) size, cast(int) alignment, old_memory, cast(int) old_size )
|
||||
assert(error == .None)
|
||||
|
||||
case .STB_TrueType:
|
||||
// Do nothing intentional
|
||||
if type == .Alloc || type == .Resize {
|
||||
raw := transmute(Raw_Slice) result
|
||||
// assert(raw.len > 0, "Allocation is 0 bytes?")
|
||||
return transmute(rawptr) raw.data
|
||||
}
|
||||
else do return nil
|
||||
}
|
||||
|
||||
ctx.kind = kind
|
||||
parser_init :: proc( ctx : ^Parser_Context, kind : Parser_Kind, allocator := context.allocator )
|
||||
{
|
||||
ctx.kind = kind
|
||||
ctx.lib_backing = allocator
|
||||
|
||||
stbtt_allocator := stbtt.zpl_allocator { parser_stbtt_allocator_proc, & ctx.lib_backing }
|
||||
stbtt.SetAllocator( stbtt_allocator )
|
||||
}
|
||||
|
||||
parser_reload :: proc( ctx : ^Parser_Context, allocator := context.allocator) {
|
||||
ctx.lib_backing = allocator
|
||||
stbtt_allocator := stbtt.zpl_allocator { parser_stbtt_allocator_proc, & ctx.lib_backing }
|
||||
stbtt.SetAllocator( stbtt_allocator )
|
||||
}
|
||||
|
||||
parser_shutdown :: proc( ctx : ^Parser_Context ) {
|
||||
// TODO(Ed): Implement
|
||||
// Note: Not necesssary for stb_truetype
|
||||
}
|
||||
|
||||
parser_load_font :: proc( ctx : ^Parser_Context, label : string, data : []byte ) -> (font : Parser_Font_Info)
|
||||
parser_load_font :: proc( ctx : ^Parser_Context, label : string, data : []byte ) -> (font : Parser_Font_Info, error : b32)
|
||||
{
|
||||
switch ctx.kind
|
||||
{
|
||||
case .Freetype:
|
||||
when ODIN_OS == .Windows {
|
||||
error := freetype.new_memory_face( ctx.ft_library, raw_data(data), cast(i32) len(data), 0, & font.freetype_info )
|
||||
if error != .Ok do return
|
||||
}
|
||||
else when ODIN_OS == .Linux {
|
||||
error := freetype.new_memory_face( ctx.ft_library, raw_data(data), cast(i64) len(data), 0, & font.freetype_info )
|
||||
if error != .Ok do return
|
||||
}
|
||||
|
||||
case .STB_TrueType:
|
||||
success := stbtt.InitFont( & font.stbtt_info, raw_data(data), 0 )
|
||||
if ! success do return
|
||||
}
|
||||
error = ! stbtt.InitFont( & font.stbtt_info, raw_data(data), 0 )
|
||||
|
||||
font.label = label
|
||||
font.data = data
|
||||
@@ -101,242 +112,87 @@ parser_load_font :: proc( ctx : ^Parser_Context, label : string, data : []byte )
|
||||
|
||||
parser_unload_font :: proc( font : ^Parser_Font_Info )
|
||||
{
|
||||
switch font.kind {
|
||||
case .Freetype:
|
||||
error := freetype.done_face( font.freetype_info )
|
||||
assert( error == .Ok, "VEFontCache.parser_unload_font: Failed to unload freetype face" )
|
||||
|
||||
case .STB_TrueType:
|
||||
// Do Nothing
|
||||
}
|
||||
// case .STB_TrueType:
|
||||
// Do Nothing
|
||||
}
|
||||
|
||||
parser_find_glyph_index :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, codepoint : rune ) -> (glyph_index : Glyph)
|
||||
parser_find_glyph_index :: #force_inline proc "contextless" ( font : Parser_Font_Info, codepoint : rune ) -> (glyph_index : Glyph)
|
||||
{
|
||||
switch font.kind
|
||||
{
|
||||
case .Freetype:
|
||||
when ODIN_OS == .Windows {
|
||||
glyph_index = transmute(Glyph) freetype.get_char_index( font.freetype_info, transmute(u32) codepoint )
|
||||
}
|
||||
else when ODIN_OS == .Linux {
|
||||
glyph_index = transmute(Glyph) freetype.get_char_index( font.freetype_info, cast(u64) codepoint )
|
||||
}
|
||||
return
|
||||
|
||||
case .STB_TrueType:
|
||||
glyph_index = transmute(Glyph) stbtt.FindGlyphIndex( & font.stbtt_info, codepoint )
|
||||
return
|
||||
}
|
||||
return Glyph(-1)
|
||||
}
|
||||
|
||||
parser_free_shape :: proc( font : ^Parser_Font_Info, shape : Parser_Glyph_Shape )
|
||||
{
|
||||
switch font.kind
|
||||
{
|
||||
case .Freetype:
|
||||
delete(shape)
|
||||
|
||||
case .STB_TrueType:
|
||||
stbtt.FreeShape( & font.stbtt_info, transmute( [^]stbtt.vertex) raw_data(shape) )
|
||||
}
|
||||
}
|
||||
|
||||
parser_get_codepoint_horizontal_metrics :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, codepoint : rune ) -> ( advance, to_left_side_glyph : i32 )
|
||||
{
|
||||
switch font.kind
|
||||
{
|
||||
case .Freetype:
|
||||
glyph_index : Glyph
|
||||
when ODIN_OS == .Windows {
|
||||
glyph_index = transmute(Glyph) freetype.get_char_index( font.freetype_info, transmute(u32) codepoint )
|
||||
}
|
||||
else when ODIN_OS == .Linux {
|
||||
glyph_index = transmute(Glyph) freetype.get_char_index( font.freetype_info, cast(u64) codepoint )
|
||||
}
|
||||
|
||||
if glyph_index != 0
|
||||
{
|
||||
freetype.load_glyph( font.freetype_info, c.uint(codepoint), { .No_Bitmap, .No_Hinting, .No_Scale } )
|
||||
advance = i32(font.freetype_info.glyph.advance.x) >> 6
|
||||
to_left_side_glyph = i32(font.freetype_info.glyph.metrics.hori_bearing_x) >> 6
|
||||
}
|
||||
else
|
||||
{
|
||||
advance = 0
|
||||
to_left_side_glyph = 0
|
||||
}
|
||||
|
||||
case .STB_TrueType:
|
||||
stbtt.GetCodepointHMetrics( & font.stbtt_info, codepoint, & advance, & to_left_side_glyph )
|
||||
}
|
||||
glyph_index = transmute(Glyph) stbtt.FindGlyphIndex( font.stbtt_info, codepoint )
|
||||
return
|
||||
}
|
||||
|
||||
parser_get_codepoint_kern_advance :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, prev_codepoint, codepoint : rune ) -> i32
|
||||
parser_free_shape :: #force_inline proc( font : Parser_Font_Info, shape : Parser_Glyph_Shape )
|
||||
{
|
||||
switch font.kind
|
||||
{
|
||||
case .Freetype:
|
||||
prev_glyph_index : Glyph
|
||||
glyph_index : Glyph
|
||||
when ODIN_OS == .Windows {
|
||||
prev_glyph_index = transmute(Glyph) freetype.get_char_index( font.freetype_info, transmute(u32) prev_codepoint )
|
||||
glyph_index = transmute(Glyph) freetype.get_char_index( font.freetype_info, transmute(u32) codepoint )
|
||||
}
|
||||
else when ODIN_OS == .Linux {
|
||||
prev_glyph_index = transmute(Glyph) freetype.get_char_index( font.freetype_info, cast(u64) prev_codepoint )
|
||||
glyph_index = transmute(Glyph) freetype.get_char_index( font.freetype_info, cast(u64) codepoint )
|
||||
}
|
||||
|
||||
if prev_glyph_index != 0 && glyph_index != 0
|
||||
{
|
||||
kerning : freetype.Vector
|
||||
font.freetype_info.driver.clazz.get_kerning( font.freetype_info, transmute(u32) prev_codepoint, transmute(u32) codepoint, & kerning )
|
||||
}
|
||||
|
||||
case .STB_TrueType:
|
||||
kern := stbtt.GetCodepointKernAdvance( & font.stbtt_info, prev_codepoint, codepoint )
|
||||
return kern
|
||||
}
|
||||
return -1
|
||||
shape := shape
|
||||
shape_raw := transmute( ^Raw_Dynamic_Array) & shape
|
||||
stbtt.FreeShape( font.stbtt_info, transmute( [^]stbtt.vertex) shape_raw.data )
|
||||
}
|
||||
|
||||
parser_get_font_vertical_metrics :: #force_inline proc "contextless" ( font : ^Parser_Font_Info ) -> (ascent, descent, line_gap : i32 )
|
||||
parser_get_codepoint_horizontal_metrics :: #force_inline proc "contextless" ( font : Parser_Font_Info, codepoint : rune ) -> ( advance, to_left_side_glyph : i32 )
|
||||
{
|
||||
switch font.kind
|
||||
{
|
||||
case .Freetype:
|
||||
info := font.freetype_info
|
||||
ascent = i32(info.ascender)
|
||||
descent = i32(info.descender)
|
||||
line_gap = i32(info.height) - (ascent - descent)
|
||||
|
||||
case .STB_TrueType:
|
||||
stbtt.GetFontVMetrics( & font.stbtt_info, & ascent, & descent, & line_gap )
|
||||
}
|
||||
stbtt.GetCodepointHMetrics( font.stbtt_info, codepoint, & advance, & to_left_side_glyph )
|
||||
return
|
||||
}
|
||||
|
||||
parser_get_glyph_box :: #force_inline proc ( font : ^Parser_Font_Info, glyph_index : Glyph ) -> (bounds_0, bounds_1 : Vec2i)
|
||||
parser_get_codepoint_kern_advance :: #force_inline proc "contextless" ( font : Parser_Font_Info, prev_codepoint, codepoint : rune ) -> i32
|
||||
{
|
||||
switch font.kind
|
||||
{
|
||||
case .Freetype:
|
||||
freetype.load_glyph( font.freetype_info, c.uint(glyph_index), { .No_Bitmap, .No_Hinting, .No_Scale } )
|
||||
kern := stbtt.GetCodepointKernAdvance( font.stbtt_info, prev_codepoint, codepoint )
|
||||
return kern
|
||||
}
|
||||
|
||||
metrics := font.freetype_info.glyph.metrics
|
||||
|
||||
bounds_0 = {i32(metrics.hori_bearing_x), i32(metrics.hori_bearing_y - metrics.height)}
|
||||
bounds_1 = {i32(metrics.hori_bearing_x + metrics.width), i32(metrics.hori_bearing_y)}
|
||||
|
||||
case .STB_TrueType:
|
||||
x0, y0, x1, y1 : i32
|
||||
success := cast(bool) stbtt.GetGlyphBox( & font.stbtt_info, i32(glyph_index), & x0, & y0, & x1, & y1 )
|
||||
assert( success )
|
||||
|
||||
bounds_0 = { i32(x0), i32(y0) }
|
||||
bounds_1 = { i32(x1), i32(y1) }
|
||||
}
|
||||
parser_get_font_vertical_metrics :: #force_inline proc "contextless" ( font : Parser_Font_Info ) -> (ascent, descent, line_gap : i32 )
|
||||
{
|
||||
stbtt.GetFontVMetrics( font.stbtt_info, & ascent, & descent, & line_gap )
|
||||
return
|
||||
}
|
||||
|
||||
parser_get_glyph_shape :: proc( font : ^Parser_Font_Info, glyph_index : Glyph ) -> (shape : Parser_Glyph_Shape, error : Allocator_Error)
|
||||
parser_get_bounds :: #force_inline proc "contextless" ( font : Parser_Font_Info, glyph_index : Glyph ) -> (bounds : Range2)
|
||||
{
|
||||
switch font.kind
|
||||
{
|
||||
case .Freetype:
|
||||
// TODO(Ed): Don't do this, going a completely different route for handling shapes.
|
||||
// This abstraction fails to be time-saving or performant.
|
||||
// profile(#procedure)
|
||||
bounds_0, bounds_1 : Vec2i
|
||||
|
||||
case .STB_TrueType:
|
||||
stb_shape : [^]stbtt.vertex
|
||||
nverts := stbtt.GetGlyphShape( & font.stbtt_info, cast(i32) glyph_index, & stb_shape )
|
||||
|
||||
shape_raw := transmute( ^runtime.Raw_Dynamic_Array) & shape
|
||||
shape_raw.data = stb_shape
|
||||
shape_raw.len = int(nverts)
|
||||
shape_raw.cap = int(nverts)
|
||||
shape_raw.allocator = runtime.nil_allocator()
|
||||
error = Allocator_Error.None
|
||||
return
|
||||
}
|
||||
x0, y0, x1, y1 : i32
|
||||
success := cast(bool) stbtt.GetGlyphBox( font.stbtt_info, i32(glyph_index), & x0, & y0, & x1, & y1 )
|
||||
|
||||
bounds_0 = { x0, y0 }
|
||||
bounds_1 = { x1, y1 }
|
||||
bounds = { vec2(bounds_0), vec2(bounds_1) }
|
||||
return
|
||||
}
|
||||
|
||||
parser_is_glyph_empty :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, glyph_index : Glyph ) -> b32
|
||||
parser_get_glyph_shape :: #force_inline proc ( font : Parser_Font_Info, glyph_index : Glyph ) -> (shape : Parser_Glyph_Shape, error : Allocator_Error)
|
||||
{
|
||||
switch font.kind
|
||||
{
|
||||
case .Freetype:
|
||||
error := freetype.load_glyph( font.freetype_info, cast(u32) glyph_index, { .No_Bitmap, .No_Hinting, .No_Scale } )
|
||||
if error == .Ok
|
||||
{
|
||||
if font.freetype_info.glyph.format == .Outline {
|
||||
return font.freetype_info.glyph.outline.n_points == 0
|
||||
}
|
||||
else if font.freetype_info.glyph.format == .Bitmap {
|
||||
return font.freetype_info.glyph.bitmap.width == 0 && font.freetype_info.glyph.bitmap.rows == 0;
|
||||
}
|
||||
}
|
||||
return false
|
||||
stb_shape : [^]stbtt.vertex
|
||||
nverts := stbtt.GetGlyphShape( font.stbtt_info, cast(i32) glyph_index, & stb_shape )
|
||||
|
||||
case .STB_TrueType:
|
||||
return stbtt.IsGlyphEmpty( & font.stbtt_info, cast(c.int) glyph_index )
|
||||
}
|
||||
return false
|
||||
shape_raw := transmute( ^Raw_Dynamic_Array) & shape
|
||||
shape_raw.data = stb_shape
|
||||
shape_raw.len = int(nverts)
|
||||
shape_raw.cap = int(nverts)
|
||||
shape_raw.allocator = nil_allocator()
|
||||
error = Allocator_Error.None
|
||||
return
|
||||
}
|
||||
|
||||
parser_scale :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, size : f32 ) -> f32
|
||||
parser_is_glyph_empty :: #force_inline proc "contextless" ( font : Parser_Font_Info, glyph_index : Glyph ) -> b32
|
||||
{
|
||||
size_scale := size < 0.0 ? \
|
||||
parser_scale_for_pixel_height( font, -size ) \
|
||||
: parser_scale_for_mapping_em_to_pixels( font, size )
|
||||
// size_scale = 1.0
|
||||
return stbtt.IsGlyphEmpty( font.stbtt_info, cast(c.int) glyph_index )
|
||||
}
|
||||
|
||||
parser_scale :: #force_inline proc "contextless" ( font : Parser_Font_Info, size : f32 ) -> f32
|
||||
{
|
||||
// profile(#procedure)
|
||||
size_scale := size > 0.0 ? parser_scale_for_mapping_em_to_pixels( font, size ) : parser_scale_for_pixel_height( font, -size )
|
||||
return size_scale
|
||||
}
|
||||
|
||||
parser_scale_for_pixel_height :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, size : f32 ) -> f32
|
||||
parser_scale_for_pixel_height :: #force_inline proc "contextless" ( font : Parser_Font_Info, size : f32 ) -> f32
|
||||
{
|
||||
switch font.kind {
|
||||
case .Freetype:
|
||||
freetype.set_pixel_sizes( font.freetype_info, 0, cast(u32) size )
|
||||
size_scale := size / cast(f32)font.freetype_info.units_per_em
|
||||
return size_scale
|
||||
|
||||
case.STB_TrueType:
|
||||
return stbtt.ScaleForPixelHeight( & font.stbtt_info, size )
|
||||
}
|
||||
return 0
|
||||
return stbtt.ScaleForPixelHeight( font.stbtt_info, size )
|
||||
}
|
||||
|
||||
parser_scale_for_mapping_em_to_pixels :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, size : f32 ) -> f32
|
||||
parser_scale_for_mapping_em_to_pixels :: #force_inline proc "contextless" ( font : Parser_Font_Info, size : f32 ) -> f32
|
||||
{
|
||||
switch font.kind {
|
||||
case .Freetype:
|
||||
Inches_To_CM :: cast(f32) 2.54
|
||||
Points_Per_CM :: cast(f32) 28.3465
|
||||
CM_Per_Point :: cast(f32) 1.0 / DPT_DPCM
|
||||
CM_Per_Pixel :: cast(f32) 1.0 / DPT_PPCM
|
||||
DPT_DPCM :: cast(f32) 72.0 * Inches_To_CM // 182.88 points/dots per cm
|
||||
DPT_PPCM :: cast(f32) 96.0 * Inches_To_CM // 243.84 pixels per cm
|
||||
DPT_DPI :: cast(f32) 72.0
|
||||
|
||||
// TODO(Ed): Don't assume the dots or pixels per inch.
|
||||
system_dpi :: DPT_DPI
|
||||
|
||||
FT_Font_Size_Point_Unit :: 1.0 / 64.0
|
||||
FT_Point_10 :: 64.0
|
||||
|
||||
points_per_em := (size / system_dpi ) * DPT_DPI
|
||||
freetype.set_char_size( font.freetype_info, 0, cast(freetype.F26Dot6) f32(points_per_em * FT_Point_10), cast(u32) DPT_DPI, cast(u32) DPT_DPI )
|
||||
size_scale := f32(f64(size) / cast(f64) font.freetype_info.units_per_em)
|
||||
return size_scale
|
||||
|
||||
case .STB_TrueType:
|
||||
return stbtt.ScaleForMappingEmToPixels( & font.stbtt_info, size )
|
||||
}
|
||||
return 0
|
||||
return stbtt.ScaleForMappingEmToPixels( font.stbtt_info, size )
|
||||
}
|
||||
|
@@ -1,7 +1,15 @@
|
||||
package vefontcache
|
||||
|
||||
import "base:builtin"
|
||||
resize_soa_non_zero :: non_zero_resize_soa
|
||||
import "base:runtime"
|
||||
Raw_Dynamic_Array :: runtime.Raw_Dynamic_Array
|
||||
Raw_Map :: runtime.Raw_Map
|
||||
Raw_Slice :: runtime.Raw_Slice
|
||||
raw_soa_footer :: runtime.raw_soa_footer
|
||||
nil_allocator :: runtime.nil_allocator
|
||||
import "core:hash"
|
||||
fnv64a :: hash.fnv64a
|
||||
ginger16 :: hash.ginger16
|
||||
import "core:math"
|
||||
ceil_f16 :: math.ceil_f16
|
||||
ceil_f16le :: math.ceil_f16le
|
||||
@@ -29,11 +37,13 @@ import "core:mem"
|
||||
|
||||
Allocator :: mem.Allocator
|
||||
Allocator_Error :: mem.Allocator_Error
|
||||
Allocator_Mode :: mem.Allocator_Mode
|
||||
|
||||
Arena :: mem.Arena
|
||||
arena_allocator :: mem.arena_allocator
|
||||
arena_init :: mem.arena_init
|
||||
import "core:slice"
|
||||
import "core:unicode"
|
||||
|
||||
//#region("Proc overload mappings")
|
||||
|
||||
@@ -43,6 +53,10 @@ append :: proc {
|
||||
append_elem_string,
|
||||
}
|
||||
|
||||
append_soa :: proc {
|
||||
append_soa_elem,
|
||||
}
|
||||
|
||||
ceil :: proc {
|
||||
math.ceil_f16,
|
||||
math.ceil_f16le,
|
||||
@@ -58,8 +72,8 @@ ceil :: proc {
|
||||
}
|
||||
|
||||
clear :: proc {
|
||||
clear_dynamic_array,
|
||||
clear_map,
|
||||
builtin.clear_dynamic_array,
|
||||
builtin.clear_map,
|
||||
}
|
||||
|
||||
floor :: proc {
|
||||
@@ -80,16 +94,43 @@ fill :: proc {
|
||||
slice.fill,
|
||||
}
|
||||
|
||||
max :: proc {
|
||||
linalg.max_single,
|
||||
linalg.max_double,
|
||||
}
|
||||
|
||||
make :: proc {
|
||||
make_dynamic_array,
|
||||
make_dynamic_array_len,
|
||||
make_dynamic_array_len_cap,
|
||||
make_map,
|
||||
make_map_cap,
|
||||
builtin.make_dynamic_array,
|
||||
builtin.make_dynamic_array_len,
|
||||
builtin.make_dynamic_array_len_cap,
|
||||
builtin.make_slice,
|
||||
builtin.make_map,
|
||||
builtin.make_map_cap,
|
||||
}
|
||||
|
||||
make_soa :: proc {
|
||||
builtin.make_soa_dynamic_array_len_cap,
|
||||
builtin.make_soa_slice,
|
||||
}
|
||||
|
||||
mul :: proc {
|
||||
mul_range2_vec2,
|
||||
}
|
||||
|
||||
peek :: proc {
|
||||
peek_array,
|
||||
}
|
||||
|
||||
resize :: proc {
|
||||
resize_dynamic_array,
|
||||
builtin.resize_dynamic_array,
|
||||
}
|
||||
|
||||
round :: proc {
|
||||
math.round_f32,
|
||||
}
|
||||
|
||||
size :: proc {
|
||||
size_range2,
|
||||
}
|
||||
|
||||
vec2 :: proc {
|
17
vefontcache/profiling.odin
Normal file
17
vefontcache/profiling.odin
Normal file
@@ -0,0 +1,17 @@
|
||||
package vefontcache
|
||||
|
||||
// Add profiling hookup here
|
||||
|
||||
// import ""
|
||||
|
||||
@(deferred_none = profile_end, disabled = DISABLE_PROFILING)
|
||||
profile :: #force_inline proc "contextless" ( name : string, loc := #caller_location ) {
|
||||
}
|
||||
|
||||
@(disabled = DISABLE_PROFILING)
|
||||
profile_begin :: #force_inline proc "contextless" ( name : string, loc := #caller_location ) {
|
||||
}
|
||||
|
||||
@(disabled = DISABLE_PROFILING)
|
||||
profile_end :: #force_inline proc "contextless" () {
|
||||
}
|
@@ -1,131 +0,0 @@
|
||||
package vefontcache
|
||||
|
||||
Shaped_Text :: struct {
|
||||
glyphs : [dynamic]Glyph,
|
||||
positions : [dynamic]Vec2,
|
||||
end_cursor_pos : Vec2,
|
||||
size : Vec2,
|
||||
}
|
||||
|
||||
Shaped_Text_Cache :: struct {
|
||||
storage : [dynamic]Shaped_Text,
|
||||
state : LRU_Cache,
|
||||
next_cache_id : i32,
|
||||
}
|
||||
|
||||
shape_lru_hash :: #force_inline proc "contextless" ( hash : ^u64, bytes : []byte ) {
|
||||
for value in bytes {
|
||||
(hash^) = (( (hash^) << 8) + (hash^) ) + u64(value)
|
||||
}
|
||||
}
|
||||
|
||||
shape_text_cached :: proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : ^Entry ) -> ^Shaped_Text
|
||||
{
|
||||
// profile(#procedure)
|
||||
font := font
|
||||
font_bytes := slice_ptr( transmute(^byte) & font, size_of(Font_ID) )
|
||||
text_bytes := transmute( []byte) text_utf8
|
||||
|
||||
lru_code : u64
|
||||
shape_lru_hash( & lru_code, font_bytes )
|
||||
shape_lru_hash( & lru_code, text_bytes )
|
||||
|
||||
shape_cache := & ctx.shape_cache
|
||||
state := & ctx.shape_cache.state
|
||||
|
||||
shape_cache_idx := lru_get( state, lru_code )
|
||||
if shape_cache_idx == -1
|
||||
{
|
||||
if shape_cache.next_cache_id < i32(state.capacity) {
|
||||
shape_cache_idx = shape_cache.next_cache_id
|
||||
shape_cache.next_cache_id += 1
|
||||
evicted := lru_put( state, lru_code, shape_cache_idx )
|
||||
}
|
||||
else
|
||||
{
|
||||
next_evict_idx := lru_get_next_evicted( state )
|
||||
assert( next_evict_idx != 0xFFFFFFFFFFFFFFFF )
|
||||
|
||||
shape_cache_idx = lru_peek( state, next_evict_idx, must_find = true )
|
||||
assert( shape_cache_idx != - 1 )
|
||||
|
||||
lru_put( state, lru_code, shape_cache_idx )
|
||||
}
|
||||
|
||||
shape_entry := & shape_cache.storage[ shape_cache_idx ]
|
||||
shape_text_uncached( ctx, font, text_utf8, entry, shape_entry )
|
||||
}
|
||||
|
||||
return & shape_cache.storage[ shape_cache_idx ]
|
||||
}
|
||||
|
||||
shape_text_uncached :: proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : ^Entry, output : ^Shaped_Text )
|
||||
{
|
||||
// profile(#procedure)
|
||||
assert( ctx != nil )
|
||||
assert( font >= 0 && int(font) < len(ctx.entries) )
|
||||
|
||||
clear( & output.glyphs )
|
||||
clear( & output.positions )
|
||||
|
||||
ascent_i32, descent_i32, line_gap_i32 := parser_get_font_vertical_metrics( & entry.parser_info )
|
||||
ascent := f32(ascent_i32)
|
||||
descent := f32(descent_i32)
|
||||
line_gap := f32(line_gap_i32)
|
||||
line_height := (ascent - descent + line_gap) * entry.size_scale
|
||||
|
||||
if ctx.use_advanced_shaper
|
||||
{
|
||||
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 )
|
||||
return
|
||||
}
|
||||
else
|
||||
{
|
||||
// Note(Original Author):
|
||||
// We use our own fallback dumbass text shaping.
|
||||
// WARNING: PLEASE USE HARFBUZZ. GOOD TEXT SHAPING IS IMPORTANT FOR INTERNATIONALISATION.
|
||||
|
||||
line_count : int = 1
|
||||
max_line_width : f32 = 0
|
||||
position : Vec2
|
||||
|
||||
prev_codepoint : rune
|
||||
for codepoint in text_utf8
|
||||
{
|
||||
if prev_codepoint > 0 {
|
||||
kern := parser_get_codepoint_kern_advance( & entry.parser_info, prev_codepoint, codepoint )
|
||||
position.x += f32(kern) * entry.size_scale
|
||||
}
|
||||
if codepoint == '\n'
|
||||
{
|
||||
line_count += 1
|
||||
max_line_width = max(max_line_width, position.x)
|
||||
position.x = 0.0
|
||||
position.y -= line_height
|
||||
position.y = position.y
|
||||
prev_codepoint = rune(0)
|
||||
continue
|
||||
}
|
||||
if abs( entry.size ) <= ctx.shaper_ctx.adv_snap_small_font_threshold {
|
||||
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 )
|
||||
|
||||
append( & output.positions, Vec2 {
|
||||
ceil(position.x),
|
||||
ceil(position.y)
|
||||
})
|
||||
|
||||
position.x += f32(advance) * entry.size_scale
|
||||
prev_codepoint = codepoint
|
||||
}
|
||||
|
||||
output.end_cursor_pos = position
|
||||
max_line_width = max(max_line_width, position.x)
|
||||
|
||||
output.size.x = max_line_width
|
||||
output.size.y = f32(line_count) * line_height
|
||||
}
|
||||
}
|
@@ -1,16 +1,69 @@
|
||||
package vefontcache
|
||||
/*
|
||||
Note(Ed): The only reason I didn't directly use harfbuzz is because hamza exists and seems to be under active development as an alternative.
|
||||
Note(Ed): The only reason I didn't directly use harfbuzz is:
|
||||
https://github.com/saidwho12/hamza
|
||||
and seems to be under active development as an alternative.
|
||||
*/
|
||||
|
||||
import "core:c"
|
||||
import "thirdparty:harfbuzz"
|
||||
|
||||
Shape_Key :: u32
|
||||
|
||||
/* A text whose codepoints have had their relevant glyphs and
|
||||
associated data resolved for processing in a draw list generation stage.
|
||||
Traditionally a shape only refers to resolving which glyph and
|
||||
its position should be used for rendering.
|
||||
|
||||
For this library's case it also resolves any content that does not have to be done
|
||||
on a per-frame basis for draw list generation:
|
||||
* atlas lru codes
|
||||
* glyph bounds and scale
|
||||
* atlas region the glyph is associated with.
|
||||
|
||||
Ideally the user should resolve this shape once and cache/store it on their side.
|
||||
They have the best ability to avoid costly lookups.
|
||||
*/
|
||||
Shaped_Text :: struct #packed {
|
||||
glyph : [dynamic]Glyph,
|
||||
position : [dynamic]Vec2,
|
||||
visible : [dynamic]i32,
|
||||
atlas_lru_code : [dynamic]Atlas_Key,
|
||||
region_kind : [dynamic]Atlas_Region_Kind,
|
||||
bounds : [dynamic]Range2,
|
||||
end_cursor_pos : Vec2,
|
||||
size : Vec2,
|
||||
font : Font_ID,
|
||||
px_size : f32,
|
||||
}
|
||||
|
||||
// Ease of use cache, can handle thousands of lookups per frame with ease.
|
||||
// TODO(Ed) It might perform better with a tailored made hashtable implementation for the LRU_Cache or dedicated array struct/procs for the Shaped_Text.
|
||||
Shaped_Text_Cache :: struct {
|
||||
storage : [dynamic]Shaped_Text,
|
||||
state : LRU_Cache(Shape_Key),
|
||||
next_cache_id : i32,
|
||||
}
|
||||
|
||||
// Used by shaper_shape_text_cached, allows user to specify their own proc at compile-time without having to rewrite the caching implementation.
|
||||
Shaper_Shape_Text_Uncached_Proc :: #type proc( ctx : ^Shaper_Context,
|
||||
atlas : Atlas,
|
||||
glyph_buffer_size : Vec2,
|
||||
font : Font_ID,
|
||||
entry : Entry,
|
||||
font_px_Size : f32,
|
||||
font_scale : f32,
|
||||
text_utf8 : string,
|
||||
output : ^Shaped_Text
|
||||
)
|
||||
|
||||
// Note(Ed): Not used..
|
||||
Shaper_Kind :: enum {
|
||||
Naive = 0,
|
||||
Latin = 0,
|
||||
Harfbuzz = 1,
|
||||
}
|
||||
|
||||
// Not much here other than just keep track of a harfbuzz var and deciding to keep runtime config here used by the shapers.
|
||||
Shaper_Context :: struct {
|
||||
hb_buffer : harfbuzz.Buffer,
|
||||
|
||||
@@ -18,6 +71,7 @@ Shaper_Context :: struct {
|
||||
adv_snap_small_font_threshold : f32,
|
||||
}
|
||||
|
||||
// Only used with harbuzz for now. Resolved during load_font for a font Entry.
|
||||
Shaper_Info :: struct {
|
||||
blob : harfbuzz.Blob,
|
||||
face : harfbuzz.Face,
|
||||
@@ -30,112 +84,147 @@ shaper_init :: proc( ctx : ^Shaper_Context )
|
||||
assert( ctx.hb_buffer != nil, "VEFontCache.shaper_init: Failed to create harfbuzz buffer")
|
||||
}
|
||||
|
||||
shaper_shutdown :: proc( ctx : ^Shaper_Context )
|
||||
shaper_shutdown :: proc( ctx : ^Shaper_Context )
|
||||
{
|
||||
if ctx.hb_buffer != nil {
|
||||
harfbuzz.buffer_destroy( ctx.hb_buffer )
|
||||
}
|
||||
}
|
||||
|
||||
shaper_load_font :: proc( ctx : ^Shaper_Context, label : string, data : []byte, user_data : rawptr ) -> (info : Shaper_Info)
|
||||
shaper_load_font :: #force_inline proc( ctx : ^Shaper_Context, label : string, data : []byte, user_data : rawptr = nil ) -> (info : Shaper_Info)
|
||||
{
|
||||
using info
|
||||
blob = harfbuzz.blob_create( raw_data(data), cast(c.uint) len(data), harfbuzz.Memory_Mode.READONLY, user_data, nil )
|
||||
face = harfbuzz.face_create( blob, 0 )
|
||||
font = harfbuzz.font_create( face )
|
||||
info.blob = harfbuzz.blob_create( raw_data(data), cast(c.uint) len(data), harfbuzz.Memory_Mode.READONLY, user_data, nil )
|
||||
info.face = harfbuzz.face_create( info.blob, 0 )
|
||||
info.font = harfbuzz.font_create( info.face )
|
||||
return
|
||||
}
|
||||
|
||||
shaper_unload_font :: proc( ctx : ^Shaper_Info )
|
||||
shaper_unload_font :: #force_inline proc( info : ^Shaper_Info )
|
||||
{
|
||||
using ctx
|
||||
if blob != nil do harfbuzz.font_destroy( font )
|
||||
if face != nil do harfbuzz.face_destroy( face )
|
||||
if blob != nil do harfbuzz.blob_destroy( blob )
|
||||
if info.font != nil do harfbuzz.font_destroy( info.font )
|
||||
if info.face != nil do harfbuzz.face_destroy( info.face )
|
||||
if info.blob != nil do harfbuzz.blob_destroy( info.blob )
|
||||
}
|
||||
|
||||
shaper_shape_from_text :: proc( ctx : ^Shaper_Context, info : ^Shaper_Info, output :^Shaped_Text, text_utf8 : string,
|
||||
ascent, descent, line_gap : i32, size, size_scale : f32 )
|
||||
// TODO(Ed): Allow the user to override snap_glyph_position of the shaper context on a per-call basis (as a param)
|
||||
// Recommended shaper. Very performant.
|
||||
// TODO(Ed): Would be nice to properly support vertical shaping, right now its strictly just horizontal...
|
||||
@(optimization_mode="favor_size")
|
||||
shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context,
|
||||
atlas : Atlas,
|
||||
glyph_buffer_size : Vec2,
|
||||
font : Font_ID,
|
||||
entry : Entry,
|
||||
font_px_size : f32,
|
||||
font_scale : f32,
|
||||
text_utf8 : string,
|
||||
output : ^Shaped_Text
|
||||
)
|
||||
{
|
||||
// profile(#procedure)
|
||||
profile(#procedure)
|
||||
assert( ctx != nil )
|
||||
|
||||
clear( & output.glyph )
|
||||
clear( & output.position )
|
||||
clear( & output.visible )
|
||||
|
||||
current_script := harfbuzz.Script.UNKNOWN
|
||||
hb_ucfunc := harfbuzz.unicode_funcs_get_default()
|
||||
harfbuzz.buffer_clear_contents( ctx.hb_buffer )
|
||||
assert( info.font != nil )
|
||||
|
||||
ascent := f32(ascent)
|
||||
descent := f32(descent)
|
||||
line_gap := f32(line_gap)
|
||||
|
||||
ascent := entry.ascent
|
||||
descent := entry.descent
|
||||
line_gap := entry.line_gap
|
||||
|
||||
max_line_width := f32(0)
|
||||
line_count := 1
|
||||
line_height := ((ascent - descent + line_gap) * size_scale)
|
||||
line_height := ((ascent - descent + line_gap) * font_scale)
|
||||
|
||||
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,
|
||||
snap_shape_pos : b32, adv_snap_small_font_threshold : f32 )
|
||||
position : Vec2
|
||||
|
||||
@(optimization_mode="favor_size")
|
||||
shape_run :: proc( output : ^Shaped_Text,
|
||||
entry : Entry,
|
||||
buffer : harfbuzz.Buffer,
|
||||
script : harfbuzz.Script,
|
||||
|
||||
position : ^Vec2,
|
||||
max_line_width : ^f32,
|
||||
line_count : ^int,
|
||||
|
||||
font_px_size : f32,
|
||||
font_scale : f32,
|
||||
|
||||
snap_shape_pos : b32,
|
||||
adv_snap_small_font_threshold : f32
|
||||
)
|
||||
{
|
||||
// Set script and direction. We use the system's default langauge.
|
||||
// script = HB_SCRIPT_LATIN
|
||||
harfbuzz.buffer_set_script( buffer, script )
|
||||
profile(#procedure)
|
||||
harfbuzz.buffer_set_script ( buffer, script )
|
||||
harfbuzz.buffer_set_direction( buffer, harfbuzz.script_get_horizontal_direction( script ))
|
||||
harfbuzz.buffer_set_language( buffer, harfbuzz.language_get_default() )
|
||||
harfbuzz.buffer_set_language ( buffer, harfbuzz.language_get_default() )
|
||||
|
||||
// Perform the actual shaping of this run using HarfBuzz.
|
||||
harfbuzz.buffer_set_content_type( buffer, harfbuzz.Buffer_Content_Type.UNICODE )
|
||||
harfbuzz.shape( font, buffer, nil, 0 )
|
||||
harfbuzz.shape( entry.shaper_info.font, buffer, nil, 0 )
|
||||
|
||||
// Loop over glyphs and append to output buffer.
|
||||
glyph_count : u32
|
||||
glyph_infos := harfbuzz.buffer_get_glyph_infos( buffer, & glyph_count )
|
||||
glyph_positions := harfbuzz.buffer_get_glyph_positions( buffer, & glyph_count )
|
||||
|
||||
line_height := (ascent - descent + line_gap) * size_scale
|
||||
line_height := (entry.ascent - entry.descent + entry.line_gap) * font_scale
|
||||
|
||||
last_cluster := u32(0)
|
||||
for index : i32; index < i32(glyph_count); index += 1
|
||||
{
|
||||
hb_glyph := glyph_infos[ index ]
|
||||
hb_glyph := glyph_infos [ index ]
|
||||
hb_gposition := glyph_positions[ index ]
|
||||
glyph_id := cast(Glyph) hb_glyph.codepoint
|
||||
glyph := cast(Glyph) hb_glyph.codepoint
|
||||
|
||||
if hb_glyph.cluster > 0
|
||||
{
|
||||
(max_line_width^) = max( max_line_width^, position^ )
|
||||
(position^) = 0.0
|
||||
(vertical_position^) -= line_height
|
||||
(vertical_position^) = floor(vertical_position^ + 0.5)
|
||||
(line_count^) += 1
|
||||
(max_line_width^) = max( max_line_width^, position.x )
|
||||
position.x = 0.0
|
||||
position.y -= line_height
|
||||
position.y = floor(position.y)
|
||||
(line_count^) += 1
|
||||
|
||||
last_cluster = hb_glyph.cluster
|
||||
continue
|
||||
}
|
||||
if abs( size ) <= adv_snap_small_font_threshold
|
||||
{
|
||||
if abs( font_px_size ) <= adv_snap_small_font_threshold {
|
||||
(position^) = ceil( position^ )
|
||||
}
|
||||
|
||||
append( & output.glyphs, glyph_id )
|
||||
|
||||
pos := position^
|
||||
v_pos := vertical_position^
|
||||
offset_x := f32(hb_gposition.x_offset) * size_scale
|
||||
offset_y := f32(hb_gposition.y_offset) * size_scale
|
||||
pos += offset_x
|
||||
v_pos += offset_y
|
||||
glyph_pos := position^
|
||||
offset := Vec2 { f32(hb_gposition.x_offset), f32(hb_gposition.y_offset) } * font_scale
|
||||
glyph_pos += offset
|
||||
|
||||
if snap_shape_pos {
|
||||
pos = ceil(pos)
|
||||
v_pos = ceil(v_pos)
|
||||
glyph_pos = ceil(glyph_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
|
||||
(max_line_width^) = max(max_line_width^, position^)
|
||||
advance := Vec2 {
|
||||
f32(hb_gposition.x_advance) * font_scale,
|
||||
f32(hb_gposition.y_advance) * font_scale
|
||||
}
|
||||
(position^) += advance
|
||||
(max_line_width^) = max(max_line_width^, position.x)
|
||||
|
||||
// We track all glyphs so that user can use the shape for navigation purposes.
|
||||
append( & output.glyph, glyph )
|
||||
append( & output.position, glyph_pos)
|
||||
|
||||
// We don't accept all glyphs for rendering, harfbuzz preserves positions of non-visible codepoints (as .notdef glyphs)
|
||||
// We also double check to make sure the glyph isn't detected for drawing by the parser.
|
||||
visible_glyph := glyph != 0 && ! parser_is_glyph_empty(entry.parser_info, glyph)
|
||||
if visible_glyph {
|
||||
append( & output.visible, cast(i32) len(output.glyph) - 1 )
|
||||
}
|
||||
}
|
||||
|
||||
output.end_cursor_pos.x = position^
|
||||
output.end_cursor_pos.y = vertical_position^
|
||||
output.end_cursor_pos = position^
|
||||
harfbuzz.buffer_clear_contents( buffer )
|
||||
}
|
||||
|
||||
@@ -152,34 +241,246 @@ shaper_shape_from_text :: proc( ctx : ^Shaper_Context, info : ^Shaper_Info, outp
|
||||
// Can we continue the current run?
|
||||
ScriptKind :: harfbuzz.Script
|
||||
|
||||
special_script : b32 = script == ScriptKind.UNKNOWN || script == ScriptKind.INHERITED || script == ScriptKind.COMMON
|
||||
if special_script || script == current_script || byte_offset == 0 {
|
||||
// These scripts don't break runs because they don't represent script transitions - they adapt to their context.
|
||||
// Maintaining the current shaping run for these scripts ensures correct processing of marks, numbers,
|
||||
// and punctuation within the primary text flow.
|
||||
is_neutral_script := script == ScriptKind.UNKNOWN || script == ScriptKind.INHERITED || script == ScriptKind.COMMON
|
||||
|
||||
// Essentially if the script is neutral, or the same as current,
|
||||
// or this is the first codepoint: add it to the buffer and continue the loop.
|
||||
if is_neutral_script \
|
||||
|| script == current_script \
|
||||
|| byte_offset == 0
|
||||
{
|
||||
harfbuzz.buffer_add( ctx.hb_buffer, hb_codepoint, codepoint == '\n' ? 1 : 0 )
|
||||
current_script = special_script ? current_script : script
|
||||
current_script = is_neutral_script ? current_script : script
|
||||
continue
|
||||
}
|
||||
|
||||
// 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,
|
||||
ctx.snap_glyph_position, ctx.adv_snap_small_font_threshold
|
||||
)
|
||||
// End current run since we've encountred a significant script change.
|
||||
shape_run( output,
|
||||
entry,
|
||||
ctx.hb_buffer,
|
||||
current_script,
|
||||
& position,
|
||||
& max_line_width,
|
||||
& line_count,
|
||||
font_px_size,
|
||||
font_scale,
|
||||
ctx.snap_glyph_position,
|
||||
ctx.adv_snap_small_font_threshold
|
||||
)
|
||||
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,
|
||||
ctx.snap_glyph_position, ctx.adv_snap_small_font_threshold
|
||||
)
|
||||
shape_run( output,
|
||||
entry,
|
||||
ctx.hb_buffer,
|
||||
current_script,
|
||||
& position,
|
||||
& max_line_width,
|
||||
& line_count,
|
||||
font_px_size,
|
||||
font_scale,
|
||||
ctx.snap_glyph_position,
|
||||
ctx.adv_snap_small_font_threshold
|
||||
)
|
||||
|
||||
// Set the final size
|
||||
output.size.x = max_line_width
|
||||
output.size.y = f32(line_count) * line_height
|
||||
|
||||
// Resolve each glyphs: bounds, atlas lru, and the atlas region as we have everything we need now.
|
||||
|
||||
resize( & output.atlas_lru_code, len(output.visible) )
|
||||
resize( & output.region_kind, len(output.visible) )
|
||||
resize( & output.bounds, len(output.visible) )
|
||||
|
||||
profile_begin("atlas_lru_code")
|
||||
for vis_id, index in output.visible {
|
||||
glyph_id := output.glyph[vis_id]
|
||||
output.atlas_lru_code[index] = atlas_glyph_lru_code(entry.id, font_px_size, glyph_id)
|
||||
// atlas_lru_code is 1:1 with visible index
|
||||
}
|
||||
profile_end()
|
||||
|
||||
profile_begin("bounds & region")
|
||||
for vis_id, index in output.visible {
|
||||
glyph_id := output.glyph[vis_id]
|
||||
bounds := & output.bounds[index]
|
||||
(bounds ^) = parser_get_bounds( entry.parser_info, glyph_id )
|
||||
bounds_size_scaled := (bounds.p1 - bounds.p0) * font_scale
|
||||
output.region_kind[index] = atlas_decide_region( atlas, glyph_buffer_size, bounds_size_scaled )
|
||||
// bounds & region_kind are 1:1 with visible index
|
||||
}
|
||||
profile_end()
|
||||
|
||||
output.font = font
|
||||
output.px_size = font_px_size
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(Ed): Allow the user to override snap_glyph_position of the shaper context on a per-call basis (as an param)
|
||||
// Basic western alphabet based shaping. Not that much faster than harfbuzz if at all.
|
||||
shaper_shape_text_latin :: proc( ctx : ^Shaper_Context,
|
||||
atlas : Atlas,
|
||||
glyph_buffer_size : Vec2,
|
||||
font : Font_ID,
|
||||
entry : Entry,
|
||||
font_px_size : f32,
|
||||
font_scale : f32,
|
||||
text_utf8 : string,
|
||||
output : ^Shaped_Text
|
||||
)
|
||||
{
|
||||
profile(#procedure)
|
||||
assert( ctx != nil )
|
||||
|
||||
clear( & output.glyph )
|
||||
clear( & output.position )
|
||||
clear( & output.visible )
|
||||
|
||||
line_height := (entry.ascent - entry.descent + entry.line_gap) * font_scale
|
||||
|
||||
line_count : int = 1
|
||||
max_line_width : f32 = 0
|
||||
position : Vec2
|
||||
|
||||
prev_codepoint : rune
|
||||
for codepoint, index in text_utf8
|
||||
{
|
||||
if prev_codepoint > 0 {
|
||||
kern := parser_get_codepoint_kern_advance( entry.parser_info, prev_codepoint, codepoint )
|
||||
position.x += f32(kern) * font_scale
|
||||
}
|
||||
if codepoint == '\n'
|
||||
{
|
||||
line_count += 1
|
||||
max_line_width = max(max_line_width, position.x)
|
||||
position.x = 0.0
|
||||
position.y -= line_height
|
||||
position.y = position.y
|
||||
prev_codepoint = rune(0)
|
||||
continue
|
||||
}
|
||||
if abs( font_px_size ) <= ctx.adv_snap_small_font_threshold {
|
||||
position.x = ceil(position.x)
|
||||
}
|
||||
|
||||
glyph_index := parser_find_glyph_index( entry.parser_info, codepoint )
|
||||
is_glyph_empty := parser_is_glyph_empty( entry.parser_info, glyph_index )
|
||||
|
||||
if ctx.snap_glyph_position {
|
||||
position.x = ceil(position.x)
|
||||
position.y = ceil(position.y)
|
||||
}
|
||||
append( & output.glyph, glyph_index)
|
||||
append( & output.position, position)
|
||||
|
||||
if ! is_glyph_empty {
|
||||
append( & output.visible, cast(i32) len(output.glyph) - 1 )
|
||||
}
|
||||
|
||||
advance, _ := parser_get_codepoint_horizontal_metrics( entry.parser_info, codepoint )
|
||||
position.x += f32(advance) * font_scale
|
||||
prev_codepoint = codepoint
|
||||
}
|
||||
|
||||
output.end_cursor_pos = position
|
||||
max_line_width = max(max_line_width, position.x)
|
||||
|
||||
output.size.x = max_line_width
|
||||
output.size.y = f32(line_count) * line_height
|
||||
|
||||
// Resolve each glyphs: bounds, atlas lru, and the atlas region as we have everything we need now.
|
||||
|
||||
resize( & output.atlas_lru_code, len(output.glyph) )
|
||||
resize( & output.region_kind, len(output.glyph) )
|
||||
resize( & output.bounds, len(output.glyph) )
|
||||
|
||||
profile_begin("atlas_lru_code")
|
||||
for vis_id, index in output.visible {
|
||||
glyph_id := output.glyph[vis_id]
|
||||
output.atlas_lru_code[index] = atlas_glyph_lru_code(entry.id, font_px_size, glyph_id)
|
||||
// atlas_lru_code is 1:1 with visible index
|
||||
}
|
||||
profile_end()
|
||||
|
||||
profile_begin("bounds & region")
|
||||
for vis_id, index in output.visible {
|
||||
glyph_id := output.glyph[vis_id]
|
||||
bounds := & output.bounds[index]
|
||||
(bounds ^) = parser_get_bounds( entry.parser_info, glyph_id )
|
||||
bounds_size_scaled := (bounds.p1 - bounds.p0) * font_scale
|
||||
output.region_kind[index] = atlas_decide_region( atlas, glyph_buffer_size, bounds_size_scaled )
|
||||
// bounds & region_kind are 1:1 with visible index
|
||||
}
|
||||
profile_end()
|
||||
|
||||
output.font = font
|
||||
output.px_size = font_px_size
|
||||
}
|
||||
|
||||
// Shapes are tracked by the library's context using the shape cache
|
||||
// and the key is resolved using the font, the desired pixel size, and the text bytes to be shaped.
|
||||
// Thus this procedures cost will be proporitonal to how much text it has to sift through.
|
||||
// djb8_hash is used as its been pretty good for thousands of hashed lines that around 6-250 charactes long
|
||||
// (and its very fast).
|
||||
@(optimization_mode="favor_size")
|
||||
shaper_shape_text_cached :: proc( text_utf8 : string,
|
||||
ctx : ^Shaper_Context,
|
||||
shape_cache : ^Shaped_Text_Cache,
|
||||
atlas : Atlas,
|
||||
glyph_buffer_size : Vec2,
|
||||
font : Font_ID,
|
||||
entry : Entry,
|
||||
font_px_size : f32,
|
||||
font_scale : f32,
|
||||
shape_text_uncached : $Shaper_Shape_Text_Uncached_Proc
|
||||
) -> (shaped_text : Shaped_Text)
|
||||
{
|
||||
profile(#procedure)
|
||||
font := font
|
||||
font_px_size := font_px_size
|
||||
font_bytes := to_bytes( & font )
|
||||
size_bytes := to_bytes( & font_px_size )
|
||||
text_bytes := transmute( []byte) text_utf8
|
||||
|
||||
lru_code : Shape_Key
|
||||
djb8_hash( & lru_code, font_bytes )
|
||||
djb8_hash( & lru_code, size_bytes )
|
||||
djb8_hash( & lru_code, text_bytes )
|
||||
|
||||
state := & shape_cache.state
|
||||
|
||||
shape_cache_idx := lru_get( state, lru_code )
|
||||
if shape_cache_idx == -1
|
||||
{
|
||||
if shape_cache.next_cache_id < i32(state.capacity){
|
||||
shape_cache_idx = shape_cache.next_cache_id
|
||||
shape_cache.next_cache_id += 1
|
||||
evicted := lru_put( state, lru_code, shape_cache_idx )
|
||||
}
|
||||
else
|
||||
{
|
||||
next_evict_idx := lru_get_next_evicted( state ^ )
|
||||
assert( next_evict_idx != LRU_Fail_Mask_32 )
|
||||
|
||||
shape_cache_idx = lru_peek( state ^, next_evict_idx, must_find = true )
|
||||
assert( shape_cache_idx != - 1 )
|
||||
|
||||
lru_put( state, lru_code, shape_cache_idx )
|
||||
}
|
||||
|
||||
storage_entry := & shape_cache.storage[ shape_cache_idx ]
|
||||
shape_text_uncached( ctx, atlas, glyph_buffer_size, font, entry, font_px_size, font_scale, text_utf8, storage_entry )
|
||||
|
||||
shaped_text = storage_entry ^
|
||||
return
|
||||
}
|
||||
|
||||
shaped_text = shape_cache.storage[ shape_cache_idx ]
|
||||
return
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user