mach: fundamental changes
- Core doesn't depend to `App` anymore - `setOptions` has replaced with some new functions (`setTitle`, `setSize`, etc) - and more
This commit is contained in:
parent
91a53807ab
commit
1d7cd4be80
26 changed files with 2306 additions and 1999 deletions
471
src/Core.zig
471
src/Core.zig
|
|
@ -1,100 +1,403 @@
|
|||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const builtin = @import("builtin");
|
||||
const glfw = @import("glfw");
|
||||
const std = @import("std");
|
||||
const gpu = @import("gpu");
|
||||
const platform = @import("platform.zig");
|
||||
const structs = @import("structs.zig");
|
||||
const enums = @import("enums.zig");
|
||||
const Timer = @import("Timer.zig");
|
||||
|
||||
const Core = @This();
|
||||
pub const Core = @This();
|
||||
|
||||
allocator: Allocator,
|
||||
internal: *platform.Core,
|
||||
|
||||
options: structs.Options,
|
||||
pub const Options = struct {
|
||||
is_app: bool = false,
|
||||
title: [*:0]const u8 = "Mach Engine",
|
||||
size: Size = .{ .width = 640, .height = 640 },
|
||||
power_preference: gpu.PowerPreference = .undefined,
|
||||
required_features: ?[]const gpu.FeatureName = null,
|
||||
required_limits: ?gpu.Limits = null,
|
||||
};
|
||||
|
||||
/// 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,
|
||||
/// instead of writing (360.0 / 60.0) and assuming the frame rate is 60hz, write
|
||||
/// (360.0 * core.delta_time)
|
||||
delta_time: f32 = 0,
|
||||
delta_time_ns: u64 = 0,
|
||||
timer: Timer,
|
||||
|
||||
device: *gpu.Device,
|
||||
backend_type: gpu.BackendType,
|
||||
swap_chain: ?*gpu.SwapChain,
|
||||
swap_chain_format: gpu.Texture.Format,
|
||||
|
||||
surface: ?*gpu.Surface,
|
||||
current_desc: gpu.SwapChain.Descriptor,
|
||||
target_desc: gpu.SwapChain.Descriptor,
|
||||
|
||||
internal: platform.Type,
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, core: *Core) !void {
|
||||
core.allocator = allocator;
|
||||
core.options = structs.Options{};
|
||||
core.timer = try Timer.start();
|
||||
core.internal = try platform.Type.init(allocator, core);
|
||||
pub fn init(allocator: std.mem.Allocator, options: Options) !Core {
|
||||
return .{
|
||||
.internal = try platform.Core.init(allocator, options),
|
||||
};
|
||||
}
|
||||
|
||||
/// Set runtime options for application, like title, window size etc.
|
||||
///
|
||||
/// See mach.Options for details
|
||||
pub fn setOptions(core: *Core, options: structs.Options) !void {
|
||||
try core.internal.setOptions(options);
|
||||
core.options = options;
|
||||
}
|
||||
|
||||
// Signals mach to stop the update loop.
|
||||
pub fn close(core: *Core) void {
|
||||
core.internal.close();
|
||||
}
|
||||
|
||||
// Sets seconds to wait for an event with timeout before calling update()
|
||||
// again.
|
||||
//
|
||||
// timeout is in seconds (<= 0.0 disables waiting)
|
||||
// - pass std.math.inf(f64) to wait with no timeout
|
||||
//
|
||||
// update() can be called earlier than timeout if an event happens (key press,
|
||||
// mouse motion, etc.)
|
||||
//
|
||||
// update() can be called a bit later than timeout due to timer precision and
|
||||
// process scheduling.
|
||||
pub fn setWaitEvent(core: *Core, timeout: f64) void {
|
||||
core.internal.setWaitEvent(timeout);
|
||||
}
|
||||
|
||||
// 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 window size, in pixel units.
|
||||
//
|
||||
// e.g. returns 1280x960 on macOS for a window that is 640x480
|
||||
pub fn getWindowSize(core: *Core) structs.Size {
|
||||
return core.internal.getWindowSize();
|
||||
}
|
||||
|
||||
pub fn setMouseCursor(core: *Core, cursor: enums.MouseCursor) !void {
|
||||
try core.internal.setMouseCursor(cursor);
|
||||
}
|
||||
|
||||
pub fn setCursorMode(core: *Core, mode: enums.CursorMode) !void {
|
||||
try core.internal.setCursorMode(mode);
|
||||
pub fn deinit(core: *Core) void {
|
||||
return core.internal.deinit();
|
||||
}
|
||||
|
||||
pub fn hasEvent(core: *Core) bool {
|
||||
return core.internal.hasEvent();
|
||||
}
|
||||
|
||||
pub fn pollEvent(core: *Core) ?structs.Event {
|
||||
return core.internal.pollEvent();
|
||||
pub fn pollEvents(core: *Core) ?Event {
|
||||
return core.internal.pollEvents();
|
||||
}
|
||||
|
||||
/// Returns the framebuffer size, in subpixel units.
|
||||
pub fn framebufferSize(core: *Core) Size {
|
||||
return core.internal.framebufferSize();
|
||||
}
|
||||
|
||||
/// Sets seconds to wait for an event with timeout when calling `Core.update()`
|
||||
/// again.
|
||||
///
|
||||
/// timeout is in seconds (<= `0.0` disables waiting)
|
||||
/// - pass `std.math.inf(f64)` to wait with no timeout
|
||||
///
|
||||
/// `Core.update()` will return earlier than timeout if an event happens (key press,
|
||||
/// mouse motion, etc.)
|
||||
///
|
||||
/// `Core.update()` can return a bit later than timeout due to timer precision and
|
||||
/// process scheduling.
|
||||
pub fn setWaitTimeout(core: *Core, timeout: f64) void {
|
||||
return core.internal.setWaitTimeout(timeout);
|
||||
}
|
||||
|
||||
/// Set the window title
|
||||
pub fn setTitle(core: *Core, title: [:0]const u8) void {
|
||||
return core.internal.setTitle(title);
|
||||
}
|
||||
|
||||
/// Set the window mode
|
||||
pub fn setDisplayMode(core: *Core, mode: DisplayMode, monitor: ?usize) void {
|
||||
return core.internal.setDisplayMode(mode, monitor);
|
||||
}
|
||||
|
||||
/// Returns the window mode
|
||||
pub fn displayMode(core: *Core) DisplayMode {
|
||||
return core.internal.displayMode();
|
||||
}
|
||||
|
||||
pub fn setBorder(core: *Core, value: bool) void {
|
||||
return core.internal.setBorder(value);
|
||||
}
|
||||
|
||||
pub fn border(core: *Core) bool {
|
||||
return core.internal.border();
|
||||
}
|
||||
|
||||
pub fn setHeadless(core: *Core, value: bool) void {
|
||||
return core.internal.setHeadless(value);
|
||||
}
|
||||
|
||||
pub fn headless(core: *Core) bool {
|
||||
return core.internal.headless();
|
||||
}
|
||||
|
||||
pub const VSyncMode = enum {
|
||||
/// Potential screen tearing.
|
||||
/// No synchronization with monitor, render frames as fast as possible.
|
||||
///
|
||||
/// Not available on WASM, fallback to double
|
||||
none,
|
||||
|
||||
/// No tearing, synchronizes rendering with monitor refresh rate, rendering frames when ready.
|
||||
///
|
||||
/// Tries to stay one frame ahead of the monitor, so when it's ready for the next frame it is
|
||||
/// already prepared.
|
||||
double,
|
||||
|
||||
/// No tearing, synchronizes rendering with monitor refresh rate, rendering frames when ready.
|
||||
///
|
||||
/// Tries to stay two frames ahead of the monitor, so when it's ready for the next frame it is
|
||||
/// already prepared.
|
||||
///
|
||||
/// Not available on WASM, fallback to double
|
||||
triple,
|
||||
};
|
||||
|
||||
/// Set monitor synchronization mode.
|
||||
pub fn setVSync(core: *Core, mode: VSyncMode) void {
|
||||
return core.internal.setVSync(mode);
|
||||
}
|
||||
|
||||
/// Returns monitor synchronization mode.
|
||||
pub fn vsync(core: *Core) VSyncMode {
|
||||
return core.internal.vsync();
|
||||
}
|
||||
|
||||
/// Set the window size, in subpixel units.
|
||||
pub fn setSize(core: *Core, value: Size) void {
|
||||
return core.internal.setSize(value);
|
||||
}
|
||||
|
||||
/// Returns the window size, in subpixel units.
|
||||
pub fn size(core: *Core) Size {
|
||||
return core.internal.size();
|
||||
}
|
||||
|
||||
/// Set the minimum and maximum allowed size for the window.
|
||||
pub fn setSizeLimit(core: *Core, size_limit: SizeLimit) void {
|
||||
return core.internal.setSizeLimit(size_limit);
|
||||
}
|
||||
|
||||
/// Returns the minimum and maximum allowed size for the window.
|
||||
pub fn sizeLimit(core: *Core) SizeLimit {
|
||||
return core.internal.sizeLimit();
|
||||
}
|
||||
|
||||
pub fn setCursorMode(core: *Core, mode: CursorMode) void {
|
||||
return core.internal.setCursorMode(mode);
|
||||
}
|
||||
|
||||
pub fn cursorMode(core: *Core) CursorMode {
|
||||
return core.internal.cursorMode();
|
||||
}
|
||||
|
||||
pub fn setCursorShape(core: *Core, cursor: CursorShape) void {
|
||||
return core.internal.setCursorShape(cursor);
|
||||
}
|
||||
|
||||
pub fn cursorShape(core: *Core) CursorShape {
|
||||
return core.internal.cursorShape();
|
||||
}
|
||||
|
||||
pub fn adapter(core: *Core) *gpu.Adapter {
|
||||
return core.internal.adapter();
|
||||
}
|
||||
|
||||
pub fn device(core: *Core) *gpu.Device {
|
||||
return core.internal.device();
|
||||
}
|
||||
|
||||
pub fn swapChain(core: *Core) *gpu.SwapChain {
|
||||
return core.internal.swapChain();
|
||||
}
|
||||
|
||||
pub fn descriptor(core: *Core) gpu.SwapChain.Descriptor {
|
||||
return core.internal.descriptor();
|
||||
}
|
||||
|
||||
pub const Size = struct {
|
||||
width: u32,
|
||||
height: u32,
|
||||
};
|
||||
|
||||
pub const SizeOptional = struct {
|
||||
width: ?u32,
|
||||
height: ?u32,
|
||||
};
|
||||
|
||||
pub const SizeLimit = struct {
|
||||
min: SizeOptional,
|
||||
max: SizeOptional,
|
||||
};
|
||||
|
||||
pub const Position = struct {
|
||||
x: f64,
|
||||
y: f64,
|
||||
};
|
||||
|
||||
pub const Event = union(enum) {
|
||||
key_press: KeyEvent,
|
||||
key_repeat: KeyEvent,
|
||||
key_release: KeyEvent,
|
||||
char_input: struct {
|
||||
codepoint: u21,
|
||||
},
|
||||
mouse_motion: struct {
|
||||
pos: Position,
|
||||
},
|
||||
mouse_press: MouseButtonEvent,
|
||||
mouse_release: MouseButtonEvent,
|
||||
mouse_scroll: struct {
|
||||
xoffset: f32,
|
||||
yoffset: f32,
|
||||
},
|
||||
framebuffer_resize: Size,
|
||||
focus_gained,
|
||||
focus_lost,
|
||||
close,
|
||||
};
|
||||
|
||||
pub const KeyEvent = struct {
|
||||
key: Key,
|
||||
mods: KeyMods,
|
||||
};
|
||||
|
||||
pub const MouseButtonEvent = struct {
|
||||
button: MouseButton,
|
||||
pos: Position,
|
||||
mods: KeyMods,
|
||||
};
|
||||
|
||||
pub const MouseButton = enum {
|
||||
left,
|
||||
right,
|
||||
middle,
|
||||
four,
|
||||
five,
|
||||
six,
|
||||
seven,
|
||||
eight,
|
||||
};
|
||||
|
||||
pub const Key = enum {
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
d,
|
||||
e,
|
||||
f,
|
||||
g,
|
||||
h,
|
||||
i,
|
||||
j,
|
||||
k,
|
||||
l,
|
||||
m,
|
||||
n,
|
||||
o,
|
||||
p,
|
||||
q,
|
||||
r,
|
||||
s,
|
||||
t,
|
||||
u,
|
||||
v,
|
||||
w,
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
|
||||
zero,
|
||||
one,
|
||||
two,
|
||||
three,
|
||||
four,
|
||||
five,
|
||||
six,
|
||||
seven,
|
||||
eight,
|
||||
nine,
|
||||
|
||||
f1,
|
||||
f2,
|
||||
f3,
|
||||
f4,
|
||||
f5,
|
||||
f6,
|
||||
f7,
|
||||
f8,
|
||||
f9,
|
||||
f10,
|
||||
f11,
|
||||
f12,
|
||||
f13,
|
||||
f14,
|
||||
f15,
|
||||
f16,
|
||||
f17,
|
||||
f18,
|
||||
f19,
|
||||
f20,
|
||||
f21,
|
||||
f22,
|
||||
f23,
|
||||
f24,
|
||||
f25,
|
||||
|
||||
kp_divide,
|
||||
kp_multiply,
|
||||
kp_subtract,
|
||||
kp_add,
|
||||
kp_0,
|
||||
kp_1,
|
||||
kp_2,
|
||||
kp_3,
|
||||
kp_4,
|
||||
kp_5,
|
||||
kp_6,
|
||||
kp_7,
|
||||
kp_8,
|
||||
kp_9,
|
||||
kp_decimal,
|
||||
kp_equal,
|
||||
kp_enter,
|
||||
|
||||
enter,
|
||||
escape,
|
||||
tab,
|
||||
left_shift,
|
||||
right_shift,
|
||||
left_control,
|
||||
right_control,
|
||||
left_alt,
|
||||
right_alt,
|
||||
left_super,
|
||||
right_super,
|
||||
menu,
|
||||
num_lock,
|
||||
caps_lock,
|
||||
print,
|
||||
scroll_lock,
|
||||
pause,
|
||||
delete,
|
||||
home,
|
||||
end,
|
||||
page_up,
|
||||
page_down,
|
||||
insert,
|
||||
left,
|
||||
right,
|
||||
up,
|
||||
down,
|
||||
backspace,
|
||||
space,
|
||||
minus,
|
||||
equal,
|
||||
left_bracket,
|
||||
right_bracket,
|
||||
backslash,
|
||||
semicolon,
|
||||
apostrophe,
|
||||
comma,
|
||||
period,
|
||||
slash,
|
||||
grave,
|
||||
|
||||
unknown,
|
||||
};
|
||||
|
||||
pub const KeyMods = packed struct {
|
||||
shift: bool,
|
||||
control: bool,
|
||||
alt: bool,
|
||||
super: bool,
|
||||
caps_lock: bool,
|
||||
num_lock: bool,
|
||||
_reserved: u2 = 0,
|
||||
};
|
||||
|
||||
pub const DisplayMode = enum {
|
||||
windowed,
|
||||
fullscreen,
|
||||
// TODO: fullscreen_windowed,
|
||||
};
|
||||
|
||||
pub const CursorMode = enum {
|
||||
/// Makes the cursor visible and behaving normally.
|
||||
normal,
|
||||
|
||||
/// Makes the cursor invisible when it is over the content area of the window but does not
|
||||
/// restrict it from leaving.
|
||||
hidden,
|
||||
|
||||
/// Hides and grabs the cursor, providing virtual and unlimited cursor movement. This is useful
|
||||
/// for implementing for example 3D camera controls.
|
||||
disabled,
|
||||
};
|
||||
|
||||
pub const CursorShape = enum {
|
||||
arrow,
|
||||
ibeam,
|
||||
crosshair,
|
||||
pointing_hand,
|
||||
resize_ew,
|
||||
resize_ns,
|
||||
resize_nwse,
|
||||
resize_nesw,
|
||||
resize_all,
|
||||
not_allowed,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
const std = @import("std");
|
||||
const platform = @import("platform.zig");
|
||||
|
||||
const Timer = @This();
|
||||
pub const Timer = @This();
|
||||
|
||||
backing_timer: platform.BackingTimerType = undefined,
|
||||
internal: platform.Timer,
|
||||
|
||||
/// Initialize the timer.
|
||||
pub fn start() !Timer {
|
||||
return Timer{
|
||||
.backing_timer = try platform.BackingTimerType.start(),
|
||||
.internal = try platform.Timer.start(),
|
||||
};
|
||||
}
|
||||
|
||||
/// Reads the timer value since start or the last reset in nanoseconds.
|
||||
pub inline fn readPrecise(timer: *Timer) u64 {
|
||||
return timer.backing_timer.read();
|
||||
return timer.internal.read();
|
||||
}
|
||||
|
||||
/// Reads the timer value since start or the last reset in seconds.
|
||||
|
|
@ -24,12 +24,12 @@ pub inline fn read(timer: *Timer) f32 {
|
|||
|
||||
/// Resets the timer value to 0/now.
|
||||
pub inline fn reset(timer: *Timer) void {
|
||||
timer.backing_timer.reset();
|
||||
timer.internal.reset();
|
||||
}
|
||||
|
||||
/// Returns the current value of the timer in nanoseconds, then resets it.
|
||||
pub inline fn lapPrecise(timer: *Timer) u64 {
|
||||
return timer.backing_timer.lap();
|
||||
return timer.internal.lap();
|
||||
}
|
||||
|
||||
/// Returns the current value of the timer in seconds, then resets it.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
const std = @import("std");
|
||||
pub const Core = @import("Core.zig");
|
||||
pub const gpu = @import("gpu");
|
||||
pub const ecs = @import("ecs");
|
||||
|
|
@ -12,6 +13,9 @@ pub const module = ecs.Module(.{
|
|||
},
|
||||
});
|
||||
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
pub fn App(
|
||||
comptime modules: anytype,
|
||||
comptime app_init: anytype, // fn (engine: *ecs.World(modules)) !void
|
||||
|
|
@ -22,26 +26,32 @@ pub fn App(
|
|||
return struct {
|
||||
engine: ecs.World(modules),
|
||||
|
||||
pub fn init(app: *@This(), core: *Core) !void {
|
||||
pub fn init(app: *@This()) !void {
|
||||
app.* = .{
|
||||
.engine = try ecs.World(modules).init(core.allocator),
|
||||
.engine = try ecs.World(modules).init(allocator),
|
||||
};
|
||||
app.*.engine.set(.mach, .core, core);
|
||||
app.*.engine.set(.mach, .device, core.device);
|
||||
var core = try allocator.create(Core);
|
||||
core.* = try Core.init(allocator, .{});
|
||||
app.engine.set(.mach, .core, core);
|
||||
app.engine.set(.mach, .device, core.device());
|
||||
try app_init(&app.engine);
|
||||
}
|
||||
|
||||
pub fn deinit(app: *@This(), _: *Core) void {
|
||||
pub fn deinit(app: *@This()) void {
|
||||
const core = app.engine.get(.mach, .core);
|
||||
core.deinit();
|
||||
allocator.destroy(core);
|
||||
app.engine.deinit();
|
||||
_ = gpa.deinit();
|
||||
}
|
||||
|
||||
pub fn update(app: *@This(), _: *Core) !void {
|
||||
pub fn update(app: *@This()) !bool {
|
||||
app.engine.tick();
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn resize(app: *@This(), core: *Core, width: u32, height: u32) !void {
|
||||
pub fn resize(app: *@This(), width: u32, height: u32) !void {
|
||||
_ = app;
|
||||
_ = core;
|
||||
_ = width;
|
||||
_ = height;
|
||||
// TODO: send resize messages to ECS modules
|
||||
|
|
|
|||
40
src/entry.zig
Normal file
40
src/entry.zig
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
const builtin = @import("builtin");
|
||||
|
||||
pub usingnamespace @import("platform.zig").entry;
|
||||
|
||||
comptime {
|
||||
if (!builtin.is_test) {
|
||||
if (!@hasDecl(@import("app"), "App")) {
|
||||
@compileError("expected e.g. `pub const App = mach.App(modules, init)' (App definition missing in your main Zig file)");
|
||||
}
|
||||
|
||||
const App = @import("app").App;
|
||||
if (@typeInfo(App) != .Struct) {
|
||||
@compileError("App must be a struct type. Found:" ++ @typeName(App));
|
||||
}
|
||||
|
||||
if (@hasDecl(App, "init")) {
|
||||
const InitFn = @TypeOf(@field(App, "init"));
|
||||
if (InitFn != fn (*App) @typeInfo(@typeInfo(InitFn).Fn.return_type.?).ErrorUnion.error_set!void)
|
||||
@compileError("expected 'pub fn init(app: *App) !void' found '" ++ @typeName(InitFn) ++ "'");
|
||||
} else {
|
||||
@compileError("App must export 'pub fn init(app: *App) !void'");
|
||||
}
|
||||
|
||||
if (@hasDecl(App, "update")) {
|
||||
const UpdateFn = @TypeOf(@field(App, "update"));
|
||||
if (UpdateFn != fn (app: *App) @typeInfo(@typeInfo(UpdateFn).Fn.return_type.?).ErrorUnion.error_set!bool)
|
||||
@compileError("expected 'pub fn update(app: *App) !bool' found '" ++ @typeName(UpdateFn) ++ "'");
|
||||
} else {
|
||||
@compileError("App must export 'pub fn update(app: *App) !bool'");
|
||||
}
|
||||
|
||||
if (@hasDecl(App, "deinit")) {
|
||||
const DeinitFn = @TypeOf(@field(App, "deinit"));
|
||||
if (DeinitFn != fn (app: *App) void)
|
||||
@compileError("expected 'pub fn deinit(app: *App) void' found '" ++ @typeName(DeinitFn) ++ "'");
|
||||
} else {
|
||||
@compileError("App must export 'pub fn deinit(app: *App) void'");
|
||||
}
|
||||
}
|
||||
}
|
||||
185
src/enums.zig
185
src/enums.zig
|
|
@ -1,185 +0,0 @@
|
|||
pub const VSyncMode = enum {
|
||||
/// Potential screen tearing.
|
||||
/// No synchronization with monitor, render frames as fast as possible.
|
||||
///
|
||||
/// Not available on WASM, fallback to double
|
||||
none,
|
||||
|
||||
/// No tearing, synchronizes rendering with monitor refresh rate, rendering frames when ready.
|
||||
///
|
||||
/// Tries to stay one frame ahead of the monitor, so when it's ready for the next frame it is
|
||||
/// already prepared.
|
||||
double,
|
||||
|
||||
/// No tearing, synchronizes rendering with monitor refresh rate, rendering frames when ready.
|
||||
///
|
||||
/// Tries to stay two frames ahead of the monitor, so when it's ready for the next frame it is
|
||||
/// already prepared.
|
||||
///
|
||||
/// Not available on WASM, fallback to double
|
||||
triple,
|
||||
};
|
||||
|
||||
pub const MouseCursor = enum {
|
||||
arrow,
|
||||
ibeam,
|
||||
crosshair,
|
||||
pointing_hand,
|
||||
resize_ew,
|
||||
resize_ns,
|
||||
resize_nwse,
|
||||
resize_nesw,
|
||||
resize_all,
|
||||
not_allowed,
|
||||
};
|
||||
|
||||
pub const CursorMode = enum {
|
||||
/// Makes the cursor visible and behaving normally.
|
||||
normal,
|
||||
|
||||
/// Makes the cursor invisible when it is over the content area of the window but does not
|
||||
/// restrict it from leaving.
|
||||
hidden,
|
||||
|
||||
/// Hides and grabs the cursor, providing virtual and unlimited cursor movement. This is useful
|
||||
/// for implementing for example 3D camera controls.
|
||||
disabled,
|
||||
};
|
||||
|
||||
pub const MouseButton = enum {
|
||||
left,
|
||||
right,
|
||||
middle,
|
||||
four,
|
||||
five,
|
||||
six,
|
||||
seven,
|
||||
eight,
|
||||
};
|
||||
|
||||
pub const Key = enum {
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
d,
|
||||
e,
|
||||
f,
|
||||
g,
|
||||
h,
|
||||
i,
|
||||
j,
|
||||
k,
|
||||
l,
|
||||
m,
|
||||
n,
|
||||
o,
|
||||
p,
|
||||
q,
|
||||
r,
|
||||
s,
|
||||
t,
|
||||
u,
|
||||
v,
|
||||
w,
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
|
||||
zero,
|
||||
one,
|
||||
two,
|
||||
three,
|
||||
four,
|
||||
five,
|
||||
six,
|
||||
seven,
|
||||
eight,
|
||||
nine,
|
||||
|
||||
f1,
|
||||
f2,
|
||||
f3,
|
||||
f4,
|
||||
f5,
|
||||
f6,
|
||||
f7,
|
||||
f8,
|
||||
f9,
|
||||
f10,
|
||||
f11,
|
||||
f12,
|
||||
f13,
|
||||
f14,
|
||||
f15,
|
||||
f16,
|
||||
f17,
|
||||
f18,
|
||||
f19,
|
||||
f20,
|
||||
f21,
|
||||
f22,
|
||||
f23,
|
||||
f24,
|
||||
f25,
|
||||
|
||||
kp_divide,
|
||||
kp_multiply,
|
||||
kp_subtract,
|
||||
kp_add,
|
||||
kp_0,
|
||||
kp_1,
|
||||
kp_2,
|
||||
kp_3,
|
||||
kp_4,
|
||||
kp_5,
|
||||
kp_6,
|
||||
kp_7,
|
||||
kp_8,
|
||||
kp_9,
|
||||
kp_decimal,
|
||||
kp_equal,
|
||||
kp_enter,
|
||||
|
||||
enter,
|
||||
escape,
|
||||
tab,
|
||||
left_shift,
|
||||
right_shift,
|
||||
left_control,
|
||||
right_control,
|
||||
left_alt,
|
||||
right_alt,
|
||||
left_super,
|
||||
right_super,
|
||||
menu,
|
||||
num_lock,
|
||||
caps_lock,
|
||||
print,
|
||||
scroll_lock,
|
||||
pause,
|
||||
delete,
|
||||
home,
|
||||
end,
|
||||
page_up,
|
||||
page_down,
|
||||
insert,
|
||||
left,
|
||||
right,
|
||||
up,
|
||||
down,
|
||||
backspace,
|
||||
space,
|
||||
minus,
|
||||
equal,
|
||||
left_bracket,
|
||||
right_bracket,
|
||||
backslash,
|
||||
semicolon,
|
||||
apostrophe,
|
||||
comma,
|
||||
period,
|
||||
slash,
|
||||
grave,
|
||||
|
||||
unknown,
|
||||
};
|
||||
14
src/main.zig
14
src/main.zig
|
|
@ -1,27 +1,21 @@
|
|||
pub usingnamespace @import("structs.zig");
|
||||
pub usingnamespace @import("enums.zig");
|
||||
pub usingnamespace @import("entry.zig");
|
||||
pub const Core = @import("Core.zig");
|
||||
pub const Timer = @import("Timer.zig");
|
||||
pub const ResourceManager = @import("resource/ResourceManager.zig");
|
||||
|
||||
pub const gpu = @import("gpu");
|
||||
pub const ecs = @import("ecs");
|
||||
pub const sysaudio = @import("sysaudio");
|
||||
pub const sysjs = @import("sysjs");
|
||||
pub const earcut = @import("earcut");
|
||||
pub const gfx = @import("gfx/util.zig");
|
||||
pub const ResourceManager = @import("resource/ResourceManager.zig");
|
||||
|
||||
// Engine exports
|
||||
pub const App = @import("engine.zig").App;
|
||||
pub const module = @import("engine.zig").module;
|
||||
|
||||
const std = @import("std");
|
||||
const testing = std.testing;
|
||||
|
||||
test {
|
||||
// TODO: can't reference because they import "app"
|
||||
// testing.refAllDeclsRecursive(Core);
|
||||
// testing.refAllDeclsRecursive(Timer);
|
||||
testing.refAllDeclsRecursive(ResourceManager);
|
||||
testing.refAllDeclsRecursive(gfx);
|
||||
std.testing.refAllDeclsRecursive(ResourceManager);
|
||||
std.testing.refAllDeclsRecursive(gfx);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,42 +1,62 @@
|
|||
const builtin = @import("builtin");
|
||||
|
||||
const Platform = if (builtin.cpu.arch == .wasm32)
|
||||
Interface(@import("platform/wasm.zig"))
|
||||
pub usingnamespace if (builtin.cpu.arch == .wasm32)
|
||||
@import("platform/wasm.zig")
|
||||
else
|
||||
Interface(@import("platform/native.zig"));
|
||||
@import("platform/native.zig");
|
||||
|
||||
pub const Type = Platform.Platform;
|
||||
pub const BackingTimerType = Platform.BackingTimer;
|
||||
// Verifies that a platform implementation exposes the expected function declarations.
|
||||
comptime {
|
||||
assertHasDecl(@This(), "entry");
|
||||
assertHasDecl(@This(), "Core");
|
||||
assertHasDecl(@This(), "Timer");
|
||||
|
||||
/// Verifies that a Platform implementation exposes the expected function declarations.
|
||||
fn Interface(comptime T: type) type {
|
||||
assertHasDecl(T, "Platform");
|
||||
assertHasDecl(T, "BackingTimer");
|
||||
assertHasDecl(T.Platform, "init");
|
||||
assertHasDecl(T.Platform, "deinit");
|
||||
assertHasDecl(T.Platform, "setOptions");
|
||||
assertHasDecl(T.Platform, "close");
|
||||
assertHasDecl(T.Platform, "setWaitEvent");
|
||||
assertHasDecl(T.Platform, "getFramebufferSize");
|
||||
assertHasDecl(T.Platform, "getWindowSize");
|
||||
assertHasDecl(T.Platform, "setMouseCursor");
|
||||
assertHasDecl(T.Platform, "setCursorMode");
|
||||
assertHasDecl(T.Platform, "hasEvent");
|
||||
assertHasDecl(T.Platform, "pollEvent");
|
||||
assertHasDecl(T.BackingTimer, "start");
|
||||
assertHasDecl(T.BackingTimer, "read");
|
||||
assertHasDecl(T.BackingTimer, "reset");
|
||||
assertHasDecl(T.BackingTimer, "lap");
|
||||
// Core
|
||||
assertHasDecl(@This().Core, "init");
|
||||
assertHasDecl(@This().Core, "deinit");
|
||||
assertHasDecl(@This().Core, "hasEvent");
|
||||
assertHasDecl(@This().Core, "pollEvents");
|
||||
assertHasDecl(@This().Core, "framebufferSize");
|
||||
|
||||
return T;
|
||||
}
|
||||
assertHasDecl(@This().Core, "setWaitTimeout");
|
||||
assertHasDecl(@This().Core, "setTitle");
|
||||
|
||||
fn assertDecl(comptime T: anytype, comptime name: []const u8, comptime Decl: type) void {
|
||||
assertHasDecl(T, name);
|
||||
const FoundDecl = @TypeOf(@field(T, name));
|
||||
if (FoundDecl != Decl) @compileError("Platform field '" ++ name ++ "'\n\texpected type: " ++ @typeName(Decl) ++ "\n\t found type: " ++ @typeName(FoundDecl));
|
||||
assertHasDecl(@This().Core, "setDisplayMode");
|
||||
assertHasDecl(@This().Core, "displayMode");
|
||||
|
||||
assertHasDecl(@This().Core, "setBorder");
|
||||
assertHasDecl(@This().Core, "border");
|
||||
|
||||
assertHasDecl(@This().Core, "setHeadless");
|
||||
assertHasDecl(@This().Core, "headless");
|
||||
|
||||
assertHasDecl(@This().Core, "setVSync");
|
||||
assertHasDecl(@This().Core, "vsync");
|
||||
|
||||
assertHasDecl(@This().Core, "setSize");
|
||||
assertHasDecl(@This().Core, "size");
|
||||
|
||||
assertHasDecl(@This().Core, "setSizeLimit");
|
||||
assertHasDecl(@This().Core, "sizeLimit");
|
||||
|
||||
assertHasDecl(@This().Core, "setCursorMode");
|
||||
assertHasDecl(@This().Core, "cursorMode");
|
||||
|
||||
assertHasDecl(@This().Core, "setCursorShape");
|
||||
assertHasDecl(@This().Core, "cursorShape");
|
||||
|
||||
assertHasDecl(@This().Core, "adapter");
|
||||
assertHasDecl(@This().Core, "device");
|
||||
assertHasDecl(@This().Core, "swapChain");
|
||||
assertHasDecl(@This().Core, "descriptor");
|
||||
|
||||
// Timer
|
||||
assertHasDecl(@This().Timer, "start");
|
||||
assertHasDecl(@This().Timer, "read");
|
||||
assertHasDecl(@This().Timer, "reset");
|
||||
assertHasDecl(@This().Timer, "lap");
|
||||
}
|
||||
|
||||
fn assertHasDecl(comptime T: anytype, comptime name: []const u8) void {
|
||||
if (!@hasDecl(T, name)) @compileError("Platform missing declaration: " ++ name);
|
||||
if (!@hasDecl(T, name)) @compileError("Core missing declaration: " ++ name);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
const Core = @import("../Core.zig");
|
||||
|
||||
pub fn checkApplication(comptime app_pkg: type) void {
|
||||
if (!@hasDecl(app_pkg, "App")) {
|
||||
@compileError("expected e.g. `pub const App = mach.App(modules, init)' (App definition missing in your main Zig file)");
|
||||
}
|
||||
const App = app_pkg.App;
|
||||
|
||||
if (@typeInfo(App) != .Struct) {
|
||||
@compileError("App must be a struct type. Found:" ++ @typeName(App));
|
||||
}
|
||||
|
||||
if (@hasDecl(App, "init")) {
|
||||
const InitFn = @TypeOf(@field(App, "init"));
|
||||
if (InitFn != fn (app: *App, core: *Core) @typeInfo(@typeInfo(InitFn).Fn.return_type.?).ErrorUnion.error_set!void)
|
||||
@compileError("expected 'pub fn init(app: *App, core: *mach.Core) !void' found '" ++ @typeName(InitFn) ++ "'");
|
||||
} else {
|
||||
@compileError("App must export 'pub fn init(app: *App, core: *mach.Core) !void'");
|
||||
}
|
||||
|
||||
if (@hasDecl(App, "update")) {
|
||||
const UpdateFn = @TypeOf(@field(App, "update"));
|
||||
if (UpdateFn != fn (app: *App, core: *Core) @typeInfo(@typeInfo(UpdateFn).Fn.return_type.?).ErrorUnion.error_set!void)
|
||||
@compileError("expected 'pub fn update(app: *App, core: *mach.Core) !void' found '" ++ @typeName(UpdateFn) ++ "'");
|
||||
} else {
|
||||
@compileError("App must export 'pub fn update(app: *App, core: *mach.Core) !void'");
|
||||
}
|
||||
|
||||
if (@hasDecl(App, "deinit")) {
|
||||
const DeinitFn = @TypeOf(@field(App, "deinit"));
|
||||
if (DeinitFn != fn (app: *App, core: *Core) void)
|
||||
@compileError("expected 'pub fn deinit(app: *App, core: *mach.Core) void' found '" ++ @typeName(DeinitFn) ++ "'");
|
||||
} else {
|
||||
@compileError("App must export 'pub fn deinit(app: *App, core: *mach.Core) void'");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
const std = @import("std");
|
||||
const Core = @import("../Core.zig");
|
||||
const gpu = @import("gpu");
|
||||
const ecs = @import("ecs");
|
||||
const glfw = @import("glfw");
|
||||
const Core = @import("../Core.zig");
|
||||
const native = @import("native.zig");
|
||||
|
||||
pub const App = @This();
|
||||
|
|
@ -11,54 +11,32 @@ pub const GPUInterface = gpu.dawn.Interface;
|
|||
|
||||
const _ = gpu.Export(GPUInterface);
|
||||
|
||||
// Dummy init, deinit, and update functions
|
||||
pub fn init(_: *App, _: *Core) !void {}
|
||||
|
||||
pub fn deinit(_: *App, _: *Core) void {}
|
||||
|
||||
pub fn update(_: *App, _: *Core) !void {}
|
||||
|
||||
// Current Limitations:
|
||||
// 1. Currently, ecs seems to be using some weird compile-time type trickery, so I'm not exactly sure how
|
||||
// `engine` should be integrated into the C API
|
||||
// 2. Core might need to expose more state so more API functions can be exposed (for example, the WebGPU API)
|
||||
// 3. Be very careful about arguments, types, memory, etc - any mismatch will result in undefined behavior
|
||||
|
||||
pub export fn mach_core_close(core: *Core) void {
|
||||
core.close();
|
||||
}
|
||||
|
||||
pub export fn mach_core_delta_time(core: *Core) f32 {
|
||||
return core.delta_time;
|
||||
}
|
||||
|
||||
pub export fn mach_core_window_should_close(core: *Core) bool {
|
||||
return core.internal.window.shouldClose();
|
||||
}
|
||||
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
// Returns a pointer to a newly allocated Core
|
||||
// Will return a null pointer if an error occurred while initializing Core
|
||||
pub export fn mach_core_init() ?*Core {
|
||||
pub export fn mach_core_init() ?*native.Core {
|
||||
gpu.Impl.init();
|
||||
const core = native.coreInit(allocator) catch {
|
||||
return @intToPtr(?*Core, 0);
|
||||
const core = native.Core.init(allocator, .{}) catch {
|
||||
return @intToPtr(?*native.Core, 0);
|
||||
};
|
||||
return core;
|
||||
}
|
||||
|
||||
pub export fn mach_core_deinit(core: *Core) void {
|
||||
native.coreDeinit(core, allocator);
|
||||
pub export fn mach_core_deinit(core: *native.Core) void {
|
||||
native.Core.deinit(core);
|
||||
}
|
||||
|
||||
pub export fn mach_core_update(core: *Core, resize: ?native.CoreResizeCallback) MachStatus {
|
||||
native.coreUpdate(core, resize) catch {
|
||||
return MachStatus.Error;
|
||||
};
|
||||
return MachStatus.Success;
|
||||
}
|
||||
// pub export fn mach_core_poll_events(core: *native.Core) Core.Event {
|
||||
// return native.Core.pollEvents(core);
|
||||
// }
|
||||
|
||||
const MachStatus = enum(c_int) {
|
||||
Success = 0x00000000,
|
||||
|
|
|
|||
|
|
@ -1,367 +0,0 @@
|
|||
const original_title = document.title;
|
||||
const text_decoder = new TextDecoder();
|
||||
const text_encoder = new TextEncoder();
|
||||
let log_buf = "";
|
||||
|
||||
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,
|
||||
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,
|
||||
NumpadDivide: 61,
|
||||
NumpadMultiply: 62,
|
||||
NumpadSubtract: 63,
|
||||
NumpadAdd: 64,
|
||||
Numpad0: 65,
|
||||
Numpad1: 66,
|
||||
Numpad2: 67,
|
||||
Numpad3: 68,
|
||||
Numpad4: 69,
|
||||
Numpad5: 70,
|
||||
Numpad6: 71,
|
||||
Numpad7: 72,
|
||||
Numpad8: 73,
|
||||
Numpad9: 74,
|
||||
NumpadDecimal: 75,
|
||||
NumpadEqual: 76,
|
||||
NumpadEnter: 77,
|
||||
Enter: 78,
|
||||
Escape: 79,
|
||||
Tab: 80,
|
||||
ShiftLeft: 81,
|
||||
ShiftRight: 82,
|
||||
ControlLeft: 83,
|
||||
ControlRight: 84,
|
||||
AltLeft: 85,
|
||||
AltRight: 86,
|
||||
OSLeft: 87,
|
||||
MetaLeft: 87,
|
||||
OSRight: 88,
|
||||
MetaRight: 88,
|
||||
ContextMenu: 89,
|
||||
NumLock: 90,
|
||||
CapsLock: 91,
|
||||
PrintScreen: 92,
|
||||
ScrollLock: 93,
|
||||
Pause: 94,
|
||||
Delete: 95,
|
||||
Home: 96,
|
||||
End: 97,
|
||||
PageUp: 98,
|
||||
PageDown: 99,
|
||||
Insert: 100,
|
||||
ArrowLeft: 101,
|
||||
ArrowRight: 102,
|
||||
ArrowUp: 103,
|
||||
ArrowDown: 104,
|
||||
Backspace: 105,
|
||||
Space: 106,
|
||||
Minus: 107,
|
||||
Equal: 108,
|
||||
BracketLeft: 109,
|
||||
BracketRight: 110,
|
||||
Backslash: 111,
|
||||
Semicolon: 112,
|
||||
Quote: 113,
|
||||
Comma: 114,
|
||||
Period: 115,
|
||||
Slash: 116,
|
||||
Backquote: 117,
|
||||
};
|
||||
|
||||
const k = mapKeyCode[code];
|
||||
if (k != undefined)
|
||||
return k;
|
||||
return 118; // Unknown
|
||||
}
|
||||
|
||||
const mach = {
|
||||
canvases: [],
|
||||
wasm: undefined,
|
||||
observer: undefined,
|
||||
events: [],
|
||||
changes: [],
|
||||
wait_event_timeout: 0,
|
||||
|
||||
init(wasm) {
|
||||
this.wasm = wasm;
|
||||
this.observer = new MutationObserver((mutables) => {
|
||||
mutables.forEach((mutable) => {
|
||||
if (mutable.type === 'attributes') {
|
||||
if (mutable.attributeName === "width" || mutable.attributeName === "height") {
|
||||
mutable.target.dispatchEvent(new Event("mach-canvas-resize"));
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
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(id) {
|
||||
let canvas = document.createElement("canvas");
|
||||
canvas.id = "#mach-canvas-" + mach.canvases.length;
|
||||
canvas.style.border = "1px solid";
|
||||
canvas.style.position = "absolute";
|
||||
canvas.style.display = "block";
|
||||
canvas.tabIndex = 1;
|
||||
|
||||
mach.observer.observe(canvas, { attributes: true });
|
||||
|
||||
mach.setString(canvas.id, id);
|
||||
|
||||
canvas.addEventListener("contextmenu", (ev) => ev.preventDefault());
|
||||
|
||||
canvas.addEventListener("keydown", (ev) => {
|
||||
if (ev.repeat) {
|
||||
mach.events.push(...[2, convertKeyCode(ev.code)]);
|
||||
} else {
|
||||
mach.events.push(...[1, convertKeyCode(ev.code)]);
|
||||
}
|
||||
});
|
||||
|
||||
canvas.addEventListener("keyup", (ev) => {
|
||||
mach.events.push(...[3, convertKeyCode(ev.code)]);
|
||||
});
|
||||
|
||||
canvas.addEventListener("mousemove", (ev) => {
|
||||
mach.events.push(...[4, ev.clientX, ev.clientY]);
|
||||
});
|
||||
|
||||
canvas.addEventListener("mousedown", (ev) => {
|
||||
mach.events.push(...[5, ev.button]);
|
||||
});
|
||||
|
||||
canvas.addEventListener("mouseup", (ev) => {
|
||||
mach.events.push(...[6, ev.button]);
|
||||
});
|
||||
|
||||
canvas.addEventListener("wheel", (ev) => {
|
||||
mach.events.push(...[7, ev.deltaX, ev.deltaY]);
|
||||
});
|
||||
|
||||
canvas.addEventListener("focus", (ev) => {
|
||||
mach.events.push(...[8]);
|
||||
});
|
||||
|
||||
canvas.addEventListener("blur", (ev) => {
|
||||
mach.events.push(...[9]);
|
||||
});
|
||||
|
||||
canvas.addEventListener("mach-canvas-resize", (ev) => {
|
||||
const cv_index = mach.canvases.findIndex((el) => el.canvas === ev.currentTarget);
|
||||
const cv = mach.canvases[cv_index];
|
||||
mach.changes.push(...[1, cv.canvas.width, cv.canvas.height, window.devicePixelRatio]);
|
||||
});
|
||||
|
||||
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 = Math.floor(width * window.devicePixelRatio);
|
||||
cv.canvas.height = Math.floor(height * window.devicePixelRatio);
|
||||
}
|
||||
},
|
||||
|
||||
machCanvasSetFullscreen(canvas, value) {
|
||||
const cv = mach.canvases[canvas];
|
||||
if (value) {
|
||||
cv.canvas.style.border = "0px";
|
||||
cv.canvas.style.width = "100%";
|
||||
cv.canvas.style.height = "100%";
|
||||
cv.canvas.style.top = "0";
|
||||
cv.canvas.style.left = "0";
|
||||
cv.canvas.style.margin = "0px";
|
||||
} else {
|
||||
cv.canvas.style.border = "1px solid;"
|
||||
cv.canvas.style.top = "2px";
|
||||
cv.canvas.style.left = "2px";
|
||||
}
|
||||
},
|
||||
|
||||
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;
|
||||
},
|
||||
|
||||
machEmitCloseEvent() {
|
||||
window.dispatchEvent(new Event("mach-close"));
|
||||
},
|
||||
|
||||
machSetMouseCursor(cursor_ptr, len) {
|
||||
let mach_name = mach.getString(cursor_ptr, len);
|
||||
|
||||
if (mach_name === 'arrow') document.body.style.cursor = 'default';
|
||||
else if (mach_name === 'ibeam') document.body.style.cursor = 'text';
|
||||
else if (mach_name === 'crosshair') document.body.style.cursor = 'crosshair';
|
||||
else if (mach_name === 'pointing_hand') document.body.style.cursor = 'pointer';
|
||||
else if (mach_name === 'resize_ew') document.body.style.cursor = 'ew-resize';
|
||||
else if (mach_name === 'resize_ns') document.body.style.cursor = 'ns-resize';
|
||||
else if (mach_name === 'resize_nwse') document.body.style.cursor = 'nwse-resize';
|
||||
else if (mach_name === 'resize_nesw') document.body.style.cursor = 'nesw-resize';
|
||||
else if (mach_name === 'resize_all') document.body.style.cursor = 'move';
|
||||
else if (mach_name === 'not_allowed') document.body.style.cursor = 'not-allowed';
|
||||
else {
|
||||
console.log("machSetMouseCursor failed for " + mach_name);
|
||||
}
|
||||
},
|
||||
|
||||
machSetCursorMode(cursor_ptr, len) {
|
||||
let mach_name = mach.getString(cursor_ptr, len);
|
||||
|
||||
if (mach_name === 'normal') document.body.style.cursor = 'default';
|
||||
else if (mach_name === 'hidden' || mach_name === 'disabled') document.body.style.cursor = 'none';
|
||||
else {
|
||||
console.log("machSetMouseCursor failed for " + mach_name);
|
||||
}
|
||||
},
|
||||
|
||||
machSetWaitEvent(timeout) {
|
||||
mach.wait_event_timeout = timeout;
|
||||
},
|
||||
|
||||
machHasEvent() {
|
||||
return (mach.events.length > 0);
|
||||
},
|
||||
|
||||
machEventShift() {
|
||||
if (mach.events.length === 0)
|
||||
return 0;
|
||||
|
||||
return mach.events.shift();
|
||||
},
|
||||
|
||||
machEventShiftFloat() {
|
||||
return mach.machEventShift();
|
||||
},
|
||||
|
||||
machChangeShift() {
|
||||
if (mach.changes.length === 0)
|
||||
return 0;
|
||||
|
||||
return mach.changes.shift();
|
||||
},
|
||||
|
||||
machPerfNow() {
|
||||
return performance.now();
|
||||
},
|
||||
};
|
||||
|
||||
export { mach };
|
||||
|
|
@ -1,714 +1,5 @@
|
|||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const glfw = @import("glfw");
|
||||
const gpu = @import("gpu");
|
||||
const app_pkg = @import("app");
|
||||
const Core = @import("../Core.zig");
|
||||
const structs = @import("../structs.zig");
|
||||
const enums = @import("../enums.zig");
|
||||
const util = @import("util.zig");
|
||||
|
||||
const common = @import("common.zig");
|
||||
comptime {
|
||||
common.checkApplication(app_pkg);
|
||||
}
|
||||
const App = app_pkg.App;
|
||||
|
||||
pub const scope_levels = if (@hasDecl(App, "scope_levels")) App.scope_levels else [0]std.log.ScopeLevel{};
|
||||
pub const log_level = if (@hasDecl(App, "log_level")) App.log_level else std.log.default_level;
|
||||
|
||||
pub const Platform = struct {
|
||||
window: glfw.Window,
|
||||
core: *Core,
|
||||
backend_type: gpu.BackendType,
|
||||
allocator: std.mem.Allocator,
|
||||
events: EventQueue = .{},
|
||||
user_ptr: UserPtr = undefined,
|
||||
|
||||
last_window_size: structs.Size,
|
||||
last_framebuffer_size: structs.Size,
|
||||
last_position: glfw.Window.Pos,
|
||||
wait_event_timeout: f64 = 0.0,
|
||||
|
||||
cursors: [@typeInfo(enums.MouseCursor).Enum.fields.len]?glfw.Cursor =
|
||||
std.mem.zeroes([@typeInfo(enums.MouseCursor).Enum.fields.len]?glfw.Cursor),
|
||||
cursors_tried: [@typeInfo(enums.MouseCursor).Enum.fields.len]bool =
|
||||
[_]bool{false} ** @typeInfo(enums.MouseCursor).Enum.fields.len,
|
||||
|
||||
// TODO: these can be moved to Core
|
||||
instance: *gpu.Instance,
|
||||
adapter: *gpu.Adapter,
|
||||
|
||||
last_cursor_position: structs.WindowPos,
|
||||
|
||||
linux_gamemode: ?bool,
|
||||
|
||||
const EventQueue = std.TailQueue(structs.Event);
|
||||
const EventNode = EventQueue.Node;
|
||||
|
||||
const UserPtr = struct {
|
||||
platform: *Platform,
|
||||
};
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, core: *Core) !Platform {
|
||||
const options = core.options;
|
||||
const backend_type = try util.detectBackendType(allocator);
|
||||
|
||||
defer glfw.clearError();
|
||||
glfw.setErrorCallback(Platform.errorCallback);
|
||||
if (!glfw.init(.{})) try glfw.getErrorCode();
|
||||
|
||||
// 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 = glfw.Window.create(
|
||||
options.width,
|
||||
options.height,
|
||||
options.title,
|
||||
null,
|
||||
null,
|
||||
hints,
|
||||
) orelse return glfw.mustGetErrorCode();
|
||||
|
||||
if (backend_type == .opengl) glfw.makeContextCurrent(window);
|
||||
if (backend_type == .opengles) glfw.makeContextCurrent(window);
|
||||
const window_size = window.getSize();
|
||||
const framebuffer_size = window.getFramebufferSize();
|
||||
try glfw.getErrorCode();
|
||||
|
||||
const instance = gpu.createInstance(null);
|
||||
if (instance == null) {
|
||||
std.log.err("mach: failed to create GPU instance\n", .{});
|
||||
std.process.exit(1);
|
||||
}
|
||||
const surface = util.createSurfaceForWindow(instance.?, window, comptime util.detectGLFWOptions());
|
||||
|
||||
var response: ?util.RequestAdapterResponse = null;
|
||||
instance.?.requestAdapter(&gpu.RequestAdapterOptions{
|
||||
.compatible_surface = surface,
|
||||
.power_preference = options.power_preference,
|
||||
.force_fallback_adapter = false,
|
||||
}, &response, util.requestAdapterCallback);
|
||||
if (response.?.status != .success) {
|
||||
std.log.err("mach: failed to create GPU adapter: {?s}\n", .{response.?.message});
|
||||
std.log.info("-> maybe try MACH_GPU_BACKEND=opengl ?\n", .{});
|
||||
std.process.exit(1);
|
||||
}
|
||||
|
||||
// Print which adapter we are going to use.
|
||||
var props: gpu.Adapter.Properties = undefined;
|
||||
response.?.adapter.getProperties(&props);
|
||||
if (props.backend_type == .null) {
|
||||
std.log.err("no backend found for {s} adapter", .{props.adapter_type.name()});
|
||||
std.process.exit(1);
|
||||
}
|
||||
std.log.info("mach: found {s} backend on {s} adapter: {s}, {s}\n", .{
|
||||
props.backend_type.name(),
|
||||
props.adapter_type.name(),
|
||||
props.name,
|
||||
props.driver_description,
|
||||
});
|
||||
|
||||
// Create a device with default limits/features.
|
||||
const device = response.?.adapter.createDevice(&.{
|
||||
.required_features_count = if (options.required_features) |v| @intCast(u32, v.len) else 0,
|
||||
.required_features = if (options.required_features) |v| @as(?[*]gpu.FeatureName, v.ptr) else null,
|
||||
.required_limits = if (options.required_limits) |limits| @as(?*gpu.RequiredLimits, &gpu.RequiredLimits{
|
||||
.limits = limits,
|
||||
}) else null,
|
||||
});
|
||||
if (device == null) {
|
||||
std.log.err("mach: failed to create GPU device\n", .{});
|
||||
std.process.exit(1);
|
||||
}
|
||||
|
||||
core.swap_chain_format = .bgra8_unorm;
|
||||
const descriptor = gpu.SwapChain.Descriptor{
|
||||
.label = "main swap chain",
|
||||
.usage = .{ .render_attachment = true },
|
||||
.format = core.swap_chain_format,
|
||||
.width = framebuffer_size.width,
|
||||
.height = framebuffer_size.height,
|
||||
.present_mode = switch (options.vsync) {
|
||||
.none => .immediate,
|
||||
.double => .fifo,
|
||||
.triple => .mailbox,
|
||||
},
|
||||
};
|
||||
|
||||
device.?.setUncapturedErrorCallback({}, util.printUnhandledErrorCallback);
|
||||
|
||||
core.device = device.?;
|
||||
core.backend_type = backend_type;
|
||||
core.surface = surface;
|
||||
core.current_desc = descriptor;
|
||||
core.target_desc = descriptor;
|
||||
core.swap_chain = null;
|
||||
const cursor_pos = window.getCursorPos();
|
||||
try glfw.getErrorCode();
|
||||
|
||||
return Platform{
|
||||
.window = window,
|
||||
.core = core,
|
||||
.backend_type = backend_type,
|
||||
.allocator = core.allocator,
|
||||
.last_window_size = .{ .width = window_size.width, .height = window_size.height },
|
||||
.last_framebuffer_size = .{ .width = framebuffer_size.width, .height = framebuffer_size.height },
|
||||
.last_position = window.getPos(),
|
||||
.last_cursor_position = .{
|
||||
.x = cursor_pos.xpos,
|
||||
.y = cursor_pos.ypos,
|
||||
},
|
||||
.instance = instance.?,
|
||||
.adapter = response.?.adapter,
|
||||
.linux_gamemode = null,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(platform: *Platform) void {
|
||||
for (platform.cursors) |glfw_cursor| {
|
||||
if (glfw_cursor) |cur| {
|
||||
cur.destroy();
|
||||
}
|
||||
}
|
||||
while (platform.events.popFirst()) |ev| {
|
||||
platform.allocator.destroy(ev);
|
||||
}
|
||||
|
||||
if (builtin.os.tag == .linux and
|
||||
platform.linux_gamemode != null and
|
||||
platform.linux_gamemode.?)
|
||||
deinitLinuxGamemode();
|
||||
}
|
||||
|
||||
fn pushEvent(platform: *Platform, event: structs.Event) void {
|
||||
const node = platform.allocator.create(EventNode) catch unreachable;
|
||||
node.* = .{ .data = event };
|
||||
platform.events.append(node);
|
||||
}
|
||||
|
||||
pub fn initCallback(platform: *Platform) void {
|
||||
platform.user_ptr = UserPtr{ .platform = platform };
|
||||
|
||||
platform.window.setUserPointer(&platform.user_ptr);
|
||||
|
||||
const key_callback = struct {
|
||||
fn callback(window: glfw.Window, key: glfw.Key, scancode: i32, action: glfw.Action, mods: glfw.Mods) void {
|
||||
const pf = (window.getUserPointer(UserPtr) orelse unreachable).platform;
|
||||
const key_event = structs.KeyEvent{
|
||||
.key = toMachKey(key),
|
||||
.mods = toMachMods(mods),
|
||||
};
|
||||
switch (action) {
|
||||
.press => pf.pushEvent(.{ .key_press = key_event }),
|
||||
.repeat => pf.pushEvent(.{ .key_repeat = key_event }),
|
||||
.release => pf.pushEvent(.{ .key_release = key_event }),
|
||||
}
|
||||
_ = scancode;
|
||||
}
|
||||
}.callback;
|
||||
platform.window.setKeyCallback(key_callback);
|
||||
|
||||
const char_callback = struct {
|
||||
fn callback(window: glfw.Window, codepoint: u21) void {
|
||||
const pf = (window.getUserPointer(UserPtr) orelse unreachable).platform;
|
||||
pf.pushEvent(.{
|
||||
.char_input = .{
|
||||
.codepoint = codepoint,
|
||||
},
|
||||
});
|
||||
}
|
||||
}.callback;
|
||||
platform.window.setCharCallback(char_callback);
|
||||
|
||||
const mouse_motion_callback = struct {
|
||||
fn callback(window: glfw.Window, xpos: f64, ypos: f64) void {
|
||||
const pf = (window.getUserPointer(UserPtr) orelse unreachable).platform;
|
||||
pf.last_cursor_position = .{
|
||||
.x = xpos,
|
||||
.y = ypos,
|
||||
};
|
||||
pf.pushEvent(.{
|
||||
.mouse_motion = .{
|
||||
.pos = .{
|
||||
.x = xpos,
|
||||
.y = ypos,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}.callback;
|
||||
platform.window.setCursorPosCallback(mouse_motion_callback);
|
||||
|
||||
const mouse_button_callback = struct {
|
||||
fn callback(window: glfw.Window, button: glfw.mouse_button.MouseButton, action: glfw.Action, mods: glfw.Mods) void {
|
||||
const pf = (window.getUserPointer(UserPtr) orelse unreachable).platform;
|
||||
const mouse_button_event = structs.MouseButtonEvent{
|
||||
.button = toMachButton(button),
|
||||
.pos = pf.last_cursor_position,
|
||||
.mods = toMachMods(mods),
|
||||
};
|
||||
switch (action) {
|
||||
.press => pf.pushEvent(.{ .mouse_press = mouse_button_event }),
|
||||
.release => pf.pushEvent(.{
|
||||
.mouse_release = mouse_button_event,
|
||||
}),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
}.callback;
|
||||
platform.window.setMouseButtonCallback(mouse_button_callback);
|
||||
|
||||
const scroll_callback = struct {
|
||||
fn callback(window: glfw.Window, xoffset: f64, yoffset: f64) void {
|
||||
const pf = (window.getUserPointer(UserPtr) orelse unreachable).platform;
|
||||
pf.pushEvent(.{
|
||||
.mouse_scroll = .{
|
||||
.xoffset = @floatCast(f32, xoffset),
|
||||
.yoffset = @floatCast(f32, yoffset),
|
||||
},
|
||||
});
|
||||
}
|
||||
}.callback;
|
||||
platform.window.setScrollCallback(scroll_callback);
|
||||
|
||||
const focus_callback = struct {
|
||||
fn callback(window: glfw.Window, focused: bool) void {
|
||||
const pf = (window.getUserPointer(UserPtr) orelse unreachable).platform;
|
||||
pf.pushEvent(if (focused) .focus_gained else .focus_lost);
|
||||
}
|
||||
}.callback;
|
||||
platform.window.setFocusCallback(focus_callback);
|
||||
|
||||
const close_callback = struct {
|
||||
fn callback(window: glfw.Window) void {
|
||||
const pf = (window.getUserPointer(UserPtr) orelse unreachable).platform;
|
||||
pf.pushEvent(.closed);
|
||||
}
|
||||
}.callback;
|
||||
platform.window.setCloseCallback(close_callback);
|
||||
|
||||
const size_callback = struct {
|
||||
fn callback(window: glfw.Window, width: i32, height: i32) void {
|
||||
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;
|
||||
platform.window.setSizeCallback(size_callback);
|
||||
|
||||
const framebuffer_size_callback = struct {
|
||||
fn callback(window: glfw.Window, width: u32, height: u32) void {
|
||||
const pf = (window.getUserPointer(UserPtr) orelse unreachable).platform;
|
||||
pf.last_framebuffer_size.width = width;
|
||||
pf.last_framebuffer_size.height = height;
|
||||
render(pf.core) catch {};
|
||||
}
|
||||
}.callback;
|
||||
platform.window.setFramebufferSizeCallback(framebuffer_size_callback);
|
||||
}
|
||||
|
||||
pub fn setOptions(platform: *Platform, options: structs.Options) !void {
|
||||
try platform.window.setSize(.{ .width = options.width, .height = options.height });
|
||||
try platform.window.setTitle(options.title);
|
||||
try platform.window.setSizeLimits(
|
||||
glfwSizeOptional(options.size_min),
|
||||
glfwSizeOptional(options.size_max),
|
||||
);
|
||||
platform.core.target_desc.present_mode = switch (options.vsync) {
|
||||
.none => .immediate,
|
||||
.double => .fifo,
|
||||
.triple => .mailbox,
|
||||
};
|
||||
|
||||
platform.last_position = try platform.window.getPos();
|
||||
|
||||
if (options.borderless_window) {
|
||||
glfw.Window.setAttrib(platform.window, .decorated, false);
|
||||
try glfw.getErrorCode();
|
||||
}
|
||||
|
||||
if (options.fullscreen) {
|
||||
var monitor: ?glfw.Monitor = null;
|
||||
|
||||
if (options.monitor) |monitorIndex| {
|
||||
const monitorList = try glfw.Monitor.getAll(platform.allocator);
|
||||
defer platform.allocator.free(monitorList);
|
||||
monitor = monitorList[monitorIndex];
|
||||
} else {
|
||||
monitor = glfw.Monitor.getPrimary();
|
||||
}
|
||||
|
||||
const video_mode = try monitor.?.getVideoMode();
|
||||
try platform.window.setMonitor(monitor, 0, 0, video_mode.getWidth(), video_mode.getHeight(), null);
|
||||
} else {
|
||||
const position = platform.last_position;
|
||||
try platform.window.setMonitor(null, @intCast(i32, position.x), @intCast(i32, position.y), options.width, options.height, null);
|
||||
}
|
||||
if (options.headless) platform.window.hide() catch {};
|
||||
}
|
||||
|
||||
pub fn close(platform: *Platform) void {
|
||||
platform.window.setShouldClose(true);
|
||||
}
|
||||
|
||||
pub fn getFramebufferSize(platform: *Platform) structs.Size {
|
||||
return platform.last_framebuffer_size;
|
||||
}
|
||||
|
||||
pub fn getWindowSize(platform: *Platform) structs.Size {
|
||||
return platform.last_window_size;
|
||||
}
|
||||
|
||||
pub fn setMouseCursor(platform: *Platform, cursor: enums.MouseCursor) !void {
|
||||
// Try to create glfw standard cursor, but could fail. In the future
|
||||
// we hope to provide custom backup images for these.
|
||||
// See https://github.com/hexops/mach/pull/352 for more info
|
||||
|
||||
const enum_int = @enumToInt(cursor);
|
||||
const tried = platform.cursors_tried[enum_int];
|
||||
if (!tried) {
|
||||
platform.cursors_tried[enum_int] = true;
|
||||
platform.cursors[enum_int] = switch (cursor) {
|
||||
.arrow => glfw.Cursor.createStandard(.arrow) catch null,
|
||||
.ibeam => glfw.Cursor.createStandard(.ibeam) catch null,
|
||||
.crosshair => glfw.Cursor.createStandard(.crosshair) catch null,
|
||||
.pointing_hand => glfw.Cursor.createStandard(.pointing_hand) catch null,
|
||||
.resize_ew => glfw.Cursor.createStandard(.resize_ew) catch null,
|
||||
.resize_ns => glfw.Cursor.createStandard(.resize_ns) catch null,
|
||||
.resize_nwse => glfw.Cursor.createStandard(.resize_nwse) catch null,
|
||||
.resize_nesw => glfw.Cursor.createStandard(.resize_nesw) catch null,
|
||||
.resize_all => glfw.Cursor.createStandard(.resize_all) catch null,
|
||||
.not_allowed => glfw.Cursor.createStandard(.not_allowed) catch null,
|
||||
};
|
||||
}
|
||||
|
||||
if (platform.cursors[enum_int]) |cur| {
|
||||
try platform.window.setCursor(cur);
|
||||
} else {
|
||||
// TODO: In the future we shouldn't hit this because we'll provide backup
|
||||
// custom cursors.
|
||||
// See https://github.com/hexops/mach/pull/352 for more info
|
||||
std.log.warn("mach: setMouseCursor: {s} not yet supported\n", .{@tagName(cursor)});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setCursorMode(platform: *Platform, mode: enums.CursorMode) !void {
|
||||
const glfw_mode: glfw.Window.InputModeCursor = switch (mode) {
|
||||
.normal => .normal,
|
||||
.hidden => .hidden,
|
||||
.disabled => .disabled,
|
||||
};
|
||||
try platform.window.setInputModeCursor(glfw_mode);
|
||||
}
|
||||
|
||||
pub fn hasEvent(platform: *Platform) bool {
|
||||
return platform.events.first != null;
|
||||
}
|
||||
|
||||
pub fn setWaitEvent(platform: *Platform, timeout: f64) void {
|
||||
platform.wait_event_timeout = timeout;
|
||||
}
|
||||
|
||||
pub fn pollEvent(platform: *Platform) ?structs.Event {
|
||||
if (platform.events.popFirst()) |n| {
|
||||
const data = n.data;
|
||||
platform.allocator.destroy(n);
|
||||
return data;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn toMachButton(button: glfw.mouse_button.MouseButton) enums.MouseButton {
|
||||
return switch (button) {
|
||||
.left => .left,
|
||||
.right => .right,
|
||||
.middle => .middle,
|
||||
.four => .four,
|
||||
.five => .five,
|
||||
.six => .six,
|
||||
.seven => .seven,
|
||||
.eight => .eight,
|
||||
};
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
fn toMachMods(mods: glfw.Mods) structs.KeyMods {
|
||||
return .{
|
||||
.shift = mods.shift,
|
||||
.control = mods.control,
|
||||
.alt = mods.alt,
|
||||
.super = mods.super,
|
||||
.caps_lock = mods.caps_lock,
|
||||
.num_lock = mods.num_lock,
|
||||
};
|
||||
}
|
||||
|
||||
/// Default GLFW error handling callback
|
||||
fn errorCallback(error_code: glfw.ErrorCode, description: [:0]const u8) void {
|
||||
std.log.err("glfw: {}: {s}\n", .{ error_code, description });
|
||||
}
|
||||
};
|
||||
|
||||
pub const BackingTimer = std.time.Timer;
|
||||
|
||||
var app: App = undefined;
|
||||
|
||||
pub const GPUInterface = gpu.dawn.Interface;
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer _ = gpa.deinit();
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
gpu.Impl.init();
|
||||
_ = gpu.Export(GPUInterface);
|
||||
|
||||
var core = try coreInit(allocator);
|
||||
defer coreDeinit(core, allocator);
|
||||
|
||||
try app.init(core);
|
||||
defer app.deinit(core);
|
||||
|
||||
while (!core.internal.window.shouldClose()) {
|
||||
try render(core);
|
||||
}
|
||||
}
|
||||
|
||||
fn render(core: *Core) !void {
|
||||
// On Darwin targets, Dawn requires an NSAutoreleasePool per frame to release
|
||||
// some resources. See Dawn's CHelloWorld example.
|
||||
const pool = try util.AutoReleasePool.init();
|
||||
defer util.AutoReleasePool.release(pool);
|
||||
|
||||
try coreUpdate(core, null);
|
||||
|
||||
try app.update(core);
|
||||
}
|
||||
|
||||
pub fn coreInit(allocator: std.mem.Allocator) !*Core {
|
||||
const core: *Core = try allocator.create(Core);
|
||||
errdefer allocator.destroy(core);
|
||||
try Core.init(allocator, core);
|
||||
|
||||
// Glfw specific: initialize the user pointer used in callbacks
|
||||
core.*.internal.initCallback();
|
||||
|
||||
return core;
|
||||
}
|
||||
|
||||
pub fn coreDeinit(core: *Core, allocator: std.mem.Allocator) void {
|
||||
core.internal.deinit();
|
||||
allocator.destroy(core);
|
||||
}
|
||||
|
||||
pub const CoreResizeCallback = *const fn (*Core, u32, u32) callconv(.C) void;
|
||||
|
||||
pub fn coreUpdate(core: *Core, resize: ?CoreResizeCallback) !void {
|
||||
if (builtin.os.tag == .linux and !core.options.is_app and
|
||||
core.internal.linux_gamemode == null and try activateGamemode(core.allocator))
|
||||
core.internal.linux_gamemode = initLinuxGamemode();
|
||||
|
||||
if (core.internal.wait_event_timeout > 0.0) {
|
||||
if (core.internal.wait_event_timeout == std.math.inf(f64)) {
|
||||
// Wait for an event
|
||||
glfw.waitEvents();
|
||||
} else {
|
||||
// Wait for an event with a timeout
|
||||
glfw.waitEventsTimeout(core.internal.wait_event_timeout);
|
||||
}
|
||||
} else {
|
||||
// Don't wait for events
|
||||
glfw.pollEvents();
|
||||
}
|
||||
try glfw.getErrorCode();
|
||||
|
||||
core.delta_time_ns = core.timer.lapPrecise();
|
||||
core.delta_time = @intToFloat(f32, core.delta_time_ns) / @intToFloat(f32, std.time.ns_per_s);
|
||||
|
||||
var framebuffer_size = core.getFramebufferSize();
|
||||
core.target_desc.width = framebuffer_size.width;
|
||||
core.target_desc.height = framebuffer_size.height;
|
||||
|
||||
if ((core.swap_chain == null or !std.meta.eql(core.current_desc, core.target_desc)) and !(core.target_desc.width == 0 or core.target_desc.height == 0)) {
|
||||
core.swap_chain = core.device.createSwapChain(core.surface, &core.target_desc);
|
||||
|
||||
if (@hasDecl(App, "resize")) {
|
||||
try app.resize(core, core.target_desc.width, core.target_desc.height);
|
||||
} else if (resize != null) {
|
||||
resize.?(core, core.target_desc.width, core.target_desc.height);
|
||||
}
|
||||
core.current_desc = core.target_desc;
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
fn glfwSizeOptional(size: structs.SizeOptional) glfw.Window.SizeOptional {
|
||||
return .{
|
||||
.width = size.width,
|
||||
.height = size.height,
|
||||
};
|
||||
}
|
||||
|
||||
/// Check if gamemode should be activated
|
||||
fn activateGamemode(allocator: std.mem.Allocator) error{ OutOfMemory, InvalidUtf8 }!bool {
|
||||
if (try getEnvVarOwned(allocator, "MACH_USE_GAMEMODE")) |env| {
|
||||
defer allocator.free(env);
|
||||
return !(std.ascii.eqlIgnoreCase(env, "off") or std.ascii.eqlIgnoreCase(env, "false"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
fn initLinuxGamemode() bool {
|
||||
const gamemode = @import("gamemode");
|
||||
gamemode.requestStart() catch |err| {
|
||||
if (!std.mem.containsAtLeast(u8, gamemode.errorString(), 1, "dlopen failed"))
|
||||
std.log.err("Gamemode error {} -> {s}", .{ err, gamemode.errorString() });
|
||||
return false;
|
||||
};
|
||||
std.log.info("Gamemode activated", .{});
|
||||
return true;
|
||||
}
|
||||
|
||||
fn deinitLinuxGamemode() void {
|
||||
const gamemode = @import("gamemode");
|
||||
gamemode.requestEnd() catch |err| {
|
||||
std.log.err("Gamemode error {} -> {s}", .{ err, gamemode.errorString() });
|
||||
};
|
||||
}
|
||||
pub const entry = @import("native/entry.zig");
|
||||
pub const Core = @import("native/Core.zig");
|
||||
pub const Timer = std.time.Timer;
|
||||
|
|
|
|||
767
src/platform/native/Core.zig
Normal file
767
src/platform/native/Core.zig
Normal file
|
|
@ -0,0 +1,767 @@
|
|||
const builtin = @import("builtin");
|
||||
const std = @import("std");
|
||||
const gpu = @import("gpu");
|
||||
const glfw = @import("glfw");
|
||||
const util = @import("util.zig");
|
||||
const Options = @import("../../Core.zig").Options;
|
||||
const Event = @import("../../Core.zig").Event;
|
||||
const KeyEvent = @import("../../Core.zig").KeyEvent;
|
||||
const MouseButtonEvent = @import("../../Core.zig").MouseButtonEvent;
|
||||
const MouseButton = @import("../../Core.zig").MouseButton;
|
||||
const Size = @import("../../Core.zig").Size;
|
||||
const DisplayMode = @import("../../Core.zig").DisplayMode;
|
||||
const SizeLimit = @import("../../Core.zig").SizeLimit;
|
||||
const CursorShape = @import("../../Core.zig").CursorShape;
|
||||
const VSyncMode = @import("../../Core.zig").VSyncMode;
|
||||
const CursorMode = @import("../../Core.zig").CursorMode;
|
||||
const Key = @import("../../Core.zig").Key;
|
||||
const KeyMods = @import("../../Core.zig").KeyMods;
|
||||
|
||||
pub const Core = @This();
|
||||
|
||||
allocator: std.mem.Allocator,
|
||||
window: glfw.Window,
|
||||
backend_type: gpu.BackendType,
|
||||
user_ptr: UserPtr,
|
||||
|
||||
instance: *gpu.Instance,
|
||||
surface: *gpu.Surface,
|
||||
gpu_adapter: *gpu.Adapter,
|
||||
gpu_device: *gpu.Device,
|
||||
swap_chain: *gpu.SwapChain,
|
||||
swap_chain_desc: gpu.SwapChain.Descriptor,
|
||||
|
||||
events: EventQueue,
|
||||
wait_timeout: f64,
|
||||
|
||||
last_size: glfw.Window.Size,
|
||||
last_pos: glfw.Window.Pos,
|
||||
size_limit: SizeLimit,
|
||||
frame_buffer_resized: bool,
|
||||
|
||||
current_cursor: CursorShape,
|
||||
cursors: [@typeInfo(CursorShape).Enum.fields.len]?glfw.Cursor,
|
||||
cursors_tried: [@typeInfo(CursorShape).Enum.fields.len]bool,
|
||||
|
||||
linux_gamemode: ?bool,
|
||||
|
||||
const EventQueue = std.TailQueue(Event);
|
||||
const EventNode = EventQueue.Node;
|
||||
|
||||
const UserPtr = struct {
|
||||
self: *Core,
|
||||
};
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, options: Options) !*Core {
|
||||
const backend_type = try util.detectBackendType(allocator);
|
||||
|
||||
glfw.setErrorCallback(errorCallback);
|
||||
if (!glfw.init(.{}))
|
||||
glfw.getErrorCode() catch |err| switch (err) {
|
||||
error.PlatformError,
|
||||
error.PlatformUnavailable,
|
||||
=> return err,
|
||||
else => unreachable,
|
||||
};
|
||||
|
||||
// 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 = glfw.Window.create(
|
||||
options.size.width,
|
||||
options.size.height,
|
||||
options.title,
|
||||
null,
|
||||
null,
|
||||
hints,
|
||||
) orelse switch (glfw.mustGetErrorCode()) {
|
||||
error.InvalidEnum,
|
||||
error.InvalidValue,
|
||||
error.FormatUnavailable,
|
||||
=> unreachable,
|
||||
error.APIUnavailable,
|
||||
error.VersionUnavailable,
|
||||
error.PlatformError,
|
||||
=> |err| return err,
|
||||
else => unreachable,
|
||||
};
|
||||
|
||||
switch (backend_type) {
|
||||
.opengl, .opengles => {
|
||||
glfw.makeContextCurrent(window);
|
||||
glfw.getErrorCode() catch |err| switch (err) {
|
||||
error.PlatformError => return err,
|
||||
else => unreachable,
|
||||
};
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
const instance = gpu.createInstance(null) orelse {
|
||||
std.log.err("mach: failed to create GPU instance", .{});
|
||||
std.process.exit(1);
|
||||
};
|
||||
const surface = util.createSurfaceForWindow(instance, window, comptime util.detectGLFWOptions());
|
||||
|
||||
var response: util.RequestAdapterResponse = undefined;
|
||||
instance.requestAdapter(&gpu.RequestAdapterOptions{
|
||||
.compatible_surface = surface,
|
||||
.power_preference = options.power_preference,
|
||||
.force_fallback_adapter = false,
|
||||
}, &response, util.requestAdapterCallback);
|
||||
if (response.status != .success) {
|
||||
std.log.err("mach: failed to create GPU adapter: {?s}", .{response.message});
|
||||
std.log.info("-> maybe try MACH_GPU_BACKEND=opengl ?", .{});
|
||||
std.process.exit(1);
|
||||
}
|
||||
|
||||
// Print which adapter we are going to use.
|
||||
var props: gpu.Adapter.Properties = undefined;
|
||||
response.adapter.getProperties(&props);
|
||||
if (props.backend_type == .null) {
|
||||
std.log.err("no backend found for {s} adapter", .{props.adapter_type.name()});
|
||||
std.process.exit(1);
|
||||
}
|
||||
std.log.info("mach: found {s} backend on {s} adapter: {s}, {s}\n", .{
|
||||
props.backend_type.name(),
|
||||
props.adapter_type.name(),
|
||||
props.name,
|
||||
props.driver_description,
|
||||
});
|
||||
|
||||
// Create a device with default limits/features.
|
||||
const gpu_device = response.adapter.createDevice(&.{
|
||||
.required_features_count = if (options.required_features) |v| @intCast(u32, v.len) else 0,
|
||||
.required_features = if (options.required_features) |v| @as(?[*]const gpu.FeatureName, v.ptr) else null,
|
||||
.required_limits = if (options.required_limits) |limits| @as(?*gpu.RequiredLimits, &gpu.RequiredLimits{
|
||||
.limits = limits,
|
||||
}) else null,
|
||||
}) orelse {
|
||||
std.log.err("mach: failed to create GPU device\n", .{});
|
||||
std.process.exit(1);
|
||||
};
|
||||
gpu_device.setUncapturedErrorCallback({}, util.printUnhandledErrorCallback);
|
||||
|
||||
const framebuffer_size = window.getFramebufferSize();
|
||||
const swap_chain_desc = gpu.SwapChain.Descriptor{
|
||||
.label = "main swap chain",
|
||||
.usage = .{ .render_attachment = true },
|
||||
.format = .bgra8_unorm,
|
||||
.width = framebuffer_size.width,
|
||||
.height = framebuffer_size.height,
|
||||
.present_mode = .fifo,
|
||||
};
|
||||
const swap_chain = gpu_device.createSwapChain(surface, &swap_chain_desc);
|
||||
|
||||
const self: *Core = try allocator.create(Core);
|
||||
errdefer allocator.destroy(self);
|
||||
self.* = .{
|
||||
.allocator = allocator,
|
||||
.window = window,
|
||||
.backend_type = backend_type,
|
||||
.user_ptr = undefined,
|
||||
|
||||
.instance = instance,
|
||||
.surface = surface,
|
||||
.gpu_adapter = response.adapter,
|
||||
.gpu_device = gpu_device,
|
||||
.swap_chain = swap_chain,
|
||||
.swap_chain_desc = swap_chain_desc,
|
||||
|
||||
.events = .{},
|
||||
.wait_timeout = 0.0,
|
||||
|
||||
.last_size = window.getSize(),
|
||||
.last_pos = window.getPos(),
|
||||
.size_limit = .{
|
||||
.min = .{ .width = 350, .height = 350 },
|
||||
.max = .{ .width = null, .height = null },
|
||||
},
|
||||
.frame_buffer_resized = false,
|
||||
|
||||
.current_cursor = .arrow,
|
||||
.cursors = std.mem.zeroes([@typeInfo(CursorShape).Enum.fields.len]?glfw.Cursor),
|
||||
.cursors_tried = std.mem.zeroes([@typeInfo(CursorShape).Enum.fields.len]bool),
|
||||
|
||||
.linux_gamemode = null,
|
||||
};
|
||||
|
||||
self.setSizeLimit(self.size_limit);
|
||||
|
||||
self.initCallbacks();
|
||||
if (builtin.os.tag == .linux and !options.is_app and
|
||||
self.linux_gamemode == null and try activateGamemode(self.allocator))
|
||||
self.linux_gamemode = initLinuxGamemode();
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
fn initCallbacks(self: *Core) void {
|
||||
self.user_ptr = UserPtr{ .self = self };
|
||||
|
||||
self.window.setUserPointer(&self.user_ptr);
|
||||
|
||||
const key_callback = struct {
|
||||
fn callback(window: glfw.Window, key: glfw.Key, scancode: i32, action: glfw.Action, mods: glfw.Mods) void {
|
||||
const pf = (window.getUserPointer(UserPtr) orelse unreachable).self;
|
||||
const key_event = KeyEvent{
|
||||
.key = toMachKey(key),
|
||||
.mods = toMachMods(mods),
|
||||
};
|
||||
switch (action) {
|
||||
.press => pf.pushEvent(.{ .key_press = key_event }),
|
||||
.repeat => pf.pushEvent(.{ .key_repeat = key_event }),
|
||||
.release => pf.pushEvent(.{ .key_release = key_event }),
|
||||
}
|
||||
_ = scancode;
|
||||
}
|
||||
}.callback;
|
||||
self.window.setKeyCallback(key_callback);
|
||||
|
||||
const char_callback = struct {
|
||||
fn callback(window: glfw.Window, codepoint: u21) void {
|
||||
const pf = (window.getUserPointer(UserPtr) orelse unreachable).self;
|
||||
pf.pushEvent(.{
|
||||
.char_input = .{
|
||||
.codepoint = codepoint,
|
||||
},
|
||||
});
|
||||
}
|
||||
}.callback;
|
||||
self.window.setCharCallback(char_callback);
|
||||
|
||||
const mouse_motion_callback = struct {
|
||||
fn callback(window: glfw.Window, xpos: f64, ypos: f64) void {
|
||||
const pf = (window.getUserPointer(UserPtr) orelse unreachable).self;
|
||||
pf.pushEvent(.{
|
||||
.mouse_motion = .{
|
||||
.pos = .{
|
||||
.x = xpos,
|
||||
.y = ypos,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}.callback;
|
||||
self.window.setCursorPosCallback(mouse_motion_callback);
|
||||
|
||||
const mouse_button_callback = struct {
|
||||
fn callback(window: glfw.Window, button: glfw.mouse_button.MouseButton, action: glfw.Action, mods: glfw.Mods) void {
|
||||
const pf = (window.getUserPointer(UserPtr) orelse unreachable).self;
|
||||
const cursor_pos = pf.window.getCursorPos();
|
||||
const mouse_button_event = MouseButtonEvent{
|
||||
.button = toMachButton(button),
|
||||
.pos = .{ .x = cursor_pos.xpos, .y = cursor_pos.ypos },
|
||||
.mods = toMachMods(mods),
|
||||
};
|
||||
switch (action) {
|
||||
.press => pf.pushEvent(.{ .mouse_press = mouse_button_event }),
|
||||
.release => pf.pushEvent(.{
|
||||
.mouse_release = mouse_button_event,
|
||||
}),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
}.callback;
|
||||
self.window.setMouseButtonCallback(mouse_button_callback);
|
||||
|
||||
const scroll_callback = struct {
|
||||
fn callback(window: glfw.Window, xoffset: f64, yoffset: f64) void {
|
||||
const pf = (window.getUserPointer(UserPtr) orelse unreachable).self;
|
||||
pf.pushEvent(.{
|
||||
.mouse_scroll = .{
|
||||
.xoffset = @floatCast(f32, xoffset),
|
||||
.yoffset = @floatCast(f32, yoffset),
|
||||
},
|
||||
});
|
||||
}
|
||||
}.callback;
|
||||
self.window.setScrollCallback(scroll_callback);
|
||||
|
||||
const focus_callback = struct {
|
||||
fn callback(window: glfw.Window, focused: bool) void {
|
||||
const pf = (window.getUserPointer(UserPtr) orelse unreachable).self;
|
||||
pf.pushEvent(if (focused) .focus_gained else .focus_lost);
|
||||
}
|
||||
}.callback;
|
||||
self.window.setFocusCallback(focus_callback);
|
||||
|
||||
const framebuffer_size_callback = struct {
|
||||
fn callback(window: glfw.Window, _: u32, _: u32) void {
|
||||
const pf = (window.getUserPointer(UserPtr) orelse unreachable).self;
|
||||
pf.frame_buffer_resized = true;
|
||||
}
|
||||
}.callback;
|
||||
self.window.setFramebufferSizeCallback(framebuffer_size_callback);
|
||||
}
|
||||
|
||||
fn pushEvent(self: *Core, event: Event) void {
|
||||
const node = self.allocator.create(EventNode) catch unreachable;
|
||||
node.* = .{ .data = event };
|
||||
self.events.append(node);
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Core) void {
|
||||
for (self.cursors) |glfw_cursor| {
|
||||
if (glfw_cursor) |cur| {
|
||||
cur.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
while (self.events.popFirst()) |ev| {
|
||||
self.allocator.destroy(ev);
|
||||
}
|
||||
|
||||
if (builtin.os.tag == .linux and
|
||||
self.linux_gamemode != null and
|
||||
self.linux_gamemode.?)
|
||||
deinitLinuxGamemode();
|
||||
|
||||
self.allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn hasEvent(self: *Core) bool {
|
||||
return self.events.first != null;
|
||||
}
|
||||
|
||||
pub fn pollEvents(self: *Core) ?Event {
|
||||
if (self.wait_timeout > 0.0) {
|
||||
if (self.wait_timeout == std.math.inf(f64)) {
|
||||
// Wait for an event
|
||||
glfw.waitEvents();
|
||||
} else {
|
||||
// Wait for an event with a timeout
|
||||
glfw.waitEventsTimeout(self.wait_timeout);
|
||||
}
|
||||
} else {
|
||||
// Don't wait for events
|
||||
glfw.pollEvents();
|
||||
}
|
||||
|
||||
glfw.getErrorCode() catch |err| switch (err) {
|
||||
error.PlatformError => std.log.err("glfw: failed to poll events", .{}),
|
||||
error.InvalidValue => unreachable,
|
||||
else => unreachable,
|
||||
};
|
||||
|
||||
if (self.frame_buffer_resized) blk: {
|
||||
self.frame_buffer_resized = false;
|
||||
|
||||
const framebuffer_size = self.window.getFramebufferSize();
|
||||
glfw.getErrorCode() catch break :blk;
|
||||
|
||||
if (framebuffer_size.width != 0 and framebuffer_size.height != 0) {
|
||||
self.swap_chain_desc.width = framebuffer_size.width;
|
||||
self.swap_chain_desc.height = framebuffer_size.height;
|
||||
self.swap_chain = self.gpu_device.createSwapChain(self.surface, &self.swap_chain_desc);
|
||||
self.pushEvent(.{
|
||||
.framebuffer_resize = .{
|
||||
.width = framebuffer_size.width,
|
||||
.height = framebuffer_size.height,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (self.window.shouldClose()) {
|
||||
self.pushEvent(.close);
|
||||
}
|
||||
|
||||
if (self.events.popFirst()) |n| {
|
||||
const data = n.data;
|
||||
self.allocator.destroy(n);
|
||||
return data;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn shouldClose(self: *Core) bool {
|
||||
return self.window.shouldClose();
|
||||
}
|
||||
|
||||
pub fn framebufferSize(self: *Core) Size {
|
||||
const framebuffer_size = self.window.getFramebufferSize();
|
||||
return .{
|
||||
.width = framebuffer_size.width,
|
||||
.height = framebuffer_size.height,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn setWaitTimeout(self: *Core, timeout: f64) void {
|
||||
self.wait_timeout = timeout;
|
||||
}
|
||||
|
||||
pub fn setTitle(self: *Core, title: [:0]const u8) void {
|
||||
self.window.setTitle(title);
|
||||
}
|
||||
|
||||
pub fn setDisplayMode(self: *Core, mode: DisplayMode, monitor_index: ?usize) !void {
|
||||
switch (mode) {
|
||||
.windowed => {
|
||||
try self.window.setMonitor(
|
||||
null,
|
||||
@intCast(i32, self.last_pos.x),
|
||||
@intCast(i32, self.last_pos.y),
|
||||
self.last_size.width,
|
||||
self.last_size.height,
|
||||
null,
|
||||
);
|
||||
},
|
||||
.fullscreen => {
|
||||
if (try self.displayMode() == .windowed) {
|
||||
self.last_size = try self.window.getSize();
|
||||
self.last_pos = try self.window.getPos();
|
||||
}
|
||||
|
||||
const monitor = blk: {
|
||||
if (monitor_index) |i| {
|
||||
const monitor_list = try glfw.Monitor.getAll(self.allocator);
|
||||
defer self.allocator.free(monitor_list);
|
||||
break :blk monitor_list[i];
|
||||
}
|
||||
break :blk glfw.Monitor.getPrimary();
|
||||
};
|
||||
|
||||
const video_mode = try monitor.?.getVideoMode();
|
||||
try self.window.setMonitor(monitor, 0, 0, video_mode.getWidth(), video_mode.getHeight(), null);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn displayMode(self: *Core) DisplayMode {
|
||||
if (self.window.getMonitor()) |_| {
|
||||
return .fullscreen;
|
||||
} else {
|
||||
return .windowed;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setBorder(self: *Core, value: bool) void {
|
||||
self.window.setAttrib(.decorated, value);
|
||||
}
|
||||
|
||||
pub fn border(self: *Core) !bool {
|
||||
const decorated = try self.window.getAttrib(.decorated);
|
||||
return decorated == 1;
|
||||
}
|
||||
|
||||
pub fn setHeadless(self: *Core, value: bool) void {
|
||||
if (value) {
|
||||
self.window.hide();
|
||||
} else {
|
||||
self.window.show();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn headless(self: *Core) bool {
|
||||
const visible = self.window.getAttrib(.visible);
|
||||
return visible == 0;
|
||||
}
|
||||
|
||||
pub fn setVSync(self: *Core, mode: VSyncMode) void {
|
||||
self.swap_chain_desc.present_mode = switch (mode) {
|
||||
.none => .immediate,
|
||||
.double => .fifo,
|
||||
.triple => .mailbox,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn vsync(self: *Core) VSyncMode {
|
||||
return switch (self.swap_chain_desc.present_mode) {
|
||||
.immediate => .none,
|
||||
.fifo => .double,
|
||||
.mailbox => .triple,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn setSize(self: *Core, value: Size) void {
|
||||
self.window.setSize(.{
|
||||
.width = value.width,
|
||||
.height = value.height,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn size(self: *Core) Size {
|
||||
const window_size = self.window.getSize();
|
||||
return .{ .width = window_size.width, .height = window_size.height };
|
||||
}
|
||||
|
||||
pub fn setSizeLimit(self: *Core, limit: SizeLimit) void {
|
||||
self.window.setSizeLimits(
|
||||
.{ .width = limit.min.width, .height = limit.min.height },
|
||||
.{ .width = limit.max.width, .height = limit.max.height },
|
||||
);
|
||||
self.size_limit = limit;
|
||||
}
|
||||
|
||||
pub fn sizeLimit(self: *Core) SizeLimit {
|
||||
return self.size_limit;
|
||||
}
|
||||
|
||||
pub fn setCursorMode(self: *Core, mode: CursorMode) void {
|
||||
const glfw_mode: glfw.Window.InputModeCursor = switch (mode) {
|
||||
.normal => .normal,
|
||||
.hidden => .hidden,
|
||||
.disabled => .disabled,
|
||||
};
|
||||
self.window.setInputModeCursor(glfw_mode);
|
||||
}
|
||||
|
||||
pub fn cursorMode(self: *Core) CursorMode {
|
||||
const glfw_mode = self.window.getInputModeCursor();
|
||||
return switch (glfw_mode) {
|
||||
.normal => .normal,
|
||||
.hidden => .hidden,
|
||||
.disabled => .disabled,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn setCursorShape(self: *Core, cursor: CursorShape) void {
|
||||
// Try to create glfw standard cursor, but could fail. In the future
|
||||
// we hope to provide custom backup images for these.
|
||||
// See https://github.com/hexops/mach/pull/352 for more info
|
||||
|
||||
const enum_int = @enumToInt(cursor);
|
||||
const tried = self.cursors_tried[enum_int];
|
||||
if (!tried) {
|
||||
self.cursors_tried[enum_int] = true;
|
||||
self.cursors[enum_int] = switch (cursor) {
|
||||
.arrow => glfw.Cursor.createStandard(.arrow) catch null,
|
||||
.ibeam => glfw.Cursor.createStandard(.ibeam) catch null,
|
||||
.crosshair => glfw.Cursor.createStandard(.crosshair) catch null,
|
||||
.pointing_hand => glfw.Cursor.createStandard(.pointing_hand) catch null,
|
||||
.resize_ew => glfw.Cursor.createStandard(.resize_ew) catch null,
|
||||
.resize_ns => glfw.Cursor.createStandard(.resize_ns) catch null,
|
||||
.resize_nwse => glfw.Cursor.createStandard(.resize_nwse) catch null,
|
||||
.resize_nesw => glfw.Cursor.createStandard(.resize_nesw) catch null,
|
||||
.resize_all => glfw.Cursor.createStandard(.resize_all) catch null,
|
||||
.not_allowed => glfw.Cursor.createStandard(.not_allowed) catch null,
|
||||
};
|
||||
}
|
||||
|
||||
if (self.cursors[enum_int]) |cur| {
|
||||
self.window.setCursor(cur);
|
||||
} else {
|
||||
// TODO: In the future we shouldn't hit this because we'll provide backup
|
||||
// custom cursors.
|
||||
// See https://github.com/hexops/mach/pull/352 for more info
|
||||
std.log.warn("mach: setCursorShape: {s} not yet supported\n", .{@tagName(cursor)});
|
||||
}
|
||||
|
||||
self.current_cursor = cursor;
|
||||
}
|
||||
|
||||
pub fn cursorShape(self: *Core) CursorShape {
|
||||
return self.current_cursor;
|
||||
}
|
||||
|
||||
pub fn adapter(self: *Core) *gpu.Adapter {
|
||||
return self.gpu_adapter;
|
||||
}
|
||||
|
||||
pub fn device(self: *Core) *gpu.Device {
|
||||
return self.gpu_device;
|
||||
}
|
||||
|
||||
pub fn swapChain(self: *Core) *gpu.SwapChain {
|
||||
return self.swap_chain;
|
||||
}
|
||||
|
||||
pub fn descriptor(self: *Core) gpu.SwapChain.Descriptor {
|
||||
return self.swap_chain_desc;
|
||||
}
|
||||
|
||||
fn toMachButton(button: glfw.mouse_button.MouseButton) MouseButton {
|
||||
return switch (button) {
|
||||
.left => .left,
|
||||
.right => .right,
|
||||
.middle => .middle,
|
||||
.four => .four,
|
||||
.five => .five,
|
||||
.six => .six,
|
||||
.seven => .seven,
|
||||
.eight => .eight,
|
||||
};
|
||||
}
|
||||
|
||||
fn toMachKey(key: glfw.Key) 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,
|
||||
};
|
||||
}
|
||||
|
||||
fn toMachMods(mods: glfw.Mods) KeyMods {
|
||||
return .{
|
||||
.shift = mods.shift,
|
||||
.control = mods.control,
|
||||
.alt = mods.alt,
|
||||
.super = mods.super,
|
||||
.caps_lock = mods.caps_lock,
|
||||
.num_lock = mods.num_lock,
|
||||
};
|
||||
}
|
||||
|
||||
/// Default GLFW error handling callback
|
||||
fn errorCallback(error_code: glfw.ErrorCode, description: [:0]const u8) void {
|
||||
std.log.err("glfw: {}: {s}\n", .{ error_code, description });
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
/// Check if gamemode should be activated
|
||||
fn activateGamemode(allocator: std.mem.Allocator) error{ OutOfMemory, InvalidUtf8 }!bool {
|
||||
if (try getEnvVarOwned(allocator, "MACH_USE_GAMEMODE")) |env| {
|
||||
defer allocator.free(env);
|
||||
return !(std.ascii.eqlIgnoreCase(env, "off") or std.ascii.eqlIgnoreCase(env, "false"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
fn initLinuxGamemode() bool {
|
||||
const gamemode = @import("gamemode");
|
||||
gamemode.requestStart() catch |err| {
|
||||
if (!std.mem.containsAtLeast(u8, gamemode.errorString(), 1, "dlopen failed"))
|
||||
std.log.err("Gamemode error {} -> {s}", .{ err, gamemode.errorString() });
|
||||
return false;
|
||||
};
|
||||
std.log.info("Gamemode activated", .{});
|
||||
return true;
|
||||
}
|
||||
|
||||
fn deinitLinuxGamemode() void {
|
||||
const gamemode = @import("gamemode");
|
||||
gamemode.requestEnd() catch |err| {
|
||||
std.log.err("Gamemode error {} -> {s}", .{ err, gamemode.errorString() });
|
||||
};
|
||||
}
|
||||
22
src/platform/native/entry.zig
Normal file
22
src/platform/native/entry.zig
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
const std = @import("std");
|
||||
const gpu = @import("gpu");
|
||||
const App = @import("app").App;
|
||||
const util = @import("util.zig");
|
||||
|
||||
pub const GPUInterface = gpu.dawn.Interface;
|
||||
pub const scope_levels = if (@hasDecl(App, "scope_levels")) App.scope_levels else [0]std.log.ScopeLevel{};
|
||||
pub const log_level = if (@hasDecl(App, "log_level")) App.log_level else std.log.default_level;
|
||||
pub fn main() !void {
|
||||
gpu.Impl.init();
|
||||
_ = gpu.Export(GPUInterface);
|
||||
|
||||
var app: App = undefined;
|
||||
try app.init();
|
||||
defer app.deinit();
|
||||
|
||||
while (true) {
|
||||
const pool = try util.AutoReleasePool.init();
|
||||
defer util.AutoReleasePool.release(pool);
|
||||
if (try app.update()) return;
|
||||
}
|
||||
}
|
||||
7
src/platform/native/objc_message.zig
Normal file
7
src/platform/native/objc_message.zig
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
// Extracted from `zig translate-c tmp.c` with `#include <objc/message.h>` in the file.
|
||||
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;
|
||||
|
|
@ -49,7 +49,7 @@ pub const RequestAdapterResponse = struct {
|
|||
};
|
||||
|
||||
pub inline fn requestAdapterCallback(
|
||||
context: *?RequestAdapterResponse,
|
||||
context: *RequestAdapterResponse,
|
||||
status: gpu.RequestAdapterStatus,
|
||||
adapter: *gpu.Adapter,
|
||||
message: ?[*:0]const u8,
|
||||
|
|
@ -168,11 +168,11 @@ pub fn msgSend(obj: anytype, sel_name: [:0]const u8, args: anytype, comptime Ret
|
|||
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,
|
||||
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"),
|
||||
};
|
||||
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
// Extracted from `zig translate-c tmp.c` with `#include <objc/message.h>` in the file.
|
||||
pub const struct_objc_selector = opaque {};
|
||||
pub const SEL = ?*struct_objc_selector;
|
||||
pub const Class = ?*struct_objc_class;
|
||||
pub const struct_objc_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;
|
||||
|
|
@ -1,349 +1,3 @@
|
|||
const std = @import("std");
|
||||
const app_pkg = @import("app");
|
||||
const Core = @import("../Core.zig");
|
||||
const structs = @import("../structs.zig");
|
||||
const enums = @import("../enums.zig");
|
||||
const gpu = @import("gpu");
|
||||
|
||||
const js = struct {
|
||||
extern "mach" fn machCanvasInit(selector_id: *u8) CanvasId;
|
||||
extern "mach" fn machCanvasDeinit(canvas: CanvasId) void;
|
||||
extern "mach" fn machCanvasSetTitle(canvas: CanvasId, title: [*]const u8, len: u32) void;
|
||||
extern "mach" fn machCanvasSetSize(canvas: CanvasId, width: u32, height: u32) void;
|
||||
extern "mach" fn machCanvasSetFullscreen(canvas: CanvasId, value: bool) void;
|
||||
extern "mach" fn machCanvasGetWindowWidth(canvas: CanvasId) u32;
|
||||
extern "mach" fn machCanvasGetWindowHeight(canvas: CanvasId) u32;
|
||||
extern "mach" fn machCanvasGetFramebufferWidth(canvas: CanvasId) u32;
|
||||
extern "mach" fn machCanvasGetFramebufferHeight(canvas: CanvasId) u32;
|
||||
extern "mach" fn machSetMouseCursor(cursor_name: [*]const u8, len: u32) void;
|
||||
extern "mach" fn machEmitCloseEvent() void;
|
||||
extern "mach" fn machSetWaitEvent(timeout: f64) void;
|
||||
extern "mach" fn machHasEvent() bool;
|
||||
extern "mach" fn machEventShift() i32;
|
||||
extern "mach" fn machEventShiftFloat() f64;
|
||||
extern "mach" fn machChangeShift() u32;
|
||||
extern "mach" fn machPerfNow() f64;
|
||||
|
||||
extern "mach" fn machLog(str: [*]const u8, len: u32) void;
|
||||
extern "mach" fn machLogWrite(str: [*]const u8, len: u32) void;
|
||||
extern "mach" fn machLogFlush() void;
|
||||
extern "mach" fn machPanic(str: [*]const u8, len: u32) void;
|
||||
};
|
||||
|
||||
const common = @import("common.zig");
|
||||
comptime {
|
||||
common.checkApplication(app_pkg);
|
||||
}
|
||||
const App = app_pkg.App;
|
||||
|
||||
pub const GPUInterface = gpu.StubInterface;
|
||||
|
||||
pub const CanvasId = u32;
|
||||
|
||||
pub const Platform = struct {
|
||||
id: CanvasId,
|
||||
selector_id: []const u8,
|
||||
allocator: std.mem.Allocator,
|
||||
|
||||
last_window_size: structs.Size,
|
||||
last_framebuffer_size: structs.Size,
|
||||
|
||||
last_cursor_position: structs.WindowPos,
|
||||
last_key_mods: structs.KeyMods,
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, eng: *Core) !Platform {
|
||||
var selector = [1]u8{0} ** 15;
|
||||
const id = js.machCanvasInit(&selector[0]);
|
||||
|
||||
var platform = Platform{
|
||||
.id = id,
|
||||
.selector_id = try allocator.dupe(u8, selector[0 .. selector.len - @as(u32, if (selector[selector.len - 1] == 0) 1 else 0)]),
|
||||
.allocator = allocator,
|
||||
.last_window_size = .{
|
||||
.width = js.machCanvasGetWindowWidth(id),
|
||||
.height = js.machCanvasGetWindowHeight(id),
|
||||
},
|
||||
.last_framebuffer_size = .{
|
||||
.width = js.machCanvasGetFramebufferWidth(id),
|
||||
.height = js.machCanvasGetFramebufferHeight(id),
|
||||
},
|
||||
|
||||
// TODO initialize these properly
|
||||
.last_cursor_position = .{
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
},
|
||||
.last_key_mods = .{
|
||||
.shift = false,
|
||||
.control = false,
|
||||
.alt = false,
|
||||
.super = false,
|
||||
.caps_lock = false,
|
||||
.num_lock = false,
|
||||
},
|
||||
};
|
||||
|
||||
try platform.setOptions(eng.options);
|
||||
return platform;
|
||||
}
|
||||
|
||||
pub fn deinit(platform: *Platform) void {
|
||||
js.machCanvasDeinit(platform.id);
|
||||
platform.allocator.free(platform.selector_id);
|
||||
}
|
||||
|
||||
pub fn setOptions(platform: *Platform, options: structs.Options) !void {
|
||||
// NOTE: size limits do not exists on wasm
|
||||
js.machCanvasSetSize(platform.id, options.width, options.height);
|
||||
|
||||
const title = std.mem.span(options.title);
|
||||
js.machCanvasSetTitle(platform.id, title.ptr, title.len);
|
||||
|
||||
js.machCanvasSetFullscreen(platform.id, options.fullscreen);
|
||||
}
|
||||
|
||||
pub fn close(_: *Platform) void {
|
||||
js.machEmitCloseEvent();
|
||||
}
|
||||
|
||||
pub fn setWaitEvent(_: *Platform, timeout: f64) void {
|
||||
js.machSetWaitEvent(timeout);
|
||||
}
|
||||
|
||||
pub fn getFramebufferSize(platform: *Platform) structs.Size {
|
||||
return platform.last_framebuffer_size;
|
||||
}
|
||||
|
||||
pub fn getWindowSize(platform: *Platform) structs.Size {
|
||||
return platform.last_window_size;
|
||||
}
|
||||
|
||||
pub fn setMouseCursor(_: *Platform, cursor: enums.MouseCursor) !void {
|
||||
const cursor_name = @tagName(cursor);
|
||||
js.machSetMouseCursor(cursor_name.ptr, cursor_name.len);
|
||||
}
|
||||
|
||||
pub fn setCursorMode(_: *Platform, mode: enums.CursorMode) !void {
|
||||
const mode_name = @tagName(mode);
|
||||
js.machSetCursorMode(mode_name.ptr, mode_name.len);
|
||||
}
|
||||
|
||||
fn pollChanges(platform: *Platform) void {
|
||||
const change_type = js.machChangeShift();
|
||||
|
||||
switch (change_type) {
|
||||
1 => {
|
||||
const width = js.machChangeShift();
|
||||
const height = js.machChangeShift();
|
||||
const device_pixel_ratio = js.machChangeShift();
|
||||
|
||||
platform.last_window_size = .{
|
||||
.width = @divFloor(width, device_pixel_ratio),
|
||||
.height = @divFloor(height, device_pixel_ratio),
|
||||
};
|
||||
|
||||
platform.last_framebuffer_size = .{
|
||||
.width = width,
|
||||
.height = height,
|
||||
};
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hasEvent(_: *Platform) bool {
|
||||
return js.machHasEvent();
|
||||
}
|
||||
|
||||
pub fn pollEvent(platform: *Platform) ?structs.Event {
|
||||
const event_type = js.machEventShift();
|
||||
|
||||
return switch (event_type) {
|
||||
1, 2 => key_down: {
|
||||
const key = @intToEnum(enums.Key, js.machEventShift());
|
||||
switch (key) {
|
||||
.left_shift, .right_shift => platform.last_key_mods.shift = true,
|
||||
.left_control, .right_control => platform.last_key_mods.control = true,
|
||||
.left_alt, .right_alt => platform.last_key_mods.alt = true,
|
||||
.left_super, .right_super => platform.last_key_mods.super = true,
|
||||
.caps_lock => platform.last_key_mods.caps_lock = true,
|
||||
.num_lock => platform.last_key_mods.num_lock = true,
|
||||
else => {},
|
||||
}
|
||||
break :key_down switch (event_type) {
|
||||
1 => structs.Event{
|
||||
.key_press = .{
|
||||
.key = key,
|
||||
.mods = platform.last_key_mods,
|
||||
},
|
||||
},
|
||||
2 => structs.Event{
|
||||
.key_repeat = .{
|
||||
.key = key,
|
||||
.mods = platform.last_key_mods,
|
||||
},
|
||||
},
|
||||
else => unreachable,
|
||||
};
|
||||
},
|
||||
3 => key_release: {
|
||||
const key = @intToEnum(enums.Key, js.machEventShift());
|
||||
switch (key) {
|
||||
.left_shift, .right_shift => platform.last_key_mods.shift = false,
|
||||
.left_control, .right_control => platform.last_key_mods.control = false,
|
||||
.left_alt, .right_alt => platform.last_key_mods.alt = false,
|
||||
.left_super, .right_super => platform.last_key_mods.super = false,
|
||||
.caps_lock => platform.last_key_mods.caps_lock = false,
|
||||
.num_lock => platform.last_key_mods.num_lock = false,
|
||||
else => {},
|
||||
}
|
||||
break :key_release structs.Event{
|
||||
.key_release = .{
|
||||
.key = key,
|
||||
.mods = platform.last_key_mods,
|
||||
},
|
||||
};
|
||||
},
|
||||
4 => mouse_motion: {
|
||||
const x = @intToFloat(f64, js.machEventShift());
|
||||
const y = @intToFloat(f64, js.machEventShift());
|
||||
platform.last_cursor_position = .{
|
||||
.x = x,
|
||||
.y = y,
|
||||
};
|
||||
break :mouse_motion structs.Event{
|
||||
.mouse_motion = .{
|
||||
.pos = .{
|
||||
.x = x,
|
||||
.y = y,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
5 => structs.Event{
|
||||
.mouse_press = .{
|
||||
.button = toMachButton(js.machEventShift()),
|
||||
.pos = platform.last_cursor_position,
|
||||
.mods = platform.last_key_mods,
|
||||
},
|
||||
},
|
||||
6 => structs.Event{
|
||||
.mouse_release = .{
|
||||
.button = toMachButton(js.machEventShift()),
|
||||
.pos = platform.last_cursor_position,
|
||||
.mods = platform.last_key_mods,
|
||||
},
|
||||
},
|
||||
7 => structs.Event{
|
||||
.mouse_scroll = .{
|
||||
.xoffset = @floatCast(f32, sign(js.machEventShiftFloat())),
|
||||
.yoffset = @floatCast(f32, sign(js.machEventShiftFloat())),
|
||||
},
|
||||
},
|
||||
8 => structs.Event.focus_gained,
|
||||
9 => structs.Event.focus_lost,
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
inline fn sign(val: f64) f64 {
|
||||
return if (val == 0.0) 0.0 else -val;
|
||||
}
|
||||
|
||||
fn toMachButton(button: i32) enums.MouseButton {
|
||||
return switch (button) {
|
||||
0 => .left,
|
||||
1 => .middle,
|
||||
2 => .right,
|
||||
3 => .four,
|
||||
4 => .five,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
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 core: Core = undefined;
|
||||
|
||||
export fn wasmInit() void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
Core.init(allocator, &core) catch unreachable;
|
||||
app.init(&core) catch {};
|
||||
}
|
||||
|
||||
export fn wasmUpdate() void {
|
||||
// Poll internal events, like resize
|
||||
core.internal.pollChanges();
|
||||
|
||||
core.delta_time_ns = core.timer.lapPrecise();
|
||||
core.delta_time = @intToFloat(f32, core.delta_time_ns) / @intToFloat(f32, std.time.ns_per_s);
|
||||
|
||||
app.update(&core) catch core.close();
|
||||
}
|
||||
|
||||
export fn wasmDeinit() void {
|
||||
app.deinit(&core);
|
||||
core.internal.deinit();
|
||||
}
|
||||
|
||||
pub const log_level = if (@hasDecl(App, "log_level")) App.log_level else std.log.default_level;
|
||||
pub const scope_levels = if (@hasDecl(App, "scope_levels")) App.scope_levels else [0]std.log.ScopeLevel{};
|
||||
|
||||
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, error_return_trace: ?*std.builtin.StackTrace, ret_addr: ?usize) noreturn {
|
||||
_ = error_return_trace;
|
||||
_ = ret_addr;
|
||||
js.machPanic(msg.ptr, msg.len);
|
||||
unreachable;
|
||||
}
|
||||
pub const Core = @import("wasm/Core.zig");
|
||||
pub const Timer = @import("wasm/Timer.zig");
|
||||
pub const entry = @import("wasm/entry.zig");
|
||||
|
|
|
|||
299
src/platform/wasm/Core.zig
Normal file
299
src/platform/wasm/Core.zig
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
const std = @import("std");
|
||||
const gpu = @import("gpu");
|
||||
const js = @import("js.zig");
|
||||
const Timer = @import("Timer.zig");
|
||||
const Options = @import("../../Core.zig").Options;
|
||||
const Event = @import("../../Core.zig").Event;
|
||||
const KeyEvent = @import("../../Core.zig").KeyEvent;
|
||||
const MouseButtonEvent = @import("../../Core.zig").MouseButtonEvent;
|
||||
const MouseButton = @import("../../Core.zig").MouseButton;
|
||||
const Size = @import("../../Core.zig").Size;
|
||||
const Position = @import("../../Core.zig").Position;
|
||||
const DisplayMode = @import("../../Core.zig").DisplayMode;
|
||||
const SizeLimit = @import("../../Core.zig").SizeLimit;
|
||||
const CursorShape = @import("../../Core.zig").CursorShape;
|
||||
const VSyncMode = @import("../../Core.zig").VSyncMode;
|
||||
const CursorMode = @import("../../Core.zig").CursorMode;
|
||||
const Key = @import("../../Core.zig").Key;
|
||||
const KeyMods = @import("../../Core.zig").KeyMods;
|
||||
|
||||
pub const Core = @This();
|
||||
|
||||
allocator: std.mem.Allocator,
|
||||
id: js.CanvasId,
|
||||
|
||||
last_cursor_position: Position,
|
||||
last_key_mods: KeyMods,
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, options: Options) !*Core {
|
||||
_ = options;
|
||||
var selector = [1]u8{0} ** 15;
|
||||
const id = js.machCanvasInit(&selector[0]);
|
||||
|
||||
const self: *Core = try allocator.create(Core);
|
||||
errdefer allocator.destroy(self);
|
||||
self.* = Core{
|
||||
.allocator = allocator,
|
||||
.id = id,
|
||||
|
||||
// TODO initialize these properly
|
||||
.last_cursor_position = .{
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
},
|
||||
.last_key_mods = .{
|
||||
.shift = false,
|
||||
.control = false,
|
||||
.alt = false,
|
||||
.super = false,
|
||||
.caps_lock = false,
|
||||
.num_lock = false,
|
||||
},
|
||||
};
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Core) void {
|
||||
js.machCanvasDeinit(self.id);
|
||||
}
|
||||
|
||||
pub fn hasEvent(_: *Core) bool {
|
||||
return js.machHasEvent();
|
||||
}
|
||||
|
||||
pub fn pollEvents(self: *Core) ?Event {
|
||||
const event_int = js.machEventShift();
|
||||
if (event_int == -1) return null;
|
||||
|
||||
const event_type = @intToEnum(std.meta.Tag(Event), event_int);
|
||||
return switch (event_type) {
|
||||
.key_press, .key_repeat => blk: {
|
||||
const key = @intToEnum(Key, js.machEventShift());
|
||||
switch (key) {
|
||||
.left_shift, .right_shift => self.last_key_mods.shift = true,
|
||||
.left_control, .right_control => self.last_key_mods.control = true,
|
||||
.left_alt, .right_alt => self.last_key_mods.alt = true,
|
||||
.left_super, .right_super => self.last_key_mods.super = true,
|
||||
.caps_lock => self.last_key_mods.caps_lock = true,
|
||||
.num_lock => self.last_key_mods.num_lock = true,
|
||||
else => {},
|
||||
}
|
||||
break :blk switch (event_type) {
|
||||
.key_press => Event{
|
||||
.key_press = .{
|
||||
.key = key,
|
||||
.mods = self.last_key_mods,
|
||||
},
|
||||
},
|
||||
.key_repeat => Event{
|
||||
.key_repeat = .{
|
||||
.key = key,
|
||||
.mods = self.last_key_mods,
|
||||
},
|
||||
},
|
||||
else => unreachable,
|
||||
};
|
||||
},
|
||||
.key_release => blk: {
|
||||
const key = @intToEnum(Key, js.machEventShift());
|
||||
switch (key) {
|
||||
.left_shift, .right_shift => self.last_key_mods.shift = false,
|
||||
.left_control, .right_control => self.last_key_mods.control = false,
|
||||
.left_alt, .right_alt => self.last_key_mods.alt = false,
|
||||
.left_super, .right_super => self.last_key_mods.super = false,
|
||||
.caps_lock => self.last_key_mods.caps_lock = false,
|
||||
.num_lock => self.last_key_mods.num_lock = false,
|
||||
else => {},
|
||||
}
|
||||
break :blk Event{
|
||||
.key_release = .{
|
||||
.key = key,
|
||||
.mods = self.last_key_mods,
|
||||
},
|
||||
};
|
||||
},
|
||||
.mouse_motion => blk: {
|
||||
const x = @intToFloat(f64, js.machEventShift());
|
||||
const y = @intToFloat(f64, js.machEventShift());
|
||||
self.last_cursor_position = .{
|
||||
.x = x,
|
||||
.y = y,
|
||||
};
|
||||
break :blk Event{
|
||||
.mouse_motion = .{
|
||||
.pos = .{
|
||||
.x = x,
|
||||
.y = y,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
.mouse_press => Event{
|
||||
.mouse_press = .{
|
||||
.button = toMachButton(js.machEventShift()),
|
||||
.pos = self.last_cursor_position,
|
||||
.mods = self.last_key_mods,
|
||||
},
|
||||
},
|
||||
.mouse_release => Event{
|
||||
.mouse_release = .{
|
||||
.button = toMachButton(js.machEventShift()),
|
||||
.pos = self.last_cursor_position,
|
||||
.mods = self.last_key_mods,
|
||||
},
|
||||
},
|
||||
.mouse_scroll => Event{
|
||||
.mouse_scroll = .{
|
||||
.xoffset = @floatCast(f32, std.math.sign(js.machEventShiftFloat())),
|
||||
.yoffset = @floatCast(f32, std.math.sign(js.machEventShiftFloat())),
|
||||
},
|
||||
},
|
||||
.framebuffer_resize => blk: {
|
||||
const width = @intCast(u32, js.machEventShift());
|
||||
const height = @intCast(u32, js.machEventShift());
|
||||
const pixel_ratio = @intCast(u32, js.machEventShift());
|
||||
break :blk Event{
|
||||
.framebuffer_resize = .{
|
||||
.width = width * pixel_ratio,
|
||||
.height = height * pixel_ratio,
|
||||
},
|
||||
};
|
||||
},
|
||||
.focus_gained => Event.focus_gained,
|
||||
.focus_lost => Event.focus_lost,
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn framebufferSize(self: *Core) Size {
|
||||
return .{
|
||||
.width = js.machCanvasFramebufferWidth(self.id),
|
||||
.height = js.machCanvasFramebufferHeight(self.id),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn setWaitTimeout(_: *Core, timeout: f64) void {
|
||||
js.machSetWaitTimeout(timeout);
|
||||
}
|
||||
|
||||
pub fn setTitle(self: *Core, title: [:0]const u8) void {
|
||||
js.machCanvasSetTitle(self.id, title.ptr, title.len);
|
||||
}
|
||||
|
||||
pub fn setDisplayMode(self: *Core, mode: DisplayMode, monitor: ?usize) void {
|
||||
_ = monitor;
|
||||
js.machCanvasSetDisplayMode(self.id, @enumToInt(mode));
|
||||
}
|
||||
|
||||
pub fn displayMode(self: *Core) DisplayMode {
|
||||
return @intToEnum(DisplayMode, js.machDisplayMode(self.id));
|
||||
}
|
||||
|
||||
pub fn setBorder(self: *Core, value: bool) void {
|
||||
_ = self;
|
||||
_ = value;
|
||||
}
|
||||
|
||||
pub fn border(self: *Core) bool {
|
||||
_ = self;
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn setHeadless(self: *Core, value: bool) void {
|
||||
_ = self;
|
||||
_ = value;
|
||||
}
|
||||
|
||||
pub fn headless(self: *Core) bool {
|
||||
_ = self;
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn setVSync(self: *Core, mode: VSyncMode) void {
|
||||
_ = self;
|
||||
_ = mode;
|
||||
}
|
||||
|
||||
// TODO: https://github.com/gpuweb/gpuweb/issues/1224
|
||||
pub fn vsync(self: *Core) VSyncMode {
|
||||
_ = self;
|
||||
return .double;
|
||||
}
|
||||
|
||||
pub fn setSize(self: *Core, value: Size) void {
|
||||
js.machCanvasSetSize(self.id, value.width, value.height);
|
||||
}
|
||||
|
||||
pub fn size(self: *Core) Size {
|
||||
return .{
|
||||
.width = js.machCanvasWidth(self.id),
|
||||
.height = js.machCanvasHeight(self.id),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn setSizeLimit(self: *Core, limit: SizeLimit) void {
|
||||
js.machCanvasSetSizeLimit(
|
||||
self.id,
|
||||
if (limit.min.width) |val| @intCast(i32, val) else -1,
|
||||
if (limit.min.height) |val| @intCast(i32, val) else -1,
|
||||
if (limit.max.width) |val| @intCast(i32, val) else -1,
|
||||
if (limit.max.height) |val| @intCast(i32, val) else -1,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn sizeLimit(self: *Core) SizeLimit {
|
||||
return .{
|
||||
.min = .{
|
||||
.width = js.machCanvasMinWidth(self.id),
|
||||
.height = js.machCanvasMinHeight(self.id),
|
||||
},
|
||||
.max = .{
|
||||
.width = js.machCanvasMaxWidth(self.id),
|
||||
.height = js.machCanvasMaxHeight(self.id),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn setCursorMode(self: *Core, mode: CursorMode) void {
|
||||
js.machSetCursorMode(self.id, @enumToInt(mode));
|
||||
}
|
||||
|
||||
pub fn cursorMode(self: *Core) CursorMode {
|
||||
return @intToEnum(CursorMode, js.machCursorMode(self.id));
|
||||
}
|
||||
|
||||
pub fn setCursorShape(self: *Core, shape: CursorShape) void {
|
||||
js.machSetCursorShape(self.id, @enumToInt(shape));
|
||||
}
|
||||
|
||||
pub fn cursorShape(self: *Core) CursorShape {
|
||||
return @intToEnum(CursorShape, js.machCursorShape(self.id));
|
||||
}
|
||||
|
||||
pub fn adapter(_: *Core) *gpu.Adapter {
|
||||
unreachable;
|
||||
}
|
||||
|
||||
pub fn device(_: *Core) *gpu.Device {
|
||||
unreachable;
|
||||
}
|
||||
|
||||
pub fn swapChain(_: *Core) *gpu.SwapChain {
|
||||
unreachable;
|
||||
}
|
||||
|
||||
pub fn descriptor(_: *Core) gpu.SwapChain.Descriptor {
|
||||
unreachable;
|
||||
}
|
||||
|
||||
fn toMachButton(button: i32) MouseButton {
|
||||
return switch (button) {
|
||||
0 => .left,
|
||||
1 => .middle,
|
||||
2 => .right,
|
||||
3 => .four,
|
||||
4 => .five,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
25
src/platform/wasm/Timer.zig
Normal file
25
src/platform/wasm/Timer.zig
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
const std = @import("std");
|
||||
const js = @import("js.zig");
|
||||
|
||||
pub const Timer = @This();
|
||||
|
||||
initial: f64 = undefined,
|
||||
|
||||
pub fn start() !Timer {
|
||||
return Timer{ .initial = js.machPerfNow() };
|
||||
}
|
||||
|
||||
pub fn read(timer: *Timer) u64 {
|
||||
return (js.machPerfNow() - timer.initial) * std.time.ns_per_ms;
|
||||
}
|
||||
|
||||
pub fn reset(timer: *Timer) void {
|
||||
timer.initial = js.machPerfNow();
|
||||
}
|
||||
|
||||
pub fn lap(timer: *Timer) u64 {
|
||||
const now = js.machPerfNow();
|
||||
const initial = timer.initial;
|
||||
timer.initial = now;
|
||||
return @floatToInt(u64, now - initial) * std.time.ns_per_ms;
|
||||
}
|
||||
49
src/platform/wasm/entry.zig
Normal file
49
src/platform/wasm/entry.zig
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
const std = @import("std");
|
||||
const gpu = @import("gpu");
|
||||
const App = @import("app").App;
|
||||
const js = @import("js.zig");
|
||||
|
||||
pub const GPUInterface = gpu.StubInterface;
|
||||
pub const log_level = if (@hasDecl(App, "log_level")) App.log_level else std.log.default_level;
|
||||
pub const scope_levels = if (@hasDecl(App, "scope_levels")) App.scope_levels else [0]std.log.ScopeLevel{};
|
||||
|
||||
var app: App = undefined;
|
||||
export fn wasmInit() void {
|
||||
app.init() catch unreachable;
|
||||
}
|
||||
|
||||
export fn wasmUpdate() bool {
|
||||
return app.update() catch unreachable;
|
||||
}
|
||||
|
||||
export fn wasmDeinit() void {
|
||||
app.deinit();
|
||||
}
|
||||
|
||||
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, error_return_trace: ?*std.builtin.StackTrace, ret_addr: ?usize) noreturn {
|
||||
_ = error_return_trace;
|
||||
_ = ret_addr;
|
||||
js.machPanic(msg.ptr, msg.len);
|
||||
unreachable;
|
||||
}
|
||||
40
src/platform/wasm/js.zig
Normal file
40
src/platform/wasm/js.zig
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
pub const CanvasId = u32;
|
||||
|
||||
pub extern "mach" fn machLogWrite(str: [*]const u8, len: u32) void;
|
||||
pub extern "mach" fn machLogFlush() void;
|
||||
pub extern "mach" fn machPanic(str: [*]const u8, len: u32) void;
|
||||
|
||||
pub extern "mach" fn machCanvasInit(selector_id: *u8) CanvasId;
|
||||
pub extern "mach" fn machCanvasDeinit(canvas: CanvasId) void;
|
||||
pub extern "mach" fn machCanvasFramebufferWidth(canvas: CanvasId) u32;
|
||||
pub extern "mach" fn machCanvasFramebufferHeight(canvas: CanvasId) u32;
|
||||
pub extern "mach" fn machCanvasSetTitle(canvas: CanvasId, title: [*]const u8, len: u32) void;
|
||||
pub extern "mach" fn machCanvasSetDisplayMode(canvas: CanvasId, mode: u32) void;
|
||||
pub extern "mach" fn machCanvasDisplayMode(canvas: CanvasId) u32;
|
||||
pub extern "mach" fn machCanvasSetBorder(canvas: CanvasId, value: bool) void;
|
||||
pub extern "mach" fn machCanvasBorder(canvas: CanvasId) bool;
|
||||
pub extern "mach" fn machCanvasSetHeadless(canvas: CanvasId, value: bool) void;
|
||||
pub extern "mach" fn machCanvasHeadless(canvas: CanvasId) bool;
|
||||
pub extern "mach" fn machCanvasSetVsync(canvas: CanvasId, mode: u32) void;
|
||||
pub extern "mach" fn machCanvasVsync(canvas: CanvasId) u32;
|
||||
pub extern "mach" fn machCanvasSetSize(canvas: CanvasId, width: u32, height: u32) void;
|
||||
pub extern "mach" fn machCanvasWidth(canvas: CanvasId) u32;
|
||||
pub extern "mach" fn machCanvasHeight(canvas: CanvasId) u32;
|
||||
pub extern "mach" fn machCanvasSetSizeLimit(canvas: CanvasId, min_width: i32, min_height: i32, max_width: i32, max_height: i32) void;
|
||||
pub extern "mach" fn machCanvasMinWidth(canvas: CanvasId) u32;
|
||||
pub extern "mach" fn machCanvasMinHeight(canvas: CanvasId) u32;
|
||||
pub extern "mach" fn machCanvasMaxWidth(canvas: CanvasId) u32;
|
||||
pub extern "mach" fn machCanvasMaxHeight(canvas: CanvasId) u32;
|
||||
pub extern "mach" fn machSetCursorMode(canvas: CanvasId, mode: u32) void;
|
||||
pub extern "mach" fn machCursorMode(canvas: CanvasId) u32;
|
||||
pub extern "mach" fn machSetCursorShape(canvas: CanvasId, shape: u32) void;
|
||||
pub extern "mach" fn machCursorShape(canvas: CanvasId) u32;
|
||||
|
||||
pub extern "mach" fn machShouldClose() bool;
|
||||
pub extern "mach" fn machHasEvent() bool;
|
||||
pub extern "mach" fn machSetWaitTimeout(timeout: f64) void;
|
||||
pub extern "mach" fn machEventShift() i32;
|
||||
pub extern "mach" fn machEventShiftFloat() f64;
|
||||
pub extern "mach" fn machChangeShift() u32;
|
||||
|
||||
pub extern "mach" fn machPerfNow() f64;
|
||||
511
src/platform/wasm/mach.js
Normal file
511
src/platform/wasm/mach.js
Normal file
|
|
@ -0,0 +1,511 @@
|
|||
const text_decoder = new TextDecoder();
|
||||
const text_encoder = new TextEncoder();
|
||||
|
||||
const mach = {
|
||||
canvases: [],
|
||||
wasm: undefined,
|
||||
observer: undefined,
|
||||
events: [],
|
||||
changes: [],
|
||||
wait_timeout: 0,
|
||||
log_buf: "",
|
||||
|
||||
init(wasm) {
|
||||
mach.wasm = wasm;
|
||||
mach.observer = new MutationObserver((mutables) => {
|
||||
mutables.forEach((mutable) => {
|
||||
mach.canvases.forEach((canvas) => {
|
||||
if (mutable.target == canvas) {
|
||||
if (mutable.attributeName === "width" ||
|
||||
mutable.attributeName === "height" ||
|
||||
mutable.attributeName === "style") {
|
||||
mutable.target.dispatchEvent(new Event("mach-canvas-resize"));
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
getString(str, len) {
|
||||
const memory = mach.wasm.exports.memory.buffer;
|
||||
return text_decoder.decode(new Uint8Array(memory, str, len));
|
||||
},
|
||||
|
||||
setString(str, buf) {
|
||||
const memory = mach.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) {
|
||||
mach.log_buf += mach.getString(str, len);
|
||||
},
|
||||
|
||||
machLogFlush() {
|
||||
console.log(log_buf);
|
||||
mach.log_buf = "";
|
||||
},
|
||||
|
||||
machPanic(str, len) {
|
||||
throw Error(mach.getString(str, len));
|
||||
},
|
||||
|
||||
machCanvasInit(id) {
|
||||
let canvas = document.createElement("canvas");
|
||||
canvas.id = "#mach-canvas-" + mach.canvases.length;
|
||||
canvas.style.border = "1px solid";
|
||||
canvas.style.position = "absolute";
|
||||
canvas.style.display = "block";
|
||||
canvas.tabIndex = 1;
|
||||
|
||||
mach.observer.observe(canvas, { attributes: true });
|
||||
|
||||
mach.setString(canvas.id, id);
|
||||
|
||||
canvas.addEventListener("contextmenu", (ev) => ev.preventDefault());
|
||||
|
||||
canvas.addEventListener("keydown", (ev) => {
|
||||
if (ev.repeat) {
|
||||
mach.events.push(...[EventCode.key_repeat, convertKeyCode(ev.code)]);
|
||||
} else {
|
||||
mach.events.push(...[EventCode.key_press, convertKeyCode(ev.code)]);
|
||||
}
|
||||
});
|
||||
|
||||
canvas.addEventListener("keyup", (ev) => {
|
||||
mach.events.push(...[EventCode.key_release, convertKeyCode(ev.code)]);
|
||||
});
|
||||
|
||||
canvas.addEventListener("mousemove", (ev) => {
|
||||
mach.events.push(...[EventCode.mouse_motion, ev.clientX, ev.clientY]);
|
||||
});
|
||||
|
||||
canvas.addEventListener("mousedown", (ev) => {
|
||||
mach.events.push(...[EventCode.mouse_press, ev.button]);
|
||||
});
|
||||
|
||||
canvas.addEventListener("mouseup", (ev) => {
|
||||
mach.events.push(...[EventCode.mouse_release, ev.button]);
|
||||
});
|
||||
|
||||
canvas.addEventListener("wheel", (ev) => {
|
||||
mach.events.push(...[EventCode.mouse_scroll, ev.deltaX, ev.deltaY]);
|
||||
});
|
||||
|
||||
canvas.addEventListener("mach-canvas-resize", (ev) => {
|
||||
const cv_index = mach.canvases.findIndex((el) => el === ev.currentTarget);
|
||||
const cv = mach.canvases[cv_index];
|
||||
mach.events.push(...[EventCode.framebuffer_resize, cv.width, cv.height, window.devicePixelRatio]);
|
||||
});
|
||||
|
||||
canvas.addEventListener("focus", (ev) => {
|
||||
mach.events.push(...[EventCode.focus_gained]);
|
||||
});
|
||||
|
||||
canvas.addEventListener("blur", (ev) => {
|
||||
mach.events.push(...[EventCode.focus_lost]);
|
||||
});
|
||||
|
||||
document.body.appendChild(canvas);
|
||||
return mach.canvases.push(canvas) - 1;
|
||||
},
|
||||
|
||||
machCanvasDeinit(canvas) {
|
||||
if (mach.canvases[canvas] != undefined) {
|
||||
mach.canvases.splice(canvas, 1);
|
||||
}
|
||||
},
|
||||
|
||||
machCanvasFramebufferWidth(canvas) {
|
||||
const cv = mach.canvases[canvas];
|
||||
return cv.width;
|
||||
},
|
||||
|
||||
machCanvasFramebufferHeight(canvas) {
|
||||
const cv = mach.canvases[canvas];
|
||||
return cv.height;
|
||||
},
|
||||
|
||||
machCanvasSetTitle(canvas, title, len) {
|
||||
// TODO
|
||||
},
|
||||
|
||||
machCanvasSetDisplayMode(canvas, mode) {
|
||||
const cv = mach.canvases[canvas];
|
||||
switch (mode) {
|
||||
case DisplayMode.windowed:
|
||||
document.exitFullscreen();
|
||||
break;
|
||||
case DisplayMode.fullscreen:
|
||||
cv.requestFullscreen();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
machCanvasDisplayMode(canvas) {
|
||||
if (mach.canvases[canvas].fullscreenElement == null) {
|
||||
return DisplayMode.windowed;
|
||||
} else {
|
||||
return DisplayMode.fullscreen;
|
||||
}
|
||||
},
|
||||
|
||||
machCanvasSetBorder(canvas, value) {
|
||||
// TODO
|
||||
},
|
||||
|
||||
machCanvasBorder(canvas) {
|
||||
// TODO
|
||||
},
|
||||
|
||||
machCanvasSetHeadless(canvas, value) {
|
||||
// TODO
|
||||
},
|
||||
|
||||
machCanvasHeadless(canvas) {
|
||||
// TODO
|
||||
},
|
||||
|
||||
machCanvasSetVSync(canvas, mode) {
|
||||
// TODO
|
||||
},
|
||||
|
||||
machCanvasVSync(canvas) {
|
||||
// TODO
|
||||
},
|
||||
|
||||
machCanvasSetSize(canvas, width, height) {
|
||||
const cv = mach.canvases[canvas];
|
||||
if (width > 0 && height > 0) {
|
||||
cv.style.width = width + "px";
|
||||
cv.style.height = height + "px";
|
||||
cv.width = Math.floor(width * window.devicePixelRatio);
|
||||
cv.height = Math.floor(height * window.devicePixelRatio);
|
||||
}
|
||||
},
|
||||
|
||||
machCanvasWidth(canvas) {
|
||||
const cv = mach.canvases[canvas];
|
||||
return cv.width / window.devicePixelRatio;
|
||||
},
|
||||
|
||||
machCanvasHeight(canvas) {
|
||||
const cv = mach.canvases[canvas];
|
||||
return cv.height / window.devicePixelRatio;
|
||||
},
|
||||
|
||||
machCanvasSetSizeLimit(canvas, min_width, min_height, max_width, max_height) {
|
||||
const cv = mach.canvases[canvas];
|
||||
if (min_width == -1) {
|
||||
cv.style.minWidth = "inherit"
|
||||
} else {
|
||||
cv.style.minWidth = min_width + "px";
|
||||
}
|
||||
if (min_width == -1) {
|
||||
cv.style.minHeight = "inherit"
|
||||
} else {
|
||||
cv.style.minHeight = min_height + "px";
|
||||
}
|
||||
if (min_width == -1) {
|
||||
cv.style.maxWidth = "inherit"
|
||||
} else {
|
||||
cv.style.maxWidth = max_width + "px";
|
||||
}
|
||||
if (min_width == -1) {
|
||||
cv.style.maxHeight = "inherit"
|
||||
} else {
|
||||
cv.style.maxHeight = max_height + "px";
|
||||
}
|
||||
},
|
||||
|
||||
machCanvasMinWidth(canvas) {
|
||||
const cv = mach.canvases[canvas];
|
||||
return cv.style.minWidth;
|
||||
},
|
||||
|
||||
machCanvasMinHeight(canvas) {
|
||||
const cv = mach.canvases[canvas];
|
||||
return cv.style.minHeight;
|
||||
},
|
||||
|
||||
machCanvasMaxWidth(canvas) {
|
||||
const cv = mach.canvases[canvas];
|
||||
return cv.style.maxWidth;
|
||||
},
|
||||
|
||||
machCanvasMaxHeight(canvas) {
|
||||
const cv = mach.canvases[canvas];
|
||||
return cv.style.maxHeight;
|
||||
},
|
||||
|
||||
machSetCursorMode(canvas, mode) {
|
||||
const cv = mach.canvases[canvas];
|
||||
switch (mode) {
|
||||
case CursorMode.normal:
|
||||
cv.style.cursor = 'default';
|
||||
break;
|
||||
case CursorMode.hidden:
|
||||
cv.style.cursor = 'none';
|
||||
break;
|
||||
case CursorMode.hidden:
|
||||
cv.style.cursor = 'none';
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
machCursorMode(canvas) {
|
||||
switch (mach.canvases[canvas].style.cursor) {
|
||||
case 'none': return CursorMode.hidden;
|
||||
default: return CursorMode.normal;
|
||||
}
|
||||
},
|
||||
|
||||
machSetCursorShape(canvas, shape) {
|
||||
const cv = mach.canvases[canvas];
|
||||
switch (shape) {
|
||||
case CursorShape.arrow:
|
||||
cv.style.cursor = 'default';
|
||||
break;
|
||||
case CursorShape.ibeam:
|
||||
cv.style.cursor = 'text';
|
||||
break;
|
||||
case CursorShape.crosshair:
|
||||
cv.style.cursor = 'crosshair';
|
||||
break;
|
||||
case CursorShape.pointing_hand:
|
||||
cv.style.cursor = 'pointer';
|
||||
break;
|
||||
case CursorShape.resize_ew:
|
||||
cv.style.cursor = 'ew-resize';
|
||||
break;
|
||||
case CursorShape.resize_ns:
|
||||
cv.style.cursor = 'ns-resize';
|
||||
break;
|
||||
case CursorShape.resize_nwse:
|
||||
cv.style.cursor = 'nwse-resize';
|
||||
break;
|
||||
case CursorShape.resize_nesw:
|
||||
cv.style.cursor = 'nesw-resize';
|
||||
break;
|
||||
case CursorShape.resize_all:
|
||||
cv.style.cursor = 'move';
|
||||
break;
|
||||
case CursorShape.not_allowed:
|
||||
cv.style.cursor = 'not-allowed';
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
machCursorShape(canvas) {
|
||||
switch (mach.canvases[canvas].style.cursor) {
|
||||
case 'default': return CursorShape.arrow;
|
||||
case 'text': return CursorShape.ibeam;
|
||||
case 'crosshair': return CursorShape.crosshair;
|
||||
case 'pointer': return CursorShape.pointing_hand;
|
||||
case 'ew-resize': return CursorShape.resize_ew;
|
||||
case 'ns-resize': return CursorShape.resize_ns;
|
||||
case 'nwse-resize': return CursorShape.resize_nwse;
|
||||
case 'nesw-resize': return CursorShape.resize_nesw;
|
||||
case 'move': return CursorShape.resize_all;
|
||||
case 'not-allowed': return CursorShape.not_allowed;
|
||||
}
|
||||
},
|
||||
|
||||
machSetWaitTimeout(timeout) {
|
||||
mach.wait_timeout = timeout;
|
||||
},
|
||||
|
||||
machHasEvent() {
|
||||
return mach.events.length > 0;
|
||||
},
|
||||
|
||||
machEventShift() {
|
||||
if (mach.machHasEvent())
|
||||
return mach.events.shift();
|
||||
|
||||
return -1;
|
||||
},
|
||||
|
||||
machEventShiftFloat() {
|
||||
return mach.machEventShift();
|
||||
},
|
||||
|
||||
machPerfNow() {
|
||||
return performance.now();
|
||||
},
|
||||
};
|
||||
|
||||
function convertKeyCode(code) {
|
||||
const k = Key[code];
|
||||
if (k != undefined)
|
||||
return k;
|
||||
return 118; // Unknown
|
||||
}
|
||||
|
||||
const Key = {
|
||||
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,
|
||||
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,
|
||||
NumpadDivide: 61,
|
||||
NumpadMultiply: 62,
|
||||
NumpadSubtract: 63,
|
||||
NumpadAdd: 64,
|
||||
Numpad0: 65,
|
||||
Numpad1: 66,
|
||||
Numpad2: 67,
|
||||
Numpad3: 68,
|
||||
Numpad4: 69,
|
||||
Numpad5: 70,
|
||||
Numpad6: 71,
|
||||
Numpad7: 72,
|
||||
Numpad8: 73,
|
||||
Numpad9: 74,
|
||||
NumpadDecimal: 75,
|
||||
NumpadEqual: 76,
|
||||
NumpadEnter: 77,
|
||||
Enter: 78,
|
||||
Escape: 79,
|
||||
Tab: 80,
|
||||
ShiftLeft: 81,
|
||||
ShiftRight: 82,
|
||||
ControlLeft: 83,
|
||||
ControlRight: 84,
|
||||
AltLeft: 85,
|
||||
AltRight: 86,
|
||||
OSLeft: 87,
|
||||
MetaLeft: 87,
|
||||
OSRight: 88,
|
||||
MetaRight: 88,
|
||||
ContextMenu: 89,
|
||||
NumLock: 90,
|
||||
CapsLock: 91,
|
||||
PrintScreen: 92,
|
||||
ScrollLock: 93,
|
||||
Pause: 94,
|
||||
Delete: 95,
|
||||
Home: 96,
|
||||
End: 97,
|
||||
PageUp: 98,
|
||||
PageDown: 99,
|
||||
Insert: 100,
|
||||
ArrowLeft: 101,
|
||||
ArrowRight: 102,
|
||||
ArrowUp: 103,
|
||||
ArrowDown: 104,
|
||||
Backspace: 105,
|
||||
Space: 106,
|
||||
Minus: 107,
|
||||
Equal: 108,
|
||||
BracketLeft: 109,
|
||||
BracketRight: 110,
|
||||
Backslash: 111,
|
||||
Semicolon: 112,
|
||||
Quote: 113,
|
||||
Comma: 114,
|
||||
Period: 115,
|
||||
Slash: 116,
|
||||
Backquote: 117,
|
||||
};
|
||||
|
||||
const EventCode = {
|
||||
key_press: 0,
|
||||
key_repeat: 1,
|
||||
key_release: 2,
|
||||
char_input: 3,
|
||||
mouse_motion: 4,
|
||||
mouse_press: 5,
|
||||
mouse_release: 6,
|
||||
mouse_scroll: 7,
|
||||
framebuffer_resize: 8,
|
||||
focus_gained: 9,
|
||||
focus_lost: 10,
|
||||
close: 11,
|
||||
};
|
||||
|
||||
const DisplayMode = {
|
||||
windowed: 0,
|
||||
fullscreen: 1,
|
||||
};
|
||||
|
||||
const CursorMode = {
|
||||
normal: 0,
|
||||
hidden: 1,
|
||||
disabled: 2,
|
||||
};
|
||||
|
||||
const CursorShape = {
|
||||
arrow: 0,
|
||||
ibeam: 1,
|
||||
crosshair: 2,
|
||||
pointing_hand: 3,
|
||||
resize_ew: 4,
|
||||
resize_ns: 5,
|
||||
resize_nwse: 6,
|
||||
resize_nesw: 7,
|
||||
resize_all: 8,
|
||||
not_allowed: 9,
|
||||
};
|
||||
|
||||
export { mach };
|
||||
110
src/structs.zig
110
src/structs.zig
|
|
@ -1,110 +0,0 @@
|
|||
const gpu = @import("gpu");
|
||||
const enums = @import("enums.zig");
|
||||
|
||||
pub const Size = struct {
|
||||
width: u32,
|
||||
height: u32,
|
||||
};
|
||||
|
||||
pub const SizeOptional = struct {
|
||||
width: ?u32,
|
||||
height: ?u32,
|
||||
};
|
||||
|
||||
/// Application options that can be configured at init time.
|
||||
pub const StartupOptions = struct {};
|
||||
|
||||
/// Application options that can be configured at run time.
|
||||
pub const Options = struct {
|
||||
/// The title of the window.
|
||||
title: [*:0]const u8 = "Mach core",
|
||||
|
||||
/// The width of the window.
|
||||
width: u32 = 640,
|
||||
|
||||
/// The height of the window.
|
||||
height: u32 = 480,
|
||||
|
||||
/// The minimum allowed size for the window. On Linux, if we don't set a minimum size,
|
||||
/// you can squish the window to 0 width and height with strange effects, so it's better to leave
|
||||
/// a minimum size to avoid that. This doesn't prevent you from minimizing the window.
|
||||
size_min: SizeOptional = .{ .width = 350, .height = 350 },
|
||||
|
||||
/// The maximum allowed size for the window.
|
||||
size_max: SizeOptional = .{ .width = null, .height = null },
|
||||
|
||||
/// Fullscreen window.
|
||||
fullscreen: bool = false,
|
||||
|
||||
/// Fullscreen monitor index
|
||||
monitor: ?u32 = null,
|
||||
|
||||
/// Headless mode.
|
||||
headless: bool = false,
|
||||
|
||||
/// Borderless window
|
||||
borderless_window: bool = false,
|
||||
|
||||
/// Monitor synchronization modes.
|
||||
vsync: enums.VSyncMode = .double,
|
||||
|
||||
/// GPU features required by the application.
|
||||
required_features: ?[]gpu.FeatureName = null,
|
||||
|
||||
/// GPU limits required by the application.
|
||||
required_limits: ?gpu.Limits = null,
|
||||
|
||||
/// Whether the application has a preference for low power or high performance GPU.
|
||||
power_preference: gpu.PowerPreference = .undefined,
|
||||
|
||||
/// If set, optimize for regular applications rather than games. e.g. disable Linux gamemode / process priority, prefer low-power GPU (if preference is .undefined), etc.
|
||||
is_app: bool = false,
|
||||
};
|
||||
|
||||
pub const Event = union(enum) {
|
||||
key_press: KeyEvent,
|
||||
key_repeat: KeyEvent,
|
||||
key_release: KeyEvent,
|
||||
char_input: struct {
|
||||
codepoint: u21,
|
||||
},
|
||||
mouse_motion: struct {
|
||||
pos: WindowPos,
|
||||
},
|
||||
mouse_press: MouseButtonEvent,
|
||||
mouse_release: MouseButtonEvent,
|
||||
mouse_scroll: struct {
|
||||
xoffset: f32,
|
||||
yoffset: f32,
|
||||
},
|
||||
focus_gained,
|
||||
focus_lost,
|
||||
closed,
|
||||
};
|
||||
|
||||
pub const KeyEvent = struct {
|
||||
key: enums.Key,
|
||||
mods: KeyMods,
|
||||
};
|
||||
|
||||
pub const MouseButtonEvent = struct {
|
||||
button: enums.MouseButton,
|
||||
pos: WindowPos,
|
||||
mods: KeyMods,
|
||||
};
|
||||
|
||||
pub const KeyMods = packed struct {
|
||||
shift: bool,
|
||||
control: bool,
|
||||
alt: bool,
|
||||
super: bool,
|
||||
caps_lock: bool,
|
||||
num_lock: bool,
|
||||
_reserved: u2 = 0,
|
||||
};
|
||||
|
||||
pub const WindowPos = struct {
|
||||
// These are in window coordinates (not framebuffer coords)
|
||||
x: f64,
|
||||
y: f64,
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue