ona/src/ona/gfx/Context.zig
kayomn 89d975f668
Some checks reported errors
continuous-integration/drone/push Build was killed
Add deferred system operations
2025-08-11 10:13:21 +01:00

251 lines
6.7 KiB
Zig

const builtin = @import("builtin");
const c = @cImport({
@cInclude("SDL3/SDL.h");
});
const coral = @import("coral");
const ona = @import("../ona.zig");
const std = @import("std");
shared: *Shared,
thread: std.Thread,
pub const Command = union(enum) {};
pub const Queue = struct {
commands: coral.stack.Sequential(Command) = .empty,
has_next: ?*Queue = null,
pub fn reset(self: *Queue) void {
self.commands.clear();
}
fn deinit(self: *Queue) void {
self.commands.deinit(coral.heap.allocator);
}
};
const Self = @This();
const Shared = struct {
device: *c.SDL_GPUDevice,
display: ona.gfx.Display,
frame_started: std.Thread.ResetEvent = .{},
frame_finished: std.Thread.ResetEvent = .{},
has_pooled_queue: std.atomic.Value(?*Queue) = .init(null),
swap_buffers: [2]Buffer = .{ .{}, .{} },
is_swapped: bool = false,
is_running: bool = true,
const Buffer = struct {
has_head_queue: ?*Queue = null,
has_tail_queue: ?*Queue = null,
pub fn dequeue(self: *Buffer) ?*Queue {
if (self.has_head_queue) |head| {
self.has_head_queue = head.has_next;
head.has_next = null;
if (self.has_head_queue == null) {
self.has_tail_queue = null;
}
return head;
}
return null;
}
pub fn enqueue(self: *Buffer, queue: *Queue) void {
if (self.has_tail_queue) |tail| {
std.debug.assert(self.has_head_queue != null);
tail.has_next = queue;
self.has_tail_queue = queue;
} else {
self.has_head_queue = queue;
self.has_tail_queue = queue;
}
queue.has_next = null;
}
};
pub fn acquireQueue(self: *Shared) error{OutOfMemory}!*Queue {
const queue = fetch: {
if (self.has_pooled_queue.load(.acquire)) |queue| {
self.has_pooled_queue.store(queue.has_next, .release);
break :fetch queue;
}
const queue = try coral.heap.allocator.create(Queue);
queue.* = .{};
break :fetch queue;
};
self.swap_buffers[@intFromBool(self.is_swapped)].enqueue(queue);
return queue;
}
fn deinit(self: *Shared, thread: std.Thread) void {
self.frame_finished.wait();
self.frame_finished.reset();
self.is_running = false;
self.frame_started.set();
thread.join();
c.SDL_DestroyGPUDevice(self.device);
while (self.has_pooled_queue.load(.acquire)) |queue| {
self.has_pooled_queue.store(queue.has_next, .release);
coral.heap.allocator.destroy(queue);
}
for (&self.swap_buffers) |*buffer| {
while (buffer.dequeue()) |queue| {
queue.deinit();
coral.heap.allocator.destroy(queue);
}
}
}
fn run(self: *Shared) !void {
const window = c.SDL_CreateWindow("Ona", 1280, 720, c.SDL_WINDOW_HIDDEN) orelse {
return error.SdlFailure;
};
defer {
c.SDL_DestroyWindow(window);
}
if (!c.SDL_ClaimWindowForGPUDevice(self.device, window)) {
return error.SdlFailure;
}
while (self.is_running) {
const window_flags = c.SDL_GetWindowFlags(window);
const is_window_hidden = (window_flags & c.SDL_WINDOW_HIDDEN) != 0;
if (is_window_hidden != self.display.is_hidden) {
const was_window_changed = switch (self.display.is_hidden) {
true => c.SDL_HideWindow(window),
false => c.SDL_ShowWindow(window),
};
if (!was_window_changed) {
std.log.warn("failed to change window visibility", .{});
}
}
const commands = c.SDL_AcquireGPUCommandBuffer(self.device) orelse {
return error.SdlFailure;
};
errdefer {
_ = c.SDL_CancelGPUCommandBuffer(commands);
}
var has_swapchain_texture: ?*c.SDL_GPUTexture = null;
var sawpchain_width, var swapchain_height = [2]u32{ 0, 0 };
if (!c.SDL_WaitAndAcquireGPUSwapchainTexture(
commands,
window,
&has_swapchain_texture,
&sawpchain_width,
&swapchain_height,
)) {
return error.SdlFailure;
}
if (has_swapchain_texture) |swapchain_texture| {
const color_targets = [_]c.SDL_GPUColorTargetInfo{
.{
.texture = swapchain_texture,
.load_op = c.SDL_GPU_LOADOP_DONT_CARE,
},
};
const render_pass = c.SDL_BeginGPURenderPass(commands, &color_targets, color_targets.len, null) orelse {
return error.SdlFailure;
};
defer {
c.SDL_EndGPURenderPass(render_pass);
}
while (self.swap_buffers[@intFromBool(!self.is_swapped)].dequeue()) |queue| {
defer {
queue.reset();
queue.has_next = self.has_pooled_queue.load(.acquire);
self.has_pooled_queue.store(queue, .release);
}
for (queue.commands.items.slice()) |_| {}
}
}
_ = c.SDL_SubmitGPUCommandBuffer(commands);
self.frame_finished.set();
self.frame_started.wait();
self.frame_started.reset();
}
}
pub fn swapBuffers(self: *Shared, display: ona.gfx.Display) void {
self.frame_finished.wait();
self.frame_finished.reset();
self.display = display;
self.is_swapped = !self.is_swapped;
self.frame_started.set();
}
};
pub fn init(display: ona.gfx.Display) !Self {
const device = c.SDL_CreateGPUDevice(c.SDL_GPU_SHADERFORMAT_SPIRV, builtin.mode == .Debug, null) orelse {
return error.SdlFailure;
};
errdefer {
c.SDL_DestroyGPUDevice(device);
}
const shared = try coral.heap.allocator.create(Shared);
errdefer {
coral.heap.allocator.destroy(shared);
}
shared.* = .{
.device = device,
.display = display,
};
shared.frame_started.set();
return .{
.thread = try .spawn(.{}, Shared.run, .{shared}),
.shared = shared,
};
}
pub fn deinit(self: *Self) void {
self.shared.deinit(self.thread);
coral.heap.allocator.destroy(self.shared);
self.* = undefined;
}