diff --git a/examples/core-custom-entrypoint/App.zig b/examples/core-custom-entrypoint/App.zig index 4de8495694..8784c3d057 100644 --- a/examples/core-custom-entrypoint/App.zig +++ b/examples/core-custom-entrypoint/App.zig @@ -28,8 +28,10 @@ pub fn init( core.on_tick = app_mod.id.tick; core.on_exit = app_mod.id.deinit; + const main_window = core.windows.getValue(core.main_window); + // Create our shader module - const shader_module = core.device.createShaderModuleWGSL("shader.wgsl", @embedFile("shader.wgsl")); + const shader_module = main_window.device.createShaderModuleWGSL("shader.wgsl", @embedFile("shader.wgsl")); defer shader_module.release(); // Blend state describes how rendered colors get blended @@ -37,7 +39,7 @@ pub fn init( // Color target describes e.g. the pixel format of the window we are rendering to. const color_target = gpu.ColorTargetState{ - .format = core.windows.get(core.main_window, .framebuffer_format).?, + .format = main_window.framebuffer_format, .blend = &blend, }; @@ -58,7 +60,7 @@ pub fn init( .entry_point = "vertex_main", }, }; - const pipeline = core.device.createRenderPipeline(&pipeline_descriptor); + const pipeline = main_window.device.createRenderPipeline(&pipeline_descriptor); // Store our render pipeline in our module's state, so we can access it later on. app.* = .{ @@ -78,14 +80,16 @@ pub fn tick(core: *mach.Core, app: *App) !void { } } + const main_window = core.windows.getValue(core.main_window); + // Grab the back buffer of the swapchain // TODO(Core) - const back_buffer_view = core.swap_chain.getCurrentTextureView().?; + const back_buffer_view = main_window.swap_chain.getCurrentTextureView().?; defer back_buffer_view.release(); // Create a command encoder const label = @tagName(mach_module) ++ ".tick"; - const encoder = core.device.createCommandEncoder(&.{ .label = label }); + const encoder = main_window.device.createCommandEncoder(&.{ .label = label }); defer encoder.release(); // Begin render pass @@ -112,7 +116,7 @@ pub fn tick(core: *mach.Core, app: *App) !void { // Submit our commands to the queue var command = encoder.finish(&.{ .label = label }); defer command.release(); - core.queue.submit(&[_]*gpu.CommandBuffer{command}); + main_window.queue.submit(&[_]*gpu.CommandBuffer{command}); // update the window title every second if (app.title_timer.read() >= 1.0) { diff --git a/examples/core-triangle/App.zig b/examples/core-triangle/App.zig index 8bcba25923..0a280dbce3 100644 --- a/examples/core-triangle/App.zig +++ b/examples/core-triangle/App.zig @@ -1,3 +1,4 @@ +const std = @import("std"); const mach = @import("mach"); const gpu = mach.gpu; @@ -24,64 +25,95 @@ pub fn init( core.on_tick = app_mod.id.tick; core.on_exit = app_mod.id.deinit; - // Create our shader module - const shader_module = core.device.createShaderModuleWGSL("shader.wgsl", @embedFile("shader.wgsl")); - defer shader_module.release(); - - // Blend state describes how rendered colors get blended - const blend = gpu.BlendState{}; - - // Color target describes e.g. the pixel format of the window we are rendering to. - const color_target = gpu.ColorTargetState{ - .format = core.windows.get(core.main_window, .framebuffer_format).?, - .blend = &blend, - }; - - // Fragment state describes which shader and entrypoint to use for rendering fragments. - const fragment = gpu.FragmentState.init(.{ - .module = shader_module, - .entry_point = "frag_main", - .targets = &.{color_target}, - }); - - // Create our render pipeline that will ultimately get pixels onto the screen. - const label = @tagName(mach_module) ++ ".init"; - const pipeline_descriptor = gpu.RenderPipeline.Descriptor{ - .label = label, - .fragment = &fragment, - .vertex = gpu.VertexState{ + const main_window = core.windows.getValue(core.main_window); + if (main_window.native != null) { + // if window.native is not null, the window is initialized + + // Create our shader module + const shader_module = main_window.device.createShaderModuleWGSL("shader.wgsl", @embedFile("shader.wgsl")); + defer shader_module.release(); + + // Blend state describes how rendered colors get blended + const blend = gpu.BlendState{}; + + // Color target describes e.g. the pixel format of the window we are rendering to. + const color_target = gpu.ColorTargetState{ + .format = core.windows.get(core.main_window, .framebuffer_format), + .blend = &blend, + }; + + // Fragment state describes which shader and entrypoint to use for rendering fragments. + const fragment = gpu.FragmentState.init(.{ .module = shader_module, - .entry_point = "vertex_main", - }, - }; - const pipeline = core.device.createRenderPipeline(&pipeline_descriptor); - - // Store our render pipeline in our module's state, so we can access it later on. - app.* = .{ - .title_timer = try mach.time.Timer.start(), - .pipeline = pipeline, - }; - - // TODO(object): window-title - // try updateWindowTitle(core); + .entry_point = "frag_main", + .targets = &.{color_target}, + }); + + // Create our render pipeline that will ultimately get pixels onto the screen. + const label = @tagName(mach_module) ++ ".init"; + const pipeline_descriptor = gpu.RenderPipeline.Descriptor{ + .label = label, + .fragment = &fragment, + .vertex = gpu.VertexState{ + .module = shader_module, + .entry_point = "vertex_main", + }, + }; + const pipeline = main_window.device.createRenderPipeline(&pipeline_descriptor); + + // Store our render pipeline in our module's state, so we can access it later on. + app.* = .{ + .title_timer = try mach.time.Timer.start(), + .pipeline = pipeline, + }; + } } +// TODO(object): window-title +// try updateWindowTitle(core); + pub fn tick(app: *App, core: *mach.Core) void { while (core.nextEvent()) |event| { switch (event) { + .key_press => |ev| { + switch (ev.key) { + .right => { + var w = core.windows.getValue(core.main_window); + w.width = w.width + 10; + core.windows.setValue(core.main_window, w); + }, + .left => { + var w = core.windows.getValue(core.main_window); + w.width = w.width - 10; + core.windows.setValue(core.main_window, w); + }, + .up => { + core.windows.set(core.main_window, .height, core.windows.get(core.main_window, .height) + 10); + }, + .down => { + core.windows.set(core.main_window, .height, core.windows.get(core.main_window, .height) - 10); + }, + else => {}, + } + }, .close => core.exit(), else => {}, } } + var main_window = core.windows.getValue(core.main_window); + + // Window is ready when device is not null + // Grab the back buffer of the swapchain // TODO(Core) - const back_buffer_view = core.swap_chain.getCurrentTextureView().?; + const back_buffer_view = main_window.swap_chain.getCurrentTextureView().?; defer back_buffer_view.release(); // Create a command encoder const label = @tagName(mach_module) ++ ".tick"; - const encoder = core.device.createCommandEncoder(&.{ .label = label }); + + const encoder = main_window.device.createCommandEncoder(&.{ .label = label }); defer encoder.release(); // Begin render pass @@ -108,14 +140,14 @@ pub fn tick(app: *App, core: *mach.Core) void { // Submit our commands to the queue var command = encoder.finish(&.{ .label = label }); defer command.release(); - core.queue.submit(&[_]*gpu.CommandBuffer{command}); + main_window.queue.submit(&[_]*gpu.CommandBuffer{command}); // update the window title every second - if (app.title_timer.read() >= 1.0) { - app.title_timer.reset(); - // TODO(object): window-title - // try updateWindowTitle(core); - } + // if (app.title_timer.read() >= 1.0) { + // app.title_timer.reset(); + // // TODO(object): window-title + // // try updateWindowTitle(core); + // } } pub fn deinit(app: *App) void { diff --git a/src/Core.zig b/src/Core.zig index 6b83c9f543..05f5430a3f 100644 --- a/src/Core.zig +++ b/src/Core.zig @@ -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, + + /// 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, + }, - /// Whether the window is fullscreen (read-only) - fullscreen: bool, + 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.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_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.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"); - - _ = try core.platform.update(); - mach.sysgpu.Impl.deviceTick(core.device); - core.swap_chain.present(); - - // Update swapchain for the next frame - if (core.swap_chain_update.isSet()) blk: { - core.swap_chain_update.reset(); - - switch (core.platform.vsync_mode) { - .triple => core.frame.target = 2 * core.platform.refresh_rate, - else => core.frame.target = 0, - } + var windows = core.windows.slice(); + while (windows.next()) |window_id| { + var core_window = core.windows.getValue(window_id); - if (core.platform.size.width == 0 or core.platform.size.height == 0) break :blk; + mach.sysgpu.Impl.deviceTick(core_window.device); - 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); + core_window.swap_chain.present(); + + // 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); + } } // 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); diff --git a/src/core/Darwin.zig b/src/core/Darwin.zig index ed9c9c26f7..eee8e40a72 100644 --- a/src/core/Darwin.zig +++ b/src/core/Darwin.zig @@ -2,7 +2,6 @@ const std = @import("std"); const mach = @import("../main.zig"); const Core = @import("../Core.zig"); const gpu = mach.gpu; -const InitOptions = Core.InitOptions; const Event = Core.Event; const KeyEvent = Core.KeyEvent; const MouseButtonEvent = Core.MouseButtonEvent; @@ -21,26 +20,14 @@ const log = std.log.scoped(.mach); pub const Darwin = @This(); -// -------------------------- -// Module state -// -------------------------- -allocator: std.mem.Allocator, -core: *Core, - -// Core platform interface -surface_descriptor: gpu.Surface.Descriptor, -title: [:0]const u8, -display_mode: DisplayMode, -vsync_mode: VSyncMode, -cursor_mode: CursorMode, -cursor_shape: CursorShape, -border: bool, -headless: bool, -refresh_rate: u32, -size: Size, - -// Internals -window: ?*objc.app_kit.Window, +pub const Native = struct { + window: ?*objc.app_kit.Window = null, +}; + +pub const Context = struct { + core: *Core, + window_id: mach.ObjectID, +}; pub fn run(comptime on_each_update_fn: anytype, args_tuple: std.meta.ArgsTuple(@TypeOf(on_each_update_fn))) noreturn { const Args = @TypeOf(args_tuple); @@ -76,193 +63,177 @@ pub fn run(comptime on_each_update_fn: anytype, args_tuple: std.meta.ArgsTuple(@ // TODO: support UIKit. } -pub fn init( - darwin: *Darwin, - core: *Core, - options: InitOptions, -) !void { - var surface_descriptor = gpu.Surface.Descriptor{}; - - // TODO: support UIKit. - var window_opt: ?*objc.app_kit.Window = null; - if (!options.headless) { - // If the application is not headless, we need to make the application a genuine UI application - // by setting the activation policy, this moves the process to foreground - _ = objc.app_kit.Application.sharedApplication().setActivationPolicy(objc.app_kit.ApplicationActivationPolicyRegular); - - const metal_descriptor = try options.allocator.create(gpu.Surface.DescriptorFromMetalLayer); - const layer = objc.quartz_core.MetalLayer.new(); - defer layer.release(); - layer.setDisplaySyncEnabled(true); - metal_descriptor.* = .{ - .layer = layer, - }; - surface_descriptor.next_in_chain = .{ .from_metal_layer = metal_descriptor }; - - const screen = objc.app_kit.Screen.mainScreen(); - const rect = objc.core_graphics.Rect{ - .origin = .{ .x = 0, .y = 0 }, - .size = .{ .width = @floatFromInt(options.size.width), .height = @floatFromInt(options.size.height) }, - }; - - const window_style = - (if (options.display_mode == .fullscreen) objc.app_kit.WindowStyleMaskFullScreen else 0) | - (if (options.display_mode == .windowed) objc.app_kit.WindowStyleMaskTitled else 0) | - (if (options.display_mode == .windowed) objc.app_kit.WindowStyleMaskClosable else 0) | - (if (options.display_mode == .windowed) objc.app_kit.WindowStyleMaskMiniaturizable else 0) | - (if (options.display_mode == .windowed) objc.app_kit.WindowStyleMaskResizable else 0); - - window_opt = objc.app_kit.Window.alloc().initWithContentRect_styleMask_backing_defer_screen( - rect, - window_style, - objc.app_kit.BackingStoreBuffered, - false, - screen, - ); - if (window_opt) |window| { - window.setReleasedWhenClosed(false); - - var view = objc.mach.View.allocInit(); - view.setLayer(@ptrCast(layer)); - - { - var keyDown = objc.foundation.stackBlockLiteral(ViewCallbacks.keyDown, darwin, null, null); - view.setBlock_keyDown(keyDown.asBlock().copy()); - - var keyUp = objc.foundation.stackBlockLiteral(ViewCallbacks.keyUp, darwin, null, null); - view.setBlock_keyUp(keyUp.asBlock().copy()); - } - window.setContentView(@ptrCast(view)); - window.center(); - window.setIsVisible(true); - window.makeKeyAndOrderFront(null); - - const delegate = objc.mach.WindowDelegate.allocInit(); - defer window.setDelegate(@ptrCast(delegate)); - { // Set WindowDelegate blocks - var windowWillResize_toSize = objc.foundation.stackBlockLiteral(WindowDelegateCallbacks.windowWillResize_toSize, darwin, null, null); - delegate.setBlock_windowWillResize_toSize(windowWillResize_toSize.asBlock().copy()); - - var windowShouldClose = objc.foundation.stackBlockLiteral(WindowDelegateCallbacks.windowShouldClose, darwin, null, null); - delegate.setBlock_windowShouldClose(windowShouldClose.asBlock().copy()); +pub fn tick(core: *Core) !void { + var windows = core.windows.slice(); + while (windows.next()) |window_id| { + const native_opt: ?Native = core.windows.get(window_id, .native); + + if (native_opt) |native| { + if (native.window) |native_window| { + // Handle resizing the window when the user changes width or height + if (core.windows.updated(window_id, .width) or core.windows.updated(window_id, .height)) { + var frame = native_window.frame(); + frame.size.height = @floatFromInt(core.windows.get(window_id, .width)); + frame.size.width = @floatFromInt(core.windows.get(window_id, .height)); + native_window.setFrame_display_animate(frame, true, true); + } } - } else std.debug.panic("mach: window failed to initialize", .{}); - } - - darwin.* = .{ - .allocator = options.allocator, - .core = core, - .title = options.title, - .display_mode = options.display_mode, - .vsync_mode = .none, - .cursor_mode = .normal, - .cursor_shape = .arrow, - .border = options.border, - .headless = options.headless, - .refresh_rate = 60, // TODO: set to something meaningful - .size = options.size, - .surface_descriptor = surface_descriptor, - .window = window_opt, - }; -} - -pub fn deinit(darwin: *Darwin) void { - if (darwin.window) |w| @as(*objc.foundation.ObjectProtocol, @ptrCast(w)).release(); - return; -} - -pub fn update(darwin: *Darwin) !void { - if (darwin.window) |window| window.update(); -} - -pub fn setTitle(darwin: *Darwin, title: [:0]const u8) void { - if (darwin.window) |window| { - var string = objc.app_kit.String.allocInit(); - defer string.release(); - string = string.initWithUTF8String(title.ptr); - window.setTitle(string); + } else { + try initWindow(core, window_id); + } } } -pub fn setDisplayMode(_: *Darwin, _: DisplayMode) void { - return; -} - -pub fn setBorder(_: *Darwin, _: bool) void { - return; -} - -pub fn setHeadless(_: *Darwin, _: bool) void { - return; -} - -pub fn setVSync(_: *Darwin, _: VSyncMode) void { - return; -} +fn initWindow( + core: *Core, + window_id: mach.ObjectID, +) !void { + var core_window = core.windows.getValue(window_id); + // If the application is not headless, we need to make the application a genuine UI application + // by setting the activation policy, this moves the process to foreground + // TODO: Only call this on the first window creation + _ = objc.app_kit.Application.sharedApplication().setActivationPolicy(objc.app_kit.ApplicationActivationPolicyRegular); + + const metal_descriptor = try core.allocator.create(gpu.Surface.DescriptorFromMetalLayer); + const layer = objc.quartz_core.MetalLayer.new(); + defer layer.release(); + layer.setDisplaySyncEnabled(true); + metal_descriptor.* = .{ + .layer = layer, + }; + core_window.surface_descriptor = .{}; + core_window.surface_descriptor.next_in_chain = .{ .from_metal_layer = metal_descriptor }; -pub fn setSize(darwin: *Darwin, size: Size) void { - if (darwin.window) |window| { - var frame = window.frame(); - frame.size.height = @floatFromInt(size.height); - frame.size.width = @floatFromInt(size.width); - window.setFrame_display_animate(frame, true, true); - } -} + const screen = objc.app_kit.Screen.mainScreen(); + const rect = objc.core_graphics.Rect{ + .origin = .{ .x = 0, .y = 0 }, + .size = .{ .width = @floatFromInt(core_window.width), .height = @floatFromInt(core_window.height) }, + }; -pub fn setCursorMode(_: *Darwin, _: CursorMode) void { - return; -} + const window_style = + (if (core_window.display_mode == .fullscreen) objc.app_kit.WindowStyleMaskFullScreen else 0) | + (if (core_window.display_mode == .windowed) objc.app_kit.WindowStyleMaskTitled else 0) | + (if (core_window.display_mode == .windowed) objc.app_kit.WindowStyleMaskClosable else 0) | + (if (core_window.display_mode == .windowed) objc.app_kit.WindowStyleMaskMiniaturizable else 0) | + (if (core_window.display_mode == .windowed) objc.app_kit.WindowStyleMaskResizable else 0); + + const native_window_opt: ?*objc.app_kit.Window = objc.app_kit.Window.alloc().initWithContentRect_styleMask_backing_defer_screen( + rect, + window_style, + objc.app_kit.BackingStoreBuffered, + false, + screen, + ); + if (native_window_opt) |native_window| { + native_window.setReleasedWhenClosed(false); + + var view = objc.mach.View.allocInit(); + view.setLayer(@ptrCast(layer)); + + var context: Context = .{ .core = core, .window_id = window_id }; + { + var keyDown = objc.foundation.stackBlockLiteral( + ViewCallbacks.keyDown, + &context, + null, + null, + ); + view.setBlock_keyDown(keyDown.asBlock().copy()); + + var keyUp = objc.foundation.stackBlockLiteral( + ViewCallbacks.keyUp, + &context, + null, + null, + ); + view.setBlock_keyUp(keyUp.asBlock().copy()); + } + native_window.setContentView(@ptrCast(view)); + native_window.center(); + native_window.setIsVisible(true); + native_window.makeKeyAndOrderFront(null); + + const delegate = objc.mach.WindowDelegate.allocInit(); + defer native_window.setDelegate(@ptrCast(delegate)); + { // Set WindowDelegate blocks + + var windowWillResize_toSize = objc.foundation.stackBlockLiteral( + WindowDelegateCallbacks.windowWillResize_toSize, + &context, + null, + null, + ); + delegate.setBlock_windowWillResize_toSize(windowWillResize_toSize.asBlock().copy()); + + var windowShouldClose = objc.foundation.stackBlockLiteral( + WindowDelegateCallbacks.windowShouldClose, + &context, + null, + null, + ); + delegate.setBlock_windowShouldClose(windowShouldClose.asBlock().copy()); + } -pub fn setCursorShape(_: *Darwin, _: CursorShape) void { - return; + // Set core_window.native, which we use to check if a window is initialized + // Then call core.initWindow to finish initializing the window + core_window.native = .{ .window = native_window }; + core.windows.setValueRaw(window_id, core_window); + try core.initWindow(window_id); + } else std.debug.panic("mach: window failed to initialize", .{}); } const WindowDelegateCallbacks = struct { - pub fn windowWillResize_toSize(block: *objc.foundation.BlockLiteral(*Darwin), size: objc.app_kit.Size) callconv(.C) void { - const darwin: *Darwin = block.context; + pub fn windowWillResize_toSize(block: *objc.foundation.BlockLiteral(*Context), size: objc.app_kit.Size) callconv(.C) void { + const core: *Core = block.context.core; const s: Size = .{ .width = @intFromFloat(size.width), .height = @intFromFloat(size.height) }; - // TODO: Eventually we need to be able to tie a window here with the window Objects in core, and treat the windows - // as a list, rather than a single main window - darwin.size = .{ - .height = s.width, - .width = s.height, - }; - darwin.core.swap_chain_update.set(); - - darwin.core.windows.setRaw(darwin.core.main_window, .width, s.width); - darwin.core.windows.setRaw(darwin.core.main_window, .height, s.height); + var window = core.windows.getValue(block.context.window_id); + window.width = s.width; + window.height = s.height; + window.swap_chain_update.set(); + core.windows.setValueRaw(block.context.window_id, window); - darwin.core.pushEvent(.{ .framebuffer_resize = .{ .width = s.width, .height = s.height } }); + core.pushEvent(.{ .window_resize = .{ + .window_id = block.context.window_id, + .size = s, + } }); } - pub fn windowShouldClose(block: *objc.foundation.BlockLiteral(*Darwin)) callconv(.C) bool { - const darwin: *Darwin = block.context; - darwin.core.pushEvent(.close); + pub fn windowShouldClose(block: *objc.foundation.BlockLiteral(*Context)) callconv(.C) bool { + const core: *Core = block.context.core; + core.pushEvent(.{ .close = .{ .window_id = block.context.window_id } }); + + // TODO: This should just attempt to close the window, not the entire program, unless + // this is the only window. return false; } }; const ViewCallbacks = struct { - pub fn keyDown(block: *objc.foundation.BlockLiteral(*Darwin), event: *objc.app_kit.Event) callconv(.C) void { - const darwin: *Darwin = block.context; + pub fn keyDown(block: *objc.foundation.BlockLiteral(*Context), event: *objc.app_kit.Event) callconv(.C) void { + const core: *Core = block.context.core; + const window_id = block.context.window_id; if (event.isARepeat()) { - darwin.core.pushEvent(.{ .key_repeat = .{ + core.pushEvent(.{ .key_repeat = .{ + .window_id = window_id, .key = machKeyFromKeycode(event.keyCode()), .mods = machModifierFromModifierFlag(event.modifierFlags()), } }); } else { - darwin.core.pushEvent(.{ .key_press = .{ + core.pushEvent(.{ .key_press = .{ + .window_id = window_id, .key = machKeyFromKeycode(event.keyCode()), .mods = machModifierFromModifierFlag(event.modifierFlags()), } }); } } - pub fn keyUp(block: *objc.foundation.BlockLiteral(*Darwin), event: *objc.app_kit.Event) callconv(.C) void { - const darwin: *Darwin = block.context; + pub fn keyUp(block: *objc.foundation.BlockLiteral(*Context), event: *objc.app_kit.Event) callconv(.C) void { + const core: *Core = block.context.core; + const window_id = block.context.window_id; - darwin.core.pushEvent(.{ .key_release = .{ + core.pushEvent(.{ .key_release = .{ + .window_id = window_id, .key = machKeyFromKeycode(event.keyCode()), .mods = machModifierFromModifierFlag(event.modifierFlags()), } }); diff --git a/src/core/Null.zig b/src/core/Null.zig index 37767c71ff..a222f2e3e4 100644 --- a/src/core/Null.zig +++ b/src/core/Null.zig @@ -5,7 +5,7 @@ const std = @import("std"); const mach = @import("../main.zig"); const Core = @import("../Core.zig"); const gpu = mach.gpu; -const InitOptions = Core.InitOptions; +//const InitOptions = Core.InitOptions; const Event = Core.Event; const KeyEvent = Core.KeyEvent; const MouseButtonEvent = Core.MouseButtonEvent; @@ -40,10 +40,10 @@ surface_descriptor: gpu.Surface.Descriptor, pub fn init( nul: *Null, core: *Core, - options: InitOptions, + //options: InitOptions, ) !void { _ = nul; - _ = options; + //_ = options; _ = core; return; } diff --git a/src/core/Windows.zig b/src/core/Windows.zig index d955a129df..d6e2aa452c 100644 --- a/src/core/Windows.zig +++ b/src/core/Windows.zig @@ -4,7 +4,6 @@ const mach = @import("../main.zig"); const Core = @import("../Core.zig"); const gpu = mach.gpu; -const InitOptions = Core.InitOptions; const Event = Core.Event; const KeyEvent = Core.KeyEvent; const MouseButtonEvent = Core.MouseButtonEvent; @@ -21,42 +20,147 @@ const KeyMods = Core.KeyMods; const EventQueue = std.fifo.LinearFifo(Event, .Dynamic); const Win32 = @This(); +pub const Native = struct { + window: w.HWND = undefined, + surrogate: u16 = 0, + dinput: *w.IDirectInput8W = undefined, + saved_window_rect: w.RECT = undefined, + surface_descriptor_from_hwnd: gpu.Surface.DescriptorFromWindowsHWND = undefined, +}; + +pub const Context = struct { + core: *Core, + window_id: mach.ObjectID, +}; + // -------------------------- // Module state // -------------------------- -allocator: std.mem.Allocator, -core: *Core, +// allocator: std.mem.Allocator, +// core: *Core, // Core platform interface -surface_descriptor: gpu.Surface.Descriptor, -display_mode: DisplayMode, -vsync_mode: VSyncMode, -cursor_mode: CursorMode, -cursor_shape: CursorShape, -border: bool, -headless: bool, -size: Size, - -// Internals -window: w.HWND, -refresh_rate: u32, -surrogate: u16 = 0, -dinput: *w.IDirectInput8W, -saved_window_rect: w.RECT, -surface_descriptor_from_hwnd: gpu.Surface.DescriptorFromWindowsHWND, +// surface_descriptor: gpu.Surface.Descriptor, +// display_mode: DisplayMode, +// vsync_mode: VSyncMode, +// cursor_mode: CursorMode, +// cursor_shape: CursorShape, +// border: bool, +// headless: bool, +// size: Size, + +// // Internals +// window: w.HWND, +// refresh_rate: u32, +// surrogate: u16 = 0, +// dinput: *w.IDirectInput8W, +// saved_window_rect: w.RECT, +// surface_descriptor_from_hwnd: gpu.Surface.DescriptorFromWindowsHWND, // ------------------------------ // Platform interface // ------------------------------ -pub fn init( - self: *Win32, +// pub fn init( +// self: *Win32, +// core: *Core, +// //options: InitOptions, +// ) !void { +// // self.allocator = options.allocator; +// // self.core = core; +// // self.size = options.size; +// // self.saved_window_rect = .{ .top = 0, .left = 0, .right = 0, .bottom = 0 }; + +// var native: Native = .{}; + +// const hInstance = w.GetModuleHandleW(null); +// const class_name = w.L("mach"); +// const class = std.mem.zeroInit(w.WNDCLASSW, .{ +// .style = w.CS_OWNDC, +// .lpfnWndProc = wndProc, +// .hInstance = hInstance, +// .hIcon = w.LoadIconW(null, @as([*:0]align(1) const u16, @ptrFromInt(@as(u32, w.IDI_APPLICATION)))), +// .hCursor = w.LoadCursorW(null, @as([*:0]align(1) const u16, @ptrFromInt(@as(u32, w.IDC_ARROW)))), +// .lpszClassName = class_name, +// }); +// if (w.RegisterClassW(&class) == 0) return error.Unexpected; + +// const title = try std.unicode.utf8ToUtf16LeAllocZ(self.allocator, options.title); +// defer self.allocator.free(title); + +// var request_window_width: i32 = @bitCast(self.size.width); +// var request_window_height: i32 = @bitCast(self.size.height); + +// const window_ex_style: w.WINDOW_EX_STYLE = .{ .APPWINDOW = 1 }; +// const window_style: w.WINDOW_STYLE = if (options.border) w.WS_OVERLAPPEDWINDOW else w.WS_POPUPWINDOW; // w.WINDOW_STYLE{.POPUP = 1}; +// // TODO (win32): should border == false mean borderless display_mode? + +// var rect: w.RECT = .{ .left = 0, .top = 0, .right = request_window_width, .bottom = request_window_height }; + +// if (w.TRUE == w.AdjustWindowRectEx(&rect, window_style, w.FALSE, window_ex_style)) { +// request_window_width = rect.right - rect.left; +// request_window_height = rect.bottom - rect.top; +// } + +// const window = w.CreateWindowExW( +// window_ex_style, +// class_name, +// title, +// window_style, +// w.CW_USEDEFAULT, +// w.CW_USEDEFAULT, +// request_window_width, +// request_window_height, +// null, +// null, +// hInstance, +// null, +// ) orelse return error.Unexpected; + +// native.window = window; + +// var dinput: ?*w.IDirectInput8W = undefined; +// const ptr: ?*?*anyopaque = @ptrCast(&dinput); +// if (w.DirectInput8Create(hInstance, w.DIRECTINPUT_VERSION, w.IID_IDirectInput8W, ptr, null) != w.DI_OK) { +// return error.Unexpected; +// } +// native.dinput = dinput.?; + +// native.surface_descriptor_from_hwnd = .{ +// .hinstance = std.os.windows.kernel32.GetModuleHandleW(null).?, +// .hwnd = window, +// }; + +// core_window.surface_descriptor = .{ .next_in_chain = .{ +// .from_windows_hwnd = &self.surface_descriptor_from_hwnd, +// } }; +// self.border = options.border; +// self.headless = options.headless; +// self.refresh_rate = 60; // TODO (win32) get monitor refresh rate +// self.vsync_mode = .triple; + +// _ = w.SetWindowLongPtrW(window, w.GWLP_USERDATA, @bitCast(@intFromPtr(self))); +// if (!options.headless) { +// setDisplayMode(self, options.display_mode); +// } + +// self.size = getClientRect(self); +// _ = w.GetWindowRect(self.window, &self.saved_window_rect); +// } + +// pub fn deinit(self: *Win32) void { +// _ = self.dinput.IUnknown_Release(); +// } + +pub fn tick(core: *Core) !void { + _ = core; // autofix + +} + +fn initWindow( core: *Core, - options: InitOptions, + window_id: mach.ObjectID, ) !void { - self.allocator = options.allocator; - self.core = core; - self.size = options.size; - self.saved_window_rect = .{ .top = 0, .left = 0, .right = 0, .bottom = 0 }; + const core_window = core.windows.getValue(window_id); const hInstance = w.GetModuleHandleW(null); const class_name = w.L("mach"); @@ -70,15 +174,14 @@ pub fn init( }); if (w.RegisterClassW(&class) == 0) return error.Unexpected; - const title = try std.unicode.utf8ToUtf16LeAllocZ(self.allocator, options.title); - defer self.allocator.free(title); + const title = try std.unicode.utf8ToUtf16LeAllocZ(core.allocator, core_window.title); + defer core.allocator.free(title); - var request_window_width: i32 = @bitCast(self.size.width); - var request_window_height: i32 = @bitCast(self.size.height); + var request_window_width: i32 = @bitCast(core_window.width); + var request_window_height: i32 = @bitCast(core_window.height); const window_ex_style: w.WINDOW_EX_STYLE = .{ .APPWINDOW = 1 }; - const window_style: w.WINDOW_STYLE = if (options.border) w.WS_OVERLAPPEDWINDOW else w.WS_POPUPWINDOW; // w.WINDOW_STYLE{.POPUP = 1}; - // TODO (win32): should border == false mean borderless display_mode? + const window_style: w.WINDOW_STYLE = if (core_window.border) w.WS_OVERLAPPEDWINDOW else w.WS_POPUPWINDOW; // w.WINDOW_STYLE{.POPUP = 1}; var rect: w.RECT = .{ .left = 0, .top = 0, .right = request_window_width, .bottom = request_window_height }; @@ -87,7 +190,7 @@ pub fn init( request_window_height = rect.bottom - rect.top; } - const window = w.CreateWindowExW( + const native_window = w.CreateWindowExW( window_ex_style, class_name, title, @@ -102,50 +205,44 @@ pub fn init( null, ) orelse return error.Unexpected; - self.window = window; + var native: Native = .{}; var dinput: ?*w.IDirectInput8W = undefined; const ptr: ?*?*anyopaque = @ptrCast(&dinput); if (w.DirectInput8Create(hInstance, w.DIRECTINPUT_VERSION, w.IID_IDirectInput8W, ptr, null) != w.DI_OK) { return error.Unexpected; } - self.dinput = dinput.?; + native.dinput = dinput.?; - self.surface_descriptor_from_hwnd = .{ + native.surface_descriptor_from_hwnd = .{ .hinstance = std.os.windows.kernel32.GetModuleHandleW(null).?, - .hwnd = window, + .hwnd = native_window, }; - self.surface_descriptor = .{ .next_in_chain = .{ - .from_windows_hwnd = &self.surface_descriptor_from_hwnd, + core_window.surface_descriptor = .{ .next_in_chain = .{ + .from_windows_hwnd = &native.surface_descriptor_from_hwnd, } }; - self.border = options.border; - self.headless = options.headless; - self.refresh_rate = 60; // TODO (win32) get monitor refresh rate - self.vsync_mode = .triple; - - _ = w.SetWindowLongPtrW(window, w.GWLP_USERDATA, @bitCast(@intFromPtr(self))); - if (!options.headless) { - setDisplayMode(self, options.display_mode); - } - self.size = getClientRect(self); - _ = w.GetWindowRect(self.window, &self.saved_window_rect); -} + const context: Context = .{ .core = core, .window_id = window_id }; -pub fn deinit(self: *Win32) void { - _ = self.dinput.IUnknown_Release(); -} + _ = w.SetWindowLongPtrW(native_window, w.GWLP_USERDATA, @bitCast(@intFromPtr(&context))); -pub fn update(self: *Win32) !void { - _ = self; - var msg: w.MSG = undefined; - while (w.PeekMessageW(&msg, null, 0, 0, w.PM_REMOVE) != 0) { - _ = w.TranslateMessage(&msg); - _ = w.DispatchMessageW(&msg); - } + const size = getClientRect(core, window_id); + core_window.width = size.width; + core_window.height = size.height; + + _ = w.GetWindowRect(native.window, &native.saved_window_rect); } +// pub fn update(self: *Win32) !void { +// _ = self; +// var msg: w.MSG = undefined; +// while (w.PeekMessageW(&msg, null, 0, 0, w.PM_REMOVE) != 0) { +// _ = w.TranslateMessage(&msg); +// _ = w.DispatchMessageW(&msg); +// } +// } + pub fn setTitle(self: *Win32, title: [:0]const u8) void { const wtitle = std.unicode.utf8ToUtf16LeAllocZ(self.allocator, title) catch { self.state.oom.set(); @@ -260,21 +357,30 @@ pub fn nativeWindowWin32(self: *Win32) w.HWND { // ----------------------------- // Internal functions // ----------------------------- -fn getClientRect(self: *Win32) Size { - var rect: w.RECT = undefined; - _ = w.GetClientRect(self.window, &rect); +fn getClientRect(core: *Core, window_id: mach.ObjectID) Size { + const window = core.windows.getValue(window_id); + + if (window.native) |native| { + var rect: w.RECT = undefined; + _ = w.GetClientRect(native.window, &rect); - const width: u32 = @intCast(rect.right - rect.left); - const height: u32 = @intCast(rect.bottom - rect.top); + const width: u32 = @intCast(rect.right - rect.left); + const height: u32 = @intCast(rect.bottom - rect.top); - return .{ .width = width, .height = height }; + return .{ .width = width, .height = height }; + } + + return .{ .width = 0, .height = 0 }; } -fn restoreWindowPosition(self: *Win32) void { - if (self.saved_window_rect.right - self.saved_window_rect.left == 0) { - _ = w.ShowWindow(self.window, w.SW_RESTORE); - } else { - _ = w.SetWindowPos(self.window, null, self.saved_window_rect.left, self.saved_window_rect.top, self.saved_window_rect.right - self.saved_window_rect.left, self.saved_window_rect.bottom - self.saved_window_rect.top, w.SWP_SHOWWINDOW); +fn restoreWindowPosition(core: *Core, window_id: mach.ObjectID) void { + const window = core.windows.getValue(window_id); + if (window.native) |native| { + if (native.saved_window_rect.right - native.saved_window_rect.left == 0) { + _ = w.ShowWindow(native.window, w.SW_RESTORE); + } else { + _ = w.SetWindowPos(native.window, null, native.saved_window_rect.left, native.saved_window_rect.top, native.saved_window_rect.right - native.saved_window_rect.left, native.saved_window_rect.bottom - native.saved_window_rect.top, w.SWP_SHOWWINDOW); + } } } @@ -291,28 +397,38 @@ fn getKeyboardModifiers() mach.Core.KeyMods { } fn wndProc(wnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w.WINAPI) w.LRESULT { - const self = blk: { + const context = blk: { const userdata: usize = @bitCast(w.GetWindowLongPtrW(wnd, w.GWLP_USERDATA)); - const ptr: ?*Win32 = @ptrFromInt(userdata); + const ptr: ?*Context = @ptrFromInt(userdata); break :blk ptr orelse return w.DefWindowProcW(wnd, msg, wParam, lParam); }; + const core = context.core; + const window_id = context.window_id; + + const window = core.windows.getValue(window_id); + defer core.windows.setValueRaw(window_id, window); + switch (msg) { w.WM_CLOSE => { - self.core.pushEvent(.close); + core.pushEvent(.close); return 0; }, w.WM_SIZE => { const width: u32 = @as(u32, @intCast(lParam & 0xFFFF)); const height: u32 = @as(u32, @intCast((lParam >> 16) & 0xFFFF)); - self.size = .{ .width = width, .height = height }; + + window.width = width; + window.height = height; + + core.pushEvent(.{ .window_resize = .{ .window_id = window_id, .size = .{ .width = width, .height = height } } }); // TODO (win32): only send resize event when sizing is done. // the main mach loops does not run while resizing. // Which means if events are pushed here they will // queue up until resize is done. - self.core.swap_chain_update.set(); + window.swap_chain_update.set(); return 0; }, @@ -321,7 +437,7 @@ fn wndProc(wnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w if (vkey == w.VK_PROCESSKEY) return 0; if (msg == w.WM_SYSKEYDOWN and vkey == w.VK_F4) { - self.core.pushEvent(.close); + core.pushEvent(.close); return 0; } @@ -332,12 +448,14 @@ fn wndProc(wnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w // right alt sends left control first var next: w.MSG = undefined; const time = w.GetMessageTime(); - if (w.PeekMessageW(&next, self.window, 0, 0, w.PM_NOREMOVE) != 0 and - next.time == time and - (next.message == msg or (msg == w.WM_SYSKEYDOWN and next.message == w.WM_KEYUP)) and - ((next.lParam >> 16) & 0x1FF) == 0x138) - { - return 0; + if (window.native) |native| { + if (w.PeekMessageW(&next, native.window, 0, 0, w.PM_NOREMOVE) != 0 and + next.time == time and + (next.message == msg or (msg == w.WM_SYSKEYDOWN and next.message == w.WM_KEYUP)) and + ((next.lParam >> 16) & 0x1FF) == 0x138) + { + return 0; + } } } @@ -345,21 +463,24 @@ fn wndProc(wnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w const key = keyFromScancode(scancode); if (msg == w.WM_KEYDOWN or msg == w.WM_SYSKEYDOWN) { if (flags & w.KF_REPEAT == 0) - self.core.pushEvent(.{ + core.pushEvent(.{ .key_press = .{ + .window_id = window_id, .key = key, .mods = mods, }, }) else - self.core.pushEvent(.{ + core.pushEvent(.{ .key_repeat = .{ + .window_id = window_id, .key = key, .mods = mods, }, }); - } else self.core.pushEvent(.{ + } else core.pushEvent(.{ .key_release = .{ + .window_id = window_id, .key = key, .mods = mods, }, @@ -368,24 +489,29 @@ fn wndProc(wnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w return 0; }, w.WM_CHAR => { - const char: u16 = @truncate(wParam); - var chars: []const u16 = undefined; - if (self.surrogate != 0) { - chars = &.{ self.surrogate, char }; - self.surrogate = 0; - } else if (std.unicode.utf16IsHighSurrogate(char)) { - self.surrogate = char; + if (window.native) |native| { + const char: u16 = @truncate(wParam); + var chars: []const u16 = undefined; + if (native.surrogate != 0) { + chars = &.{ native.surrogate, char }; + native.surrogate = 0; + } else if (std.unicode.utf16IsHighSurrogate(char)) { + native.surrogate = char; + return 0; + } else { + chars = &.{char}; + } + var iter = std.unicode.Utf16LeIterator.init(chars); + if (iter.nextCodepoint()) |codepoint| { + core.pushEvent(.{ .char_input = .{ + .window_id = window_id, + .codepoint = codepoint.?, + } }); + } else |err| { + err catch {}; + } return 0; - } else { - chars = &.{char}; - } - var iter = std.unicode.Utf16LeIterator.init(chars); - if (iter.nextCodepoint()) |codepoint| { - self.core.pushEvent(.{ .char_input = .{ .codepoint = codepoint.? } }); - } else |err| { - err catch {}; } - return 0; }, w.WM_LBUTTONDOWN, w.WM_LBUTTONUP, @@ -412,15 +538,17 @@ fn wndProc(wnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w w.WM_MBUTTONDOWN, w.WM_RBUTTONDOWN, w.WM_XBUTTONDOWN, - => self.core.pushEvent(.{ + => core.pushEvent(.{ .mouse_press = .{ + .window_id = window_id, .button = button, .mods = mods, .pos = .{ .x = x, .y = y }, }, }), - else => self.core.pushEvent(.{ + else => core.pushEvent(.{ .mouse_release = .{ + .window_id = window_id, .button = button, .mods = mods, .pos = .{ .x = x, .y = y }, @@ -433,8 +561,9 @@ fn wndProc(wnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w w.WM_MOUSEMOVE => { const x: f64 = @floatFromInt(@as(i16, @truncate(lParam & 0xFFFF))); const y: f64 = @floatFromInt(@as(i16, @truncate((lParam >> 16) & 0xFFFF))); - self.core.pushEvent(.{ + core.pushEvent(.{ .mouse_motion = .{ + .window_id = window_id, .pos = .{ .x = x, .y = y, @@ -448,8 +577,9 @@ fn wndProc(wnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w const wheel_high_word: u16 = @truncate((wParam >> 16) & 0xffff); const delta_y: f32 = @as(f32, @floatFromInt(@as(i16, @bitCast(wheel_high_word)))) / WHEEL_DELTA; - self.core.pushEvent(.{ + core.pushEvent(.{ .mouse_scroll = .{ + .window_id = window_id, .xoffset = 0, .yoffset = delta_y, }, @@ -457,11 +587,11 @@ fn wndProc(wnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w return 0; }, w.WM_SETFOCUS => { - self.core.pushEvent(.{ .focus_gained = {} }); + core.pushEvent(.{ .focus_gained = .{ .window_id = window_id } }); return 0; }, w.WM_KILLFOCUS => { - self.core.pushEvent(.{ .focus_lost = {} }); + core.pushEvent(.{ .focus_lost = .{ .window_id = window_id } }); return 0; }, else => return w.DefWindowProcW(wnd, msg, wParam, lParam), diff --git a/src/module.zig b/src/module.zig index 2efd61b45d..d45fdf1a84 100644 --- a/src/module.zig +++ b/src/module.zig @@ -85,7 +85,7 @@ pub fn Objects(options: ObjectsOptions, comptime T: type) type { pub const Slice = struct { index: Index, - objs: *Objects(T), + objs: *Objects(options, T), /// Same as Objects(T).set but doesn't employ safety checks pub fn set(objs: *@This(), id: ObjectID, value: T) void { @@ -127,6 +127,7 @@ pub fn Objects(options: ObjectsOptions, comptime T: type) type { defer iter.index += 1; if (!dead.isSet(iter.index)) return @bitCast(PackedID{ + .type_id = iter.objs.internal.type_id, .generation = generation.items[iter.index], .index = iter.index, }); @@ -208,10 +209,10 @@ pub fn Objects(options: ObjectsOptions, comptime T: type) type { /// /// Unlike setAll(), this method does not respect any mach.Objects tracking /// options, so changes made to an object through this method will not be tracked. - pub fn setAllRaw(objs: *@This(), id: ObjectID, value: T) void { + pub fn setValueRaw(objs: *@This(), id: ObjectID, value: T) void { const data = &objs.internal.data; - const unpacked = objs.validateAndUnpack(id, "setAllRaw"); + const unpacked = objs.validateAndUnpack(id, "setValueRaw"); data.set(unpacked.index, value); } @@ -219,10 +220,10 @@ pub fn Objects(options: ObjectsOptions, comptime T: type) type { /// /// Unlike setAllRaw, this method respects mach.Objects tracking /// and changes made to an object through this method will be tracked. - pub fn setAll(objs: *@This(), id: ObjectID, value: T) void { + pub fn setValue(objs: *@This(), id: ObjectID, value: T) void { const data = &objs.internal.data; - const unpacked = objs.validateAndUnpack(id, "setAll"); + const unpacked = objs.validateAndUnpack(id, "setValue"); data.set(unpacked.index, value); if (objs.internal.updated) |*updated_fields| { @@ -266,7 +267,7 @@ pub fn Objects(options: ObjectsOptions, comptime T: type) type { } /// Get a single field. - pub fn get(objs: *@This(), id: ObjectID, comptime field_name: std.meta.FieldEnum(T)) ?std.meta.FieldType(T, field_name) { + pub fn get(objs: *@This(), id: ObjectID, comptime field_name: std.meta.FieldEnum(T)) std.meta.FieldType(T, field_name) { const data = &objs.internal.data; const unpacked = objs.validateAndUnpack(id, "get"); @@ -275,10 +276,10 @@ pub fn Objects(options: ObjectsOptions, comptime T: type) type { } /// Get all fields. - pub fn getAll(objs: *@This(), id: ObjectID) ?T { + pub fn getValue(objs: *@This(), id: ObjectID) T { const data = &objs.internal.data; - const unpacked = objs.validateAndUnpack(id, "getAll"); + const unpacked = objs.validateAndUnpack(id, "getValue"); return data.get(unpacked.index); }