Add graphics device resources

This commit is contained in:
kayomn 2025-09-18 19:07:29 +01:00
parent 070a35f694
commit be4972e75e
33 changed files with 2112 additions and 923 deletions

1
.gitattributes vendored
View File

@ -1,3 +1,4 @@
*.bmp filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
*.spv filter=lfs diff=lfs merge=lfs -text
*.qoi filter=lfs diff=lfs merge=lfs -text

11
.vscode/launch.json vendored
View File

@ -11,6 +11,17 @@
"preLaunchTask": "Build"
},
{
"name": "Debug Qoi",
"type": "gdb",
"request": "launch",
"target": "${workspaceRoot}/qoi-master/qoiconv",
"arguments": "${workspaceRoot}/src/demos/graphics_image.qoi ${workspaceRoot}/test.qoi",
"cwd": "${workspaceRoot}/src/demos/",
"valuesFormatting": "parseText",
"preLaunchTask": "Build"
},
{
"name": "Debug Graphics Demo",
"type": "gdb",

View File

@ -1 +1 @@
0.14.0
0.15.1

View File

@ -4,6 +4,7 @@ const BuildConfig = struct {
module_target: std.Build.ResolvedTarget,
spirv_target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
sdl_dependency: *std.Build.Dependency,
fn scan_demos(self: BuildConfig, ona_module: *std.Build.Module) void {
const b = ona_module.owner;
@ -53,10 +54,9 @@ const BuildConfig = struct {
}),
});
demo_executable.linkSystemLibrary2("SDL3", .{
.needed = true,
.preferred_link_mode = .dynamic,
});
demo_executable.use_llvm = true;
demo_executable.linkLibrary(self.sdl_dependency.artifact("SDL3"));
const demo_installation = b.addInstallArtifact(demo_executable, .{});
@ -94,9 +94,21 @@ const CommonArgs = struct {
};
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{
.preferred_optimize_mode = .Debug,
});
const config = BuildConfig{
.optimize = b.standardOptimizeOption(.{}),
.module_target = b.standardTargetOptions(.{}),
.optimize = optimize,
.module_target = target,
.sdl_dependency = b.dependency("sdl", .{
.target = target,
.optimize = optimize,
.preferred_linkage = .static,
}),
.spirv_target = b.resolveTargetQuery(.{
.cpu_arch = .spirv64,
@ -109,12 +121,6 @@ pub fn build(b: *std.Build) void {
const shaderc_dependency = b.dependency("shaderc_zig", .{});
const sdl_dependency = b.dependency("sdl", .{
.target = config.module_target,
.optimize = config.optimize,
.preferred_linkage = .static,
});
const coral_module = b.addModule("coral", .{
.root_source_file = b.path("src/coral/coral.zig"),
.target = config.module_target,
@ -136,7 +142,7 @@ pub fn build(b: *std.Build) void {
});
ona_module.linkLibrary(shaderc_dependency.artifact("shaderc"));
ona_module.linkLibrary(sdl_dependency.artifact("SDL3"));
ona_module.linkLibrary(config.sdl_dependency.artifact("SDL3"));
// config.addShaders(ona_module, &.{
// "./src/ona/gfx/effect_shader.zig",

View File

@ -4,12 +4,12 @@
.dependencies = .{
.shaderc_zig = .{
.url = "git+https://github.com/tiawl/shaderc.zig#06565d2af3beec9780b11524984211ebd104fd21",
.hash = "shaderc_zig-1.0.0-mOl840tjAwBiAnMSfRskq0Iq3JJ9jPRHy2JoEgnUvSpV",
.url = "git+https://github.com/tiawl/shaderc.zig#69b67221988aa84c91447775ad6157e4e80bab00",
.hash = "shaderc_zig-1.0.0-mOl846VjAwDV8YlqQFVvFsWsBa6dLNSiskpTy7lC1hmD",
},
.sdl = .{
.url = "git+https://github.com/castholm/SDL.git#0f81c0affb2584b242b2fb5744e7dfebcfd904a5",
.hash = "sdl-0.2.6+3.2.20-7uIn9JkjfwGIQ6j3-etow2rCe-Zt16Yj-2gdp9jW7WZ9",
.url = "git+https://github.com/castholm/SDL.git#b1913e7c31ad72ecfd3ab04aeac387027754cfaf",
.hash = "sdl-0.3.0+3.2.22-7uIn9Pg3fwGG2IyIOPxxOSVe-75nUng9clt7tXGFLzMr",
},
},

View File

@ -1,103 +0,0 @@
const Queue = @import("./Tasks/Queue.zig");
const builtin = @import("builtin");
const coral = @import("./coral.zig");
const std = @import("std");
prioritised_tasks: [priorities.len]?*Queue = [_]?*Queue{null} ** priorities.len,
pub const Priority = enum {
high_priority,
low_priority,
background,
fn getThreadCount(self: Priority, cpu_count: usize) usize {
const cpu_share: f64 = @floatFromInt(cpu_count);
return @intFromFloat(switch (self) {
.high_priority => cpu_share * 0.375,
.low_priority => cpu_share * 0.25,
.background => cpu_share * 0.125,
});
}
};
const Self = @This();
pub fn create(self: *Self, allocator: std.mem.Allocator, priority: Priority, task: anytype) std.mem.Allocator.Error!*@TypeOf(task) {
const Task = @TypeOf(task);
const run_fn = coral.meta.hasFn(Task, "run") orelse {
@compileError(std.fmt.comptimePrint("{s} requires a .run fn to be a valid task type", .{@typeName(Task)}));
};
if (run_fn.return_type.? != void) {
@compileError(std.fmt.comptimePrint("{s}.run fn must return a void type", .{@typeName(Task)}));
}
if (run_fn.params.len != 1 or run_fn.params[0].type != *Task) {
@compileError(std.fmt.comptimePrint("{s}.run fn must accept a {s} as it's one and only parameter, not {s}", .{
@typeName(Task),
@typeName(*Task),
if (run_fn.params[0].type) |Type| @typeName(Type) else "anytype",
}));
}
const created_task = try allocator.create(Task);
created_task.* = task;
if (self.prioritised_tasks[@intFromEnum(priority)]) |tasks| {
tasks.enqueue(.initRef(created_task, Task.run));
} else {
created_task.run();
}
return created_task;
}
pub fn finish(self: *Self, priority: Priority) void {
if (self.prioritised_tasks[@intFromEnum(priority)]) |tasks| {
tasks.finish();
}
}
const priorities = std.enums.values(Priority);
pub fn start(self: *Self) std.Thread.SpawnError!void {
self.* = .{};
if (!builtin.single_threaded) {
if (std.Thread.getCpuCount()) |cpu_count| {
errdefer {
self.stop();
}
inline for (priorities, &self.prioritised_tasks) |priority, *has_tasks| {
const thread_count = priority.getThreadCount(cpu_count);
if (thread_count != 0) {
has_tasks.* = try Queue.spawn(thread_count);
}
}
} else |cpu_count_error| {
std.log.warn("Failed to get number of CPU cores available: {s}", .{@errorName(cpu_count_error)});
}
}
}
pub fn stop(self: *Self) void {
inline for (&self.prioritised_tasks) |*has_tasks| {
if (has_tasks.*) |tasks| {
tasks.stop();
}
has_tasks.* = null;
}
for (priorities) |priority| {
self.finish(priority);
}
}

View File

@ -13,11 +13,17 @@ pub const ReadOnlySpan = struct {
read_cursor: usize = 0,
pub fn read(self: *ReadOnlySpan, buffer: []u8) usize {
_ = self;
_ = buffer;
const bytes_read = @min(buffer.len, self.bytes.len - self.read_cursor);
// TODO: Implement.
unreachable;
@memcpy(buffer[0..bytes_read], self.bytes[self.read_cursor .. self.read_cursor + bytes_read]);
self.read_cursor += bytes_read;
return bytes_read;
}
pub fn reader(self: *ReadOnlySpan) Readable {
return .initRef(self, read);
}
};
@ -30,14 +36,6 @@ pub const ReadWriteSpan = struct {
write_cursor: usize = 0,
read_cursor: usize = 0,
pub fn read(self: *ReadWriteSpan, buffer: []u8) usize {
_ = self;
_ = buffer;
// TODO: Implement.
unreachable;
}
pub fn put(self: *ReadWriteSpan, byte: u8) bool {
if (self.write_cursor >= self.bytes.len) {
return false;
@ -49,14 +47,28 @@ pub const ReadWriteSpan = struct {
return true;
}
pub fn read(self: *ReadWriteSpan, buffer: []u8) usize {
const bytes_read = @min(buffer.len, self.bytes.len - self.read_cursor);
@memcpy(buffer[0..bytes_read], self.bytes[self.read_cursor .. self.read_cursor + bytes_read]);
self.read_cursor += bytes_read;
return bytes_read;
}
pub fn reader(self: *ReadWriteSpan) Readable {
return .initRef(self, read);
}
pub fn write(self: *ReadWriteSpan, buffer: []const u8) usize {
const written = @min(buffer.len, self.bytes.len - self.write_cursor);
const bytes_written = @min(buffer.len, self.bytes.len - self.write_cursor);
@memcpy(self.bytes[self.write_cursor .. self.write_cursor + written], buffer[0..written]);
@memcpy(self.bytes[self.write_cursor .. self.write_cursor + bytes_written], buffer[0..bytes_written]);
self.write_cursor += written;
self.write_cursor += bytes_written;
return written;
return bytes_written;
}
pub fn writer(self: *ReadWriteSpan) Writable {
@ -128,7 +140,7 @@ pub fn printFormatted(buffer: [:0]u8, comptime format: []const u8, args: anytype
return error.BufferOverflow;
}
var buffer_span = span(buffer);
var buffer_span = spanOf(buffer);
writeFormatted(buffer_span.writer(), format, args) catch unreachable;
@ -139,40 +151,56 @@ pub fn printFormatted(buffer: [:0]u8, comptime format: []const u8, args: anytype
return buffer[0..len :0];
}
pub fn readBig(input: Readable, comptime T: type) ReadWriteError!T {
var buffer: [@sizeOf(T)]u8 align(@alignOf(T)) = undefined;
if (input.call(.{&buffer}) != buffer.len) {
pub fn readAll(input: Readable, buffer: []u8) ReadWriteError!void {
if (input.call(.{buffer}) != buffer.len) {
return error.IncompleteRead;
}
switch (builtin.cpu.arch.endian()) {
.big => {},
.little => {
std.mem.byteSwapAllFields(T, @ptrCast(&buffer));
},
}
return std.mem.bytesToValue(T, &buffer);
}
pub fn readLittle(input: Readable, comptime T: type) ReadWriteError!T {
var buffer: [@sizeOf(T)]u8 = undefined;
pub fn readBig(input: Readable, comptime Value: type) ReadWriteError!Value {
var buffer: [@sizeOf(Value)]u8 align(@alignOf(Value)) = undefined;
if (input.call(.{&buffer}) != buffer.len) {
try readAll(input, &buffer);
if (@sizeOf(Value) > 1) {
switch (builtin.cpu.arch.endian()) {
.little => {
std.mem.reverse(u8, &buffer);
},
.big => {},
}
}
return std.mem.bytesToValue(Value, &buffer);
}
pub fn readByte(input: Readable) ReadWriteError!u8 {
var byte: u8 = undefined;
if (input.call(.{std.mem.asBytes(&byte)}) != 1) {
return error.IncompleteRead;
}
switch (builtin.cpu.arch.endian()) {
.big => {
std.mem.byteSwapAllFields(T, &buffer);
},
return byte;
}
.little => {},
pub fn readLittle(input: Readable, comptime Value: type) ReadWriteError!Value {
var buffer: [@sizeOf(Value)]u8 align(@alignOf(Value)) = undefined;
try readAll(input, &buffer);
if (@sizeOf(Value) > 1) {
switch (builtin.cpu.arch.endian()) {
.little => {},
.big => {
std.mem.reverse(u8, &buffer);
},
}
}
return std.mem.bytesToValue(T, &buffer);
return std.mem.bytesToValue(Value, &buffer);
}
pub const readNative = switch (builtin.cpu.arch.endian()) {
@ -180,7 +208,7 @@ pub const readNative = switch (builtin.cpu.arch.endian()) {
.big => readBig,
};
pub fn span(ptr: anytype) Span(@TypeOf(ptr)) {
pub fn spanOf(ptr: anytype) Span(@TypeOf(ptr)) {
return .{
.bytes = switch (@typeInfo(@TypeOf(ptr)).pointer.size) {
.slice => std.mem.sliceAsBytes(ptr),
@ -250,11 +278,21 @@ pub fn writeAll(output: Writable, data: []const u8) ReadWriteError!void {
}
}
pub fn writeN(output: Writable, data: []const u8, count: usize) ReadWriteError!void {
var remaining = count;
pub fn writeBig(output: Writable, value: anytype) ReadWriteError!void {
switch (builtin.cpu.arch.endian()) {
.little => {
var buffer = std.mem.toBytes(value);
while (remaining != 0) : (remaining -= 1) {
try writeAll(output, data);
if (@sizeOf(@TypeOf(value)) > 1) {
std.mem.reverse(u8, &buffer);
}
try writeAll(output, &buffer);
},
.big => {
try writeAll(output, std.mem.asBytes(&value));
},
}
}
@ -376,33 +414,30 @@ pub fn writeLittle(output: Writable, value: anytype) ReadWriteError!void {
},
.big => {
const Value = @TypeOf(value);
var buffer = std.mem.toBytes(value);
switch (@typeInfo(Value)) {
.@"struct", .array => {
var copy = value;
std.mem.byteSwapAllFields(Value, &copy);
try writeAll(output, std.mem.asBytes(&copy));
},
.int, .float, .bool => {
try writeAll(output, std.mem.asBytes(&@byteSwap(value)));
},
.@"enum" => {
try writeLittle(output, @intFromEnum(value));
},
else => {
@compileError(std.fmt.comptimePrint("{s} is not byte-swappable", .{@typeName(Value)}));
},
if (@sizeOf(@TypeOf(value)) > 1) {
std.mem.reverse(u8, &buffer);
}
try writeAll(output, &buffer);
},
}
}
pub fn writeN(output: Writable, data: []const u8, count: usize) ReadWriteError!void {
var remaining = count;
while (remaining != 0) : (remaining -= 1) {
try writeAll(output, data);
}
}
pub const writeNative = switch (builtin.cpu.arch.endian()) {
.little => writeLittle,
.big => writeBig,
};
fn writeNull(buffer: []const u8) usize {
return buffer.len;
}

View File

@ -1,7 +1,5 @@
pub const Box = @import("./Box.zig");
const Tasks = @import("./Tasks.zig");
pub const TypeId = @import("./TypeId.zig");
pub const bytes = @import("./bytes.zig");
@ -24,49 +22,6 @@ pub const tree = @import("./tree.zig");
pub const utf8 = @import("./utf8.zig");
pub fn CallTask(comptime function: anytype) type {
const Function = @TypeOf(function);
const function_fn = switch (@typeInfo(Function)) {
.@"fn" => |@"fn"| @"fn",
else => @compileError("expeceted param `function` to be an fn type"),
};
return struct {
args: std.meta.ArgsTuple(Function),
payload: Return = undefined,
resolved: std.Thread.ResetEvent = .{},
const Return = function_fn.return_type.?;
const Self = @This();
pub fn get(self: *const Self) Return {
std.debug.assert(self.resolved.isSet());
self.resolved.wait();
self.resolved.reset();
return self.payload;
}
pub fn resolve(self: *Self, payload: Return) bool {
if (self.resolved.isSet()) {
return false;
}
self.payload = payload;
self.resolved.set();
return true;
}
pub fn run(self: *Self) void {
std.debug.assert(self.resolve(@call(.auto, function, self.args)));
}
};
}
pub fn Callable(comptime Output: type, comptime input_types: []const type) type {
const InputTuple = std.meta.Tuple(input_types);
@ -178,5 +133,3 @@ pub fn expect(function: anytype, args: std.meta.ArgsTuple(@TypeOf(function))) me
else => @call(.auto, function, args),
};
}
pub var tasks = Tasks{};

View File

@ -2,74 +2,19 @@ const builtin = @import("builtin");
const std = @import("std");
pub fn RefCounting(comptime Payload: type, comptime finalize: fn (*Payload) void) type {
const AtomicCount = std.atomic.Value(usize);
return opaque {
const Layout = struct {
ref_count: AtomicCount,
payload: Payload,
fn get(self: *Self) *Layout {
return @ptrCast(@alignCast(self));
}
fn get_const(self: *const Self) *const Layout {
return @ptrCast(@alignCast(self));
}
};
const Self = @This();
pub fn acquire(self: *Self) *const Payload {
const layout = Layout.get(self);
const ref_count = layout.ref_count.fetchAdd(1, .monotonic);
std.debug.assert(ref_count != 0);
return &layout.payload;
}
pub fn create(payload: Payload) std.mem.Allocator.Error!*Self {
const allocation = try allocator.create(Layout);
errdefer {
allocator.destroy(allocation);
}
allocation.* = .{
.ref_count = AtomicCount.init(1),
.payload = payload,
};
return @ptrCast(allocation);
}
pub fn release(self: *Self) void {
const layout = Layout.get(self);
const ref_count = layout.ref_count.fetchSub(1, .monotonic);
std.debug.assert(ref_count != 0);
if (ref_count == 1) {
finalize(&layout.payload);
allocator.destroy(layout);
}
}
pub fn weak(self: *const Self) *const Payload {
return &Layout.get_const(self).payload;
}
};
}
pub const allocator = switch (builtin.mode) {
.ReleaseFast => std.heap.smp_allocator,
else => gpa.allocator(),
.ReleaseFast, .ReleaseSmall => std.heap.smp_allocator,
else => debug_allocator.allocator(),
};
var gpa = std.heap.GeneralPurposeAllocator(.{ .thread_safe = true }){};
var debug_allocator = switch (builtin.mode) {
.ReleaseFast, .ReleaseSmall => {},
else => std.heap.DebugAllocator(.{}){},
};
pub fn traceLeaks() void {
_ = gpa.detectLeaks();
switch (builtin.mode) {
.ReleaseFast, .ReleaseSmall => {},
else => _ = debug_allocator.detectLeaks(),
}
}

View File

@ -22,13 +22,6 @@ pub fn UnwrappedOptional(comptime Value: type) type {
};
}
pub fn isContainer(@"type": std.builtin.Type) bool {
return switch (@"type") {
.@"struct", .@"union", .@"enum", .@"opaque" => true,
else => false,
};
}
pub fn hasFn(comptime Type: type, comptime fn_name: []const u8) ?std.builtin.Type.Fn {
const @"type" = @typeInfo(Type);
@ -47,3 +40,20 @@ pub fn hasFn(comptime Type: type, comptime fn_name: []const u8) ?std.builtin.Typ
else => null,
};
}
pub fn isContainer(@"type": std.builtin.Type) bool {
return switch (@"type") {
.@"struct", .@"union", .@"enum", .@"opaque" => true,
else => false,
};
}
pub fn parametersMatch(parameters: []const std.builtin.Type.Fn.Param, types: []const type) bool {
for (parameters, types) |parameter, Type| {
if (parameter.type != Type) {
return false;
}
}
return true;
}

View File

@ -118,11 +118,11 @@ pub fn Parallel(comptime Item: type) type {
else => @compileError("`Item` must be a struct type"),
};
const item_align = @alignOf(Item);
const item_align = std.mem.Alignment.of(Item);
const item_size = @sizeOf(Item);
return Generic(Item, struct {
ptr: [*]align(item_align) u8 = undefined,
ptr: [*]align(item_align.toByteUnits()) u8 = undefined,
len: u32 = 0,
cap: u32 = 0,

View File

@ -66,67 +66,6 @@ pub fn Binary(comptime Key: type, comptime Value: type, comptime traits: Traits(
return node;
}
fn remove(self: *Node, key: Key) ?*Node {
const node = self.find(key) orelse {
return null;
};
if (node.has_lesser == null) {
std.debug.assert(self.transplant(node.has_greater));
} else if (node.has_greater == null) {
std.debug.assert(self.transplant(node.has_lesser));
} else {
var successor = node.has_greater.?.getMin();
if (successor.has_parent != node) {
// Move successor up: replace successor with its right child first
std.debug.assert(successor.transplant(successor.has_greater));
// Attach node.right to successor
successor.has_greater = node.has_greater;
if (successor.has_greater) |g| {
g.has_parent = successor;
}
}
// Replace node with successor
std.debug.assert(node.transplant(successor));
// Attach node.left to successor
successor.has_lesser = node.has_lesser;
if (successor.has_lesser) |l| {
l.has_parent = successor;
}
}
// Detach the removed node completely and return it
node.has_parent = null;
node.has_lesser = null;
node.has_greater = null;
return node;
}
fn transplant(self: *Node, has_node: ?*Node) bool {
const parent = self.has_parent orelse {
return false;
};
if (parent.has_lesser == self) {
parent.has_lesser = has_node;
} else {
parent.has_greater = has_node;
}
if (has_node) |node| {
node.has_parent = self.has_parent;
}
return true;
}
};
pub const KeyValues = struct {
@ -330,10 +269,45 @@ pub fn Binary(comptime Key: type, comptime Value: type, comptime traits: Traits(
return null;
};
const node = root.remove(key) orelse {
const node = root.find(key) orelse {
return null;
};
if (node.has_lesser == null) {
self.transplant(node, node.has_greater);
} else if (node.has_greater == null) {
self.transplant(node, node.has_lesser);
} else {
// Successor = min(node.right)
var successor = node.has_greater.?.getMin();
if (successor.has_parent != node) {
// move successor up (replace successor with its right child)
self.transplant(successor, successor.has_greater);
// attach node.right to successor
successor.has_greater = node.has_greater;
if (successor.has_greater) |g| {
g.has_parent = successor;
}
}
// replace node with successor
self.transplant(node, successor);
// attach node.left to successor
successor.has_lesser = node.has_lesser;
if (successor.has_lesser) |l| {
l.has_parent = successor;
}
}
node.has_parent = null;
node.has_lesser = null;
node.has_greater = null;
defer {
self.free_nodes.destroy(node);
}
@ -343,6 +317,25 @@ pub fn Binary(comptime Key: type, comptime Value: type, comptime traits: Traits(
.value = node.value,
};
}
fn transplant(self: *Self, u: *Node, has_v: ?*Node) void {
const u_parent = u.has_parent orelse {
// U is root.
self.has_root = has_v;
return;
};
if (u_parent.has_lesser == u) {
u_parent.has_lesser = has_v;
} else {
u_parent.has_greater = has_v;
}
if (has_v) |v| {
v.has_parent = u.has_parent;
}
}
};
}

22
src/demos/crt.frag Normal file
View File

@ -0,0 +1,22 @@
vec2 crt(vec2 coords, float bend) {
vec2 symmetrical_coords = (coords - 0.5) * 2.0;
vec2 deformed_coords = symmetrical_coords * (symmetrical_coords + vec2(
pow((abs(coords.y) / bend), 2.0),
pow((abs(coords.x) / bend), 2.0)
));
return (deformed_coords / 2.0) + 0.5;
}
void main() {
const vec2 crt_coords = crt(vertex_uv, 4.8);
const float scanlineCount = 480.0;
const float scan = sin(crt_coords.y * scanlineCount + effect.time * 29.0);
const vec3 image_color = texture(image, crt_coords).rgb;
const vec3 shaded = image_color - vec3(scan * 0.02);
color = vec4(image_color, 1.0);
}

View File

@ -1,20 +0,0 @@
function crt(coords float2, bend float) -> float2 {
let symmetrical_coords = (coords - 0.5) * 2.0
let deformed_coords = symmetrical_coords * (symmetrical_coords + float2(
pow((abs(coords.y) / bend), 2.0),
pow((abs(coords.x) / bend), 2.0)
))
return (deformed_coords / 2.0) + 0.5
}
function frag(color float4, uv float2) -> float4 {
let crt_coords = crt(uv, 4.8)
let screenspace = crt_coords * float2(effect.screen_width, effect.screen_height)
let color = sample(albedo, crt_coords).rgb
let scanline = float4((color - sin((screenspace.y + (effect.time * 29.0))) * 0.02), 1.0)
return scanline
}

View File

@ -9,65 +9,90 @@ const CRT = extern struct {
padding: [4]u8 = undefined,
};
const Effects = struct {
render_texture: ona.AssetHandle = .default,
image_textures: [2]ona.AssetHandle = [_]ona.AssetHandle{.default} ** 2,
const State = struct {
images: [2]ona.gfx.Images.Handle = [_]ona.gfx.Images.Handle{.default} ** 2,
last_time: f64 = 0,
image_index: usize = 0,
crt_effect: ona.AssetHandle = .default,
crt_effect: ona.gfx.Effects.Handle = .default,
loaded_image: ona.gfx.Images.Handle = .default,
};
fn load(display: ona.Write(ona.gfx.Display), state: ona.Write(State), images: ona.gfx.Images, effects: ona.gfx.Effects) !void {
display.ptr.size = .{ 1280, 720 };
state.ptr.loaded_image = try images.load(ona.gfx.QoiImage{
.path = try .parse("graphics_image.qoi"),
});
errdefer {
images.unload(state.ptr.images[1]);
}
state.ptr.crt_effect = try effects.load(ona.gfx.GlslEffect{
.fragment = .{
.glsl = @embedFile("crt.frag"),
.name = "crt.frag",
},
});
errdefer {
effects.unload(state.ptr.crt_effect);
}
state.ptr.images[0] = try images.load(ona.gfx.CheckerImage{
.colors = .{ .black, .purple },
.square_size = 4,
.width = 8,
.height = 8,
});
errdefer {
images.unload(state.ptr.images[0]);
}
state.ptr.images[1] = try images.load(ona.gfx.CheckerImage{
.colors = .{ .black, .grey },
.square_size = 4,
.width = 8,
.height = 8,
});
errdefer {
images.unload(state.ptr.images[1]);
}
}
pub fn main() void {
ona.realtime_app
.with(.initModule(ona.hid))
.with(.initModule(ona.gfx))
// .with(.initSystem(.render, .of(render)))
.with(.initState(State{}))
.with(.initSystem(.load, .of(load)))
.with(.initSystem(.render, .of(render)))
.with(.initSystem(.update, .of(update)))
.run();
}
// fn update(effects: ona.Write(Effects), loop: ona.Read(ona.Loop)) void {
// const update_seconds = 5;
fn render(scene: ona.gfx.Scene, state: ona.Read(State), display: ona.Write(ona.gfx.Display), time: ona.Read(ona.App.Time)) void {
const width, const height = display.ptr.size;
// if ((loop.state.elapsed_time - effects.state.last_time) > update_seconds) {
// effects.state.image_index = (effects.state.image_index + 1) % effects.state.image_textures.len;
// effects.state.last_time = loop.state.elapsed_time;
// }
// }
scene.updateEffect(state.ptr.crt_effect, CRT{
.width = @floatFromInt(width),
.height = @floatFromInt(height),
.time = @floatCast(time.ptr.elapsed),
});
// fn render(commands: ona.gfx.Commands) void {
// try commands.setTarget(.{
// .texture = effects.state.render_texture,
// .clear_color = gfx.colors.black,
// .clear_depth = 0,
// .clear_stencil = 0,
// });
scene.drawSprite(state.ptr.images[state.ptr.image_index], .{
.size = .{ @floatFromInt(width), @floatFromInt(height) },
.effect = state.ptr.crt_effect,
});
}
// const width: f32 = @floatFromInt(display.state.width);
// const height: f32 = @floatFromInt(display.state.height);
fn update(state: ona.Write(State), time: ona.Read(ona.App.Time)) void {
const update_seconds = 5;
// try commands.draw_texture(.{
// .texture = effects.state.image_textures[effects.state.image_index],
// .size = .{ width, height },
// });
// try commands.set_effect(.{
// .effect = effects.state.crt_effect,
// .properties = std.mem.asBytes(&CRT{
// .width = width,
// .height = height,
// .time = @floatCast(loop.state.elapsed_time),
// }),
// });
// try commands.set_target(.{
// .clear_color = null,
// .clear_depth = null,
// .clear_stencil = null,
// });
// try commands.draw_texture(.{
// .texture = effects.state.render_texture,
// .size = .{ width, height },
// });
// }
if ((time.ptr.elapsed - state.ptr.last_time) > update_seconds) {
state.ptr.image_index = (state.ptr.image_index + 1) % state.ptr.images.len;
state.ptr.last_time = time.ptr.elapsed;
}
}

Binary file not shown.

View File

@ -1,23 +1,139 @@
pub const Behavior = @import("./App/Behavior.zig");
const SystemGraph = @import("./SystemGraph.zig");
pub const Setup = @import("./App/Setup.zig");
const builtin = @import("builtin");
const SystemGraph = @import("./App/SystemGraph.zig");
const c = @cImport({
@cInclude("SDL3/SDL.h");
});
const coral = @import("coral");
const ona = @import("./ona.zig");
const ona = @import("ona.zig");
const std = @import("std");
initialized_states: coral.map.Hashed(*const coral.TypeId, coral.Box, coral.map.scalarTraits(*const coral.TypeId)),
named_systems: coral.tree.Binary([]const u8, SystemGraph, coral.tree.sliceTraits([]const u8)),
pub const Data = struct {
opened_storages: [32]*c.SDL_Storage = undefined,
storages_opened: usize = 0,
pub const AccessError = error{SystemResources};
pub fn deinit(self: *Data) void {
for (self.opened_storages[0..self.storages_opened]) |storage| {
_ = c.SDL_CloseStorage(storage);
}
self.* = undefined;
}
pub fn loadFile(self: Data, path: Path, allocator: std.mem.Allocator) (AccessError || std.mem.Allocator.Error)![]u8 {
for (self.opened_storages[0..self.storages_opened]) |storage| {
const path_ptr = path.ptr();
var file_size: u64 = 0;
if (c.SDL_GetStorageFileSize(storage, path_ptr, &file_size)) {
const file_data = try allocator.alloc(u8, file_size);
errdefer {
allocator.free(file_data);
}
if (c.SDL_ReadStorageFile(storage, path_ptr, @ptrCast(file_data.ptr), file_data.len)) {
return file_data;
}
}
}
return error.SystemResources;
}
pub fn openNative(self: *Data, native_path: [:0]const u8) error{SystemResources}!void {
if (self.storages_opened >= self.opened_storages.len) {
return error.SystemResources;
}
const storage = &self.opened_storages[self.storages_opened];
storage.* = c.SDL_OpenTitleStorage(native_path, 0) orelse {
return error.SystemResources;
};
self.storages_opened += 1;
while (!c.SDL_StorageReady(storage.*)) {
c.SDL_Delay(1);
}
}
pub fn readFile(self: Data, path: Path, output: []u8) AccessError!void {
for (self.storages[0..self.storages_opened]) |storage| {
if (c.SDL_ReadStorageFile(storage, path.ptr(), @ptrCast(output.ptr), output.len)) {
return;
}
}
return error.SystemResources;
}
pub fn sizeFile(self: Data, path: Path) AccessError!usize {
var file_size: u64 = 0;
for (self.storages[0..self.storages_opened]) |storage| {
if (c.SDL_GetStorageFileSize(storage, path.ptr(), &file_size)) {
return file_size;
}
}
return error.SystemResources;
}
};
pub const Exit = union(enum) {
success,
failure: anyerror,
};
pub const Path = struct {
buffer: [max]u8,
unused: u8,
pub const ParseError = error{
BadPath,
};
pub const empty = Path{
.buffer = std.mem.zeroes([max]u8),
.unused = max,
};
const max = std.math.maxInt(u8);
pub fn parse(data: []const u8) ParseError!Path {
if (data.len == 0) {
return error.BadPath;
}
if (data.len > max) {
return error.BadPath;
}
var path = empty;
path.unused -= @intCast(data.len);
@memcpy(path.buffer[0..data.len], data);
return path;
}
pub fn ptr(self: *const Path) [*:0]const u8 {
return @ptrCast(&self.buffer);
}
};
pub const RunError = std.mem.Allocator.Error || error{
MissingDependency,
};
@ -25,7 +141,7 @@ pub const RunError = std.mem.Allocator.Error || error{
const Self = @This();
pub const Time = struct {
elapsed: f64,
elapsed: f64 = 0,
};
pub fn deinit(self: *Self) void {
@ -57,7 +173,7 @@ fn scheduleName(comptime schedule: anytype) [:0]const u8 {
};
}
pub fn hasState(self: *const Self, comptime State: type) ?*State {
pub fn getState(self: *const Self, comptime State: type) ?*State {
if (self.initialized_states.get(.of(State))) |boxed_state| {
return boxed_state.has(State).?;
}
@ -65,22 +181,34 @@ pub fn hasState(self: *const Self, comptime State: type) ?*State {
return null;
}
pub fn init() error{OutOfMemory}!Self {
pub fn init() error{ OutOfMemory, SystemResources }!Self {
var self = Self{
.initialized_states = .empty,
.named_systems = .empty,
};
try self.setState(Time{
.elapsed = 0,
});
try self.setState(Data{});
try self.setState(Time{});
try ona.registerChannel(&self, Exit);
const data = self.getState(Data).?;
try data.openNative(std.mem.span(c.SDL_GetBasePath()));
if (builtin.mode == .Debug) {
const current_directory = c.SDL_GetCurrentDirectory();
defer {
c.SDL_free(current_directory);
}
try data.openNative(std.mem.span(current_directory));
}
return self;
}
pub fn on(self: *Self, comptime schedule: anytype, behavior: *const Behavior) error{OutOfMemory}!void {
pub fn on(self: *Self, comptime schedule: anytype, behavior: *const ona.System) error{OutOfMemory}!void {
const schedule_name = scheduleName(schedule);
const systems = self.named_systems.get(schedule_name) orelse (try self.named_systems.insert(coral.heap.allocator, schedule_name, .{})).?;

View File

@ -1,6 +1,6 @@
const coral = @import("coral");
const ona = @import("../ona.zig");
const ona = @import("ona.zig");
const std = @import("std");
@ -52,7 +52,17 @@ pub fn initModule(comptime namespace: anytype) Self {
return init(namespace.setup);
}
pub fn initSystem(comptime schedule: anytype, comptime behavior: *const ona.App.Behavior) Self {
pub fn initState(comptime state: anytype) Self {
const state_initialization = struct {
fn apply(app: *ona.App) !void {
try app.setState(state);
}
};
return init(state_initialization.apply);
}
pub fn initSystem(comptime schedule: anytype, comptime behavior: *const ona.System) Self {
const system_initialization = struct {
fn apply(app: *ona.App) !void {
try app.on(schedule, behavior);

View File

@ -1,8 +1,8 @@
const SystemGraph = @import("./SystemGraph.zig");
const SystemGraph = @import("SystemGraph.zig");
const coral = @import("coral");
const ona = @import("../ona.zig");
const ona = @import("ona.zig");
const std = @import("std");
@ -143,8 +143,6 @@ pub fn of(comptime function: anytype) *const Self {
};
}
// TODO: Errdefer deinit the param states.
return .init(coral.heap.allocator, param_states);
}
@ -182,9 +180,10 @@ pub fn of(comptime function: anytype) *const Self {
},
.pointer => |pointer| {
init_args[arg_index] = app.hasState(pointer.child) orelse {
@panic(std.fmt.comptimePrint("{s} is a required state but not present in the App", .{
init_args[arg_index] = app.getState(pointer.child) orelse {
@panic(std.fmt.comptimePrint("{s} is a required state but not present in the App. {s}", .{
@typeName(pointer.child),
"Did you mean to add it with App.putState?",
}));
};
},

View File

@ -1,6 +1,6 @@
const coral = @import("coral");
const ona = @import("../ona.zig");
const ona = @import("ona.zig");
const std = @import("std");
@ -15,7 +15,7 @@ run_arena: std.heap.ArenaAllocator = .init(coral.heap.allocator),
const AccessMap = coral.tree.Binary(*const coral.TypeId, BehaviorSet, coral.tree.scalarTraits(*const coral.TypeId));
const BehaviorSet = coral.stack.Sequential(*const ona.App.Behavior);
const BehaviorSet = coral.stack.Sequential(*const ona.System);
const Edge = struct {
dependencies: BehaviorSet = .empty,
@ -30,7 +30,7 @@ const Edge = struct {
};
fn Map(comptime Payload: type) type {
return coral.tree.Binary(*const ona.App.Behavior, Payload, coral.tree.scalarTraits(*const ona.App.Behavior));
return coral.tree.Binary(*const ona.System, Payload, coral.tree.scalarTraits(*const ona.System));
}
const Processed = struct {
@ -38,7 +38,7 @@ const Processed = struct {
};
const Work = struct {
behavior: *const ona.App.Behavior,
behavior: *const ona.System,
local_state: coral.Box,
};
@ -55,6 +55,16 @@ pub const TypeDependency = struct {
is_read_only: bool,
};
pub fn applyDeferred(self: *Self, app: *ona.App) void {
for (self.parallel_work.items.slice()) |work| {
work.behavior.apply(work.local_state, app);
}
for (self.blocking_work.items.slice()) |work| {
work.behavior.apply(work.local_state, app);
}
}
pub fn deinit(self: *Self) void {
const allocator = coral.heap.allocator;
@ -75,20 +85,20 @@ pub fn deinit(self: *Self) void {
}
}
pub fn dependOnBehavior(self: *Self, app: *ona.App, dependant: *const ona.App.Behavior, dependency: *const ona.App.Behavior) std.mem.Allocator.Error!void {
pub fn dependOnBehavior(self: *Self, app: *ona.App, dependant: *const ona.System, dependency: *const ona.System) std.mem.Allocator.Error!void {
try self.insert(app, dependant);
const edge = self.edges.get(dependant).?;
if (std.mem.indexOfScalar(*const ona.App.Behavior, edge.dependencies.items.slice(), dependency) == null) {
if (std.mem.indexOfScalar(*const ona.System, edge.dependencies.items.slice(), dependency) == null) {
try edge.dependencies.pushGrow(coral.heap.allocator, dependency);
}
}
pub fn dependOnType(self: *Self, app: *ona.App, dependant: *const ona.App.Behavior, dependency: TypeDependency) std.mem.Allocator.Error!void {
pub fn dependOnType(self: *Self, app: *ona.App, dependant: *const ona.System, dependency: TypeDependency) std.mem.Allocator.Error!void {
const readers = self.state_readers.get(dependency.id) orelse (try self.state_readers.insert(coral.heap.allocator, dependency.id, .empty)).?;
if (std.mem.indexOfScalar(*const ona.App.Behavior, readers.items.slice(), dependant)) |index| {
if (std.mem.indexOfScalar(*const ona.System, readers.items.slice(), dependant)) |index| {
for (readers.items.slice()[0..index]) |reader| {
try self.dependOnBehavior(app, dependant, reader);
}
@ -107,7 +117,7 @@ pub fn dependOnType(self: *Self, app: *ona.App, dependant: *const ona.App.Behavi
if (!dependency.is_read_only) {
const writers = self.state_writers.get(dependency.id) orelse (try self.state_writers.insert(coral.heap.allocator, dependency.id, .empty)).?;
if (std.mem.indexOfScalar(*const ona.App.Behavior, writers.items.slice(), dependant)) |index| {
if (std.mem.indexOfScalar(*const ona.System, writers.items.slice(), dependant)) |index| {
for (writers.items.slice()[0..index]) |reader| {
try self.dependOnBehavior(app, dependant, reader);
}
@ -125,17 +135,7 @@ pub fn dependOnType(self: *Self, app: *ona.App, dependant: *const ona.App.Behavi
}
}
pub fn applyDeferred(self: *Self, app: *ona.App) void {
for (self.parallel_work.items.slice()) |work| {
work.behavior.apply(work.local_state, app);
}
for (self.blocking_work.items.slice()) |work| {
work.behavior.apply(work.local_state, app);
}
}
pub fn insert(self: *Self, app: *ona.App, behavior: *const ona.App.Behavior) std.mem.Allocator.Error!void {
pub fn insert(self: *Self, app: *ona.App, behavior: *const ona.System) std.mem.Allocator.Error!void {
self.processed.clear();
// TODO: Refactor so partial initialisation isn't happening here anymore.
@ -151,7 +151,7 @@ pub fn insert(self: *Self, app: *ona.App, behavior: *const ona.App.Behavior) std
}
}
fn process(self: *Self, behavior: *const ona.App.Behavior, edge: Edge) !Processed {
fn process(self: *Self, behavior: *const ona.System, edge: Edge) !Processed {
var processed = Processed{ .is_blocking = behavior.is_blocking };
for (edge.dependencies.items.slice()) |dependency| {
@ -218,7 +218,7 @@ pub fn run(self: *Self, app: *const ona.App) RunError!void {
var parallel_work = self.parallel_work.items.slice();
for (self.parallel_work_ranges.items.slice()) |parallel_work_range| {
_ = try coral.tasks.create(run_allocator, .high_priority, coral.CallTask(runWorkGroup){
_ = try ona.tasks.create(run_allocator, .high_priority, ona.tasks.CallTask(runWorkGroup){
.args = .{ app, parallel_work[0..parallel_work_range] },
});
@ -226,7 +226,7 @@ pub fn run(self: *Self, app: *const ona.App) RunError!void {
}
}
coral.tasks.finish(.high_priority);
ona.tasks.finish(.high_priority);
runWorkGroup(app, self.blocking_work.items.slice());
}

View File

@ -1,84 +1,291 @@
const ona = @import("ona.zig");
const coral = @import("coral");
const std = @import("std");
pub const Handle = packed struct {
index: u24,
salt: u20,
type_id: u20,
pub const GenericHandle = struct {
type_id: *const coral.TypeId,
index: u32,
salt: u32,
pub fn reify(self: GenericHandle, comptime Asset: type) ?Handle(Asset) {
if (self.type_id != coral.TypeId.of(Asset)) {
return null;
}
return .{
.index = self.index,
.salt = self.salt,
};
}
};
pub const Path = struct {
buffer: [256]u8,
};
pub fn Handle(comptime Asset: type) type {
return packed struct(u64) {
index: u32,
salt: u32,
pub fn Store(comptime Asset: type) type {
const Self = @This();
pub const asset_type_id = coral.TypeId.of(Asset);
pub const default: Self = @bitCast(@as(u64, 0));
pub fn generic(self: Self) GenericHandle {
return .{
.type_id = asset_type_id,
.index = self.index,
.salt = self.salt,
};
}
};
}
pub fn ResolveQueue(comptime Asset: type) type {
return struct {
released_handles: coral.stack.Sequential(Handle) = .empty,
arena: std.heap.ArenaAllocator = .init(coral.heap.allocator),
assets: coral.stack.Parallel(struct {
asset: Asset,
state: AssetState,
tasks: coral.stack.Sequential(struct {
future: *ona.tasks.Future(?Asset),
handle: AssetHandle,
}) = .empty,
const AssetState = struct {
usage: enum { vacant, reserved, occupied },
salt: u20,
};
const AssetHandle = Handle(Asset);
pub fn reserve(self: *Store) std.mem.Allocator.Error!Handle {
if (self.released_handles.pop()) |handle| {
const state = &self.assets.items.slice(.state)[handle.index];
fn ResolveTask(comptime Loadable: type) type {
const load_fn_name = "load";
state.usage = .reserved;
const load_fn = coral.meta.hasFn(Loadable, load_fn_name) orelse {
@compileError(std.fmt.comptimePrint("{s} must contain a .{s} fn", .{
@typeName(Loadable),
load_fn_name,
}));
};
return handle;
const all_args: [2]type = .{ Loadable, ona.App.Data };
const loadable_type_name = @typeName(Loadable);
if (load_fn.params.len > all_args.len or !coral.meta.parametersMatch(load_fn.params, all_args[0..load_fn.params.len])) {
@compileError(std.fmt.comptimePrint("Fn {s}.{s} must accept only {s} or ({s}, {s}) to be a valid loadable type", .{
loadable_type_name,
load_fn_name,
loadable_type_name,
loadable_type_name,
@typeName(ona.App.Data),
}));
}
const handle = Handle{
.type_id = type_id,
.index = self.assets.items.len,
const Result = coral.meta.UnwrappedError(load_fn.return_type.?);
if (Result != Asset) {
const result_type_name = @typeName(Result);
@compileError(std.fmt.comptimePrint("Fn {s}.{s} must return {s} or !{s}, not {s}", .{
loadable_type_name,
load_fn_name,
result_type_name,
result_type_name,
@typeName(load_fn.return_type.?),
}));
}
return struct {
loadable: Loadable,
future: ona.tasks.Future(?Result) = .unresolved,
app_data: ona.App.Data,
pub fn run(self: *@This()) void {
const loaded = switch (load_fn.params.len) {
1 => self.loadable.load(),
2 => self.loadable.load(self.app_data),
else => unreachable,
};
std.debug.assert(self.future.resolve(loaded catch |load_error| nullify: {
std.log.err("Failed to resolve {s}: {s}", .{
loadable_type_name,
@errorName(load_error),
});
break :nullify null;
}));
}
};
}
const Self = @This();
pub fn apply(self: *Self, app: *ona.App) void {
if (!self.tasks.isEmpty()) {
const store = app.getState(ResolvedStorage(Asset)).?;
for (self.tasks.items.slice()) |tasks| {
std.debug.assert(store.resolve(tasks.handle, tasks.future.get()));
}
self.tasks.clear();
std.debug.assert(self.arena.reset(.free_all));
}
}
pub fn load(self: *Self, app_data: ona.App.Data, reserved_handle: AssetHandle, loadable: anytype) error{OutOfMemory}!void {
try self.tasks.pushGrow(coral.heap.allocator, undefined);
errdefer {
std.debug.assert(self.tasks.pop() != null);
}
const resolve = try ona.tasks.create(self.arena.allocator(), .low_priority, ResolveTask(@TypeOf(loadable)){
.loadable = loadable,
.app_data = app_data,
});
std.debug.assert(self.tasks.set(.{
.future = &resolve.future,
.handle = reserved_handle,
}));
}
pub fn deinit(self: *Self) void {
std.debug.assert(self.tasks.isEmpty());
self.tasks.deinit(coral.heap.allocator);
self.arena.deinit();
}
};
}
pub fn ResolvedStorage(comptime Asset: type) type {
return struct {
free_index: u32 = 0,
asset_entries: coral.stack.Parallel(struct {
value: Value,
entry: Entry,
const Entry = struct {
usage: enum { vacant, reserved, occupied, missing },
salt: u32,
};
const Value = union {
asset: Asset,
free_index: u32,
};
}) = .empty,
const AssetHandle = Handle(Asset);
const Self = @This();
pub fn deinit(self: *Self) void {
// TODO: Cleanup assets.
// for (self.asset_entries.items.slice(.entry), 0 .. self.asset_entries.items.len) |entry, i| {
// if (entry != .occupied) {
// continue;
// }
// self.asset_entries.items.slice(.value)[i].asset.deinit();
// }
self.asset_entries.deinit(coral.heap.allocator);
}
pub fn get(self: *const Self, handle: AssetHandle) error{ExpiredHandle}!?Asset {
const entry = &self.asset_entries.items.slice(.entry)[handle.index];
if (handle.salt != entry.salt) {
return error.ExpiredHandle;
}
const value = &self.asset_entries.items.slice(.value)[handle.index];
return switch (entry.usage) {
.vacant => error.ExpiredHandle,
.reserved, .missing => null,
.occupied => value.asset,
};
}
pub fn remove(self: *Self, handle: AssetHandle) ?Asset {
const entry = &self.asset_entries.items.slice(.entry)[handle.index];
if (handle.salt != entry.salt) {
return null;
}
if (entry.usage != .occupied) {
return null;
}
const value = &self.asset_entries.items.slice(.value)[handle.index];
defer {
entry.usage = .vacant;
entry.salt = @max(1, entry.salt +% 1);
value.* = .{ .free_index = self.free_index };
self.free_index = value.free_index;
}
return value.asset;
}
pub fn reserve(self: *Self) error{OutOfMemory}!AssetHandle {
if (self.free_index < self.asset_entries.items.len) {
const entry = &self.asset_entries.items.slice(.entry)[self.free_index];
defer {
self.free_index = self.asset_entries.items.slice(.value)[self.free_index].free_index;
entry.usage = .reserved;
}
return .{
.index = self.free_index,
.salt = entry.salt,
};
}
const handle = AssetHandle{
.index = self.asset_entries.items.len,
.salt = 1,
};
try self.assets.pushGrow(coral.heap.allocator, .{
.asset = undefined,
try self.asset_entries.pushGrow(coral.heap.allocator, .{
.value = undefined,
.state = .{
.entry = .{
.usage = .reserved,
.salt = handle.salt,
},
});
self.free_index += 1;
return handle;
}
pub fn resolve(self: *Store, reserved_handle: Handle, asset: Asset) bool {
const state = self.assets.items.slice(.state)[reserved_handle.index];
pub fn resolve(self: *Self, reserved_handle: AssetHandle, resolved_asset: ?Asset) bool {
const index = reserved_handle.index;
const entry = &self.asset_entries.items.slice(.entry)[index];
if (state.usage != .reserved) {
if (entry.usage != .reserved) {
return false;
}
if (reserved_handle.salt != state.salt) {
if (reserved_handle.salt != entry.salt) {
return false;
}
self.assets.items.slice(.asset)[reserved_handle.index] = asset;
if (resolved_asset) |asset| {
entry.usage = .occupied;
self.asset_entries.items.slice(.value)[index] = .{ .asset = asset };
} else {
entry.usage = .missing;
}
return true;
}
const type_id = generateTypeId();
};
}
fn generateTypeId() error{Overflow}!u20 {
const id = struct {
var last_assigned: u20 = 0;
};
id.last_assigned = try std.math.add(u20, id.last_assigned, 1);
return id.last_assigned;
}
pub const extension_len_max = 7;

View File

@ -1,79 +1,455 @@
const Context = @import("./gfx/Context.zig");
pub const Color = @import("gfx/Color.zig");
const builtin = @import("builtin");
const coral = @import("coral");
const c = @cImport({
@cInclude("SDL3/SDL.h");
@cInclude("shaderc/shaderc.h");
});
const device = @import("gfx/device.zig");
const glsl = @import("./gfx/glsl.zig");
const glsl = @import("gfx/glsl.zig");
const ona = @import("./ona.zig");
const ona = @import("ona.zig");
const qoi = @import("gfx/qoi.zig");
const std = @import("std");
pub const Commands = struct {
pending: *Context.Queue,
pub const CheckerImage = struct {
square_size: u32,
colors: [2]Color,
width: u32,
height: u32,
pub fn init(context: *const Context) !Commands {
return .{
.pending = try context.shared.acquireQueue(),
pub fn load(self: CheckerImage) !device.Image {
var image = try device.Image.init(self.width, self.height, .rgba8, .{ .is_input = true });
errdefer {
image.deinit();
}
const image_size = image.height * image.width * device.Image.Format.rgba8.stride();
{
var image_memory = try device.Memory.init(image_size);
defer {
image_memory.deinit();
}
if (self.square_size == 0) {
try coral.bytes.writeN(image_memory.writer(), &self.colors[0].toRgba8(), self.width * self.height);
} else {
for (0..self.width * self.height) |i| {
const x = i % self.width;
const y = i / self.width;
const square_x = x / self.square_size;
const square_y = y / self.square_size;
const color_index = (square_x + square_y) % 2;
try coral.bytes.writeAll(image_memory.writer(), &self.colors[color_index].toRgba8());
}
}
image_memory.commit();
const commands = try device.acquireCommands();
commands.copyPass().uploadImage(image, image_memory).finish();
commands.submit();
}
return image;
}
};
const Context = struct {
swapchain: device.Swapchain,
backbuffer: device.Target,
swap_buffers: [2]Buffer = .{ .{}, .{} },
is_swapped: bool = false,
frame_arena: std.heap.ArenaAllocator = .init(coral.heap.allocator),
renderer: ?*RenderTask = null,
const Buffer = struct {
head: ?*Queue = null,
tail: ?*Queue = null,
fn dequeue(self: *Buffer) ?*Queue {
if (self.head) |head| {
self.head = head.next;
head.next = null;
if (self.head == null) {
self.tail = null;
}
return head;
}
return null;
}
fn enqueue(self: *Buffer, node: *Queue) void {
if (self.tail) |tail| {
std.debug.assert(self.head != null);
tail.next = node;
self.tail = node;
} else {
self.head = node;
self.tail = node;
}
node.next = null;
}
};
const RenderTask = struct {
backbuffer: device.Target,
submitted_buffer: *Buffer,
finished: std.Thread.ResetEvent = .{},
pub fn run(self: *RenderTask) void {
defer {
self.finished.set();
}
const commands = device.acquireCommands() catch |acquire_error| {
return std.log.err("Render task failed: {s}", .{@errorName(acquire_error)});
};
var render_pass = commands.renderPass(self.backbuffer);
while (self.submitted_buffer.dequeue()) |renderables| {
defer {
renderables.reset();
}
for (renderables.commands.items.slice()) |command| {
switch (command) {}
}
}
render_pass.finish();
commands.submit();
}
};
pub fn deinit(self: *Context) void {
if (self.renderer) |renderer| {
renderer.finished.wait();
}
self.frame_arena.deinit();
self.backbuffer.deinit();
self.swapchain.deinit();
device.stop();
self.* = undefined;
}
pub fn finish(self: *Context) void {
if (self.renderer) |renderer| {
renderer.finished.wait();
}
if (!self.frame_arena.reset(.retain_capacity)) {
std.log.warn("Failed to retain capacity of {s} frame allocator, this may impact performance", .{
@typeName(Context),
});
}
self.renderer = null;
self.is_swapped = !self.is_swapped;
self.swapchain.present(self.backbuffer);
}
pub fn render(self: *Context) void {
self.renderer = ona.tasks.create(self.frame_arena.allocator(), .multimedia, RenderTask{
.submitted_buffer = &self.swap_buffers[@intFromBool(!self.is_swapped)],
.backbuffer = self.backbuffer,
}) catch |creation_error| {
return std.log.warn("{s}", .{switch (creation_error) {
error.OutOfMemory => "Not enough memory was avaialble to start a render, skipping frame...",
}});
};
}
pub fn submit(self: *Context, renderables: *Queue) void {
self.swap_buffers[@intFromBool(self.is_swapped)].enqueue(renderables);
}
};
pub const Display = struct {
width: u16,
height: u16,
size: [2]u16,
is_hidden: bool,
};
pub fn synchronize(commands: ona.Commands) !void {
const buffer_swap = struct {
fn apply(app: *ona.App) void {
const context = app.hasState(Context).?;
const display = app.hasState(Display).?;
pub const Effects = ona.Assets(device.Effect);
context.shared.swapBuffers(display.*);
pub const Images = ona.Assets(device.Image);
pub const QoiImage = struct {
path: ona.App.Path,
pub fn load(self: QoiImage, data: ona.App.Data) !device.Image {
var qoi_data = coral.bytes.spanOf(try data.loadFile(self.path, coral.heap.allocator));
defer {
coral.heap.allocator.free(qoi_data.bytes);
}
const header = try qoi.Info.decode(qoi_data.reader());
var image_memory = try device.Memory.init(header.width * header.height * device.Image.Format.rgba8.stride());
defer {
image_memory.deinit();
}
{
var decoder = qoi.DecodeStream.init(header);
while (try decoder.fetch(qoi_data.reader())) |run| {
try coral.bytes.writeN(image_memory.writer(), &run.pixel.bytes(), run.count);
}
}
image_memory.commit();
var image = try device.Image.init(header.width, header.height, .rgba8, .{
.is_input = true,
});
errdefer {
image.deinit();
}
const commands = try device.acquireCommands();
commands.copyPass().uploadImage(image, image_memory).finish();
commands.submit();
return image;
}
};
const Queue = struct {
commands: coral.stack.Sequential(Command) = .empty,
next: ?*Queue = null,
const Command = union(enum) {};
pub fn apply(self: *Queue, app: *ona.App) void {
if (!self.commands.isEmpty()) {
const context = app.getState(Context).?;
context.submit(self);
}
}
pub fn reset(self: *Queue) void {
self.commands.clear();
}
};
pub const Scene = struct {
renderables: *Queue,
image_store: *const Images.Store,
effect_store: *const Effects.Store,
pub const Rect = struct {
left: f32,
top: f32,
right: f32,
bottom: f32,
};
try commands.push(buffer_swap.apply);
pub const SpriteDraw = struct {
anchor: [2]f32 = @splat(0),
size: ?@Vector(2, f32) = null,
source: Rect = .{ .left = 0, .top = 0, .right = 1, .bottom = 1 },
position: [2]f32 = @splat(0),
tint: Color = .white,
rotation: f32 = 0,
depth: f32 = 0,
effect: Effects.Handle = .default,
};
pub fn bind() Queue {
return .{};
}
pub fn drawSprite(self: Scene, image: Images.Handle, draw: SpriteDraw) void {
_ = self;
_ = image;
_ = draw;
}
pub fn updateEffect(self: Scene, effect: Effects.Handle, properties: anytype) void {
_ = self;
_ = effect;
_ = properties;
}
pub fn updateImage(self: Scene, effect: Effects.Handle, properties: anytype) void {
_ = self;
_ = effect;
_ = properties;
}
pub fn init(renderables: *Queue, image_store: *const Images.Store, effect_store: *const Effects.Store) Scene {
return .{
.renderables = renderables,
.image_store = image_store,
.effect_store = effect_store,
};
}
};
pub const GlslEffect = struct {
fragment: Source = .empty,
vertex: Source = .empty,
pub const Source = glsl.Assembly.Source;
pub fn load(self: GlslEffect) !device.Effect {
var arena = std.heap.ArenaAllocator.init(coral.heap.allocator);
defer {
arena.deinit();
}
var assembly = try glsl.Assembly.init(.spirv, switch (builtin.mode) {
.ReleaseSafe, .Debug => .unoptimized,
.ReleaseFast => .optimize_speed,
.ReleaseSmall => .optimize_size,
});
defer {
assembly.deinit();
}
const Effect = extern struct {
time: f32,
};
const Camera = extern struct {
projection: [4]@Vector(4, f32),
};
const arena_allocator = arena.allocator();
const effect_uniform_block = glsl.nativeUniformBlock(0, Effect);
const camera_uniform_block = glsl.nativeUniformBlock(1, Camera);
const default_shader_name = "[unnamed shader]";
const glsl_version = 430;
return device.Effect.init(.{
.fragment_code = try assembly.assemble(arena_allocator, .fragment, .{
.glsl = try glsl.inject(arena_allocator, switch (self.fragment.glsl.len) {
0 => @embedFile("gfx/canvas.frag"),
else => self.fragment.glsl,
}, glsl_version, .{
.vertex_uv = glsl.nativeInput(0, @Vector(2, f32)),
.vertex_rgba = glsl.nativeInput(1, @Vector(4, f32)),
.image = glsl.Sampler{ .sampler_2d = 0 },
.color = glsl.nativeOutput(0, @Vector(4, f32)),
.effect = effect_uniform_block,
.camera = camera_uniform_block,
}),
.name = switch (self.fragment.name.len) {
0 => default_shader_name,
else => self.fragment.name,
},
}),
.vertex_code = try assembly.assemble(arena_allocator, .vertex, .{
.glsl = try glsl.inject(arena_allocator, switch (self.vertex.glsl.len) {
0 => @embedFile("gfx/canvas.vert"),
else => self.vertex.glsl,
}, glsl_version, .{
.model_xy = glsl.nativeInput(0, @Vector(2, f32)),
.model_uv = glsl.nativeInput(1, @Vector(2, f32)),
.model_rgba = glsl.nativeInput(2, @Vector(4, f32)),
.instance_uv_offset = glsl.nativeInput(3, @Vector(2, f32)),
.instance_uv_scale = glsl.nativeInput(4, @Vector(2, f32)),
.instance_xbasis = glsl.nativeInput(5, @Vector(2, f32)),
.instance_ybasis = glsl.nativeInput(6, @Vector(2, f32)),
.instance_origin = glsl.nativeInput(7, @Vector(2, f32)),
.instance_pivot = glsl.nativeInput(8, @Vector(2, f32)),
.instance_rgb = glsl.nativeInput(9, @Vector(3, f32)),
.instance_bits = glsl.nativeInput(10, u32),
.uv = glsl.nativeOutput(0, @Vector(2, f32)),
.rgba = glsl.nativeOutput(1, @Vector(4, f32)),
.effect = effect_uniform_block,
.camera = camera_uniform_block,
}),
.name = switch (self.vertex.name.len) {
0 => default_shader_name,
else => self.vertex.name,
},
}),
});
}
};
pub fn renderFrame(context: ona.Exclusive(Context), display: ona.Read(Display)) void {
if (display.ptr.is_hidden) {
_ = context.ptr.swapchain.hide();
} else {
_ = context.ptr.swapchain.show();
}
context.ptr.render();
}
pub fn setup(app: *ona.App) !void {
if (!c.SDL_Init(c.SDL_INIT_VIDEO)) {
return error.SystemFailure;
try device.start();
errdefer {
device.stop();
}
const default_display = Display{
.width = 1280,
.height = 720,
.is_hidden = false,
};
var arena = std.heap.ArenaAllocator.init(coral.heap.allocator);
try app.setState(default_display);
defer {
arena.deinit();
}
// var shader_compiler = try glsl.Assembly.init(.spirv, switch (builtin.mode) {
// .Debug, .ReleaseSafe => .unoptimized,
// .ReleaseSmall => .optimize_size,
// .ReleaseFast => .optimize_speed,
// });
// errdefer {
// shader_compiler.deinit();
// }
const default_width, const default_height = .{ 1280, 720 };
{
var context = try Context.init(default_display);
var swapchain = try device.Swapchain.init(default_width, default_height, "Ona");
errdefer {
context.deinit();
swapchain.deinit();
}
try app.setState(context);
var backbuffer = try device.Target.init(default_width, default_height);
errdefer {
backbuffer.deinit();
}
try app.setState(Context{
.swapchain = swapchain,
.backbuffer = backbuffer,
});
}
try app.on(.render, .of(synchronize));
try app.setState(Display{
.size = .{ default_width, default_height },
.is_hidden = false,
});
try ona.registerAsset(app, device.Image);
try ona.registerAsset(app, device.Effect);
try app.on(.pre_update, .of(renderFrame));
try app.on(.post_update, .of(swapBuffers));
}
pub fn swapBuffers(context: ona.Exclusive(Context)) void {
context.ptr.finish();
}

34
src/ona/gfx/Color.zig Normal file
View File

@ -0,0 +1,34 @@
const std = @import("std");
r: f32,
g: f32,
b: f32,
a: f32 = 1,
const Self = @This();
pub const Vector = @Vector(4, f32);
pub fn toRgba8(self: Self) [4]u8 {
const lower_limit = 0;
const upper_limit = std.math.maxInt(u8);
return .{
@intFromFloat(std.math.clamp(self.r * upper_limit, lower_limit, upper_limit)),
@intFromFloat(std.math.clamp(self.g * upper_limit, lower_limit, upper_limit)),
@intFromFloat(std.math.clamp(self.b * upper_limit, lower_limit, upper_limit)),
@intFromFloat(std.math.clamp(self.a * upper_limit, lower_limit, upper_limit)),
};
}
pub fn vector(self: Self) Vector {
return .{ self.r, self.g, self.b, self.a };
}
pub const black = Self{ .r = 0, .g = 0, .b = 0 };
pub const grey = Self{ .r = 0.5, .g = 0.5, .b = 0.5 };
pub const purple = Self{ .r = 0.5, .g = 0, .b = 0.5 };
pub const white = Self{ .r = 1, .g = 1, .b = 1 };

View File

@ -1,250 +0,0 @@
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;
}

View File

@ -2,11 +2,11 @@
void main() {
const vec2 local_xy = model_xy + instance_pivot;
const vec2 world_position = instance_origin + (local_xy.x * instance_xbasis) + (local_xy.y * instance_ybasis);
const vec2 projected = (camera.projection * vec4(world_position, 0, 1)).xy;
const vec2 projected = (camera.projection * vec4(world_position, 0, 1)).xy;
const vec2 depth_alpha = unpackHalf2x16(instance_bits);
gl_Position = vec4(projected, depth_alpha.x, 1.0);
gl_Position = vec4(projected, depth_alpha.x, 1.0);
rgba = model_rgba * vec4(instance_rgb, depth_alpha.y);
uv = instance_uv_offset + (model_uv * instance_uv_scale);
rgba = model_rgba * vec4(instance_rgb, depth_alpha.y);
uv = instance_uv_offset + (model_uv * instance_uv_scale);
}

View File

@ -1,3 +0,0 @@
void main() {
color = texture(texture, texCoord);
}

View File

@ -1,25 +0,0 @@
const vec2 quadVertices[6] = vec2[](
vec2(-1.0, 1.0),
vec2(-1.0, -1.0),
vec2( 1.0, -1.0),
vec2(-1.0, 1.0),
vec2( 1.0, -1.0),
vec2( 1.0, 1.0)
);
const vec2 quadTexCoords[6] = vec2[](
vec2(0.0, 1.0),
vec2(0.0, 0.0),
vec2(1.0, 0.0),
vec2(0.0, 1.0),
vec2(1.0, 0.0),
vec2(1.0, 1.0)
);
void main() {
gl_Position = vec4(quadVertices[gl_VertexID], 0.0, 1.0);
texCoord = quadTexCoords[gl_VertexID];
}

435
src/ona/gfx/device.zig Normal file
View File

@ -0,0 +1,435 @@
const Color = @import("Color.zig");
const builtin = @import("builtin");
const c = @cImport({
@cInclude("SDL3/SDL.h");
});
const coral = @import("coral");
const std = @import("std");
pub const Commands = opaque {
pub const CopyPass = opaque {
pub fn finish(self: *CopyPass) void {
c.SDL_EndGPUCopyPass(@ptrCast(self));
}
pub fn uploadImage(self: *CopyPass, image: Image, memory: Memory) *CopyPass {
c.SDL_UploadToGPUTexture(@ptrCast(self), &.{ .transfer_buffer = memory.buffer }, &.{
.texture = image.texture,
.w = image.width,
.h = image.height,
.d = 1,
}, false);
return self;
}
};
pub const RenderPass = struct {
pub fn finish(self: *RenderPass) void {
c.SDL_EndGPURenderPass(@ptrCast(self));
}
};
pub fn copyPass(self: *Commands) *CopyPass {
// Beginning a copy pass cannot fail unless the command buffer is invalid.
return @ptrCast(c.SDL_BeginGPUCopyPass(@ptrCast(self)));
}
pub fn cancel(self: *Commands) void {
_ = c.SDL_CancelGPUCommandBuffer(@ptrCast(self));
}
pub fn renderPass(self: *Commands, target: Target) *RenderPass {
const color_targets: []const c.SDL_GPUColorTargetInfo = &.{
.{
.texture = target.color,
.load_op = c.SDL_GPU_LOADOP_CLEAR,
.clear_color = @bitCast(Color.black.vector()),
},
};
// Beginning a render pass cannot fail unless the command buffer is invalid.
return @ptrCast(c.SDL_BeginGPURenderPass(@ptrCast(self), color_targets.ptr, @intCast(color_targets.len), &.{
.texture = target.depth_stencil,
.load_op = c.SDL_GPU_LOADOP_CLEAR,
.clear_depth = 0,
}));
}
pub fn submit(self: *Commands) void {
_ = c.SDL_SubmitGPUCommandBuffer(@ptrCast(self));
}
};
pub const Effect = struct {
pipeline: *c.SDL_GPUGraphicsPipeline,
pub const Spirv = struct {
fragment_code: []const u8,
fragment_entrypoint: [:0]const u8 = "main",
vertex_code: []const u8 = &.{},
vertex_entrypoint: [:0]const u8 = "main",
};
var default_vertex_shader: *c.SDL_GPUShader = undefined;
pub fn deinit(self: *Effect) void {
c.SDL_ReleaseGPUGraphicsPipeline(device, self.pipeline);
self.* = undefined;
}
pub fn init(spirv: Spirv) error{SystemResources}!Effect {
const fragment_shader = c.SDL_CreateGPUShader(device, &.{
.code = spirv.fragment_code.ptr,
.code_size = spirv.fragment_code.len,
.format = c.SDL_GPU_SHADERFORMAT_SPIRV,
.stage = c.SDL_GPU_SHADERSTAGE_FRAGMENT,
.entrypoint = spirv.fragment_entrypoint,
}) orelse {
return error.SystemResources;
};
defer {
c.SDL_ReleaseGPUShader(device, fragment_shader);
}
const vertex_shader = c.SDL_CreateGPUShader(device, &.{
.code = spirv.vertex_code.ptr,
.code_size = spirv.vertex_code.len,
.format = c.SDL_GPU_SHADERFORMAT_SPIRV,
.stage = c.SDL_GPU_SHADERSTAGE_VERTEX,
.entrypoint = spirv.vertex_entrypoint,
}) orelse {
return error.SystemResources;
};
defer {
c.SDL_ReleaseGPUShader(device, vertex_shader);
}
const pipeline = c.SDL_CreateGPUGraphicsPipeline(device, &.{
.fragment_shader = fragment_shader,
.vertex_shader = vertex_shader,
.primitive_type = c.SDL_GPU_PRIMITIVETYPE_TRIANGLELIST,
.depth_stencil_state = .{
.enable_depth_write = true,
.compare_op = c.SDL_GPU_COMPAREOP_GREATER,
},
}) orelse {
return error.SystemResources;
};
errdefer {
c.SDL_ReleaseGPUGraphicsPipeline(device, pipeline);
}
return .{
.pipeline = pipeline,
};
}
};
pub const Image = struct {
texture: *c.SDL_GPUTexture,
width: u32,
height: u32,
format: Format,
attributes: Attributes,
pub const Attributes = packed struct {
is_input: bool = false,
is_output: bool = false,
};
pub const Format = enum {
rgba8,
pub fn stride(self: Format) usize {
return switch (self) {
.rgba8 => @sizeOf([4]u8),
};
}
};
pub fn deinit(self: *Image) void {
c.SDL_ReleaseGPUTexture(device, self.texture);
self.* = undefined;
}
pub fn init(width: usize, height: usize, format: Format, attributes: Attributes) error{SystemResources}!Image {
if (width == 0 or height == 0) {
return error.SystemResources;
}
const constrained_width = std.math.cast(u32, width) orelse {
return error.SystemResources;
};
const constrained_height = std.math.cast(u32, height) orelse {
return error.SystemResources;
};
var usage: c.SDL_GPUTextureUsageFlags = 0;
if (attributes.is_input) {
usage |= c.SDL_GPU_TEXTUREUSAGE_SAMPLER;
}
return .{
.width = constrained_width,
.height = constrained_height,
.format = format,
.attributes = attributes,
.texture = c.SDL_CreateGPUTexture(device, &.{
.type = c.SDL_GPU_TEXTURETYPE_2D,
.width = constrained_width,
.height = constrained_height,
.layer_count_or_depth = 1,
.num_levels = 1,
.usage = usage,
.format = switch (format) {
.rgba8 => c.SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM,
},
}) orelse {
return error.SystemResources;
},
};
}
};
pub const Memory = struct {
buffer: *c.SDL_GPUTransferBuffer,
mapped: ?[*]u8 = null,
unused: usize,
pub fn commit(self: *Memory) void {
c.SDL_UnmapGPUTransferBuffer(device, self.buffer);
self.mapped = null;
}
pub fn deinit(self: *Memory) void {
c.SDL_ReleaseGPUTransferBuffer(device, self.buffer);
}
pub fn init(byte_size: usize) error{SystemResources}!Memory {
const buffer = c.SDL_CreateGPUTransferBuffer(device, &.{
.size = std.math.cast(u32, byte_size) orelse {
return error.SystemResources;
},
.usage = c.SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
}) orelse {
return error.SystemResources;
};
errdefer {
c.SDL_ReleaseGPUTransferBuffer(device, buffer);
}
const mapped = c.SDL_MapGPUTransferBuffer(device, buffer, false) orelse {
return error.SystemResources;
};
return .{
.mapped = @ptrCast(mapped),
.buffer = buffer,
.unused = byte_size,
};
}
pub fn isCommited(self: Memory) bool {
return self.mapped == null;
}
pub fn isFilled(self: Memory) bool {
return self.unused == 0;
}
pub fn write(memory: *Memory, data: []const u8) usize {
const len = @min(data.len, memory.unused);
@memcpy(memory.mapped.?[0..len], data[0..len]);
memory.unused -= len;
return len;
}
pub fn writer(self: *Memory) coral.bytes.Writable {
return .initRef(self, write);
}
};
pub const Swapchain = struct {
window: *c.SDL_Window,
pub fn hide(self: Swapchain) bool {
const is_hidden = (c.SDL_GetWindowFlags(self.window) & c.SDL_WINDOW_HIDDEN) != 0;
if (is_hidden) {
return true;
}
return c.SDL_HideWindow(self.window);
}
pub fn deinit(self: *Swapchain) void {
c.SDL_DestroyWindow(self.window);
self.* = undefined;
}
pub fn init(width: u32, height: u32, title: [:0]const u8) error{SystemResources}!Swapchain {
// TODO: Unsafe int casts.
const window = c.SDL_CreateWindow(title, @intCast(width), @intCast(height), c.SDL_WINDOW_HIDDEN) orelse {
return error.SystemResources;
};
errdefer {
c.SDL_DestroyWindow(window);
}
if (!c.SDL_ClaimWindowForGPUDevice(device, window)) {
return error.SystemResources;
}
return .{
.window = window,
};
}
pub fn present(self: Swapchain, target: Target) void {
if (c.SDL_AcquireGPUCommandBuffer(device)) |commands| {
defer {
_ = c.SDL_SubmitGPUCommandBuffer(commands);
}
var swapchain_texture: ?*c.SDL_GPUTexture = null;
var sawpchain_width, var swapchain_height = [2]u32{ 0, 0 };
if (c.SDL_WaitAndAcquireGPUSwapchainTexture(
commands,
self.window,
&swapchain_texture,
&sawpchain_width,
&swapchain_height,
)) {
_ = c.SDL_BlitGPUTexture(commands, &.{
.load_op = c.SDL_GPU_LOADOP_DONT_CARE,
.source = .{
.texture = target.color,
.w = sawpchain_width,
.h = swapchain_height,
},
.destination = .{
.texture = swapchain_texture,
.w = sawpchain_width,
.h = swapchain_height,
},
});
}
}
}
pub fn show(self: Swapchain) bool {
const is_shown = (c.SDL_GetWindowFlags(self.window) & c.SDL_WINDOW_HIDDEN) == 0;
if (is_shown) {
return true;
}
return c.SDL_ShowWindow(self.window);
}
};
pub const Target = struct {
color: *c.SDL_GPUTexture,
depth_stencil: *c.SDL_GPUTexture,
width: u16,
height: u16,
pub fn deinit(self: *Target) void {
c.SDL_ReleaseGPUTexture(device, self.color);
c.SDL_ReleaseGPUTexture(device, self.depth_stencil);
self.* = undefined;
}
pub fn init(width: u16, height: u16) error{SystemResources}!Target {
const color = c.SDL_CreateGPUTexture(device, &.{
.type = c.SDL_GPU_TEXTURETYPE_2D,
.width = width,
.height = height,
.layer_count_or_depth = 1,
.num_levels = 1,
.format = c.SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM,
.usage = c.SDL_GPU_TEXTUREUSAGE_COLOR_TARGET | c.SDL_GPU_TEXTUREUSAGE_SAMPLER,
}) orelse {
return error.SystemResources;
};
errdefer {
c.SDL_ReleaseGPUTexture(device, color);
}
const depth_stencil = c.SDL_CreateGPUTexture(device, &.{
.type = c.SDL_GPU_TEXTURETYPE_2D,
.width = width,
.height = height,
.layer_count_or_depth = 1,
.num_levels = 1,
.format = c.SDL_GPU_TEXTUREFORMAT_D32_FLOAT,
.usage = c.SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET,
}) orelse {
return error.SystemResources;
};
errdefer {
c.SDL_ReleaseGPUTexture(device, depth_stencil);
}
return .{
.color = color,
.depth_stencil = depth_stencil,
.width = width,
.height = height,
};
}
};
var device: *c.SDL_GPUDevice = undefined;
pub fn acquireCommands() error{SystemResources}!*Commands {
return @ptrCast(c.SDL_AcquireGPUCommandBuffer(device) orelse {
return error.SystemResources;
});
}
pub fn start() error{SystemResources}!void {
if (!c.SDL_InitSubSystem(c.SDL_INIT_VIDEO)) {
return error.SystemResources;
}
device = c.SDL_CreateGPUDevice(c.SDL_GPU_SHADERFORMAT_SPIRV, builtin.mode == .Debug, null) orelse {
return error.SystemResources;
};
}
pub fn stop() void {
c.SDL_DestroyGPUDevice(device);
c.SDL_QuitSubSystem(c.SDL_INIT_VIDEO);
device = undefined;
}

View File

@ -4,8 +4,6 @@ const c = @cImport({
const coral = @import("coral");
const ona = @import("../ona.zig");
const std = @import("std");
///
@ -24,6 +22,27 @@ pub const Assembly = struct {
optimize_size,
};
///
/// Textual GLSL source code with an associated name.
///
pub const Source = struct {
name: [:0]const u8,
glsl: []const u8,
pub const empty = Source{
.name = "",
.glsl = "",
};
};
///
/// Different types of shader execution stages.
///
pub const Stage = enum {
vertex,
fragment,
};
///
/// Target format used for shader binaries.
///
@ -50,13 +69,12 @@ pub const Assembly = struct {
self: Assembly,
allocator: std.mem.Allocator,
stage: Stage,
name: [*:0]const u8,
source: []const u8,
) (std.mem.Allocator.Error || error{BadSyntax})![]const u8 {
const result = c.shaderc_compile_into_spv(self.shader_compiler, source.ptr, source.len, switch (stage) {
source: Source,
) (std.mem.Allocator.Error || error{BadSyntax})![]u8 {
const result = c.shaderc_compile_into_spv(self.compiler, source.glsl.ptr, source.glsl.len, switch (stage) {
.fragment => c.shaderc_glsl_fragment_shader,
.vertex => c.shaderc_glsl_vertex_shader,
}, name, "main", self.spirv_options) orelse {
}, source.name.ptr, "main", self.options) orelse {
return error.OutOfMemory;
};
@ -74,7 +92,7 @@ pub const Assembly = struct {
c.shaderc_compilation_status_compilation_error, c.shaderc_compilation_status_invalid_stage => {
std.log.err("{s}", .{c.shaderc_result_get_error_message(result)});
std.log.debug("problematic shader:\n{s}", .{source});
std.log.debug("problematic shader:\n{s}", .{source.glsl});
return error.BadSyntax;
},
@ -85,8 +103,8 @@ pub const Assembly = struct {
}
pub fn deinit(self: *Assembly) void {
c.shaderc_compile_options_release(self.spirv_options);
c.shaderc_compiler_release(self.shader_compiler);
c.shaderc_compile_options_release(self.options);
c.shaderc_compiler_release(self.compiler);
self.* = undefined;
}
@ -212,11 +230,6 @@ pub const Sampler = union(enum) {
}
};
pub const Stage = enum {
vertex,
fragment,
};
pub const UniformBlock = struct {
name: [:0]const u8,
binding: usize,
@ -321,7 +334,7 @@ pub fn inject(allocator: std.mem.Allocator, source_base: []const u8, version: us
try field_value.serialize(field.name, output);
}
try coral.bytes.writeAll(output, "#line 1\n");
try coral.bytes.writeAll(output, "#line 0\n");
}
};

177
src/ona/gfx/qoi.zig Normal file
View File

@ -0,0 +1,177 @@
const coral = @import("coral");
const std = @import("std");
pub const DecodeStream = struct {
last_pixel: Pixel,
pixel_index: [hash_max]Pixel,
remaining: usize,
const Hash = u6;
pub const Run = struct {
pixel: Pixel,
count: u32,
};
pub fn fetch(self: *DecodeStream, reader: coral.bytes.Readable) DecodeError!?Run {
if (self.remaining == 0) {
const terminator = &.{ 0, 0, 0, 0, 0, 0, 0, 1 };
const message = try coral.bytes.readLittle(reader, [8]u8);
if (!std.mem.eql(u8, &message, terminator)) {
return error.BadFormat;
}
return null;
}
const chunk = try coral.bytes.readBig(reader, enum(u8) {
rgb = 0xfe,
rgba = 0xff,
_,
});
var run_count: u32 = 1;
switch (chunk) {
.rgb => {
self.last_pixel.r, self.last_pixel.g, self.last_pixel.b = try coral.bytes.readBig(reader, [3]u8);
},
.rgba => {
self.last_pixel.r, self.last_pixel.g, self.last_pixel.b, self.last_pixel.a = try coral.bytes.readBig(reader, [4]u8);
},
_ => {
const byte = @intFromEnum(chunk);
const op = byte & 0xc0;
const arg: u6 = @intCast(byte & 0x3f);
switch (op) {
0x00 => {
self.last_pixel = self.pixel_index[arg];
},
0x40 => {
const diff: packed struct(u6) { r: i2, g: i2, b: i2 } = @bitCast(arg);
self.last_pixel = self.last_pixel.diff(diff.r, diff.g, diff.b);
},
0x80 => {
const g_diff = @as(i6, @intCast(@as(i8, @as(u6, @truncate(arg))) - 32));
const luma: packed struct(u8) { r_minus_g_diff: i4, b_minus_g_diff: i4 } = @bitCast(try coral.bytes.readByte(reader));
const r_diff = @as(i8, g_diff) + luma.r_minus_g_diff;
const b_diff = @as(i8, g_diff) + luma.b_minus_g_diff;
self.last_pixel = self.last_pixel.diff(r_diff, g_diff, b_diff);
},
0xc0 => {
run_count += arg;
},
else => {
return error.BadFormat;
},
}
},
}
self.pixel_index[hash(self.last_pixel)] = self.last_pixel;
self.remaining -= run_count;
return .{
.pixel = self.last_pixel,
.count = run_count,
};
}
fn hash(pixel: Pixel) Hash {
return @intCast(((pixel.r *% 3) +% (pixel.g *% 5) +% (pixel.b *% 7) +% (pixel.a *% 11)) % hash_max);
}
const hash_max = std.math.maxInt(Hash) + 1;
pub fn init(info: Info) DecodeStream {
return .{
.pixel_index = std.mem.zeroes([hash_max]Pixel),
.last_pixel = .{ .r = 0, .g = 0, .b = 0, .a = std.math.maxInt(u8) },
.remaining = info.width * info.height,
};
}
};
pub const DecodeError = coral.bytes.ReadWriteError || error{
BadFormat,
};
pub const Info = struct {
width: u32,
height: u32,
channels: Channels,
colorspace: Colorspace,
pub const Channels = enum { rgb, rgba };
pub const Colorspace = enum { srgb, linear };
pub fn decode(reader: coral.bytes.Readable) DecodeError!Info {
const magic = try coral.bytes.readLittle(reader, [4]u8);
if (!std.mem.eql(u8, &magic, "qoif")) {
return error.BadFormat;
}
const width, const height = try coral.bytes.readBig(reader, [2]u32);
const format = try coral.bytes.readLittle(reader, extern struct {
channels: u8,
colorspace: u8,
});
return .{
.channels = switch (format.channels) {
3 => .rgb,
4 => .rgba,
else => {
return error.BadFormat;
},
},
.colorspace = switch (format.colorspace) {
0 => .srgb,
1 => .linear,
else => {
return error.BadFormat;
},
},
.width = width,
.height = height,
};
}
};
pub const Pixel = struct {
r: u8,
g: u8,
b: u8,
a: u8,
pub fn bytes(self: Pixel) [4]u8 {
return .{ self.r, self.g, self.b, self.a };
}
pub fn diff(self: Pixel, r_diff: i8, g_diff: i8, b_diff: i8) Pixel {
return .{
.r = self.r +% @as(u8, @bitCast(r_diff)),
.g = self.g +% @as(u8, @bitCast(g_diff)),
.b = self.b +% @as(u8, @bitCast(b_diff)),
.a = self.a,
};
}
};

View File

@ -1,47 +1,62 @@
pub const App = @import("./App.zig");
pub const App = @import("App.zig");
const asset = @import("./asset.zig");
pub const Setup = @import("Setup.zig");
pub const System = @import("System.zig");
const asset = @import("asset.zig");
const coral = @import("coral");
pub const gfx = @import("./gfx.zig");
pub const gfx = @import("gfx.zig");
pub const hid = @import("./hid.zig");
pub const hid = @import("hid.zig");
const std = @import("std");
pub const AssetHandle = asset.Handle;
pub const AssetPath = asset.Path;
pub const tasks = @import("tasks.zig");
pub fn Assets(comptime Asset: type) type {
const Store = asset.Store(Asset);
return struct {
cached: *Store,
app_data: App.Data,
resolved: *Store,
resolves: *ResolveQueue,
pub const Handle = asset.Handle(Asset);
const ResolveQueue = asset.ResolveQueue(Asset);
const Self = @This();
pub fn init(store: *Store) Self {
pub const Store = asset.ResolvedStorage(Asset);
pub fn bind() ResolveQueue {
return .{};
}
pub fn init(resolves: *ResolveQueue, resolved: *Store, app_data: *const App.Data) Self {
return .{
.store = store,
.resolved = resolved,
.resolves = resolves,
.app_data = app_data.*,
};
}
pub fn insert(self: Self, uncached_asset: Asset) std.mem.Allocator.Error!AssetHandle {
const reserved_handle = try self.store.reserve();
pub fn load(self: Self, loadable: anytype) error{OutOfMemory}!Handle {
const reserved_handle = try self.resolved.reserve();
std.debug.assert(self.store.resolve(reserved_handle, uncached_asset));
errdefer {
std.debug.assert(self.resolved.resolve(reserved_handle, null));
}
try self.resolves.load(self.app_data, reserved_handle, loadable);
return reserved_handle;
}
pub fn load(self: Self, load_path: AssetPath) std.mem.Allocator.Error!AssetHandle {
const reserved_handle = try self.store.reserve();
_ = load_path;
return reserved_handle;
pub fn unload(self: Self, handle: Handle) void {
// TODO: Implement.
_ = self.resolved.remove(handle);
}
};
}
@ -230,33 +245,20 @@ pub fn Write(comptime State: type) type {
};
}
pub const realtime_app = App.Setup.init(run_realtime_loop);
pub const realtime_app = Setup.init(realtimeLoop);
pub fn registerAsset(app: *App, comptime Asset: type) std.mem.Allocator.Error!void {
const AssetStore = asset.Store(Asset);
try app.setState(AssetStore{});
}
pub fn registerChannel(app: *App, comptime Message: type) std.mem.Allocator.Error!void {
const MessageChannel = Channel(Message);
try app.on(.post_update, .of(MessageChannel.swap));
try app.setState(MessageChannel{});
}
fn run_realtime_loop(app: *App) !void {
// ext.SDL_SetLogPriorities(ext.SDL_LOG_PRIORITY_VERBOSE);
// ext.SDL_SetLogOutputFunction(sdl_log, null);
fn realtimeLoop(app: *App) !void {
// c.SDL_SetLogPriorities(c.SDL_LOG_PRIORITY_VERBOSE);
// c.SDL_SetLogOutputFunction(sdl_log, null);
const updates_per_frame = 60.0;
const target_frame_time = 1.0 / updates_per_frame;
const time = app.hasState(App.Time).?;
const exit_channel = app.hasState(Channel(App.Exit)).?;
const time = app.getState(App.Time).?;
const exit_channel = app.getState(Channel(App.Exit)).?;
try coral.tasks.start();
try tasks.start();
defer {
coral.tasks.stop();
tasks.stop();
}
try app.run(.load);
@ -280,7 +282,6 @@ fn run_realtime_loop(app: *App) !void {
}
try app.run(.post_update);
try app.run(.render);
const exit_messages = exit_channel.messages();
@ -299,3 +300,15 @@ fn run_realtime_loop(app: *App) !void {
}
}
}
pub fn registerAsset(app: *App, comptime Asset: type) error{OutOfMemory}!void {
try app.setState(asset.ResolvedStorage(Asset){});
try app.setState(asset.ResolveQueue(Asset){});
}
pub fn registerChannel(app: *App, comptime Message: type) error{OutOfMemory}!void {
const MessageChannel = Channel(Message);
try app.on(.post_update, .of(MessageChannel.swap));
try app.setState(MessageChannel{});
}

192
src/ona/tasks.zig Normal file
View File

@ -0,0 +1,192 @@
const Queue = @import("./tasks/Queue.zig");
const builtin = @import("builtin");
const coral = @import("coral");
const std = @import("std");
pub fn CallTask(comptime function: anytype) type {
const Function = @TypeOf(function);
const function_fn = switch (@typeInfo(Function)) {
.@"fn" => |@"fn"| @"fn",
else => @compileError("expeceted param `function` to be an fn type"),
};
return struct {
args: std.meta.ArgsTuple(Function),
future: Future(Return) = .unresolved,
const Return = function_fn.return_type.?;
const Self = @This();
pub fn run(self: *Self) void {
std.debug.assert(self.future.resolve(@call(.auto, function, self.args)));
}
};
}
pub const Category = enum {
high_priority,
low_priority,
multimedia,
fn getThreadCount(self: Category, cpu_count: usize) usize {
const cpu_share: f64 = @floatFromInt(cpu_count);
return switch (self) {
.high_priority => @intFromFloat(cpu_share * 0.375),
.low_priority => @intFromFloat(cpu_share * 0.25),
.multimedia => 1,
};
}
};
pub fn Future(comptime Payload: type) type {
return struct {
payload: Payload,
resolved: std.Thread.ResetEvent,
const Self = @This();
pub const unresolved = Self{
.payload = undefined,
.resolved = .{},
};
pub fn get(self: *Self) Payload {
self.resolved.wait();
return self.payload;
}
pub fn resolve(self: *Self, payload: Payload) bool {
if (self.resolved.isSet()) {
return false;
}
self.payload = payload;
self.resolved.set();
return true;
}
};
}
const categories = std.enums.values(Category);
var categorised_tasks: [categories.len]?*Queue = [_]?*Queue{null} ** categories.len;
///
/// Allocates and pushes a copy of `task` onto the task queue for `category`, blocking the current thread if the queue
/// is currently full.
///
/// Any container type supplying a `run` function declaration matching `fn (*TypeOf(task)) void` may be used as a "task
/// type".
///
/// A reference to the allocated memory is returned or `std.mem.Allocator.Error`.
///
/// **Note** any allocated memory successfully returned must be freed at a later point, either directly or indirectly.
///
pub fn create(allocator: std.mem.Allocator, category: Category, task: anytype) std.mem.Allocator.Error!*@TypeOf(task) {
const Task = @TypeOf(task);
const run_fn = coral.meta.hasFn(Task, "run") orelse {
@compileError(std.fmt.comptimePrint("{s} requires a .run fn to be a valid task type", .{@typeName(Task)}));
};
if (run_fn.return_type.? != void) {
@compileError(std.fmt.comptimePrint("{s}.run fn must return a void type", .{@typeName(Task)}));
}
if (run_fn.params.len != 1 or run_fn.params[0].type != *Task) {
@compileError(std.fmt.comptimePrint("{s}.run fn must accept a {s} as it's one and only parameter, not {s}", .{
@typeName(Task),
@typeName(*Task),
if (run_fn.params[0].type) |Type| @typeName(Type) else "anytype",
}));
}
const created_task = try allocator.create(Task);
created_task.* = task;
if (categorised_tasks[@intFromEnum(category)]) |tasks| {
tasks.enqueue(.initRef(created_task, Task.run));
} else {
created_task.run();
}
return created_task;
}
///
/// Blocks the calling thread to wait until all tasks under `category` are finished.
///
pub fn finish(category: Category) void {
if (categorised_tasks[@intFromEnum(category)]) |tasks| {
tasks.finish();
}
}
pub fn isOnCategoryThread(category: Category) bool {
if (categorised_tasks[@intFromEnum(category)]) |tasks| {
const current_thread_id = std.Thread.getCurrentId();
for (tasks.threads) |thread| {
if (thread.getCurrentId() == current_thread_id) {
return true;
}
}
}
return false;
}
pub fn isOnMainThread() bool {
return std.Thread.getCurrentId() == mainThreadId;
}
var mainThreadId: std.Thread.Id = 0;
///
/// Starts the task pool, marking the thread that it was called from as what it considers to be the "main thread".
///
pub fn start() std.Thread.SpawnError!void {
categorised_tasks = .{ null, null, null };
mainThreadId = std.Thread.getCurrentId();
if (!builtin.single_threaded) {
if (std.Thread.getCpuCount()) |cpu_count| {
errdefer {
stop();
}
inline for (categories, &categorised_tasks) |category, *has_tasks| {
const thread_count = category.getThreadCount(cpu_count);
if (thread_count != 0) {
has_tasks.* = try Queue.spawn(thread_count);
}
}
} else |cpu_count_error| {
std.log.warn("Failed to get number of CPU cores available: {s}", .{@errorName(cpu_count_error)});
}
}
}
///
/// Stops all threaded task pools.
///
pub fn stop() void {
inline for (&categorised_tasks) |*has_tasks| {
if (has_tasks.*) |tasks| {
tasks.stop();
}
has_tasks.* = null;
}
}

View File

@ -1,8 +1,8 @@
const coral = @import("../coral.zig");
const coral = @import("coral");
const std = @import("std");
threads_active: std.atomic.Value(usize) = .init(0),
threads: []std.Thread = &.{},
buffer: [max]Runnable = undefined,
head: usize = 0,
tail: usize = 0,
@ -63,8 +63,6 @@ pub fn finish(self: *Self) void {
}
fn main(self: *Self) void {
_ = self.threads_active.fetchAdd(1, .monotonic);
while (true) {
const runner = self.dequeue();
@ -75,14 +73,6 @@ fn main(self: *Self) void {
runner.call(.{});
self.waiting.finish();
}
const previous_threads_active = self.threads_active.fetchSub(1, .monotonic);
std.debug.assert(previous_threads_active != 0);
if (previous_threads_active == 1) {
coral.heap.allocator.destroy(self);
}
}
const max = 512;
@ -90,12 +80,19 @@ const max = 512;
fn poisonPill() void {}
pub fn stop(self: *Self) void {
var threads_remaining = self.threads_active.load(.monotonic);
var threads_remaining = self.threads.len;
const poison_pill = Runnable.initFn(poisonPill);
while (threads_remaining != 0) : (threads_remaining -= 1) {
self.enqueue(poison_pill);
}
for (self.threads) |thread| {
thread.join();
}
coral.heap.allocator.free(self.threads);
coral.heap.allocator.destroy(self);
}
pub fn spawn(thread_count: usize) std.Thread.SpawnError!*Self {
@ -105,12 +102,20 @@ pub fn spawn(thread_count: usize) std.Thread.SpawnError!*Self {
const self = try coral.heap.allocator.create(Self);
self.* = .{};
errdefer {
coral.heap.allocator.destroy(self);
}
var threads_spawned: usize = 0;
self.* = .{
.threads = try coral.heap.allocator.alloc(std.Thread, thread_count),
};
while (threads_spawned < thread_count) : (threads_spawned += 1) {
(try std.Thread.spawn(.{}, main, .{self})).detach();
errdefer {
coral.heap.allocator.free(self.threads);
}
for (self.threads) |*thread| {
thread.* = try .spawn(.{}, main, .{self});
}
return self;