#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 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(); }