Files
m3te/main.sx
swipelab bb07bd9cae P0.3: flip the quad color on a real iOS-Simulator tap, driven by sx
Prove the input path end-to-end. Poll platform events in the frame loop
and, on a mouse_down (UIKit touchesBegan, mapped to an Event in uikit.sx),
toggle the centered quad between orange and green. The toggle marks the
vertex buffer dirty; the next frame re-uploads the active color palette
before drawing. Flip on the press only — a tap also delivers mouse_up
(touchesEnded), so toggling on both would net to no visible change.

goldens/p0_input_before.png (orange quad) and goldens/p0_input_after.png
(green quad) bracket a real tap injected at the device-screen center
(201,437 pt) via `idb ui tap` on the booted iOS 26.0 simulator.
2026-06-04 18:40:49 +03:00

192 lines
7.2 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#import "modules/std.sx";
#import "build.sx";
#import "modules/compiler.sx";
#import "modules/opengl.sx";
#import "modules/sdl3.sx";
#import "modules/gpu/api.sx";
#import "modules/gpu/types.sx";
#import "modules/gpu/metal.sx";
#import "modules/platform/api.sx";
#import "modules/platform/sdl3.sx";
#import "modules/platform/uikit.sx";
#run configure_build();
g_plat : Platform = ---;
g_viewport_w : f32 = 800.0;
g_viewport_h : f32 = 600.0;
// iOS-only concrete handles kept alongside the boxed `g_plat` so the frame
// loop can reach the CAMetalLayer pointer / pixel dims without going through
// the protocol box.
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;
// ── Input-path proof (P0.3) ──────────────────────────────────────────────
// A tap toggles the quad between two distinct colors, proving a real touch
// reaches sx and changes the rendered frame. UIKit touchesBegan is mapped to
// a `mouse_down` Event in uikit.sx; the frame loop's poll flips
// `g_quad_flipped` and marks `g_quad_dirty`, which re-uploads the matching
// vertex colors before the next draw.
g_quad_flipped : bool = false;
g_quad_dirty : bool = true;
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. Two
// arrays share the same geometry and differ only in color so the buffer
// re-upload is a flat memcpy of the active palette.
QUAD_VERTS_ORANGE : [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,
];
QUAD_VERTS_GREEN : [36]f32 = .[
-0.5, 0.5, 0.15, 0.85, 0.35, 1.0,
0.5, 0.5, 0.15, 0.85, 0.35, 1.0,
-0.5, -0.5, 0.15, 0.85, 0.35, 1.0,
0.5, 0.5, 0.15, 0.85, 0.35, 1.0,
0.5, -0.5, 0.15, 0.85, 0.35, 1.0,
-0.5, -0.5, 0.15, 0.85, 0.35, 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 :: () {
fc := g_plat.begin_frame();
g_viewport_w = fc.viewport_w;
g_viewport_h = fc.viewport_h;
for g_plat.poll_events(): (*ev) {
if ev == {
// Flip on the press only. A tap also produces mouse_up
// (touchesEnded); toggling on both would net to no change.
case .mouse_down: {
g_quad_flipped = !g_quad_flipped;
g_quad_dirty = true;
}
}
inline if OS != .ios {
if ev == {
case .key_up: (e) {
if e.key == .escape { g_plat.stop(); }
}
}
}
}
inline if OS == .ios {
// Lazy-attach Metal once -[SxAppDelegate didFinishLaunching:] has
// installed the SxMetalView and its bounds have been measured; both
// can lag the first CADisplayLink tick, and a zero-sized drawable
// aborts via XPC.
if g_uikit_plat.gl_layer == null { return; }
if g_uikit_plat.pixel_w <= 0 or g_uikit_plat.pixel_h <= 0 { return; }
if g_metal_gpu.layer == null {
g_metal_gpu.init(g_uikit_plat.gl_layer, g_uikit_plat.pixel_w, 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);
}
// 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; }
}
if g_quad_dirty {
verts := if g_quad_flipped then @QUAD_VERTS_GREEN else @QUAD_VERTS_ORANGE;
g_metal_gpu.update_buffer(g_quad_vbuf, xx verts, size_of([36]f32));
g_quad_dirty = false;
}
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.10, 0.20, 0.55, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
}
g_plat.end_frame();
}
main :: () -> void {
inline if OS == .ios {
u : *UIKitPlatform = xx context.allocator.alloc(size_of(UIKitPlatform));
u.gpu_mode = .metal;
if !u.init("m3te", 800, 600) { return; }
g_plat = xx u;
g_uikit_plat = u;
// The CAMetalLayer doesn't exist until didFinishLaunching: runs after
// we return into UIApplicationMain, so attach lazily on the first
// frame. init(null, 0, 0) only needs the MTLDevice.
g_metal_gpu = xx context.allocator.alloc(size_of(MetalGPU));
// alloc returns uninitialized memory; struct field defaults are NOT
// applied, so List caps/lens would be garbage without this memset.
memset(xx g_metal_gpu, 0, size_of(MetalGPU));
if !g_metal_gpu.init(null, 0, 0) { return; }
} else {
s : *SdlPlatform = xx context.allocator.alloc(size_of(SdlPlatform));
if !s.init("m3te", 800, 600) { return; }
g_plat = xx s;
}
fc := g_plat.begin_frame();
g_viewport_w = fc.viewport_w;
g_viewport_h = fc.viewport_h;
g_plat.run_frame_loop(closure(frame));
g_plat.shutdown();
}