a73x

96e8ea4e

Plumb text coverage controls through renderer

a73x   2026-04-09 08:05


diff --git a/shaders/cell.frag b/shaders/cell.frag
index 7de861c..e793941 100644
--- a/shaders/cell.frag
+++ b/shaders/cell.frag
@@ -2,13 +2,25 @@

layout(binding = 0) uniform sampler2D glyph_atlas;

layout(push_constant) uniform PushConstants {
    vec2 viewport_size;
    vec2 cell_size;
    vec2 coverage_params;
} pc;

layout(location = 0) in vec2 in_uv;
layout(location = 1) in vec4 in_fg;
layout(location = 2) in vec4 in_bg;

layout(location = 0) out vec4 out_color;

float shape_coverage(float alpha) {
    float curved = pow(alpha, pc.coverage_params.x);
    return clamp(curved + pc.coverage_params.y, 0.0, 1.0);
}

void main() {
    float alpha = texture(glyph_atlas, in_uv).r;
    alpha = shape_coverage(alpha);
    out_color = mix(in_bg, in_fg, alpha);
}
diff --git a/src/renderer.zig b/src/renderer.zig
index 53e574c..1728139 100644
--- a/src/renderer.zig
+++ b/src/renderer.zig
@@ -234,6 +234,7 @@ fn createFramebuffers(
pub const PushConstants = extern struct {
    viewport_size: [2]f32,
    cell_size: [2]f32,
    coverage_params: [2]f32,
};

/// Per-vertex data (binding 0, per-vertex rate)
@@ -628,7 +629,7 @@ pub const Context = struct {

        // Create pipeline layout (push constants + descriptor set)
        const push_range = vk.PushConstantRange{
            .stage_flags = .{ .vertex_bit = true },
            .stage_flags = .{ .vertex_bit = true, .fragment_bit = true },
            .offset = 0,
            .size = @sizeOf(PushConstants),
        };
@@ -1337,6 +1338,7 @@ pub const Context = struct {
        instance_count: u32,
        cell_size: [2]f32,
        clear_color: [4]f32,
        coverage_params: [2]f32,
    ) !void {
        // Wait for previous frame to finish
        _ = 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 {
                @floatFromInt(self.swapchain_extent.height),
            },
            .cell_size = cell_size,
            .coverage_params = coverage_params,
        };
        self.vkd.cmdPushConstants(
            self.command_buffer,
            self.pipeline_layout,
            .{ .vertex_bit = true },
            .{ .vertex_bit = true, .fragment_bit = true },
            0,
            @sizeOf(PushConstants),
            @ptrCast(&pc),
@@ -1488,6 +1491,16 @@ test "coverageVariantParams returns baseline values" {
    try std.testing.expectEqualDeep([2]f32{ 1.0, 0.0 }, coverageVariantParams(.baseline));
}

test "PushConstants carries baseline coverage params" {
    const push_constants = PushConstants{
        .viewport_size = .{ 1920.0, 1080.0 },
        .cell_size = .{ 9.0, 18.0 },
        .coverage_params = coverageVariantParams(.baseline),
    };

    try std.testing.expectEqualDeep([2]f32{ 1.0, 0.0 }, push_constants.coverage_params);
}

test "coverageVariantParams steepens progressively" {
    const mild = coverageVariantParams(.mild);
    const medium = coverageVariantParams(.medium);