Bug fixes - text_fragment now v-flips its sample. TextRasterizer writes glyphs via a y-flipped CGContext (CoreText y-up → image-y-down), so memory row 0 holds the visual top of the text. quad_vertex already flips v for camera frames, so HUD text needed its own compensating flip in the fragment. - EdgePass was creating the threshold compute pipeline but never binding the threshold value to buffer(0). The kernel read garbage memory which usually evaluated as ~0, letting almost every fragment pass and producing the inverted look in the EDGES pane. Now binds Theme.Performance.edgeThreshold (default 0.18) explicitly. - Footer was anchored to y=2 (top of drawable). Moved to drawableSize.height minus footer height so it sits along the bottom edge where it belongs. Pane changes (per user request) - Pane 1 (JARVIS) now combines the stylization with the detection overlay: jarvis fragment → bounding-box strokes → per-box text labels. Labels are cached by string in labelTextureCache so a typical scene only rasterizes half a dozen unique strings ever. - Pane 3 was DETECT; now ASCII. Renders the live source through a new ascii_fragment that quantizes the image into a grid of monospaced glyphs picked from a pre-rasterized dark→bright atlas (` .:-=+*#%@`). New components - Sources/Shaders/Ascii.metal — ascii_fragment with AsciiUniforms (gridSize, atlasCharCount, tintColor). - Sources/Process/AsciiPass.swift — builds the glyph atlas at startup via CTLineDraw into a CGContext, uploads to a BGRA MTLTexture, and produces uniforms sized to keep glyph cells roughly square at any pane aspect. - Pipelines gains an ascii render state (quad_vertex + ascii_fragment). - Theme: paneLabels now ["LIVE", "JARVIS", "EDGES", "ASCII"], plus Performance.edgeThreshold and Performance.asciiColumns knobs. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
83 lines
2.9 KiB
Metal
83 lines
2.9 KiB
Metal
#include <metal_stdlib>
|
|
using namespace metal;
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// VisionPOC shared shader types.
|
|
//
|
|
// IMPORTANT: The field order of every uniform struct here is a hard contract
|
|
// with the Swift renderer. Do not reorder. See docs/.../visionpoc-design.md.
|
|
// -----------------------------------------------------------------------------
|
|
|
|
struct QuadVaryings {
|
|
float4 position [[position]];
|
|
float2 uv;
|
|
};
|
|
|
|
struct JarvisUniforms {
|
|
float time;
|
|
float2 resolution;
|
|
float scanlineDensity;
|
|
float hexGridScale;
|
|
float beamPhase;
|
|
float4 tintColor;
|
|
};
|
|
|
|
struct BoxUniforms {
|
|
float4 rect; // xy = origin in NDC ([-1,1]), zw = size in NDC
|
|
float4 color;
|
|
};
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Fullscreen quad vertex shader.
|
|
//
|
|
// Six-vertex triangle pair covering NDC [-1,1]^2. UVs are emitted with v
|
|
// flipped (origin top-left) so camera textures stored origin-top-left sample
|
|
// upright when displayed.
|
|
// -----------------------------------------------------------------------------
|
|
vertex QuadVaryings quad_vertex(uint vid [[vertex_id]]) {
|
|
float2 positions[6] = {
|
|
float2(-1.0, -1.0),
|
|
float2( 1.0, -1.0),
|
|
float2(-1.0, 1.0),
|
|
float2( 1.0, -1.0),
|
|
float2( 1.0, 1.0),
|
|
float2(-1.0, 1.0)
|
|
};
|
|
// v flipped: origin top-left for camera textures.
|
|
float2 uvs[6] = {
|
|
float2(0.0, 1.0),
|
|
float2(1.0, 1.0),
|
|
float2(0.0, 0.0),
|
|
float2(1.0, 1.0),
|
|
float2(1.0, 0.0),
|
|
float2(0.0, 0.0)
|
|
};
|
|
|
|
QuadVaryings out;
|
|
out.position = float4(positions[vid], 0.0, 1.0);
|
|
out.uv = uvs[vid];
|
|
return out;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// HUD text fragment.
|
|
//
|
|
// Samples a BGRA premultiplied glyph texture and multiplies by a tint passed
|
|
// in constant buffer 0. Linear filtering, clamp-to-edge.
|
|
// -----------------------------------------------------------------------------
|
|
fragment float4 text_fragment(QuadVaryings in [[stage_in]],
|
|
texture2d<float> glyphTex [[texture(0)]],
|
|
constant float4& tint [[buffer(0)]]) {
|
|
constexpr sampler s(coord::normalized, filter::linear,
|
|
address::clamp_to_edge);
|
|
// The TextRasterizer renders glyphs via a y-flipped CGContext so the
|
|
// bitmap's memory row 0 is the BOTTOM of the rendered text (CoreText y-up
|
|
// convention). quad_vertex emits uv with v=0 at the top of the screen, so
|
|
// we'd sample top-of-screen at memory-row-0 = bottom-of-text → upside
|
|
// down. Flip v on the sampling side to put the top of the text at the top
|
|
// of the quad.
|
|
float2 uv = float2(in.uv.x, 1.0 - in.uv.y);
|
|
float4 sampled = glyphTex.sample(s, uv);
|
|
return sampled * tint;
|
|
}
|