shape2d
This commit is contained in:
204
sdl_gpu.cm
204
sdl_gpu.cm
@@ -36,6 +36,7 @@ var _crt_frag = null
|
||||
var _accumulator_frag = null
|
||||
var _text_sdf_frag = null
|
||||
var _text_msdf_frag = null
|
||||
var _shape2d_frag = null
|
||||
|
||||
// Pipelines
|
||||
var _pipelines = {}
|
||||
@@ -259,6 +260,18 @@ function _load_shaders() {
|
||||
})
|
||||
}
|
||||
|
||||
var shape2d_frag_code = io.slurp("shaders/msl/shape2d.frag.msl")
|
||||
if (shape2d_frag_code) {
|
||||
_shape2d_frag = new gpu_mod.shader(_gpu, {
|
||||
code: shape2d_frag_code,
|
||||
stage: "fragment",
|
||||
format: "msl",
|
||||
entrypoint: "fragment_main",
|
||||
num_uniform_buffers: 1,
|
||||
num_samplers: 0
|
||||
})
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -582,6 +595,7 @@ function _create_pipelines() {
|
||||
}]
|
||||
}
|
||||
})
|
||||
}
|
||||
// Accumulator pipeline
|
||||
if (_blit_vert && _accumulator_frag) {
|
||||
_pipelines.accumulator = new gpu_mod.graphics_pipeline(_gpu, {
|
||||
@@ -604,7 +618,43 @@ function _create_pipelines() {
|
||||
color_targets: [{format: _swapchain_format, blend: {enabled: false}}]
|
||||
}
|
||||
})
|
||||
} }
|
||||
}
|
||||
|
||||
// Shape2D pipeline
|
||||
if (_sprite_vert && _shape2d_frag) {
|
||||
_pipelines.shape2d = new gpu_mod.graphics_pipeline(_gpu, {
|
||||
vertex: _sprite_vert,
|
||||
fragment: _shape2d_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"
|
||||
}
|
||||
}]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
@@ -1155,6 +1205,10 @@ function _execute_commands(commands, window_size) {
|
||||
pending_draws.push(cmd)
|
||||
break
|
||||
|
||||
case 'draw_shape':
|
||||
pending_draws.push(cmd)
|
||||
break
|
||||
|
||||
case 'blit':
|
||||
// Flush pending draws first
|
||||
if (current_pass && pending_draws.length > 0) {
|
||||
@@ -1357,6 +1411,13 @@ function _flush_draws(cmd_buffer, pass, draws, camera, target) {
|
||||
|
||||
// Render pre-rendered effect texture
|
||||
_render_texture_ref(cmd_buffer, pass, draw.drawable, camera, target)
|
||||
} else if (draw.cmd == 'draw_shape') {
|
||||
// Flush current batch
|
||||
if (current_batch) _render_batch(cmd_buffer, pass, current_batch, camera, target)
|
||||
current_batch = null
|
||||
|
||||
// Render shape immediately
|
||||
_render_shape(cmd_buffer, pass, draw.drawable, camera, target)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1472,6 +1533,147 @@ function _render_texture_ref(cmd_buffer, pass, drawable, camera, target) {
|
||||
pass.draw_indexed(geom.index_count, 1, 0, 0, 0)
|
||||
}
|
||||
|
||||
function _render_shape(cmd_buffer, pass, drawable, camera, target) {
|
||||
if (!_pipelines.shape2d) return
|
||||
|
||||
var pos = drawable.pos || {x: 0, y: 0}
|
||||
var w = drawable.width || 100
|
||||
var h = drawable.height || 100
|
||||
var ax = drawable.anchor_x != null ? drawable.anchor_x : 0.5
|
||||
var ay = drawable.anchor_y != null ? drawable.anchor_y : 0.5
|
||||
|
||||
// Calculate padding required for stroke and feather
|
||||
var stroke_thickness = drawable.stroke_thickness || 0
|
||||
var feather = drawable.feather != null ? drawable.feather : 0.5
|
||||
|
||||
// Resolve stroke alignment (0=inside, 0.5=center, 1=outside)
|
||||
var stroke_aligns = {inside: 0, center: 0.5, outside: 1}
|
||||
var sa = drawable.stroke_align in stroke_aligns ? stroke_aligns[drawable.stroke_align] : 0.5
|
||||
|
||||
var pad = feather
|
||||
if (stroke_thickness > 0) {
|
||||
if (sa > 0.75) pad += stroke_thickness // Outside
|
||||
else if (sa > 0.25) pad += stroke_thickness * 0.5 // Center
|
||||
// Inside adds 0
|
||||
}
|
||||
|
||||
// Expand quad by padding
|
||||
var x = pos.x - w * ax - pad
|
||||
var y = pos.y - h * ay - pad
|
||||
var qw = w + pad * 2
|
||||
var qh = h + pad * 2
|
||||
|
||||
// Expand UVs to match padding (p calculation depends on this)
|
||||
// logical size is w, h. padded size is qw, qh.
|
||||
// 0..1 maps to w, h.
|
||||
// We need UVs such that (uv - 0.5) * w spans the padded area logic.
|
||||
// u=0 -> -0.5 * w = left edge of shape
|
||||
// target left edge is -pad relative to shape left edge.
|
||||
// So we need uv such that (uv - 0.5) * w = -w/2 - pad
|
||||
// uv * w - w/2 = -w/2 - pad
|
||||
// uv * w = -pad => uv = -pad / w
|
||||
var u0 = -pad / w
|
||||
var v0 = -pad / h
|
||||
var u1 = 1.0 + pad / w
|
||||
var v1 = 1.0 + pad / h
|
||||
|
||||
var fill = drawable.fill || {r: 1, g: 1, b: 1, a: 1}
|
||||
var opacity = drawable.opacity != null ? drawable.opacity : 1
|
||||
|
||||
// Vertex data: pos(2) + uv(2) + color(4) = 8 floats = 32 bytes per vertex
|
||||
var vertex_data = new blob_mod(4 * 32)
|
||||
var index_data = new blob_mod(6 * 2)
|
||||
|
||||
// v0: bottom-left
|
||||
vertex_data.wf(x); vertex_data.wf(y)
|
||||
vertex_data.wf(u0); vertex_data.wf(v1)
|
||||
vertex_data.wf(1); vertex_data.wf(1); vertex_data.wf(1); vertex_data.wf(1)
|
||||
|
||||
// v1: bottom-right
|
||||
vertex_data.wf(x + qw); vertex_data.wf(y)
|
||||
vertex_data.wf(u1); vertex_data.wf(v1)
|
||||
vertex_data.wf(1); vertex_data.wf(1); vertex_data.wf(1); vertex_data.wf(1)
|
||||
|
||||
// v2: top-right
|
||||
vertex_data.wf(x + qw); vertex_data.wf(y + qh)
|
||||
vertex_data.wf(u1); vertex_data.wf(v0)
|
||||
vertex_data.wf(1); vertex_data.wf(1); vertex_data.wf(1); vertex_data.wf(1)
|
||||
|
||||
// v3: top-left
|
||||
vertex_data.wf(x); vertex_data.wf(y + qh)
|
||||
vertex_data.wf(u0); vertex_data.wf(v0)
|
||||
vertex_data.wf(1); vertex_data.wf(1); vertex_data.wf(1); vertex_data.wf(1)
|
||||
|
||||
// Indices
|
||||
index_data.w16(0); index_data.w16(1); index_data.w16(2)
|
||||
index_data.w16(0); index_data.w16(2); index_data.w16(3)
|
||||
|
||||
// Upload geometry
|
||||
var vb_size = 4 * 32
|
||||
var ib_size = 6 * 2
|
||||
|
||||
var vb = new gpu_mod.buffer(_gpu, {size: vb_size, vertex: true})
|
||||
var ib = new gpu_mod.buffer(_gpu, {size: ib_size, index: true})
|
||||
|
||||
var vb_transfer = new gpu_mod.transfer_buffer(_gpu, {size: vb_size, usage: "upload"})
|
||||
var ib_transfer = new gpu_mod.transfer_buffer(_gpu, {size: ib_size, usage: "upload"})
|
||||
|
||||
vb_transfer.copy_blob(_gpu, stone(vertex_data))
|
||||
ib_transfer.copy_blob(_gpu, stone(index_data))
|
||||
|
||||
var copy_cmd = _gpu.acquire_cmd_buffer()
|
||||
var copy = copy_cmd.copy_pass()
|
||||
copy.upload_to_buffer({transfer_buffer: vb_transfer, offset: 0}, {buffer: vb, offset: 0, size: vb_size}, false)
|
||||
copy.upload_to_buffer({transfer_buffer: ib_transfer, offset: 0}, {buffer: ib, offset: 0, size: ib_size}, false)
|
||||
copy.end()
|
||||
copy_cmd.submit()
|
||||
|
||||
// Build camera matrix
|
||||
var proj = _build_camera_matrix(camera, target.width, target.height)
|
||||
|
||||
// Build shape uniforms - must match ShapeParams struct in shader
|
||||
// Total size: 112 bytes
|
||||
var stroke = drawable.stroke || {r: 0, g: 0, b: 0, a: 0}
|
||||
var shape_types = {rect: 0, circle: 1, ellipse: 2, pill: 3}
|
||||
var shape_type = shape_types[drawable.shape_type] || 0
|
||||
|
||||
var u_data = new blob_mod(112)
|
||||
u_data.wf(fill.r); u_data.wf(fill.g); u_data.wf(fill.b); u_data.wf(fill.a) // fill_color (16)
|
||||
u_data.wf(stroke.r); u_data.wf(stroke.g); u_data.wf(stroke.b); u_data.wf(stroke.a) // stroke_color (16)
|
||||
u_data.wf(w); u_data.wf(h) // size (8) - PASS LOGICAL SIZE
|
||||
u_data.wf(drawable.radius || 0) // radius (4)
|
||||
u_data.wf(feather) // feather (4)
|
||||
u_data.wf(stroke_thickness) // stroke_thickness (4)
|
||||
u_data.wf(sa) // stroke_align (4)
|
||||
u_data.w32(shape_type) // shape_type (4)
|
||||
u_data.w32(0) // corner_style (4)
|
||||
u_data.wf(drawable.dash_len || 0) // dash_len (4)
|
||||
u_data.wf(drawable.gap_len || 0) // gap_len (4)
|
||||
u_data.wf(drawable.dash_offset || 0) // dash_offset (4)
|
||||
u_data.w32(0) // cap_type (4)
|
||||
|
||||
// uv_transform (16)
|
||||
if (drawable.uv && drawable.uv.scale) {
|
||||
u_data.wf(drawable.uv.scale.x); u_data.wf(drawable.uv.scale.y)
|
||||
u_data.wf(drawable.uv.offset.x); u_data.wf(drawable.uv.offset.y)
|
||||
} else {
|
||||
u_data.wf(1); u_data.wf(1); u_data.wf(0); u_data.wf(0)
|
||||
}
|
||||
|
||||
u_data.wf(drawable.uv && drawable.uv.rotate ? drawable.uv.rotate : 0) // uv_rotate (4)
|
||||
u_data.w32(0) // has_texture (4)
|
||||
u_data.wf(opacity) // opacity (4)
|
||||
u_data.wf(0) // _pad (4)
|
||||
|
||||
pass.bind_pipeline(_pipelines.shape2d)
|
||||
pass.bind_vertex_buffers(0, [{buffer: vb, offset: 0}])
|
||||
pass.bind_index_buffer({buffer: ib, offset: 0}, 16)
|
||||
pass.bind_fragment_samplers(0, [{texture: _white_texture, sampler: _sampler_linear}])
|
||||
cmd_buffer.push_vertex_uniform_data(0, proj)
|
||||
cmd_buffer.push_fragment_uniform_data(0, stone(u_data))
|
||||
pass.draw_indexed(6, 1, 0, 0, 0)
|
||||
}
|
||||
|
||||
function _render_text(cmd_buffer, pass, drawable, camera, target) {
|
||||
// Get font - support mode tag: 'bitmap', 'sdf', 'msdf'
|
||||
var font_path = drawable.font
|
||||
|
||||
Reference in New Issue
Block a user