P0.2: draw a colored quad on the iOS Simulator, driven by sx
Prove the render path end-to-end: bring up UIKit + Metal (already scaffolded), clear to a solid blue, and draw one centered orange quad via the GPU protocol's clear+quad path — an MSL pass-through pipeline plus a 6-vertex (2-triangle) NDC buffer, created lazily once the MTLDevice exists. Geometry is NDC [-0.5, 0.5]² so the quad is the central 50%x50% of the drawable regardless of device resolution, keeping the screenshot golden unambiguous. Background (0.10, 0.20, 0.55), quad (1.0, 0.6, 0.0). goldens/p0_quad.png is the first screenshot golden, captured from the booted iOS 26.0 simulator after install + launch.
This commit is contained in:
BIN
goldens/p0_quad.png
Normal file
BIN
goldens/p0_quad.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 74 KiB |
74
main.sx
74
main.sx
@@ -22,6 +22,60 @@ g_viewport_h : f32 = 600.0;
|
|||||||
g_uikit_plat : *UIKitPlatform = null;
|
g_uikit_plat : *UIKitPlatform = null;
|
||||||
g_metal_gpu : *MetalGPU = 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 <metal_stdlib>
|
||||||
|
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 :: () {
|
frame :: () {
|
||||||
fc := g_plat.begin_frame();
|
fc := g_plat.begin_frame();
|
||||||
g_viewport_w = fc.viewport_w;
|
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 {
|
} 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);
|
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 };
|
// Compile the quad pipeline + upload its vertices once. The MTLDevice
|
||||||
if !g_metal_gpu.begin_frame(clear) { return; }
|
// 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);
|
g_metal_gpu.end_frame(fc.target_present_time);
|
||||||
} else {
|
} else {
|
||||||
glViewport(0, 0, fc.pixel_w, fc.pixel_h);
|
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);
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
}
|
}
|
||||||
g_plat.end_frame();
|
g_plat.end_frame();
|
||||||
|
|||||||
Reference in New Issue
Block a user