From 85ddeeed5c396a2c273a9bb2493c84df678b9ca3 Mon Sep 17 00:00:00 2001 From: Stephen Gutekanst Date: Thu, 11 Aug 2022 00:44:06 -0700 Subject: [PATCH] mach: update to latest mach/gpu API Signed-off-by: Stephen Gutekanst --- src/Core.zig | 8 +- src/engine.zig | 2 +- src/platform/c.zig | 5 -- src/platform/native.zig | 174 +++++++++++++--------------------------- src/platform/util.zig | 82 +++++++++---------- src/structs.zig | 4 +- 6 files changed, 100 insertions(+), 175 deletions(-) delete mode 100644 src/platform/c.zig diff --git a/src/Core.zig b/src/Core.zig index 4d571dc4..9156be6a 100644 --- a/src/Core.zig +++ b/src/Core.zig @@ -23,12 +23,12 @@ delta_time: f32 = 0, delta_time_ns: u64 = 0, timer: Timer, -device: gpu.Device, -backend_type: gpu.Adapter.BackendType, -swap_chain: ?gpu.SwapChain, +device: *gpu.Device, +backend_type: gpu.BackendType, +swap_chain: ?*gpu.SwapChain, swap_chain_format: gpu.Texture.Format, -surface: ?gpu.Surface, +surface: ?*gpu.Surface, current_desc: gpu.SwapChain.Descriptor, target_desc: gpu.SwapChain.Descriptor, diff --git a/src/engine.zig b/src/engine.zig index b146b7f7..733b8ad4 100644 --- a/src/engine.zig +++ b/src/engine.zig @@ -8,7 +8,7 @@ pub const ecs = @import("ecs"); pub const module = ecs.Module(.{ .globals = struct { core: *Core, - device: gpu.Device, + device: *gpu.Device, }, }); diff --git a/src/platform/c.zig b/src/platform/c.zig deleted file mode 100644 index 2648fad7..00000000 --- a/src/platform/c.zig +++ /dev/null @@ -1,5 +0,0 @@ -pub const c = @cImport({ - @cInclude("dawn/webgpu.h"); - @cInclude("dawn/dawn_proc.h"); - @cInclude("dawn_native_mach.h"); -}); diff --git a/src/platform/native.zig b/src/platform/native.zig index 9f8b1225..4e65b2ec 100644 --- a/src/platform/native.zig +++ b/src/platform/native.zig @@ -7,7 +7,6 @@ const Core = @import("../Core.zig"); const structs = @import("../structs.zig"); const enums = @import("../enums.zig"); const util = @import("util.zig"); -const c = @import("c.zig").c; const common = @import("common.zig"); comptime { @@ -20,7 +19,7 @@ pub const log_level = if (@hasDecl(App, "log_level")) App.log_level else std.log pub const Platform = struct { window: glfw.Window, - backend_type: gpu.Adapter.BackendType, + backend_type: gpu.BackendType, allocator: std.mem.Allocator, events: EventQueue = .{}, user_ptr: UserPtr = undefined, @@ -34,7 +33,9 @@ pub const Platform = struct { cursors_tried: [@typeInfo(enums.MouseCursor).Enum.fields.len]bool = [_]bool{false} ** @typeInfo(enums.MouseCursor).Enum.fields.len, - native_instance: gpu.NativeInstance, + // TODO: these can be moved to Core + instance: *gpu.Instance, + adapter: *gpu.Adapter, last_cursor_position: structs.WindowPos, @@ -73,131 +74,73 @@ pub const Platform = struct { null, hints, ); + if (backend_type == .opengl) try glfw.makeContextCurrent(window); + if (backend_type == .opengles) try glfw.makeContextCurrent(window); const window_size = try window.getSize(); const framebuffer_size = try window.getFramebufferSize(); - const backend_procs = c.machDawnNativeGetProcs(); - c.dawnProcSetProcs(backend_procs); - - const instance = c.machDawnNativeInstance_init(); - var native_instance = gpu.NativeInstance.wrap(c.machDawnNativeInstance_get(instance).?); - - // Discover e.g. OpenGL adapters. - try util.discoverAdapters(instance, window, backend_type); - - // Request an adapter. - // - // TODO: It would be nice if we could use gpu_interface.waitForAdapter here, however the webgpu.h - // API does not yet have a way to specify what type of backend you want (vulkan, opengl, etc.) - // In theory, I suppose we shouldn't need to and Dawn should just pick the best adapter - but in - // practice if Vulkan is not supported today waitForAdapter/requestAdapter merely generates an error. - // - // const gpu_interface = native_instance.interface(); - // const backend_adapter = switch (gpu_interface.waitForAdapter(&.{ - // .power_preference = .high_performance, - // })) { - // .adapter => |v| v, - // .err => |err| { - // std.debug.print("mach: failed to get adapter: error={} {s}\n", .{ err.code, err.message }); - // std.process.exit(1); - // }, - // }; - const adapters = c.machDawnNativeInstance_getAdapters(instance); - var dawn_adapter: ?c.MachDawnNativeAdapter = null; - var i: usize = 0; - while (i < c.machDawnNativeAdapters_length(adapters)) : (i += 1) { - const adapter = c.machDawnNativeAdapters_index(adapters, i); - const properties = c.machDawnNativeAdapter_getProperties(adapter); - const found_backend_type = @intToEnum(gpu.Adapter.BackendType, c.machDawnNativeAdapterProperties_getBackendType(properties)); - if (found_backend_type == backend_type) { - dawn_adapter = adapter; - break; - } + const instance = gpu.createInstance(null); + if (instance == null) { + std.debug.print("mach: failed to create GPU instance\n", .{}); + std.process.exit(1); } - if (dawn_adapter == null) { - std.debug.print("mach: no matching adapter found for {s}", .{@tagName(backend_type)}); + const surface = util.createSurfaceForWindow(instance.?, window, comptime util.detectGLFWOptions()); + + var response: ?util.RequestAdapterResponse = null; + instance.?.requestAdapter(&gpu.RequestAdapterOptions{ + .compatible_surface = surface, + .power_preference = options.power_preference, + .force_fallback_adapter = false, + }, &response, util.requestAdapterCallback); + if (response.?.status != .success) { + std.debug.print("mach: failed to create GPU adapter: {s}\n", .{response.?.message}); std.debug.print("-> maybe try GPU_BACKEND=opengl ?\n", .{}); std.process.exit(1); } - std.debug.assert(dawn_adapter != null); - const backend_adapter = gpu.NativeInstance.fromWGPUAdapter(c.machDawnNativeAdapter_get(dawn_adapter.?).?); // Print which adapter we are going to use. - const props = backend_adapter.properties; + var props: gpu.Adapter.Properties = undefined; + response.?.adapter.getProperties(&props); std.debug.print("mach: found {s} backend on {s} adapter: {s}, {s}\n", .{ - gpu.Adapter.backendTypeName(props.backend_type), - gpu.Adapter.typeName(props.adapter_type), + props.backend_type.name(), + props.adapter_type.name(), props.name, props.driver_description, }); - const device = switch (backend_adapter.waitForDevice(&.{ - .required_features = options.required_features, - .required_limits = options.required_limits, - })) { - .device => |v| v, - .err => |err| { - // TODO: return a proper error type - std.debug.print("mach: failed to get device: error={} {s}\n", .{ err.code, err.message }); - std.process.exit(1); - }, - }; - - // If targeting OpenGL, we can't use the newer WGPUSurface API. Instead, we need to use the - // older Dawn-specific API. https://bugs.chromium.org/p/dawn/issues/detail?id=269&q=surface&can=2 - const use_legacy_api = backend_type == .opengl or backend_type == .opengles; - var descriptor: gpu.SwapChain.Descriptor = undefined; - var swap_chain: ?gpu.SwapChain = null; - var swap_chain_format: gpu.Texture.Format = undefined; - var surface: ?gpu.Surface = null; - if (!use_legacy_api) { - swap_chain_format = .bgra8_unorm; - descriptor = .{ - .label = "basic swap chain", - .usage = .{ .render_attachment = true }, - .format = swap_chain_format, - .width = framebuffer_size.width, - .height = framebuffer_size.height, - .present_mode = switch (options.vsync) { - .none => .immediate, - .double => .fifo, - .triple => .mailbox, - }, - .implementation = 0, - }; - surface = util.createSurfaceForWindow( - &native_instance, - window, - comptime util.detectGLFWOptions(), - ); - } else { - const binding = c.machUtilsCreateBinding(@enumToInt(backend_type), @ptrCast(*c.GLFWwindow, window.handle), @ptrCast(c.WGPUDevice, device.ptr)); - if (binding == null) { - @panic("failed to create Dawn backend binding"); - } - descriptor = std.mem.zeroes(gpu.SwapChain.Descriptor); - descriptor.implementation = c.machUtilsBackendBinding_getSwapChainImplementation(binding); - swap_chain = device.nativeCreateSwapChain(null, &descriptor); - - swap_chain_format = @intToEnum(gpu.Texture.Format, @intCast(u32, c.machUtilsBackendBinding_getPreferredSwapChainTextureFormat(binding))); - swap_chain.?.configure( - swap_chain_format, - .{ .render_attachment = true }, - framebuffer_size.width, - framebuffer_size.height, - ); + // Create a device with default limits/features. + const device = response.?.adapter.createDevice(&.{ + .required_features_count = if (options.required_features) |v| @intCast(u32, v.len) else 0, + .required_features = if (options.required_features) |v| @as(?[*]gpu.FeatureName, v.ptr) else null, + .required_limits = if (options.required_limits) |limits| @as(?*gpu.RequiredLimits, &gpu.RequiredLimits{ + .limits = limits, + }) else null, + }); + if (device == null) { + std.debug.print("mach: failed to create GPU device\n", .{}); + std.process.exit(1); } - device.setUncapturedErrorCallback(&util.printUnhandledErrorCallback); + core.swap_chain_format = .bgra8_unorm; + const descriptor = gpu.SwapChain.Descriptor{ + .label = "main swap chain", + .usage = .{ .render_attachment = true }, + .format = core.swap_chain_format, + .width = framebuffer_size.width, + .height = framebuffer_size.height, + .present_mode = .fifo, + }; - core.device = device; + device.?.setUncapturedErrorCallback({}, util.printUnhandledErrorCallback); + + core.device = device.?; core.backend_type = backend_type; core.surface = surface; - core.swap_chain = swap_chain; - core.swap_chain_format = swap_chain_format; core.current_desc = descriptor; core.target_desc = descriptor; + core.swap_chain = core.device.createSwapChain(core.surface, &core.target_desc); + // TODO: should resize fire on startup here? Might be nice for consistency const cursor_pos = try window.getCursorPos(); @@ -212,7 +155,8 @@ pub const Platform = struct { .x = cursor_pos.xpos, .y = cursor_pos.ypos, }, - .native_instance = native_instance, + .instance = instance.?, + .adapter = response.?.adapter, .linux_gamemode_is_active = linux_gamemode_is_active, }; } @@ -635,11 +579,15 @@ pub const BackingTimer = std.time.Timer; var app: App = undefined; +pub const GPUInterface = gpu.dawn.Interface; + pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); + gpu.Impl.init(); + var core = try coreInit(allocator); defer coreDeinit(core, allocator); @@ -691,16 +639,8 @@ pub fn coreUpdate(core: *Core, resize: ?CoreResizeCallback) !void { core.target_desc.width = framebuffer_size.width; core.target_desc.height = framebuffer_size.height; - if (core.swap_chain == null or !core.current_desc.equal(&core.target_desc)) { - const use_legacy_api = core.surface == null; - if (!use_legacy_api) { - core.swap_chain = core.device.nativeCreateSwapChain(core.surface, &core.target_desc); - } else core.swap_chain.?.configure( - core.swap_chain_format, - .{ .render_attachment = true }, - core.target_desc.width, - core.target_desc.height, - ); + if (core.swap_chain == null or !std.meta.eql(core.current_desc, core.target_desc)) { + core.swap_chain = core.device.createSwapChain(core.surface, &core.target_desc); if (@hasDecl(App, "resize")) { try app.resize(core, core.target_desc.width, core.target_desc.height); diff --git a/src/platform/util.zig b/src/platform/util.zig index 0e7282ee..c8cea234 100644 --- a/src/platform/util.zig +++ b/src/platform/util.zig @@ -2,12 +2,11 @@ const std = @import("std"); const glfw = @import("glfw"); const gpu = @import("gpu"); -const c = @import("c.zig").c; const objc = @cImport({ @cInclude("objc/message.h"); }); -fn printUnhandledError(_: void, typ: gpu.ErrorType, message: [*:0]const u8) void { +pub inline fn printUnhandledErrorCallback(typ: gpu.ErrorType, message: [*:0]const u8, _: void) void { switch (typ) { .validation => std.debug.print("gpu: validation error: {s}\n", .{message}), .out_of_memory => std.debug.print("gpu: out of memory: {s}\n", .{message}), @@ -17,7 +16,6 @@ fn printUnhandledError(_: void, typ: gpu.ErrorType, message: [*:0]const u8) void } std.os.exit(1); } -pub var printUnhandledErrorCallback = gpu.ErrorCallback.init(void, {}, printUnhandledError); fn getEnvVarOwned(allocator: std.mem.Allocator, key: []const u8) error{ OutOfMemory, InvalidUtf8 }!?[]u8 { return std.process.getEnvVarOwned(allocator, key) catch |err| switch (err) { @@ -26,17 +24,17 @@ fn getEnvVarOwned(allocator: std.mem.Allocator, key: []const u8) error{ OutOfMem }; } -pub fn detectBackendType(allocator: std.mem.Allocator) !gpu.Adapter.BackendType { +pub fn detectBackendType(allocator: std.mem.Allocator) !gpu.BackendType { const GPU_BACKEND = try getEnvVarOwned(allocator, "GPU_BACKEND"); if (GPU_BACKEND) |backend| { defer allocator.free(backend); - if (std.ascii.eqlIgnoreCase(backend, "opengl")) return .opengl; - if (std.ascii.eqlIgnoreCase(backend, "opengles")) return .opengles; + if (std.ascii.eqlIgnoreCase(backend, "null")) return .nul; if (std.ascii.eqlIgnoreCase(backend, "d3d11")) return .d3d11; if (std.ascii.eqlIgnoreCase(backend, "d3d12")) return .d3d12; if (std.ascii.eqlIgnoreCase(backend, "metal")) return .metal; - if (std.ascii.eqlIgnoreCase(backend, "null")) return .nul; if (std.ascii.eqlIgnoreCase(backend, "vulkan")) return .vulkan; + if (std.ascii.eqlIgnoreCase(backend, "opengl")) return .opengl; + if (std.ascii.eqlIgnoreCase(backend, "opengles")) return .opengles; @panic("unknown GPU_BACKEND type"); } @@ -46,7 +44,26 @@ pub fn detectBackendType(allocator: std.mem.Allocator) !gpu.Adapter.BackendType return .vulkan; } -pub fn glfwWindowHintsForBackend(backend: gpu.Adapter.BackendType) glfw.Window.Hints { +pub const RequestAdapterResponse = struct { + status: gpu.RequestAdapterStatus, + adapter: *gpu.Adapter, + message: ?[*:0]const u8, +}; + +pub inline fn requestAdapterCallback( + status: gpu.RequestAdapterStatus, + adapter: *gpu.Adapter, + message: ?[*:0]const u8, + context: *?RequestAdapterResponse, +) void { + context.* = RequestAdapterResponse{ + .status = status, + .adapter = adapter, + .message = message, + }; +} + +pub fn glfwWindowHintsForBackend(backend: gpu.BackendType) glfw.Window.Hints { return switch (backend) { .opengl => .{ // Ask for OpenGL 4.4 which is what the GL backend requires for compute shaders and @@ -70,28 +87,6 @@ pub fn glfwWindowHintsForBackend(backend: gpu.Adapter.BackendType) glfw.Window.H }; } -pub fn discoverAdapters(instance: c.MachDawnNativeInstance, window: glfw.Window, typ: gpu.Adapter.BackendType) !void { - switch (typ) { - .opengl => { - try glfw.makeContextCurrent(window); - const adapter_options = c.MachDawnNativeAdapterDiscoveryOptions_OpenGL{ - .getProc = @ptrCast(fn ([*c]const u8) callconv(.C) ?*anyopaque, glfw.getProcAddress), - }; - _ = c.machDawnNativeInstance_discoverAdapters(instance, @enumToInt(typ), &adapter_options); - }, - .opengles => { - try glfw.makeContextCurrent(window); - const adapter_options = c.MachDawnNativeAdapterDiscoveryOptions_OpenGLES{ - .getProc = @ptrCast(fn ([*c]const u8) callconv(.C) ?*anyopaque, glfw.getProcAddress), - }; - _ = c.machDawnNativeInstance_discoverAdapters(instance, @enumToInt(typ), &adapter_options); - }, - else => { - c.machDawnNativeInstance_discoverDefaultAdapters(instance); - }, - } -} - pub fn detectGLFWOptions() glfw.BackendOptions { const target = @import("builtin").target; if (target.isDarwin()) return .{ .cocoa = true }; @@ -103,20 +98,18 @@ pub fn detectGLFWOptions() glfw.BackendOptions { } pub fn createSurfaceForWindow( - native_instance: *const gpu.NativeInstance, + instance: *gpu.Instance, window: glfw.Window, comptime glfw_options: glfw.BackendOptions, -) gpu.Surface { +) *gpu.Surface { const glfw_native = glfw.Native(glfw_options); - const descriptor = if (glfw_options.win32) gpu.Surface.Descriptor{ - .windows_hwnd = .{ - .label = "basic surface", + const extension = if (glfw_options.win32) gpu.Surface.Extension{ + .from_windows_hwnd = &.{ .hinstance = std.os.windows.kernel32.GetModuleHandleW(null).?, .hwnd = glfw_native.getWin32Window(window), }, - } else if (glfw_options.x11) gpu.Surface.Descriptor{ - .xlib = .{ - .label = "basic surface", + } else if (glfw_options.x11) gpu.Surface.Extension{ + .from_xlib_window = &.{ .display = glfw_native.getX11Display(), .window = glfw_native.getX11Window(window), }, @@ -134,17 +127,14 @@ pub fn createSurfaceForWindow( const scale_factor = msgSend(ns_window, "backingScaleFactor", .{}, f64); // [ns_window backingScaleFactor] msgSend(layer.?, "setContentsScale:", .{scale_factor}, void); // [layer setContentsScale:scale_factor] - break :blk gpu.Surface.Descriptor{ - .metal_layer = .{ - .label = "basic surface", - .layer = layer.?, - }, - }; + break :blk gpu.Surface.Extension{ .from_metal_layer = &.{ .layer = layer.? } }; } else if (glfw_options.wayland) { - @panic("Dawn does not yet have Wayland support, see https://bugs.chromium.org/p/dawn/issues/detail?id=1246&q=surface&can=2"); + @panic("TODO: this example does not support Wayland"); } else unreachable; - return native_instance.createSurface(&descriptor); + return instance.createSurface(&gpu.Surface.Descriptor{ + .next_in_chain = extension, + }); } // Borrowed from https://github.com/hazeycode/zig-objcrt diff --git a/src/structs.zig b/src/structs.zig index 68b73ab0..dfadc879 100644 --- a/src/structs.zig +++ b/src/structs.zig @@ -42,13 +42,13 @@ pub const Options = struct { vsync: enums.VSyncMode = .double, /// GPU features required by the application. - required_features: ?[]gpu.Feature = null, + required_features: ?[]gpu.FeatureName = null, /// GPU limits required by the application. required_limits: ?gpu.Limits = null, /// Whether the application has a preference for low power or high performance GPU. - power_preference: gpu.PowerPreference = .none, + power_preference: gpu.PowerPreference = .undef, }; pub const Event = union(enum) {