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
18
build.zig
18
build.zig
|
|
@ -127,12 +127,7 @@ fn testStep(b: *Builder, mode: std.builtin.Mode, target: CrossTarget) *std.build
|
||||||
for (pkg.dependencies.?) |dependency| {
|
for (pkg.dependencies.?) |dependency| {
|
||||||
main_tests.addPackage(dependency);
|
main_tests.addPackage(dependency);
|
||||||
}
|
}
|
||||||
|
|
||||||
main_tests.addPackage(freetype.pkg);
|
|
||||||
freetype.link(b, main_tests, .{});
|
|
||||||
|
|
||||||
main_tests.install();
|
main_tests.install();
|
||||||
|
|
||||||
return main_tests.run();
|
return main_tests.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -226,16 +221,12 @@ pub const App = struct {
|
||||||
|
|
||||||
const step = blk: {
|
const step = blk: {
|
||||||
if (platform == .web) {
|
if (platform == .web) {
|
||||||
const lib = b.addSharedLibrary(options.name, sdkPath("/src/platform/wasm.zig"), .unversioned);
|
const lib = b.addSharedLibrary(options.name, sdkPath("/src/main.zig"), .unversioned);
|
||||||
lib.addPackage(gpu.pkg);
|
|
||||||
lib.addPackage(sysaudio.pkg);
|
|
||||||
lib.addPackage(sysjs.pkg);
|
lib.addPackage(sysjs.pkg);
|
||||||
|
|
||||||
break :blk lib;
|
break :blk lib;
|
||||||
} else {
|
} else {
|
||||||
const exe = b.addExecutable(options.name, sdkPath("/src/platform/native.zig"));
|
const exe = b.addExecutable(options.name, sdkPath("/src/main.zig"));
|
||||||
exe.addPackage(gpu.pkg);
|
|
||||||
exe.addPackage(sysaudio.pkg);
|
|
||||||
exe.addPackage(glfw.pkg);
|
exe.addPackage(glfw.pkg);
|
||||||
|
|
||||||
if (target.os.tag == .linux)
|
if (target.os.tag == .linux)
|
||||||
|
|
@ -246,6 +237,9 @@ pub const App = struct {
|
||||||
};
|
};
|
||||||
|
|
||||||
step.main_pkg_path = sdkPath("/src");
|
step.main_pkg_path = sdkPath("/src");
|
||||||
|
step.addPackage(ecs.pkg);
|
||||||
|
step.addPackage(sysaudio.pkg);
|
||||||
|
step.addPackage(gpu.pkg);
|
||||||
step.addPackage(app_pkg);
|
step.addPackage(app_pkg);
|
||||||
step.setTarget(options.target);
|
step.setTarget(options.target);
|
||||||
step.setBuildMode(options.mode);
|
step.setBuildMode(options.mode);
|
||||||
|
|
@ -285,7 +279,7 @@ pub const App = struct {
|
||||||
// Set install directory to '{prefix}/www'
|
// Set install directory to '{prefix}/www'
|
||||||
app.getInstallStep().?.dest_dir = web_install_dir;
|
app.getInstallStep().?.dest_dir = web_install_dir;
|
||||||
|
|
||||||
inline for (.{ "/src/platform/mach.js", "/libs/sysjs/src/mach-sysjs.js" }) |js| {
|
inline for (.{ "/src/platform/wasm/mach.js", "/libs/sysjs/src/mach-sysjs.js" }) |js| {
|
||||||
const install_js = app.b.addInstallFileWithDir(
|
const install_js = app.b.addInstallFileWithDir(
|
||||||
.{ .path = sdkPath(js) },
|
.{ .path = sdkPath(js) },
|
||||||
web_install_dir,
|
web_install_dir,
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,11 @@ const UniformBufferObject = struct {
|
||||||
time: f32,
|
time: f32,
|
||||||
};
|
};
|
||||||
|
|
||||||
var timer: std.time.Timer = undefined;
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
|
core: mach.Core,
|
||||||
|
timer: mach.Timer,
|
||||||
pipeline: *gpu.RenderPipeline,
|
pipeline: *gpu.RenderPipeline,
|
||||||
queue: *gpu.Queue,
|
queue: *gpu.Queue,
|
||||||
uniform_buffer: *gpu.Buffer,
|
uniform_buffer: *gpu.Buffer,
|
||||||
|
|
@ -21,8 +24,8 @@ fragment_shader_file: std.fs.File,
|
||||||
fragment_shader_code: [:0]const u8,
|
fragment_shader_code: [:0]const u8,
|
||||||
last_mtime: i128,
|
last_mtime: i128,
|
||||||
|
|
||||||
pub fn init(app: *App, core: *mach.Core) !void {
|
pub fn init(app: *App) !void {
|
||||||
timer = try std.time.Timer.start();
|
app.core = try mach.Core.init(allocator, .{ .title = "shaderexp" });
|
||||||
|
|
||||||
var fragment_file: std.fs.File = undefined;
|
var fragment_file: std.fs.File = undefined;
|
||||||
var last_mtime: i128 = undefined;
|
var last_mtime: i128 = undefined;
|
||||||
|
|
@ -40,21 +43,21 @@ pub fn init(app: *App, core: *mach.Core) !void {
|
||||||
std.debug.print("Something went wrong when attempting to open file: {}\n", .{e});
|
std.debug.print("Something went wrong when attempting to open file: {}\n", .{e});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var code = try fragment_file.readToEndAllocOptions(core.allocator, std.math.maxInt(u16), null, 1, 0);
|
var code = try fragment_file.readToEndAllocOptions(allocator, std.math.maxInt(u16), null, 1, 0);
|
||||||
|
|
||||||
const queue = core.device.getQueue();
|
const queue = app.core.device().getQueue();
|
||||||
|
|
||||||
// We need a bgl to bind the UniformBufferObject, but it is also needed for creating
|
// We need a bgl to bind the UniformBufferObject, but it is also needed for creating
|
||||||
// the RenderPipeline, so we pass it to recreatePipeline as a pointer
|
// the RenderPipeline, so we pass it to recreatePipeline as a pointer
|
||||||
var bgl: *gpu.BindGroupLayout = undefined;
|
var bgl: *gpu.BindGroupLayout = undefined;
|
||||||
const pipeline = recreatePipeline(core, code, &bgl);
|
const pipeline = recreatePipeline(&app.core, code, &bgl);
|
||||||
|
|
||||||
const uniform_buffer = core.device.createBuffer(&.{
|
const uniform_buffer = app.core.device().createBuffer(&.{
|
||||||
.usage = .{ .copy_dst = true, .uniform = true },
|
.usage = .{ .copy_dst = true, .uniform = true },
|
||||||
.size = @sizeOf(UniformBufferObject),
|
.size = @sizeOf(UniformBufferObject),
|
||||||
.mapped_at_creation = false,
|
.mapped_at_creation = false,
|
||||||
});
|
});
|
||||||
const bind_group = core.device.createBindGroup(
|
const bind_group = app.core.device().createBindGroup(
|
||||||
&gpu.BindGroup.Descriptor.init(.{
|
&gpu.BindGroup.Descriptor.init(.{
|
||||||
.layout = bgl,
|
.layout = bgl,
|
||||||
.entries = &.{
|
.entries = &.{
|
||||||
|
|
@ -63,6 +66,8 @@ pub fn init(app: *App, core: *mach.Core) !void {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
app.timer = try mach.Timer.start();
|
||||||
|
|
||||||
app.pipeline = pipeline;
|
app.pipeline = pipeline;
|
||||||
app.queue = queue;
|
app.queue = queue;
|
||||||
app.uniform_buffer = uniform_buffer;
|
app.uniform_buffer = uniform_buffer;
|
||||||
|
|
@ -75,21 +80,24 @@ pub fn init(app: *App, core: *mach.Core) !void {
|
||||||
bgl.release();
|
bgl.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(app: *App, core: *mach.Core) void {
|
pub fn deinit(app: *App) void {
|
||||||
|
defer _ = gpa.deinit();
|
||||||
|
defer app.core.deinit();
|
||||||
|
|
||||||
app.fragment_shader_file.close();
|
app.fragment_shader_file.close();
|
||||||
core.allocator.free(app.fragment_shader_code);
|
allocator.free(app.fragment_shader_code);
|
||||||
|
|
||||||
app.uniform_buffer.release();
|
app.uniform_buffer.release();
|
||||||
app.bind_group.release();
|
app.bind_group.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(app: *App, core: *mach.Core) !void {
|
pub fn update(app: *App) !bool {
|
||||||
while (core.pollEvent()) |event| {
|
while (app.core.pollEvents()) |event| {
|
||||||
switch (event) {
|
switch (event) {
|
||||||
.key_press => |ev| {
|
.key_press => |ev| {
|
||||||
if (ev.key == .space)
|
if (ev.key == .space) return true;
|
||||||
core.close();
|
|
||||||
},
|
},
|
||||||
|
.close => return true,
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -99,17 +107,17 @@ pub fn update(app: *App, core: *mach.Core) !void {
|
||||||
std.log.info("The fragment shader has been changed", .{});
|
std.log.info("The fragment shader has been changed", .{});
|
||||||
app.last_mtime = stat.mtime;
|
app.last_mtime = stat.mtime;
|
||||||
app.fragment_shader_file.seekTo(0) catch unreachable;
|
app.fragment_shader_file.seekTo(0) catch unreachable;
|
||||||
app.fragment_shader_code = app.fragment_shader_file.readToEndAllocOptions(core.allocator, std.math.maxInt(u32), null, 1, 0) catch |err| {
|
app.fragment_shader_code = app.fragment_shader_file.readToEndAllocOptions(allocator, std.math.maxInt(u32), null, 1, 0) catch |err| {
|
||||||
std.log.err("Err: {}", .{err});
|
std.log.err("Err: {}", .{err});
|
||||||
return core.close();
|
return true;
|
||||||
};
|
};
|
||||||
app.pipeline = recreatePipeline(core, app.fragment_shader_code, null);
|
app.pipeline = recreatePipeline(&app.core, app.fragment_shader_code, null);
|
||||||
}
|
}
|
||||||
} else |err| {
|
} else |err| {
|
||||||
std.log.err("Something went wrong when attempting to stat file: {}\n", .{err});
|
std.log.err("Something went wrong when attempting to stat file: {}\n", .{err});
|
||||||
}
|
}
|
||||||
|
|
||||||
const back_buffer_view = core.swap_chain.?.getCurrentTextureView();
|
const back_buffer_view = app.core.swapChain().getCurrentTextureView();
|
||||||
const color_attachment = gpu.RenderPassColorAttachment{
|
const color_attachment = gpu.RenderPassColorAttachment{
|
||||||
.view = back_buffer_view,
|
.view = back_buffer_view,
|
||||||
.clear_value = std.mem.zeroes(gpu.Color),
|
.clear_value = std.mem.zeroes(gpu.Color),
|
||||||
|
|
@ -117,14 +125,14 @@ pub fn update(app: *App, core: *mach.Core) !void {
|
||||||
.store_op = .store,
|
.store_op = .store,
|
||||||
};
|
};
|
||||||
|
|
||||||
const encoder = core.device.createCommandEncoder(null);
|
const encoder = app.core.device().createCommandEncoder(null);
|
||||||
const render_pass_info = gpu.RenderPassDescriptor.init(.{
|
const render_pass_info = gpu.RenderPassDescriptor.init(.{
|
||||||
.color_attachments = &.{color_attachment},
|
.color_attachments = &.{color_attachment},
|
||||||
});
|
});
|
||||||
|
|
||||||
const time = @intToFloat(f32, timer.read()) / @as(f32, std.time.ns_per_s);
|
const time = app.timer.read() / @as(f32, std.time.ns_per_s);
|
||||||
const ubo = UniformBufferObject{
|
const ubo = UniformBufferObject{
|
||||||
.resolution = .{ @intToFloat(f32, core.current_desc.width), @intToFloat(f32, core.current_desc.height) },
|
.resolution = .{ @intToFloat(f32, app.core.descriptor().width), @intToFloat(f32, app.core.descriptor().height) },
|
||||||
.time = time,
|
.time = time,
|
||||||
};
|
};
|
||||||
encoder.writeBuffer(app.uniform_buffer, 0, &[_]UniformBufferObject{ubo});
|
encoder.writeBuffer(app.uniform_buffer, 0, &[_]UniformBufferObject{ubo});
|
||||||
|
|
@ -141,21 +149,23 @@ pub fn update(app: *App, core: *mach.Core) !void {
|
||||||
|
|
||||||
app.queue.submit(&[_]*gpu.CommandBuffer{command});
|
app.queue.submit(&[_]*gpu.CommandBuffer{command});
|
||||||
command.release();
|
command.release();
|
||||||
core.swap_chain.?.present();
|
app.core.swapChain().present();
|
||||||
back_buffer_view.release();
|
back_buffer_view.release();
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn recreatePipeline(core: *mach.Core, fragment_shader_code: [:0]const u8, bgl: ?**gpu.BindGroupLayout) *gpu.RenderPipeline {
|
fn recreatePipeline(core: *mach.Core, fragment_shader_code: [:0]const u8, bgl: ?**gpu.BindGroupLayout) *gpu.RenderPipeline {
|
||||||
const vs_module = core.device.createShaderModuleWGSL("vert.wgsl", @embedFile("vert.wgsl"));
|
const vs_module = core.device().createShaderModuleWGSL("vert.wgsl", @embedFile("vert.wgsl"));
|
||||||
defer vs_module.release();
|
defer vs_module.release();
|
||||||
|
|
||||||
// Check wether the fragment shader code compiled successfully, if not
|
// Check wether the fragment shader code compiled successfully, if not
|
||||||
// print the validation layer error and show a black screen
|
// print the validation layer error and show a black screen
|
||||||
core.device.pushErrorScope(.validation);
|
core.device().pushErrorScope(.validation);
|
||||||
var fs_module = core.device.createShaderModuleWGSL("fragment shader", fragment_shader_code);
|
var fs_module = core.device().createShaderModuleWGSL("fragment shader", fragment_shader_code);
|
||||||
var error_occurred: bool = false;
|
var error_occurred: bool = false;
|
||||||
// popErrorScope() returns always true, (unless maybe it fails to capture the error scope?)
|
// popErrorScope() returns always true, (unless maybe it fails to capture the error scope?)
|
||||||
_ = core.device.popErrorScope(&error_occurred, struct {
|
_ = core.device().popErrorScope(&error_occurred, struct {
|
||||||
inline fn callback(ctx: *bool, typ: gpu.ErrorType, message: [*:0]const u8) void {
|
inline fn callback(ctx: *bool, typ: gpu.ErrorType, message: [*:0]const u8) void {
|
||||||
if (typ != .no_error) {
|
if (typ != .no_error) {
|
||||||
std.debug.print("🔴🔴🔴🔴:\n{s}\n", .{message});
|
std.debug.print("🔴🔴🔴🔴:\n{s}\n", .{message});
|
||||||
|
|
@ -164,7 +174,7 @@ fn recreatePipeline(core: *mach.Core, fragment_shader_code: [:0]const u8, bgl: ?
|
||||||
}
|
}
|
||||||
}.callback);
|
}.callback);
|
||||||
if (error_occurred) {
|
if (error_occurred) {
|
||||||
fs_module = core.device.createShaderModuleWGSL(
|
fs_module = core.device().createShaderModuleWGSL(
|
||||||
"black_screen_frag.wgsl",
|
"black_screen_frag.wgsl",
|
||||||
@embedFile("black_screen_frag.wgsl"),
|
@embedFile("black_screen_frag.wgsl"),
|
||||||
);
|
);
|
||||||
|
|
@ -173,7 +183,7 @@ fn recreatePipeline(core: *mach.Core, fragment_shader_code: [:0]const u8, bgl: ?
|
||||||
|
|
||||||
const blend = gpu.BlendState{};
|
const blend = gpu.BlendState{};
|
||||||
const color_target = gpu.ColorTargetState{
|
const color_target = gpu.ColorTargetState{
|
||||||
.format = core.swap_chain_format,
|
.format = core.descriptor().format,
|
||||||
.blend = &blend,
|
.blend = &blend,
|
||||||
.write_mask = gpu.ColorWriteMaskFlags.all,
|
.write_mask = gpu.ColorWriteMaskFlags.all,
|
||||||
};
|
};
|
||||||
|
|
@ -185,7 +195,7 @@ fn recreatePipeline(core: *mach.Core, fragment_shader_code: [:0]const u8, bgl: ?
|
||||||
|
|
||||||
const bgle = gpu.BindGroupLayout.Entry.buffer(0, .{ .fragment = true }, .uniform, true, 0);
|
const bgle = gpu.BindGroupLayout.Entry.buffer(0, .{ .fragment = true }, .uniform, true, 0);
|
||||||
// bgl is needed outside, for the creation of the uniform_buffer in main
|
// bgl is needed outside, for the creation of the uniform_buffer in main
|
||||||
const bgl_tmp = core.device.createBindGroupLayout(&gpu.BindGroupLayout.Descriptor.init(.{
|
const bgl_tmp = core.device().createBindGroupLayout(&gpu.BindGroupLayout.Descriptor.init(.{
|
||||||
.entries = &.{bgle},
|
.entries = &.{bgle},
|
||||||
}));
|
}));
|
||||||
defer {
|
defer {
|
||||||
|
|
@ -198,7 +208,7 @@ fn recreatePipeline(core: *mach.Core, fragment_shader_code: [:0]const u8, bgl: ?
|
||||||
}
|
}
|
||||||
|
|
||||||
const bind_group_layouts = [_]*gpu.BindGroupLayout{bgl_tmp};
|
const bind_group_layouts = [_]*gpu.BindGroupLayout{bgl_tmp};
|
||||||
const pipeline_layout = core.device.createPipelineLayout(&gpu.PipelineLayout.Descriptor.init(.{
|
const pipeline_layout = core.device().createPipelineLayout(&gpu.PipelineLayout.Descriptor.init(.{
|
||||||
.bind_group_layouts = &bind_group_layouts,
|
.bind_group_layouts = &bind_group_layouts,
|
||||||
}));
|
}));
|
||||||
defer pipeline_layout.release();
|
defer pipeline_layout.release();
|
||||||
|
|
@ -214,10 +224,10 @@ fn recreatePipeline(core: *mach.Core, fragment_shader_code: [:0]const u8, bgl: ?
|
||||||
|
|
||||||
// Create the render pipeline. Even if the shader compilation succeeded, this could fail if the
|
// Create the render pipeline. Even if the shader compilation succeeded, this could fail if the
|
||||||
// shader is missing a `main` entrypoint.
|
// shader is missing a `main` entrypoint.
|
||||||
core.device.pushErrorScope(.validation);
|
core.device().pushErrorScope(.validation);
|
||||||
const pipeline = core.device.createRenderPipeline(&pipeline_descriptor);
|
const pipeline = core.device().createRenderPipeline(&pipeline_descriptor);
|
||||||
// popErrorScope() returns always true, (unless maybe it fails to capture the error scope?)
|
// popErrorScope() returns always true, (unless maybe it fails to capture the error scope?)
|
||||||
_ = core.device.popErrorScope(&error_occurred, struct {
|
_ = core.device().popErrorScope(&error_occurred, struct {
|
||||||
inline fn callback(ctx: *bool, typ: gpu.ErrorType, message: [*:0]const u8) void {
|
inline fn callback(ctx: *bool, typ: gpu.ErrorType, message: [*:0]const u8) void {
|
||||||
if (typ != .no_error) {
|
if (typ != .no_error) {
|
||||||
std.debug.print("🔴🔴🔴🔴:\n{s}\n", .{message});
|
std.debug.print("🔴🔴🔴🔴:\n{s}\n", .{message});
|
||||||
|
|
|
||||||
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 builtin = @import("builtin");
|
||||||
const glfw = @import("glfw");
|
const std = @import("std");
|
||||||
const gpu = @import("gpu");
|
const gpu = @import("gpu");
|
||||||
const platform = @import("platform.zig");
|
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.
|
pub fn init(allocator: std.mem.Allocator, options: Options) !Core {
|
||||||
///
|
return .{
|
||||||
/// For example, if you are animating a cube which should rotate 360 degrees every second,
|
.internal = try platform.Core.init(allocator, options),
|
||||||
/// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set runtime options for application, like title, window size etc.
|
pub fn deinit(core: *Core) void {
|
||||||
///
|
return core.internal.deinit();
|
||||||
/// 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 hasEvent(core: *Core) bool {
|
pub fn hasEvent(core: *Core) bool {
|
||||||
return core.internal.hasEvent();
|
return core.internal.hasEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pollEvent(core: *Core) ?structs.Event {
|
pub fn pollEvents(core: *Core) ?Event {
|
||||||
return core.internal.pollEvent();
|
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 std = @import("std");
|
||||||
const platform = @import("platform.zig");
|
const platform = @import("platform.zig");
|
||||||
|
|
||||||
const Timer = @This();
|
pub const Timer = @This();
|
||||||
|
|
||||||
backing_timer: platform.BackingTimerType = undefined,
|
internal: platform.Timer,
|
||||||
|
|
||||||
/// Initialize the timer.
|
/// Initialize the timer.
|
||||||
pub fn start() !Timer {
|
pub fn start() !Timer {
|
||||||
return 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.
|
/// Reads the timer value since start or the last reset in nanoseconds.
|
||||||
pub inline fn readPrecise(timer: *Timer) u64 {
|
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.
|
/// 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.
|
/// Resets the timer value to 0/now.
|
||||||
pub inline fn reset(timer: *Timer) void {
|
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.
|
/// Returns the current value of the timer in nanoseconds, then resets it.
|
||||||
pub inline fn lapPrecise(timer: *Timer) u64 {
|
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.
|
/// 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 Core = @import("Core.zig");
|
||||||
pub const gpu = @import("gpu");
|
pub const gpu = @import("gpu");
|
||||||
pub const ecs = @import("ecs");
|
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(
|
pub fn App(
|
||||||
comptime modules: anytype,
|
comptime modules: anytype,
|
||||||
comptime app_init: anytype, // fn (engine: *ecs.World(modules)) !void
|
comptime app_init: anytype, // fn (engine: *ecs.World(modules)) !void
|
||||||
|
|
@ -22,26 +26,32 @@ pub fn App(
|
||||||
return struct {
|
return struct {
|
||||||
engine: ecs.World(modules),
|
engine: ecs.World(modules),
|
||||||
|
|
||||||
pub fn init(app: *@This(), core: *Core) !void {
|
pub fn init(app: *@This()) !void {
|
||||||
app.* = .{
|
app.* = .{
|
||||||
.engine = try ecs.World(modules).init(core.allocator),
|
.engine = try ecs.World(modules).init(allocator),
|
||||||
};
|
};
|
||||||
app.*.engine.set(.mach, .core, core);
|
var core = try allocator.create(Core);
|
||||||
app.*.engine.set(.mach, .device, core.device);
|
core.* = try Core.init(allocator, .{});
|
||||||
|
app.engine.set(.mach, .core, core);
|
||||||
|
app.engine.set(.mach, .device, core.device());
|
||||||
try app_init(&app.engine);
|
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();
|
app.engine.deinit();
|
||||||
|
_ = gpa.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(app: *@This(), _: *Core) !void {
|
pub fn update(app: *@This()) !bool {
|
||||||
app.engine.tick();
|
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;
|
_ = app;
|
||||||
_ = core;
|
|
||||||
_ = width;
|
_ = width;
|
||||||
_ = height;
|
_ = height;
|
||||||
// TODO: send resize messages to ECS modules
|
// 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("entry.zig");
|
||||||
pub usingnamespace @import("enums.zig");
|
|
||||||
pub const Core = @import("Core.zig");
|
pub const Core = @import("Core.zig");
|
||||||
pub const Timer = @import("Timer.zig");
|
pub const Timer = @import("Timer.zig");
|
||||||
pub const ResourceManager = @import("resource/ResourceManager.zig");
|
|
||||||
|
|
||||||
pub const gpu = @import("gpu");
|
pub const gpu = @import("gpu");
|
||||||
pub const ecs = @import("ecs");
|
pub const ecs = @import("ecs");
|
||||||
pub const sysaudio = @import("sysaudio");
|
pub const sysaudio = @import("sysaudio");
|
||||||
pub const sysjs = @import("sysjs");
|
pub const sysjs = @import("sysjs");
|
||||||
pub const earcut = @import("earcut");
|
pub const earcut = @import("earcut");
|
||||||
pub const gfx = @import("gfx/util.zig");
|
pub const gfx = @import("gfx/util.zig");
|
||||||
|
pub const ResourceManager = @import("resource/ResourceManager.zig");
|
||||||
|
|
||||||
// Engine exports
|
// Engine exports
|
||||||
pub const App = @import("engine.zig").App;
|
pub const App = @import("engine.zig").App;
|
||||||
pub const module = @import("engine.zig").module;
|
pub const module = @import("engine.zig").module;
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const testing = std.testing;
|
|
||||||
|
|
||||||
test {
|
test {
|
||||||
// TODO: can't reference because they import "app"
|
std.testing.refAllDeclsRecursive(ResourceManager);
|
||||||
// testing.refAllDeclsRecursive(Core);
|
std.testing.refAllDeclsRecursive(gfx);
|
||||||
// testing.refAllDeclsRecursive(Timer);
|
|
||||||
testing.refAllDeclsRecursive(ResourceManager);
|
|
||||||
testing.refAllDeclsRecursive(gfx);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,42 +1,62 @@
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
const Platform = if (builtin.cpu.arch == .wasm32)
|
pub usingnamespace if (builtin.cpu.arch == .wasm32)
|
||||||
Interface(@import("platform/wasm.zig"))
|
@import("platform/wasm.zig")
|
||||||
else
|
else
|
||||||
Interface(@import("platform/native.zig"));
|
@import("platform/native.zig");
|
||||||
|
|
||||||
pub const Type = Platform.Platform;
|
// Verifies that a platform implementation exposes the expected function declarations.
|
||||||
pub const BackingTimerType = Platform.BackingTimer;
|
comptime {
|
||||||
|
assertHasDecl(@This(), "entry");
|
||||||
|
assertHasDecl(@This(), "Core");
|
||||||
|
assertHasDecl(@This(), "Timer");
|
||||||
|
|
||||||
/// Verifies that a Platform implementation exposes the expected function declarations.
|
// Core
|
||||||
fn Interface(comptime T: type) type {
|
assertHasDecl(@This().Core, "init");
|
||||||
assertHasDecl(T, "Platform");
|
assertHasDecl(@This().Core, "deinit");
|
||||||
assertHasDecl(T, "BackingTimer");
|
assertHasDecl(@This().Core, "hasEvent");
|
||||||
assertHasDecl(T.Platform, "init");
|
assertHasDecl(@This().Core, "pollEvents");
|
||||||
assertHasDecl(T.Platform, "deinit");
|
assertHasDecl(@This().Core, "framebufferSize");
|
||||||
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");
|
|
||||||
|
|
||||||
return T;
|
assertHasDecl(@This().Core, "setWaitTimeout");
|
||||||
}
|
assertHasDecl(@This().Core, "setTitle");
|
||||||
|
|
||||||
fn assertDecl(comptime T: anytype, comptime name: []const u8, comptime Decl: type) void {
|
assertHasDecl(@This().Core, "setDisplayMode");
|
||||||
assertHasDecl(T, name);
|
assertHasDecl(@This().Core, "displayMode");
|
||||||
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, "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 {
|
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 std = @import("std");
|
||||||
const Core = @import("../Core.zig");
|
|
||||||
const gpu = @import("gpu");
|
const gpu = @import("gpu");
|
||||||
const ecs = @import("ecs");
|
const ecs = @import("ecs");
|
||||||
const glfw = @import("glfw");
|
const glfw = @import("glfw");
|
||||||
|
const Core = @import("../Core.zig");
|
||||||
const native = @import("native.zig");
|
const native = @import("native.zig");
|
||||||
|
|
||||||
pub const App = @This();
|
pub const App = @This();
|
||||||
|
|
@ -11,54 +11,32 @@ pub const GPUInterface = gpu.dawn.Interface;
|
||||||
|
|
||||||
const _ = gpu.Export(GPUInterface);
|
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:
|
// Current Limitations:
|
||||||
// 1. Currently, ecs seems to be using some weird compile-time type trickery, so I'm not exactly sure how
|
// 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
|
// `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)
|
// 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
|
// 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(.{}){};
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
const allocator = gpa.allocator();
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
// Returns a pointer to a newly allocated Core
|
// Returns a pointer to a newly allocated Core
|
||||||
// Will return a null pointer if an error occurred while initializing 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();
|
gpu.Impl.init();
|
||||||
const core = native.coreInit(allocator) catch {
|
const core = native.Core.init(allocator, .{}) catch {
|
||||||
return @intToPtr(?*Core, 0);
|
return @intToPtr(?*native.Core, 0);
|
||||||
};
|
};
|
||||||
return core;
|
return core;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub export fn mach_core_deinit(core: *Core) void {
|
pub export fn mach_core_deinit(core: *native.Core) void {
|
||||||
native.coreDeinit(core, allocator);
|
native.Core.deinit(core);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub export fn mach_core_update(core: *Core, resize: ?native.CoreResizeCallback) MachStatus {
|
// pub export fn mach_core_poll_events(core: *native.Core) Core.Event {
|
||||||
native.coreUpdate(core, resize) catch {
|
// return native.Core.pollEvents(core);
|
||||||
return MachStatus.Error;
|
// }
|
||||||
};
|
|
||||||
return MachStatus.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
const MachStatus = enum(c_int) {
|
const MachStatus = enum(c_int) {
|
||||||
Success = 0x00000000,
|
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 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");
|
pub const entry = @import("native/entry.zig");
|
||||||
comptime {
|
pub const Core = @import("native/Core.zig");
|
||||||
common.checkApplication(app_pkg);
|
pub const Timer = std.time.Timer;
|
||||||
}
|
|
||||||
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() });
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
||||||
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(
|
pub inline fn requestAdapterCallback(
|
||||||
context: *?RequestAdapterResponse,
|
context: *RequestAdapterResponse,
|
||||||
status: gpu.RequestAdapterStatus,
|
status: gpu.RequestAdapterStatus,
|
||||||
adapter: *gpu.Adapter,
|
adapter: *gpu.Adapter,
|
||||||
message: ?[*:0]const u8,
|
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 args_meta = @typeInfo(@TypeOf(args)).Struct.fields;
|
||||||
|
|
||||||
const FnType = switch (args_meta.len) {
|
const FnType = switch (args_meta.len) {
|
||||||
0 => *const fn (@TypeOf(obj), objc.SEL) 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,
|
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,
|
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,
|
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,
|
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"),
|
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");
|
pub const Core = @import("wasm/Core.zig");
|
||||||
const app_pkg = @import("app");
|
pub const Timer = @import("wasm/Timer.zig");
|
||||||
const Core = @import("../Core.zig");
|
pub const entry = @import("wasm/entry.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;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
|
|
@ -31,21 +31,21 @@
|
||||||
let frame = true;
|
let frame = true;
|
||||||
let last_update_time = performance.now();
|
let last_update_time = performance.now();
|
||||||
let update = function () {
|
let update = function () {
|
||||||
if (!frame) return;
|
if (!frame) {
|
||||||
if (mach.machHasEvent() ||
|
instance.exports.wasmDeinit();
|
||||||
(last_update_time + (mach.wait_event_timeout * 1000)) <= performance.now()) {
|
return;
|
||||||
instance.exports.wasmUpdate();
|
}
|
||||||
|
if (mach.machHasEvent() ||
|
||||||
|
last_update_time + mach.wait_timeout * 1000 <= performance.now()) {
|
||||||
|
if (instance.exports.wasmUpdate()) {
|
||||||
|
instance.exports.wasmDeinit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
last_update_time = performance.now();
|
last_update_time = performance.now();
|
||||||
}
|
}
|
||||||
window.requestAnimationFrame(update);
|
window.requestAnimationFrame(update);
|
||||||
};
|
};
|
||||||
|
|
||||||
window.requestAnimationFrame(update);
|
window.requestAnimationFrame(update);
|
||||||
|
|
||||||
window.addEventListener("mach-close", () => {
|
|
||||||
instance.exports.wasmDeinit();
|
|
||||||
frame = false;
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
.catch(err => console.error(err));
|
.catch(err => console.error(err));
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue