Add graphics device resources
This commit is contained in:
parent
070a35f694
commit
be4972e75e
1
.gitattributes
vendored
1
.gitattributes
vendored
@ -1,3 +1,4 @@
|
|||||||
*.bmp filter=lfs diff=lfs merge=lfs -text
|
*.bmp filter=lfs diff=lfs merge=lfs -text
|
||||||
*.png filter=lfs diff=lfs merge=lfs -text
|
*.png filter=lfs diff=lfs merge=lfs -text
|
||||||
*.spv 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
11
.vscode/launch.json
vendored
@ -11,6 +11,17 @@
|
|||||||
"preLaunchTask": "Build"
|
"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",
|
"name": "Debug Graphics Demo",
|
||||||
"type": "gdb",
|
"type": "gdb",
|
||||||
|
@ -1 +1 @@
|
|||||||
0.14.0
|
0.15.1
|
32
build.zig
32
build.zig
@ -4,6 +4,7 @@ const BuildConfig = struct {
|
|||||||
module_target: std.Build.ResolvedTarget,
|
module_target: std.Build.ResolvedTarget,
|
||||||
spirv_target: std.Build.ResolvedTarget,
|
spirv_target: std.Build.ResolvedTarget,
|
||||||
optimize: std.builtin.OptimizeMode,
|
optimize: std.builtin.OptimizeMode,
|
||||||
|
sdl_dependency: *std.Build.Dependency,
|
||||||
|
|
||||||
fn scan_demos(self: BuildConfig, ona_module: *std.Build.Module) void {
|
fn scan_demos(self: BuildConfig, ona_module: *std.Build.Module) void {
|
||||||
const b = ona_module.owner;
|
const b = ona_module.owner;
|
||||||
@ -53,10 +54,9 @@ const BuildConfig = struct {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
demo_executable.linkSystemLibrary2("SDL3", .{
|
demo_executable.use_llvm = true;
|
||||||
.needed = true,
|
|
||||||
.preferred_link_mode = .dynamic,
|
demo_executable.linkLibrary(self.sdl_dependency.artifact("SDL3"));
|
||||||
});
|
|
||||||
|
|
||||||
const demo_installation = b.addInstallArtifact(demo_executable, .{});
|
const demo_installation = b.addInstallArtifact(demo_executable, .{});
|
||||||
|
|
||||||
@ -94,9 +94,21 @@ const CommonArgs = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub fn build(b: *std.Build) void {
|
pub fn build(b: *std.Build) void {
|
||||||
|
const target = b.standardTargetOptions(.{});
|
||||||
|
|
||||||
|
const optimize = b.standardOptimizeOption(.{
|
||||||
|
.preferred_optimize_mode = .Debug,
|
||||||
|
});
|
||||||
|
|
||||||
const config = BuildConfig{
|
const config = BuildConfig{
|
||||||
.optimize = b.standardOptimizeOption(.{}),
|
.optimize = optimize,
|
||||||
.module_target = b.standardTargetOptions(.{}),
|
.module_target = target,
|
||||||
|
|
||||||
|
.sdl_dependency = b.dependency("sdl", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
.preferred_linkage = .static,
|
||||||
|
}),
|
||||||
|
|
||||||
.spirv_target = b.resolveTargetQuery(.{
|
.spirv_target = b.resolveTargetQuery(.{
|
||||||
.cpu_arch = .spirv64,
|
.cpu_arch = .spirv64,
|
||||||
@ -109,12 +121,6 @@ pub fn build(b: *std.Build) void {
|
|||||||
|
|
||||||
const shaderc_dependency = b.dependency("shaderc_zig", .{});
|
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", .{
|
const coral_module = b.addModule("coral", .{
|
||||||
.root_source_file = b.path("src/coral/coral.zig"),
|
.root_source_file = b.path("src/coral/coral.zig"),
|
||||||
.target = config.module_target,
|
.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(shaderc_dependency.artifact("shaderc"));
|
||||||
ona_module.linkLibrary(sdl_dependency.artifact("SDL3"));
|
ona_module.linkLibrary(config.sdl_dependency.artifact("SDL3"));
|
||||||
|
|
||||||
// config.addShaders(ona_module, &.{
|
// config.addShaders(ona_module, &.{
|
||||||
// "./src/ona/gfx/effect_shader.zig",
|
// "./src/ona/gfx/effect_shader.zig",
|
||||||
|
@ -4,12 +4,12 @@
|
|||||||
|
|
||||||
.dependencies = .{
|
.dependencies = .{
|
||||||
.shaderc_zig = .{
|
.shaderc_zig = .{
|
||||||
.url = "git+https://github.com/tiawl/shaderc.zig#06565d2af3beec9780b11524984211ebd104fd21",
|
.url = "git+https://github.com/tiawl/shaderc.zig#69b67221988aa84c91447775ad6157e4e80bab00",
|
||||||
.hash = "shaderc_zig-1.0.0-mOl840tjAwBiAnMSfRskq0Iq3JJ9jPRHy2JoEgnUvSpV",
|
.hash = "shaderc_zig-1.0.0-mOl846VjAwDV8YlqQFVvFsWsBa6dLNSiskpTy7lC1hmD",
|
||||||
},
|
},
|
||||||
.sdl = .{
|
.sdl = .{
|
||||||
.url = "git+https://github.com/castholm/SDL.git#0f81c0affb2584b242b2fb5744e7dfebcfd904a5",
|
.url = "git+https://github.com/castholm/SDL.git#b1913e7c31ad72ecfd3ab04aeac387027754cfaf",
|
||||||
.hash = "sdl-0.2.6+3.2.20-7uIn9JkjfwGIQ6j3-etow2rCe-Zt16Yj-2gdp9jW7WZ9",
|
.hash = "sdl-0.3.0+3.2.22-7uIn9Pg3fwGG2IyIOPxxOSVe-75nUng9clt7tXGFLzMr",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -13,11 +13,17 @@ pub const ReadOnlySpan = struct {
|
|||||||
read_cursor: usize = 0,
|
read_cursor: usize = 0,
|
||||||
|
|
||||||
pub fn read(self: *ReadOnlySpan, buffer: []u8) usize {
|
pub fn read(self: *ReadOnlySpan, buffer: []u8) usize {
|
||||||
_ = self;
|
const bytes_read = @min(buffer.len, self.bytes.len - self.read_cursor);
|
||||||
_ = buffer;
|
|
||||||
|
|
||||||
// TODO: Implement.
|
@memcpy(buffer[0..bytes_read], self.bytes[self.read_cursor .. self.read_cursor + bytes_read]);
|
||||||
unreachable;
|
|
||||||
|
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,
|
write_cursor: usize = 0,
|
||||||
read_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 {
|
pub fn put(self: *ReadWriteSpan, byte: u8) bool {
|
||||||
if (self.write_cursor >= self.bytes.len) {
|
if (self.write_cursor >= self.bytes.len) {
|
||||||
return false;
|
return false;
|
||||||
@ -49,14 +47,28 @@ pub const ReadWriteSpan = struct {
|
|||||||
return true;
|
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 {
|
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 {
|
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;
|
return error.BufferOverflow;
|
||||||
}
|
}
|
||||||
|
|
||||||
var buffer_span = span(buffer);
|
var buffer_span = spanOf(buffer);
|
||||||
|
|
||||||
writeFormatted(buffer_span.writer(), format, args) catch unreachable;
|
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];
|
return buffer[0..len :0];
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn readBig(input: Readable, comptime T: type) ReadWriteError!T {
|
pub fn readAll(input: Readable, buffer: []u8) ReadWriteError!void {
|
||||||
var buffer: [@sizeOf(T)]u8 align(@alignOf(T)) = undefined;
|
if (input.call(.{buffer}) != buffer.len) {
|
||||||
|
|
||||||
if (input.call(.{&buffer}) != buffer.len) {
|
|
||||||
return error.IncompleteRead;
|
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 {
|
pub fn readBig(input: Readable, comptime Value: type) ReadWriteError!Value {
|
||||||
var buffer: [@sizeOf(T)]u8 = undefined;
|
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;
|
return error.IncompleteRead;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (builtin.cpu.arch.endian()) {
|
return byte;
|
||||||
.big => {
|
}
|
||||||
std.mem.byteSwapAllFields(T, &buffer);
|
|
||||||
},
|
|
||||||
|
|
||||||
.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()) {
|
pub const readNative = switch (builtin.cpu.arch.endian()) {
|
||||||
@ -180,7 +208,7 @@ pub const readNative = switch (builtin.cpu.arch.endian()) {
|
|||||||
.big => readBig,
|
.big => readBig,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn span(ptr: anytype) Span(@TypeOf(ptr)) {
|
pub fn spanOf(ptr: anytype) Span(@TypeOf(ptr)) {
|
||||||
return .{
|
return .{
|
||||||
.bytes = switch (@typeInfo(@TypeOf(ptr)).pointer.size) {
|
.bytes = switch (@typeInfo(@TypeOf(ptr)).pointer.size) {
|
||||||
.slice => std.mem.sliceAsBytes(ptr),
|
.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 {
|
pub fn writeBig(output: Writable, value: anytype) ReadWriteError!void {
|
||||||
var remaining = count;
|
switch (builtin.cpu.arch.endian()) {
|
||||||
|
.little => {
|
||||||
|
var buffer = std.mem.toBytes(value);
|
||||||
|
|
||||||
while (remaining != 0) : (remaining -= 1) {
|
if (@sizeOf(@TypeOf(value)) > 1) {
|
||||||
try writeAll(output, data);
|
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 => {
|
.big => {
|
||||||
const Value = @TypeOf(value);
|
var buffer = std.mem.toBytes(value);
|
||||||
|
|
||||||
switch (@typeInfo(Value)) {
|
if (@sizeOf(@TypeOf(value)) > 1) {
|
||||||
.@"struct", .array => {
|
std.mem.reverse(u8, &buffer);
|
||||||
var copy = value;
|
|
||||||
|
|
||||||
std.mem.byteSwapAllFields(Value, ©);
|
|
||||||
|
|
||||||
try writeAll(output, std.mem.asBytes(©));
|
|
||||||
},
|
|
||||||
|
|
||||||
.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)}));
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
fn writeNull(buffer: []const u8) usize {
|
||||||
return buffer.len;
|
return buffer.len;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
pub const Box = @import("./Box.zig");
|
pub const Box = @import("./Box.zig");
|
||||||
|
|
||||||
const Tasks = @import("./Tasks.zig");
|
|
||||||
|
|
||||||
pub const TypeId = @import("./TypeId.zig");
|
pub const TypeId = @import("./TypeId.zig");
|
||||||
|
|
||||||
pub const bytes = @import("./bytes.zig");
|
pub const bytes = @import("./bytes.zig");
|
||||||
@ -24,49 +22,6 @@ pub const tree = @import("./tree.zig");
|
|||||||
|
|
||||||
pub const utf8 = @import("./utf8.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 {
|
pub fn Callable(comptime Output: type, comptime input_types: []const type) type {
|
||||||
const InputTuple = std.meta.Tuple(input_types);
|
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),
|
else => @call(.auto, function, args),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub var tasks = Tasks{};
|
|
||||||
|
@ -2,74 +2,19 @@ const builtin = @import("builtin");
|
|||||||
|
|
||||||
const std = @import("std");
|
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) {
|
pub const allocator = switch (builtin.mode) {
|
||||||
.ReleaseFast => std.heap.smp_allocator,
|
.ReleaseFast, .ReleaseSmall => std.heap.smp_allocator,
|
||||||
else => gpa.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 {
|
pub fn traceLeaks() void {
|
||||||
_ = gpa.detectLeaks();
|
switch (builtin.mode) {
|
||||||
|
.ReleaseFast, .ReleaseSmall => {},
|
||||||
|
else => _ = debug_allocator.detectLeaks(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
pub fn hasFn(comptime Type: type, comptime fn_name: []const u8) ?std.builtin.Type.Fn {
|
||||||
const @"type" = @typeInfo(Type);
|
const @"type" = @typeInfo(Type);
|
||||||
|
|
||||||
@ -47,3 +40,20 @@ pub fn hasFn(comptime Type: type, comptime fn_name: []const u8) ?std.builtin.Typ
|
|||||||
else => null,
|
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;
|
||||||
|
}
|
||||||
|
@ -118,11 +118,11 @@ pub fn Parallel(comptime Item: type) type {
|
|||||||
else => @compileError("`Item` must be a struct 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);
|
const item_size = @sizeOf(Item);
|
||||||
|
|
||||||
return Generic(Item, struct {
|
return Generic(Item, struct {
|
||||||
ptr: [*]align(item_align) u8 = undefined,
|
ptr: [*]align(item_align.toByteUnits()) u8 = undefined,
|
||||||
len: u32 = 0,
|
len: u32 = 0,
|
||||||
cap: u32 = 0,
|
cap: u32 = 0,
|
||||||
|
|
||||||
|
@ -66,67 +66,6 @@ pub fn Binary(comptime Key: type, comptime Value: type, comptime traits: Traits(
|
|||||||
|
|
||||||
return node;
|
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 {
|
pub const KeyValues = struct {
|
||||||
@ -330,10 +269,45 @@ pub fn Binary(comptime Key: type, comptime Value: type, comptime traits: Traits(
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const node = root.remove(key) orelse {
|
const node = root.find(key) orelse {
|
||||||
return null;
|
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 {
|
defer {
|
||||||
self.free_nodes.destroy(node);
|
self.free_nodes.destroy(node);
|
||||||
}
|
}
|
||||||
@ -343,6 +317,25 @@ pub fn Binary(comptime Key: type, comptime Value: type, comptime traits: Traits(
|
|||||||
.value = node.value,
|
.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
22
src/demos/crt.frag
Normal 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);
|
||||||
|
}
|
@ -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
|
|
||||||
}
|
|
@ -9,65 +9,90 @@ const CRT = extern struct {
|
|||||||
padding: [4]u8 = undefined,
|
padding: [4]u8 = undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Effects = struct {
|
const State = struct {
|
||||||
render_texture: ona.AssetHandle = .default,
|
images: [2]ona.gfx.Images.Handle = [_]ona.gfx.Images.Handle{.default} ** 2,
|
||||||
image_textures: [2]ona.AssetHandle = [_]ona.AssetHandle{.default} ** 2,
|
|
||||||
last_time: f64 = 0,
|
last_time: f64 = 0,
|
||||||
image_index: usize = 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 {
|
pub fn main() void {
|
||||||
ona.realtime_app
|
ona.realtime_app
|
||||||
.with(.initModule(ona.hid))
|
.with(.initModule(ona.hid))
|
||||||
.with(.initModule(ona.gfx))
|
.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();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
// fn update(effects: ona.Write(Effects), loop: ona.Read(ona.Loop)) void {
|
fn render(scene: ona.gfx.Scene, state: ona.Read(State), display: ona.Write(ona.gfx.Display), time: ona.Read(ona.App.Time)) void {
|
||||||
// const update_seconds = 5;
|
const width, const height = display.ptr.size;
|
||||||
|
|
||||||
// if ((loop.state.elapsed_time - effects.state.last_time) > update_seconds) {
|
scene.updateEffect(state.ptr.crt_effect, CRT{
|
||||||
// effects.state.image_index = (effects.state.image_index + 1) % effects.state.image_textures.len;
|
.width = @floatFromInt(width),
|
||||||
// effects.state.last_time = loop.state.elapsed_time;
|
.height = @floatFromInt(height),
|
||||||
// }
|
.time = @floatCast(time.ptr.elapsed),
|
||||||
// }
|
});
|
||||||
|
|
||||||
// fn render(commands: ona.gfx.Commands) void {
|
scene.drawSprite(state.ptr.images[state.ptr.image_index], .{
|
||||||
// try commands.setTarget(.{
|
.size = .{ @floatFromInt(width), @floatFromInt(height) },
|
||||||
// .texture = effects.state.render_texture,
|
.effect = state.ptr.crt_effect,
|
||||||
// .clear_color = gfx.colors.black,
|
});
|
||||||
// .clear_depth = 0,
|
}
|
||||||
// .clear_stencil = 0,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const width: f32 = @floatFromInt(display.state.width);
|
fn update(state: ona.Write(State), time: ona.Read(ona.App.Time)) void {
|
||||||
// const height: f32 = @floatFromInt(display.state.height);
|
const update_seconds = 5;
|
||||||
|
|
||||||
// try commands.draw_texture(.{
|
if ((time.ptr.elapsed - state.ptr.last_time) > update_seconds) {
|
||||||
// .texture = effects.state.image_textures[effects.state.image_index],
|
state.ptr.image_index = (state.ptr.image_index + 1) % state.ptr.images.len;
|
||||||
// .size = .{ width, height },
|
state.ptr.last_time = time.ptr.elapsed;
|
||||||
// });
|
}
|
||||||
|
}
|
||||||
// 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 },
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
BIN
src/demos/graphics_image.qoi
Normal file
BIN
src/demos/graphics_image.qoi
Normal file
Binary file not shown.
152
src/ona/App.zig
152
src/ona/App.zig
@ -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 coral = @import("coral");
|
||||||
|
|
||||||
const ona = @import("./ona.zig");
|
const ona = @import("ona.zig");
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
initialized_states: coral.map.Hashed(*const coral.TypeId, coral.Box, coral.map.scalarTraits(*const coral.TypeId)),
|
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)),
|
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) {
|
pub const Exit = union(enum) {
|
||||||
success,
|
success,
|
||||||
failure: anyerror,
|
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{
|
pub const RunError = std.mem.Allocator.Error || error{
|
||||||
MissingDependency,
|
MissingDependency,
|
||||||
};
|
};
|
||||||
@ -25,7 +141,7 @@ pub const RunError = std.mem.Allocator.Error || error{
|
|||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
pub const Time = struct {
|
pub const Time = struct {
|
||||||
elapsed: f64,
|
elapsed: f64 = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
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| {
|
if (self.initialized_states.get(.of(State))) |boxed_state| {
|
||||||
return boxed_state.has(State).?;
|
return boxed_state.has(State).?;
|
||||||
}
|
}
|
||||||
@ -65,22 +181,34 @@ pub fn hasState(self: *const Self, comptime State: type) ?*State {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init() error{OutOfMemory}!Self {
|
pub fn init() error{ OutOfMemory, SystemResources }!Self {
|
||||||
var self = Self{
|
var self = Self{
|
||||||
.initialized_states = .empty,
|
.initialized_states = .empty,
|
||||||
.named_systems = .empty,
|
.named_systems = .empty,
|
||||||
};
|
};
|
||||||
|
|
||||||
try self.setState(Time{
|
try self.setState(Data{});
|
||||||
.elapsed = 0,
|
try self.setState(Time{});
|
||||||
});
|
|
||||||
|
|
||||||
try ona.registerChannel(&self, Exit);
|
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;
|
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 schedule_name = scheduleName(schedule);
|
||||||
const systems = self.named_systems.get(schedule_name) orelse (try self.named_systems.insert(coral.heap.allocator, schedule_name, .{})).?;
|
const systems = self.named_systems.get(schedule_name) orelse (try self.named_systems.insert(coral.heap.allocator, schedule_name, .{})).?;
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const coral = @import("coral");
|
const coral = @import("coral");
|
||||||
|
|
||||||
const ona = @import("../ona.zig");
|
const ona = @import("ona.zig");
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
@ -52,7 +52,17 @@ pub fn initModule(comptime namespace: anytype) Self {
|
|||||||
return init(namespace.setup);
|
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 {
|
const system_initialization = struct {
|
||||||
fn apply(app: *ona.App) !void {
|
fn apply(app: *ona.App) !void {
|
||||||
try app.on(schedule, behavior);
|
try app.on(schedule, behavior);
|
@ -1,8 +1,8 @@
|
|||||||
const SystemGraph = @import("./SystemGraph.zig");
|
const SystemGraph = @import("SystemGraph.zig");
|
||||||
|
|
||||||
const coral = @import("coral");
|
const coral = @import("coral");
|
||||||
|
|
||||||
const ona = @import("../ona.zig");
|
const ona = @import("ona.zig");
|
||||||
|
|
||||||
const std = @import("std");
|
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);
|
return .init(coral.heap.allocator, param_states);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,9 +180,10 @@ pub fn of(comptime function: anytype) *const Self {
|
|||||||
},
|
},
|
||||||
|
|
||||||
.pointer => |pointer| {
|
.pointer => |pointer| {
|
||||||
init_args[arg_index] = app.hasState(pointer.child) orelse {
|
init_args[arg_index] = app.getState(pointer.child) orelse {
|
||||||
@panic(std.fmt.comptimePrint("{s} is a required state but not present in the App", .{
|
@panic(std.fmt.comptimePrint("{s} is a required state but not present in the App. {s}", .{
|
||||||
@typeName(pointer.child),
|
@typeName(pointer.child),
|
||||||
|
"Did you mean to add it with App.putState?",
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
},
|
},
|
@ -1,6 +1,6 @@
|
|||||||
const coral = @import("coral");
|
const coral = @import("coral");
|
||||||
|
|
||||||
const ona = @import("../ona.zig");
|
const ona = @import("ona.zig");
|
||||||
|
|
||||||
const std = @import("std");
|
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 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 {
|
const Edge = struct {
|
||||||
dependencies: BehaviorSet = .empty,
|
dependencies: BehaviorSet = .empty,
|
||||||
@ -30,7 +30,7 @@ const Edge = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
fn Map(comptime Payload: type) type {
|
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 {
|
const Processed = struct {
|
||||||
@ -38,7 +38,7 @@ const Processed = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const Work = struct {
|
const Work = struct {
|
||||||
behavior: *const ona.App.Behavior,
|
behavior: *const ona.System,
|
||||||
local_state: coral.Box,
|
local_state: coral.Box,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -55,6 +55,16 @@ pub const TypeDependency = struct {
|
|||||||
is_read_only: bool,
|
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 {
|
pub fn deinit(self: *Self) void {
|
||||||
const allocator = coral.heap.allocator;
|
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);
|
try self.insert(app, dependant);
|
||||||
|
|
||||||
const edge = self.edges.get(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);
|
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)).?;
|
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| {
|
for (readers.items.slice()[0..index]) |reader| {
|
||||||
try self.dependOnBehavior(app, dependant, 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) {
|
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)).?;
|
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| {
|
for (writers.items.slice()[0..index]) |reader| {
|
||||||
try self.dependOnBehavior(app, dependant, 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 {
|
pub fn insert(self: *Self, app: *ona.App, behavior: *const ona.System) std.mem.Allocator.Error!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 {
|
|
||||||
self.processed.clear();
|
self.processed.clear();
|
||||||
|
|
||||||
// TODO: Refactor so partial initialisation isn't happening here anymore.
|
// 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 };
|
var processed = Processed{ .is_blocking = behavior.is_blocking };
|
||||||
|
|
||||||
for (edge.dependencies.items.slice()) |dependency| {
|
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();
|
var parallel_work = self.parallel_work.items.slice();
|
||||||
|
|
||||||
for (self.parallel_work_ranges.items.slice()) |parallel_work_range| {
|
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] },
|
.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());
|
runWorkGroup(app, self.blocking_work.items.slice());
|
||||||
}
|
}
|
||||||
|
|
@ -1,84 +1,291 @@
|
|||||||
|
const ona = @import("ona.zig");
|
||||||
|
|
||||||
const coral = @import("coral");
|
const coral = @import("coral");
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub const Handle = packed struct {
|
pub const GenericHandle = struct {
|
||||||
index: u24,
|
type_id: *const coral.TypeId,
|
||||||
salt: u20,
|
index: u32,
|
||||||
type_id: u20,
|
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 {
|
pub fn Handle(comptime Asset: type) type {
|
||||||
buffer: [256]u8,
|
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 {
|
return struct {
|
||||||
released_handles: coral.stack.Sequential(Handle) = .empty,
|
arena: std.heap.ArenaAllocator = .init(coral.heap.allocator),
|
||||||
|
|
||||||
assets: coral.stack.Parallel(struct {
|
tasks: coral.stack.Sequential(struct {
|
||||||
asset: Asset,
|
future: *ona.tasks.Future(?Asset),
|
||||||
state: AssetState,
|
handle: AssetHandle,
|
||||||
}) = .empty,
|
}) = .empty,
|
||||||
|
|
||||||
const AssetState = struct {
|
const AssetHandle = Handle(Asset);
|
||||||
usage: enum { vacant, reserved, occupied },
|
|
||||||
salt: u20,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn reserve(self: *Store) std.mem.Allocator.Error!Handle {
|
fn ResolveTask(comptime Loadable: type) type {
|
||||||
if (self.released_handles.pop()) |handle| {
|
const load_fn_name = "load";
|
||||||
const state = &self.assets.items.slice(.state)[handle.index];
|
|
||||||
|
|
||||||
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{
|
const Result = coral.meta.UnwrappedError(load_fn.return_type.?);
|
||||||
.type_id = type_id,
|
|
||||||
.index = self.assets.items.len,
|
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,
|
.salt = 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
try self.assets.pushGrow(coral.heap.allocator, .{
|
try self.asset_entries.pushGrow(coral.heap.allocator, .{
|
||||||
.asset = undefined,
|
.value = undefined,
|
||||||
|
|
||||||
.state = .{
|
.entry = .{
|
||||||
.usage = .reserved,
|
.usage = .reserved,
|
||||||
.salt = handle.salt,
|
.salt = handle.salt,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
self.free_index += 1;
|
||||||
|
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resolve(self: *Store, reserved_handle: Handle, asset: Asset) bool {
|
pub fn resolve(self: *Self, reserved_handle: AssetHandle, resolved_asset: ?Asset) bool {
|
||||||
const state = self.assets.items.slice(.state)[reserved_handle.index];
|
const index = reserved_handle.index;
|
||||||
|
const entry = &self.asset_entries.items.slice(.entry)[index];
|
||||||
|
|
||||||
if (state.usage != .reserved) {
|
if (entry.usage != .reserved) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reserved_handle.salt != state.salt) {
|
if (reserved_handle.salt != entry.salt) {
|
||||||
return false;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const type_id = generateTypeId();
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generateTypeId() error{Overflow}!u20 {
|
pub const extension_len_max = 7;
|
||||||
const id = struct {
|
|
||||||
var last_assigned: u20 = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
id.last_assigned = try std.math.add(u20, id.last_assigned, 1);
|
|
||||||
|
|
||||||
return id.last_assigned;
|
|
||||||
}
|
|
||||||
|
460
src/ona/gfx.zig
460
src/ona/gfx.zig
@ -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 coral = @import("coral");
|
||||||
|
|
||||||
const c = @cImport({
|
const device = @import("gfx/device.zig");
|
||||||
@cInclude("SDL3/SDL.h");
|
|
||||||
@cInclude("shaderc/shaderc.h");
|
|
||||||
});
|
|
||||||
|
|
||||||
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");
|
const std = @import("std");
|
||||||
|
|
||||||
pub const Commands = struct {
|
pub const CheckerImage = struct {
|
||||||
pending: *Context.Queue,
|
square_size: u32,
|
||||||
|
colors: [2]Color,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
|
||||||
pub fn init(context: *const Context) !Commands {
|
pub fn load(self: CheckerImage) !device.Image {
|
||||||
return .{
|
var image = try device.Image.init(self.width, self.height, .rgba8, .{ .is_input = true });
|
||||||
.pending = try context.shared.acquireQueue(),
|
|
||||||
|
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 {
|
pub const Display = struct {
|
||||||
width: u16,
|
size: [2]u16,
|
||||||
height: u16,
|
|
||||||
is_hidden: bool,
|
is_hidden: bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn synchronize(commands: ona.Commands) !void {
|
pub const Effects = ona.Assets(device.Effect);
|
||||||
const buffer_swap = struct {
|
|
||||||
fn apply(app: *ona.App) void {
|
|
||||||
const context = app.hasState(Context).?;
|
|
||||||
const display = app.hasState(Display).?;
|
|
||||||
|
|
||||||
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 {
|
pub fn setup(app: *ona.App) !void {
|
||||||
if (!c.SDL_Init(c.SDL_INIT_VIDEO)) {
|
try device.start();
|
||||||
return error.SystemFailure;
|
|
||||||
|
errdefer {
|
||||||
|
device.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
const default_display = Display{
|
var arena = std.heap.ArenaAllocator.init(coral.heap.allocator);
|
||||||
.width = 1280,
|
|
||||||
.height = 720,
|
|
||||||
.is_hidden = false,
|
|
||||||
};
|
|
||||||
|
|
||||||
try app.setState(default_display);
|
defer {
|
||||||
|
arena.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
// var shader_compiler = try glsl.Assembly.init(.spirv, switch (builtin.mode) {
|
const default_width, const default_height = .{ 1280, 720 };
|
||||||
// .Debug, .ReleaseSafe => .unoptimized,
|
|
||||||
// .ReleaseSmall => .optimize_size,
|
|
||||||
// .ReleaseFast => .optimize_speed,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// errdefer {
|
|
||||||
// shader_compiler.deinit();
|
|
||||||
// }
|
|
||||||
|
|
||||||
{
|
{
|
||||||
var context = try Context.init(default_display);
|
var swapchain = try device.Swapchain.init(default_width, default_height, "Ona");
|
||||||
|
|
||||||
errdefer {
|
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
34
src/ona/gfx/Color.zig
Normal 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 };
|
@ -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;
|
|
||||||
}
|
|
@ -2,11 +2,11 @@
|
|||||||
void main() {
|
void main() {
|
||||||
const vec2 local_xy = model_xy + instance_pivot;
|
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 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);
|
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);
|
rgba = model_rgba * vec4(instance_rgb, depth_alpha.y);
|
||||||
uv = instance_uv_offset + (model_uv * instance_uv_scale);
|
uv = instance_uv_offset + (model_uv * instance_uv_scale);
|
||||||
}
|
}
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
void main() {
|
|
||||||
color = texture(texture, texCoord);
|
|
||||||
}
|
|
@ -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
435
src/ona/gfx/device.zig
Normal 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;
|
||||||
|
}
|
@ -4,8 +4,6 @@ const c = @cImport({
|
|||||||
|
|
||||||
const coral = @import("coral");
|
const coral = @import("coral");
|
||||||
|
|
||||||
const ona = @import("../ona.zig");
|
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
///
|
///
|
||||||
@ -24,6 +22,27 @@ pub const Assembly = struct {
|
|||||||
optimize_size,
|
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.
|
/// Target format used for shader binaries.
|
||||||
///
|
///
|
||||||
@ -50,13 +69,12 @@ pub const Assembly = struct {
|
|||||||
self: Assembly,
|
self: Assembly,
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
stage: Stage,
|
stage: Stage,
|
||||||
name: [*:0]const u8,
|
source: Source,
|
||||||
source: []const u8,
|
) (std.mem.Allocator.Error || error{BadSyntax})![]u8 {
|
||||||
) (std.mem.Allocator.Error || error{BadSyntax})![]const u8 {
|
const result = c.shaderc_compile_into_spv(self.compiler, source.glsl.ptr, source.glsl.len, switch (stage) {
|
||||||
const result = c.shaderc_compile_into_spv(self.shader_compiler, source.ptr, source.len, switch (stage) {
|
|
||||||
.fragment => c.shaderc_glsl_fragment_shader,
|
.fragment => c.shaderc_glsl_fragment_shader,
|
||||||
.vertex => c.shaderc_glsl_vertex_shader,
|
.vertex => c.shaderc_glsl_vertex_shader,
|
||||||
}, name, "main", self.spirv_options) orelse {
|
}, source.name.ptr, "main", self.options) orelse {
|
||||||
return error.OutOfMemory;
|
return error.OutOfMemory;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -74,7 +92,7 @@ pub const Assembly = struct {
|
|||||||
|
|
||||||
c.shaderc_compilation_status_compilation_error, c.shaderc_compilation_status_invalid_stage => {
|
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.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;
|
return error.BadSyntax;
|
||||||
},
|
},
|
||||||
@ -85,8 +103,8 @@ pub const Assembly = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Assembly) void {
|
pub fn deinit(self: *Assembly) void {
|
||||||
c.shaderc_compile_options_release(self.spirv_options);
|
c.shaderc_compile_options_release(self.options);
|
||||||
c.shaderc_compiler_release(self.shader_compiler);
|
c.shaderc_compiler_release(self.compiler);
|
||||||
|
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
@ -212,11 +230,6 @@ pub const Sampler = union(enum) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Stage = enum {
|
|
||||||
vertex,
|
|
||||||
fragment,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const UniformBlock = struct {
|
pub const UniformBlock = struct {
|
||||||
name: [:0]const u8,
|
name: [:0]const u8,
|
||||||
binding: usize,
|
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 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
177
src/ona/gfx/qoi.zig
Normal 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
@ -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");
|
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");
|
const std = @import("std");
|
||||||
|
|
||||||
pub const AssetHandle = asset.Handle;
|
pub const tasks = @import("tasks.zig");
|
||||||
|
|
||||||
pub const AssetPath = asset.Path;
|
|
||||||
|
|
||||||
pub fn Assets(comptime Asset: type) type {
|
pub fn Assets(comptime Asset: type) type {
|
||||||
const Store = asset.Store(Asset);
|
|
||||||
|
|
||||||
return struct {
|
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();
|
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 .{
|
return .{
|
||||||
.store = store,
|
.resolved = resolved,
|
||||||
|
.resolves = resolves,
|
||||||
|
.app_data = app_data.*,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(self: Self, uncached_asset: Asset) std.mem.Allocator.Error!AssetHandle {
|
pub fn load(self: Self, loadable: anytype) error{OutOfMemory}!Handle {
|
||||||
const reserved_handle = try self.store.reserve();
|
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;
|
return reserved_handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load(self: Self, load_path: AssetPath) std.mem.Allocator.Error!AssetHandle {
|
pub fn unload(self: Self, handle: Handle) void {
|
||||||
const reserved_handle = try self.store.reserve();
|
// TODO: Implement.
|
||||||
|
_ = self.resolved.remove(handle);
|
||||||
_ = load_path;
|
|
||||||
|
|
||||||
return reserved_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 {
|
fn realtimeLoop(app: *App) !void {
|
||||||
const AssetStore = asset.Store(Asset);
|
// c.SDL_SetLogPriorities(c.SDL_LOG_PRIORITY_VERBOSE);
|
||||||
|
// c.SDL_SetLogOutputFunction(sdl_log, null);
|
||||||
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);
|
|
||||||
const updates_per_frame = 60.0;
|
const updates_per_frame = 60.0;
|
||||||
const target_frame_time = 1.0 / updates_per_frame;
|
const target_frame_time = 1.0 / updates_per_frame;
|
||||||
const time = app.hasState(App.Time).?;
|
const time = app.getState(App.Time).?;
|
||||||
const exit_channel = app.hasState(Channel(App.Exit)).?;
|
const exit_channel = app.getState(Channel(App.Exit)).?;
|
||||||
|
|
||||||
try coral.tasks.start();
|
try tasks.start();
|
||||||
|
|
||||||
defer {
|
defer {
|
||||||
coral.tasks.stop();
|
tasks.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
try app.run(.load);
|
try app.run(.load);
|
||||||
@ -280,7 +282,6 @@ fn run_realtime_loop(app: *App) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try app.run(.post_update);
|
try app.run(.post_update);
|
||||||
try app.run(.render);
|
|
||||||
|
|
||||||
const exit_messages = exit_channel.messages();
|
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
192
src/ona/tasks.zig
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
const coral = @import("../coral.zig");
|
const coral = @import("coral");
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
threads_active: std.atomic.Value(usize) = .init(0),
|
threads: []std.Thread = &.{},
|
||||||
buffer: [max]Runnable = undefined,
|
buffer: [max]Runnable = undefined,
|
||||||
head: usize = 0,
|
head: usize = 0,
|
||||||
tail: usize = 0,
|
tail: usize = 0,
|
||||||
@ -63,8 +63,6 @@ pub fn finish(self: *Self) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main(self: *Self) void {
|
fn main(self: *Self) void {
|
||||||
_ = self.threads_active.fetchAdd(1, .monotonic);
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const runner = self.dequeue();
|
const runner = self.dequeue();
|
||||||
|
|
||||||
@ -75,14 +73,6 @@ fn main(self: *Self) void {
|
|||||||
runner.call(.{});
|
runner.call(.{});
|
||||||
self.waiting.finish();
|
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;
|
const max = 512;
|
||||||
@ -90,12 +80,19 @@ const max = 512;
|
|||||||
fn poisonPill() void {}
|
fn poisonPill() void {}
|
||||||
|
|
||||||
pub fn stop(self: *Self) 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);
|
const poison_pill = Runnable.initFn(poisonPill);
|
||||||
|
|
||||||
while (threads_remaining != 0) : (threads_remaining -= 1) {
|
while (threads_remaining != 0) : (threads_remaining -= 1) {
|
||||||
self.enqueue(poison_pill);
|
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 {
|
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);
|
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) {
|
errdefer {
|
||||||
(try std.Thread.spawn(.{}, main, .{self})).detach();
|
coral.heap.allocator.free(self.threads);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (self.threads) |*thread| {
|
||||||
|
thread.* = try .spawn(.{}, main, .{self});
|
||||||
}
|
}
|
||||||
|
|
||||||
return self;
|
return self;
|
Loading…
x
Reference in New Issue
Block a user