mach: Move all platform specific files to platform/ directory
This commit is contained in:
parent
68190e863a
commit
21c49ff9be
7 changed files with 11 additions and 10 deletions
5
src/platform/c.zig
Normal file
5
src/platform/c.zig
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
pub const c = @cImport({
|
||||
@cInclude("dawn/webgpu.h");
|
||||
@cInclude("dawn/dawn_proc.h");
|
||||
@cInclude("dawn_native_mach.h");
|
||||
});
|
||||
254
src/platform/mach.js
Normal file
254
src/platform/mach.js
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
const original_title = document.title;
|
||||
const text_decoder = new TextDecoder();
|
||||
const text_encoder = new TextEncoder();
|
||||
let log_buf = "";
|
||||
|
||||
// TODO: Arrange in numberical order of value
|
||||
function convertKeyCode(code) {
|
||||
const mapKeyCode = {
|
||||
KeyA: 0,
|
||||
KeyB: 1,
|
||||
KeyC: 2,
|
||||
KeyD: 3,
|
||||
KeyE: 4,
|
||||
KeyF: 5,
|
||||
KeyG: 6,
|
||||
KeyH: 7,
|
||||
KeyI: 8,
|
||||
KeyJ: 9,
|
||||
KeyK: 10,
|
||||
KeyL: 11,
|
||||
KeyM: 12,
|
||||
KeyN: 13,
|
||||
KeyO: 14,
|
||||
KeyP: 15,
|
||||
KeyQ: 16,
|
||||
KeyR: 17,
|
||||
KeyS: 18,
|
||||
KeyT: 19,
|
||||
KeyU: 20,
|
||||
KeyV: 21,
|
||||
KeyW: 22,
|
||||
KeyX: 23,
|
||||
KeyY: 24,
|
||||
KeyZ: 25,
|
||||
Digit0: 26,
|
||||
Digit1: 27,
|
||||
Digit2: 28,
|
||||
Digit3: 29,
|
||||
Digit4: 30,
|
||||
Digit5: 31,
|
||||
Digit6: 32,
|
||||
Digit7: 33,
|
||||
Digit8: 34,
|
||||
Digit9: 35,
|
||||
Enter: 78,
|
||||
Escape: 79,
|
||||
Backspace: 105,
|
||||
Tab: 80,
|
||||
Space: 106,
|
||||
Minus: 107,
|
||||
Equal: 108,
|
||||
BracketLeft: 109,
|
||||
BracketRight: 110,
|
||||
Backslash: 111,
|
||||
ShiftLeft: 81,
|
||||
ShiftRight: 82,
|
||||
ControlLeft: 83,
|
||||
ControlRight: 84,
|
||||
AltLeft: 85,
|
||||
AltRight: 86,
|
||||
OSLeft: 87,
|
||||
MetaLeft: 87,
|
||||
OSRight: 88,
|
||||
MetaRight: 88,
|
||||
Semicolon: 112,
|
||||
Quote: 113,
|
||||
Backquote: 117,
|
||||
ContextMenu: 89,
|
||||
Comma: 114,
|
||||
Period: 115,
|
||||
Slash: 116,
|
||||
CapsLock: 91,
|
||||
PrintScreen: 92,
|
||||
ScrollLock: 93,
|
||||
Pause: 94,
|
||||
Insert: 100,
|
||||
Home: 96,
|
||||
PageUp: 98,
|
||||
Delete: 95,
|
||||
End: 97,
|
||||
PageDown: 99,
|
||||
ArrowRight: 102,
|
||||
ArrowLeft: 101,
|
||||
ArrowDown: 104,
|
||||
ArrowUp: 103,
|
||||
NumLock: 90,
|
||||
NumpadDivide: 61,
|
||||
NumpadMultiply: 62,
|
||||
NumpadSubtract: 63,
|
||||
NumpadAdd: 64,
|
||||
Numpad1: 66,
|
||||
Numpad2: 67,
|
||||
Numpad3: 68,
|
||||
Numpad4: 69,
|
||||
Numpad5: 70,
|
||||
Numpad6: 71,
|
||||
Numpad7: 72,
|
||||
Numpad8: 73,
|
||||
Numpad9: 74,
|
||||
Numpad0: 65,
|
||||
NumpadDecimal: 75,
|
||||
NumpadEqual: 76,
|
||||
NumpadEnter: 77,
|
||||
F1: 36,
|
||||
F2: 37,
|
||||
F3: 38,
|
||||
F4: 39,
|
||||
F5: 40,
|
||||
F6: 41,
|
||||
F7: 42,
|
||||
F8: 43,
|
||||
F9: 44,
|
||||
F10: 45,
|
||||
F11: 46,
|
||||
F12: 47,
|
||||
F13: 48,
|
||||
F14: 49,
|
||||
F15: 50,
|
||||
F16: 51,
|
||||
F17: 52,
|
||||
F18: 53,
|
||||
F19: 54,
|
||||
F20: 55,
|
||||
F21: 56,
|
||||
F22: 57,
|
||||
F23: 58,
|
||||
F24: 59,
|
||||
F25: 60,
|
||||
};
|
||||
|
||||
const k = mapKeyCode[code];
|
||||
if (k != undefined)
|
||||
return k;
|
||||
return 118; // Unknown
|
||||
}
|
||||
|
||||
const mach = {
|
||||
canvases: [],
|
||||
wasm: undefined,
|
||||
events: [],
|
||||
|
||||
init(wasm) {
|
||||
this.wasm = wasm;
|
||||
},
|
||||
|
||||
getString(str, len) {
|
||||
const memory = mach.wasm.exports.memory.buffer;
|
||||
return text_decoder.decode(new Uint8Array(memory, str, len));
|
||||
},
|
||||
|
||||
setString(str, buf) {
|
||||
const memory = this.wasm.exports.memory.buffer;
|
||||
const strbuf = text_encoder.encode(str);
|
||||
const outbuf = new Uint8Array(memory, buf, strbuf.length);
|
||||
for (let i = 0; i < strbuf.length; i += 1) {
|
||||
outbuf[i] = strbuf[i];
|
||||
}
|
||||
},
|
||||
|
||||
machLogWrite(str, len) {
|
||||
log_buf += mach.getString(str, len);
|
||||
},
|
||||
|
||||
machLogFlush() {
|
||||
console.log(log_buf);
|
||||
log_buf = "";
|
||||
},
|
||||
|
||||
machPanic(str, len) {
|
||||
throw Error(mach.getString(str, len));
|
||||
},
|
||||
|
||||
machCanvasInit(width, height, id) {
|
||||
let canvas = document.createElement("canvas");
|
||||
canvas.id = "#mach-canvas-" + mach.canvases.length;
|
||||
canvas.style.width = width + "px";
|
||||
canvas.style.height = height + "px";
|
||||
canvas.width = Math.floor(width * window.devicePixelRatio);
|
||||
canvas.height = Math.floor(height * window.devicePixelRatio);
|
||||
canvas.tabIndex = 1;
|
||||
|
||||
mach.setString(canvas.id, id);
|
||||
|
||||
canvas.addEventListener("contextmenu", (ev) => ev.preventDefault());
|
||||
|
||||
canvas.addEventListener("keydown", (ev) => {
|
||||
mach.events.push(...[1, convertKeyCode(ev.code)]);
|
||||
});
|
||||
|
||||
canvas.addEventListener("keyup", (ev) => {
|
||||
mach.events.push(...[2, convertKeyCode(ev.code)]);
|
||||
});
|
||||
|
||||
document.body.appendChild(canvas);
|
||||
return mach.canvases.push({ canvas: canvas, title: undefined }) - 1;
|
||||
},
|
||||
|
||||
machCanvasDeinit(canvas) {
|
||||
if (mach.canvases[canvas] != undefined) {
|
||||
mach.canvases.splice(canvas, 1);
|
||||
}
|
||||
},
|
||||
|
||||
machCanvasSetTitle(canvas, title, len) {
|
||||
const str = len > 0 ?
|
||||
mach.getString(title, len) :
|
||||
original_title;
|
||||
|
||||
mach.canvases[canvas].title = str;
|
||||
},
|
||||
|
||||
machCanvasSetSize(canvas, width, height) {
|
||||
const cv = mach.canvases[canvas];
|
||||
if (width > 0 && height > 0) {
|
||||
cv.canvas.style.width = width + "px";
|
||||
cv.canvas.style.height = height + "px";
|
||||
cv.canvas.width = width * window.devicePixelRatio;
|
||||
cv.canvas.height = height * window.devicePixelRatio;
|
||||
}
|
||||
},
|
||||
|
||||
machCanvasGetWindowWidth(canvas) {
|
||||
const cv = mach.canvases[canvas];
|
||||
return cv.canvas.width / window.devicePixelRatio;
|
||||
},
|
||||
|
||||
machCanvasGetWindowHeight(canvas) {
|
||||
const cv = mach.canvases[canvas];
|
||||
return cv.canvas.height / window.devicePixelRatio;
|
||||
},
|
||||
|
||||
machCanvasGetFramebufferWidth(canvas) {
|
||||
const cv = mach.canvases[canvas];
|
||||
return cv.canvas.width;
|
||||
},
|
||||
|
||||
machCanvasGetFramebufferHeight(canvas) {
|
||||
const cv = mach.canvases[canvas];
|
||||
return cv.canvas.height;
|
||||
},
|
||||
|
||||
machEventShift() {
|
||||
if (mach.events.length < 0)
|
||||
return 0;
|
||||
|
||||
return mach.events.shift();
|
||||
},
|
||||
|
||||
machPerfNow() {
|
||||
return performance.now();
|
||||
},
|
||||
};
|
||||
|
||||
export { mach };
|
||||
446
src/platform/native.zig
Normal file
446
src/platform/native.zig
Normal file
|
|
@ -0,0 +1,446 @@
|
|||
const std = @import("std");
|
||||
const glfw = @import("glfw");
|
||||
const gpu = @import("gpu");
|
||||
const App = @import("app");
|
||||
const Engine = @import("../Engine.zig");
|
||||
const structs = @import("../structs.zig");
|
||||
const enums = @import("../enums.zig");
|
||||
const util = @import("util.zig");
|
||||
const c = @import("c.zig").c;
|
||||
|
||||
pub const Core = struct {
|
||||
window: glfw.Window,
|
||||
backend_type: gpu.Adapter.BackendType,
|
||||
allocator: std.mem.Allocator,
|
||||
events: EventQueue = .{},
|
||||
user_ptr: UserPtr = undefined,
|
||||
|
||||
const EventQueue = std.TailQueue(structs.Event);
|
||||
const EventNode = EventQueue.Node;
|
||||
|
||||
const UserPtr = struct {
|
||||
core: *Core,
|
||||
};
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, engine: *Engine) !Core {
|
||||
const options = engine.options;
|
||||
const backend_type = try util.detectBackendType(allocator);
|
||||
|
||||
glfw.setErrorCallback(Core.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 Core{
|
||||
.window = window,
|
||||
.backend_type = backend_type,
|
||||
.allocator = engine.allocator,
|
||||
};
|
||||
}
|
||||
|
||||
fn pushEvent(self: *Core, event: structs.Event) void {
|
||||
const node = self.allocator.create(EventNode) catch unreachable;
|
||||
node.* = .{ .data = event };
|
||||
self.events.append(node);
|
||||
}
|
||||
|
||||
fn initCallback(self: *Core) void {
|
||||
self.user_ptr = UserPtr{ .core = self };
|
||||
|
||||
self.window.setUserPointer(&self.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;
|
||||
|
||||
switch (action) {
|
||||
.press => core.pushEvent(.{
|
||||
.key_press = .{
|
||||
.key = toMachKey(key),
|
||||
},
|
||||
}),
|
||||
.release => core.pushEvent(.{
|
||||
.key_release = .{
|
||||
.key = toMachKey(key),
|
||||
},
|
||||
}),
|
||||
else => {},
|
||||
}
|
||||
|
||||
_ = scancode;
|
||||
_ = mods;
|
||||
}
|
||||
}.callback;
|
||||
self.window.setKeyCallback(callback);
|
||||
}
|
||||
|
||||
pub fn setShouldClose(self: *Core, value: bool) void {
|
||||
self.window.setShouldClose(value);
|
||||
}
|
||||
|
||||
pub fn getFramebufferSize(self: *Core) !structs.Size {
|
||||
const size = try self.window.getFramebufferSize();
|
||||
return @bitCast(structs.Size, size);
|
||||
}
|
||||
|
||||
pub fn getWindowSize(self: *Core) !structs.Size {
|
||||
const size = try self.window.getSize();
|
||||
return @bitCast(structs.Size, size);
|
||||
}
|
||||
|
||||
pub fn setSizeLimits(self: *Core, min: structs.SizeOptional, max: structs.SizeOptional) !void {
|
||||
try self.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);
|
||||
return n.data;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn toMachKey(key: glfw.Key) enums.Key {
|
||||
return switch (key) {
|
||||
.a => .a,
|
||||
.b => .b,
|
||||
.c => .c,
|
||||
.d => .d,
|
||||
.e => .e,
|
||||
.f => .f,
|
||||
.g => .g,
|
||||
.h => .h,
|
||||
.i => .i,
|
||||
.j => .j,
|
||||
.k => .k,
|
||||
.l => .l,
|
||||
.m => .m,
|
||||
.n => .n,
|
||||
.o => .o,
|
||||
.p => .p,
|
||||
.q => .q,
|
||||
.r => .r,
|
||||
.s => .s,
|
||||
.t => .t,
|
||||
.u => .u,
|
||||
.v => .v,
|
||||
.w => .w,
|
||||
.x => .x,
|
||||
.y => .y,
|
||||
.z => .z,
|
||||
|
||||
.zero => .zero,
|
||||
.one => .one,
|
||||
.two => .two,
|
||||
.three => .three,
|
||||
.four => .four,
|
||||
.five => .five,
|
||||
.six => .six,
|
||||
.seven => .seven,
|
||||
.eight => .eight,
|
||||
.nine => .nine,
|
||||
|
||||
.F1 => .f1,
|
||||
.F2 => .f2,
|
||||
.F3 => .f3,
|
||||
.F4 => .f4,
|
||||
.F5 => .f5,
|
||||
.F6 => .f6,
|
||||
.F7 => .f7,
|
||||
.F8 => .f8,
|
||||
.F9 => .f9,
|
||||
.F10 => .f10,
|
||||
.F11 => .f11,
|
||||
.F12 => .f12,
|
||||
.F13 => .f13,
|
||||
.F14 => .f14,
|
||||
.F15 => .f15,
|
||||
.F16 => .f16,
|
||||
.F17 => .f17,
|
||||
.F18 => .f18,
|
||||
.F19 => .f19,
|
||||
.F20 => .f20,
|
||||
.F21 => .f21,
|
||||
.F22 => .f22,
|
||||
.F23 => .f23,
|
||||
.F24 => .f24,
|
||||
.F25 => .f25,
|
||||
|
||||
.kp_divide => .kp_divide,
|
||||
.kp_multiply => .kp_multiply,
|
||||
.kp_subtract => .kp_subtract,
|
||||
.kp_add => .kp_add,
|
||||
.kp_0 => .kp_0,
|
||||
.kp_1 => .kp_1,
|
||||
.kp_2 => .kp_2,
|
||||
.kp_3 => .kp_3,
|
||||
.kp_4 => .kp_4,
|
||||
.kp_5 => .kp_5,
|
||||
.kp_6 => .kp_6,
|
||||
.kp_7 => .kp_7,
|
||||
.kp_8 => .kp_8,
|
||||
.kp_9 => .kp_9,
|
||||
.kp_decimal => .kp_decimal,
|
||||
.kp_equal => .kp_equal,
|
||||
.kp_enter => .kp_enter,
|
||||
|
||||
.enter => .enter,
|
||||
.escape => .escape,
|
||||
.tab => .tab,
|
||||
.left_shift => .left_shift,
|
||||
.right_shift => .right_shift,
|
||||
.left_control => .left_control,
|
||||
.right_control => .right_control,
|
||||
.left_alt => .left_alt,
|
||||
.right_alt => .right_alt,
|
||||
.left_super => .left_super,
|
||||
.right_super => .right_super,
|
||||
.menu => .menu,
|
||||
.num_lock => .num_lock,
|
||||
.caps_lock => .caps_lock,
|
||||
.print_screen => .print,
|
||||
.scroll_lock => .scroll_lock,
|
||||
.pause => .pause,
|
||||
.delete => .delete,
|
||||
.home => .home,
|
||||
.end => .end,
|
||||
.page_up => .page_up,
|
||||
.page_down => .page_down,
|
||||
.insert => .insert,
|
||||
.left => .left,
|
||||
.right => .right,
|
||||
.up => .up,
|
||||
.down => .down,
|
||||
.backspace => .backspace,
|
||||
.space => .space,
|
||||
.minus => .minus,
|
||||
.equal => .equal,
|
||||
.left_bracket => .left_bracket,
|
||||
.right_bracket => .right_bracket,
|
||||
.backslash => .backslash,
|
||||
.semicolon => .semicolon,
|
||||
.apostrophe => .apostrophe,
|
||||
.comma => .comma,
|
||||
.period => .period,
|
||||
.slash => .slash,
|
||||
.grave_accent => .grave,
|
||||
|
||||
.world_1 => .unknown,
|
||||
.world_2 => .unknown,
|
||||
.unknown => .unknown,
|
||||
};
|
||||
}
|
||||
|
||||
/// 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 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 = 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 GpuDriver{
|
||||
.native_instance = native_instance,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const BackingTimer = std.time.Timer;
|
||||
|
||||
// TODO: check signatures
|
||||
comptime {
|
||||
if (!@hasDecl(App, "init")) @compileError("App must export 'pub fn init(app: *App, engine: *mach.Engine) !void'");
|
||||
if (!@hasDecl(App, "deinit")) @compileError("App must export 'pub fn deinit(app: *App, engine: *mach.Engine) void'");
|
||||
if (!@hasDecl(App, "update")) @compileError("App must export 'pub fn update(app: *App, engine: *mach.Engine) !bool'");
|
||||
}
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
const options = if (@hasDecl(App, "options")) App.options else structs.Options{};
|
||||
var engine = try Engine.init(allocator, options);
|
||||
var app: App = undefined;
|
||||
|
||||
try app.init(&engine);
|
||||
defer app.deinit(&engine);
|
||||
|
||||
// Glfw specific: initialize the user pointer used in callbacks
|
||||
engine.core.internal.initCallback();
|
||||
|
||||
const window = engine.core.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 = try window.getFramebufferSize();
|
||||
engine.gpu_driver.target_desc.width = framebuffer_size.width;
|
||||
engine.gpu_driver.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 (!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,
|
||||
.{ .render_attachment = true },
|
||||
engine.gpu_driver.target_desc.width,
|
||||
engine.gpu_driver.target_desc.height,
|
||||
);
|
||||
|
||||
if (@hasDecl(App, "resize")) {
|
||||
try app.resize(&engine, engine.gpu_driver.target_desc.width, engine.gpu_driver.target_desc.height);
|
||||
}
|
||||
engine.gpu_driver.current_desc = engine.gpu_driver.target_desc;
|
||||
}
|
||||
|
||||
const success = try app.update(&engine);
|
||||
if (!success)
|
||||
break;
|
||||
}
|
||||
}
|
||||
168
src/platform/util.zig
Normal file
168
src/platform/util.zig
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
const std = @import("std");
|
||||
|
||||
const glfw = @import("glfw");
|
||||
const gpu = @import("gpu");
|
||||
const c = @import("c.zig").c;
|
||||
const objc = @cImport({
|
||||
@cInclude("objc/message.h");
|
||||
});
|
||||
|
||||
fn printUnhandledError(_: void, typ: gpu.ErrorType, message: [*:0]const u8) 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}),
|
||||
.device_lost => std.debug.print("gpu: device lost: {s}\n", .{message}),
|
||||
.unknown => std.debug.print("gpu: unknown error: {s}\n", .{message}),
|
||||
else => unreachable,
|
||||
}
|
||||
std.os.exit(1);
|
||||
}
|
||||
pub var printUnhandledErrorCallback = gpu.ErrorCallback.init(void, {}, printUnhandledError);
|
||||
|
||||
fn getEnvVarOwned(allocator: std.mem.Allocator, key: []const u8) error{ OutOfMemory, InvalidUtf8 }!?[]u8 {
|
||||
return std.process.getEnvVarOwned(allocator, key) catch |err| switch (err) {
|
||||
error.EnvironmentVariableNotFound => @as(?[]u8, null),
|
||||
else => |e| e,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn detectBackendType(allocator: std.mem.Allocator) !gpu.Adapter.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, "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;
|
||||
@panic("unknown GPU_BACKEND type");
|
||||
}
|
||||
|
||||
const target = @import("builtin").target;
|
||||
if (target.isDarwin()) return .metal;
|
||||
if (target.os.tag == .windows) return .d3d12;
|
||||
return .vulkan;
|
||||
}
|
||||
|
||||
pub 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,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub 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 };
|
||||
return switch (target.os.tag) {
|
||||
.windows => .{ .win32 = true },
|
||||
.linux => .{ .x11 = true },
|
||||
else => .{},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn createSurfaceForWindow(
|
||||
native_instance: *const gpu.NativeInstance,
|
||||
window: glfw.Window,
|
||||
comptime glfw_options: glfw.BackendOptions,
|
||||
) 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),
|
||||
},
|
||||
} else if (glfw_options.cocoa) blk: {
|
||||
const ns_window = glfw_native.getCocoaWindow(window);
|
||||
const ns_view = msgSend(ns_window, "contentView", .{}, *anyopaque); // [nsWindow contentView]
|
||||
|
||||
// Create a CAMetalLayer that covers the whole window that will be passed to CreateSurface.
|
||||
msgSend(ns_view, "setWantsLayer:", .{true}, void); // [view setWantsLayer:YES]
|
||||
const layer = msgSend(objc.objc_getClass("CAMetalLayer"), "layer", .{}, ?*anyopaque); // [CAMetalLayer layer]
|
||||
if (layer == null) @panic("failed to create Metal layer");
|
||||
msgSend(ns_view, "setLayer:", .{layer.?}, void); // [view setLayer:layer]
|
||||
|
||||
// Use retina if the window was created with retina support.
|
||||
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.?,
|
||||
},
|
||||
};
|
||||
} 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");
|
||||
} else unreachable;
|
||||
|
||||
return native_instance.createSurface(&descriptor);
|
||||
}
|
||||
|
||||
// Borrowed from https://github.com/hazeycode/zig-objcrt
|
||||
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 => fn (@TypeOf(obj), objc.SEL) callconv(.C) ReturnType,
|
||||
1 => fn (@TypeOf(obj), objc.SEL, args_meta[0].field_type) callconv(.C) ReturnType,
|
||||
2 => fn (@TypeOf(obj), objc.SEL, args_meta[0].field_type, args_meta[1].field_type) callconv(.C) ReturnType,
|
||||
3 => fn (@TypeOf(obj), objc.SEL, args_meta[0].field_type, args_meta[1].field_type, args_meta[2].field_type) callconv(.C) ReturnType,
|
||||
4 => fn (@TypeOf(obj), objc.SEL, args_meta[0].field_type, args_meta[1].field_type, args_meta[2].field_type, args_meta[3].field_type) callconv(.C) ReturnType,
|
||||
else => @compileError("Unsupported number of args"),
|
||||
};
|
||||
|
||||
// NOTE: func is a var because making it const causes a compile error which I believe is a compiler bug
|
||||
var func = @ptrCast(FnType, objc.objc_msgSend);
|
||||
const sel = objc.sel_getUid(sel_name);
|
||||
|
||||
return @call(.{}, func, .{ obj, sel } ++ args);
|
||||
}
|
||||
165
src/platform/wasm.zig
Normal file
165
src/platform/wasm.zig
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
const std = @import("std");
|
||||
const App = @import("app");
|
||||
const Engine = @import("../Engine.zig");
|
||||
const structs = @import("../structs.zig");
|
||||
const enums = @import("../enums.zig");
|
||||
|
||||
const js = struct {
|
||||
extern fn machCanvasInit(width: u32, height: u32, selector_id: *u8) CanvasId;
|
||||
extern fn machCanvasDeinit(canvas: CanvasId) void;
|
||||
extern fn machCanvasSetTitle(canvas: CanvasId, title: [*]const u8, len: u32) void;
|
||||
extern fn machCanvasSetSize(canvas: CanvasId, width: u32, height: u32) void;
|
||||
extern fn machCanvasGetWindowWidth(canvas: CanvasId) u32;
|
||||
extern fn machCanvasGetWindowHeight(canvas: CanvasId) u32;
|
||||
extern fn machCanvasGetFramebufferWidth(canvas: CanvasId) u32;
|
||||
extern fn machCanvasGetFramebufferHeight(canvas: CanvasId) u32;
|
||||
extern fn machEventShift() u32;
|
||||
extern fn machPerfNow() f64;
|
||||
|
||||
extern fn machLog(str: [*]const u8, len: u32) void;
|
||||
extern fn machLogWrite(str: [*]const u8, len: u32) void;
|
||||
extern fn machLogFlush() void;
|
||||
extern fn machPanic(str: [*]const u8, len: u32) void;
|
||||
};
|
||||
|
||||
pub const CanvasId = u32;
|
||||
|
||||
pub const Core = struct {
|
||||
id: CanvasId,
|
||||
selector_id: []const u8,
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, eng: *Engine) !Core {
|
||||
const options = eng.options;
|
||||
var selector = [1]u8{0} ** 15;
|
||||
const id = js.machCanvasInit(options.width, options.height, &selector[0]);
|
||||
|
||||
const title = std.mem.span(options.title);
|
||||
js.machCanvasSetTitle(id, title.ptr, title.len);
|
||||
|
||||
return Core{
|
||||
.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 getFramebufferSize(core: *Core) !structs.Size {
|
||||
return structs.Size{
|
||||
.width = js.machCanvasGetFramebufferWidth(core.id),
|
||||
.height = js.machCanvasGetFramebufferHeight(core.id),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getWindowSize(core: *Core) !structs.Size {
|
||||
return structs.Size{
|
||||
.width = js.machCanvasGetWindowWidth(core.id),
|
||||
.height = js.machCanvasGetWindowHeight(core.id),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn setSizeLimits(_: *Core, _: structs.SizeOptional, _: structs.SizeOptional) !void {}
|
||||
|
||||
pub fn pollEvent(_: *Core) ?structs.Event {
|
||||
const event_type = js.machEventShift();
|
||||
|
||||
return switch (event_type) {
|
||||
1 => structs.Event{
|
||||
.key_press = .{ .key = @intToEnum(enums.Key, js.machEventShift()) },
|
||||
},
|
||||
2 => structs.Event{
|
||||
.key_release = .{ .key = @intToEnum(enums.Key, js.machEventShift()) },
|
||||
},
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const GpuDriver = struct {
|
||||
pub fn init(_: std.mem.Allocator, _: *Engine) !GpuDriver {
|
||||
return GpuDriver{};
|
||||
}
|
||||
};
|
||||
|
||||
pub const BackingTimer = struct {
|
||||
initial: f64 = undefined,
|
||||
|
||||
const WasmTimer = @This();
|
||||
|
||||
pub fn start() !WasmTimer {
|
||||
return WasmTimer{ .initial = js.machPerfNow() };
|
||||
}
|
||||
|
||||
pub fn read(timer: *WasmTimer) u64 {
|
||||
return timeToNs(js.machPerfNow() - timer.initial);
|
||||
}
|
||||
|
||||
pub fn reset(timer: *WasmTimer) void {
|
||||
timer.initial = js.machPerfNow();
|
||||
}
|
||||
|
||||
pub fn lap(timer: *WasmTimer) u64 {
|
||||
const now = js.machPerfNow();
|
||||
const initial = timer.initial;
|
||||
timer.initial = now;
|
||||
return timeToNs(now - initial);
|
||||
}
|
||||
|
||||
fn timeToNs(t: f64) u64 {
|
||||
return @floatToInt(u64, t) * 1000000;
|
||||
}
|
||||
};
|
||||
|
||||
var app: App = undefined;
|
||||
var engine: Engine = undefined;
|
||||
|
||||
export fn wasmInit() void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
// NOTE: On wasm, vsync is double by default and cannot be changed.
|
||||
// Hence options.vsync is not used anywhere.
|
||||
const options = if (@hasDecl(App, "options")) App.options else structs.Options{};
|
||||
engine = Engine.init(allocator, options) catch unreachable;
|
||||
|
||||
app.init(&engine) catch {};
|
||||
}
|
||||
|
||||
export fn wasmUpdate() bool {
|
||||
engine.delta_time_ns = engine.timer.lapPrecise();
|
||||
engine.delta_time = @intToFloat(f32, engine.delta_time_ns) / @intToFloat(f32, std.time.ns_per_s);
|
||||
|
||||
return app.update(&engine) catch false;
|
||||
}
|
||||
|
||||
export fn wasmDeinit() void {
|
||||
app.deinit(&engine);
|
||||
}
|
||||
|
||||
pub const log_level = .info;
|
||||
|
||||
const LogError = error{};
|
||||
const LogWriter = std.io.Writer(void, LogError, writeLog);
|
||||
|
||||
fn writeLog(_: void, msg: []const u8) LogError!usize {
|
||||
js.machLogWrite(msg.ptr, msg.len);
|
||||
return msg.len;
|
||||
}
|
||||
|
||||
pub fn log(
|
||||
comptime message_level: std.log.Level,
|
||||
comptime scope: @Type(.EnumLiteral),
|
||||
comptime format: []const u8,
|
||||
args: anytype,
|
||||
) void {
|
||||
const prefix = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";
|
||||
const writer = LogWriter{ .context = {} };
|
||||
|
||||
writer.print(message_level.asText() ++ prefix ++ format ++ "\n", args) catch return;
|
||||
js.machLogFlush();
|
||||
}
|
||||
|
||||
pub fn panic(msg: []const u8, _: ?*std.builtin.StackTrace) noreturn {
|
||||
js.machPanic(msg.ptr, msg.len);
|
||||
unreachable;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue