Phase 8 step 3a of the Metal renderer port:
- New library/modules/gpu/ with types.sx (handles + ClearColor +
TextureFormat enum), api.sx (GPU :: protocol { ... } covering the
lifecycle / per-frame / resource / per-draw surface), and metal.sx
(MetalGPU backend implementing the protocol against CAMetalLayer).
Resource handles are 1-based indices into backend List(*void) tables.
MTL aggregates >16 bytes (MTLRegion, MTLScissorRect) pass via *T to
match arm64 Apple's indirect-by-reference ABI; MTLClearColor + CGSize
go through the HFA path as direct fn-pointer casts on objc_msgSend.
- UIKitPlatform got a gpu_mode: GpuMode toggle + sibling SxMetalView
class registration. In metal mode init skips EAGL context, the
did_finish_launching IMP skips the EAGL drawable-properties dict,
layoutSubviews reads the layer's bounds * dpi_scale into pixel_w/h
instead of allocating a GL renderbuffer, and end_frame is a no-op
(the MetalGPU owns its own present).
- examples/63-metal-clear.sx verifies the pipeline end-to-end on iOS
sim — compiles a pass-through MSL shader (packed_float2/packed_float4
to avoid alignment padding), uploads 3 vertices, draws a colored
triangle on a dark-blue clear.
Compiler fixes (filed-and-fixed in this branch):
- inline if X { return E; } followed by a fall-through final expression
no longer emits two terminators into the same basic block. Verified
by examples/83-inline-if-return-fallthrough.sx.
- Top-level type alias Name :: u32; now resolves correctly as the type
annotation on a global variable (was treated as ptr {}, breaking
comparisons + initializers). Verified by examples/84-global-type-alias.sx.
Issue->feature promotion:
- 16 historical examples/issue-NNNN.sx repros now confirmed-fixed and
renamed to focused feature names (67-82). Each gains a
tests/expected/*.txt + .exit pair so the regression suite covers them.
- 5 stale issue repros deleted (subsumed by broader tests).
Regression suite: 68 passing, 0 failed. macOS chess builds + runs; wasm
chess builds; iOS sim GLES chess still renders the full board; iOS sim
Metal demo renders the triangle.
121 lines
3.9 KiB
Plaintext
121 lines
3.9 KiB
Plaintext
// iOS-only: bring up UIKitPlatform in Metal mode, clear the screen dark
|
||
// blue each frame, then draw a colored triangle via the GPU protocol —
|
||
// exercises create_shader (MSL compile + pipeline state), create_buffer
|
||
// + update_buffer, set_shader, set_vertex_buffer, and draw_triangles.
|
||
// Step 3b will port the UI renderer to use this same surface.
|
||
//
|
||
// Build for iOS sim:
|
||
// /Users/agra/projects/sx/zig-out/bin/sx build --target ios-sim \
|
||
// examples/63-metal-clear.sx \
|
||
// -o /tmp/MetalClear --bundle /tmp/MetalClear.app \
|
||
// --bundle-id co.swipelab.metalclear -F ~/Library/Frameworks
|
||
// codesign --force --sign - --timestamp=none /tmp/MetalClear.app
|
||
// xcrun simctl install booted /tmp/MetalClear.app
|
||
// xcrun simctl launch --terminate-running-process booted co.swipelab.metalclear
|
||
//
|
||
// This file is iOS-only and not part of the JIT regression suite (no
|
||
// tests/expected/63-metal-clear.txt). The test runner skips it on macOS.
|
||
|
||
#import "modules/std.sx";
|
||
#import "modules/std/objc.sx";
|
||
#import "modules/compiler.sx";
|
||
#import "modules/platform/api.sx";
|
||
#import "modules/platform/uikit.sx";
|
||
#import "modules/gpu/api.sx";
|
||
#import "modules/gpu/metal.sx";
|
||
|
||
#framework "UIKit";
|
||
#framework "QuartzCore";
|
||
#framework "OpenGLES";
|
||
|
||
// Pass-through vertex + fragment shader for a colored triangle. Vertex
|
||
// layout is { packed_float2 pos; packed_float4 color; } = 24 bytes —
|
||
// `packed_*` types have 4-byte alignment so the struct doesn't get
|
||
// padded between fields (a plain `float4` would force 16-byte alignment
|
||
// and pad the struct out to 32 bytes per vertex). Entry-point names
|
||
// (vmain / fmain) match what MetalGPU.create_shader looks up.
|
||
TRI_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;
|
||
|
||
TRI_VERTS : [18]f32 = .[
|
||
-0.6, -0.4, 1.0, 0.0, 0.0, 1.0,
|
||
0.6, -0.4, 0.0, 1.0, 0.0, 1.0,
|
||
0.0, 0.6, 0.0, 0.0, 1.0, 1.0,
|
||
];
|
||
|
||
g_plat : *UIKitPlatform = null;
|
||
g_gpu : *MetalGPU = null;
|
||
g_shader : ShaderHandle = 0;
|
||
g_vbuf : BufferHandle = 0;
|
||
|
||
frame :: () {
|
||
if g_plat == null { return; }
|
||
if g_gpu == null { return; }
|
||
|
||
// Lazy-init the GPU on the first frame where the layer is available
|
||
// (the layer is created during -[SxAppDelegate didFinishLaunching:]
|
||
// which fires AFTER our main() returns into UIApplicationMain).
|
||
if g_gpu.layer == null {
|
||
if g_plat.gl_layer == null { return; }
|
||
if !g_gpu.init(g_plat.gl_layer, g_plat.pixel_w, g_plat.pixel_h) { return; }
|
||
}
|
||
|
||
// Compile shader + upload vertex buffer once.
|
||
if g_shader == 0 {
|
||
g_shader = g_gpu.create_shader(TRI_MSL, "");
|
||
if g_shader == 0 { return; }
|
||
}
|
||
if g_vbuf == 0 {
|
||
g_vbuf = g_gpu.create_buffer(72); // 3 verts × 24 bytes
|
||
if g_vbuf == 0 { return; }
|
||
g_gpu.update_buffer(g_vbuf, xx @TRI_VERTS, 72);
|
||
}
|
||
|
||
bg : ClearColor = .{ r = 0.07, g = 0.10, b = 0.18, a = 1.0 };
|
||
if !g_gpu.begin_frame(bg) { return; }
|
||
g_gpu.set_shader(g_shader);
|
||
g_gpu.set_vertex_buffer(g_vbuf);
|
||
g_gpu.draw_triangles(0, 3);
|
||
g_gpu.end_frame(0.0);
|
||
}
|
||
|
||
main :: () -> s32 {
|
||
inline if OS != .ios { return 0; }
|
||
|
||
plat : *UIKitPlatform = xx context.allocator.alloc(size_of(UIKitPlatform));
|
||
plat.gpu_mode = .metal;
|
||
if !plat.init("Metal Clear", 0, 0) { return 1; }
|
||
g_plat = plat;
|
||
|
||
gpu : *MetalGPU = xx context.allocator.alloc(size_of(MetalGPU));
|
||
g_gpu = gpu;
|
||
|
||
plat.run_frame_loop(closure(frame));
|
||
plat.shutdown();
|
||
0;
|
||
}
|