obj: Move Platform and InitOptions fields into core.windows (#1309)

* obj: Make field tracking use a single bitset

* obj: module: fix comment

* obj: Move `Platform` state and `InitOptions` fields into `core.windows`, initial push, only triangle example working on macos currently

* obj: `get` and `getValue` (renamed `getAll`) now do not return optionals, comment revisions, `device` is no longer optional, `native` is optional

* core: Lots of cleanup of unnecessary comments

* core: `Event`s now all contain `window_id`, darwin/windows: event functions now send window id

* core: comments, examples: fix `core-custom-entrypoint`
This commit is contained in:
Colton Franklin 2024-11-30 16:13:14 -06:00 committed by GitHub
parent b4e2da1b69
commit 1fe47b2b19
Failed to generate hash of commit
7 changed files with 651 additions and 610 deletions

View file

@ -30,7 +30,7 @@ pub var non_blocking = false;
pub const mach_module = .mach_core;
pub const mach_systems = .{ .main, .init, .tick, .presentFrame, .processWindowUpdates, .deinit };
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.
@ -40,25 +40,63 @@ windows: mach.Objects(
/// Window title string
// TODO: document how to set this using a format string
// TODO: allocation/free strategy
title: []const u8,
title: []const u8 = "Mach Window",
/// Texture format of the framebuffer (read-only)
framebuffer_format: gpu.Texture.Format,
framebuffer_format: gpu.Texture.Format = .bgra8_unorm,
/// Width of the framebuffer in texels (read-only)
framebuffer_width: u32,
/// 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)
framebuffer_height: u32,
/// 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,
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 (read-only)
width: u32,
width: u32 = 1920 / 2,
/// Height of the window in virtual pixels (read-only)
height: u32,
height: u32 = 1080 / 2,
/// Whether the window is fullscreen (read-only)
fullscreen: bool,
/// Target frames per second
refresh_rate: u32 = 0,
// GPU
// When device 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,
swap_chain_update: std.Thread.ResetEvent = .{},
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,
},
native: ?Platform.Native = null,
},
),
@ -79,60 +117,26 @@ state: enum {
exited,
} = .running,
// TODO: handle window titles better
title: [256:0]u8 = undefined,
frame: mach.time.Frequency,
input: mach.time.Frequency,
swap_chain_update: std.Thread.ResetEvent = .{},
// GPU
instance: *gpu.Instance,
adapter: *gpu.Adapter,
device: *gpu.Device,
queue: *gpu.Queue,
surface: *gpu.Surface,
swap_chain: *gpu.SwapChain,
descriptor: gpu.SwapChain.Descriptor,
// Internal module state
allocator: std.mem.Allocator,
platform: Platform,
events: EventQueue,
input_state: InputState,
oom: std.Thread.ResetEvent = .{},
pub fn init(core: *Core) !void {
// TODO: this needs to be removed.
const options: InitOptions = .{
.allocator = std.heap.c_allocator,
};
const allocator = options.allocator;
const allocator = std.heap.c_allocator;
// TODO: fix all leaks and use options.allocator
try mach.sysgpu.Impl.init(allocator, .{});
const main_window = try core.windows.new(.{
.title = options.title, // TODO
.framebuffer_format = undefined, // TODO: null?
.framebuffer_width = undefined, // TODO: null?
.framebuffer_height = undefined, // TODO: null?
.width = 1920 / 2,
.height = 1080 / 2,
.fullscreen = false,
});
// Copy window title into owned buffer.
var title: [256:0]u8 = undefined;
if (options.title.len < title.len) {
@memcpy(title[0..options.title.len], options.title);
title[options.title.len] = 0;
}
const main_window = try core.windows.new(.{});
var events = EventQueue.init(allocator);
try events.ensureTotalCapacity(8192);
// TODO: remove undefined initialization (disgusting!)
const platform: Platform = undefined;
core.* = .{
// Note: since core.windows is initialized for us already, we just copy the pointer.
.windows = core.windows,
@ -142,33 +146,32 @@ pub fn init(core: *Core) !void {
.events = events,
.input_state = .{},
.platform = platform,
// TODO: these should not be state, they should be components.
.title = title,
.frame = undefined,
.input = undefined,
.instance = undefined,
.adapter = undefined,
.device = undefined,
.queue = undefined,
.surface = undefined,
.swap_chain = undefined,
.descriptor = undefined,
.input = .{ .target = 0 },
.frame = .{ .target = 1 },
};
try Platform.init(&core.platform, core, options);
// Tick the platform so that the platform can grab the newly created window
// and perform initialization
// TODO: consider removing `main_window` and then this wont be necessary
try Platform.tick(core);
core.instance = gpu.createInstance(null) orelse {
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);
core_window.instance = gpu.createInstance(null) orelse {
log.err("failed to create GPU instance", .{});
std.process.exit(1);
};
core.surface = core.instance.createSurface(&core.platform.surface_descriptor);
core_window.surface = core_window.instance.createSurface(&core_window.surface_descriptor);
var response: RequestAdapterResponse = undefined;
core.instance.requestAdapter(&gpu.RequestAdapterOptions{
.compatible_surface = core.surface,
.power_preference = options.power_preference,
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) {
@ -191,13 +194,13 @@ pub fn init(core: *Core) !void {
props.driver_description,
});
core.adapter = response.adapter.?;
core_window.adapter = response.adapter.?;
// Create a device with default limits/features.
core.device = response.adapter.?.createDevice(&.{
.required_features_count = if (options.required_features) |v| @as(u32, @intCast(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(?*const gpu.RequiredLimits, &gpu.RequiredLimits{
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,
@ -206,51 +209,46 @@ pub fn init(core: *Core) !void {
log.err("failed to create GPU device\n", .{});
std.process.exit(1);
};
core.device.setUncapturedErrorCallback({}, printUnhandledErrorCallback);
core.queue = core.device.getQueue();
core_window.device.setUncapturedErrorCallback({}, printUnhandledErrorCallback);
core_window.queue = core_window.device.getQueue();
core.descriptor = gpu.SwapChain.Descriptor{
core_window.swap_chain_descriptor = gpu.SwapChain.Descriptor{
.label = "main swap chain",
.usage = options.swap_chain_usage,
.usage = core_window.swap_chain_usage,
.format = .bgra8_unorm,
.width = @intCast(core.platform.size.width),
.height = @intCast(core.platform.size.height),
.present_mode = switch (core.platform.vsync_mode) {
.width = core_window.width,
.height = core_window.height,
.present_mode = switch (core_window.vsync_mode) {
.none => .immediate,
.double => .fifo,
.triple => .mailbox,
},
};
core.swap_chain = core.device.createSwapChain(core.surface, &core.descriptor);
core_window.swap_chain = core_window.device.createSwapChain(core_window.surface, &core_window.swap_chain_descriptor);
core_window.framebuffer_format = core_window.swap_chain_descriptor.format;
core_window.framebuffer_width = core_window.swap_chain_descriptor.width;
core_window.framebuffer_height = core_window.swap_chain_descriptor.height;
core.windows.setRaw(core.main_window, .framebuffer_format, core.descriptor.format);
core.windows.setRaw(core.main_window, .framebuffer_width, core.descriptor.width);
core.windows.setRaw(core.main_window, .framebuffer_height, core.descriptor.height);
// TODO(important): update this information upon framebuffer resize events
// var w = core.windows.get(core.main_window).?;
// w.framebuffer_format = core.descriptor.format;
// w.framebuffer_width = core.descriptor.width;
// w.framebuffer_height = core.descriptor.height;
// w.width = core.platform.size.width;
// w.height = core.platform.size.height;
// core.windows.setAll(core.main_window, w);
core.frame = .{ .target = 0 };
core.input = .{ .target = 1 };
try core.frame.start();
try core.input.start();
core.windows.setValueRaw(window_id, core_window);
}
pub fn tick(core: *Core, core_mod: mach.Mod(Core)) void {
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);
core_mod.call(.processWindowUpdates);
}
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);
@ -287,50 +285,32 @@ pub fn main(core: *Core, core_mod: mach.Mod(Core)) !void {
}
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);
core_mod.call(.processWindowUpdates);
//core_mod.call(.processWindowUpdates);
return core.state != .exited;
}
pub fn processWindowUpdates(core: *Core) void {
if (core.windows.updated(core.main_window, .width) or core.windows.updated(core.main_window, .height)) {
const window = core.windows.getAll(core.main_window);
if (window) |main_window| {
core.platform.setSize(.{
.width = main_window.width,
.height = main_window.height,
});
}
}
}
pub fn deinit(core: *Core) !void {
core.state = .exited;
// TODO(object)(window-title)
// var q = try entities.query(.{
// .titles = Mod.read(.title),
// });
// while (q.next()) |v| {
// for (v.titles) |title| {
// state.allocator.free(title);
// }
// }
// GPU backend must be released BEFORE platform deinit, otherwise we may enter a race
// where the GPU might try to present to the window server.
core.swap_chain.release();
core.queue.release();
core.device.release();
core.surface.release();
core.adapter.release();
core.instance.release();
// Deinit the platform
core.platform.deinit();
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();
}
@ -376,50 +356,6 @@ pub fn outOfMemory(core: *@This()) bool {
return true;
}
// TODO(object)
// /// Sets the window title. The string must be owned by Core, and will not be copied or freed. It is
// /// advised to use the `core.title` buffer for this purpose, e.g.:
// ///
// /// ```
// /// const title = try std.fmt.bufPrintZ(&core.title, "Hello, world!", .{});
// /// core.setTitle(title);
// /// ```
// pub inline fn setTitle(core: *@This(), value: [:0]const u8) void {
// return core.platform.setTitle(value);
// }
// TODO(object)
// /// Set the window mode
// pub inline fn setDisplayMode(core: *@This(), mode: DisplayMode) void {
// return core.platform.setDisplayMode(mode);
// }
// TODO(object)
// /// Returns the window mode
// pub inline fn displayMode(core: *@This()) DisplayMode {
// return core.platform.display_mode;
// }
// TODO(object)
// pub inline fn setBorder(core: *@This(), value: bool) void {
// return core.platform.setBorder(value);
// }
// TODO(object)
// pub inline fn border(core: *@This()) bool {
// return core.platform.border;
// }
// TODO(object)
// pub inline fn setHeadless(core: *@This(), value: bool) void {
// return core.platform.setHeadless(value);
// }
// TODO(object)
// pub inline fn headless(core: *@This()) bool {
// return core.platform.headless;
// }
pub fn keyPressed(core: *@This(), key: Key) bool {
return core.input_state.isKeyPressed(key);
}
@ -587,45 +523,37 @@ pub fn mousePosition(core: *@This()) Position {
// }
pub fn presentFrame(core: *Core, core_mod: mach.Mod(Core)) !void {
// TODO(object)(window-title)
// // Update windows title
// var num_windows: usize = 0;
// var q = try entities.query(.{
// .ids = mach.Entities.Mod.read(.id),
// .titles = Mod.read(.title),
// });
// while (q.next()) |v| {
// for (v.ids, v.titles) |_, title| {
// num_windows += 1;
// state.platform.setTitle(title);
// }
// }
// if (num_windows > 1) @panic("mach: Core currently only supports a single window");
var windows = core.windows.slice();
while (windows.next()) |window_id| {
var core_window = core.windows.getValue(window_id);
_ = try core.platform.update();
mach.sysgpu.Impl.deviceTick(core.device);
core.swap_chain.present();
mach.sysgpu.Impl.deviceTick(core_window.device);
// Update swapchain for the next frame
if (core.swap_chain_update.isSet()) blk: {
core.swap_chain_update.reset();
core_window.swap_chain.present();
switch (core.platform.vsync_mode) {
.triple => core.frame.target = 2 * core.platform.refresh_rate,
else => core.frame.target = 0,
// Update swapchain for the next frame
if (core_window.swap_chain_update.isSet()) blk: {
core_window.swap_chain_update.reset();
switch (core_window.vsync_mode) {
.triple => core.frame.target = 2 * core_window.refresh_rate,
else => core.frame.target = 0,
}
if (core_window.width == 0 or core_window.height == 0) break :blk;
core_window.swap_chain_descriptor.present_mode = switch (core_window.vsync_mode) {
.none => .immediate,
.double => .fifo,
.triple => .mailbox,
};
core_window.swap_chain_descriptor.width = core_window.width;
core_window.swap_chain_descriptor.height = core_window.height;
core_window.swap_chain.release();
core_window.swap_chain = core_window.device.createSwapChain(core_window.surface, &core_window.swap_chain_descriptor);
}
if (core.platform.size.width == 0 or core.platform.size.height == 0) break :blk;
core.descriptor.present_mode = switch (core.platform.vsync_mode) {
.none => .immediate,
.double => .fifo,
.triple => .mailbox,
};
core.descriptor.width = @intCast(core.platform.size.width);
core.descriptor.height = @intCast(core.platform.size.height);
core.swap_chain.release();
core.swap_chain = core.device.createSwapChain(core.surface, &core.descriptor);
}
// TODO(important): update this information in response to resize events rather than
@ -653,28 +581,6 @@ pub fn presentFrame(core: *Core, core_mod: mach.Mod(Core)) !void {
}
}
// TODO(object)(window-title)
// /// Prints into the window title buffer using a format string and arguments. e.g.
// ///
// /// ```
// /// try core.state().printTitle(core_mod, core_mod.state().main_window, "Hello, {s}!", .{"Mach"});
// /// ```
// pub fn printTitle(
// core: *@This(),
// window_id: mach.EntityID,
// comptime fmt: []const u8,
// args: anytype,
// ) !void {
// _ = window_id;
// // Allocate and assign a new window title slice.
// const slice = try std.fmt.allocPrintZ(core.allocator, fmt, args);
// defer core.allocator.free(slice);
// core.setTitle(slice);
// // TODO: This function does not have access to *core.Mod to update
// // try core.Mod.set(window_id, .title, slice);
// }
pub fn exit(core: *Core) void {
core.state = .exiting;
}
@ -745,24 +651,6 @@ const Platform = switch (build_options.core_platform) {
.null => @import("core/Null.zig"),
};
// TODO(object): this struct should not exist
// TODO: this should not be here, it is exposed because the platform implementations need it.
pub const InitOptions = struct {
allocator: std.mem.Allocator,
is_app: bool = false,
headless: bool = false,
display_mode: DisplayMode = .windowed,
border: bool = true,
title: [:0]const u8 = "Mach core",
size: Size = .{ .width = 1920 / 2, .height = 1080 / 2 },
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,
},
};
pub const InputState = struct {
const KeyBitSet = std.StaticBitSet(@as(u8, @intFromEnum(Key.max)) + 1);
const MouseButtonSet = std.StaticBitSet(@as(u4, @intFromEnum(MouseButton.max)) + 1);
@ -793,34 +681,50 @@ pub const Event = union(enum) {
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,
},
framebuffer_resize: Size,
focus_gained,
focus_lost,
close,
window_resize: ResizeEvent,
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 MouseButton = enum {
left,
right,
@ -1070,37 +974,37 @@ const RequestAdapterResponse = struct {
};
// Verifies that a platform implementation exposes the expected function declarations.
comptime {
// Core
assertHasField(Platform, "surface_descriptor");
assertHasField(Platform, "refresh_rate");
// comptime {
// // Core
// assertHasField(Platform, "surface_descriptor");
// assertHasField(Platform, "refresh_rate");
assertHasDecl(Platform, "init");
assertHasDecl(Platform, "deinit");
// assertHasDecl(Platform, "init");
// assertHasDecl(Platform, "deinit");
assertHasDecl(Platform, "setTitle");
// assertHasDecl(Platform, "setTitle");
assertHasDecl(Platform, "setDisplayMode");
assertHasField(Platform, "display_mode");
// assertHasDecl(Platform, "setDisplayMode");
// assertHasField(Platform, "display_mode");
assertHasDecl(Platform, "setBorder");
assertHasField(Platform, "border");
// assertHasDecl(Platform, "setBorder");
// assertHasField(Platform, "border");
assertHasDecl(Platform, "setHeadless");
assertHasField(Platform, "headless");
// assertHasDecl(Platform, "setHeadless");
// assertHasField(Platform, "headless");
assertHasDecl(Platform, "setVSync");
assertHasField(Platform, "vsync_mode");
// assertHasDecl(Platform, "setVSync");
// assertHasField(Platform, "vsync_mode");
assertHasDecl(Platform, "setSize");
assertHasField(Platform, "size");
// assertHasDecl(Platform, "setSize");
// assertHasField(Platform, "size");
assertHasDecl(Platform, "setCursorMode");
assertHasField(Platform, "cursor_mode");
// assertHasDecl(Platform, "setCursorMode");
// assertHasField(Platform, "cursor_mode");
assertHasDecl(Platform, "setCursorShape");
assertHasField(Platform, "cursor_shape");
}
// assertHasDecl(Platform, "setCursorShape");
// assertHasField(Platform, "cursor_shape");
// }
fn assertHasDecl(comptime T: anytype, comptime decl_name: []const u8) void {
if (!@hasDecl(T, decl_name)) @compileError(@typeName(T) ++ " missing declaration: " ++ decl_name);
@ -1112,7 +1016,6 @@ fn assertHasField(comptime T: anytype, comptime field_name: []const u8) void {
test {
_ = Platform;
@import("std").testing.refAllDeclsRecursive(InitOptions);
@import("std").testing.refAllDeclsRecursive(VSyncMode);
@import("std").testing.refAllDeclsRecursive(Size);
@import("std").testing.refAllDeclsRecursive(Position);