mach: merge and remove the extra layer of indirection caused by Core and

GpuDriver

Core and GpuDriver both are merged into one type called Platform. Also
previously the fields and methods which were called as
``engine.core.field`` will now be ``engine.field`` i.e an extra layer is
removed.
This commit is contained in:
iddev5 2022-05-31 23:13:58 +05:30 committed by Stephen Gutekanst
parent 3961073084
commit 50fe649ab1
4 changed files with 225 additions and 263 deletions

View file

@ -10,12 +10,6 @@ const Timer = @import("Timer.zig");
const Engine = @This();
/// Window, events, inputs etc.
core: Core,
/// WebGPU driver - stores device, swap chains, targets and more
gpu_driver: GpuDriver,
allocator: Allocator,
options: structs.Options,
@ -29,39 +23,6 @@ delta_time: f32 = 0,
delta_time_ns: u64 = 0,
timer: Timer,
pub const Core = struct {
internal: platform.CoreType,
pub fn setShouldClose(core: *Core, value: bool) void {
core.internal.setShouldClose(value);
}
// Returns the framebuffer size, in subpixel units.
//
// e.g. returns 1280x960 on macOS for a window that is 640x480
pub fn getFramebufferSize(core: *Core) structs.Size {
return core.internal.getFramebufferSize();
}
// Returns the widow size, in pixel units.
//
// e.g. returns 640x480 on macOS for a window that is 640x480
pub fn getWindowSize(core: *Core) structs.Size {
return core.internal.getWindowSize();
}
pub fn setSizeLimits(core: *Core, min: structs.SizeOptional, max: structs.SizeOptional) !void {
return core.internal.setSizeLimits(min, max);
}
pub fn pollEvent(core: *Core) ?structs.Event {
return core.internal.pollEvent();
}
};
pub const GpuDriver = struct {
internal: platform.GpuDriverType,
device: gpu.Device,
backend_type: gpu.Adapter.BackendType,
swap_chain: ?gpu.SwapChain,
@ -70,21 +31,42 @@ pub const GpuDriver = struct {
surface: ?gpu.Surface,
current_desc: gpu.SwapChain.Descriptor,
target_desc: gpu.SwapChain.Descriptor,
};
internal: platform.Type,
pub fn init(allocator: std.mem.Allocator, options: structs.Options) !Engine {
var engine = Engine{
.allocator = allocator,
.options = options,
.timer = try Timer.start(),
.core = undefined,
.gpu_driver = undefined,
};
var engine: Engine = undefined;
engine.allocator = allocator;
engine.options = options;
engine.timer = try Timer.start();
// Note: if in future, there is a conflict in init() signature of different backends,
// move these calls to the entry point file, which is native.zig for Glfw, for example
engine.core.internal = try platform.CoreType.init(allocator, &engine);
engine.gpu_driver.internal = try platform.GpuDriverType.init(allocator, &engine);
engine.internal = try platform.Type.init(allocator, &engine);
return engine;
}
pub fn setShouldClose(engine: *Engine, value: bool) void {
engine.internal.setShouldClose(value);
}
// Returns the framebuffer size, in subpixel units.
//
// e.g. returns 1280x960 on macOS for a window that is 640x480
pub fn getFramebufferSize(engine: *Engine) structs.Size {
return engine.internal.getFramebufferSize();
}
// Returns the widow size, in pixel units.
//
// e.g. returns 1280x960 on macOS for a window that is 640x480
pub fn getWindowSize(engine: *Engine) structs.Size {
return engine.internal.getWindowSize();
}
pub fn setSizeLimits(engine: *Engine, min: structs.SizeOptional, max: structs.SizeOptional) !void {
return engine.internal.setSizeLimits(min, max);
}
pub fn pollEvent(engine: *Engine) ?structs.Event {
return engine.internal.pollEvent();
}

View file

@ -3,6 +3,5 @@ const builtin = @import("builtin");
const Platform = if (builtin.cpu.arch == .wasm32) @import("platform/wasm.zig") else @import("platform/native.zig");
// TODO: verify declarations and its signatures
pub const CoreType = Platform.Core;
pub const GpuDriverType = Platform.GpuDriver;
pub const Type = Platform.Platform;
pub const BackingTimerType = Platform.BackingTimer;

View file

@ -8,7 +8,7 @@ const enums = @import("../enums.zig");
const util = @import("util.zig");
const c = @import("c.zig").c;
pub const Core = struct {
pub const Platform = struct {
window: glfw.Window,
backend_type: gpu.Adapter.BackendType,
allocator: std.mem.Allocator,
@ -18,18 +18,20 @@ pub const Core = struct {
last_window_size: structs.Size,
last_framebuffer_size: structs.Size,
native_instance: gpu.NativeInstance,
const EventQueue = std.TailQueue(structs.Event);
const EventNode = EventQueue.Node;
const UserPtr = struct {
core: *Core,
platform: *Platform,
};
pub fn init(allocator: std.mem.Allocator, engine: *Engine) !Core {
pub fn init(allocator: std.mem.Allocator, engine: *Engine) !Platform {
const options = engine.options;
const backend_type = try util.detectBackendType(allocator);
glfw.setErrorCallback(Core.errorCallback);
glfw.setErrorCallback(Platform.errorCallback);
try glfw.init(.{});
// Create the test window and discover adapters using it (esp. for OpenGL)
@ -47,37 +49,160 @@ pub const Core = struct {
const window_size = try window.getSize();
const framebuffer_size = try window.getFramebufferSize();
return Core{
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);
},
};
// 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.device = device;
engine.backend_type = backend_type;
engine.surface = surface;
engine.swap_chain = swap_chain;
engine.swap_chain_format = swap_chain_format;
engine.current_desc = descriptor;
engine.target_desc = descriptor;
return Platform{
.window = window,
.backend_type = backend_type,
.allocator = engine.allocator,
.last_window_size = .{ .width = window_size.width, .height = window_size.height },
.last_framebuffer_size = .{ .width = framebuffer_size.width, .height = framebuffer_size.height },
.native_instance = native_instance,
};
}
fn pushEvent(self: *Core, event: structs.Event) void {
const node = self.allocator.create(EventNode) catch unreachable;
fn pushEvent(platform: *Platform, event: structs.Event) void {
const node = platform.allocator.create(EventNode) catch unreachable;
node.* = .{ .data = event };
self.events.append(node);
platform.events.append(node);
}
fn initCallback(self: *Core) void {
self.user_ptr = UserPtr{ .core = self };
fn initCallback(platform: *Platform) void {
platform.user_ptr = UserPtr{ .platform = platform };
self.window.setUserPointer(&self.user_ptr);
platform.window.setUserPointer(&platform.user_ptr);
const callback = struct {
fn callback(window: glfw.Window, key: glfw.Key, scancode: i32, action: glfw.Action, mods: glfw.Mods) void {
const core = (window.getUserPointer(UserPtr) orelse unreachable).core;
const pf = (window.getUserPointer(UserPtr) orelse unreachable).platform;
switch (action) {
.press => core.pushEvent(.{
.press => pf.pushEvent(.{
.key_press = .{
.key = toMachKey(key),
},
}),
.release => core.pushEvent(.{
.release => pf.pushEvent(.{
.key_release = .{
.key = toMachKey(key),
},
@ -89,49 +214,49 @@ pub const Core = struct {
_ = mods;
}
}.callback;
self.window.setKeyCallback(callback);
platform.window.setKeyCallback(callback);
const size_callback = struct {
fn callback(window: glfw.Window, width: i32, height: i32) void {
const core = (window.getUserPointer(UserPtr) orelse unreachable).core;
core.last_window_size.width = @intCast(u32, width);
core.last_window_size.height = @intCast(u32, height);
const pf = (window.getUserPointer(UserPtr) orelse unreachable).platform;
pf.last_window_size.width = @intCast(u32, width);
pf.last_window_size.height = @intCast(u32, height);
}
}.callback;
self.window.setSizeCallback(size_callback);
platform.window.setSizeCallback(size_callback);
const framebuffer_size_callback = struct {
fn callback(window: glfw.Window, width: u32, height: u32) void {
const core = (window.getUserPointer(UserPtr) orelse unreachable).core;
core.last_framebuffer_size.width = width;
core.last_framebuffer_size.height = height;
const pf = (window.getUserPointer(UserPtr) orelse unreachable).platform;
pf.last_framebuffer_size.width = width;
pf.last_framebuffer_size.height = height;
}
}.callback;
self.window.setFramebufferSizeCallback(framebuffer_size_callback);
platform.window.setFramebufferSizeCallback(framebuffer_size_callback);
}
pub fn setShouldClose(self: *Core, value: bool) void {
self.window.setShouldClose(value);
pub fn setShouldClose(platform: *Platform, value: bool) void {
platform.window.setShouldClose(value);
}
pub fn getFramebufferSize(self: *Core) structs.Size {
return self.last_framebuffer_size;
pub fn getFramebufferSize(platform: *Platform) structs.Size {
return platform.last_framebuffer_size;
}
pub fn getWindowSize(self: *Core) structs.Size {
return self.last_window_size;
pub fn getWindowSize(platform: *Platform) structs.Size {
return platform.last_window_size;
}
pub fn setSizeLimits(self: *Core, min: structs.SizeOptional, max: structs.SizeOptional) !void {
try self.window.setSizeLimits(
pub fn setSizeLimits(platform: *Platform, min: structs.SizeOptional, max: structs.SizeOptional) !void {
try platform.window.setSizeLimits(
@bitCast(glfw.Window.SizeOptional, min),
@bitCast(glfw.Window.SizeOptional, max),
);
}
pub fn pollEvent(self: *Core) ?structs.Event {
if (self.events.popFirst()) |n| {
defer self.allocator.destroy(n);
pub fn pollEvent(platform: *Platform) ?structs.Event {
if (platform.events.popFirst()) |n| {
defer platform.allocator.destroy(n);
return n.data;
}
return null;
@ -274,144 +399,6 @@ pub const Core = struct {
}
};
pub const GpuDriver = struct {
native_instance: gpu.NativeInstance,
pub fn init(_: std.mem.Allocator, engine: *Engine) !GpuDriver {
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 = engine.core.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 GpuDriver{
.native_instance = native_instance,
};
}
};
pub const BackingTimer = std.time.Timer;
const common = @import("common.zig");
@ -431,34 +418,34 @@ pub fn main() !void {
defer app.deinit(&engine);
// Glfw specific: initialize the user pointer used in callbacks
engine.core.internal.initCallback();
engine.internal.initCallback();
const window = engine.core.internal.window;
const window = engine.internal.window;
while (!window.shouldClose()) {
try glfw.pollEvents();
engine.delta_time_ns = engine.timer.lapPrecise();
engine.delta_time = @intToFloat(f32, engine.delta_time_ns) / @intToFloat(f32, std.time.ns_per_s);
var framebuffer_size = engine.core.getFramebufferSize();
engine.gpu_driver.target_desc.width = framebuffer_size.width;
engine.gpu_driver.target_desc.height = framebuffer_size.height;
var framebuffer_size = engine.getFramebufferSize();
engine.target_desc.width = framebuffer_size.width;
engine.target_desc.height = framebuffer_size.height;
if (engine.gpu_driver.swap_chain == null or !engine.gpu_driver.current_desc.equal(&engine.gpu_driver.target_desc)) {
const use_legacy_api = engine.gpu_driver.surface == null;
if (engine.swap_chain == null or !engine.current_desc.equal(&engine.target_desc)) {
const use_legacy_api = engine.surface == null;
if (!use_legacy_api) {
engine.gpu_driver.swap_chain = engine.gpu_driver.device.nativeCreateSwapChain(engine.gpu_driver.surface, &engine.gpu_driver.target_desc);
} else engine.gpu_driver.swap_chain.?.configure(
engine.gpu_driver.swap_chain_format,
engine.swap_chain = engine.device.nativeCreateSwapChain(engine.surface, &engine.target_desc);
} else engine.swap_chain.?.configure(
engine.swap_chain_format,
.{ .render_attachment = true },
engine.gpu_driver.target_desc.width,
engine.gpu_driver.target_desc.height,
engine.target_desc.width,
engine.target_desc.height,
);
if (@hasDecl(App, "resize")) {
try app.resize(&engine, engine.gpu_driver.target_desc.width, engine.gpu_driver.target_desc.height);
try app.resize(&engine, engine.target_desc.width, engine.target_desc.height);
}
engine.gpu_driver.current_desc = engine.gpu_driver.target_desc;
engine.current_desc = engine.target_desc;
}
const success = try app.update(&engine);

View file

@ -24,11 +24,11 @@ const js = struct {
pub const CanvasId = u32;
pub const Core = struct {
pub const Platform = struct {
id: CanvasId,
selector_id: []const u8,
pub fn init(allocator: std.mem.Allocator, eng: *Engine) !Core {
pub fn init(allocator: std.mem.Allocator, eng: *Engine) !Platform {
const options = eng.options;
var selector = [1]u8{0} ** 15;
const id = js.machCanvasInit(options.width, options.height, &selector[0]);
@ -36,31 +36,31 @@ pub const Core = struct {
const title = std.mem.span(options.title);
js.machCanvasSetTitle(id, title.ptr, title.len);
return Core{
return Platform{
.id = id,
.selector_id = try allocator.dupe(u8, selector[0 .. selector.len - @as(u32, if (selector[selector.len - 1] == 0) 1 else 0)]),
};
}
pub fn setShouldClose(_: *Core, _: bool) void {}
pub fn setShouldClose(_: *Platform, _: bool) void {}
pub fn getFramebufferSize(core: *Core) structs.Size {
pub fn getFramebufferSize(platform: *Platform) structs.Size {
return structs.Size{
.width = js.machCanvasGetFramebufferWidth(core.id),
.height = js.machCanvasGetFramebufferHeight(core.id),
.width = js.machCanvasGetFramebufferWidth(platform.id),
.height = js.machCanvasGetFramebufferHeight(platform.id),
};
}
pub fn getWindowSize(core: *Core) structs.Size {
pub fn getWindowSize(platform: *Platform) structs.Size {
return structs.Size{
.width = js.machCanvasGetWindowWidth(core.id),
.height = js.machCanvasGetWindowHeight(core.id),
.width = js.machCanvasGetWindowWidth(platform.id),
.height = js.machCanvasGetWindowHeight(platform.id),
};
}
pub fn setSizeLimits(_: *Core, _: structs.SizeOptional, _: structs.SizeOptional) !void {}
pub fn setSizeLimits(_: *Platform, _: structs.SizeOptional, _: structs.SizeOptional) !void {}
pub fn pollEvent(_: *Core) ?structs.Event {
pub fn pollEvent(_: *Platform) ?structs.Event {
const event_type = js.machEventShift();
return switch (event_type) {
@ -75,12 +75,6 @@ pub const Core = struct {
}
};
pub const GpuDriver = struct {
pub fn init(_: std.mem.Allocator, _: *Engine) !GpuDriver {
return GpuDriver{};
}
};
pub const BackingTimer = struct {
initial: f64 = undefined,