concinnity-device 0.18.66

GPU backends (Metal, Vulkan, DirectX) behind a device facade for Concinnity
Documentation
// World-space line pass: single source for every backend.
//
// The CPU expanded each line into a camera-facing ribbon whose corners already
// sit off the line centre (gfx::lines::build_vertices), so the vertex stage
// only applies the camera VP.
//
// Alpha does the two things a line needs: `edge` fades the outer pixel of the
// ribbon so the line antialiases without MSAA, and the vertex colour's own
// alpha carries the CPU-side distance fade. The pass binds no depth attachment
// -- it runs after the main pass resolved -- so the fragment tests the resolved
// scene depth itself, which is what lets an occluded line fade instead of
// vanishing.
//
// USE_MSAA is a HOST difference rather than a target one: Vulkan reads the
// multisampled main depth while Metal reads the resolved copy.

#ifndef USE_MSAA
#define USE_MSAA 0
#endif

// Per-frame view inputs, 80 B. Mirrors `LineView` in each backend's uniforms
// module.
struct LineView
{
    // The main pass's view-projection (jittered when TAA is on), so a line
    // lands on the same pixel the geometry it sits on did.
    float4x4 vp;
    // Alpha multiplier for the part of a line behind scene geometry. 0 hides
    // occluded lines outright; a small value leaves a hint of the line showing
    // through, which is what makes it useful for orienting in a dense scene.
    float occluded_alpha;
    float _pad0;
    float _pad1;
    float _pad2;
};

[[vk::binding(0, 0)]] ConstantBuffer<LineView> view : register(b0);

#if USE_MSAA
[[vk::binding(1, 0)]] Texture2DMS<float> scene_depth;
#else
[[vk::binding(1, 0)]] Texture2D<float> scene_depth;
#endif

// Matches `LineVertex` (32 B: pos at 0, edge at 12, colour at 16), which the
// host input layouts declare and `line_vertex_layout_matches_shaders` pins.
struct LineVertexIn
{
    [[vk::location(0)]] float3 pos : POSITION;
    [[vk::location(1)]] float edge : TEXCOORD0;
    [[vk::location(2)]] float4 color : COLOR;
};

// The varyings lead deliberately: D3D packs a stage signature in declaration
// order and links the two stages by matching semantic *and* register.
struct LineVertexOut
{
    [[vk::location(0)]] float edge : TEXCOORD0;
    [[vk::location(1)]] float4 color : TEXCOORD1;
    float4 position : SV_Position;
};

[shader("vertex")]
LineVertexOut line_vertex(LineVertexIn v)
{
    LineVertexOut o;
    o.position = mul(view.vp, float4(v.pos, 1.0));
    o.edge = v.edge;
    o.color = v.color;
    return o;
}

float line_scene_depth(int2 pixel)
{
#if USE_MSAA
    return scene_depth.Load(pixel, 0);
#else
    return scene_depth.Load(int3(pixel, 0));
#endif
}

[shader("fragment")]
float4 line_fragment(LineVertexOut i) : SV_Target
{
    // Ribbon edge fade: `edge` runs -1..1 across the width, so one pixel of its
    // own screen-space derivative is the softest edge that still reads as a
    // solid line.
    float aa = max(fwidth(i.edge), 1e-4);
    float coverage = 1.0 - smoothstep(1.0 - aa, 1.0, abs(i.edge));

    // Manual depth test against the scene depth. The bias keeps a line drawn
    // exactly on a surface (a ground-plane axis) from z-fighting itself into a
    // dashed mess.
    float scene_z = line_scene_depth(int2(i.position.xy));
    float occlusion = (i.position.z > scene_z + 1e-6) ? view.occluded_alpha : 1.0;

    float alpha = i.color.a * coverage * occlusion;
    if (alpha <= 0.002)
    {
        discard;
    }
    return float4(i.color.rgb, alpha);
}