sdf
This commit is contained in:
203
sdl_gpu.cm
203
sdl_gpu.cm
@@ -36,6 +36,7 @@ var _mask_frag = null
|
||||
var _mask_frag = null
|
||||
var _crt_frag = null
|
||||
var _text_sdf_frag = null
|
||||
var _text_msdf_frag = null
|
||||
|
||||
// Pipelines
|
||||
var _pipelines = {}
|
||||
@@ -218,6 +219,18 @@ function _load_shaders() {
|
||||
})
|
||||
}
|
||||
|
||||
var text_msdf_frag_code = io.slurp("shaders/msl/text_msdf.frag.msl")
|
||||
if (text_msdf_frag_code) {
|
||||
_text_msdf_frag = new gpu_mod.shader(_gpu, {
|
||||
code: text_msdf_frag_code,
|
||||
stage: "fragment",
|
||||
format: "msl",
|
||||
entrypoint: "fragment_main",
|
||||
num_uniform_buffers: 1,
|
||||
num_samplers: 1
|
||||
})
|
||||
}
|
||||
|
||||
var crt_frag_code = io.slurp("shaders/msl/crt.frag.msl")
|
||||
if (crt_frag_code) {
|
||||
_crt_frag = new gpu_mod.shader(_gpu, {
|
||||
@@ -482,6 +495,78 @@ function _create_pipelines() {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// SDF text pipeline
|
||||
if (_sprite_vert && _text_sdf_frag) {
|
||||
_pipelines.text_sdf = new gpu_mod.graphics_pipeline(_gpu, {
|
||||
vertex: _sprite_vert,
|
||||
fragment: _text_sdf_frag,
|
||||
primitive: "triangle",
|
||||
cull: "none",
|
||||
face: "counter_clockwise",
|
||||
fill: "fill",
|
||||
vertex_buffer_descriptions: [{
|
||||
slot: 0,
|
||||
pitch: 32,
|
||||
input_rate: "vertex"
|
||||
}],
|
||||
vertex_attributes: [
|
||||
{location: 0, buffer_slot: 0, format: "float2", offset: 0},
|
||||
{location: 1, buffer_slot: 0, format: "float2", offset: 8},
|
||||
{location: 2, buffer_slot: 0, format: "float4", offset: 16}
|
||||
],
|
||||
target: {
|
||||
color_targets: [{
|
||||
format: _swapchain_format,
|
||||
blend: {
|
||||
enabled: true,
|
||||
src_rgb: "src_alpha",
|
||||
dst_rgb: "one_minus_src_alpha",
|
||||
op_rgb: "add",
|
||||
src_alpha: "one",
|
||||
dst_alpha: "one_minus_src_alpha",
|
||||
op_alpha: "add"
|
||||
}
|
||||
}]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// MSDF text pipeline
|
||||
if (_sprite_vert && _text_msdf_frag) {
|
||||
_pipelines.text_msdf = new gpu_mod.graphics_pipeline(_gpu, {
|
||||
vertex: _sprite_vert,
|
||||
fragment: _text_msdf_frag,
|
||||
primitive: "triangle",
|
||||
cull: "none",
|
||||
face: "counter_clockwise",
|
||||
fill: "fill",
|
||||
vertex_buffer_descriptions: [{
|
||||
slot: 0,
|
||||
pitch: 32,
|
||||
input_rate: "vertex"
|
||||
}],
|
||||
vertex_attributes: [
|
||||
{location: 0, buffer_slot: 0, format: "float2", offset: 0},
|
||||
{location: 1, buffer_slot: 0, format: "float2", offset: 8},
|
||||
{location: 2, buffer_slot: 0, format: "float4", offset: 16}
|
||||
],
|
||||
target: {
|
||||
color_targets: [{
|
||||
format: _swapchain_format,
|
||||
blend: {
|
||||
enabled: true,
|
||||
src_rgb: "src_alpha",
|
||||
dst_rgb: "one_minus_src_alpha",
|
||||
op_rgb: "add",
|
||||
src_alpha: "one",
|
||||
dst_alpha: "one_minus_src_alpha",
|
||||
op_alpha: "add"
|
||||
}
|
||||
}]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
@@ -1140,21 +1225,15 @@ function _render_batch(cmd_buffer, pass, batch, camera, target) {
|
||||
}
|
||||
|
||||
function _render_text(cmd_buffer, pass, drawable, camera, target) {
|
||||
// Get font
|
||||
// Get font - support mode tag: 'bitmap', 'sdf', 'msdf'
|
||||
var font_path = drawable.font
|
||||
var size = drawable.size || 16
|
||||
var is_sdf = drawable.sdf || false
|
||||
var font = _get_font_cache(font_path, size, is_sdf)
|
||||
var mode = drawable.mode || (drawable.sdf ? 'sdf' : 'bitmap')
|
||||
var font = _get_font_cache(font_path, size, mode)
|
||||
if (!font) return
|
||||
|
||||
// Generate vertices using staef
|
||||
var pos = drawable.pos
|
||||
// Convert world/camera pos to screen/local
|
||||
// Note: staef generates raw vertex positions. We usually want to transform them by camera matrix.
|
||||
// The sprite pipeline applies the camera matrix (proj) to the positions.
|
||||
// So we should feed "local" positions (or world positions) into the buffer, and let vertex shader transform them.
|
||||
// staef's make_text_buffer generates vertices relative to the input position.
|
||||
|
||||
var text_pos = {x: pos.x, y: pos.y, width: 0, height: 0}
|
||||
var color = drawable.color || {r:1, g:1, b:1, a:1}
|
||||
|
||||
@@ -1164,10 +1243,9 @@ function _render_text(cmd_buffer, pass, drawable, camera, target) {
|
||||
|
||||
if (ax != 0 || ay != 0) {
|
||||
var dim = font.text_size(drawable.text)
|
||||
// dim is {x, y} (width, height)
|
||||
if (dim) {
|
||||
text_pos.x -= dim.x * ax
|
||||
text_pos.y -= dim.y * ay // staef usually draws from top-left, need to verify
|
||||
text_pos.y -= dim.y * ay
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1178,7 +1256,7 @@ function _render_text(cmd_buffer, pass, drawable, camera, target) {
|
||||
var num_verts = mesh.num_vertices
|
||||
var interleaved = geometry.weave([{data:mesh.xy, stride: mesh.xy_stride}, {data:mesh.uv, stride: mesh.uv_stride}, {data:mesh.color, stride: mesh.color_stride}])
|
||||
|
||||
var indices = mesh.indices // This is a blob of uint16
|
||||
var indices = mesh.indices
|
||||
var num_indices = mesh.num_indices
|
||||
|
||||
// Upload
|
||||
@@ -1204,37 +1282,58 @@ function _render_text(cmd_buffer, pass, drawable, camera, target) {
|
||||
// Setup pipeline
|
||||
var proj = _build_camera_matrix(camera, target.width, target.height)
|
||||
|
||||
if (is_sdf && _pipelines.text_sdf) {
|
||||
// Select pipeline based on mode
|
||||
var is_sdf = (mode == 'sdf')
|
||||
var is_msdf = (mode == 'msdf')
|
||||
|
||||
if (is_msdf && _pipelines.text_msdf) {
|
||||
pass.bind_pipeline(_pipelines.text_msdf)
|
||||
|
||||
// Build uniforms for MSDF
|
||||
// Struct: float outline_width, float sharpness, float2 _pad, float4 outline_color
|
||||
var u_data = new blob_mod(32)
|
||||
|
||||
// Convert outline_width from pixel-ish units to normalized SDF units
|
||||
// outline_width in drawable is in "visual" units, we need to normalize
|
||||
// A typical range is 0.0-0.3 in SDF units
|
||||
var outline_w = drawable.outline_width || 0
|
||||
if (outline_w > 0) outline_w = outline_w / 100.0 // Scale down from user units
|
||||
|
||||
u_data.wf(outline_w) // outline_width
|
||||
u_data.wf(font.sharpness || 1.0) // sharpness from font
|
||||
u_data.wf(0) // _pad.x
|
||||
u_data.wf(0) // _pad.y
|
||||
|
||||
var oc = drawable.outline_color || {r:0, g:0, b:0, a:1}
|
||||
u_data.wf(oc.r) // outline_color.r
|
||||
u_data.wf(oc.g) // outline_color.g
|
||||
u_data.wf(oc.b) // outline_color.b
|
||||
u_data.wf(oc.a || 1) // outline_color.a
|
||||
|
||||
cmd_buffer.push_fragment_uniform_data(0, stone(u_data))
|
||||
|
||||
} else if (is_sdf && _pipelines.text_sdf) {
|
||||
pass.bind_pipeline(_pipelines.text_sdf)
|
||||
|
||||
// Upload uniforms for SDF (outline)
|
||||
// Build uniforms for SDF
|
||||
// Struct: float outline_width, float sharpness, float2 _pad, float4 outline_color
|
||||
var u_data = new blob_mod(32)
|
||||
u_data.wf(drawable.outline_width || 0)
|
||||
u_data.wf(0) // padding/unused
|
||||
u_data.wf(0) // padding
|
||||
u_data.wf(0) // padding
|
||||
|
||||
var oc = drawable.outline_color || {r:0, g:0, b:0, a:0}
|
||||
var outline_w = drawable.outline_width || 0
|
||||
if (outline_w > 0) outline_w = outline_w / 100.0
|
||||
|
||||
u_data.wf(outline_w) // outline_width
|
||||
u_data.wf(font.sharpness || 1.0) // sharpness from font
|
||||
u_data.wf(0) // _pad.x
|
||||
u_data.wf(0) // _pad.y
|
||||
|
||||
var oc = drawable.outline_color || {r:0, g:0, b:0, a:1}
|
||||
u_data.wf(oc.r)
|
||||
u_data.wf(oc.g)
|
||||
u_data.wf(oc.b)
|
||||
u_data.wf(oc.a) // Used as alpha?? Shader expects float3 color. Structure has float4 color?
|
||||
// Shader: float outline_width; float3 outline_color;
|
||||
// Layout: offset 0 (4 bytes), offset 16 (16 bytes vec4 alignment usually for float3)
|
||||
// Actually metal float3 is 16 byte aligned/sized often in buffers?
|
||||
// Let's assume standard packed: float (4), float3 (12 needed, but alignment constraints).
|
||||
// Uniforms struct: width (4), padding (12) -> size 16. Color (12/16) -> offset 16.
|
||||
u_data.wf(oc.a || 1)
|
||||
|
||||
// Let's rewrite struct in shader or be careful.
|
||||
// Struct: float outline_width; float3 outline_color;
|
||||
// If strict metal alignment:
|
||||
// width at 0.
|
||||
// float3 at 16 (since it's a type that requires 16 byte alignment? No, float4 does. float3 is usually float4 size/alignment in buffers).
|
||||
|
||||
pass.push_fragment_uniform_data(0, stone(u_data)) // Wait, push_fragment_uniform_data on cmd_buffer?
|
||||
// The code below uses cmd_buffer.push_vertex_uniform_data(0, proj)
|
||||
// We need push_fragment_uniform_data.
|
||||
cmd_buffer.push_fragment_uniform_data(0, stone(u_data)) // Bind to buffer(0) in fragment
|
||||
cmd_buffer.push_fragment_uniform_data(0, stone(u_data))
|
||||
|
||||
} else {
|
||||
pass.bind_pipeline(_pipelines.sprite_alpha)
|
||||
@@ -1243,32 +1342,46 @@ function _render_text(cmd_buffer, pass, drawable, camera, target) {
|
||||
pass.bind_vertex_buffers(0, [{buffer: vb, offset: 0}])
|
||||
pass.bind_index_buffer({buffer: ib, offset: 0}, 16)
|
||||
|
||||
// Bind font texture
|
||||
// staef font has 'texture' property which is pixel blob + dims. We need to upload it to GPU if not already.
|
||||
var font_tex = _get_font_texture(font, is_sdf)
|
||||
// Bind font texture - use linear filtering for SDF/MSDF
|
||||
var font_tex = _get_font_texture(font, mode)
|
||||
var sampler = (is_sdf || is_msdf) ? _sampler_linear : _sampler_nearest
|
||||
|
||||
pass.bind_fragment_samplers(0, [{texture: font_tex, sampler: _sampler_nearest}])
|
||||
pass.bind_fragment_samplers(0, [{texture: font_tex, sampler: sampler}])
|
||||
cmd_buffer.push_vertex_uniform_data(0, proj)
|
||||
pass.draw_indexed(num_indices, 1, 0, 0, 0)
|
||||
}
|
||||
|
||||
function _get_font_cache(path, size, is_sdf) {
|
||||
var key = `${path}.${size}.${is_sdf ? 'sdf' : 'bmp'}`
|
||||
function _get_font_cache(path, size, mode) {
|
||||
// mode can be 'bitmap', 'sdf', 'msdf', or boolean (legacy)
|
||||
if (mode == true) mode = 'sdf'
|
||||
else if (mode == false || !mode) mode = 'bitmap'
|
||||
|
||||
var key = `${path}.${size}.${mode}`
|
||||
if (_font_cache[key]) return _font_cache[key]
|
||||
|
||||
var fullpath = res.find_font(path) // Assuming this resolves correctly
|
||||
var fullpath = res.find_font(path)
|
||||
if (!fullpath) return null
|
||||
|
||||
var data = io.slurp(fullpath)
|
||||
if (!data) return null
|
||||
|
||||
// Create staef font
|
||||
// Create staef font based on mode
|
||||
try {
|
||||
var font = new staef.font(data, size, is_sdf)
|
||||
var font
|
||||
if (mode == 'msdf') {
|
||||
// MSDF: em_px=size, range_px=4, padding_px=6, sharpness=1.0
|
||||
font = new staef.msdf_font(data, size, 4.0, 6, 1.0)
|
||||
} else if (mode == 'sdf') {
|
||||
// SDF: em_px=size, range_px=12, padding_px=14, sharpness=1.0
|
||||
font = new staef.sdf_font(data, size, 12.0, 14, 1.0)
|
||||
} else {
|
||||
// Bitmap
|
||||
font = new staef.font(data, size, false)
|
||||
}
|
||||
_font_cache[key] = font
|
||||
return font
|
||||
} catch(e) {
|
||||
log.console(`sdl_gpu: Failed to load font ${path}:${size}: ${e.message}`)
|
||||
log.console(`sdl_gpu: Failed to load font ${path}:${size}:${mode}: ${e.message}`)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user