commit bc9c9cb43fb639a35195dc334ce1ae3a68ecdebc
parent e7a7f0e33865a00e05e3fbafe46e61c69ca2c34c
Author: Christian Ermann <christianermann@gmail.com>
Date: Thu, 25 Apr 2024 17:27:01 -0400
Clear screen
Diffstat:
2 files changed, 269 insertions(+), 19 deletions(-)
diff --git a/src/main.zig b/src/main.zig
@@ -1,34 +1,276 @@
const std = @import("std");
const glfw = @import("mach_glfw");
const gpu = @import("mach_gpu");
+const builtin = @import("builtin");
-fn errorCallback(error_code: glfw.ErrorCode, description: [:0]const u8) void {
+const objc = @import("objc_message.zig");
+
+fn glfwErrorCallback(error_code: glfw.ErrorCode, description: [:0]const u8) void {
std.log.err("glfw: {}: {s}\n", .{ error_code, description });
}
-pub fn main() !void {
- glfw.setErrorCallback(errorCallback);
- if (!glfw.init(.{})) {
- std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
- std.process.exit(1);
- }
- defer glfw.terminate();
-
- const title = "Hello, World!";
- const window = glfw.Window.create(640, 480, title, null, null, .{}) orelse {
- const msg = "failed to create GLFW window: {?s}";
- std.log.err(msg, .{glfw.getErrorString()});
- std.process.exit(1);
+inline fn gpuErrorCallback(_: void, error_type: gpu.ErrorType, message: [*:0]const u8) void {
+ switch (error_type) {
+ .validation => std.log.err("gpu: validation error: {s}\n", .{message}),
+ .out_of_memory => std.log.err("gpu: out of memory: {s}\n", .{message}),
+ .device_lost => std.log.err("gpu: device lost: {s}\n", .{message}),
+ .unknown => std.log.err("gpu: unknown error: {s}\n", .{message}),
+ else => unreachable,
+ }
+ std.process.exit(1);
+}
+
+fn gpuDetectBackendType() !gpu.BackendType {
+ return .metal;
+}
+
+fn glfwWindowHintsForBackend(backend: gpu.BackendType) glfw.Window.Hints {
+ return switch (backend) {
+ else => .{
+ .client_api = .no_api,
+ },
};
- defer window.destroy();
+}
- while (!window.shouldClose()) {
- window.swapBuffers();
- glfw.pollEvents();
+fn glfwDetectBackendOptions() glfw.BackendOptions {
+ if (builtin.target.isDarwin()) return .{ .cocoa = true };
+ return switch (builtin.target.os.tag) {
+ .windows => .{ .win32 = true },
+ .linux => .{ .x11 = true, .wayland = true },
+ else => .{},
+ };
+}
+
+const AppSwapChain = struct {
+ gpu_swap_chain: ?*gpu.SwapChain,
+ format: gpu.Texture.Format,
+ current_descriptor: gpu.SwapChain.Descriptor,
+ target_descriptor: gpu.SwapChain.Descriptor,
+};
+
+const App = struct {
+ window: glfw.Window,
+ instance: *gpu.Instance,
+ surface: *gpu.Surface,
+ adapter: *gpu.Adapter,
+ device: *gpu.Device,
+ queue: *gpu.Queue,
+ swap_chain: *AppSwapChain,
+
+ pub fn init(app: *App, allocator: std.mem.Allocator) !void {
+ try gpu.Impl.init(allocator, .{});
+
+ glfw.setErrorCallback(glfwErrorCallback);
+ if (!glfw.init(.{})) {
+ const msg = "failed to initialize GLFW: {?s}";
+ std.log.err(msg, .{glfw.getErrorString()});
+ std.process.exit(1);
+ }
+
+ const backend_type = try gpuDetectBackendType();
+ const hints = glfwWindowHintsForBackend(backend_type);
+ const title = "Hello, World!";
+ app.window = glfw.Window.create(640, 480, title, null, null, hints) orelse {
+ const msg = "failed to create GLFW window: {?s}";
+ std.log.err(msg, .{glfw.getErrorString()});
+ std.process.exit(1);
+ };
+
+ app.instance = gpu.createInstance(null) orelse {
+ std.log.err("failed to create GPU instance", .{});
+ std.process.exit(1);
+ };
+
+ app.surface = try app.createSurfaceForWindow();
+ app.adapter = try app.createAdapter();
+
+ var props = std.mem.zeroes(gpu.Adapter.Properties);
+ app.adapter.getProperties(&props);
+ std.debug.print("found {s} backend on {s} adapter: {s}, {s}\n", .{
+ props.backend_type.name(),
+ props.adapter_type.name(),
+ props.name,
+ props.driver_description,
+ });
+
+ app.device = app.adapter.createDevice(null) orelse {
+ std.log.err("failed to create GPU device", .{});
+ std.process.exit(1);
+ };
+ app.device.setUncapturedErrorCallback({}, gpuErrorCallback);
+
+ app.queue = app.device.getQueue();
+
+ app.swap_chain = try app.createSwapChain(allocator);
+ app.window.setUserPointer(app.swap_chain);
+ }
+
+ pub fn deinit(app: *App) void {
+ app.window.destroy();
+ glfw.terminate();
}
+ fn createSurfaceForWindow(app: *App) !*gpu.Surface {
+ const glfw_options = comptime glfwDetectBackendOptions();
+ const glfw_native = glfw.Native(glfw_options);
+ if (glfw_options.cocoa) {
+ const ns_window = glfw_native.getCocoaWindow(app.window);
+ const ns_view = msgSend(ns_window, "contentView", .{}, *anyopaque);
+
+ // Create a CAMetalLayer that covers the whole window.
+ msgSend(ns_view, "setWantsLayer:", .{true}, void);
+ const layer = msgSend(objc.objc_getClass("CAMetalLayer"), "layer", .{}, ?*anyopaque) orelse {
+ @panic("failed to create Metal layer");
+ };
+ msgSend(ns_view, "setLayer:", .{layer}, void);
+
+ // Use retina if the window was created with retina support.
+ const scale_factor = msgSend(ns_window, "backingScaleFactor", .{}, f64);
+ msgSend(layer, "setContentsScale:", .{scale_factor}, void);
+
+ return app.instance.createSurface(&gpu.Surface.Descriptor{
+ .next_in_chain = .{
+ .from_metal_layer = &.{ .layer = layer },
+ },
+ });
+ }
+ }
+
+ fn createAdapter(app: *App) !*gpu.Adapter {
+ const RequestAdapterResponse = struct {
+ status: gpu.RequestAdapterStatus,
+ adapter: ?*gpu.Adapter,
+ message: ?[*:0]const u8,
+
+ pub inline fn callback(
+ context: *@This(),
+ status: gpu.RequestAdapterStatus,
+ adapter: ?*gpu.Adapter,
+ message: ?[*:0]const u8,
+ ) void {
+ context.* = @This(){
+ .status = status,
+ .adapter = adapter,
+ .message = message,
+ };
+ }
+ };
+
+ var response: RequestAdapterResponse = undefined;
+ app.instance.requestAdapter(&gpu.RequestAdapterOptions{
+ .compatible_surface = app.surface,
+ .power_preference = .undefined,
+ .force_fallback_adapter = .false,
+ }, &response, RequestAdapterResponse.callback);
+ if (response.status != .success) {
+ const msg = "failed to create GPU adapter: {s}\n";
+ std.debug.print(msg, .{response.message.?});
+ std.process.exit(1);
+ }
+ return response.adapter.?;
+ }
+
+ fn createSwapChain(app: *App, allocator: std.mem.Allocator) !*AppSwapChain {
+ const format = .bgra8_unorm;
+ const framebuffer_size = app.window.getFramebufferSize();
+ const descriptor = gpu.SwapChain.Descriptor{
+ .label = "basic swap chain",
+ .usage = .{ .render_attachment = true },
+ .format = format,
+ .width = framebuffer_size.width,
+ .height = framebuffer_size.height,
+ .present_mode = .fifo,
+ };
+ const swap_chain = try allocator.create(AppSwapChain);
+ swap_chain.* = .{
+ .gpu_swap_chain = null,
+ .format = format,
+ .current_descriptor = descriptor,
+ .target_descriptor = descriptor,
+ };
+ return swap_chain;
+ }
+
+ fn getCurrentSwapChain(app: *App) *gpu.SwapChain {
+ const sc = app.window.getUserPointer(AppSwapChain).?;
+ const not_exists = sc.gpu_swap_chain == null;
+ const changed = !std.meta.eql(sc.current_descriptor, sc.target_descriptor);
+ if (not_exists or changed) {
+ sc.gpu_swap_chain = app.device.createSwapChain(app.surface, &sc.target_descriptor);
+ sc.current_descriptor = sc.target_descriptor;
+ }
+ return sc.gpu_swap_chain.?;
+ }
+
+ pub fn frame(app: *App) !void {
+ app.device.tick();
+ const swap_chain = app.getCurrentSwapChain();
+ defer swap_chain.present();
+
+ const back_buffer_view = swap_chain.getCurrentTextureView().?;
+ defer back_buffer_view.release();
+
+ {
+ const encoder = app.device.createCommandEncoder(null);
+ defer encoder.release();
+
+ const color_attachment = gpu.RenderPassColorAttachment{
+ .view = back_buffer_view,
+ .resolve_target = null,
+ .clear_value = gpu.Color{ .r = 1, .g = 1, .b = 1, .a = 1 },
+ .load_op = .clear,
+ .store_op = .store,
+ };
+ const render_pass_info = gpu.RenderPassDescriptor.init(.{
+ .color_attachments = &.{color_attachment},
+ });
+
+ {
+ const pass = encoder.beginRenderPass(&render_pass_info);
+ defer pass.release();
+ defer pass.end();
+ }
+
+ {
+ var command = encoder.finish(null);
+ defer command.release();
+
+ app.queue.submit(&[_]*gpu.CommandBuffer{command});
+ }
+ }
+ }
+};
+
+pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
- try gpu.Impl.init(allocator, .{});
+ var app: App = undefined;
+ try app.init(allocator);
+ defer app.deinit();
+
+ while (!app.window.shouldClose()) {
+ glfw.pollEvents();
+ try app.frame();
+ }
+}
+
+// Borrowed from https://github.com/hexops/mach-gpu
+// Borrowed from https://github.com/hazeycode/zig-objcrt
+pub fn msgSend(obj: anytype, sel_name: [:0]const u8, args: anytype, comptime ReturnType: type) ReturnType {
+ const args_meta = @typeInfo(@TypeOf(args)).Struct.fields;
+
+ const FnType = switch (args_meta.len) {
+ 0 => *const fn (@TypeOf(obj), ?*objc.SEL) callconv(.C) ReturnType,
+ 1 => *const fn (@TypeOf(obj), ?*objc.SEL, args_meta[0].type) callconv(.C) ReturnType,
+ 2 => *const fn (@TypeOf(obj), ?*objc.SEL, args_meta[0].type, args_meta[1].type) callconv(.C) ReturnType,
+ 3 => *const fn (@TypeOf(obj), ?*objc.SEL, args_meta[0].type, args_meta[1].type, args_meta[2].type) callconv(.C) ReturnType,
+ 4 => *const fn (@TypeOf(obj), ?*objc.SEL, args_meta[0].type, args_meta[1].type, args_meta[2].type, args_meta[3].type) callconv(.C) ReturnType,
+ else => @compileError("Unsupported number of args"),
+ };
+
+ const func = @as(FnType, @ptrCast(&objc.objc_msgSend));
+ const sel = objc.sel_getUid(@as([*c]const u8, @ptrCast(sel_name)));
+
+ return @call(.auto, func, .{ obj, sel } ++ args);
}
diff --git a/src/objc_message.zig b/src/objc_message.zig
@@ -0,0 +1,8 @@
+// Borrowed from https://github.com/hexops/mach-gpu
+
+pub const SEL = opaque {};
+pub const Class = opaque {};
+
+pub extern fn sel_getUid(str: [*c]const u8) ?*SEL;
+pub extern fn objc_getClass(name: [*c]const u8) ?*Class;
+pub extern fn objc_msgSend() void;