mach: comptime interface-like implementation of engine cores
This commit is contained in:
parent
2b978a6883
commit
8904536632
3 changed files with 210 additions and 162 deletions
|
|
@ -45,6 +45,8 @@ pub const Options = struct {
|
|||
power_preference: gpu.PowerPreference = .none,
|
||||
};
|
||||
|
||||
const Engine = @This();
|
||||
|
||||
/// Window, events, inputs etc.
|
||||
core: Core,
|
||||
|
||||
|
|
@ -53,6 +55,8 @@ gpu_driver: GpuDriver,
|
|||
|
||||
allocator: Allocator,
|
||||
|
||||
options: Options,
|
||||
|
||||
/// The amount of time (in seconds) that has passed since the last frame was rendered.
|
||||
///
|
||||
/// For example, if you are animating a cube which should rotate 360 degrees every second,
|
||||
|
|
@ -63,19 +67,41 @@ delta_time_ns: u64 = 0,
|
|||
timer: std.time.Timer,
|
||||
|
||||
pub const Core = struct {
|
||||
internal: union {
|
||||
window: glfw.Window,
|
||||
},
|
||||
internal: GetCoreInternalType(),
|
||||
};
|
||||
|
||||
pub const GpuDriver = struct {
|
||||
internal: GetGpuDriverInternalType(),
|
||||
|
||||
device: gpu.Device,
|
||||
backend_type: gpu.Adapter.BackendType,
|
||||
swap_chain: ?gpu.SwapChain,
|
||||
swap_chain_format: gpu.Texture.Format,
|
||||
|
||||
native_instance: gpu.NativeInstance,
|
||||
surface: ?gpu.Surface,
|
||||
current_desc: gpu.SwapChain.Descriptor,
|
||||
target_desc: gpu.SwapChain.Descriptor,
|
||||
};
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, options: Options) !Engine {
|
||||
var engine = Engine{
|
||||
.allocator = allocator,
|
||||
.options = options,
|
||||
.timer = try std.time.Timer.start(),
|
||||
.core = undefined,
|
||||
.gpu_driver = undefined,
|
||||
};
|
||||
|
||||
engine.core.internal = try GetCoreInternalType().init(allocator, &engine);
|
||||
engine.gpu_driver.internal = try GetGpuDriverInternalType().init(allocator, &engine);
|
||||
|
||||
return engine;
|
||||
}
|
||||
|
||||
fn GetCoreInternalType() type {
|
||||
return @import("native.zig").CoreGlfw;
|
||||
}
|
||||
|
||||
fn GetGpuDriverInternalType() type {
|
||||
return @import("native.zig").GpuDriverNative;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,163 +10,6 @@ const c = @import("c.zig").c;
|
|||
const Engine = @import("Engine.zig");
|
||||
const Options = Engine.Options;
|
||||
|
||||
/// Default GLFW error handling callback
|
||||
fn glfwErrorCallback(error_code: glfw.Error, description: [:0]const u8) void {
|
||||
std.debug.print("glfw: {}: {s}\n", .{ error_code, description });
|
||||
}
|
||||
|
||||
fn init(allocator: Allocator, options: Options) !Engine {
|
||||
const backend_type = try util.detectBackendType(allocator);
|
||||
|
||||
glfw.setErrorCallback(glfwErrorCallback);
|
||||
try glfw.init(.{});
|
||||
|
||||
// Create the test window and discover adapters using it (esp. for OpenGL)
|
||||
var hints = util.glfwWindowHintsForBackend(backend_type);
|
||||
hints.cocoa_retina_framebuffer = true;
|
||||
const window = try glfw.Window.create(
|
||||
options.width,
|
||||
options.height,
|
||||
options.title,
|
||||
null,
|
||||
null,
|
||||
hints,
|
||||
);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (dawn_adapter == null) {
|
||||
std.debug.print("mach: no matching adapter found for {s}", .{@tagName(backend_type)});
|
||||
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;
|
||||
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.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);
|
||||
},
|
||||
};
|
||||
|
||||
var framebuffer_size = try window.getFramebufferSize();
|
||||
|
||||
// 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,
|
||||
);
|
||||
}
|
||||
|
||||
device.setUncapturedErrorCallback(&util.printUnhandledErrorCallback);
|
||||
return Engine{
|
||||
.allocator = allocator,
|
||||
.timer = try std.time.Timer.start(),
|
||||
.core = .{ .internal = .{
|
||||
.window = window,
|
||||
} },
|
||||
.gpu_driver = .{
|
||||
.device = device,
|
||||
.backend_type = backend_type,
|
||||
.native_instance = native_instance,
|
||||
.surface = surface,
|
||||
.swap_chain = swap_chain,
|
||||
.swap_chain_format = swap_chain_format,
|
||||
.current_desc = descriptor,
|
||||
.target_desc = descriptor,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: check signatures
|
||||
comptime {
|
||||
if (!@hasDecl(App, "init")) @compileError("App must export 'pub fn init(app: *App, engine: *mach.Engine) !void'");
|
||||
|
|
@ -179,7 +22,7 @@ pub fn main() !void {
|
|||
const allocator = gpa.allocator();
|
||||
|
||||
const options = if (@hasDecl(App, "options")) App.options else Options{};
|
||||
var engine = try init(allocator, options);
|
||||
var engine = try Engine.init(allocator, options);
|
||||
var app: App = undefined;
|
||||
|
||||
try app.init(&engine);
|
||||
|
|
|
|||
179
src/native.zig
Normal file
179
src/native.zig
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
const std = @import("std");
|
||||
const glfw = @import("glfw");
|
||||
const gpu = @import("gpu");
|
||||
const Engine = @import("Engine.zig");
|
||||
const util = @import("util.zig");
|
||||
const c = @import("c.zig").c;
|
||||
|
||||
pub const CoreGlfw = struct {
|
||||
window: glfw.Window,
|
||||
backend_type: gpu.Adapter.BackendType,
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, engine: *Engine) !CoreGlfw {
|
||||
const options = engine.options;
|
||||
const backend_type = try util.detectBackendType(allocator);
|
||||
|
||||
glfw.setErrorCallback(CoreGlfw.errorCallback);
|
||||
try glfw.init(.{});
|
||||
|
||||
// Create the test window and discover adapters using it (esp. for OpenGL)
|
||||
var hints = util.glfwWindowHintsForBackend(backend_type);
|
||||
hints.cocoa_retina_framebuffer = true;
|
||||
const window = try glfw.Window.create(
|
||||
options.width,
|
||||
options.height,
|
||||
options.title,
|
||||
null,
|
||||
null,
|
||||
hints,
|
||||
);
|
||||
|
||||
return CoreGlfw{
|
||||
.window = window,
|
||||
.backend_type = backend_type,
|
||||
};
|
||||
}
|
||||
|
||||
/// Default GLFW error handling callback
|
||||
fn errorCallback(error_code: glfw.Error, description: [:0]const u8) void {
|
||||
std.debug.print("glfw: {}: {s}\n", .{ error_code, description });
|
||||
}
|
||||
};
|
||||
|
||||
pub const GpuDriverNative = struct {
|
||||
native_instance: gpu.NativeInstance,
|
||||
|
||||
pub fn init(_: std.mem.Allocator, engine: *Engine) !GpuDriverNative {
|
||||
const options = engine.options;
|
||||
const window = engine.core.internal.window;
|
||||
const backend_type = engine.core.internal.backend_type;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (dawn_adapter == null) {
|
||||
std.debug.print("mach: no matching adapter found for {s}", .{@tagName(backend_type)});
|
||||
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;
|
||||
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.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);
|
||||
},
|
||||
};
|
||||
|
||||
var framebuffer_size = try window.getFramebufferSize();
|
||||
|
||||
// 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,
|
||||
);
|
||||
}
|
||||
|
||||
device.setUncapturedErrorCallback(&util.printUnhandledErrorCallback);
|
||||
|
||||
engine.gpu_driver.device = device;
|
||||
engine.gpu_driver.backend_type = backend_type;
|
||||
engine.gpu_driver.surface = surface;
|
||||
engine.gpu_driver.swap_chain = swap_chain;
|
||||
engine.gpu_driver.swap_chain_format = swap_chain_format;
|
||||
engine.gpu_driver.current_desc = descriptor;
|
||||
engine.gpu_driver.target_desc = descriptor;
|
||||
|
||||
return GpuDriverNative{
|
||||
.native_instance = native_instance,
|
||||
};
|
||||
}
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue