diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d068704 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +.sx-cache +.sx-tmp/ +sx-out/ +# iOS/macOS .app bundles land inside the ignored sx-out/ dir. +*.app/ +# Built binaries from `sx build main.sx` at repo root. +/main +# Flow scaffolding (nested working copy). +current/ diff --git a/assets/.gitkeep b/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/build.sx b/build.sx new file mode 100644 index 0000000..d7ce4fc --- /dev/null +++ b/build.sx @@ -0,0 +1,35 @@ +#import "modules/compiler.sx"; +#import "modules/platform/bundle.sx"; + +configure_build :: () { + opts := build_options(); + opts.set_bundle_id("co.swipelab.m3te"); + opts.set_post_link_callback(bundle_main); + if OS == { + case .macos: { + opts.set_output_path("sx-out/macos/M3te"); + opts.set_bundle_path("sx-out/macos/M3te.app"); + // Ad-hoc sign (empty identity) so a local `open` launch isn't + // Gatekeeper-rejected. SDL3 comes from Homebrew. + opts.add_link_flag("-L/opt/homebrew/lib"); + opts.add_link_flag("-lSDL3"); + opts.add_asset_dir("assets", "assets"); + } + case .ios: { + opts.set_output_path("sx-out/ios/M3te"); + opts.set_bundle_path("sx-out/ios/M3te.app"); + // Device-only: the simulator is ad-hoc signed by the bundler, + // which also grants get-task-allow for lldb. Embedding a device + // identity / profile in a sim build blocks install + launch. + if opts.is_ios_device() { + opts.set_codesign_identity("Apple Development: Alexandru Agrapine (DC8VVHJ9W4)"); + opts.set_provisioning_profile("/Users/agra/Downloads/SwipeS32DevProfile.mobileprovision"); + } + opts.add_framework("UIKit"); + opts.add_framework("OpenGLES"); + opts.add_framework("QuartzCore"); + opts.add_framework("Metal"); + opts.add_asset_dir("assets", "assets"); + } + } +} diff --git a/goldens/.gitkeep b/goldens/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/main.sx b/main.sx new file mode 100644 index 0000000..0535515 --- /dev/null +++ b/main.sx @@ -0,0 +1,91 @@ +#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; + +frame :: () { + fc := g_plat.begin_frame(); + g_viewport_w = fc.viewport_w; + g_viewport_h = fc.viewport_h; + + for g_plat.poll_events(): (*ev) { + 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); + } + clear : ClearColor = .{ r = 0.12, g = 0.12, b = 0.15, a = 1.0 }; + if !g_metal_gpu.begin_frame(clear) { return; } + 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); + 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(); +}