Files
VEFontCache-Odin/backend/sokol/backend_sokol.odin

599 lines
17 KiB
Odin

package ve_sokol
import ve "../../vefontcache"
import gfx "thirdparty:sokol/gfx"
import glue "thirdparty:sokol/glue"
Context :: struct {
draw_list_vbuf : gfx.Buffer,
draw_list_ibuf : gfx.Buffer,
glyph_shader : gfx.Shader,
atlas_shader : gfx.Shader,
screen_shader : gfx.Shader,
// ve.glyph_buffer.(width, height), R8
glyph_rt_color : gfx.Image,
glyph_rt_depth : gfx.Image,
glyph_rt_sampler : gfx.Sampler,
// ve.atlas.(width, height), R8
atlas_rt_color : gfx.Image,
atlas_rt_depth : gfx.Image,
atlas_rt_sampler : gfx.Sampler,
glyph_pipeline : gfx.Pipeline,
atlas_pipeline : gfx.Pipeline,
screen_pipeline : gfx.Pipeline,
glyph_pass : gfx.Pass,
atlas_pass : gfx.Pass,
screen_pass : gfx.Pass,
}
setup_gfx_objects :: proc( ctx : ^Context, ve_ctx : ^ve.Context, vert_cap, index_cap : u64 )
{
Attachment_Desc :: gfx.Attachment_Desc
Blend_Factor :: gfx.Blend_Factor
Blend_Op :: gfx.Blend_Op
Blend_State :: gfx.Blend_State
Border_Color :: gfx.Border_Color
Buffer_Desciption :: gfx.Buffer_Desc
Buffer_Usage :: gfx.Buffer_Usage
Color_Target_State :: gfx.Color_Target_State
Filter :: gfx.Filter
Image_Desc :: gfx.Image_Desc
Image_Usage :: gfx.Image_Usage
Pass_Action :: gfx.Pass_Action
Range :: gfx.Range
Resource_State :: gfx.Resource_State
Sampler_Description :: gfx.Sampler_Desc
Wrap :: gfx.Wrap
Vertex_Attribute_State :: gfx.Vertex_Attr_State
Vertex_Buffer_Layout_State :: gfx.Vertex_Buffer_Layout_State
Vertex_Index_Type :: gfx.Index_Type
Vertex_Format :: gfx.Vertex_Format
Vertex_Layout_State :: gfx.Vertex_Layout_State
Vertex_Step :: gfx.Vertex_Step
backend := gfx.query_backend()
app_env := glue.environment()
ctx.glyph_shader = gfx.make_shader(render_glyph_shader_desc(backend) )
ctx.atlas_shader = gfx.make_shader(blit_atlas_shader_desc(backend) )
ctx.screen_shader = gfx.make_shader(draw_text_shader_desc(backend) )
ctx.draw_list_vbuf = gfx.make_buffer( Buffer_Desciption {
size = cast(uint)(size_of([4]f32) * vert_cap),
usage = Buffer_Usage { vertex_buffer = true, stream_update = true, immutable = false },
})
assert( gfx.query_buffer_state( ctx.draw_list_vbuf) < Resource_State.FAILED, "Failed to make draw_list_vbuf" )
ctx.draw_list_ibuf = gfx.make_buffer( Buffer_Desciption {
size = cast(uint)(size_of(u32) * index_cap),
usage = { index_buffer = true, stream_update = true, immutable = false },
})
assert( gfx.query_buffer_state( ctx.draw_list_ibuf) < Resource_State.FAILED, "Failed to make draw_list_iubuf" )
Image_Filter := Filter.LINEAR
// glyph_pipeline
{
vs_layout : Vertex_Layout_State
{
vs_layout.attrs[ATTR_render_glyph_v_position] = Vertex_Attribute_State {
format = Vertex_Format.FLOAT2,
offset = 0,
buffer_index = 0,
}
vs_layout.attrs[ATTR_render_glyph_v_texture] = Vertex_Attribute_State {
format = Vertex_Format.FLOAT2,
offset = size_of(ve.Vec2),
buffer_index = 0,
}
vs_layout.buffers[0] = Vertex_Buffer_Layout_State {
stride = size_of([4]f32),
step_func = Vertex_Step.PER_VERTEX
}
}
color_target := Color_Target_State {
pixel_format = .R8,
write_mask = .RGBA,
blend = Blend_State {
enabled = true,
src_factor_rgb = .ONE_MINUS_DST_COLOR,
dst_factor_rgb = .ONE_MINUS_SRC_COLOR,
op_rgb = Blend_Op.ADD,
src_factor_alpha = .ONE_MINUS_DST_ALPHA,
dst_factor_alpha = .ONE_MINUS_SRC_ALPHA,
op_alpha = Blend_Op.ADD,
},
}
ctx.glyph_pipeline = gfx.make_pipeline({
shader = ctx.glyph_shader,
layout = vs_layout,
index_type = Vertex_Index_Type.UINT32,
colors = {
0 = color_target,
},
color_count = 1,
depth = {
pixel_format = .DEPTH,
compare = .ALWAYS,
write_enabled = false,
},
cull_mode = .NONE,
sample_count = 1,
})
assert( gfx.query_pipeline_state(ctx.glyph_pipeline) < Resource_State.FAILED, "Failed to make glyph_pipeline" )
}
// glyph_pass
{
ctx.glyph_rt_color = gfx.make_image( Image_Desc {
type = ._2D,
usage = Image_Usage { render_attachment = true, immutable = true },
width = i32(ve_ctx.glyph_buffer.size.x),
height = i32(ve_ctx.glyph_buffer.size.y),
num_slices = 1,
num_mipmaps = 1,
pixel_format = .R8,
sample_count = 1,
})
assert( gfx.query_image_state(ctx.glyph_rt_color) < Resource_State.FAILED, "Failed to make glyph_pipeline" )
ctx.glyph_rt_depth = gfx.make_image( Image_Desc {
type = ._2D,
usage = Image_Usage { render_attachment = true, immutable = true },
width = i32(ve_ctx.glyph_buffer.size.x),
height = i32(ve_ctx.glyph_buffer.size.y),
num_slices = 1,
num_mipmaps = 1,
pixel_format = .DEPTH,
sample_count = 1,
})
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 = -1.0,
max_lod = 1.0,
border_color = Border_Color.OPAQUE_BLACK,
compare = .NEVER,
max_anisotropy = 1,
})
assert( gfx.query_sampler_state( ctx.glyph_rt_sampler) < Resource_State.FAILED, "Failed to make atlas_rt_sampler" )
color_attach := Attachment_Desc {
image = ctx.glyph_rt_color,
}
glyph_attachments := gfx.make_attachments({
colors = {
0 = color_attach,
},
depth_stencil = {
image = ctx.glyph_rt_depth,
},
})
assert( gfx.query_attachments_state(glyph_attachments) < Resource_State.FAILED, "Failed to make glyph_attachments" )
glyph_action := Pass_Action {
colors = {
0 = {
load_action = .LOAD,
store_action = .STORE,
clear_value = {0.00, 0.00, 0.00, 1.00},
}
},
depth = {
load_action = .DONTCARE,
store_action = .DONTCARE,
clear_value = 0.0,
},
stencil = {
load_action = .DONTCARE,
store_action = .DONTCARE,
clear_value = 0,
}
}
ctx.glyph_pass = gfx.Pass {
action = glyph_action,
attachments = glyph_attachments,
// label =
}
}
// atlas_pipeline
{
vs_layout : Vertex_Layout_State
{
vs_layout.attrs[ATTR_blit_atlas_v_position] = Vertex_Attribute_State {
format = Vertex_Format.FLOAT2,
offset = 0,
buffer_index = 0,
}
vs_layout.attrs[ATTR_blit_atlas_v_texture] = Vertex_Attribute_State {
format = Vertex_Format.FLOAT2,
offset = size_of(ve.Vec2),
buffer_index = 0,
}
vs_layout.buffers[0] = Vertex_Buffer_Layout_State {
stride = size_of([4]f32),
step_func = Vertex_Step.PER_VERTEX
}
}
color_target := Color_Target_State {
pixel_format = .R8,
write_mask = .RGBA,
blend = Blend_State {
enabled = true,
src_factor_rgb = .SRC_ALPHA,
dst_factor_rgb = .ONE_MINUS_SRC_ALPHA,
op_rgb = Blend_Op.ADD,
src_factor_alpha = .SRC_ALPHA,
dst_factor_alpha = .ONE_MINUS_SRC_ALPHA,
op_alpha = Blend_Op.ADD,
},
}
ctx.atlas_pipeline = gfx.make_pipeline({
shader = ctx.atlas_shader,
layout = vs_layout,
index_type = Vertex_Index_Type.UINT32,
colors = {
0 = color_target,
},
color_count = 1,
depth = {
pixel_format = .DEPTH,
compare = .ALWAYS,
write_enabled = false,
},
cull_mode = .NONE,
sample_count = 1,
})
}
// atlas_pass
{
ctx.atlas_rt_color = gfx.make_image( Image_Desc {
type = ._2D,
usage = { render_attachment = true, immutable = true },
width = i32(ve_ctx.atlas.size.x),
height = i32(ve_ctx.atlas.size.y),
num_slices = 1,
num_mipmaps = 1,
pixel_format = .R8,
sample_count = 1,
// TODO(Ed): Setup labels for debug tracing/logging
// label =
})
assert( gfx.query_image_state(ctx.atlas_rt_color) < Resource_State.FAILED, "Failed to make atlas_rt_color")
ctx.atlas_rt_depth = gfx.make_image( Image_Desc {
type = ._2D,
usage = { render_attachment = true, immutable = true },
width = i32(ve_ctx.atlas.size.x),
height = i32(ve_ctx.atlas.size.y),
num_slices = 1,
num_mipmaps = 1,
pixel_format = .DEPTH,
sample_count = 1,
})
assert( gfx.query_image_state(ctx.atlas_rt_depth) < Resource_State.FAILED, "Failed to make atlas_rt_depth")
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 = -1.0,
max_lod = 1.0,
border_color = Border_Color.OPAQUE_BLACK,
compare = .NEVER,
max_anisotropy = 1,
})
assert( gfx.query_sampler_state( ctx.atlas_rt_sampler) < Resource_State.FAILED, "Failed to make atlas_rt_sampler" )
color_attach := Attachment_Desc {
image = ctx.atlas_rt_color,
}
atlas_attachments := gfx.make_attachments({
colors = {
0 = color_attach,
},
depth_stencil = {
image = ctx.atlas_rt_depth,
},
})
assert( gfx.query_attachments_state(atlas_attachments) < Resource_State.FAILED, "Failed to make atlas_attachments")
atlas_action := Pass_Action {
colors = {
0 = {
load_action = .LOAD,
store_action = .STORE,
clear_value = {0.00, 0.00, 0.00, 1.0},
}
},
depth = {
load_action = .DONTCARE,
store_action = .DONTCARE,
clear_value = 0.0,
},
stencil = {
load_action = .DONTCARE,
store_action = .DONTCARE,
clear_value = 0,
}
}
ctx.atlas_pass = gfx.Pass {
action = atlas_action,
attachments = atlas_attachments,
}
}
// screen pipeline
{
vs_layout : Vertex_Layout_State
{
vs_layout.attrs[ATTR_draw_text_v_position] = Vertex_Attribute_State {
format = Vertex_Format.FLOAT2,
offset = 0,
buffer_index = 0,
}
vs_layout.attrs[ATTR_draw_text_v_texture] = Vertex_Attribute_State {
format = Vertex_Format.FLOAT2,
offset = size_of(ve.Vec2),
buffer_index = 0,
}
vs_layout.buffers[0] = Vertex_Buffer_Layout_State {
stride = size_of([4]f32),
step_func = Vertex_Step.PER_VERTEX
}
}
color_target := Color_Target_State {
pixel_format = app_env.defaults.color_format,
write_mask = .RGBA,
blend = Blend_State {
enabled = true,
src_factor_rgb = .SRC_ALPHA,
dst_factor_rgb = .ONE_MINUS_SRC_ALPHA,
op_rgb = Blend_Op.ADD,
src_factor_alpha = .SRC_ALPHA,
dst_factor_alpha = .ONE_MINUS_SRC_ALPHA,
op_alpha = Blend_Op.ADD,
},
}
ctx.screen_pipeline = gfx.make_pipeline({
shader = ctx.screen_shader,
layout = vs_layout,
index_type = Vertex_Index_Type.UINT32,
colors = {
0 = color_target,
},
color_count = 1,
sample_count = 1,
depth = {
pixel_format = app_env.defaults.depth_format,
compare = .ALWAYS,
write_enabled = false,
},
cull_mode = .NONE,
})
assert( gfx.query_pipeline_state(ctx.screen_pipeline) < Resource_State.FAILED, "Failed to make screen_pipeline" )
}
// screen_pass
{
screen_action := Pass_Action {
colors = {
0 = {
load_action = .LOAD,
store_action = .STORE,
clear_value = {0.00, 0.00, 0.00, 0.0},
},
1 = {
load_action = .LOAD,
store_action = .STORE,
clear_value = {0.00, 0.00, 0.00, 0.0},
},
2 = {
load_action = .LOAD,
store_action = .STORE,
clear_value = {0.00, 0.00, 0.00, 0.0},
}
},
depth = {
load_action = .DONTCARE,
store_action = .DONTCARE,
clear_value = 0.0,
},
stencil = {
load_action = .DONTCARE,
store_action = .DONTCARE,
clear_value = 0,
}
}
ctx.screen_pass = gfx.Pass {
action = screen_action,
}
}
}
render_text_layer :: proc( screen_extent : ve.Vec2, ve_ctx : ^ve.Context, ctx : Context )
{
// profile("VEFontCache: render text layer")
Bindings :: gfx.Bindings
Range :: gfx.Range
Shader_Stage :: gfx.Shader_Stage
vbuf_layer_slice, ibuf_layer_slice, calls_layer_slice := ve.get_draw_list_layer( ve_ctx, optimize_before_returning = true )
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( ctx.draw_list_vbuf, vbuf_ve_range )
gfx.append_buffer( ctx.draw_list_ibuf, ibuf_ve_range )
ve.flush_draw_list_layer( ve_ctx )
screen_width := u32(screen_extent.x * 2)
screen_height := u32(screen_extent.y * 2)
for & draw_call in calls_layer_slice
{
watch := draw_call
// profile("VEFontCache: draw call")
num_indices := draw_call.end_index - draw_call.start_index
switch draw_call.pass
{
// 1. Do the glyph rendering pass
// Glyphs are first rendered to an intermediate 2k x 512px R8 texture
case .Glyph:
// profile("VEFontCache: draw call: glyph")
if num_indices == 0 && ! draw_call.clear_before_draw {
continue
}
width := ve_ctx.glyph_buffer.size.x
height := ve_ctx.glyph_buffer.size.y
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
}
gfx.begin_pass( pass )
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( ctx.glyph_pipeline )
bindings := Bindings {
vertex_buffers = {
0 = ctx.draw_list_vbuf,
},
vertex_buffer_offsets = {
0 = 0,
},
index_buffer = ctx.draw_list_ibuf,
index_buffer_offset = 0,
}
gfx.apply_bindings( bindings )
// 2. Do the atlas rendering pass
// A simple 16-tap box downsample shader is then used to blit from this intermediate texture to the final atlas location
case .Atlas:
// profile("VEFontCache: draw call: atlas")
if num_indices == 0 && ! draw_call.clear_before_draw {
continue
}
width := ve_ctx.atlas.size.x
height := ve_ctx.atlas.size.y
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
}
gfx.begin_pass( pass )
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( ctx.atlas_pipeline )
fs_uniform := 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_blit_atlas_fs_params, Range { & fs_uniform, size_of(Blit_Atlas_Fs_Params) })
gfx.apply_bindings(Bindings {
vertex_buffers = {
0 = ctx.draw_list_vbuf,
},
vertex_buffer_offsets = {
0 = 0,
},
index_buffer = ctx.draw_list_ibuf,
index_buffer_offset = 0,
images = { IMG_blit_atlas_src_texture = ctx.glyph_rt_color, },
samplers = { SMP_blit_atlas_src_sampler = ctx.glyph_rt_sampler, },
})
// 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
}
// profile("VEFontCache: draw call: target")
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( ctx.screen_pipeline )
src_rt := ctx.atlas_rt_color
src_sampler := ctx.atlas_rt_sampler
fs_target_uniform := 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.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_bindings(Bindings {
vertex_buffers = {
0 = ctx.draw_list_vbuf,
},
vertex_buffer_offsets = {
0 = 0,
},
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, },
})
}
if num_indices != 0 {
gfx.draw( draw_call.start_index, num_indices, 1 )
}
gfx.end_pass()
}
}