251 lines
6.7 KiB
Zig
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;
|
|
}
|