96e8ea4e
Plumb text coverage controls through renderer
a73x 2026-04-09 08:05
Commit message
shaders/cell.frag
| Old | New | ||
|---|---|---|---|
| @@ -2,13 +2,25 @@ | |||
| 2 | 2 | ||
| 3 | layout(binding = 0) uniform sampler2D glyph_atlas; | 3 | layout(binding = 0) uniform sampler2D glyph_atlas; |
| 4 | 4 | ||
| 5 | layout(push_constant) uniform PushConstants { | ||
| 6 | vec2 viewport_size; | ||
| 7 | vec2 cell_size; | ||
| 8 | vec2 coverage_params; | ||
| 9 | } pc; | ||
| 10 | |||
| 5 | layout(location = 0) in vec2 in_uv; | 11 | layout(location = 0) in vec2 in_uv; |
| 6 | layout(location = 1) in vec4 in_fg; | 12 | layout(location = 1) in vec4 in_fg; |
| 7 | layout(location = 2) in vec4 in_bg; | 13 | layout(location = 2) in vec4 in_bg; |
| 8 | 14 | ||
| 9 | layout(location = 0) out vec4 out_color; | 15 | layout(location = 0) out vec4 out_color; |
| 10 | 16 | ||
| 17 | float shape_coverage(float alpha) { | ||
| 18 | float curved = pow(alpha, pc.coverage_params.x); | ||
| 19 | return clamp(curved + pc.coverage_params.y, 0.0, 1.0); | ||
| 20 | } | ||
| 21 | |||
| 11 | void main() { | 22 | void main() { |
| 12 | float alpha = texture(glyph_atlas, in_uv).r; | 23 | float alpha = texture(glyph_atlas, in_uv).r; |
| 24 | alpha = shape_coverage(alpha); | ||
| 13 | out_color = mix(in_bg, in_fg, alpha); | 25 | out_color = mix(in_bg, in_fg, alpha); |
| 14 | } | 26 | } |
src/renderer.zig
| Old | New | ||
|---|---|---|---|
| @@ -234,6 +234,7 @@ fn createFramebuffers( | |||
| 234 | pub const PushConstants = extern struct { | 234 | pub const PushConstants = extern struct { |
| 235 | viewport_size: [2]f32, | 235 | viewport_size: [2]f32, |
| 236 | cell_size: [2]f32, | 236 | cell_size: [2]f32, |
| 237 | coverage_params: [2]f32, | ||
| 237 | }; | 238 | }; |
| 238 | 239 | ||
| 239 | /// Per-vertex data (binding 0, per-vertex rate) | 240 | /// Per-vertex data (binding 0, per-vertex rate) |
| @@ -628,7 +629,7 @@ pub const Context = struct { | |||
| 628 | 629 | ||
| 629 | // Create pipeline layout (push constants + descriptor set) | 630 | // Create pipeline layout (push constants + descriptor set) |
| 630 | const push_range = vk.PushConstantRange{ | 631 | const push_range = vk.PushConstantRange{ |
| 631 | .stage_flags = .{ .vertex_bit = true }, | 632 | .stage_flags = .{ .vertex_bit = true, .fragment_bit = true }, |
| 632 | .offset = 0, | 633 | .offset = 0, |
| 633 | .size = @sizeOf(PushConstants), | 634 | .size = @sizeOf(PushConstants), |
| 634 | }; | 635 | }; |
| @@ -1337,6 +1338,7 @@ pub const Context = struct { | |||
| 1337 | instance_count: u32, | 1338 | instance_count: u32, |
| 1338 | cell_size: [2]f32, | 1339 | cell_size: [2]f32, |
| 1339 | clear_color: [4]f32, | 1340 | clear_color: [4]f32, |
| 1341 | coverage_params: [2]f32, | ||
| 1340 | ) !void { | 1342 | ) !void { |
| 1341 | // Wait for previous frame to finish | 1343 | // Wait for previous frame to finish |
| 1342 | _ = try self.vkd.waitForFences(self.device, 1, @ptrCast(&self.in_flight_fence), .true, std.math.maxInt(u64)); | 1344 | _ = try self.vkd.waitForFences(self.device, 1, @ptrCast(&self.in_flight_fence), .true, std.math.maxInt(u64)); |
| @@ -1401,11 +1403,12 @@ pub const Context = struct { | |||
| 1401 | @floatFromInt(self.swapchain_extent.height), | 1403 | @floatFromInt(self.swapchain_extent.height), |
| 1402 | }, | 1404 | }, |
| 1403 | .cell_size = cell_size, | 1405 | .cell_size = cell_size, |
| 1406 | .coverage_params = coverage_params, | ||
| 1404 | }; | 1407 | }; |
| 1405 | self.vkd.cmdPushConstants( | 1408 | self.vkd.cmdPushConstants( |
| 1406 | self.command_buffer, | 1409 | self.command_buffer, |
| 1407 | self.pipeline_layout, | 1410 | self.pipeline_layout, |
| 1408 | .{ .vertex_bit = true }, | 1411 | .{ .vertex_bit = true, .fragment_bit = true }, |
| 1409 | 0, | 1412 | 0, |
| 1410 | @sizeOf(PushConstants), | 1413 | @sizeOf(PushConstants), |
| 1411 | @ptrCast(&pc), | 1414 | @ptrCast(&pc), |
| @@ -1488,6 +1491,16 @@ test "coverageVariantParams returns baseline values" { | |||
| 1488 | try std.testing.expectEqualDeep([2]f32{ 1.0, 0.0 }, coverageVariantParams(.baseline)); | 1491 | try std.testing.expectEqualDeep([2]f32{ 1.0, 0.0 }, coverageVariantParams(.baseline)); |
| 1489 | } | 1492 | } |
| 1490 | 1493 | ||
| 1494 | test "PushConstants carries baseline coverage params" { | ||
| 1495 | const push_constants = PushConstants{ | ||
| 1496 | .viewport_size = .{ 1920.0, 1080.0 }, | ||
| 1497 | .cell_size = .{ 9.0, 18.0 }, | ||
| 1498 | .coverage_params = coverageVariantParams(.baseline), | ||
| 1499 | }; | ||
| 1500 | |||
| 1501 | try std.testing.expectEqualDeep([2]f32{ 1.0, 0.0 }, push_constants.coverage_params); | ||
| 1502 | } | ||
| 1503 | |||
| 1491 | test "coverageVariantParams steepens progressively" { | 1504 | test "coverageVariantParams steepens progressively" { |
| 1492 | const mild = coverageVariantParams(.mild); | 1505 | const mild = coverageVariantParams(.mild); |
| 1493 | const medium = coverageVariantParams(.medium); | 1506 | const medium = coverageVariantParams(.medium); |