diff --git a/build.zig b/build.zig index 4d27e74d7..b6757e6b7 100644 --- a/build.zig +++ b/build.zig @@ -13,6 +13,8 @@ pub fn build(b: *std.build.Builder) void { exe.setTarget(target); exe.setBuildMode(mode); exe.install(); + exe.addPackagePath("gpu", "vendor/mach/gpu/src/main.zig"); + exe.addPackagePath("dawn", "vendor/mach/gpu-dawn/src/dawn/c.zig"); exe.addPackagePath("glfw", "vendor/mach/glfw/src/main.zig"); glfw.link(b, exe, .{}); gpu_dawn.link(b, exe, if (target.getCpuArch() == .aarch64) .{ diff --git a/nix/devshell.nix b/nix/devshell.nix index 97a3fa871..0f59e23bf 100644 --- a/nix/devshell.nix +++ b/nix/devshell.nix @@ -3,6 +3,7 @@ , gdb , pkg-config , scdoc +, vulkan-loader , vttest , zig @@ -26,5 +27,5 @@ libX11 ]; - LD_LIBRARY_PATH = "${libGL}/lib"; + LD_LIBRARY_PATH = "${vulkan-loader}/lib:${libGL}/lib"; } diff --git a/src/main.zig b/src/main.zig index b64bdf644..3e10a5b9d 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,16 +1,19 @@ const std = @import("std"); +const dawn = @import("dawn"); const glfw = @import("glfw"); +const gpu = @import("gpu"); + +const setup = @import("setup.zig"); pub fn main() !void { - try glfw.init(.{}); + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + var allocator = gpa.allocator(); + + const s = try setup.setup(allocator); defer glfw.terminate(); - // Create our window - const window = try glfw.Window.create(640, 480, "ghostty", null, null, .{}); - defer window.destroy(); - // Wait for the user to close the window. - while (!window.shouldClose()) { + while (!s.window.shouldClose()) { try glfw.pollEvents(); } } diff --git a/src/setup.zig b/src/setup.zig new file mode 100644 index 000000000..a623d1788 --- /dev/null +++ b/src/setup.zig @@ -0,0 +1,144 @@ +const std = @import("std"); +const dawn = @import("dawn"); +const glfw = @import("glfw"); +const gpu = @import("gpu"); +const c = dawn.c; + +const Setup = struct { + native_instance: gpu.NativeInstance, + backend_type: gpu.Adapter.BackendType, + device: gpu.Device, + window: glfw.Window, +}; + +pub fn setup(allocator: std.mem.Allocator) !Setup { + const backend_type = try detectBackendType(allocator); + std.log.info("detected backend type: {}", .{backend_type}); + + // Initialize glfw + try glfw.init(.{}); + errdefer glfw.terminate(); + + // Create the window and discover adapters using it (esp. for OpenGL) + var hints = glfwWindowHintsForBackend(backend_type); + hints.cocoa_retina_framebuffer = true; + const window = try glfw.Window.create(640, 480, "ghostty", null, null, hints); + + const backend_procs = dawn.c.machDawnNativeGetProcs(); + dawn.c.dawnProcSetProcs(backend_procs); + + const instance = dawn.c.machDawnNativeInstance_init(); + var native_instance = gpu.NativeInstance.wrap(dawn.c.machDawnNativeInstance_get(instance).?); + const gpu_interface = native_instance.interface(); + + // Discovers e.g. OpenGL adapters. + try discoverAdapters(instance, window, backend_type); + + // Request an adapter. + 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); + }, + }; + + // Print which adapter we are going to use. + const props = backend_adapter.properties; + 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.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); + }, + }; + + return Setup{ + .native_instance = native_instance, + .backend_type = backend_type, + .device = device, + .window = window, + }; +} + +fn detectBackendType(allocator: std.mem.Allocator) !gpu.Adapter.BackendType { + const GPU_BACKEND = std.process.getEnvVarOwned(allocator, "GPU_BACKEND") catch |err| switch (err) { + error.EnvironmentVariableNotFound => @as(?[]u8, null), + else => |e| return e, + }; + if (GPU_BACKEND) |backend| { + defer allocator.free(backend); + 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, "opengl")) return .opengl; + if (std.ascii.eqlIgnoreCase(backend, "opengles")) return .opengles; + if (std.ascii.eqlIgnoreCase(backend, "vulkan")) return .vulkan; + @panic("unknown GPU_BACKEND value"); + } + + const target = @import("builtin").target; + if (target.isDarwin()) return .metal; + if (target.os.tag == .windows) return .d3d12; + return .vulkan; +} + +fn glfwWindowHintsForBackend(backend: gpu.Adapter.BackendType) glfw.Window.Hints { + return switch (backend) { + .opengl => .{ + // Ask for OpenGL 4.4 which is what the GL backend requires for + // compute shaders and texture views. + .context_version_major = 4, + .context_version_minor = 4, + .opengl_forward_compat = true, + .opengl_profile = .opengl_core_profile, + }, + .opengles => .{ + .context_version_major = 3, + .context_version_minor = 1, + .client_api = .opengl_es_api, + .context_creation_api = .egl_context_api, + }, + else => .{ + // Without this GLFW will initialize a GL context on the window, + // which prevents using the window with other APIs (by crashing in weird ways). + .client_api = .no_api, + }, + }; +} + +fn discoverAdapters( + instance: c.MachDawnNativeInstance, + window: glfw.Window, + backend: gpu.Adapter.BackendType, +) !void { + switch (backend) { + .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(backend), &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(backend), &adapter_options); + }, + else => { + c.machDawnNativeInstance_discoverDefaultAdapters(instance); + }, + } +}