std: restructure step 3 — ffi/ moves, build.sx, math dir spelling, fixtures

- objc.sx, objc_block.sx (from std/) + sdl3/opengl/raylib/stb/stb_truetype/
  wasm vendor bindings (from modules/ root) -> modules/ffi/
- std/uikit.sx deleted: platform/uikit.sx already declares UIApplicationMain
  and imports objc; '#framework "UIKit"' cannot live in a file imported on
  macOS targets (unconditional link directive, UIKit is iOS-only), so the
  three iOS-only examples carry the 3-line glue inline. 1607/1608/1616 also
  un-rotted (dead ns_string -> 'xx "..."' Into conversions, callconv(.c)
  msgSend fn-ptrs) — all three build for ios-sim/ios again.
- math/math.sx -> math/scalar.sx; one spelling '#import "modules/math"'
  everywhere (4 pinned IR snapshots regenerated: dir import adds Vec2/Mat4
  to the type tables).
- compiler.sx -> build.sx (imports, CLAUDE.md bundling table, specs.md).
- testpkg/ + test_c.sx -> tests/fixtures/ (resolve CWD-relative from repo
  root, same as vendors/).
- library-internal imports use full modules/... paths (std.sx tail,
  platform/bundle.sx, fixtures).
This commit is contained in:
agra
2026-06-11 08:37:22 +03:00
parent 59f0aa7716
commit 12bf61a9fc
142 changed files with 5026 additions and 4161 deletions

144
library/modules/ffi/objc.sx Normal file
View File

@@ -0,0 +1,144 @@
// Obj-C runtime FFI primitives.
//
// objc_msgSend has the standard ARM64 calling convention (no varargs path).
// Each call site must invoke through a function pointer of the *exact*
// argument and return shape. The idiom:
//
// msg_fn : (recv: *void, sel: *void, arg: [*]u8) -> *void = xx objc_msgSend;
// result := msg_fn(receiver, selector, c_str);
// ─── Obj-C primitive type aliases ───────────────────────────────────────
// Named stand-ins for the three opaque Obj-C runtime types. They all
// resolve to `*void` at the LLVM layer (no runtime cost) but improve
// readability in foreign-class declarations and call sites.
//
// id — any Obj-C instance pointer
// Class — a class object pointer
// SEL — a registered selector
//
// `Class(T)` parameterization (phantom-typed, with `#extends`-aware
// covariance) is a follow-up — needs compiler-level type-check support.
// For now, `Class` alone is the only form; assignments are not checked
// against the referent's class hierarchy.
id :: *void;
Class :: *void;
SEL :: *void;
// Apple's `BOOL` is a signed char (NOT sx's built-in `bool`, which is
// LLVM `i1`). Obj-C method signatures that take or return `BOOL` cross
// the FFI boundary as `s8`.
BOOL :: s8;
// On macOS libobjc is auto-loaded by libSystem; on iOS it isn't, so we
// link it explicitly. Foundation registers NSString etc. with the runtime,
// also auto-loaded on macOS and required as an explicit framework on iOS.
objc :: #library "objc";
#framework "Foundation";
objc_getClass :: (name: [*]u8) -> *void #foreign objc;
objc_lookUpClass :: (name: [*]u8) -> *void #foreign objc;
sel_registerName :: (name: [*]u8) -> *void #foreign objc;
class_createInstance :: (cls: *void, extra: usize) -> *void #foreign objc;
object_getClass :: (obj: *void) -> *void #foreign objc;
object_getIvar :: (obj: *void, ivar: *void) -> *void #foreign objc;
object_setIvar :: (obj: *void, ivar: *void, val: *void) #foreign objc;
// Declared with the simplest non-variadic shape. Cast per call site.
objc_msgSend :: (recv: *void, sel: *void) -> *void #foreign objc;
// ─── Dynamic class registration ─────────────────────────────────────────
// Define a new Obj-C class at runtime: allocate the pair, attach methods +
// protocols, then finalize with `objc_registerClassPair`. The class is then
// usable via `class_createInstance` and Obj-C dispatch.
//
// IMPs (method implementations) are function pointers with the implicit
// Obj-C method shape: `(self: *void, _cmd: *void, ...args) -> ret` with
// `callconv(.c)` so they land args in the standard C registers.
//
// Method type encoding strings follow Apple's runtime DSL:
// v = void c = char/BOOL i = int l = long f = float d = double
// @ = id (object) : = SEL # = Class
// Return type comes first, then receiver (`@`), then `_cmd` (`:`), then args.
// Examples:
// "v@:" -> void method(id, SEL)
// "c@:" -> BOOL method(id, SEL)
// "@@:@" -> id method(id, SEL, id)
// "B@:@@" -> BOOL method(id, SEL, id, id)
objc_allocateClassPair :: (super: *void, name: [*]u8, extra: usize) -> *void #foreign objc;
class_addMethod :: (cls: *void, sel: *void, imp: *void, types: [*]u8) -> bool #foreign objc;
class_addProtocol :: (cls: *void, proto: *void) -> bool #foreign objc;
objc_getProtocol :: (name: [*]u8) -> *void #foreign objc;
objc_registerClassPair :: (cls: *void) -> void #foreign objc;
// Foundation C-API helpers (Foundation is already linked above via #framework).
// NSLog takes an NSString format; the variadic tail is not exposed here.
NSLog :: (fmt: *NSString) #foreign;
// ─── NSObject (Phase 4 / M4.A) ───────────────────────────────────────────
// Root of every Obj-C class hierarchy. Apple's runtime supplies the IMPs;
// sx declares the method surface so user code can write
// `defer view.release();` and `view.retain()` directly instead of going
// through `objc_msgSend` casts. M2.3's `#extends`-aware dispatch finds
// these methods automatically once a class roots its `#extends` chain at
// NSObject (foreign classes in uikit.sx etc. add `#extends NSObject;` to
// inherit the surface).
//
// `release` is NOT a reserved keyword in sx — modern clang ARC bans
// user-source calls to it (the ARC compiler emits them automatically), but
// sx isn't under clang ARC. Calling `view.release()` here is equivalent to
// pre-ARC Obj-C source code: dispatches through the runtime, decrements the
// refcount, fires `-dealloc` at zero.
NSObject :: #foreign #objc_class("NSObject") {
alloc :: () -> *NSObject;
init :: (self: *Self) -> *NSObject;
new :: () -> *NSObject; // shorthand for [[Cls alloc] init]
retain :: (self: *Self) -> *Self;
release :: (self: *Self);
autorelease :: (self: *Self) -> *Self;
class :: () -> *void; // metaclass query — `Cls.class()`
description :: (self: *Self) -> *void; // returns *NSString
hash :: (self: *Self) -> u64;
isEqual :: (self: *Self, other: *void) -> BOOL;
isKindOfClass :: (self: *Self, cls: *void) -> BOOL;
respondsToSelector :: (self: *Self, sel: *void) -> BOOL;
}
// ─── NSString ────────────────────────────────────────────────────────────
// Foundation's immutable string. `UTF8String` views the bytes as a C string
// whose lifetime is tied to the NSString (don't free it). The `Into` impl
// lets a string literal flow into any `*NSString` slot via `xx`, e.g.
// `dict.objectForKey(xx "SomeKey")`.
NSString :: #foreign #objc_class("NSString") {
#extends NSObject;
UTF8String :: (self: *Self) -> [*]u8;
}
// Wraps the bytes in an autoreleased NSString via `+stringWithUTF8String:`.
// `self.ptr` must be NUL-terminated — string literals are; an arbitrary
// sliced/built `string` may not be.
impl Into(*NSString) for string {
convert :: (self: string) -> *NSString {
cls := objc_getClass("NSString".ptr);
sel := sel_registerName("stringWithUTF8String:".ptr);
msg : (*void, *void, [*]u8) -> *void callconv(.c) = xx objc_msgSend;
return xx msg(cls, sel, self.ptr);
}
}
// ─── Autoreleasepool (M4.A) ──────────────────────────────────────────────
// Foundation factory methods (`NSString.stringWithUTF8String:`,
// `[NSArray array]`, ...) return autoreleased objects — valid until the
// current pool drains. Wrap such code in `autoreleasepool(() => { ... })`
// so the pool drains deterministically at block end.
//
// Stdlib helper, not a language keyword. The closure call adds a frame —
// for hot loops, inline the push/defer-pop pattern manually.
objc_autoreleasePoolPush :: () -> *void #foreign objc;
objc_autoreleasePoolPop :: (pool: *void) #foreign objc;
autoreleasepool :: (body: Closure()) {
pool := objc_autoreleasePoolPush();
defer objc_autoreleasePoolPop(pool);
body();
}

View File

@@ -0,0 +1,140 @@
// Obj-C blocks bridged to sx closures.
//
// Apple's block ABI (clang's "Block Implementation Specification"): a block
// pointer is a struct whose first five fields are { isa, flags, reserved,
// invoke, descriptor } followed by per-block captured state. When an API
// like `[UIView animateWithDuration:animations:]` receives a block, the
// runtime reads `invoke` and calls it with the block pointer as the first
// argument. UIKit / Foundation callers always `_Block_copy` synchronously
// before returning, so a stack-allocated block is safe to pass directly.
//
// We layer the sx closure onto Apple's layout by appending two pointer
// fields to the standard 32-byte header: `sx_env` (the closure's captured
// environment pointer) and `sx_fn` (the closure trampoline). The per-
// signature `__block_invoke_*` C-ABI fn knows the offsets and calls
// through to `sx_fn(sx_env, args...)`.
//
// ── Lifetime contract ───────────────────────────────────────────────────
// `xx <closure> : *Block` returns a pointer into the surrounding sx
// function's stack frame. Same rule as `&local_var`: pass it directly to
// a callee that consumes it immediately or `_Block_copy`s internally
// (UIKit/Foundation always do). Don't store the pointer to use after the
// caller returns. If you need that, ship a `Block_copy`-backed sibling
// API and use it instead.
// `build_block_convert` (below) is a comptime metaprogram that emits sx source
// with `concat` / `int_to_string`; those live in std.sx. A metaprogram body
// resolves its bare names in its OWN module (issue 0106), so this module must
// import std.sx itself rather than relying on the call site's visibility.
#import "modules/std.sx";
// Standard 32-byte block header plus two trailing slots for the sx closure
// it wraps. Total = 48 bytes.
Block :: struct {
isa: *void;
flags: s32;
reserved: s32;
invoke: *void;
descriptor: *void;
sx_env: *void;
sx_fn: *void;
}
// Per-block-shape metadata. The runtime reads `size` when copying the
// block to the heap, so it must equal the actual instance size.
BlockDescriptor :: struct {
reserved: u64;
size: u64;
}
// libSystem isa pointer for stack-allocated blocks. Resolved at link time
// (auto-linked on every Apple target via libSystem).
_NSConcreteStackBlock : *void #foreign;
// Shared descriptor for the 48-byte sx-block layout. All Into impls below
// point their `descriptor` field at this.
__sx_block_descriptor : BlockDescriptor = .{
reserved = 0,
size = 48,
};
// Single generic impl covers every closure shape. The compiler
// monomorphises this body per call shape; inside each mono,
// `$args` is the bound pack of arg types and `$R` is the bound
// return type. The `#insert` evaluates `build_block_convert` at
// comptime and substitutes the resulting source — a nested
// `callconv(.c)` trampoline + the Block literal that points its
// `invoke` slot at it. One impl in stdlib replaces every per-
// signature hand-rolled `__block_invoke_*` + `Into(Block)` pair.
impl Into(Block) for Closure(..$args) -> $R {
convert :: (self: Closure(..$args) -> $R) -> Block {
#insert build_block_convert($args, $R);
}
}
// Comptime builder for the generic `Into(Block) for Closure(..$args)
// -> $R` impl body. Receives the closure's per-position pack types
// (`args`, a comptime `[]Type`) and the closure's return type
// (`$ret`), emits source that:
//
// 1. Declares a nested `__invoke` `callconv(.c)` trampoline whose
// signature matches the per-shape Apple Block ABI: first arg is
// `block_self: *Block`, then the pack types verbatim. The body
// reconstructs a typed fn-pointer from `block_self.sx_fn`,
// prepends `block_self.sx_env`, and tail-calls the sx closure.
// 2. Returns a stack Block literal whose `invoke` slot points at
// the nested trampoline via `xx @__invoke`.
//
// The host impl wraps the emitted source via `#insert
// build_block_convert($args, $R);`. Per-call-shape monomorphisation
// of the impl body re-runs this builder with the concrete types
// bound, so each closure shape gets its own dedicated trampoline.
//
// Void return type emits `typed_fn(...)`; non-void emits
// `return typed_fn(...)`.
build_block_convert :: (args: []Type, $ret: Type) -> string {
ret_name := type_name(ret);
code := "__invoke :: (block_self: *Block";
i : s64 = 0;
while i < args.len {
code = concat(code, ", arg");
code = concat(code, int_to_string(i));
code = concat(code, ": ");
code = concat(code, type_name(args[i]));
i = i + 1;
}
code = concat(code, ") -> ");
code = concat(code, ret_name);
code = concat(code, " callconv(.c) { typed_fn : (*void");
i = 0;
while i < args.len {
code = concat(code, ", ");
code = concat(code, type_name(args[i]));
i = i + 1;
}
code = concat(code, ") -> ");
code = concat(code, ret_name);
code = concat(code, " = xx block_self.sx_fn; ");
if ret_name == "void" {
code = concat(code, "typed_fn(block_self.sx_env");
} else {
code = concat(code, "return typed_fn(block_self.sx_env");
}
i = 0;
while i < args.len {
code = concat(code, ", arg");
code = concat(code, int_to_string(i));
i = i + 1;
}
code = concat(code, "); } ");
code = concat(code, "return .{ ");
code = concat(code, "isa = @_NSConcreteStackBlock, ");
code = concat(code, "flags = 0, ");
code = concat(code, "reserved = 0, ");
code = concat(code, "invoke = xx @__invoke, ");
code = concat(code, "descriptor = xx @__sx_block_descriptor, ");
code = concat(code, "sx_env = self.env, ");
code = concat(code, "sx_fn = self.fn_ptr, ");
code = concat(code, "};");
return code;
}

View File

@@ -0,0 +1,200 @@
// OpenGL 3.3 Core — runtime-loaded function pointers
// No #library needed — caller provides a loader (e.g. SDL_GL_GetProcAddress)
// Constants
GL_FALSE :s32: 0;
GL_TRUE :s32: 1;
GL_DEPTH_TEST :u32: 0x0B71;
GL_CULL_FACE :u32: 0x0B44;
GL_BLEND :u32: 0x0BE2;
GL_TRIANGLES :u32: 4;
GL_LINES :u32: 1;
GL_FLOAT :u32: 0x1406;
GL_UNSIGNED_INT :u32: 0x1405;
GL_VERTEX_SHADER :u32: 0x8B31;
GL_FRAGMENT_SHADER :u32: 0x8B30;
GL_COMPILE_STATUS :u32: 0x8B81;
GL_LINK_STATUS :u32: 0x8B82;
GL_ARRAY_BUFFER :u32: 0x8892;
GL_ELEMENT_ARRAY_BUFFER :u32: 0x8893;
GL_STATIC_DRAW :u32: 0x88E4;
GL_COLOR_BUFFER_BIT :u32: 0x4000;
GL_DEPTH_BUFFER_BIT :u32: 0x0100;
GL_FRONT_AND_BACK :u32: 0x0408;
GL_LINE :u32: 0x1B01;
GL_FILL :u32: 0x1B02;
// Function pointer variables (mutable, loaded at runtime)
glClearColor : (f32, f32, f32, f32) -> void callconv(.c) = ---;
glClear : (u32) -> void callconv(.c) = ---;
glEnable : (u32) -> void callconv(.c) = ---;
glDisable : (u32) -> void callconv(.c) = ---;
glViewport : (s32, s32, s32, s32) -> void callconv(.c) = ---;
glFlush : () -> void callconv(.c) = ---;
glDrawArrays : (u32, s32, s32) -> void callconv(.c) = ---;
glPolygonMode : (u32, u32) -> void callconv(.c) = ---;
glLineWidth : (f32) -> void callconv(.c) = ---;
glCreateShader : (u32) -> u32 callconv(.c) = ---;
glShaderSource : (u32, s32, *[:0]u8, *s32) -> void callconv(.c) = ---;
glCompileShader : (u32) -> void callconv(.c) = ---;
glGetShaderiv : (u32, u32, *s32) -> void callconv(.c) = ---;
glGetShaderInfoLog : (u32, s32, *s32, [*]u8) -> void callconv(.c) = ---;
glCreateProgram : () -> u32 callconv(.c) = ---;
glAttachShader : (u32, u32) -> void callconv(.c) = ---;
glLinkProgram : (u32) -> void callconv(.c) = ---;
glGetProgramiv : (u32, u32, *s32) -> void callconv(.c) = ---;
glGetProgramInfoLog : (u32, s32, *s32, [*]u8) -> void callconv(.c) = ---;
glUseProgram : (u32) -> void callconv(.c) = ---;
glDeleteShader : (u32) -> void callconv(.c) = ---;
glGenVertexArrays : (s32, *u32) -> void callconv(.c) = ---;
glGenBuffers : (s32, *u32) -> void callconv(.c) = ---;
glBindVertexArray : (u32) -> void callconv(.c) = ---;
glBindBuffer : (u32, u32) -> void callconv(.c) = ---;
glBufferData : (u32, isize, *void, u32) -> void callconv(.c) = ---;
glVertexAttribPointer : (u32, s32, u32, u8, s32, *void) -> void callconv(.c) = ---;
glEnableVertexAttribArray : (u32) -> void callconv(.c) = ---;
glGetUniformLocation : (u32, [*]u8) -> s32 callconv(.c) = ---;
glUniformMatrix4fv : (s32, s32, u8, [*]f32) -> void callconv(.c) = ---;
glUniform3f : (s32, f32, f32, f32) -> void callconv(.c) = ---;
glDepthFunc : (u32) -> void callconv(.c) = ---;
glUniform1f : (s32, f32) -> void callconv(.c) = ---;
GL_LESS :u32: 0x0201;
GL_LEQUAL :u32: 0x0203;
GL_SCISSOR_TEST :u32: 0x0C11;
GL_DYNAMIC_DRAW :u32: 0x88E8;
GL_TEXTURE_2D :u32: 0x0DE1;
GL_TEXTURE_MIN_FILTER :u32: 0x2801;
GL_TEXTURE_MAG_FILTER :u32: 0x2800;
GL_NEAREST :u32: 0x2600;
GL_RGBA :u32: 0x1908;
GL_UNSIGNED_BYTE :u32: 0x1401;
GL_SRC_ALPHA :u32: 0x0302;
GL_ONE_MINUS_SRC_ALPHA :u32: 0x0303;
GL_TEXTURE0 :u32: 0x84C0;
GL_LINEAR :u32: 0x2601;
GL_RED :u32: 0x1903;
GL_R8 :u32: 0x8229;
GL_UNPACK_ALIGNMENT :u32: 0x0CF5;
glScissor : (s32, s32, s32, s32) -> void callconv(.c) = ---;
glBufferSubData : (u32, isize, isize, *void) -> void callconv(.c) = ---;
glGenTextures : (s32, *u32) -> void callconv(.c) = ---;
glBindTexture : (u32, u32) -> void callconv(.c) = ---;
glTexImage2D : (u32, s32, s32, s32, s32, s32, u32, u32, *void) -> void callconv(.c) = ---;
glTexParameteri : (u32, u32, s32) -> void callconv(.c) = ---;
glBlendFunc : (u32, u32) -> void callconv(.c) = ---;
glReadPixels : (s32, s32, s32, s32, u32, u32, *void) -> void callconv(.c) = ---;
glActiveTexture : (u32) -> void callconv(.c) = ---;
glUniform1i : (s32, s32) -> void callconv(.c) = ---;
glPixelStorei : (u32, s32) -> void callconv(.c) = ---;
glTexSubImage2D : (u32, s32, s32, s32, s32, s32, u32, u32, *void) -> void callconv(.c) = ---;
glDeleteTextures : (s32, *u32) -> void callconv(.c) = ---;
glGenFramebuffers : (s32, *u32) -> void callconv(.c) = ---;
glGenRenderbuffers : (s32, *u32) -> void callconv(.c) = ---;
glBindFramebuffer : (u32, u32) -> void callconv(.c) = ---;
glBindRenderbuffer : (u32, u32) -> void callconv(.c) = ---;
glFramebufferRenderbuffer : (u32, u32, u32, u32) -> void callconv(.c) = ---;
glGetRenderbufferParameteriv : (u32, u32, *s32) -> void callconv(.c) = ---;
glCheckFramebufferStatus : (u32) -> u32 callconv(.c) = ---;
GL_TEXTURE_WRAP_S :u32: 0x2802;
GL_TEXTURE_WRAP_T :u32: 0x2803;
GL_CLAMP_TO_EDGE :u32: 0x812F;
// Loader: call once after creating GL context
// Pass in a proc loader (e.g. SDL_GL_GetProcAddress)
load_gl :: (get_proc: ([*]u8) -> *void callconv(.c)) {
glClearColor = xx get_proc("glClearColor");
glClear = xx get_proc("glClear");
glEnable = xx get_proc("glEnable");
glDisable = xx get_proc("glDisable");
glViewport = xx get_proc("glViewport");
glFlush = xx get_proc("glFlush");
glDrawArrays = xx get_proc("glDrawArrays");
glPolygonMode = xx get_proc("glPolygonMode");
glLineWidth = xx get_proc("glLineWidth");
glCreateShader = xx get_proc("glCreateShader");
glShaderSource = xx get_proc("glShaderSource");
glCompileShader = xx get_proc("glCompileShader");
glGetShaderiv = xx get_proc("glGetShaderiv");
glGetShaderInfoLog = xx get_proc("glGetShaderInfoLog");
glCreateProgram = xx get_proc("glCreateProgram");
glAttachShader = xx get_proc("glAttachShader");
glLinkProgram = xx get_proc("glLinkProgram");
glGetProgramiv = xx get_proc("glGetProgramiv");
glGetProgramInfoLog = xx get_proc("glGetProgramInfoLog");
glUseProgram = xx get_proc("glUseProgram");
glDeleteShader = xx get_proc("glDeleteShader");
glGenVertexArrays = xx get_proc("glGenVertexArrays");
glGenBuffers = xx get_proc("glGenBuffers");
glBindVertexArray = xx get_proc("glBindVertexArray");
glBindBuffer = xx get_proc("glBindBuffer");
glBufferData = xx get_proc("glBufferData");
glVertexAttribPointer = xx get_proc("glVertexAttribPointer");
glEnableVertexAttribArray = xx get_proc("glEnableVertexAttribArray");
glGetUniformLocation = xx get_proc("glGetUniformLocation");
glUniformMatrix4fv = xx get_proc("glUniformMatrix4fv");
glUniform3f = xx get_proc("glUniform3f");
glDepthFunc = xx get_proc("glDepthFunc");
glUniform1f = xx get_proc("glUniform1f");
glScissor = xx get_proc("glScissor");
glBufferSubData = xx get_proc("glBufferSubData");
glGenTextures = xx get_proc("glGenTextures");
glBindTexture = xx get_proc("glBindTexture");
glTexImage2D = xx get_proc("glTexImage2D");
glTexParameteri = xx get_proc("glTexParameteri");
glBlendFunc = xx get_proc("glBlendFunc");
glReadPixels = xx get_proc("glReadPixels");
glActiveTexture = xx get_proc("glActiveTexture");
glUniform1i = xx get_proc("glUniform1i");
glPixelStorei = xx get_proc("glPixelStorei");
glTexSubImage2D = xx get_proc("glTexSubImage2D");
glDeleteTextures = xx get_proc("glDeleteTextures");
glGenFramebuffers = xx get_proc("glGenFramebuffers");
glGenRenderbuffers = xx get_proc("glGenRenderbuffers");
glBindFramebuffer = xx get_proc("glBindFramebuffer");
glBindRenderbuffer = xx get_proc("glBindRenderbuffer");
glFramebufferRenderbuffer = xx get_proc("glFramebufferRenderbuffer");
glGetRenderbufferParameteriv = xx get_proc("glGetRenderbufferParameteriv");
glCheckFramebufferStatus = xx get_proc("glCheckFramebufferStatus");
}
// --- Shader utilities ---
create_program :: (vert_src: [:0]u8, frag_src: [:0]u8) -> u32 {
vs := compile_shader(GL_VERTEX_SHADER, vert_src);
fs := compile_shader(GL_FRAGMENT_SHADER, frag_src);
prog := glCreateProgram();
glAttachShader(prog, vs);
glAttachShader(prog, fs);
glLinkProgram(prog);
status : s32 = 0;
glGetProgramiv(prog, GL_LINK_STATUS, @status);
if status == GL_FALSE {
log_buf: [512]u8 = ---;
glGetProgramInfoLog(prog, 512, null, log_buf);
}
glDeleteShader(vs);
glDeleteShader(fs);
return prog;
}
compile_shader :: (shader_type : u32, source: [:0]u8) -> u32 {
shader := glCreateShader(shader_type);
glShaderSource(shader, 1, source, null);
glCompileShader(shader);
status : s32 = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, @status);
if status == GL_FALSE {
log_buf : [512]u8 = ---;
glGetShaderInfoLog(shader, 512, null, log_buf);
}
return shader;
}

View File

@@ -0,0 +1,17 @@
raylib :: #library "raylib";
Color :: struct {
r, g, b, a: u8;
}
Vector2 :: struct {
x, y: f32;
}
InitWindow :: (width: s32, height: s32, title: [:0]u8) -> void #foreign raylib;
CloseWindow :: () -> void #foreign raylib;
WindowShouldClose :: () -> bool #foreign raylib;
BeginDrawing :: () -> void #foreign raylib;
EndDrawing :: () -> void #foreign raylib;
ClearBackground :: (color: Color) -> void #foreign raylib;
DrawTriangle :: (v1: Vector2, v2: Vector2, v3: Vector2, color: Color) -> void #foreign raylib;

353
library/modules/ffi/sdl3.sx Normal file
View File

@@ -0,0 +1,353 @@
// SDL3 bindings. Linking is per-target (handled in build.sx):
// SDL_InitFlags
SDL_INIT_VIDEO :u32: 0x20;
// SDL_WindowFlags
SDL_WINDOW_OPENGL :u64: 0x2;
SDL_WINDOW_RESIZABLE :u64: 0x20;
SDL_WINDOW_HIGH_PIXEL_DENSITY :u64: 0x2000;
// SDL_GLAttr (enum starting at 0)
SDL_GL_DOUBLEBUFFER :s32: 5;
SDL_GL_DEPTH_SIZE :s32: 6;
SDL_GL_CONTEXT_MAJOR_VERSION :s32: 17;
SDL_GL_CONTEXT_MINOR_VERSION :s32: 18;
SDL_GL_CONTEXT_FLAGS :s32: 19;
SDL_GL_CONTEXT_PROFILE_MASK :s32: 20;
// SDL_GLProfile
SDL_GL_CONTEXT_PROFILE_CORE :s32: 0x1;
SDL_GL_CONTEXT_PROFILE_ES :s32: 0x4;
// SDL_GLContextFlag
SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG :s32: 0x2;
// SDL_Keycode — virtual key codes (layout-dependent)
SDL_Keycode :: enum u32 {
// Common
unknown :: 0x00;
return_key :: 0x0D;
escape :: 0x1B;
backspace :: 0x08;
tab :: 0x09;
space :: 0x20;
delete_key :: 0x7F;
// Punctuation
exclaim :: 0x21;
double_quote :: 0x22;
hash :: 0x23;
dollar :: 0x24;
percent :: 0x25;
ampersand :: 0x26;
apostrophe :: 0x27;
leftparen :: 0x28;
rightparen :: 0x29;
asterisk :: 0x2A;
plus :: 0x2B;
comma :: 0x2C;
minus :: 0x2D;
period :: 0x2E;
slash :: 0x2F;
colon :: 0x3A;
semicolon :: 0x3B;
less :: 0x3C;
equals :: 0x3D;
greater :: 0x3E;
question :: 0x3F;
at :: 0x40;
leftbracket :: 0x5B;
backslash :: 0x5C;
rightbracket :: 0x5D;
caret :: 0x5E;
underscore :: 0x5F;
grave :: 0x60;
leftbrace :: 0x7B;
pipe :: 0x7C;
rightbrace :: 0x7D;
tilde :: 0x7E;
plusminus :: 0xB1;
// Numbers
key_0 :: 0x30;
key_1 :: 0x31;
key_2 :: 0x32;
key_3 :: 0x33;
key_4 :: 0x34;
key_5 :: 0x35;
key_6 :: 0x36;
key_7 :: 0x37;
key_8 :: 0x38;
key_9 :: 0x39;
// Letters
a :: 0x61;
b :: 0x62;
c :: 0x63;
d :: 0x64;
e :: 0x65;
f :: 0x66;
g :: 0x67;
h :: 0x68;
i :: 0x69;
j :: 0x6A;
k :: 0x6B;
l :: 0x6C;
m :: 0x6D;
n :: 0x6E;
o :: 0x6F;
p :: 0x70;
q :: 0x71;
r :: 0x72;
s :: 0x73;
t :: 0x74;
u :: 0x75;
v :: 0x76;
w :: 0x77;
x :: 0x78;
y :: 0x79;
z :: 0x7A;
// Function keys
f1 :: 0x4000003A;
f2 :: 0x4000003B;
f3 :: 0x4000003C;
f4 :: 0x4000003D;
f5 :: 0x4000003E;
f6 :: 0x4000003F;
f7 :: 0x40000040;
f8 :: 0x40000041;
f9 :: 0x40000042;
f10 :: 0x40000043;
f11 :: 0x40000044;
f12 :: 0x40000045;
f13 :: 0x40000068;
f14 :: 0x40000069;
f15 :: 0x4000006A;
f16 :: 0x4000006B;
f17 :: 0x4000006C;
f18 :: 0x4000006D;
f19 :: 0x4000006E;
f20 :: 0x4000006F;
f21 :: 0x40000070;
f22 :: 0x40000071;
f23 :: 0x40000072;
f24 :: 0x40000073;
// Navigation
capslock :: 0x40000039;
printscreen :: 0x40000046;
scrolllock :: 0x40000047;
pause :: 0x40000048;
insert :: 0x40000049;
home :: 0x4000004A;
pageup :: 0x4000004B;
end :: 0x4000004D;
pagedown :: 0x4000004E;
right :: 0x4000004F;
left :: 0x40000050;
down :: 0x40000051;
up :: 0x40000052;
// Keypad
numlock :: 0x40000053;
kp_divide :: 0x40000054;
kp_multiply :: 0x40000055;
kp_minus :: 0x40000056;
kp_plus :: 0x40000057;
kp_enter :: 0x40000058;
kp_1 :: 0x40000059;
kp_2 :: 0x4000005A;
kp_3 :: 0x4000005B;
kp_4 :: 0x4000005C;
kp_5 :: 0x4000005D;
kp_6 :: 0x4000005E;
kp_7 :: 0x4000005F;
kp_8 :: 0x40000060;
kp_9 :: 0x40000061;
kp_0 :: 0x40000062;
kp_period :: 0x40000063;
kp_equals :: 0x40000067;
kp_comma :: 0x40000085;
// Modifiers
lctrl :: 0x400000E0;
lshift :: 0x400000E1;
lalt :: 0x400000E2;
lgui :: 0x400000E3;
rctrl :: 0x400000E4;
rshift :: 0x400000E5;
ralt :: 0x400000E6;
rgui :: 0x400000E7;
mode :: 0x40000101;
// Editing
undo :: 0x4000007A;
cut :: 0x4000007B;
copy :: 0x4000007C;
paste :: 0x4000007D;
find :: 0x4000007E;
// Media
mute :: 0x4000007F;
volumeup :: 0x40000080;
volumedown :: 0x40000081;
media_play :: 0x40000106;
media_pause :: 0x40000107;
media_fast_forward :: 0x40000109;
media_rewind :: 0x4000010A;
media_next_track :: 0x4000010B;
media_previous_track :: 0x4000010C;
media_stop :: 0x4000010D;
media_eject :: 0x4000010E;
media_play_pause :: 0x4000010F;
// System
application :: 0x40000065;
power :: 0x40000066;
execute :: 0x40000074;
help :: 0x40000075;
menu :: 0x40000076;
select :: 0x40000077;
sleep :: 0x40000102;
wake :: 0x40000103;
}
// Event payload structs — match SDL3 layout from byte 4 onward (after the u32 type tag)
// Common header: reserved (u32), timestamp (u64)
SDL_WindowData :: struct {
timestamp: u64; // event time in nanoseconds
window_id: u32;
data1: s32; // event-dependent: x position for moved, width for resized
data2: s32; // event-dependent: y position for moved, height for resized
}
SDL_Keymod :: enum flags u16 {
lshift :: 0b0000_0000_0000_0001; // left Shift
rshift :: 0b0000_0000_0000_0010; // right Shift
level5 :: 0b0000_0000_0000_0100; // Level 5 Shift
lctrl :: 0b0000_0000_0100_0000; // left Ctrl
rctrl :: 0b0000_0000_1000_0000; // right Ctrl
lalt :: 0b0000_0001_0000_0000; // left Alt
ralt :: 0b0000_0010_0000_0000; // right Alt
lgui :: 0b0000_0100_0000_0000; // left GUI (Windows/Cmd key)
rgui :: 0b0000_1000_0000_0000; // right GUI (Windows/Cmd key)
num :: 0b0001_0000_0000_0000; // Num Lock
caps :: 0b0010_0000_0000_0000; // Caps Lock
mode :: 0b0100_0000_0000_0000; // AltGr
scroll :: 0b1000_0000_0000_0000; // Scroll Lock
}
SDL_KeyData :: struct {
timestamp: u64; // event time in nanoseconds
window_id: u32; // window with keyboard focus
which: u32; // keyboard instance id, 0 if unknown or virtual
scancode: u32; // physical key code (layout-independent)
key: SDL_Keycode; // virtual key code (layout-dependent)
mod: SDL_Keymod; // active modifier keys
raw: u16; // platform-specific scancode
down: u8; // 1 if pressed, 0 if released
repeat: u8; // 1 if this is a key repeat
}
SDL_MouseMotionData :: struct {
timestamp: u64; // event time in nanoseconds
window_id: u32; // window with mouse focus
which: u32; // mouse instance id, 0 for touch events
state: u32; // button state bitmask (bit 0 = left, 1 = middle, 2 = right)
x: f32; // x position relative to window
y: f32; // y position relative to window
xrel: f32; // relative motion in x
yrel: f32; // relative motion in y
}
SDL_MouseButtonData :: struct {
timestamp: u64; // event time in nanoseconds
window_id: u32; // window with mouse focus
which: u32; // mouse instance id, 0 for touch events
button: u8; // button index (1 = left, 2 = middle, 3 = right)
down: u8; // 1 if pressed, 0 if released
clicks: u8; // 1 for single-click, 2 for double-click, etc.
_: u8;
x: f32; // x position relative to window
y: f32; // y position relative to window
}
SDL_MouseWheelData :: struct {
timestamp: u64; // event time in nanoseconds
window_id: u32; // window with mouse focus
which: u32; // mouse instance id
x: f32; // horizontal scroll (positive = right)
y: f32; // vertical scroll (positive = away from user)
direction: u32; // 0 = normal, 1 = flipped (multiply by -1 to normalize)
mouse_x: f32; // mouse x position relative to window
mouse_y: f32; // mouse y position relative to window
}
SDL_Event :: enum struct { tag: u32; _: u32; payload: [30]u32; } {
none :: 0;
// Application
quit :: 0x100;
// Window
window_shown :: 0x202: SDL_WindowData;
window_hidden :: 0x203: SDL_WindowData;
window_exposed :: 0x204: SDL_WindowData;
window_moved :: 0x205: SDL_WindowData;
window_resized :: 0x206: SDL_WindowData;
window_minimized :: 0x209: SDL_WindowData;
window_maximized :: 0x20A: SDL_WindowData;
window_restored :: 0x20B: SDL_WindowData;
window_mouse_enter :: 0x20C: SDL_WindowData;
window_mouse_leave :: 0x20D: SDL_WindowData;
window_focus_gained :: 0x20E: SDL_WindowData;
window_focus_lost :: 0x20F: SDL_WindowData;
window_close_requested :: 0x210: SDL_WindowData;
window_destroyed :: 0x219: SDL_WindowData;
// Keyboard
key_down :: 0x300: SDL_KeyData;
key_up :: 0x301: SDL_KeyData;
// Mouse
mouse_motion :: 0x400: SDL_MouseMotionData;
mouse_button_down :: 0x401: SDL_MouseButtonData;
mouse_button_up :: 0x402: SDL_MouseButtonData;
mouse_wheel :: 0x403: SDL_MouseWheelData;
}
// Functions
SDL_Init :: (flags: u32) -> bool #foreign;
SDL_Quit :: () -> void #foreign;
SDL_CreateWindow :: (title: [:0]u8, w: s32, h: s32, flags: u64) -> *void #foreign;
SDL_DestroyWindow :: (window: *void) -> void #foreign;
SDL_GL_SetAttribute :: (attr: s32, value: s32) -> bool #foreign;
SDL_GL_CreateContext :: (window: *void) -> *void #foreign;
SDL_GL_DestroyContext :: (context: *void) -> bool #foreign;
SDL_GL_MakeCurrent :: (window: *void, context: *void) -> bool #foreign;
SDL_GL_SwapWindow :: (window: *void) -> bool #foreign;
SDL_GL_SetSwapInterval :: (interval: s32) -> bool #foreign;
SDL_GL_GetProcAddress :: (proc: [:0]u8) -> *void #foreign;
SDL_PollEvent :: (event: *SDL_Event) -> bool #foreign;
SDL_AddEventWatch :: (filter: *void, userdata: *void) -> bool #foreign;
SDL_GetTicks :: () -> u64 #foreign;
SDL_GetPerformanceCounter :: () -> u64 #foreign;
SDL_GetPerformanceFrequency :: () -> u64 #foreign;
SDL_Delay :: (ms: u32) -> void #foreign;
SDL_GetWindowDisplayScale :: (window: *void) -> f32 #foreign;
SDL_GetWindowSize :: (window: *void, w: *s32, h: *s32) -> bool #foreign;
SDL_SetWindowSize :: (window: *void, w: s32, h: s32) -> bool #foreign;
SDL_GetWindowSizeInPixels :: (window: *void, w: *s32, h: *s32) -> bool #foreign;
SDL_Rect :: struct {
x: s32;
y: s32;
w: s32;
h: s32;
}
SDL_GetPrimaryDisplay :: () -> u32 #foreign;
SDL_GetDisplayUsableBounds :: (display_id: u32, rect: *SDL_Rect) -> bool #foreign;

View File

@@ -0,0 +1,7 @@
#import c {
#include "vendors/stb_image/stb_image.h";
#source "vendors/stb_image/stb_image_impl.c";
#include "vendors/stb_image/stb_image_write.h";
#source "vendors/stb_image/stb_image_write_impl.c";
};

View File

@@ -0,0 +1,10 @@
#import c {
#include "vendors/stb_truetype/stb_truetype.h";
#source "vendors/stb_truetype/stb_truetype_impl.c";
#include "vendors/file_utils/file_utils.h";
#source "vendors/file_utils/file_utils.c";
#include "vendors/kb_text_shape/kbts_api.h";
#source "vendors/kb_text_shape/kb_text_shape_impl.c";
};

View File

@@ -0,0 +1,4 @@
libc :: #library "c";
emscripten_set_main_loop :: (func: *void, fps: s32, sim_infinite: s32) #foreign libc;
emscripten_run_script_int :: (script: [:0]u8) -> s32 #foreign libc;