Clay custom dispatch improvements & DPI scaling fixes (#26)
Co-authored-by: Zachary Levy <zachary@sunforge.is> Reviewed-on: #26
This commit was merged in pull request #26.
This commit is contained in:
+100
-81
@@ -9,11 +9,8 @@ import sdl_ttf "vendor:sdl3/ttf"
|
||||
|
||||
//----- Vertex layout ----------------------------------
|
||||
|
||||
// Vertex layout for tessellated and text geometry.
|
||||
// IMPORTANT: `color` must be premultiplied alpha (RGB channels pre-scaled by alpha).
|
||||
// The tessellated fragment shader passes vertex color through directly — it does NOT
|
||||
// premultiply. The blend state is ONE, ONE_MINUS_SRC_ALPHA (premultiplied-over).
|
||||
// Use `premultiply_color` when constructing vertices manually for `prepare_shape`.
|
||||
// Vertex layout for tessellated and text geometry. `color` must be premultiplied alpha; see
|
||||
// the package doc's "Color and blending" section for the contract.
|
||||
Vertex_2D :: struct {
|
||||
position: Vec2,
|
||||
uv: [2]f32,
|
||||
@@ -68,35 +65,35 @@ Shape_Flags :: bit_set[Shape_Flag;u8]
|
||||
|
||||
//INTERNAL
|
||||
RRect_Params :: struct {
|
||||
half_size: [2]f32,
|
||||
radii: [4]f32,
|
||||
half_feather: f32, // feather_px * 0.5; shader uses smoothstep(-h, h, d)
|
||||
_: f32,
|
||||
half_size_ppx: [2]f32,
|
||||
radii_ppx: [4]f32,
|
||||
half_feather_ppx: f32, // feather_ppx * 0.5; shader uses smoothstep(-h, h, d)
|
||||
_: f32,
|
||||
}
|
||||
|
||||
//INTERNAL
|
||||
NGon_Params :: struct {
|
||||
radius: f32,
|
||||
sides: f32,
|
||||
half_feather: f32, // feather_px * 0.5; shader uses smoothstep(-h, h, d)
|
||||
_: [5]f32,
|
||||
radius_ppx: f32,
|
||||
sides: f32,
|
||||
half_feather_ppx: f32, // feather_ppx * 0.5; shader uses smoothstep(-h, h, d)
|
||||
_: [5]f32,
|
||||
}
|
||||
|
||||
//INTERNAL
|
||||
Ellipse_Params :: struct {
|
||||
radii: [2]f32,
|
||||
half_feather: f32, // feather_px * 0.5; shader uses smoothstep(-h, h, d)
|
||||
_: [5]f32,
|
||||
radii_ppx: [2]f32,
|
||||
half_feather_ppx: f32, // feather_ppx * 0.5; shader uses smoothstep(-h, h, d)
|
||||
_: [5]f32,
|
||||
}
|
||||
|
||||
//INTERNAL
|
||||
Ring_Arc_Params :: struct {
|
||||
inner_radius: f32, // inner radius in physical pixels (0 for pie slice)
|
||||
outer_radius: f32, // outer radius in physical pixels
|
||||
normal_start: [2]f32, // pre-computed outward normal of start edge: (sin(start), -cos(start))
|
||||
normal_end: [2]f32, // pre-computed outward normal of end edge: (-sin(end), cos(end))
|
||||
half_feather: f32, // feather_px * 0.5; shader uses smoothstep(-h, h, d)
|
||||
_: f32,
|
||||
inner_radius_ppx: f32, // 0 for pie slice
|
||||
outer_radius_ppx: f32,
|
||||
normal_start: [2]f32, // pre-computed outward normal of start edge: (sin(start), -cos(start))
|
||||
normal_end: [2]f32, // pre-computed outward normal of end edge: (-sin(end), cos(end))
|
||||
half_feather_ppx: f32, // feather_ppx * 0.5; shader uses smoothstep(-h, h, d)
|
||||
_: f32,
|
||||
}
|
||||
|
||||
//INTERNAL
|
||||
@@ -176,9 +173,7 @@ Core_2D :: struct {
|
||||
sampler: ^sdl.GPUSampler,
|
||||
}
|
||||
|
||||
// MSAA is not supported by levlib (see init's doc comment in draw.odin); the PSO is hard-wired
|
||||
// to single-sample. SDF text and shapes provide analytical AA via smoothstep; tessellated user
|
||||
// geometry is not anti-aliased.
|
||||
// PSO is hard-wired to single-sample (no MSAA — see package doc's "Anti-aliasing" section).
|
||||
//INTERNAL
|
||||
create_core_2d :: proc(device: ^sdl.GPUDevice, window: ^sdl.Window) -> (core_2d: Core_2D, ok: bool) {
|
||||
// On failure, clean up any partially-created resources
|
||||
@@ -464,10 +459,31 @@ destroy_core_2d :: proc(device: ^sdl.GPUDevice, core: ^Core_2D) {
|
||||
|
||||
//----- Vertex uniforms ----------------------------------
|
||||
|
||||
//
|
||||
// Coordinate-space contract for the main pipeline's vertex shader:
|
||||
//
|
||||
// Tessellated (0) — `v_position` arrives in *logical* pixels. The vertex
|
||||
// shader multiplies by `dpi_scale` before applying the
|
||||
// ortho projection (which is sized to physical pixels).
|
||||
// SDF (1) — `v_position` is a unit-quad corner (0..1). World-space
|
||||
// coordinates come from `Core_2D_Primitive.bounds` in
|
||||
// logical pixels; the shader scales by `dpi_scale`.
|
||||
// Text (2) — `v_position` arrives in *physical* pixels already.
|
||||
// `prepare_text` and `prepare_text_transformed` bake the
|
||||
// anchor + glyph offsets (from SDL_ttf's GPU text engine,
|
||||
// which lays glyphs out in physical pixels) into the
|
||||
// vertex stream and snap the anchor to integer physical
|
||||
// pixels for atlas-aligned bilinear sampling. The shader
|
||||
// therefore must NOT rescale these vertices.
|
||||
//
|
||||
// The two raw-vertex modes (Tessellated, Text) share `prepare_shape`-style
|
||||
// glue but their coord spaces diverge — see `base_2d.vert` for the shader-
|
||||
// side branch.
|
||||
//INTERNAL
|
||||
Core_2D_Mode :: enum u32 {
|
||||
Tessellated = 0,
|
||||
SDF = 1,
|
||||
Text = 2,
|
||||
}
|
||||
|
||||
//INTERNAL
|
||||
@@ -814,9 +830,12 @@ render_layer_sub_batch_range :: proc(
|
||||
sdl.DrawGPUPrimitives(render_pass, batch.count, 1, batch.offset, 0)
|
||||
|
||||
case .Text:
|
||||
if current_mode != .Tessellated {
|
||||
push_globals(cmd_buffer, width, height, .Tessellated)
|
||||
current_mode = .Tessellated
|
||||
// Text vertices live in physical-pixel space (see Core_2D_Mode.Text
|
||||
// docs); mode 2 makes the shader skip the `* dpi_scale` step that
|
||||
// the Tessellated path applies to logical-pixel input.
|
||||
if current_mode != .Text {
|
||||
push_globals(cmd_buffer, width, height, .Text)
|
||||
current_mode = .Text
|
||||
}
|
||||
if current_vert_buf != main_vert_buf {
|
||||
sdl.BindGPUVertexBuffers(render_pass, 0, &sdl.GPUBufferBinding{buffer = main_vert_buf, offset = 0}, 1)
|
||||
@@ -922,8 +941,8 @@ prepare_text :: proc(layer: ^Layer, text: Text) {
|
||||
|
||||
// Snap base position to integer physical pixels to avoid atlas sub-pixel
|
||||
// sampling blur (and the off-by-one bottom-row clip that comes with it).
|
||||
base_x := math.round(text.position[0] * GLOB.dpi_scaling)
|
||||
base_y := math.round(text.position[1] * GLOB.dpi_scaling)
|
||||
base_x_ppx := math.round(text.position[0] * GLOB.dpi_scaling)
|
||||
base_y_ppx := math.round(text.position[1] * GLOB.dpi_scaling)
|
||||
|
||||
// Premultiply text color once — reused across all glyph vertices.
|
||||
pm_color := premultiply_color(text.color)
|
||||
@@ -938,7 +957,7 @@ prepare_text :: proc(layer: ^Layer, text: Text) {
|
||||
uv := data.uv[i]
|
||||
append(
|
||||
&GLOB.tmp_text_verts,
|
||||
Vertex_2D{position = {pos.x + base_x, -pos.y + base_y}, uv = {uv.x, uv.y}, color = pm_color},
|
||||
Vertex_2D{position = {pos.x + base_x_ppx, -pos.y + base_y_ppx}, uv = {uv.x, uv.y}, color = pm_color},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1079,7 +1098,7 @@ build_rrect_primitive :: proc(
|
||||
radii: Rectangle_Radii,
|
||||
origin: Vec2,
|
||||
rotation: f32,
|
||||
feather_px: f32,
|
||||
feather_ppx: f32,
|
||||
) -> Core_2D_Primitive {
|
||||
max_radius := min(rect.width, rect.height) * 0.5
|
||||
clamped_top_left := clamp(radii.top_left, 0, max_radius)
|
||||
@@ -1087,8 +1106,8 @@ build_rrect_primitive :: proc(
|
||||
clamped_bottom_right := clamp(radii.bottom_right, 0, max_radius)
|
||||
clamped_bottom_left := clamp(radii.bottom_left, 0, max_radius)
|
||||
|
||||
half_feather := feather_px * 0.5
|
||||
padding := half_feather / GLOB.dpi_scaling
|
||||
half_feather_ppx := feather_ppx * 0.5
|
||||
padding := half_feather_ppx / GLOB.dpi_scaling
|
||||
dpi_scale := GLOB.dpi_scaling
|
||||
|
||||
half_width := rect.width * 0.5
|
||||
@@ -1126,14 +1145,14 @@ build_rrect_primitive :: proc(
|
||||
rotation_sc = has_rotation ? pack_rotation_sc(sin_angle, cos_angle) : 0,
|
||||
}
|
||||
prim.params.rrect = RRect_Params {
|
||||
half_size = {half_width * dpi_scale, half_height * dpi_scale},
|
||||
radii = {
|
||||
half_size_ppx = {half_width * dpi_scale, half_height * dpi_scale},
|
||||
radii_ppx = {
|
||||
clamped_bottom_right * dpi_scale,
|
||||
clamped_top_right * dpi_scale,
|
||||
clamped_bottom_left * dpi_scale,
|
||||
clamped_top_left * dpi_scale,
|
||||
},
|
||||
half_feather = half_feather,
|
||||
half_feather_ppx = half_feather_ppx,
|
||||
}
|
||||
return prim
|
||||
}
|
||||
@@ -1146,10 +1165,10 @@ build_circle_primitive :: proc(
|
||||
radius: f32,
|
||||
origin: Vec2,
|
||||
rotation: f32,
|
||||
feather_px: f32,
|
||||
feather_ppx: f32,
|
||||
) -> Core_2D_Primitive {
|
||||
half_feather := feather_px * 0.5
|
||||
padding := half_feather / GLOB.dpi_scaling
|
||||
half_feather_ppx := feather_ppx * 0.5
|
||||
padding := half_feather_ppx / GLOB.dpi_scaling
|
||||
dpi_scale := GLOB.dpi_scaling
|
||||
|
||||
actual_center := center
|
||||
@@ -1166,11 +1185,11 @@ build_circle_primitive :: proc(
|
||||
actual_center.y + radius + padding,
|
||||
},
|
||||
}
|
||||
scaled_radius := radius * dpi_scale
|
||||
radius_ppx := radius * dpi_scale
|
||||
prim.params.rrect = RRect_Params {
|
||||
half_size = {scaled_radius, scaled_radius},
|
||||
radii = {scaled_radius, scaled_radius, scaled_radius, scaled_radius},
|
||||
half_feather = half_feather,
|
||||
half_size_ppx = {radius_ppx, radius_ppx},
|
||||
radii_ppx = {radius_ppx, radius_ppx, radius_ppx, radius_ppx},
|
||||
half_feather_ppx = half_feather_ppx,
|
||||
}
|
||||
return prim
|
||||
}
|
||||
@@ -1183,10 +1202,10 @@ build_ellipse_primitive :: proc(
|
||||
radius_horizontal, radius_vertical: f32,
|
||||
origin: Vec2,
|
||||
rotation: f32,
|
||||
feather_px: f32,
|
||||
feather_ppx: f32,
|
||||
) -> Core_2D_Primitive {
|
||||
half_feather := feather_px * 0.5
|
||||
padding := half_feather / GLOB.dpi_scaling
|
||||
half_feather_ppx := feather_ppx * 0.5
|
||||
padding := half_feather_ppx / GLOB.dpi_scaling
|
||||
dpi_scale := GLOB.dpi_scaling
|
||||
|
||||
actual_center := center
|
||||
@@ -1218,8 +1237,8 @@ build_ellipse_primitive :: proc(
|
||||
rotation_sc = has_rotation ? pack_rotation_sc(sin_angle, cos_angle) : 0,
|
||||
}
|
||||
prim.params.ellipse = Ellipse_Params {
|
||||
radii = {radius_horizontal * dpi_scale, radius_vertical * dpi_scale},
|
||||
half_feather = half_feather,
|
||||
radii_ppx = {radius_horizontal * dpi_scale, radius_vertical * dpi_scale},
|
||||
half_feather_ppx = half_feather_ppx,
|
||||
}
|
||||
return prim
|
||||
}
|
||||
@@ -1233,10 +1252,10 @@ build_polygon_primitive :: proc(
|
||||
radius: f32,
|
||||
origin: Vec2,
|
||||
rotation: f32,
|
||||
feather_px: f32,
|
||||
feather_ppx: f32,
|
||||
) -> Core_2D_Primitive {
|
||||
half_feather := feather_px * 0.5
|
||||
padding := half_feather / GLOB.dpi_scaling
|
||||
half_feather_ppx := feather_ppx * 0.5
|
||||
padding := half_feather_ppx / GLOB.dpi_scaling
|
||||
dpi_scale := GLOB.dpi_scaling
|
||||
|
||||
actual_center := center
|
||||
@@ -1258,9 +1277,9 @@ build_polygon_primitive :: proc(
|
||||
rotation_sc = rotation != 0 ? pack_rotation_sc(sin_rot, cos_rot) : 0,
|
||||
}
|
||||
prim.params.ngon = NGon_Params {
|
||||
radius = radius * math.cos(math.PI / f32(sides)) * dpi_scale,
|
||||
sides = f32(sides),
|
||||
half_feather = half_feather,
|
||||
radius_ppx = radius * math.cos(math.PI / f32(sides)) * dpi_scale,
|
||||
sides = f32(sides),
|
||||
half_feather_ppx = half_feather_ppx,
|
||||
}
|
||||
return prim
|
||||
}
|
||||
@@ -1278,13 +1297,13 @@ build_ring_arc_primitive :: proc(
|
||||
end_angle: f32,
|
||||
origin: Vec2,
|
||||
rotation: f32,
|
||||
feather_px: f32,
|
||||
feather_ppx: f32,
|
||||
) -> (
|
||||
Core_2D_Primitive,
|
||||
Shape_Flags,
|
||||
) {
|
||||
half_feather := feather_px * 0.5
|
||||
padding := half_feather / GLOB.dpi_scaling
|
||||
half_feather_ppx := feather_ppx * 0.5
|
||||
padding := half_feather_ppx / GLOB.dpi_scaling
|
||||
dpi_scale := GLOB.dpi_scaling
|
||||
|
||||
actual_center := center
|
||||
@@ -1327,11 +1346,11 @@ build_ring_arc_primitive :: proc(
|
||||
},
|
||||
}
|
||||
prim.params.ring_arc = Ring_Arc_Params {
|
||||
inner_radius = inner_radius * dpi_scale,
|
||||
outer_radius = outer_radius * dpi_scale,
|
||||
normal_start = normal_start,
|
||||
normal_end = normal_end,
|
||||
half_feather = half_feather,
|
||||
inner_radius_ppx = inner_radius * dpi_scale,
|
||||
outer_radius_ppx = outer_radius * dpi_scale,
|
||||
normal_start = normal_start,
|
||||
normal_end = normal_end,
|
||||
half_feather_ppx = half_feather_ppx,
|
||||
}
|
||||
return prim, arc_flags
|
||||
}
|
||||
@@ -1422,9 +1441,9 @@ rectangle :: proc(
|
||||
radii: Rectangle_Radii = {},
|
||||
origin: Vec2 = {},
|
||||
rotation: f32 = 0,
|
||||
feather_px: f32 = DFT_FEATHER_PX,
|
||||
feather_ppx: f32 = DFT_FEATHER_PPX,
|
||||
) {
|
||||
prim := build_rrect_primitive(rect, radii, origin, rotation, feather_px)
|
||||
prim := build_rrect_primitive(rect, radii, origin, rotation, feather_ppx)
|
||||
apply_brush_and_outline(layer, &prim, .RRect, brush, outline_color, outline_width)
|
||||
}
|
||||
|
||||
@@ -1445,9 +1464,9 @@ circle :: proc(
|
||||
outline_width: f32 = 0,
|
||||
origin: Vec2 = {},
|
||||
rotation: f32 = 0,
|
||||
feather_px: f32 = DFT_FEATHER_PX,
|
||||
feather_ppx: f32 = DFT_FEATHER_PPX,
|
||||
) {
|
||||
prim := build_circle_primitive(center, radius, origin, rotation, feather_px)
|
||||
prim := build_circle_primitive(center, radius, origin, rotation, feather_ppx)
|
||||
apply_brush_and_outline(layer, &prim, .RRect, brush, outline_color, outline_width)
|
||||
}
|
||||
|
||||
@@ -1462,9 +1481,9 @@ ellipse :: proc(
|
||||
outline_width: f32 = 0,
|
||||
origin: Vec2 = {},
|
||||
rotation: f32 = 0,
|
||||
feather_px: f32 = DFT_FEATHER_PX,
|
||||
feather_ppx: f32 = DFT_FEATHER_PPX,
|
||||
) {
|
||||
prim := build_ellipse_primitive(center, radius_horizontal, radius_vertical, origin, rotation, feather_px)
|
||||
prim := build_ellipse_primitive(center, radius_horizontal, radius_vertical, origin, rotation, feather_ppx)
|
||||
apply_brush_and_outline(layer, &prim, .Ellipse, brush, outline_color, outline_width)
|
||||
}
|
||||
|
||||
@@ -1481,11 +1500,11 @@ polygon :: proc(
|
||||
outline_width: f32 = 0,
|
||||
origin: Vec2 = {},
|
||||
rotation: f32 = 0,
|
||||
feather_px: f32 = DFT_FEATHER_PX,
|
||||
feather_ppx: f32 = DFT_FEATHER_PPX,
|
||||
) {
|
||||
if sides < 3 do return
|
||||
|
||||
prim := build_polygon_primitive(center, sides, radius, origin, rotation, feather_px)
|
||||
prim := build_polygon_primitive(center, sides, radius, origin, rotation, feather_ppx)
|
||||
apply_brush_and_outline(layer, &prim, .NGon, brush, outline_color, outline_width)
|
||||
}
|
||||
|
||||
@@ -1504,7 +1523,7 @@ ring :: proc(
|
||||
end_angle: f32 = DFT_CIRC_END_ANGLE,
|
||||
origin: Vec2 = {},
|
||||
rotation: f32 = 0,
|
||||
feather_px: f32 = DFT_FEATHER_PX,
|
||||
feather_ppx: f32 = DFT_FEATHER_PPX,
|
||||
) {
|
||||
prim, arc_flags := build_ring_arc_primitive(
|
||||
center,
|
||||
@@ -1514,7 +1533,7 @@ ring :: proc(
|
||||
end_angle,
|
||||
origin,
|
||||
rotation,
|
||||
feather_px,
|
||||
feather_ppx,
|
||||
)
|
||||
apply_brush_and_outline(layer, &prim, .Ring_Arc, brush, outline_color, outline_width, arc_flags)
|
||||
}
|
||||
@@ -1528,7 +1547,7 @@ line :: proc(
|
||||
thickness: f32 = DFT_STROKE_THICKNESS,
|
||||
outline_color: Color = {},
|
||||
outline_width: f32 = 0,
|
||||
feather_px: f32 = DFT_FEATHER_PX,
|
||||
feather_ppx: f32 = DFT_FEATHER_PPX,
|
||||
) {
|
||||
delta_x := end_position.x - start_position.x
|
||||
delta_y := end_position.y - start_position.y
|
||||
@@ -1544,8 +1563,8 @@ line :: proc(
|
||||
half_thickness := thickness * 0.5
|
||||
cap_radius := half_thickness
|
||||
|
||||
half_feather := feather_px * 0.5
|
||||
padding := half_feather / GLOB.dpi_scaling
|
||||
half_feather_ppx := feather_ppx * 0.5
|
||||
padding := half_feather_ppx / GLOB.dpi_scaling
|
||||
dpi_scale := GLOB.dpi_scaling
|
||||
|
||||
// Expand bounds for rotation
|
||||
@@ -1561,14 +1580,14 @@ line :: proc(
|
||||
rotation_sc = pack_rotation_sc(sin_angle, cos_angle),
|
||||
}
|
||||
prim.params.rrect = RRect_Params {
|
||||
half_size = {(half_length + cap_radius) * dpi_scale, half_thickness * dpi_scale},
|
||||
radii = {
|
||||
half_size_ppx = {(half_length + cap_radius) * dpi_scale, half_thickness * dpi_scale},
|
||||
radii_ppx = {
|
||||
cap_radius * dpi_scale,
|
||||
cap_radius * dpi_scale,
|
||||
cap_radius * dpi_scale,
|
||||
cap_radius * dpi_scale,
|
||||
},
|
||||
half_feather = half_feather,
|
||||
half_feather_ppx = half_feather_ppx,
|
||||
}
|
||||
apply_brush_and_outline(layer, &prim, .RRect, brush, outline_color, outline_width)
|
||||
}
|
||||
@@ -1581,10 +1600,10 @@ line_strip :: proc(
|
||||
thickness: f32 = DFT_STROKE_THICKNESS,
|
||||
outline_color: Color = {},
|
||||
outline_width: f32 = 0,
|
||||
feather_px: f32 = DFT_FEATHER_PX,
|
||||
feather_ppx: f32 = DFT_FEATHER_PPX,
|
||||
) {
|
||||
if len(points) < 2 do return
|
||||
for i in 0 ..< len(points) - 1 {
|
||||
line(layer, points[i], points[i + 1], brush, thickness, outline_color, outline_width, feather_px)
|
||||
line(layer, points[i], points[i + 1], brush, thickness, outline_color, outline_width, feather_ppx)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user