diff --git a/build.zig b/build.zig index 5cfd65a..7cb1ea2 100644 --- a/build.zig +++ b/build.zig @@ -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); diff --git a/build.zig.zon b/build.zig.zon index 733683a..492713e 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -21,5 +21,6 @@ "build.zig", "build.zig.zon", "src", + "tools", }, } diff --git a/src/egl/api.zig b/src/egl/api.zig index 5a0da6c..9531db7 100644 --- a/src/egl/api.zig +++ b/src/egl/api.zig @@ -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(); @@ -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; @@ -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); @@ -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; diff --git a/src/egl/egl.zig b/src/egl/egl.zig index 28f6eb3..90e7294 100644 --- a/src/egl/egl.zig +++ b/src/egl/egl.zig @@ -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 { @@ -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; @@ -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; @@ -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,