diff --git a/gpu/build.zig b/gpu/build.zig index b2ebdd8f..477222ee 100644 --- a/gpu/build.zig +++ b/gpu/build.zig @@ -13,20 +13,18 @@ pub fn build(b: *std.build.Builder) void { const test_step = b.step("test", "Run library tests"); test_step.dependOn(&testStep(b, mode, target, .{ .gpu_dawn_options = gpu_dawn_options }).step); - // const example = b.addExecutable("gpu-hello-triangle", "examples/main.zig"); - // example.setBuildMode(mode); - //example.setTarget(target); - // example.linkLibC(); - // example.addPackagePath("gpu", "src/main.zig"); - // example.addPackagePath("glfw", "libs/mach-glfw/src/main.zig"); - // link(b, example, .{ .gpu_dawn_options = gpu_dawn_options }); + const example = b.addExecutable("gpu-hello-triangle", "examples/main.zig"); + example.setBuildMode(mode); + example.setTarget(target); + example.addPackage(pkg); + example.addPackage(glfw.pkg); + link(b, example, .{ .gpu_dawn_options = gpu_dawn_options }); + example.install(); - // example.install(); - - // const example_run_cmd = example.run(); - // example_run_cmd.step.dependOn(b.getInstallStep()); - // const example_run_step = b.step("run-example", "Run the example"); - // example_run_step.dependOn(&example_run_cmd.step); + const example_run_cmd = example.run(); + example_run_cmd.step.dependOn(b.getInstallStep()); + const example_run_step = b.step("run-example", "Run the example"); + example_run_step.dependOn(&example_run_cmd.step); } pub fn testStep(b: *std.build.Builder, mode: std.builtin.Mode, target: std.zig.CrossTarget, options: Options) *std.build.RunStep { diff --git a/gpu/examples/c.zig b/gpu/examples/c.zig index 31cddaec..5c2da194 100644 --- a/gpu/examples/c.zig +++ b/gpu/examples/c.zig @@ -1,4 +1,3 @@ pub const c = @cImport({ @cInclude("dawn/webgpu.h"); - @cInclude("dawn/dawn_proc.h"); }); diff --git a/gpu/examples/main.zig b/gpu/examples/main.zig index d783d8bb..4f8695e5 100644 --- a/gpu/examples/main.zig +++ b/gpu/examples/main.zig @@ -4,16 +4,19 @@ const c = @import("c.zig").c; const glfw = @import("glfw"); const gpu = @import("gpu"); +pub const GPUInterface = gpu.dawn.Interface; + pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; var allocator = gpa.allocator(); + gpu.Impl.init(); const setup = try sample_utils.setup(allocator); const framebuffer_size = try setup.window.getFramebufferSize(); const window_data = try allocator.create(WindowData); window_data.* = .{ - .surface = null, + .surface = setup.surface, .swap_chain = null, .swap_chain_format = undefined, .current_desc = undefined, @@ -21,43 +24,16 @@ pub fn main() !void { }; setup.window.setUserPointer(window_data); - // 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 = setup.backend_type == .opengl or setup.backend_type == .opengles; - var descriptor: gpu.SwapChain.Descriptor = undefined; - if (!use_legacy_api) { - window_data.swap_chain_format = .bgra8_unorm; - descriptor = .{ - .label = "basic swap chain", - .usage = .{ .render_attachment = true }, - .format = window_data.swap_chain_format, - .width = framebuffer_size.width, - .height = framebuffer_size.height, - .present_mode = .fifo, - .implementation = 0, - }; - window_data.surface = sample_utils.createSurfaceForWindow( - &setup.native_instance, - setup.window, - comptime sample_utils.detectGLFWOptions(), - ); - } else { - const binding = c.machUtilsCreateBinding(@enumToInt(setup.backend_type), @ptrCast(*c.GLFWwindow, setup.window.handle), @ptrCast(c.WGPUDevice, setup.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); - window_data.swap_chain = setup.device.nativeCreateSwapChain(null, &descriptor); + window_data.swap_chain_format = .bgra8_unorm; + const descriptor = gpu.SwapChain.Descriptor{ + .label = "basic swap chain", + .usage = .{ .render_attachment = true }, + .format = window_data.swap_chain_format, + .width = framebuffer_size.width, + .height = framebuffer_size.height, + .present_mode = .fifo, + }; - window_data.swap_chain_format = @intToEnum(gpu.Texture.Format, @intCast(u32, c.machUtilsBackendBinding_getPreferredSwapChainTextureFormat(binding))); - window_data.swap_chain.?.configure( - window_data.swap_chain_format, - .{ .render_attachment = true }, - framebuffer_size.width, - framebuffer_size.height, - ); - } window_data.current_desc = descriptor; window_data.target_desc = descriptor; @@ -74,8 +50,11 @@ pub fn main() !void { \\ } ; const vs_module = setup.device.createShaderModule(&.{ + .next_in_chain = @ptrCast(*const gpu.ChainedStruct, &gpu.ShaderModule.WGSLDescriptor{ + .chain = .{ .next = null, .s_type = .shader_module_wgsl_descriptor }, + .source = vs, + }), .label = "my vertex shader", - .code = .{ .wgsl = vs }, }); const fs = @@ -84,33 +63,32 @@ pub fn main() !void { \\ } ; const fs_module = setup.device.createShaderModule(&.{ + .next_in_chain = @ptrCast(*const gpu.ChainedStruct, &gpu.ShaderModule.WGSLDescriptor{ + .chain = .{ .next = null, .s_type = .shader_module_wgsl_descriptor }, + .source = fs, + }), .label = "my fragment shader", - .code = .{ .wgsl = fs }, }); // Fragment state const blend = gpu.BlendState{ .color = .{ - .operation = .add, - .src_factor = .one, .dst_factor = .one, }, .alpha = .{ - .operation = .add, - .src_factor = .one, .dst_factor = .one, }, }; const color_target = gpu.ColorTargetState{ .format = window_data.swap_chain_format, .blend = &blend, - .write_mask = gpu.ColorWriteMask.all, + .write_mask = gpu.ColorWriteMaskFlags.all, }; const fragment = gpu.FragmentState{ .module = fs_module, .entry_point = "main", - .targets = &.{color_target}, - .constants = null, + .target_count = 1, + .targets = &[_]gpu.ColorTargetState{color_target}, }; const pipeline_descriptor = gpu.RenderPipeline.Descriptor{ .fragment = &fragment, @@ -119,19 +97,9 @@ pub fn main() !void { .vertex = .{ .module = vs_module, .entry_point = "main", - .buffers = null, - }, - .multisample = .{ - .count = 1, - .mask = 0xFFFFFFFF, - .alpha_to_coverage_enabled = false, - }, - .primitive = .{ - .front_face = .ccw, - .cull_mode = .none, - .topology = .triangle_list, - .strip_index_format = .none, }, + .multisample = .{}, + .primitive = .{}, }; const pipeline = setup.device.createRenderPipeline(&pipeline_descriptor); @@ -161,8 +129,8 @@ pub fn main() !void { } const WindowData = struct { - surface: ?gpu.Surface, - swap_chain: ?gpu.SwapChain, + surface: ?*gpu.Surface, + swap_chain: ?*gpu.SwapChain, swap_chain_format: gpu.Texture.Format, current_desc: gpu.SwapChain.Descriptor, target_desc: gpu.SwapChain.Descriptor, @@ -170,24 +138,16 @@ const WindowData = struct { const FrameParams = struct { window: glfw.Window, - device: gpu.Device, - pipeline: gpu.RenderPipeline, - queue: gpu.Queue, + device: *gpu.Device, + pipeline: *gpu.RenderPipeline, + queue: *gpu.Queue, }; fn frame(params: FrameParams) !void { try glfw.pollEvents(); const pl = params.window.getUserPointer(WindowData).?; - if (pl.swap_chain == null or !pl.current_desc.equal(&pl.target_desc)) { - const use_legacy_api = pl.surface == null; - if (!use_legacy_api) { - pl.swap_chain = params.device.nativeCreateSwapChain(pl.surface, &pl.target_desc); - } else pl.swap_chain.?.configure( - pl.swap_chain_format, - .{ .render_attachment = true }, - pl.target_desc.width, - pl.target_desc.height, - ); + if (pl.swap_chain == null or !std.meta.eql(pl.current_desc, pl.target_desc)) { + pl.swap_chain = params.device.createSwapChain(pl.surface, &pl.target_desc); pl.current_desc = pl.target_desc; } @@ -201,9 +161,11 @@ fn frame(params: FrameParams) !void { }; const encoder = params.device.createCommandEncoder(null); - const render_pass_info = gpu.RenderPassEncoder.Descriptor{ - .color_attachments = &.{color_attachment}, + const render_pass_info = gpu.RenderPassDescriptor{ + .color_attachment_count = 1, + .color_attachments = &[_]gpu.RenderPassColorAttachment{color_attachment}, .depth_stencil_attachment = null, + .occlusion_query_set = null, }; const pass = encoder.beginRenderPass(&render_pass_info); pass.setPipeline(params.pipeline); @@ -214,7 +176,7 @@ fn frame(params: FrameParams) !void { var command = encoder.finish(null); encoder.release(); - params.queue.submit(&.{command}); + params.queue.submit(1, &[_]*gpu.CommandBuffer{command}); command.release(); pl.swap_chain.?.present(); back_buffer_view.release(); diff --git a/gpu/examples/sample_utils.zig b/gpu/examples/sample_utils.zig index 79ba6e76..2d230fec 100644 --- a/gpu/examples/sample_utils.zig +++ b/gpu/examples/sample_utils.zig @@ -7,7 +7,7 @@ const objc = @cImport({ @cInclude("objc/message.h"); }); -fn printUnhandledError(_: void, typ: gpu.ErrorType, message: [*:0]const u8) void { +fn printUnhandledErrorCallback(typ: gpu.ErrorType, message: [*:0]const u8, _: ?*anyopaque) callconv(.C) 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}), @@ -15,14 +15,15 @@ fn printUnhandledError(_: void, typ: gpu.ErrorType, message: [*:0]const u8) void .unknown => std.debug.print("gpu: unknown error: {s}\n", .{message}), else => unreachable, } + std.process.exit(1); } -var printUnhandledErrorCallback = gpu.ErrorCallback.init(void, {}, printUnhandledError); const Setup = struct { - native_instance: gpu.NativeInstance, - backend_type: gpu.Adapter.BackendType, - device: gpu.Device, + instance: *gpu.Instance, + adapter: *gpu.Adapter, + device: *gpu.Device, window: glfw.Window, + surface: *gpu.Surface, }; fn getEnvVarOwned(allocator: std.mem.Allocator, key: []const u8) error{ OutOfMemory, InvalidUtf8 }!?[]u8 { @@ -32,17 +33,18 @@ fn getEnvVarOwned(allocator: std.mem.Allocator, key: []const u8) error{ OutOfMem }; } -fn detectBackendType(allocator: std.mem.Allocator) !gpu.Adapter.BackendType { +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, "webgpu")) 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 BACKEND type"); } @@ -52,6 +54,26 @@ fn detectBackendType(allocator: std.mem.Allocator) !gpu.Adapter.BackendType { return .vulkan; } +const RequestAdapterResponse = struct { + status: gpu.RequestAdapterStatus, + adapter: *gpu.Adapter, + message: ?[*:0]const u8, +}; + +fn requestAdapterCallback( + status: gpu.RequestAdapterStatus, + adapter: *gpu.Adapter, + message: ?[*:0]const u8, + userdata: ?*anyopaque, +) callconv(.C) void { + const response = @ptrCast(*?RequestAdapterResponse, @alignCast(@alignOf(*?RequestAdapterResponse), userdata)); + response.* = RequestAdapterResponse{ + .status = status, + .adapter = adapter, + .message = message, + }; +} + pub fn setup(allocator: std.mem.Allocator) !Setup { const backend_type = try detectBackendType(allocator); @@ -61,79 +83,55 @@ pub fn setup(allocator: std.mem.Allocator) !Setup { var hints = glfwWindowHintsForBackend(backend_type); hints.cocoa_retina_framebuffer = true; const window = try glfw.Window.create(640, 480, "mach/gpu window", null, null, hints); + if (backend_type == .opengl) try glfw.makeContextCurrent(window); + if (backend_type == .opengles) try glfw.makeContextCurrent(window); - const backend_procs = c.machDawnNativeGetProcs(); - c.dawnProcSetProcs(backend_procs); - - const instance = c.machDawnNativeInstance_init(); - var native_instance = gpu.NativeInstance.wrap(c.machDawnNativeInstance_get(instance).?); - - // Discovers e.g. OpenGL adapters. - try 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("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; - } - } - if (dawn_adapter == null) { - std.debug.print("no matching adapter found for {s}", .{@tagName(backend_type)}); - std.debug.print("-> maybe try GPU_BACKEND=opengl ?\n", .{}); + const instance = gpu.createInstance(null); + if (instance == null) { + std.debug.print("failed to create GPU instance\n", .{}); std.process.exit(1); } - assert(dawn_adapter != null); - const backend_adapter = gpu.NativeInstance.fromWGPUAdapter(c.machDawnNativeAdapter_get(dawn_adapter.?).?); + const surface = createSurfaceForWindow(instance.?, window, comptime detectGLFWOptions()); - // Print which adapter we are going to use. - const props = backend_adapter.properties; + var response: ?RequestAdapterResponse = null; + instance.?.requestAdapter(&gpu.RequestAdapterOptions{ + .compatible_surface = surface, + .power_preference = .undef, + .force_fallback_adapter = false, + }, requestAdapterCallback, &response); + if (response.?.status != .success) { + std.debug.print("failed to create GPU adapter: {s}\n", .{response.?.message}); + std.process.exit(1); + } + + // Print which adapter we are using. + var props: gpu.Adapter.Properties = undefined; + response.?.adapter.getProperties(&props); std.debug.print("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(&.{})) { - .device => |v| v, - .err => |err| { - std.debug.print("failed to get device: error={} {s}\n", .{ err.code, err.message }); - std.process.exit(1); - }, - }; + // Create a device with default limits/features. + const device = response.?.adapter.createDevice(null); + if (device == null) { + std.debug.print("failed to create GPU device\n", .{}); + std.process.exit(1); + } - device.setUncapturedErrorCallback(&printUnhandledErrorCallback); + device.?.setUncapturedErrorCallback(printUnhandledErrorCallback, null); return Setup{ - .native_instance = native_instance, - .backend_type = backend_type, - .device = device, + .instance = instance.?, + .adapter = response.?.adapter, + .device = device.?, .window = window, + .surface = surface, }; } -fn glfwWindowHintsForBackend(backend: gpu.Adapter.BackendType) glfw.Window.Hints { +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 @@ -157,28 +155,6 @@ fn glfwWindowHintsForBackend(backend: gpu.Adapter.BackendType) glfw.Window.Hints }; } -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 }; @@ -190,23 +166,19 @@ 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", - .hinstance = std.os.windows.kernel32.GetModuleHandleW(null).?, - .hwnd = glfw_native.getWin32Window(window), - }, - } else if (glfw_options.x11) gpu.Surface.Descriptor{ - .xlib = .{ - .label = "basic surface", - .display = glfw_native.getX11Display(), - .window = glfw_native.getX11Window(window), - }, + const descriptor = if (glfw_options.win32) gpu.Surface.DescriptorFromWindowsHWND{ + .chain = .{ .s_type = .surface_descriptor_from_windows_hwnd }, + .hinstance = std.os.windows.kernel32.GetModuleHandleW(null).?, + .hwnd = glfw_native.getWin32Window(window), + } else if (glfw_options.x11) gpu.Surface.DescriptorFromXlibWindow{ + .chain = .{ .s_type = .surface_descriptor_from_xlib_window }, + .display = glfw_native.getX11Display(), + .window = glfw_native.getX11Window(window), } else if (glfw_options.cocoa) blk: { const ns_window = glfw_native.getCocoaWindow(window); const ns_view = msgSend(ns_window, "contentView", .{}, *anyopaque); // [nsWindow contentView] @@ -221,17 +193,17 @@ 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.DescriptorFromMetalLayer{ + .chain = .{ .s_type = .surface_descriptor_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 = @ptrCast(*const gpu.ChainedStruct, &descriptor), + }); } // Borrowed from https://github.com/hazeycode/zig-objcrt