846 lines
24 KiB
Zig
846 lines
24 KiB
Zig
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
const build_options = @import("build-options");
|
|
|
|
const mach = @import("main.zig");
|
|
const gpu = mach.gpu;
|
|
const log = std.log.scoped(.mach);
|
|
|
|
const Core = @This();
|
|
|
|
// Whether or not you can drive the main loop in a non-blocking fashion, or if the underlying
|
|
// platform must take control and drive the main loop itself.
|
|
pub const supports_non_blocking = switch (build_options.core_platform) {
|
|
// Platforms that support non-blocking mode.
|
|
.linux => true,
|
|
.windows => true,
|
|
.null => true,
|
|
|
|
// Platforms which take control of the main loop.
|
|
.wasm => false,
|
|
.darwin => false,
|
|
};
|
|
|
|
const EventQueue = std.fifo.LinearFifo(Event, .Dynamic);
|
|
|
|
/// Set this to true if you intend to drive the main loop yourself.
|
|
///
|
|
/// A panic will occur if `supports_non_blocking == false` for the platform.
|
|
pub var non_blocking = false;
|
|
|
|
pub const mach_module = .mach_core;
|
|
|
|
pub const mach_systems = .{ .main, .init, .tick, .presentFrame, .deinit };
|
|
|
|
// Set track_fields to true so that when these field values change, we know about it
|
|
// and can update the platform windows.
|
|
windows: mach.Objects(
|
|
.{ .track_fields = true },
|
|
struct {
|
|
/// Window title string
|
|
// TODO: document how to set this using a format string
|
|
// TODO: allocation/free strategy
|
|
title: [:0]const u8 = "Mach Window",
|
|
|
|
/// Texture format of the framebuffer (read-only)
|
|
framebuffer_format: gpu.Texture.Format = .bgra8_unorm,
|
|
|
|
/// Width of the framebuffer in texels (read-only)
|
|
/// Will be updated to reflect the actual framebuffer dimensions after window creation.
|
|
framebuffer_width: u32 = 1920 / 2,
|
|
|
|
/// Height of the framebuffer in texels (read-only)
|
|
/// Will be updated to reflect the actual framebuffer dimensions after window creation.
|
|
framebuffer_height: u32 = 1080 / 2,
|
|
|
|
/// Vertical sync mode, prevents screen tearing.
|
|
vsync_mode: VSyncMode = .none,
|
|
|
|
/// Window display mode: fullscreen, windowed or borderless fullscreen
|
|
display_mode: DisplayMode = .windowed,
|
|
|
|
/// Cursor
|
|
cursor_mode: CursorMode = .normal,
|
|
cursor_shape: CursorShape = .arrow,
|
|
|
|
/// Outer border
|
|
border: bool = true,
|
|
|
|
/// Width of the window in virtual pixels
|
|
width: u32 = 1920 / 2,
|
|
|
|
/// Height of the window in virtual pixels
|
|
height: u32 = 1080 / 2,
|
|
|
|
/// Target frames per second
|
|
refresh_rate: u32 = 0,
|
|
|
|
/// Titlebar/window decorations
|
|
decorated: bool = true,
|
|
|
|
/// Color of the window decorations, i.e. titlebar
|
|
/// if null, decoration is the system-determined color
|
|
decoration_color: ?gpu.Color = null,
|
|
|
|
/// Whether the window should be completely transparent
|
|
/// or not. On macOS, to achieve a fully transparent window
|
|
/// decoration_color must also be set fully transparent.
|
|
transparent: bool = false,
|
|
|
|
// GPU
|
|
// When `native` is not null, the rest of the fields have been
|
|
// initialized.
|
|
device: *gpu.Device = undefined,
|
|
instance: *gpu.Instance = undefined,
|
|
adapter: *gpu.Adapter = undefined,
|
|
queue: *gpu.Queue = undefined,
|
|
swap_chain: *gpu.SwapChain = undefined,
|
|
swap_chain_descriptor: gpu.SwapChain.Descriptor = undefined,
|
|
surface: *gpu.Surface = undefined,
|
|
surface_descriptor: gpu.Surface.Descriptor = undefined,
|
|
|
|
// After window initialization, (when device is not null)
|
|
// changing these will have no effect
|
|
power_preference: gpu.PowerPreference = .undefined,
|
|
required_features: ?[]const gpu.FeatureName = null,
|
|
required_limits: ?gpu.Limits = null,
|
|
swap_chain_usage: gpu.Texture.UsageFlags = .{
|
|
.render_attachment = true,
|
|
},
|
|
|
|
/// Container for native platform-specific information
|
|
native: ?Platform.Native = null,
|
|
},
|
|
),
|
|
|
|
/// Callback system invoked per tick (e.g. per-frame)
|
|
on_tick: ?mach.FunctionID = null,
|
|
|
|
/// Callback system invoked when application is exiting
|
|
on_exit: ?mach.FunctionID = null,
|
|
|
|
/// Current state of the application
|
|
state: enum {
|
|
running,
|
|
exiting,
|
|
deinitializing,
|
|
exited,
|
|
} = .running,
|
|
|
|
frame: mach.time.Frequency,
|
|
input: mach.time.Frequency,
|
|
|
|
// Internal module state
|
|
allocator: std.mem.Allocator,
|
|
events: EventQueue,
|
|
input_state: InputState,
|
|
oom: std.Thread.ResetEvent = .{},
|
|
|
|
pub fn init(core: *Core) !void {
|
|
const allocator = std.heap.c_allocator;
|
|
|
|
// TODO: fix all leaks and use options.allocator
|
|
try mach.sysgpu.Impl.init(allocator, .{});
|
|
|
|
var events = EventQueue.init(allocator);
|
|
try events.ensureTotalCapacity(8192);
|
|
|
|
core.* = .{
|
|
// Note: since core.windows is initialized for us already, we just copy the pointer.
|
|
.windows = core.windows,
|
|
|
|
.allocator = allocator,
|
|
.events = events,
|
|
.input_state = .{},
|
|
|
|
.input = .{ .target = 0 },
|
|
.frame = .{ .target = 1 },
|
|
};
|
|
|
|
try core.frame.start();
|
|
try core.input.start();
|
|
}
|
|
|
|
pub fn initWindow(core: *Core, window_id: mach.ObjectID) !void {
|
|
var core_window = core.windows.getValue(window_id);
|
|
defer core.windows.setValueRaw(window_id, core_window);
|
|
|
|
core_window.instance = gpu.createInstance(null) orelse {
|
|
log.err("failed to create GPU instance", .{});
|
|
std.process.exit(1);
|
|
};
|
|
core_window.surface = core_window.instance.createSurface(&core_window.surface_descriptor);
|
|
|
|
var response: RequestAdapterResponse = undefined;
|
|
core_window.instance.requestAdapter(&gpu.RequestAdapterOptions{
|
|
.compatible_surface = core_window.surface,
|
|
.power_preference = core_window.power_preference,
|
|
.force_fallback_adapter = .false,
|
|
}, &response, requestAdapterCallback);
|
|
if (response.status != .success) {
|
|
log.err("failed to create GPU adapter: {?s}", .{response.message});
|
|
log.info("-> maybe try MACH_GPU_BACKEND=opengl ?", .{});
|
|
std.process.exit(1);
|
|
}
|
|
|
|
// Print which adapter we are going to use.
|
|
var props = std.mem.zeroes(gpu.Adapter.Properties);
|
|
response.adapter.?.getProperties(&props);
|
|
if (props.backend_type == .null) {
|
|
log.err("no backend found for {s} adapter", .{props.adapter_type.name()});
|
|
std.process.exit(1);
|
|
}
|
|
log.info("found {s} backend on {s} adapter: {s}, {s}\n", .{
|
|
props.backend_type.name(),
|
|
props.adapter_type.name(),
|
|
props.name,
|
|
props.driver_description,
|
|
});
|
|
|
|
core_window.adapter = response.adapter.?;
|
|
|
|
// Create a device with default limits/features.
|
|
core_window.device = response.adapter.?.createDevice(&.{
|
|
.required_features_count = if (core_window.required_features) |v| @as(u32, @intCast(v.len)) else 0,
|
|
.required_features = if (core_window.required_features) |v| @as(?[*]const gpu.FeatureName, v.ptr) else null,
|
|
.required_limits = if (core_window.required_limits) |limits| @as(?*const gpu.RequiredLimits, &gpu.RequiredLimits{
|
|
.limits = limits,
|
|
}) else null,
|
|
.device_lost_callback = &deviceLostCallback,
|
|
.device_lost_userdata = null,
|
|
}) orelse {
|
|
log.err("failed to create GPU device\n", .{});
|
|
std.process.exit(1);
|
|
};
|
|
core_window.device.setUncapturedErrorCallback({}, printUnhandledErrorCallback);
|
|
core_window.queue = core_window.device.getQueue();
|
|
|
|
core_window.swap_chain_descriptor = gpu.SwapChain.Descriptor{
|
|
.label = "main swap chain",
|
|
.usage = core_window.swap_chain_usage,
|
|
.format = .bgra8_unorm,
|
|
.width = core_window.framebuffer_width,
|
|
.height = core_window.framebuffer_height,
|
|
.present_mode = switch (core_window.vsync_mode) {
|
|
.none => .immediate,
|
|
.double => .fifo,
|
|
.triple => .mailbox,
|
|
},
|
|
};
|
|
core_window.swap_chain = core_window.device.createSwapChain(core_window.surface, &core_window.swap_chain_descriptor);
|
|
core.pushEvent(.{ .window_open = .{ .window_id = window_id } });
|
|
}
|
|
|
|
pub fn tick(core: *Core, core_mod: mach.Mod(Core)) !void {
|
|
// TODO(core)(slimsag): consider execution order of mach.Core (e.g. creating a new window
|
|
// during application execution, rendering to multiple windows, etc.) and how
|
|
// that relates to Platform.tick being responsible for both handling window updates
|
|
// (like title/size changes) and window creation, plus multi-threaded rendering.
|
|
try Platform.tick(core);
|
|
|
|
core_mod.run(core.on_tick.?);
|
|
core_mod.call(.presentFrame);
|
|
}
|
|
|
|
pub fn presentFrame(core: *Core, core_mod: mach.Mod(Core)) !void {
|
|
var windows = core.windows.slice();
|
|
while (windows.next()) |window_id| {
|
|
var core_window = core.windows.getValue(window_id);
|
|
defer core.windows.setValueRaw(window_id, core_window);
|
|
|
|
mach.sysgpu.Impl.deviceTick(core_window.device);
|
|
|
|
core_window.swap_chain.present();
|
|
}
|
|
|
|
// Record to frame rate frequency monitor that a frame was finished.
|
|
core.frame.tick();
|
|
|
|
switch (core.state) {
|
|
.running => {},
|
|
.exiting => {
|
|
core.state = .deinitializing;
|
|
core_mod.run(core.on_exit.?);
|
|
core_mod.call(.deinit);
|
|
},
|
|
.deinitializing => {},
|
|
.exited => @panic("application not running"),
|
|
}
|
|
}
|
|
|
|
pub fn main(core: *Core, core_mod: mach.Mod(Core)) !void {
|
|
if (core.on_tick == null) @panic("core.on_tick callback must be set");
|
|
if (core.on_exit == null) @panic("core.on_exit callback must be set");
|
|
|
|
try Platform.tick(core);
|
|
core_mod.run(core.on_tick.?);
|
|
core_mod.call(.presentFrame);
|
|
|
|
// If the user doesn't want mach.Core to take control of the main loop, we bail out - the next
|
|
// app tick is already scheduled to run in the future and they'll .present_frame to return
|
|
// control to us later.
|
|
if (non_blocking) {
|
|
if (!supports_non_blocking) std.debug.panic(
|
|
"mach.Core: platform {s} does not support non_blocking=true mode.",
|
|
.{@tagName(build_options.core_platform)},
|
|
);
|
|
return;
|
|
}
|
|
|
|
// The user wants mach.Core to take control of the main loop.
|
|
if (supports_non_blocking) {
|
|
while (core.state != .exited) {
|
|
try Platform.tick(core);
|
|
core_mod.run(core.on_tick.?);
|
|
core_mod.call(.presentFrame);
|
|
}
|
|
|
|
// Don't return, because Platform.run wouldn't either (marked noreturn due to underlying
|
|
// platform APIs never returning.)
|
|
std.process.exit(0);
|
|
} else {
|
|
// Platform drives the main loop.
|
|
Platform.run(platform_update_callback, .{ core, core_mod });
|
|
|
|
// Platform.run should be marked noreturn, so this shouldn't ever run. But just in case we
|
|
// accidentally introduce a different Platform.run in the future, we put an exit here for
|
|
// good measure.
|
|
std.process.exit(0);
|
|
}
|
|
}
|
|
|
|
fn platform_update_callback(core: *Core, core_mod: mach.Mod(Core)) !bool {
|
|
// TODO(core)(slimsag): consider execution order of mach.Core (e.g. creating a new window
|
|
// during application execution, rendering to multiple windows, etc.) and how
|
|
// that relates to Platform.tick being responsible for both handling window updates
|
|
// (like title/size changes) and window creation, plus multi-threaded rendering.
|
|
try Platform.tick(core);
|
|
|
|
core_mod.run(core.on_tick.?);
|
|
core_mod.call(.presentFrame);
|
|
|
|
return core.state != .exited;
|
|
}
|
|
|
|
pub fn exit(core: *Core) void {
|
|
core.state = .exiting;
|
|
}
|
|
|
|
pub fn deinit(core: *Core) !void {
|
|
core.state = .exited;
|
|
|
|
var windows = core.windows.slice();
|
|
while (windows.next()) |window_id| {
|
|
var core_window = core.windows.getValue(window_id);
|
|
core_window.swap_chain.release();
|
|
core_window.queue.release();
|
|
core_window.device.release();
|
|
core_window.surface.release();
|
|
core_window.adapter.release();
|
|
core_window.instance.release();
|
|
}
|
|
|
|
core.events.deinit();
|
|
}
|
|
|
|
/// Returns the next event until there are no more available. You should check for events during
|
|
/// every on_tick()
|
|
pub inline fn nextEvent(core: *@This()) ?Event {
|
|
return core.events.readItem();
|
|
}
|
|
|
|
/// Push an event onto the event queue, or set OOM if no space is available.
|
|
///
|
|
/// Updates the input_state tracker.
|
|
pub inline fn pushEvent(core: *@This(), event: Event) void {
|
|
// Write event
|
|
core.events.writeItem(event) catch {
|
|
core.oom.set();
|
|
return;
|
|
};
|
|
|
|
// Update input state
|
|
switch (event) {
|
|
.key_press => |ev| core.input_state.keys.setValue(@intFromEnum(ev.key), true),
|
|
.key_release => |ev| core.input_state.keys.setValue(@intFromEnum(ev.key), false),
|
|
.mouse_press => |ev| core.input_state.mouse_buttons.setValue(@intFromEnum(ev.button), true),
|
|
.mouse_release => |ev| core.input_state.mouse_buttons.setValue(@intFromEnum(ev.button), false),
|
|
.mouse_motion => |ev| core.input_state.mouse_position = ev.pos,
|
|
.focus_lost => {
|
|
// Clear input state that may be 'stuck' when focus is regained.
|
|
core.input_state.keys = InputState.KeyBitSet.initEmpty();
|
|
core.input_state.mouse_buttons = InputState.MouseButtonSet.initEmpty();
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
/// Reports whether mach.Core ran out of memory, indicating events may have been dropped.
|
|
///
|
|
/// Once called, the OOM flag is reset and mach.Core will continue operating normally.
|
|
pub fn outOfMemory(core: *@This()) bool {
|
|
if (!core.oom.isSet()) return false;
|
|
core.oom.reset();
|
|
return true;
|
|
}
|
|
|
|
pub fn keyPressed(core: *@This(), key: Key) bool {
|
|
return core.input_state.isKeyPressed(key);
|
|
}
|
|
|
|
pub fn keyReleased(core: *@This(), key: Key) bool {
|
|
return core.input_state.isKeyReleased(key);
|
|
}
|
|
|
|
pub fn mousePressed(core: *@This(), button: MouseButton) bool {
|
|
return core.input_state.isMouseButtonPressed(button);
|
|
}
|
|
|
|
pub fn mouseReleased(core: *@This(), button: MouseButton) bool {
|
|
return core.input_state.isMouseButtonReleased(button);
|
|
}
|
|
|
|
pub fn mousePosition(core: *@This()) Position {
|
|
return core.input_state.mouse_position;
|
|
}
|
|
|
|
inline fn requestAdapterCallback(
|
|
context: *RequestAdapterResponse,
|
|
status: gpu.RequestAdapterStatus,
|
|
adapter: ?*gpu.Adapter,
|
|
message: ?[*:0]const u8,
|
|
) void {
|
|
context.* = RequestAdapterResponse{
|
|
.status = status,
|
|
.adapter = adapter,
|
|
.message = message,
|
|
};
|
|
}
|
|
|
|
// TODO(important): expose device loss to users, this can happen especially in the web and on mobile
|
|
// devices. Users will need to re-upload all assets to the GPU in this event.
|
|
fn deviceLostCallback(reason: gpu.Device.LostReason, msg: [*:0]const u8, userdata: ?*anyopaque) callconv(.C) void {
|
|
_ = userdata;
|
|
_ = reason;
|
|
log.err("mach: device lost: {s}", .{msg});
|
|
@panic("mach: device lost");
|
|
}
|
|
|
|
pub inline fn printUnhandledErrorCallback(_: void, ty: gpu.ErrorType, message: [*:0]const u8) void {
|
|
switch (ty) {
|
|
.validation => std.log.err("gpu: validation error: {s}\n", .{message}),
|
|
.out_of_memory => std.log.err("gpu: out of memory: {s}\n", .{message}),
|
|
.device_lost => std.log.err("gpu: device lost: {s}\n", .{message}),
|
|
.unknown => std.log.err("gpu: unknown error: {s}\n", .{message}),
|
|
else => unreachable,
|
|
}
|
|
std.process.exit(1);
|
|
}
|
|
|
|
pub fn detectBackendType(allocator: std.mem.Allocator) !gpu.BackendType {
|
|
const backend = std.process.getEnvVarOwned(
|
|
allocator,
|
|
"MACH_GPU_BACKEND",
|
|
) catch |err| switch (err) {
|
|
error.EnvironmentVariableNotFound => {
|
|
if (builtin.target.isDarwin()) return .metal;
|
|
if (builtin.target.os.tag == .windows) return .d3d12;
|
|
return .vulkan;
|
|
},
|
|
else => return err,
|
|
};
|
|
defer allocator.free(backend);
|
|
|
|
if (std.ascii.eqlIgnoreCase(backend, "null")) return .null;
|
|
if (std.ascii.eqlIgnoreCase(backend, "d3d11")) return .d3d11;
|
|
if (std.ascii.eqlIgnoreCase(backend, "d3d12")) return .d3d12;
|
|
if (std.ascii.eqlIgnoreCase(backend, "metal")) return .metal;
|
|
if (std.ascii.eqlIgnoreCase(backend, "vulkan")) return .vulkan;
|
|
if (std.ascii.eqlIgnoreCase(backend, "opengl")) return .opengl;
|
|
if (std.ascii.eqlIgnoreCase(backend, "opengles")) return .opengles;
|
|
|
|
@panic("unknown MACH_GPU_BACKEND type");
|
|
}
|
|
|
|
const Platform = switch (build_options.core_platform) {
|
|
.wasm => @panic("TODO: support mach.Core WASM platform"),
|
|
.windows => @import("core/Windows.zig"),
|
|
.linux => @import("core/Linux.zig"),
|
|
.darwin => @import("core/Darwin.zig"),
|
|
.null => @import("core/Null.zig"),
|
|
};
|
|
|
|
pub const InputState = struct {
|
|
const KeyBitSet = std.StaticBitSet(@as(u8, @intFromEnum(Key.max)) + 1);
|
|
const MouseButtonSet = std.StaticBitSet(@as(u4, @intFromEnum(MouseButton.max)) + 1);
|
|
|
|
keys: KeyBitSet = KeyBitSet.initEmpty(),
|
|
mouse_buttons: MouseButtonSet = MouseButtonSet.initEmpty(),
|
|
mouse_position: Position = .{ .x = 0, .y = 0 },
|
|
|
|
pub inline fn isKeyPressed(input: InputState, key: Key) bool {
|
|
return input.keys.isSet(@intFromEnum(key));
|
|
}
|
|
|
|
pub inline fn isKeyReleased(input: InputState, key: Key) bool {
|
|
return !input.isKeyPressed(key);
|
|
}
|
|
|
|
pub inline fn isMouseButtonPressed(input: InputState, button: MouseButton) bool {
|
|
return input.mouse_buttons.isSet(@intFromEnum(button));
|
|
}
|
|
|
|
pub inline fn isMouseButtonReleased(input: InputState, button: MouseButton) bool {
|
|
return !input.isMouseButtonPressed(button);
|
|
}
|
|
};
|
|
|
|
pub const WindowColor = union(enum) {
|
|
system: void, // Default window colors
|
|
transparent: struct {
|
|
color: gpu.Color,
|
|
// If true, and the OS supports it, the titlebar will also be set to color
|
|
titlebar: bool = false,
|
|
},
|
|
solid: struct {
|
|
color: gpu.Color,
|
|
// If titlebar is true, and the OS supports it, the titlebar will also be set to color
|
|
titlebar: bool = false,
|
|
},
|
|
};
|
|
|
|
pub const Event = union(enum) {
|
|
key_press: KeyEvent,
|
|
key_repeat: KeyEvent,
|
|
key_release: KeyEvent,
|
|
char_input: struct {
|
|
window_id: mach.ObjectID,
|
|
codepoint: u21,
|
|
},
|
|
mouse_motion: struct {
|
|
window_id: mach.ObjectID,
|
|
pos: Position,
|
|
},
|
|
mouse_press: MouseButtonEvent,
|
|
mouse_release: MouseButtonEvent,
|
|
mouse_scroll: struct {
|
|
window_id: mach.ObjectID,
|
|
xoffset: f32,
|
|
yoffset: f32,
|
|
},
|
|
window_resize: ResizeEvent,
|
|
window_open: struct {
|
|
window_id: mach.ObjectID,
|
|
},
|
|
zoom_gesture: ZoomGestureEvent,
|
|
focus_gained: struct {
|
|
window_id: mach.ObjectID,
|
|
},
|
|
focus_lost: struct {
|
|
window_id: mach.ObjectID,
|
|
},
|
|
close: struct {
|
|
window_id: mach.ObjectID,
|
|
},
|
|
};
|
|
|
|
pub const KeyEvent = struct {
|
|
window_id: mach.ObjectID,
|
|
key: Key,
|
|
mods: KeyMods,
|
|
};
|
|
|
|
pub const MouseButtonEvent = struct {
|
|
window_id: mach.ObjectID,
|
|
button: MouseButton,
|
|
pos: Position,
|
|
mods: KeyMods,
|
|
};
|
|
|
|
pub const ResizeEvent = struct {
|
|
window_id: mach.ObjectID,
|
|
size: Size,
|
|
};
|
|
|
|
pub const ZoomGestureEvent = struct {
|
|
window_id: mach.ObjectID,
|
|
phase: GesturePhase,
|
|
zoom: f32,
|
|
};
|
|
|
|
pub const GesturePhase = enum {
|
|
began,
|
|
ended,
|
|
};
|
|
|
|
pub const MouseButton = enum {
|
|
left,
|
|
right,
|
|
middle,
|
|
four,
|
|
five,
|
|
six,
|
|
seven,
|
|
eight,
|
|
|
|
pub const max = MouseButton.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_comma,
|
|
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,
|
|
|
|
iso_backslash,
|
|
international1,
|
|
international2,
|
|
international3,
|
|
international4,
|
|
international5,
|
|
lang1,
|
|
lang2,
|
|
|
|
unknown,
|
|
|
|
pub const max = Key.unknown;
|
|
};
|
|
|
|
pub const KeyMods = packed struct(u8) {
|
|
shift: bool,
|
|
control: bool,
|
|
alt: bool,
|
|
super: bool,
|
|
caps_lock: bool,
|
|
num_lock: bool,
|
|
_padding: u2 = 0,
|
|
};
|
|
|
|
pub const DisplayMode = enum {
|
|
/// Windowed mode.
|
|
windowed,
|
|
|
|
/// Fullscreen mode, using this option may change the display's video mode.
|
|
fullscreen,
|
|
|
|
/// Borderless fullscreen window.
|
|
///
|
|
/// Beware that true .fullscreen is also a hint to the OS that is used in various contexts, e.g.
|
|
///
|
|
/// * macOS: Moving to a virtual space dedicated to fullscreen windows as the user expects
|
|
/// * macOS: .borderless windows cannot prevent the system menu bar from being displayed
|
|
///
|
|
/// Always allow users to choose their preferred display mode.
|
|
borderless,
|
|
};
|
|
|
|
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 Size = struct {
|
|
width: u32,
|
|
height: u32,
|
|
|
|
pub inline fn eql(a: Size, b: Size) bool {
|
|
return a.width == b.width and a.height == b.height;
|
|
}
|
|
};
|
|
|
|
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,
|
|
};
|
|
|
|
pub const Position = struct {
|
|
x: f64,
|
|
y: f64,
|
|
};
|
|
|
|
const RequestAdapterResponse = struct {
|
|
status: gpu.RequestAdapterStatus,
|
|
adapter: ?*gpu.Adapter,
|
|
message: ?[*:0]const u8,
|
|
};
|
|
|
|
fn assertHasDecl(comptime T: anytype, comptime decl_name: []const u8) void {
|
|
if (!@hasDecl(T, decl_name)) @compileError(@typeName(T) ++ " missing declaration: " ++ decl_name);
|
|
}
|
|
|
|
fn assertHasField(comptime T: anytype, comptime field_name: []const u8) void {
|
|
if (!@hasField(T, field_name)) @compileError(@typeName(T) ++ " missing field: " ++ field_name);
|
|
}
|
|
|
|
test {
|
|
_ = Platform;
|
|
@import("std").testing.refAllDeclsRecursive(VSyncMode);
|
|
@import("std").testing.refAllDeclsRecursive(Size);
|
|
@import("std").testing.refAllDeclsRecursive(Position);
|
|
@import("std").testing.refAllDeclsRecursive(Event);
|
|
@import("std").testing.refAllDeclsRecursive(KeyEvent);
|
|
@import("std").testing.refAllDeclsRecursive(MouseButtonEvent);
|
|
@import("std").testing.refAllDeclsRecursive(MouseButton);
|
|
@import("std").testing.refAllDeclsRecursive(Key);
|
|
@import("std").testing.refAllDeclsRecursive(KeyMods);
|
|
@import("std").testing.refAllDeclsRecursive(DisplayMode);
|
|
@import("std").testing.refAllDeclsRecursive(CursorMode);
|
|
@import("std").testing.refAllDeclsRecursive(CursorShape);
|
|
}
|