GPU Compute Harness
Vulkan Compute Lab
A Vulkan harness for writing GPU compute shaders and seeing them immediately. There is no graphics pipeline anywhere in the project: compute writes an offscreen storage image that is blitted straight to the swapchain, and saving a shader file reloads it live.

Employer signal
What This Project Shows
This project shows that I can work directly against an explicit low-level API with no engine underneath — managing device memory, image layouts, and multi-frame synchronization myself — and that I debug rendering problems by deriving the numerics rather than tweaking constants until they look right.
Problem
What Needed To Be Solved
The standard route into Vulkan spends hundreds of lines on render passes, framebuffers, vertex buffers, and a graphics pipeline before a single pixel appears, and none of that is needed to run a compute shader. I wanted the shortest honest path from 'the GPU executes my code' to 'I can see it', and then for the shader — not the C++ — to be the thing I iterate on.
Approach
How I Built The Solution
I cut the graphics pipeline entirely. A compute shader writes pixels into a storage image, and a blit moves that image to the swapchain, which means no render pass, no framebuffers, and not even image views on the swapchain images. Shaders are compiled to SPIR-V by glslc at runtime rather than at build time, which is what makes live reload possible: edit a .comp file, save, and the pipeline swaps within a frame.
Outcome
What It Demonstrates
The lab runs compute shaders at interactive rates with validation layers clean, reloads them on save, and captures its own frames to PNG. More usefully, it produced two transferable lessons: absolute input values are safe to read and dangerous to re-anchor, and if an invariant must hold throughout an animation, interpolate in the space where it is affine.
Evidence From Source
Numerical debugging
Deep zooms made the view snap instead of slide. The cause was fp32 quantization: adjacent pixels differ by scale/height, and once that step falls below the float32 ULP of the coordinate, a whole neighbourhood collapses onto one representable value and the view can only move in lattice steps. I measured the onset (30,000x clean, 100,000x visibly stepped, 300,000x unusable) and derived a zoom ceiling from the ULP relation rather than picking a number that looked safe.
Engineering decision
Zooming lurched toward the cursor mid-gesture because pan and log-zoom were smoothed independently, so the 'keep the point under the cursor fixed' invariant only held at the endpoints. Re-deriving the state as (centre, scale = 1/zoom) makes that invariant affine in the interpolated variables, so every intermediate frame satisfies it for free. Simulated drift went from 6.9 pixels to 6e-14.