Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ pub fn build(b: *std.Build) void {
if (target.result.os.tag != .windows) {
egl_lib.root_module.linkSystemLibrary("dl", .{});
}
// macOS surface creation resolves libobjc.A.dylib at runtime via
// std.DynLib (see egl.zig's `macos` helper), so no link-time
// dependency/SDK is required here, matching how libvulkan is loaded.
egl_lib.root_module.linkLibrary(lib);
b.installArtifact(egl_lib);

Expand Down
1 change: 1 addition & 0 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@
"build.zig",
"build.zig.zon",
"src",
"tools",
},
}
19 changes: 17 additions & 2 deletions src/egl/api.zig
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ const EGLBoolean = egl.EGLBoolean;
const EGLint = egl.EGLint;
const EGLenum = egl.EGLenum;

const builtin = @import("builtin");

fn guessPlatform() egl.Platform {
if (builtin.os.tag == .windows) return .win32;
if (builtin.os.tag == .macos) return .macos;

const io = std.Io.Threaded.global_single_threaded;
var env = io.environ.process_environ.createMap(std.heap.page_allocator) catch unreachable;
defer env.deinit();
Expand Down Expand Up @@ -80,6 +85,8 @@ pub export fn eglInitialize(display: ?*egl.Display, major: ?*EGLint, minor: ?*EG
const want: []const []const u8 = switch (d.platform) {
.x11 => &.{ "VK_KHR_surface", "VK_KHR_xlib_surface" },
.wayland => &.{ "VK_KHR_surface", "VK_KHR_wayland_surface" },
.win32 => &.{ "VK_KHR_surface", "VK_KHR_win32_surface" },
.macos => &.{ "VK_KHR_surface", "VK_EXT_metal_surface" },
};
var exts: [4][*:0]const u8 = undefined;
var n: usize = 0;
Expand Down Expand Up @@ -320,6 +327,16 @@ pub export fn eglCreateWindowSurface(display: ?*egl.Display, config: ?*egl.Confi
.surface = wl_surface,
}, null, &handle);
},
.win32 => egl.vkCreateWin32SurfaceKHR(instance, &.{
.hinstance = egl.win32.hinstanceForHwnd(window),
.hwnd = window,
}, null, &handle),
.macos => blk: {
const layer = egl.macos.metalLayerForWindow(window) orelse break :blk -1;
break :blk egl.vkCreateMetalSurfaceEXT(instance, &.{
.pLayer = layer,
}, null, &handle);
},
};
if (res != 0 or handle == 0) {
_ = egl.setError(egl.EGL_BAD_NATIVE_WINDOW);
Expand Down Expand Up @@ -566,8 +583,6 @@ pub export fn eglReleaseTexImage(display: ?*egl.Display, surface: ?*egl.Surface,
return egl.setError(egl.EGL_BAD_MATCH);
}

const builtin = @import("builtin");

const win = if (builtin.os.tag == .windows) struct {
const windows = std.os.windows;
extern "kernel32" fn GetModuleHandleA(lpModuleName: ?[*:0]const u8) callconv(.winapi) ?windows.HMODULE;
Expand Down
96 changes: 94 additions & 2 deletions src/egl/egl.zig
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,13 @@ pub const EGL_HEIGHT: c_int = 0x3056;
pub const EGL_PLATFORM_X11_KHR: EGLenum = 0x31D5;
pub const EGL_PLATFORM_XCB_KHR: EGLenum = 0x31DC;
pub const EGL_PLATFORM_WAYLAND_KHR: EGLenum = 0x31D8;
// Not real Khronos platform tokens (no official ANGLE-style win32/cocoa KHR
// platform exists in core EGL); used only internally by guessPlatform().
pub const EGL_PLATFORM_WIN32_ANGLE: EGLenum = 0x9001;
pub const EGL_PLATFORM_COCOA_ANGLE: EGLenum = 0x9002;

// ---- platform glue ----
pub const Platform = enum { x11, wayland };
pub const Platform = enum { x11, wayland, win32, macos };

// struct wl_egl_window from wayland-egl-backend.h (driver-side ABI).
pub const WlEglWindow = extern struct {
Expand All @@ -94,7 +98,7 @@ pub const WlEglWindow = extern struct {
surface: ?*anyopaque,
};

// Minimal Vulkan WSI declarations (avoid pulling in X11/Wayland headers).
// Minimal Vulkan WSI declarations (avoid pulling in X11/Wayland/Win32/Metal headers).
pub const VkInstance = *opaque {};
pub const VkSurfaceKHR = usize; // non-dispatchable handle
pub const VkResult = c_int;
Expand All @@ -120,9 +124,26 @@ pub const VkWaylandSurfaceCreateInfoKHR = extern struct {
surface: *anyopaque,
};

pub const VkWin32SurfaceCreateInfoKHR = extern struct {
sType: c_int = 1000009000, // VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR
pNext: ?*const anyopaque = null,
flags: u32 = 0,
hinstance: *anyopaque,
hwnd: *anyopaque,
};

pub const VkMetalSurfaceCreateInfoEXT = extern struct {
sType: c_int = 1000217000, // VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT
pNext: ?*const anyopaque = null,
flags: u32 = 0,
pLayer: *const anyopaque, // const CAMetalLayer*
};

pub const PFN_vkEnumerateInstanceExtensionProperties = *const fn (pLayerName: ?[*:0]const u8, pPropertyCount: *u32, pProperties: ?[*]VkExtensionProperties) callconv(.c) VkResult;
pub const PFN_vkCreateXlibSurfaceKHR = *const fn (instance: VkInstance, pCreateInfo: *const VkXlibSurfaceCreateInfoKHR, pAllocator: ?*const anyopaque, pSurface: *VkSurfaceKHR) callconv(.c) VkResult;
pub const PFN_vkCreateWaylandSurfaceKHR = *const fn (instance: VkInstance, pCreateInfo: *const VkWaylandSurfaceCreateInfoKHR, pAllocator: ?*const anyopaque, pSurface: *VkSurfaceKHR) callconv(.c) VkResult;
pub const PFN_vkCreateWin32SurfaceKHR = *const fn (instance: VkInstance, pCreateInfo: *const VkWin32SurfaceCreateInfoKHR, pAllocator: ?*const anyopaque, pSurface: *VkSurfaceKHR) callconv(.c) VkResult;
pub const PFN_vkCreateMetalSurfaceEXT = *const fn (instance: VkInstance, pCreateInfo: *const VkMetalSurfaceCreateInfoEXT, pAllocator: ?*const anyopaque, pSurface: *VkSurfaceKHR) callconv(.c) VkResult;
pub const PFN_vkDestroySurfaceKHR = *const fn (instance: VkInstance, surface: VkSurfaceKHR, pAllocator: ?*const anyopaque) callconv(.c) void;
pub const PFN_vkGetInstanceProcAddr = *const fn (instance: ?VkInstance, name: [*:0]const u8) callconv(.c) ?*const anyopaque;

Expand Down Expand Up @@ -171,11 +192,82 @@ pub fn vkCreateWaylandSurfaceKHR(instance: VkInstance, pCreateInfo: *const VkWay
return f(instance, pCreateInfo, pAllocator, pSurface);
}

pub fn vkCreateWin32SurfaceKHR(instance: VkInstance, pCreateInfo: *const VkWin32SurfaceCreateInfoKHR, pAllocator: ?*const anyopaque, pSurface: *VkSurfaceKHR) VkResult {
const f = resolve(PFN_vkCreateWin32SurfaceKHR, instance, "vkCreateWin32SurfaceKHR") orelse return -1;
return f(instance, pCreateInfo, pAllocator, pSurface);
}

pub fn vkCreateMetalSurfaceEXT(instance: VkInstance, pCreateInfo: *const VkMetalSurfaceCreateInfoEXT, pAllocator: ?*const anyopaque, pSurface: *VkSurfaceKHR) VkResult {
const f = resolve(PFN_vkCreateMetalSurfaceEXT, instance, "vkCreateMetalSurfaceEXT") orelse return -1;
return f(instance, pCreateInfo, pAllocator, pSurface);
}

pub fn vkDestroySurfaceKHR(instance: VkInstance, surface: VkSurfaceKHR, pAllocator: ?*const anyopaque) void {
const f = resolve(PFN_vkDestroySurfaceKHR, instance, "vkDestroySurfaceKHR") orelse return;
f(instance, surface, pAllocator);
}

// ---- platform-specific native-window helpers ----

const builtin = @import("builtin");

/// On Win32, native_window is an HWND; VkWin32SurfaceCreateInfoKHR also
/// wants the owning HINSTANCE, which we recover from the window itself.
pub const win32 = if (builtin.os.tag == .windows) struct {
extern "kernel32" fn GetModuleHandleA(lpModuleName: ?[*:0]const u8) callconv(.winapi) ?*anyopaque;
extern "user32" fn GetWindowLongPtrA(hwnd: *anyopaque, index: c_int) callconv(.winapi) isize;
const GWLP_HINSTANCE: c_int = -6;

pub fn hinstanceForHwnd(hwnd: *anyopaque) *anyopaque {
const v = GetWindowLongPtrA(hwnd, GWLP_HINSTANCE);
if (v != 0) return @ptrFromInt(@as(usize, @bitCast(v)));
return GetModuleHandleA(null) orelse @ptrFromInt(@as(usize, 1));
}
} else struct {
// Unreachable at runtime on non-Windows targets; exists only so the
// .win32 switch prong in eglCreateWindowSurface still type-checks.
pub fn hinstanceForHwnd(hwnd: *anyopaque) *anyopaque {
return hwnd;
}
};

/// On macOS, native_window is expected to be an NSWindow*; we pull its
/// contentView's CALayer via the Objective-C runtime (assumed already a
/// CAMetalLayer, e.g. because the view has wantsLayer=YES and a Metal-backed
/// layer class, as SDL3's Cocoa/Metal window setup provides).
pub const macos = if (builtin.os.tag == .macos) struct {
const SelRegisterName = *const fn (name: [*:0]const u8) callconv(.c) ?*anyopaque;
const MsgSend0 = *const fn (obj: *anyopaque, sel: ?*anyopaque) callconv(.c) ?*anyopaque;

var lib: ?std.DynLib = null;
var sel_registerName_fn: ?SelRegisterName = null;
var objc_msgSend_ptr: ?*const anyopaque = null;

fn ensureLoaded() bool {
if (lib != null) return sel_registerName_fn != null and objc_msgSend_ptr != null;
lib = std.DynLib.open("/usr/lib/libobjc.A.dylib") catch return false;
sel_registerName_fn = lib.?.lookup(SelRegisterName, "sel_registerName");
objc_msgSend_ptr = lib.?.lookup(*const anyopaque, "objc_msgSend");
return sel_registerName_fn != null and objc_msgSend_ptr != null;
}

pub fn metalLayerForWindow(nswindow: *anyopaque) ?*const anyopaque {
if (!ensureLoaded()) return null;
const send: MsgSend0 = @ptrCast(@alignCast(objc_msgSend_ptr.?));
const sel_content_view = sel_registerName_fn.?("contentView") orelse return null;
const view = send(nswindow, sel_content_view) orelse return null;
const sel_layer = sel_registerName_fn.?("layer") orelse return null;
const layer = send(view, sel_layer) orelse return null;
return layer;
}
} else struct {
// Unreachable at runtime on non-macOS targets; exists only so the
// .macos switch prong in eglCreateWindowSurface still type-checks.
pub fn metalLayerForWindow(nswindow: *anyopaque) ?*const anyopaque {
return nswindow;
}
};

// ---- handles ----
pub const Display = struct {
platform: Platform,
Expand Down