commit 7feda948f29af1c3618c82d5020e993fa4a1168e
parent 60e27f974db118a726215c51345505f6d8fc8874
Author: umhau <umhau@example.invalid>
Date: Wed, 21 Oct 2026 12:00:22 +0000
Add live optical tuning and filter the floor grid at pixel scale
Diffstat:
12 files changed, 334 insertions(+), 38 deletions(-)
diff --git a/NOTES.md b/NOTES.md
@@ -124,3 +124,25 @@ The calibrated live build also passed a forced panel-off recovery: display ADD a
0.654 s post-reset retrain healed automatically; native 90 Hz rendering resumed with no GPU errors.
See `evidence/calibrated-recovery-trial.json` and `evidence/calibrated-recovery-live.txt`.
The running demo logs to `/tmp/vr-calibrated-live.txt`; Q or Escape in its window quits it.
+
+## Live optical tuning and floor-grid filtering (2026-10-21)
+
+The Mac has a Headset optics window with
+scene/white-grid/green-grid patterns, red strength, and separate horizontal/vertical blue strengths (-100% to 400%).
+100% is the original factory compensation; zero uses green geometry for that channel. The controls pass settings to
+the render worker under a lock, and debounce-save them to Application Support/VRWorkstation/optics-adjustments.json.
+Factory JSON remains unchanged. Green-only mode disables the color sliders. Reset restores factory color strengths.
+
+Separately, the floor's 1.5 cm geometric grid strips projected below one panel pixel at distance. They were replaced
+with one floor surface and a derivative-filtered grid in its fragment shader: minimum readable pixel footprint,
+smoothed coverage, and per-axis fading when line spacing becomes unresolved. This reduces different aliasing between
+the two eyes, but does not change the stereo view matrices. An isolated infinity-alignment target was discussed but
+has NOT been added; the white/green patterns are angular grids.
+
+Operator feedback confirmed an improvement with optical tuning. Personal adjustment values and verbatim feedback are omitted.
+Current evidence/optics-controls-live.txt shows approximately 90 presented fps and zero GPU errors.
+
+The optical tuning shader passes float-coordinate comparisons including independent blue axes; the test also retains
+the end-to-end quantized sampling check. Screenshots show pre-compensated output before the physical lenses, so they
+cannot establish the blur/fringe that reaches a user's eye. Software rendering, optical compensation, and physical
+lens/eye placement are separate contributors; do not infer a hardware defect from the remaining residuals.
diff --git a/SETUP.md b/SETUP.md
@@ -148,3 +148,11 @@ zsh tools/build.sh # builds the app + the signe
- `verify.sh`: passive one-shot check (boot-args + summaries of existing logs). No ioreg/system_profiler/HID. Launches nothing.
- `build.sh`: builds the app and the signed `hpmctl` helper beside it (SETUP step 5).
- `hpmctl` / `recovery_trials.py` / the app's LinkRecovery helper: see NOTES.md "Link reliability" (vr0 owns these).
+
+### Optical tuning window
+
+The **Headset optics** window on the Mac switches between the 3D scene, a stationary white grid, and a green-only
+sharpness grid. Red correction and separate blue horizontal/vertical correction range from -100% to 400%; 100% is
+factory compensation. Changes save automatically to `~/Library/Application Support/VRWorkstation/optics-adjustments.json`.
+The factory reset button restores 100% for all three controls. Color controls are disabled for the green-only pattern.
+The floor now uses a filtered procedural grid to avoid subpixel line aliasing at distance.
diff --git a/Sources/VRWorkstation/App.swift b/Sources/VRWorkstation/App.swift
@@ -56,6 +56,7 @@ final class HeadsetSession: NSObject, NSApplicationDelegate {
let view: MetalSurface
private let renderLoop: MetalRenderLoop
private var window: HeadsetWindow?
+ private var opticsControls: OpticsControls?
private(set) var display: CGDirectDisplayID?
private var lostAt: Date?
private var hidRetry: Timer?
@@ -115,6 +116,8 @@ final class HeadsetSession: NSObject, NSApplicationDelegate {
}
}
+ func showOpticsControls() { opticsControls = OpticsControls(renderer: renderer) }
+
private func startHID() {
guard !stopping else { return }
do {
@@ -151,6 +154,7 @@ final class HeadsetSession: NSObject, NSApplicationDelegate {
}
hmd.stop()
window?.orderOut(nil)
+ opticsControls?.close(); opticsControls = nil
if let display { CGDisplayRelease(display) }
display = nil
}
@@ -282,6 +286,7 @@ func runApp() {
}
session.stopAfter(seconds)
}
+ session.showOpticsControls()
app.activate(ignoringOtherApps: true)
signal(SIGINT, SIG_IGN); signal(SIGTERM, SIG_IGN)
let signals = [SIGINT, SIGTERM].map { value -> DispatchSourceSignal in
diff --git a/Sources/VRWorkstation/LensWarp.swift b/Sources/VRWorkstation/LensWarp.swift
@@ -42,6 +42,7 @@ private struct WarpUniforms {
var k3: SIMD4<Float>
var ranges: SIMD4<Float>
var panel: SIMD4<Float>
+ var tuning: SIMD4<Float>
}
/// Render the scene to an oversampled target, then correct geometry and each color separately.
@@ -87,19 +88,18 @@ final class LensWarp {
uniforms.append(WarpUniforms(inverseAffine: inverse, red: channel(0), green: channel(1), blue: channel(2),
k3: SIMD4(Float(eye.channels[0].k.z), Float(eye.channels[1].k.z), Float(eye.channels[2].k.z), 0),
ranges: SIMD4(Float(b.texXRange.x), Float(b.texXRange.y), Float(b.texYRange.x), Float(b.texYRange.y)),
- panel: SIMD4(4320, 2160, Float(index), Float(width / 2))))
+ panel: SIMD4(4320, 2160, Float(index), Float(width / 2)), tuning: SIMD4(1, 1, 1, 0)))
}
}
#if LENS_WARP_CHECKS
/// Test-only float output from the exact production shader function, before sampling/quantization.
- func coordinateSamples(device: MTLDevice, points: [SIMD2<Float>], eye: Int) throws -> [SIMD2<Float>] {
+ func coordinateSamples(device: MTLDevice, points: [SIMD2<Float>], eye: Int, redGain: Float = 1, blueHorizontalGain: Float = 1, blueVerticalGain: Float = 1) throws -> [SIMD2<Float>] {
let kernel = """
kernel void check_coordinates(constant WarpUniforms &u [[buffer(0)]],
device const float2 *points [[buffer(1)]], device float2 *out [[buffer(2)]], uint i [[thread_position_in_grid]]) {
- out[3*i] = channel_uv(points[i], u.red, u.k3.x, u);
- out[3*i+1] = channel_uv(points[i], u.green, u.k3.y, u);
- out[3*i+2] = channel_uv(points[i], u.blue, u.k3.z, u);
+ ColorCoordinates tc = corrected_coordinates(points[i], u);
+ out[3*i] = tc.r; out[3*i+1] = tc.g; out[3*i+2] = tc.b;
}
"""
let lib = try device.makeLibrary(source: lensWarpShader + kernel, options: nil)
@@ -109,6 +109,7 @@ final class LensWarp {
let command = device.makeCommandQueue()!.makeCommandBuffer()!
let enc = command.makeComputeCommandEncoder()!
var u = uniforms[eye]
+ u.tuning = SIMD4(redGain, blueHorizontalGain, blueVerticalGain, 0)
enc.setComputePipelineState(pipeline)
enc.setBytes(&u, length: MemoryLayout<WarpUniforms>.stride, index: 0)
enc.setBuffer(input, offset: 0, index: 1)
@@ -120,12 +121,14 @@ final class LensWarp {
}
#endif
- func encodeGrid(_ cb: MTLCommandBuffer, pass: MTLRenderPassDescriptor) {
+ func encodeGrid(_ cb: MTLCommandBuffer, pass: MTLRenderPassDescriptor, greenOnly: Bool = false) {
guard let enc = cb.makeRenderCommandEncoder(descriptor: pass) else { return }
enc.setRenderPipelineState(gridPipeline)
for eye in 0..<2 {
enc.setViewport(MTLViewport(originX: Double(color.width / 2 * eye), originY: 0,
width: Double(color.width / 2), height: Double(color.height), znear: 0, zfar: 1))
+ var green: UInt32 = greenOnly ? 1 : 0
+ enc.setFragmentBytes(&green, length: MemoryLayout<UInt32>.stride, index: 1)
var ranges = uniforms[eye].ranges
enc.setFragmentBytes(&ranges, length: MemoryLayout<SIMD4<Float>>.stride, index: 0)
enc.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3)
@@ -133,7 +136,7 @@ final class LensWarp {
enc.endEncoding()
}
- func encode(_ cb: MTLCommandBuffer, target: MTLTexture) {
+ func encode(_ cb: MTLCommandBuffer, target: MTLTexture, redGain: Float = 1, blueHorizontalGain: Float = 1, blueVerticalGain: Float = 1) {
let pass = MTLRenderPassDescriptor()
pass.colorAttachments[0].texture = target
pass.colorAttachments[0].loadAction = .clear
@@ -146,6 +149,7 @@ final class LensWarp {
enc.setViewport(MTLViewport(originX: Double(target.width / 2 * eye), originY: 0,
width: Double(target.width / 2), height: Double(target.height), znear: 0, zfar: 1))
var u = uniforms[eye]
+ u.tuning = SIMD4(redGain, blueHorizontalGain, blueVerticalGain, 0)
enc.setFragmentBytes(&u, length: MemoryLayout<WarpUniforms>.stride, index: 0)
enc.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3)
}
@@ -158,7 +162,7 @@ private let lensWarpShader = """
using namespace metal;
struct WarpUniforms {
float3x3 inverseAffine;
- float4 red, green, blue, k3, ranges, panel;
+ float4 red, green, blue, k3, ranges, panel, tuning;
};
struct WarpVertex { float4 position [[position]]; float2 uv; };
vertex WarpVertex warp_vertex(uint id [[vertex_id]]) {
@@ -181,13 +185,20 @@ float color_sample(texture2d<float> source, float2 tc, uint channel, constant Wa
constexpr sampler linearSampler(coord::normalized, address::clamp_to_edge, filter::linear);
return source.sample(linearSampler, tc)[channel];
}
+struct ColorCoordinates { float2 r, g, b; };
+ColorCoordinates corrected_coordinates(float2 uv, constant WarpUniforms &u) {
+ ColorCoordinates tc;
+ tc.g = channel_uv(uv, u.green, u.k3.y, u);
+ tc.r = mix(tc.g, channel_uv(uv, u.red, u.k3.x, u), u.tuning.x);
+ tc.b = mix(tc.g, channel_uv(uv, u.blue, u.k3.z, u), u.tuning.yz);
+ return tc;
+}
fragment float4 warp_rgb(WarpVertex in [[stage_in]], texture2d<float> source [[texture(0)]], constant WarpUniforms &u [[buffer(0)]]) {
- float2 r = channel_uv(in.uv, u.red, u.k3.x, u);
- float2 g = channel_uv(in.uv, u.green, u.k3.y, u);
- float2 b = channel_uv(in.uv, u.blue, u.k3.z, u);
+ ColorCoordinates tc = corrected_coordinates(in.uv, u);
+ float2 r = tc.r, g = tc.g, b = tc.b;
return float4(color_sample(source, r, 0, u), color_sample(source, g, 1, u), color_sample(source, b, 2, u), 1);
}
-fragment float4 optics_grid(WarpVertex in [[stage_in]], constant float4 &ranges [[buffer(0)]]) {
+fragment float4 optics_grid(WarpVertex in [[stage_in]], constant float4 &ranges [[buffer(0)]], constant uint &greenOnly [[buffer(1)]]) {
float2 tangent = mix(ranges.xz, ranges.yw, in.uv);
float2 cell = abs(fract(tangent * 10 + .5) - .5);
float2 aa = fwidth(tangent * 10);
@@ -195,6 +206,7 @@ fragment float4 optics_grid(WarpVertex in [[stage_in]], constant float4 &ranges
float cross = min(abs(tangent.x), abs(tangent.y));
float3 color = mix(float3(.01), float3(.8), line);
if (cross < .001) color = float3(0, .8, .2);
+ if (greenOnly) color *= float3(0, 1, 0);
return float4(color, 1);
}
"""
diff --git a/Sources/VRWorkstation/OpticsControls.swift b/Sources/VRWorkstation/OpticsControls.swift
@@ -0,0 +1,111 @@
+import AppKit
+
+struct OpticalSettings: Equatable, Codable {
+ enum Pattern: Int, Codable { case scene, whiteGrid, greenGrid }
+ var pattern: Pattern = .scene
+ var redGain: Float = 1
+ var blueHorizontalGain: Float = 1
+ var blueVerticalGain: Float = 1
+
+ static var cacheURL: URL { HeadsetOptics.cacheURL.deletingLastPathComponent().appendingPathComponent("optics-adjustments.json") }
+ static func initial() -> OpticalSettings {
+ var settings = (try? JSONDecoder().decode(OpticalSettings.self, from: Data(contentsOf: cacheURL))) ?? OpticalSettings()
+ settings.redGain = settings.redGain.isFinite ? min(4, max(-1, settings.redGain)) : 1
+ settings.blueHorizontalGain = settings.blueHorizontalGain.isFinite ? min(4, max(-1, settings.blueHorizontalGain)) : 1
+ settings.blueVerticalGain = settings.blueVerticalGain.isFinite ? min(4, max(-1, settings.blueVerticalGain)) : 1
+ if CommandLine.arguments.contains("--green-grid") { settings.pattern = .greenGrid }
+ else if CommandLine.arguments.contains("--optics-grid") { settings.pattern = .whiteGrid }
+ return settings
+ }
+
+ func save() {
+ do { try JSONEncoder().encode(self).write(to: Self.cacheURL, options: .atomic) }
+ catch { log("cannot save optical adjustments: \(error)") }
+ }
+}
+
+/// Main-screen controls; the render worker receives settings through Renderer's lock.
+final class OpticsControls: NSObject {
+ private let renderer: Renderer
+ private let window: NSWindow
+ private let pattern = NSPopUpButton()
+ private let red = NSSlider(value: 1, minValue: -1, maxValue: 4, target: nil, action: nil)
+ private let blueHorizontal = NSSlider(value: 1, minValue: -1, maxValue: 4, target: nil, action: nil)
+ private let blueVertical = NSSlider(value: 1, minValue: -1, maxValue: 4, target: nil, action: nil)
+ private let redLabel = NSTextField(labelWithString: "")
+ private var saveTimer: Timer?
+ private let blueHorizontalLabel = NSTextField(labelWithString: "")
+
+ private let blueVerticalLabel = NSTextField(labelWithString: "")
+
+ init(renderer: Renderer) {
+ self.renderer = renderer
+ window = NSWindow(contentRect: NSRect(x: 60, y: 100, width: 460, height: 340),
+ styleMask: [.titled, .miniaturizable], backing: .buffered, defer: false)
+ super.init()
+ window.title = "Headset optics"
+ window.isReleasedWhenClosed = false
+ let stack = NSStackView()
+ stack.orientation = .vertical; stack.alignment = .leading; stack.spacing = 12
+ stack.edgeInsets = NSEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
+ stack.translatesAutoresizingMaskIntoConstraints = false
+ window.contentView!.addSubview(stack)
+ NSLayoutConstraint.activate([
+ stack.leadingAnchor.constraint(equalTo: window.contentView!.leadingAnchor),
+ stack.trailingAnchor.constraint(equalTo: window.contentView!.trailingAnchor),
+ stack.topAnchor.constraint(equalTo: window.contentView!.topAnchor)
+ ])
+ pattern.addItems(withTitles: ["3D scene", "White grid — sharpness and color", "Green grid — sharpness only"])
+ pattern.target = self; pattern.action = #selector(changed)
+ stack.addArrangedSubview(pattern)
+ let help = NSTextField(wrappingLabelWithString: "Compare both line directions on the stationary grids. Try one eye at a time; the green cross marks straight ahead.")
+ stack.addArrangedSubview(help)
+ for (slider, label) in [(red, redLabel), (blueHorizontal, blueHorizontalLabel), (blueVertical, blueVerticalLabel)] {
+ slider.target = self; slider.action = #selector(changed)
+ slider.isContinuous = true
+ slider.widthAnchor.constraint(equalToConstant: 410).isActive = true
+ stack.addArrangedSubview(label); stack.addArrangedSubview(slider)
+ }
+ stack.addArrangedSubview(NSTextField(labelWithString: "Color correction: 0% = off, 100% = factory"))
+ let reset = NSButton(title: "Reset colors to factory", target: self, action: #selector(resetColors))
+ stack.addArrangedSubview(reset)
+ let settings = renderer.opticalSettings()
+ pattern.selectItem(at: settings.pattern.rawValue)
+ red.floatValue = settings.redGain
+ blueHorizontal.floatValue = settings.blueHorizontalGain
+ blueVertical.floatValue = settings.blueVerticalGain
+ refreshLabels()
+ window.setContentSize(NSSize(width: 460, height: max(340, stack.fittingSize.height)))
+ if let screen = NSScreen.screens.first(where: { ($0.deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as? NSNumber)?.uint32Value == CGMainDisplayID() }) {
+ window.setFrameOrigin(NSPoint(x: screen.visibleFrame.minX + 60, y: screen.visibleFrame.maxY - window.frame.height - 60))
+ }
+ window.makeKeyAndOrderFront(nil)
+ }
+
+ @objc private func changed() {
+ var settings = renderer.opticalSettings()
+ settings.pattern = OpticalSettings.Pattern(rawValue: pattern.indexOfSelectedItem) ?? .scene
+ settings.redGain = (red.floatValue * 100).rounded() / 100
+ settings.blueHorizontalGain = (blueHorizontal.floatValue * 100).rounded() / 100
+ settings.blueVerticalGain = (blueVertical.floatValue * 100).rounded() / 100
+ renderer.setOpticalSettings(settings)
+ saveTimer?.invalidate()
+ saveTimer = Timer.scheduledTimer(withTimeInterval: 0.3, repeats: false) { _ in settings.save() }
+ refreshLabels()
+ }
+
+ private func refreshLabels() {
+ let colorsVisible = pattern.indexOfSelectedItem != OpticalSettings.Pattern.greenGrid.rawValue
+ red.isEnabled = colorsVisible; blueHorizontal.isEnabled = colorsVisible; blueVertical.isEnabled = colorsVisible
+ redLabel.stringValue = String(format: "Red correction: %.0f%%", red.floatValue * 100)
+ blueHorizontalLabel.stringValue = String(format: "Blue horizontal correction (sides): %.0f%%", blueHorizontal.floatValue * 100)
+ blueVerticalLabel.stringValue = String(format: "Blue vertical correction (top/bottom): %.0f%%", blueVertical.floatValue * 100)
+ }
+
+ @objc private func resetColors() { red.floatValue = 1; blueHorizontal.floatValue = 1; blueVertical.floatValue = 1; changed() }
+ func close() {
+ saveTimer?.invalidate(); saveTimer = nil
+ renderer.opticalSettings().save()
+ window.orderOut(nil)
+ }
+}
diff --git a/Sources/VRWorkstation/Renderer.swift b/Sources/VRWorkstation/Renderer.swift
@@ -5,7 +5,7 @@ import QuartzCore
import simd
struct Vertex { var pos: SIMD3<Float>; var normal: SIMD3<Float>; var color: SIMD3<Float> }
-struct Uniforms { var mvp: simd_float4x4; var model: simd_float4x4 }
+struct Uniforms { var mvp: simd_float4x4; var model: simd_float4x4; var material: SIMD4<Float> }
struct FrameMetrics {
var presented = 0
@@ -27,7 +27,21 @@ final class Renderer: NSObject, CAMetalDisplayLinkDelegate {
let start = CACurrentMediaTime()
let optics: HeadsetOptics
private let lensWarp: LensWarp
- private let opticsGrid = CommandLine.arguments.contains("--optics-grid")
+ private let settingsLock = NSLock()
+ private var settings = OpticalSettings.initial()
+
+ func opticalSettings() -> OpticalSettings {
+ settingsLock.lock(); defer { settingsLock.unlock() }
+ return settings
+ }
+
+ func setOpticalSettings(_ value: OpticalSettings) {
+ settingsLock.lock()
+ guard settings != value else { settingsLock.unlock(); return }
+ settings = value; settingsLock.unlock()
+
+ // Keep personal optical settings out of diagnostic logs.
+ }
/// Head orientation (world-from-head). Identity when head tracking is off.
var headOrientation: () -> simd_quatf = { simd_quatf(angle: 0, axis: [0, 1, 0]) }
@@ -86,10 +100,11 @@ final class Renderer: NSObject, CAMetalDisplayLinkDelegate {
metricsLock.unlock()
if frame == 1 { log("render worker main=\(Thread.isMainThread)") }
if frame == 1 || frame % 300 == 0 { print("frame \(frame) drawable \(width)x\(height)") }
- if opticsGrid { lensWarp.encodeGrid(cb, pass: rpd) } else {
+ let settings = opticalSettings()
+ if settings.pattern != .scene { lensWarp.encodeGrid(cb, pass: rpd, greenOnly: settings.pattern == .greenGrid) } else {
encode(cb, rpd, size: CGSize(width: lensWarp.color.width, height: lensWarp.color.height), time: Float(presentationTime - start))
}
- lensWarp.encode(cb, target: drawable.texture)
+ lensWarp.encode(cb, target: drawable.texture, redGain: settings.redGain, blueHorizontalGain: settings.blueHorizontalGain, blueVerticalGain: settings.blueVerticalGain)
drawable.addPresentedHandler { [weak self] presented in
guard let self else { return }
self.metricsLock.lock(); defer { self.metricsLock.unlock() }
@@ -136,7 +151,7 @@ final class Renderer: NSObject, CAMetalDisplayLinkDelegate {
for item in items {
// Spinning items rotate about their own origin (the model translation stays put).
let model = item.spins ? item.model * spin : item.model
- var u = Uniforms(mvp: proj * view * model, model: model)
+ var u = Uniforms(mvp: proj * view * model, model: model, material: SIMD4(item.floorGrid ? 1 : 0, 0, 0, 0))
enc.setVertexBytes(&u, length: MemoryLayout<Uniforms>.stride, index: 1)
enc.drawPrimitives(type: .triangle, vertexStart: item.first, vertexCount: item.count)
}
@@ -160,10 +175,11 @@ final class Renderer: NSObject, CAMetalDisplayLinkDelegate {
rpd.depthAttachment.loadAction = .clear
rpd.depthAttachment.clearDepth = 1
let cb = queue.makeCommandBuffer()!
- if opticsGrid { lensWarp.encodeGrid(cb, pass: rpd) } else {
+ let settings = opticalSettings()
+ if settings.pattern != .scene { lensWarp.encodeGrid(cb, pass: rpd, greenOnly: settings.pattern == .greenGrid) } else {
encode(cb, rpd, size: CGSize(width: lensWarp.color.width, height: lensWarp.color.height), time: time)
}
- lensWarp.encode(cb, target: color)
+ lensWarp.encode(cb, target: color, redGain: settings.redGain, blueHorizontalGain: settings.blueHorizontalGain, blueVerticalGain: settings.blueVerticalGain)
cb.commit(); cb.waitUntilCompleted()
guard cb.status == .completed else {
throw cb.error ?? NSError(domain: "Renderer", code: 2, userInfo: [NSLocalizedDescriptionKey: "Snapshot GPU command failed"])
diff --git a/Sources/VRWorkstation/Scene.swift b/Sources/VRWorkstation/Scene.swift
@@ -2,7 +2,7 @@
import simd
/// A run of vertices in the shared scene buffer, drawn with one model matrix.
-struct SceneItem { var first: Int; var count: Int; var model: simd_float4x4; var spins = false }
+struct SceneItem { var first: Int; var count: Int; var model: simd_float4x4; var spins = false; var floorGrid = false }
/// Eye height above the floor in metres (the head sits at the origin, -Y is down).
let floorY: Float = -1.4
@@ -31,25 +31,16 @@ enum Scene {
/// Builds the whole scene: one vertex array plus the items that index into it.
static func make() -> (vertices: [Vertex], items: [SceneItem]) {
var verts: [Vertex] = [], items: [SceneItem] = []
- func add(_ v: [Vertex], _ model: simd_float4x4 = matrix_identity_float4x4, spins: Bool = false) {
- items.append(SceneItem(first: verts.count, count: v.count, model: model, spins: spins))
+ func add(_ v: [Vertex], _ model: simd_float4x4 = matrix_identity_float4x4, spins: Bool = false, floorGrid: Bool = false) {
+ items.append(SceneItem(first: verts.count, count: v.count, model: model, spins: spins, floorGrid: floorGrid))
verts += v
}
// Hero cube: unit cube scaled to 0.4 m, 1.5 m ahead at eye level, spun by the renderer.
add(box(center: .zero, size: [1, 1, 1]), translation([0, 0, -1.5]) * scale(0.4), spins: true)
- // Floor slab plus 1 m grid lines (thin flat boxes, slightly above the slab to avoid z-fighting).
- let floor: SIMD3<Float> = [0.10, 0.11, 0.15], line: SIMD3<Float> = [0.35, 0.40, 0.55]
- var ground = box(center: [0, floorY - 0.01, -20], size: [60, 0.02, 60], color: floor)
- for i in -15...15 {
- let x = Float(i)
- ground += box(center: [x, floorY + 0.003, -20], size: [i == 0 ? 0.04 : 0.015, 0.006, 60], color: line)
- }
- for k in -9...50 {
- let z = -Float(k)
- ground += box(center: [0, floorY + 0.003, z], size: [30, 0.006, k == 0 ? 0.04 : 0.015], color: line)
- }
- add(ground)
+ // One floor surface; the fragment shader draws a pixel-filtered metric grid.
+ // Thin geometry otherwise compresses the transverse lines below one panel pixel at distance.
+ add(box(center: [0, floorY - 0.01, -20], size: [60, 0.02, 60], color: [0.10, 0.11, 0.15]), floorGrid: true)
// Reference pillars at increasing depth, alternating sides, in distinct colours.
let pillars: [(x: Float, z: Float, c: SIMD3<Float>)] = [
diff --git a/Sources/VRWorkstation/Shaders.swift b/Sources/VRWorkstation/Shaders.swift
@@ -4,20 +4,34 @@ let shaderSource = """
using namespace metal;
struct Vertex { float3 pos; float3 normal; float3 color; }; // 16-byte aligned, matches Swift SIMD3<Float>
-struct Uniforms { float4x4 mvp; float4x4 model; };
-struct VOut { float4 pos [[position]]; float3 normal; float3 color; };
+struct Uniforms { float4x4 mvp; float4x4 model; float4 material; };
+struct VOut { float4 pos [[position]]; float3 normal; float3 color; float3 world; float grid [[flat]]; };
vertex VOut vmain(uint vid [[vertex_id]], const device Vertex *v [[buffer(0)]], constant Uniforms &u [[buffer(1)]]) {
VOut o;
o.pos = u.mvp * float4(v[vid].pos, 1);
o.normal = (u.model * float4(v[vid].normal, 0)).xyz;
o.color = v[vid].color;
+ o.world = (u.model * float4(v[vid].pos, 1)).xyz;
+ o.grid = u.material.x;
return o;
}
fragment float4 fmain(VOut in [[stage_in]]) {
float3 l = normalize(float3(0.4, 0.8, 0.6));
float d = max(dot(normalize(in.normal), l), 0.0);
- return float4(in.color * (0.25 + 0.75 * d), 1);
+ float3 color = in.color;
+ if (in.grid > .5 && in.normal.y > .5) {
+ float2 p = in.world.xz;
+ float2 footprint = max(fwidth(p), float2(1e-5));
+ float2 distanceToLine = abs(fract(p + .5) - .5);
+ // Maintain a readable minimum footprint, then fade when adjacent lines become unresolved.
+ float2 halfWidth = max(float2(.0075), footprint * .8);
+ float2 coverage = 1 - smoothstep(halfWidth - footprint * .5, halfWidth + footprint * .5, distanceToLine);
+ float2 resolvable = 1 - smoothstep(float2(.20), float2(.45), footprint);
+ float line = max(coverage.x * resolvable.x, coverage.y * resolvable.y);
+ color = mix(color, float3(.35, .40, .55), line);
+ }
+ return float4(color * (0.25 + 0.75 * d), 1);
}
"""
diff --git a/Tests/LensWarpChecks.swift b/Tests/LensWarpChecks.swift
@@ -28,8 +28,33 @@ struct LensWarpChecks {
}
}
}
+ for gain: Float in [-1, 0, 0.5, 2, 4] {
+ for eye in 0..<2 {
+ let actual = try warp.coordinateSamples(device: device, points: points, eye: eye, redGain: gain, blueHorizontalGain: gain, blueVerticalGain: gain)
+ for (i, p) in points.enumerated() {
+ let cpu = optics.calibration.eyes[eye].distort(view: eye, u: Double(p.x), v: Double(p.y), bounds: optics.bounds[eye])
+ let expected = [cpu.g + (cpu.r - cpu.g) * Double(gain), cpu.g, cpu.g + (cpu.b - cpu.g) * Double(gain)]
+ for c in 0..<3 {
+ let error = simd_abs(SIMD2<Double>(actual[i * 3 + c]) - expected[c])
+ coordinateWorst = max(coordinateWorst, error.x, error.y)
+ }
+ }
+ }
+ }
+ for eye in 0..<2 {
+ let actual = try warp.coordinateSamples(device: device, points: points, eye: eye, redGain: 1.75, blueHorizontalGain: 2.25, blueVerticalGain: 1.5)
+ for (i, p) in points.enumerated() {
+ let cpu = optics.calibration.eyes[eye].distort(view: eye, u: Double(p.x), v: Double(p.y), bounds: optics.bounds[eye])
+ let expected = [cpu.g + (cpu.r - cpu.g) * Double(Float(1.75)), cpu.g,
+ cpu.g + (cpu.b - cpu.g) * SIMD2(Double(Float(2.25)), Double(Float(1.5)))]
+ for c in 0..<3 {
+ let error = simd_abs(SIMD2<Double>(actual[i * 3 + c]) - expected[c])
+ coordinateWorst = max(coordinateWorst, error.x, error.y)
+ }
+ }
+ }
precondition(coordinateWorst < 1e-5, "Float shader coordinates differ from Double model: \(coordinateWorst)")
- print("PASS: \(points.count * 2 * 3) RGB float-coordinate pairs, max GPU/CPU error \(coordinateWorst)")
+ print("PASS: \(points.count * 2 * 3 * 7) RGB float-coordinate pairs (six strengths and independent blue axes), max GPU/CPU error \(coordinateWorst)")
let w = warp.color.width, h = warp.color.height, eyeW = w / 2
let bytes = UnsafeMutablePointer<UInt8>.allocate(capacity: w * h * 4)
defer { bytes.deallocate() }
diff --git a/evidence/lenswarp-tuning-checks.txt b/evidence/lenswarp-tuning-checks.txt
@@ -0,0 +1,2 @@
+PASS: 26250 RGB float-coordinate pairs (six strengths and independent blue axes), max GPU/CPU error 1.7778522127809282e-06
+PASS: 1632 RGB channel samples, max GPU/CPU error 0.0041510994889600375; asymmetric projection edges correct
diff --git a/evidence/optics-controls-live.txt b/evidence/optics-controls-live.txt
@@ -0,0 +1,85 @@
+7851.892422 [hmd] serial REDACTED-DEVICE-SERIAL
+7852.001422 [hmd] wake commands accepted; waiting for IMU and video evidence
+7852.018422 captured display 2: ok
+7852.532422 [display callback] id=2 flags=8 main=true
+7852.545422 mode 4320x2160@90 (ui 4320x2160): ok
+7852.561422 rendering on headset (4320.0, 2160.0), drawable (4320.0, 2160.0); dedicated Metal display loop at 90 Hz
+7852.570422 render worker main=false
+frame 1 drawable 4320x2160
+7852.651422 [display callback] handled after 118.5 ms
+7853.871422 [hmd] display ready=true; raw 05 01 01 01 01 59 00 00 00 07 3b
+frame 300 drawable 4320x2160
+frame 600 drawable 4320x2160
+7862.003422 health display=2 submitted=843 presented=833 skipped=7 fps=77.81 presentAge=0.00s GPUms=6.56 GPUerrors=0 IMU=9504 lastIMU=0.0s status=1
+frame 900 drawable 4320x2160
+frame 1200 drawable 4320x2160
+frame 1500 drawable 4320x2160
+7872.002422 health display=2 submitted=1743 presented=1733 skipped=7 fps=90.00 presentAge=0.00s GPUms=6.58 GPUerrors=0 IMU=19504 lastIMU=0.0s status=1
+frame 1800 drawable 4320x2160
+frame 2100 drawable 4320x2160
+frame 2400 drawable 4320x2160
+7882.002422 health display=2 submitted=2643 presented=2633 skipped=7 fps=90.00 presentAge=0.00s GPUms=6.59 GPUerrors=0 IMU=29508 lastIMU=0.0s status=1
+frame 2700 drawable 4320x2160
+frame 3000 drawable 4320x2160
+frame 3300 drawable 4320x2160
+7892.002422 health display=2 submitted=3543 presented=3533 skipped=7 fps=90.00 presentAge=0.00s GPUms=6.59 GPUerrors=0 IMU=39512 lastIMU=0.0s status=1
+frame 3600 drawable 4320x2160
+frame 3900 drawable 4320x2160
+frame 4200 drawable 4320x2160
+frame 4500 drawable 4320x2160
+frame 4800 drawable 4320x2160
+7908.515422 health display=2 submitted=5029 presented=5019 skipped=7 fps=89.99 presentAge=0.01s GPUms=6.60 GPUerrors=0 IMU=48060 lastIMU=0.0s status=1
+frame 5100 drawable 4320x2160
+frame 5400 drawable 4320x2160
+frame 5700 drawable 4320x2160
+frame 6000 drawable 4320x2160
+frame 6300 drawable 4320x2160
+frame 6600 drawable 4320x2160
+frame 6900 drawable 4320x2160
+frame 7200 drawable 4320x2160
+frame 7500 drawable 4320x2160
+frame 7800 drawable 4320x2160
+7942.632422 health display=2 submitted=8099 presented=8087 skipped=8 fps=89.93 presentAge=0.01s GPUms=6.60 GPUerrors=0 IMU=51016 lastIMU=0.0s status=1
+frame 8100 drawable 4320x2160
+frame 8400 drawable 4320x2160
+frame 8700 drawable 4320x2160
+7952.002422 health display=2 submitted=8943 presented=8931 skipped=8 fps=90.07 presentAge=0.00s GPUms=6.60 GPUerrors=0 IMU=60388 lastIMU=0.0s status=1
+frame 9000 drawable 4320x2160
+frame 9300 drawable 4320x2160
+frame 9600 drawable 4320x2160
+7962.002422 health display=2 submitted=9843 presented=9831 skipped=8 fps=90.00 presentAge=0.00s GPUms=6.60 GPUerrors=0 IMU=70392 lastIMU=0.0s status=1
+frame 9900 drawable 4320x2160
+frame 10200 drawable 4320x2160
+frame 10500 drawable 4320x2160
+7972.002422 health display=2 submitted=10743 presented=10731 skipped=8 fps=90.00 presentAge=0.00s GPUms=6.60 GPUerrors=0 IMU=80396 lastIMU=0.0s status=1
+frame 10800 drawable 4320x2160
+frame 11100 drawable 4320x2160
+frame 11400 drawable 4320x2160
+7982.001422 health display=2 submitted=11643 presented=11631 skipped=8 fps=90.01 presentAge=0.00s GPUms=6.60 GPUerrors=0 IMU=90396 lastIMU=0.0s status=1
+frame 11700 drawable 4320x2160
+frame 12000 drawable 4320x2160
+frame 12300 drawable 4320x2160
+7992.002422 health display=2 submitted=12543 presented=12531 skipped=8 fps=89.99 presentAge=0.00s GPUms=6.60 GPUerrors=0 IMU=100400 lastIMU=0.0s status=1
+frame 12600 drawable 4320x2160
+frame 12900 drawable 4320x2160
+frame 13200 drawable 4320x2160
+8002.001422 health display=2 submitted=13443 presented=13431 skipped=8 fps=90.00 presentAge=0.00s GPUms=6.59 GPUerrors=0 IMU=110404 lastIMU=0.0s status=1
+frame 13500 drawable 4320x2160
+frame 13800 drawable 4320x2160
+frame 14100 drawable 4320x2160
+8012.001422 health display=2 submitted=14343 presented=14331 skipped=8 fps=90.00 presentAge=0.00s GPUms=6.59 GPUerrors=0 IMU=120404 lastIMU=0.0s status=1
+frame 14400 drawable 4320x2160
+frame 14700 drawable 4320x2160
+frame 15000 drawable 4320x2160
+8022.000422 health display=2 submitted=15243 presented=15231 skipped=8 fps=90.01 presentAge=0.00s GPUms=6.59 GPUerrors=0 IMU=130408 lastIMU=0.0s status=1
+frame 15300 drawable 4320x2160
+frame 15600 drawable 4320x2160
+frame 15900 drawable 4320x2160
+8032.000422 health display=2 submitted=16143 presented=16131 skipped=8 fps=90.00 presentAge=0.00s GPUms=6.59 GPUerrors=0 IMU=140412 lastIMU=0.0s status=1
+frame 16200 drawable 4320x2160
+frame 16500 drawable 4320x2160
+frame 16800 drawable 4320x2160
+8042.000422 health display=2 submitted=17043 presented=17031 skipped=8 fps=90.00 presentAge=0.01s GPUms=6.59 GPUerrors=0 IMU=150412 lastIMU=0.0s status=1
+frame 17100 drawable 4320x2160
+frame 17400 drawable 4320x2160
+frame 17700 drawable 4320x2160
diff --git a/evidence/optics-user-confirmed-checkpoint.json b/evidence/optics-user-confirmed-checkpoint.json
@@ -0,0 +1,5 @@
+{
+ "attribution": "Unknown; current runtime includes per-axis blue correction and pixel-filtered floor grid. No renderer changes between immediate user reports.",
+ "visual_validation": "Optical tuning controls were exercised during hardware testing.",
+ "privacy_note": "Personal optical adjustments and their fingerprint are omitted. Runtime defaults remain factory calibration."
+}