diff --git a/goldens/p0_quad.png b/goldens/p0_quad.png new file mode 100644 index 0000000..bbe5863 Binary files /dev/null and b/goldens/p0_quad.png differ diff --git a/main.sx b/main.sx index 0535515..62ac332 100644 --- a/main.sx +++ b/main.sx @@ -22,6 +22,60 @@ g_viewport_h : f32 = 600.0; g_uikit_plat : *UIKitPlatform = null; g_metal_gpu : *MetalGPU = null; +// ── Render-path proof (P0.2) ───────────────────────────────────────────── +// Clear to a solid blue, then draw one centered orange quad covering the +// central 50%×50% of the drawable. Geometry is in NDC ([-0.5, 0.5]²) so the +// quad stays screen-size independent across simulator devices, which keeps +// the screenshot golden unambiguous. This is the GPU protocol's +// clear+quad path: an MSL pipeline state plus a 6-vertex (2-triangle) +// buffer, created lazily once the MTLDevice exists. +g_quad_shader : ShaderHandle = 0; +g_quad_vbuf : BufferHandle = 0; + +BG_CLEAR :: ClearColor.{ r = 0.10, g = 0.20, b = 0.55, a = 1.0 }; + +// Vertex layout matches QUAD_MSL's `Vertex`: packed_float2 pos + +// packed_float4 color = 24 bytes. `packed_*` avoids the 16-byte alignment +// padding a plain `float4` would force. 6 vertices = 2 triangles. +QUAD_VERTS : [36]f32 = .[ + -0.5, 0.5, 1.0, 0.6, 0.0, 1.0, + 0.5, 0.5, 1.0, 0.6, 0.0, 1.0, + -0.5, -0.5, 1.0, 0.6, 0.0, 1.0, + 0.5, 0.5, 1.0, 0.6, 0.0, 1.0, + 0.5, -0.5, 1.0, 0.6, 0.0, 1.0, + -0.5, -0.5, 1.0, 0.6, 0.0, 1.0, +]; + +// Pass-through shader: the vertex stage emits NDC positions directly (no +// projection), the fragment stage returns the interpolated vertex color. +// Entry-point names vmain / fmain are what MetalGPU.create_shader looks up. +QUAD_MSL :: #string MSL +#include +using namespace metal; + +struct Vertex { + packed_float2 pos; + packed_float4 color; +}; + +struct RasterizerData { + float4 position [[position]]; + float4 color; +}; + +vertex RasterizerData vmain(uint vid [[vertex_id]], + constant Vertex* vertices [[buffer(0)]]) { + RasterizerData out; + out.position = float4(vertices[vid].pos, 0.0, 1.0); + out.color = float4(vertices[vid].color); + return out; +} + +fragment float4 fmain(RasterizerData in [[stage_in]]) { + return in.color; +} +MSL; + frame :: () { fc := g_plat.begin_frame(); g_viewport_w = fc.viewport_w; @@ -49,12 +103,26 @@ frame :: () { } else if g_metal_gpu.pixel_w != g_uikit_plat.pixel_w or g_metal_gpu.pixel_h != g_uikit_plat.pixel_h { g_metal_gpu.resize(g_uikit_plat.pixel_w, g_uikit_plat.pixel_h); } - clear : ClearColor = .{ r = 0.12, g = 0.12, b = 0.15, a = 1.0 }; - if !g_metal_gpu.begin_frame(clear) { return; } + // Compile the quad pipeline + upload its vertices once. The MTLDevice + // was created eagerly in main(), so both only need a valid device. + if g_quad_shader == 0 { + g_quad_shader = g_metal_gpu.create_shader(QUAD_MSL, ""); + if g_quad_shader == 0 { return; } + } + if g_quad_vbuf == 0 { + g_quad_vbuf = g_metal_gpu.create_buffer(size_of([36]f32)); + if g_quad_vbuf == 0 { return; } + g_metal_gpu.update_buffer(g_quad_vbuf, xx @QUAD_VERTS, size_of([36]f32)); + } + + if !g_metal_gpu.begin_frame(BG_CLEAR) { return; } + g_metal_gpu.set_shader(g_quad_shader); + g_metal_gpu.set_vertex_buffer(g_quad_vbuf); + g_metal_gpu.draw_triangles(0, 6); g_metal_gpu.end_frame(fc.target_present_time); } else { glViewport(0, 0, fc.pixel_w, fc.pixel_h); - glClearColor(0.12, 0.12, 0.15, 1.0); + glClearColor(0.10, 0.20, 0.55, 1.0); glClear(GL_COLOR_BUFFER_BIT); } g_plat.end_frame();