// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie // SPDX-License-Identifier: MIT //! Where a window's finished frames go: on Wayland, dma-bufs the compositor //! reads directly where it and the GPU can share them, and shared memory //! where they cannot; on Windows, a swapchain on the window's surface. const std = @import("std"); const builtin = @import("builtin"); const Device = @import("Device.zig"); const Renderer = @import("Renderer.zig"); const platform = @import("../platform/platform.zig"); const Dmabuf = @import("wayland/Dmabuf.zig"); const Shm = @import("wayland/Shm.zig"); const Swapchain = @import("win32/Swapchain.zig"); const Atlas = @import("../text/Atlas.zig"); const testing_ = @import("../testing.zig"); const Target = @This(); const log = std.log.scoped(.present); impl: switch (builtin.os.tag) { .windows => union(enum) { swapchain: *Swapchain }, else => union(enum) { dmabuf: *Dmabuf, shm: Shm, }, }, /// How frames reach the compositor. Windows has only its swapchain, and /// takes no notice. pub const Mode = enum { /// Dma-bufs if the compositor takes one, and shared memory otherwise. auto, /// Dma-bufs, or nothing. dmabuf, /// Shared memory, always: a copy back from the GPU every frame. shm, }; /// A target for `window`, which is `width` by `height` physical pixels: /// the size a dma-buf is tried at, to see whether the compositor takes it. /// With `.auto`, a compositor or device that cannot share dma-bufs, or a /// compositor that refuses the one tried, means shared memory instead; /// with `.dmabuf`, the reason is returned. On Windows, a swapchain, and /// `mode`, `width` and `height` are not used. pub fn init(device: *Device, window: *platform.Window, width: u32, height: u32, mode: Mode) !Target { if (builtin.os.tag == .windows) return .{ .impl = .{ .swapchain = try Swapchain.create(device, window.native()) } }; if (mode != .shm) dmabuf: { const d = Dmabuf.create(device, window.native()) catch |err| { if (mode == .dmabuf) return err; log.info("presenting through shared memory: {t}", .{err}); break :dmabuf; }; d.probe(width, height) catch |err| { d.destroy(); if (mode == .dmabuf) return err; log.info("presenting through shared memory: the compositor refused a dma-buf ({t})", .{err}); break :dmabuf; }; return .{ .impl = .{ .dmabuf = d } }; } return .{ .impl = .{ .shm = try .init(device, window.native()) } }; } test init { try testing_.live(); const gpa = std.testing.allocator; var env = try std.testing.environ.createMap(gpa); defer env.deinit(); const plat = try platform.Platform.init(gpa, std.testing.io, &env, .{ .app_id = "dev.jcollie.aerie-test" }); defer plat.deinit(); const device = try Device.init(gpa, .{ .application_name = "aerie doctest", .main_device = plat.mainDevice() }); defer device.deinit(); const window = try platform.Window.create(plat, .{ .title = "Target.init", .width = 320, .height = 200, .user = null }); defer window.destroy(); const size = window.bufferSize(); var target_ = try Target.init(device, window, size.width, size.height, .auto); defer target_.deinit(); } /// Lets go of the dma-bufs, shared memory or swapchain, once the GPU is /// done with them. Before the window and the device. pub fn deinit(t: *Target) void { switch (builtin.os.tag) { .windows => t.impl.swapchain.destroy(), else => switch (t.impl) { .dmabuf => |d| d.destroy(), .shm => |*s| s.deinit(), }, } t.* = undefined; } test deinit { try testing_.live(); const gpa = std.testing.allocator; var env = try std.testing.environ.createMap(gpa); defer env.deinit(); const plat = try platform.Platform.init(gpa, std.testing.io, &env, .{ .app_id = "dev.jcollie.aerie-test" }); defer plat.deinit(); const device = try Device.init(gpa, .{ .application_name = "aerie doctest", .main_device = plat.mainDevice() }); defer device.deinit(); const window = try platform.Window.create(plat, .{ .title = "Target.deinit", .width = 320, .height = 200, .user = null }); defer window.destroy(); // Shared memory, whatever the compositor can do. var target_ = try Target.init(device, window, 320, 200, .shm); target_.deinit(); } /// What `Renderer.draw` hands the frame to. pub fn target(t: *Target) Renderer.Target { return switch (builtin.os.tag) { .windows => t.impl.swapchain.target(), else => switch (t.impl) { .dmabuf => |d| d.target(), .shm => |*s| s.target(), }, }; } test target { try testing_.live(); const gpa = std.testing.allocator; var env = try std.testing.environ.createMap(gpa); defer env.deinit(); const plat = try platform.Platform.init(gpa, std.testing.io, &env, .{ .app_id = "dev.jcollie.aerie-test" }); defer plat.deinit(); const device = try Device.init(gpa, .{ .application_name = "aerie doctest", .main_device = plat.mainDevice() }); defer device.deinit(); var renderer = try Renderer.init(device); defer renderer.deinit(); var alpha = try Atlas.init(gpa, 64, .alpha8); defer alpha.deinit(gpa); var color = try Atlas.init(gpa, 64, .rgba); defer color.deinit(gpa); const window = try platform.Window.create(plat, .{ .title = "Target.target", .width = 320, .height = 200, .user = null }); defer window.destroy(); const size = window.bufferSize(); var target_ = try Target.init(device, window, size.width, size.height, .auto); defer target_.deinit(); // One frame: a blue square on white. const square = [_]Renderer.Instance{.{ .x = 20, .y = 20, .width = 100, .height = 100, .color = .{ 0x2f, 0x6f, 0xb3, 255 }, .kind = .solid }}; try renderer.draw(size.width, size.height, .{ 1, 1, 1, 1 }, &square, &.{}, &.{}, &alpha, &color, target_.target()); }