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
|
||||
*.png filter=lfs diff=lfs merge=lfs -text
|
||||
*.spv filter=lfs diff=lfs merge=lfs -text
|
||||
*.qoi filter=lfs diff=lfs merge=lfs -text
|
||||
|
11
.vscode/launch.json
vendored
11
.vscode/launch.json
vendored
@ -11,6 +11,17 @@
|
||||
"preLaunchTask": "Build"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Debug Qoi",
|
||||
"type": "gdb",
|
||||
"request": "launch",
|
||||
"target": "${workspaceRoot}/qoi-master/qoiconv",
|
||||
"arguments": "${workspaceRoot}/src/demos/graphics_image.qoi ${workspaceRoot}/test.qoi",
|
||||
"cwd": "${workspaceRoot}/src/demos/",
|
||||
"valuesFormatting": "parseText",
|
||||
"preLaunchTask": "Build"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Debug Graphics Demo",
|
||||
"type": "gdb",
|
||||
|
@ -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,
|
||||
spirv_target: std.Build.ResolvedTarget,
|
||||
optimize: std.builtin.OptimizeMode,
|
||||
sdl_dependency: *std.Build.Dependency,
|
||||
|
||||
fn scan_demos(self: BuildConfig, ona_module: *std.Build.Module) void {
|
||||
const b = ona_module.owner;
|
||||
@ -53,10 +54,9 @@ const BuildConfig = struct {
|
||||
}),
|
||||
});
|
||||
|
||||
demo_executable.linkSystemLibrary2("SDL3", .{
|
||||
.needed = true,
|
||||
.preferred_link_mode = .dynamic,
|
||||
});
|
||||
demo_executable.use_llvm = true;
|
||||
|
||||
demo_executable.linkLibrary(self.sdl_dependency.artifact("SDL3"));
|
||||
|
||||
const demo_installation = b.addInstallArtifact(demo_executable, .{});
|
||||
|
||||
@ -94,9 +94,21 @@ const CommonArgs = struct {
|
||||
};
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
|
||||
const optimize = b.standardOptimizeOption(.{
|
||||
.preferred_optimize_mode = .Debug,
|
||||
});
|
||||
|
||||
const config = BuildConfig{
|
||||
.optimize = b.standardOptimizeOption(.{}),
|
||||
.module_target = b.standardTargetOptions(.{}),
|
||||
.optimize = optimize,
|
||||
.module_target = target,
|
||||
|
||||
.sdl_dependency = b.dependency("sdl", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.preferred_linkage = .static,
|
||||
}),
|
||||
|
||||
.spirv_target = b.resolveTargetQuery(.{
|
||||
.cpu_arch = .spirv64,
|
||||
@ -109,12 +121,6 @@ pub fn build(b: *std.Build) void {
|
||||
|
||||
const shaderc_dependency = b.dependency("shaderc_zig", .{});
|
||||
|
||||
const sdl_dependency = b.dependency("sdl", .{
|
||||
.target = config.module_target,
|
||||
.optimize = config.optimize,
|
||||
.preferred_linkage = .static,
|
||||
});
|
||||
|
||||
const coral_module = b.addModule("coral", .{
|
||||
.root_source_file = b.path("src/coral/coral.zig"),
|
||||
.target = config.module_target,
|
||||
@ -136,7 +142,7 @@ pub fn build(b: *std.Build) void {
|
||||
});
|
||||
|
||||
ona_module.linkLibrary(shaderc_dependency.artifact("shaderc"));
|
||||
ona_module.linkLibrary(sdl_dependency.artifact("SDL3"));
|
||||
ona_module.linkLibrary(config.sdl_dependency.artifact("SDL3"));
|
||||
|
||||
// config.addShaders(ona_module, &.{
|
||||
// "./src/ona/gfx/effect_shader.zig",
|
||||
|
@ -4,12 +4,12 @@
|
||||
|
||||
.dependencies = .{
|
||||
.shaderc_zig = .{
|
||||
.url = "git+https://github.com/tiawl/shaderc.zig#06565d2af3beec9780b11524984211ebd104fd21",
|
||||
.hash = "shaderc_zig-1.0.0-mOl840tjAwBiAnMSfRskq0Iq3JJ9jPRHy2JoEgnUvSpV",
|
||||
.url = "git+https://github.com/tiawl/shaderc.zig#69b67221988aa84c91447775ad6157e4e80bab00",
|
||||
.hash = "shaderc_zig-1.0.0-mOl846VjAwDV8YlqQFVvFsWsBa6dLNSiskpTy7lC1hmD",
|
||||
},
|
||||
.sdl = .{
|
||||
.url = "git+https://github.com/castholm/SDL.git#0f81c0affb2584b242b2fb5744e7dfebcfd904a5",
|
||||
.hash = "sdl-0.2.6+3.2.20-7uIn9JkjfwGIQ6j3-etow2rCe-Zt16Yj-2gdp9jW7WZ9",
|
||||
.url = "git+https://github.com/castholm/SDL.git#b1913e7c31ad72ecfd3ab04aeac387027754cfaf",
|
||||
.hash = "sdl-0.3.0+3.2.22-7uIn9Pg3fwGG2IyIOPxxOSVe-75nUng9clt7tXGFLzMr",
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -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,
|
||||
|
||||
pub fn read(self: *ReadOnlySpan, buffer: []u8) usize {
|
||||
_ = self;
|
||||
_ = buffer;
|
||||
const bytes_read = @min(buffer.len, self.bytes.len - self.read_cursor);
|
||||
|
||||
// TODO: Implement.
|
||||
unreachable;
|
||||
@memcpy(buffer[0..bytes_read], self.bytes[self.read_cursor .. self.read_cursor + bytes_read]);
|
||||
|
||||
self.read_cursor += bytes_read;
|
||||
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
pub fn reader(self: *ReadOnlySpan) Readable {
|
||||
return .initRef(self, read);
|
||||
}
|
||||
};
|
||||
|
||||
@ -30,14 +36,6 @@ pub const ReadWriteSpan = struct {
|
||||
write_cursor: usize = 0,
|
||||
read_cursor: usize = 0,
|
||||
|
||||
pub fn read(self: *ReadWriteSpan, buffer: []u8) usize {
|
||||
_ = self;
|
||||
_ = buffer;
|
||||
|
||||
// TODO: Implement.
|
||||
unreachable;
|
||||
}
|
||||
|
||||
pub fn put(self: *ReadWriteSpan, byte: u8) bool {
|
||||
if (self.write_cursor >= self.bytes.len) {
|
||||
return false;
|
||||
@ -49,14 +47,28 @@ pub const ReadWriteSpan = struct {
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn read(self: *ReadWriteSpan, buffer: []u8) usize {
|
||||
const bytes_read = @min(buffer.len, self.bytes.len - self.read_cursor);
|
||||
|
||||
@memcpy(buffer[0..bytes_read], self.bytes[self.read_cursor .. self.read_cursor + bytes_read]);
|
||||
|
||||
self.read_cursor += bytes_read;
|
||||
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
pub fn reader(self: *ReadWriteSpan) Readable {
|
||||
return .initRef(self, read);
|
||||
}
|
||||
|
||||
pub fn write(self: *ReadWriteSpan, buffer: []const u8) usize {
|
||||
const written = @min(buffer.len, self.bytes.len - self.write_cursor);
|
||||
const bytes_written = @min(buffer.len, self.bytes.len - self.write_cursor);
|
||||
|
||||
@memcpy(self.bytes[self.write_cursor .. self.write_cursor + written], buffer[0..written]);
|
||||
@memcpy(self.bytes[self.write_cursor .. self.write_cursor + bytes_written], buffer[0..bytes_written]);
|
||||
|
||||
self.write_cursor += written;
|
||||
self.write_cursor += bytes_written;
|
||||
|
||||
return written;
|
||||
return bytes_written;
|
||||
}
|
||||
|
||||
pub fn writer(self: *ReadWriteSpan) Writable {
|
||||
@ -128,7 +140,7 @@ pub fn printFormatted(buffer: [:0]u8, comptime format: []const u8, args: anytype
|
||||
return error.BufferOverflow;
|
||||
}
|
||||
|
||||
var buffer_span = span(buffer);
|
||||
var buffer_span = spanOf(buffer);
|
||||
|
||||
writeFormatted(buffer_span.writer(), format, args) catch unreachable;
|
||||
|
||||
@ -139,40 +151,56 @@ pub fn printFormatted(buffer: [:0]u8, comptime format: []const u8, args: anytype
|
||||
return buffer[0..len :0];
|
||||
}
|
||||
|
||||
pub fn readBig(input: Readable, comptime T: type) ReadWriteError!T {
|
||||
var buffer: [@sizeOf(T)]u8 align(@alignOf(T)) = undefined;
|
||||
|
||||
if (input.call(.{&buffer}) != buffer.len) {
|
||||
pub fn readAll(input: Readable, buffer: []u8) ReadWriteError!void {
|
||||
if (input.call(.{buffer}) != buffer.len) {
|
||||
return error.IncompleteRead;
|
||||
}
|
||||
|
||||
switch (builtin.cpu.arch.endian()) {
|
||||
.big => {},
|
||||
|
||||
.little => {
|
||||
std.mem.byteSwapAllFields(T, @ptrCast(&buffer));
|
||||
},
|
||||
}
|
||||
|
||||
return std.mem.bytesToValue(T, &buffer);
|
||||
}
|
||||
|
||||
pub fn readLittle(input: Readable, comptime T: type) ReadWriteError!T {
|
||||
var buffer: [@sizeOf(T)]u8 = undefined;
|
||||
pub fn readBig(input: Readable, comptime Value: type) ReadWriteError!Value {
|
||||
var buffer: [@sizeOf(Value)]u8 align(@alignOf(Value)) = undefined;
|
||||
|
||||
if (input.call(.{&buffer}) != buffer.len) {
|
||||
try readAll(input, &buffer);
|
||||
|
||||
if (@sizeOf(Value) > 1) {
|
||||
switch (builtin.cpu.arch.endian()) {
|
||||
.little => {
|
||||
std.mem.reverse(u8, &buffer);
|
||||
},
|
||||
|
||||
.big => {},
|
||||
}
|
||||
}
|
||||
|
||||
return std.mem.bytesToValue(Value, &buffer);
|
||||
}
|
||||
|
||||
pub fn readByte(input: Readable) ReadWriteError!u8 {
|
||||
var byte: u8 = undefined;
|
||||
|
||||
if (input.call(.{std.mem.asBytes(&byte)}) != 1) {
|
||||
return error.IncompleteRead;
|
||||
}
|
||||
|
||||
switch (builtin.cpu.arch.endian()) {
|
||||
.big => {
|
||||
std.mem.byteSwapAllFields(T, &buffer);
|
||||
},
|
||||
return byte;
|
||||
}
|
||||
|
||||
.little => {},
|
||||
pub fn readLittle(input: Readable, comptime Value: type) ReadWriteError!Value {
|
||||
var buffer: [@sizeOf(Value)]u8 align(@alignOf(Value)) = undefined;
|
||||
|
||||
try readAll(input, &buffer);
|
||||
|
||||
if (@sizeOf(Value) > 1) {
|
||||
switch (builtin.cpu.arch.endian()) {
|
||||
.little => {},
|
||||
|
||||
.big => {
|
||||
std.mem.reverse(u8, &buffer);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return std.mem.bytesToValue(T, &buffer);
|
||||
return std.mem.bytesToValue(Value, &buffer);
|
||||
}
|
||||
|
||||
pub const readNative = switch (builtin.cpu.arch.endian()) {
|
||||
@ -180,7 +208,7 @@ pub const readNative = switch (builtin.cpu.arch.endian()) {
|
||||
.big => readBig,
|
||||
};
|
||||
|
||||
pub fn span(ptr: anytype) Span(@TypeOf(ptr)) {
|
||||
pub fn spanOf(ptr: anytype) Span(@TypeOf(ptr)) {
|
||||
return .{
|
||||
.bytes = switch (@typeInfo(@TypeOf(ptr)).pointer.size) {
|
||||
.slice => std.mem.sliceAsBytes(ptr),
|
||||
@ -250,11 +278,21 @@ pub fn writeAll(output: Writable, data: []const u8) ReadWriteError!void {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn writeN(output: Writable, data: []const u8, count: usize) ReadWriteError!void {
|
||||
var remaining = count;
|
||||
pub fn writeBig(output: Writable, value: anytype) ReadWriteError!void {
|
||||
switch (builtin.cpu.arch.endian()) {
|
||||
.little => {
|
||||
var buffer = std.mem.toBytes(value);
|
||||
|
||||
while (remaining != 0) : (remaining -= 1) {
|
||||
try writeAll(output, data);
|
||||
if (@sizeOf(@TypeOf(value)) > 1) {
|
||||
std.mem.reverse(u8, &buffer);
|
||||
}
|
||||
|
||||
try writeAll(output, &buffer);
|
||||
},
|
||||
|
||||
.big => {
|
||||
try writeAll(output, std.mem.asBytes(&value));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -376,33 +414,30 @@ pub fn writeLittle(output: Writable, value: anytype) ReadWriteError!void {
|
||||
},
|
||||
|
||||
.big => {
|
||||
const Value = @TypeOf(value);
|
||||
var buffer = std.mem.toBytes(value);
|
||||
|
||||
switch (@typeInfo(Value)) {
|
||||
.@"struct", .array => {
|
||||
var copy = value;
|
||||
|
||||
std.mem.byteSwapAllFields(Value, ©);
|
||||
|
||||
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)}));
|
||||
},
|
||||
if (@sizeOf(@TypeOf(value)) > 1) {
|
||||
std.mem.reverse(u8, &buffer);
|
||||
}
|
||||
|
||||
try writeAll(output, &buffer);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn writeN(output: Writable, data: []const u8, count: usize) ReadWriteError!void {
|
||||
var remaining = count;
|
||||
|
||||
while (remaining != 0) : (remaining -= 1) {
|
||||
try writeAll(output, data);
|
||||
}
|
||||
}
|
||||
|
||||
pub const writeNative = switch (builtin.cpu.arch.endian()) {
|
||||
.little => writeLittle,
|
||||
.big => writeBig,
|
||||
};
|
||||
|
||||
fn writeNull(buffer: []const u8) usize {
|
||||
return buffer.len;
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
pub const Box = @import("./Box.zig");
|
||||
|
||||
const Tasks = @import("./Tasks.zig");
|
||||
|
||||
pub const TypeId = @import("./TypeId.zig");
|
||||
|
||||
pub const bytes = @import("./bytes.zig");
|
||||
@ -24,49 +22,6 @@ pub const tree = @import("./tree.zig");
|
||||
|
||||
pub const utf8 = @import("./utf8.zig");
|
||||
|
||||
pub fn CallTask(comptime function: anytype) type {
|
||||
const Function = @TypeOf(function);
|
||||
|
||||
const function_fn = switch (@typeInfo(Function)) {
|
||||
.@"fn" => |@"fn"| @"fn",
|
||||
else => @compileError("expeceted param `function` to be an fn type"),
|
||||
};
|
||||
|
||||
return struct {
|
||||
args: std.meta.ArgsTuple(Function),
|
||||
payload: Return = undefined,
|
||||
resolved: std.Thread.ResetEvent = .{},
|
||||
|
||||
const Return = function_fn.return_type.?;
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn get(self: *const Self) Return {
|
||||
std.debug.assert(self.resolved.isSet());
|
||||
self.resolved.wait();
|
||||
self.resolved.reset();
|
||||
|
||||
return self.payload;
|
||||
}
|
||||
|
||||
pub fn resolve(self: *Self, payload: Return) bool {
|
||||
if (self.resolved.isSet()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.payload = payload;
|
||||
|
||||
self.resolved.set();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn run(self: *Self) void {
|
||||
std.debug.assert(self.resolve(@call(.auto, function, self.args)));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn Callable(comptime Output: type, comptime input_types: []const type) type {
|
||||
const InputTuple = std.meta.Tuple(input_types);
|
||||
|
||||
@ -178,5 +133,3 @@ pub fn expect(function: anytype, args: std.meta.ArgsTuple(@TypeOf(function))) me
|
||||
else => @call(.auto, function, args),
|
||||
};
|
||||
}
|
||||
|
||||
pub var tasks = Tasks{};
|
||||
|
@ -2,74 +2,19 @@ const builtin = @import("builtin");
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
pub fn RefCounting(comptime Payload: type, comptime finalize: fn (*Payload) void) type {
|
||||
const AtomicCount = std.atomic.Value(usize);
|
||||
|
||||
return opaque {
|
||||
const Layout = struct {
|
||||
ref_count: AtomicCount,
|
||||
payload: Payload,
|
||||
|
||||
fn get(self: *Self) *Layout {
|
||||
return @ptrCast(@alignCast(self));
|
||||
}
|
||||
|
||||
fn get_const(self: *const Self) *const Layout {
|
||||
return @ptrCast(@alignCast(self));
|
||||
}
|
||||
};
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn acquire(self: *Self) *const Payload {
|
||||
const layout = Layout.get(self);
|
||||
const ref_count = layout.ref_count.fetchAdd(1, .monotonic);
|
||||
|
||||
std.debug.assert(ref_count != 0);
|
||||
|
||||
return &layout.payload;
|
||||
}
|
||||
|
||||
pub fn create(payload: Payload) std.mem.Allocator.Error!*Self {
|
||||
const allocation = try allocator.create(Layout);
|
||||
|
||||
errdefer {
|
||||
allocator.destroy(allocation);
|
||||
}
|
||||
|
||||
allocation.* = .{
|
||||
.ref_count = AtomicCount.init(1),
|
||||
.payload = payload,
|
||||
};
|
||||
|
||||
return @ptrCast(allocation);
|
||||
}
|
||||
|
||||
pub fn release(self: *Self) void {
|
||||
const layout = Layout.get(self);
|
||||
const ref_count = layout.ref_count.fetchSub(1, .monotonic);
|
||||
|
||||
std.debug.assert(ref_count != 0);
|
||||
|
||||
if (ref_count == 1) {
|
||||
finalize(&layout.payload);
|
||||
allocator.destroy(layout);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn weak(self: *const Self) *const Payload {
|
||||
return &Layout.get_const(self).payload;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub const allocator = switch (builtin.mode) {
|
||||
.ReleaseFast => std.heap.smp_allocator,
|
||||
else => gpa.allocator(),
|
||||
.ReleaseFast, .ReleaseSmall => std.heap.smp_allocator,
|
||||
else => debug_allocator.allocator(),
|
||||
};
|
||||
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{ .thread_safe = true }){};
|
||||
var debug_allocator = switch (builtin.mode) {
|
||||
.ReleaseFast, .ReleaseSmall => {},
|
||||
else => std.heap.DebugAllocator(.{}){},
|
||||
};
|
||||
|
||||
pub fn traceLeaks() void {
|
||||
_ = gpa.detectLeaks();
|
||||
switch (builtin.mode) {
|
||||
.ReleaseFast, .ReleaseSmall => {},
|
||||
else => _ = debug_allocator.detectLeaks(),
|
||||
}
|
||||
}
|
||||
|
@ -22,13 +22,6 @@ pub fn UnwrappedOptional(comptime Value: type) type {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn isContainer(@"type": std.builtin.Type) bool {
|
||||
return switch (@"type") {
|
||||
.@"struct", .@"union", .@"enum", .@"opaque" => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn hasFn(comptime Type: type, comptime fn_name: []const u8) ?std.builtin.Type.Fn {
|
||||
const @"type" = @typeInfo(Type);
|
||||
|
||||
@ -47,3 +40,20 @@ pub fn hasFn(comptime Type: type, comptime fn_name: []const u8) ?std.builtin.Typ
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn isContainer(@"type": std.builtin.Type) bool {
|
||||
return switch (@"type") {
|
||||
.@"struct", .@"union", .@"enum", .@"opaque" => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn parametersMatch(parameters: []const std.builtin.Type.Fn.Param, types: []const type) bool {
|
||||
for (parameters, types) |parameter, Type| {
|
||||
if (parameter.type != Type) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -118,11 +118,11 @@ pub fn Parallel(comptime Item: type) type {
|
||||
else => @compileError("`Item` must be a struct type"),
|
||||
};
|
||||
|
||||
const item_align = @alignOf(Item);
|
||||
const item_align = std.mem.Alignment.of(Item);
|
||||
const item_size = @sizeOf(Item);
|
||||
|
||||
return Generic(Item, struct {
|
||||
ptr: [*]align(item_align) u8 = undefined,
|
||||
ptr: [*]align(item_align.toByteUnits()) u8 = undefined,
|
||||
len: u32 = 0,
|
||||
cap: u32 = 0,
|
||||
|
||||
|
@ -66,67 +66,6 @@ pub fn Binary(comptime Key: type, comptime Value: type, comptime traits: Traits(
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
fn remove(self: *Node, key: Key) ?*Node {
|
||||
const node = self.find(key) orelse {
|
||||
return null;
|
||||
};
|
||||
|
||||
if (node.has_lesser == null) {
|
||||
std.debug.assert(self.transplant(node.has_greater));
|
||||
} else if (node.has_greater == null) {
|
||||
std.debug.assert(self.transplant(node.has_lesser));
|
||||
} else {
|
||||
var successor = node.has_greater.?.getMin();
|
||||
|
||||
if (successor.has_parent != node) {
|
||||
// Move successor up: replace successor with its right child first
|
||||
std.debug.assert(successor.transplant(successor.has_greater));
|
||||
|
||||
// Attach node.right to successor
|
||||
successor.has_greater = node.has_greater;
|
||||
|
||||
if (successor.has_greater) |g| {
|
||||
g.has_parent = successor;
|
||||
}
|
||||
}
|
||||
|
||||
// Replace node with successor
|
||||
std.debug.assert(node.transplant(successor));
|
||||
|
||||
// Attach node.left to successor
|
||||
successor.has_lesser = node.has_lesser;
|
||||
|
||||
if (successor.has_lesser) |l| {
|
||||
l.has_parent = successor;
|
||||
}
|
||||
}
|
||||
|
||||
// Detach the removed node completely and return it
|
||||
node.has_parent = null;
|
||||
node.has_lesser = null;
|
||||
node.has_greater = null;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
fn transplant(self: *Node, has_node: ?*Node) bool {
|
||||
const parent = self.has_parent orelse {
|
||||
return false;
|
||||
};
|
||||
|
||||
if (parent.has_lesser == self) {
|
||||
parent.has_lesser = has_node;
|
||||
} else {
|
||||
parent.has_greater = has_node;
|
||||
}
|
||||
|
||||
if (has_node) |node| {
|
||||
node.has_parent = self.has_parent;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
pub const KeyValues = struct {
|
||||
@ -330,10 +269,45 @@ pub fn Binary(comptime Key: type, comptime Value: type, comptime traits: Traits(
|
||||
return null;
|
||||
};
|
||||
|
||||
const node = root.remove(key) orelse {
|
||||
const node = root.find(key) orelse {
|
||||
return null;
|
||||
};
|
||||
|
||||
if (node.has_lesser == null) {
|
||||
self.transplant(node, node.has_greater);
|
||||
} else if (node.has_greater == null) {
|
||||
self.transplant(node, node.has_lesser);
|
||||
} else {
|
||||
// Successor = min(node.right)
|
||||
var successor = node.has_greater.?.getMin();
|
||||
|
||||
if (successor.has_parent != node) {
|
||||
// move successor up (replace successor with its right child)
|
||||
self.transplant(successor, successor.has_greater);
|
||||
|
||||
// attach node.right to successor
|
||||
successor.has_greater = node.has_greater;
|
||||
|
||||
if (successor.has_greater) |g| {
|
||||
g.has_parent = successor;
|
||||
}
|
||||
}
|
||||
|
||||
// replace node with successor
|
||||
self.transplant(node, successor);
|
||||
|
||||
// attach node.left to successor
|
||||
successor.has_lesser = node.has_lesser;
|
||||
|
||||
if (successor.has_lesser) |l| {
|
||||
l.has_parent = successor;
|
||||
}
|
||||
}
|
||||
|
||||
node.has_parent = null;
|
||||
node.has_lesser = null;
|
||||
node.has_greater = null;
|
||||
|
||||
defer {
|
||||
self.free_nodes.destroy(node);
|
||||
}
|
||||
@ -343,6 +317,25 @@ pub fn Binary(comptime Key: type, comptime Value: type, comptime traits: Traits(
|
||||
.value = node.value,
|
||||
};
|
||||
}
|
||||
|
||||
fn transplant(self: *Self, u: *Node, has_v: ?*Node) void {
|
||||
const u_parent = u.has_parent orelse {
|
||||
// U is root.
|
||||
self.has_root = has_v;
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
if (u_parent.has_lesser == u) {
|
||||
u_parent.has_lesser = has_v;
|
||||
} else {
|
||||
u_parent.has_greater = has_v;
|
||||
}
|
||||
|
||||
if (has_v) |v| {
|
||||
v.has_parent = u.has_parent;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
22
src/demos/crt.frag
Normal file
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,
|
||||
};
|
||||
|
||||
const Effects = struct {
|
||||
render_texture: ona.AssetHandle = .default,
|
||||
image_textures: [2]ona.AssetHandle = [_]ona.AssetHandle{.default} ** 2,
|
||||
const State = struct {
|
||||
images: [2]ona.gfx.Images.Handle = [_]ona.gfx.Images.Handle{.default} ** 2,
|
||||
last_time: f64 = 0,
|
||||
image_index: usize = 0,
|
||||
crt_effect: ona.AssetHandle = .default,
|
||||
crt_effect: ona.gfx.Effects.Handle = .default,
|
||||
loaded_image: ona.gfx.Images.Handle = .default,
|
||||
};
|
||||
|
||||
fn load(display: ona.Write(ona.gfx.Display), state: ona.Write(State), images: ona.gfx.Images, effects: ona.gfx.Effects) !void {
|
||||
display.ptr.size = .{ 1280, 720 };
|
||||
|
||||
state.ptr.loaded_image = try images.load(ona.gfx.QoiImage{
|
||||
.path = try .parse("graphics_image.qoi"),
|
||||
});
|
||||
|
||||
errdefer {
|
||||
images.unload(state.ptr.images[1]);
|
||||
}
|
||||
|
||||
state.ptr.crt_effect = try effects.load(ona.gfx.GlslEffect{
|
||||
.fragment = .{
|
||||
.glsl = @embedFile("crt.frag"),
|
||||
.name = "crt.frag",
|
||||
},
|
||||
});
|
||||
|
||||
errdefer {
|
||||
effects.unload(state.ptr.crt_effect);
|
||||
}
|
||||
|
||||
state.ptr.images[0] = try images.load(ona.gfx.CheckerImage{
|
||||
.colors = .{ .black, .purple },
|
||||
.square_size = 4,
|
||||
.width = 8,
|
||||
.height = 8,
|
||||
});
|
||||
|
||||
errdefer {
|
||||
images.unload(state.ptr.images[0]);
|
||||
}
|
||||
|
||||
state.ptr.images[1] = try images.load(ona.gfx.CheckerImage{
|
||||
.colors = .{ .black, .grey },
|
||||
.square_size = 4,
|
||||
.width = 8,
|
||||
.height = 8,
|
||||
});
|
||||
|
||||
errdefer {
|
||||
images.unload(state.ptr.images[1]);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() void {
|
||||
ona.realtime_app
|
||||
.with(.initModule(ona.hid))
|
||||
.with(.initModule(ona.gfx))
|
||||
// .with(.initSystem(.render, .of(render)))
|
||||
.with(.initState(State{}))
|
||||
.with(.initSystem(.load, .of(load)))
|
||||
.with(.initSystem(.render, .of(render)))
|
||||
.with(.initSystem(.update, .of(update)))
|
||||
.run();
|
||||
}
|
||||
|
||||
// fn update(effects: ona.Write(Effects), loop: ona.Read(ona.Loop)) void {
|
||||
// const update_seconds = 5;
|
||||
fn render(scene: ona.gfx.Scene, state: ona.Read(State), display: ona.Write(ona.gfx.Display), time: ona.Read(ona.App.Time)) void {
|
||||
const width, const height = display.ptr.size;
|
||||
|
||||
// if ((loop.state.elapsed_time - effects.state.last_time) > update_seconds) {
|
||||
// effects.state.image_index = (effects.state.image_index + 1) % effects.state.image_textures.len;
|
||||
// effects.state.last_time = loop.state.elapsed_time;
|
||||
// }
|
||||
// }
|
||||
scene.updateEffect(state.ptr.crt_effect, CRT{
|
||||
.width = @floatFromInt(width),
|
||||
.height = @floatFromInt(height),
|
||||
.time = @floatCast(time.ptr.elapsed),
|
||||
});
|
||||
|
||||
// fn render(commands: ona.gfx.Commands) void {
|
||||
// try commands.setTarget(.{
|
||||
// .texture = effects.state.render_texture,
|
||||
// .clear_color = gfx.colors.black,
|
||||
// .clear_depth = 0,
|
||||
// .clear_stencil = 0,
|
||||
// });
|
||||
scene.drawSprite(state.ptr.images[state.ptr.image_index], .{
|
||||
.size = .{ @floatFromInt(width), @floatFromInt(height) },
|
||||
.effect = state.ptr.crt_effect,
|
||||
});
|
||||
}
|
||||
|
||||
// const width: f32 = @floatFromInt(display.state.width);
|
||||
// const height: f32 = @floatFromInt(display.state.height);
|
||||
fn update(state: ona.Write(State), time: ona.Read(ona.App.Time)) void {
|
||||
const update_seconds = 5;
|
||||
|
||||
// try commands.draw_texture(.{
|
||||
// .texture = effects.state.image_textures[effects.state.image_index],
|
||||
// .size = .{ width, height },
|
||||
// });
|
||||
|
||||
// try commands.set_effect(.{
|
||||
// .effect = effects.state.crt_effect,
|
||||
|
||||
// .properties = std.mem.asBytes(&CRT{
|
||||
// .width = width,
|
||||
// .height = height,
|
||||
// .time = @floatCast(loop.state.elapsed_time),
|
||||
// }),
|
||||
// });
|
||||
|
||||
// try commands.set_target(.{
|
||||
// .clear_color = null,
|
||||
// .clear_depth = null,
|
||||
// .clear_stencil = null,
|
||||
// });
|
||||
|
||||
// try commands.draw_texture(.{
|
||||
// .texture = effects.state.render_texture,
|
||||
// .size = .{ width, height },
|
||||
// });
|
||||
// }
|
||||
if ((time.ptr.elapsed - state.ptr.last_time) > update_seconds) {
|
||||
state.ptr.image_index = (state.ptr.image_index + 1) % state.ptr.images.len;
|
||||
state.ptr.last_time = time.ptr.elapsed;
|
||||
}
|
||||
}
|
||||
|
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 ona = @import("./ona.zig");
|
||||
const ona = @import("ona.zig");
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
initialized_states: coral.map.Hashed(*const coral.TypeId, coral.Box, coral.map.scalarTraits(*const coral.TypeId)),
|
||||
named_systems: coral.tree.Binary([]const u8, SystemGraph, coral.tree.sliceTraits([]const u8)),
|
||||
|
||||
pub const Data = struct {
|
||||
opened_storages: [32]*c.SDL_Storage = undefined,
|
||||
storages_opened: usize = 0,
|
||||
|
||||
pub const AccessError = error{SystemResources};
|
||||
|
||||
pub fn deinit(self: *Data) void {
|
||||
for (self.opened_storages[0..self.storages_opened]) |storage| {
|
||||
_ = c.SDL_CloseStorage(storage);
|
||||
}
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn loadFile(self: Data, path: Path, allocator: std.mem.Allocator) (AccessError || std.mem.Allocator.Error)![]u8 {
|
||||
for (self.opened_storages[0..self.storages_opened]) |storage| {
|
||||
const path_ptr = path.ptr();
|
||||
var file_size: u64 = 0;
|
||||
|
||||
if (c.SDL_GetStorageFileSize(storage, path_ptr, &file_size)) {
|
||||
const file_data = try allocator.alloc(u8, file_size);
|
||||
|
||||
errdefer {
|
||||
allocator.free(file_data);
|
||||
}
|
||||
|
||||
if (c.SDL_ReadStorageFile(storage, path_ptr, @ptrCast(file_data.ptr), file_data.len)) {
|
||||
return file_data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return error.SystemResources;
|
||||
}
|
||||
|
||||
pub fn openNative(self: *Data, native_path: [:0]const u8) error{SystemResources}!void {
|
||||
if (self.storages_opened >= self.opened_storages.len) {
|
||||
return error.SystemResources;
|
||||
}
|
||||
|
||||
const storage = &self.opened_storages[self.storages_opened];
|
||||
|
||||
storage.* = c.SDL_OpenTitleStorage(native_path, 0) orelse {
|
||||
return error.SystemResources;
|
||||
};
|
||||
|
||||
self.storages_opened += 1;
|
||||
|
||||
while (!c.SDL_StorageReady(storage.*)) {
|
||||
c.SDL_Delay(1);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn readFile(self: Data, path: Path, output: []u8) AccessError!void {
|
||||
for (self.storages[0..self.storages_opened]) |storage| {
|
||||
if (c.SDL_ReadStorageFile(storage, path.ptr(), @ptrCast(output.ptr), output.len)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return error.SystemResources;
|
||||
}
|
||||
|
||||
pub fn sizeFile(self: Data, path: Path) AccessError!usize {
|
||||
var file_size: u64 = 0;
|
||||
|
||||
for (self.storages[0..self.storages_opened]) |storage| {
|
||||
if (c.SDL_GetStorageFileSize(storage, path.ptr(), &file_size)) {
|
||||
return file_size;
|
||||
}
|
||||
}
|
||||
|
||||
return error.SystemResources;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Exit = union(enum) {
|
||||
success,
|
||||
failure: anyerror,
|
||||
};
|
||||
|
||||
pub const Path = struct {
|
||||
buffer: [max]u8,
|
||||
unused: u8,
|
||||
|
||||
pub const ParseError = error{
|
||||
BadPath,
|
||||
};
|
||||
|
||||
pub const empty = Path{
|
||||
.buffer = std.mem.zeroes([max]u8),
|
||||
.unused = max,
|
||||
};
|
||||
|
||||
const max = std.math.maxInt(u8);
|
||||
|
||||
pub fn parse(data: []const u8) ParseError!Path {
|
||||
if (data.len == 0) {
|
||||
return error.BadPath;
|
||||
}
|
||||
|
||||
if (data.len > max) {
|
||||
return error.BadPath;
|
||||
}
|
||||
|
||||
var path = empty;
|
||||
|
||||
path.unused -= @intCast(data.len);
|
||||
|
||||
@memcpy(path.buffer[0..data.len], data);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
pub fn ptr(self: *const Path) [*:0]const u8 {
|
||||
return @ptrCast(&self.buffer);
|
||||
}
|
||||
};
|
||||
|
||||
pub const RunError = std.mem.Allocator.Error || error{
|
||||
MissingDependency,
|
||||
};
|
||||
@ -25,7 +141,7 @@ pub const RunError = std.mem.Allocator.Error || error{
|
||||
const Self = @This();
|
||||
|
||||
pub const Time = struct {
|
||||
elapsed: f64,
|
||||
elapsed: f64 = 0,
|
||||
};
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
@ -57,7 +173,7 @@ fn scheduleName(comptime schedule: anytype) [:0]const u8 {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn hasState(self: *const Self, comptime State: type) ?*State {
|
||||
pub fn getState(self: *const Self, comptime State: type) ?*State {
|
||||
if (self.initialized_states.get(.of(State))) |boxed_state| {
|
||||
return boxed_state.has(State).?;
|
||||
}
|
||||
@ -65,22 +181,34 @@ pub fn hasState(self: *const Self, comptime State: type) ?*State {
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn init() error{OutOfMemory}!Self {
|
||||
pub fn init() error{ OutOfMemory, SystemResources }!Self {
|
||||
var self = Self{
|
||||
.initialized_states = .empty,
|
||||
.named_systems = .empty,
|
||||
};
|
||||
|
||||
try self.setState(Time{
|
||||
.elapsed = 0,
|
||||
});
|
||||
|
||||
try self.setState(Data{});
|
||||
try self.setState(Time{});
|
||||
try ona.registerChannel(&self, Exit);
|
||||
|
||||
const data = self.getState(Data).?;
|
||||
|
||||
try data.openNative(std.mem.span(c.SDL_GetBasePath()));
|
||||
|
||||
if (builtin.mode == .Debug) {
|
||||
const current_directory = c.SDL_GetCurrentDirectory();
|
||||
|
||||
defer {
|
||||
c.SDL_free(current_directory);
|
||||
}
|
||||
|
||||
try data.openNative(std.mem.span(current_directory));
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn on(self: *Self, comptime schedule: anytype, behavior: *const Behavior) error{OutOfMemory}!void {
|
||||
pub fn on(self: *Self, comptime schedule: anytype, behavior: *const ona.System) error{OutOfMemory}!void {
|
||||
const schedule_name = scheduleName(schedule);
|
||||
const systems = self.named_systems.get(schedule_name) orelse (try self.named_systems.insert(coral.heap.allocator, schedule_name, .{})).?;
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
const coral = @import("coral");
|
||||
|
||||
const ona = @import("../ona.zig");
|
||||
const ona = @import("ona.zig");
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
@ -52,7 +52,17 @@ pub fn initModule(comptime namespace: anytype) Self {
|
||||
return init(namespace.setup);
|
||||
}
|
||||
|
||||
pub fn initSystem(comptime schedule: anytype, comptime behavior: *const ona.App.Behavior) Self {
|
||||
pub fn initState(comptime state: anytype) Self {
|
||||
const state_initialization = struct {
|
||||
fn apply(app: *ona.App) !void {
|
||||
try app.setState(state);
|
||||
}
|
||||
};
|
||||
|
||||
return init(state_initialization.apply);
|
||||
}
|
||||
|
||||
pub fn initSystem(comptime schedule: anytype, comptime behavior: *const ona.System) Self {
|
||||
const system_initialization = struct {
|
||||
fn apply(app: *ona.App) !void {
|
||||
try app.on(schedule, behavior);
|
@ -1,8 +1,8 @@
|
||||
const SystemGraph = @import("./SystemGraph.zig");
|
||||
const SystemGraph = @import("SystemGraph.zig");
|
||||
|
||||
const coral = @import("coral");
|
||||
|
||||
const ona = @import("../ona.zig");
|
||||
const ona = @import("ona.zig");
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
@ -143,8 +143,6 @@ pub fn of(comptime function: anytype) *const Self {
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Errdefer deinit the param states.
|
||||
|
||||
return .init(coral.heap.allocator, param_states);
|
||||
}
|
||||
|
||||
@ -182,9 +180,10 @@ pub fn of(comptime function: anytype) *const Self {
|
||||
},
|
||||
|
||||
.pointer => |pointer| {
|
||||
init_args[arg_index] = app.hasState(pointer.child) orelse {
|
||||
@panic(std.fmt.comptimePrint("{s} is a required state but not present in the App", .{
|
||||
init_args[arg_index] = app.getState(pointer.child) orelse {
|
||||
@panic(std.fmt.comptimePrint("{s} is a required state but not present in the App. {s}", .{
|
||||
@typeName(pointer.child),
|
||||
"Did you mean to add it with App.putState?",
|
||||
}));
|
||||
};
|
||||
},
|
@ -1,6 +1,6 @@
|
||||
const coral = @import("coral");
|
||||
|
||||
const ona = @import("../ona.zig");
|
||||
const ona = @import("ona.zig");
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
@ -15,7 +15,7 @@ run_arena: std.heap.ArenaAllocator = .init(coral.heap.allocator),
|
||||
|
||||
const AccessMap = coral.tree.Binary(*const coral.TypeId, BehaviorSet, coral.tree.scalarTraits(*const coral.TypeId));
|
||||
|
||||
const BehaviorSet = coral.stack.Sequential(*const ona.App.Behavior);
|
||||
const BehaviorSet = coral.stack.Sequential(*const ona.System);
|
||||
|
||||
const Edge = struct {
|
||||
dependencies: BehaviorSet = .empty,
|
||||
@ -30,7 +30,7 @@ const Edge = struct {
|
||||
};
|
||||
|
||||
fn Map(comptime Payload: type) type {
|
||||
return coral.tree.Binary(*const ona.App.Behavior, Payload, coral.tree.scalarTraits(*const ona.App.Behavior));
|
||||
return coral.tree.Binary(*const ona.System, Payload, coral.tree.scalarTraits(*const ona.System));
|
||||
}
|
||||
|
||||
const Processed = struct {
|
||||
@ -38,7 +38,7 @@ const Processed = struct {
|
||||
};
|
||||
|
||||
const Work = struct {
|
||||
behavior: *const ona.App.Behavior,
|
||||
behavior: *const ona.System,
|
||||
local_state: coral.Box,
|
||||
};
|
||||
|
||||
@ -55,6 +55,16 @@ pub const TypeDependency = struct {
|
||||
is_read_only: bool,
|
||||
};
|
||||
|
||||
pub fn applyDeferred(self: *Self, app: *ona.App) void {
|
||||
for (self.parallel_work.items.slice()) |work| {
|
||||
work.behavior.apply(work.local_state, app);
|
||||
}
|
||||
|
||||
for (self.blocking_work.items.slice()) |work| {
|
||||
work.behavior.apply(work.local_state, app);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
const allocator = coral.heap.allocator;
|
||||
|
||||
@ -75,20 +85,20 @@ pub fn deinit(self: *Self) void {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dependOnBehavior(self: *Self, app: *ona.App, dependant: *const ona.App.Behavior, dependency: *const ona.App.Behavior) std.mem.Allocator.Error!void {
|
||||
pub fn dependOnBehavior(self: *Self, app: *ona.App, dependant: *const ona.System, dependency: *const ona.System) std.mem.Allocator.Error!void {
|
||||
try self.insert(app, dependant);
|
||||
|
||||
const edge = self.edges.get(dependant).?;
|
||||
|
||||
if (std.mem.indexOfScalar(*const ona.App.Behavior, edge.dependencies.items.slice(), dependency) == null) {
|
||||
if (std.mem.indexOfScalar(*const ona.System, edge.dependencies.items.slice(), dependency) == null) {
|
||||
try edge.dependencies.pushGrow(coral.heap.allocator, dependency);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dependOnType(self: *Self, app: *ona.App, dependant: *const ona.App.Behavior, dependency: TypeDependency) std.mem.Allocator.Error!void {
|
||||
pub fn dependOnType(self: *Self, app: *ona.App, dependant: *const ona.System, dependency: TypeDependency) std.mem.Allocator.Error!void {
|
||||
const readers = self.state_readers.get(dependency.id) orelse (try self.state_readers.insert(coral.heap.allocator, dependency.id, .empty)).?;
|
||||
|
||||
if (std.mem.indexOfScalar(*const ona.App.Behavior, readers.items.slice(), dependant)) |index| {
|
||||
if (std.mem.indexOfScalar(*const ona.System, readers.items.slice(), dependant)) |index| {
|
||||
for (readers.items.slice()[0..index]) |reader| {
|
||||
try self.dependOnBehavior(app, dependant, reader);
|
||||
}
|
||||
@ -107,7 +117,7 @@ pub fn dependOnType(self: *Self, app: *ona.App, dependant: *const ona.App.Behavi
|
||||
if (!dependency.is_read_only) {
|
||||
const writers = self.state_writers.get(dependency.id) orelse (try self.state_writers.insert(coral.heap.allocator, dependency.id, .empty)).?;
|
||||
|
||||
if (std.mem.indexOfScalar(*const ona.App.Behavior, writers.items.slice(), dependant)) |index| {
|
||||
if (std.mem.indexOfScalar(*const ona.System, writers.items.slice(), dependant)) |index| {
|
||||
for (writers.items.slice()[0..index]) |reader| {
|
||||
try self.dependOnBehavior(app, dependant, reader);
|
||||
}
|
||||
@ -125,17 +135,7 @@ pub fn dependOnType(self: *Self, app: *ona.App, dependant: *const ona.App.Behavi
|
||||
}
|
||||
}
|
||||
|
||||
pub fn applyDeferred(self: *Self, app: *ona.App) void {
|
||||
for (self.parallel_work.items.slice()) |work| {
|
||||
work.behavior.apply(work.local_state, app);
|
||||
}
|
||||
|
||||
for (self.blocking_work.items.slice()) |work| {
|
||||
work.behavior.apply(work.local_state, app);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(self: *Self, app: *ona.App, behavior: *const ona.App.Behavior) std.mem.Allocator.Error!void {
|
||||
pub fn insert(self: *Self, app: *ona.App, behavior: *const ona.System) std.mem.Allocator.Error!void {
|
||||
self.processed.clear();
|
||||
|
||||
// TODO: Refactor so partial initialisation isn't happening here anymore.
|
||||
@ -151,7 +151,7 @@ pub fn insert(self: *Self, app: *ona.App, behavior: *const ona.App.Behavior) std
|
||||
}
|
||||
}
|
||||
|
||||
fn process(self: *Self, behavior: *const ona.App.Behavior, edge: Edge) !Processed {
|
||||
fn process(self: *Self, behavior: *const ona.System, edge: Edge) !Processed {
|
||||
var processed = Processed{ .is_blocking = behavior.is_blocking };
|
||||
|
||||
for (edge.dependencies.items.slice()) |dependency| {
|
||||
@ -218,7 +218,7 @@ pub fn run(self: *Self, app: *const ona.App) RunError!void {
|
||||
var parallel_work = self.parallel_work.items.slice();
|
||||
|
||||
for (self.parallel_work_ranges.items.slice()) |parallel_work_range| {
|
||||
_ = try coral.tasks.create(run_allocator, .high_priority, coral.CallTask(runWorkGroup){
|
||||
_ = try ona.tasks.create(run_allocator, .high_priority, ona.tasks.CallTask(runWorkGroup){
|
||||
.args = .{ app, parallel_work[0..parallel_work_range] },
|
||||
});
|
||||
|
||||
@ -226,7 +226,7 @@ pub fn run(self: *Self, app: *const ona.App) RunError!void {
|
||||
}
|
||||
}
|
||||
|
||||
coral.tasks.finish(.high_priority);
|
||||
ona.tasks.finish(.high_priority);
|
||||
runWorkGroup(app, self.blocking_work.items.slice());
|
||||
}
|
||||
|
@ -1,84 +1,291 @@
|
||||
const ona = @import("ona.zig");
|
||||
|
||||
const coral = @import("coral");
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
pub const Handle = packed struct {
|
||||
index: u24,
|
||||
salt: u20,
|
||||
type_id: u20,
|
||||
pub const GenericHandle = struct {
|
||||
type_id: *const coral.TypeId,
|
||||
index: u32,
|
||||
salt: u32,
|
||||
|
||||
pub fn reify(self: GenericHandle, comptime Asset: type) ?Handle(Asset) {
|
||||
if (self.type_id != coral.TypeId.of(Asset)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return .{
|
||||
.index = self.index,
|
||||
.salt = self.salt,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Path = struct {
|
||||
buffer: [256]u8,
|
||||
};
|
||||
pub fn Handle(comptime Asset: type) type {
|
||||
return packed struct(u64) {
|
||||
index: u32,
|
||||
salt: u32,
|
||||
|
||||
pub fn Store(comptime Asset: type) type {
|
||||
const Self = @This();
|
||||
|
||||
pub const asset_type_id = coral.TypeId.of(Asset);
|
||||
|
||||
pub const default: Self = @bitCast(@as(u64, 0));
|
||||
|
||||
pub fn generic(self: Self) GenericHandle {
|
||||
return .{
|
||||
.type_id = asset_type_id,
|
||||
.index = self.index,
|
||||
.salt = self.salt,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn ResolveQueue(comptime Asset: type) type {
|
||||
return struct {
|
||||
released_handles: coral.stack.Sequential(Handle) = .empty,
|
||||
arena: std.heap.ArenaAllocator = .init(coral.heap.allocator),
|
||||
|
||||
assets: coral.stack.Parallel(struct {
|
||||
asset: Asset,
|
||||
state: AssetState,
|
||||
tasks: coral.stack.Sequential(struct {
|
||||
future: *ona.tasks.Future(?Asset),
|
||||
handle: AssetHandle,
|
||||
}) = .empty,
|
||||
|
||||
const AssetState = struct {
|
||||
usage: enum { vacant, reserved, occupied },
|
||||
salt: u20,
|
||||
};
|
||||
const AssetHandle = Handle(Asset);
|
||||
|
||||
pub fn reserve(self: *Store) std.mem.Allocator.Error!Handle {
|
||||
if (self.released_handles.pop()) |handle| {
|
||||
const state = &self.assets.items.slice(.state)[handle.index];
|
||||
fn ResolveTask(comptime Loadable: type) type {
|
||||
const load_fn_name = "load";
|
||||
|
||||
state.usage = .reserved;
|
||||
const load_fn = coral.meta.hasFn(Loadable, load_fn_name) orelse {
|
||||
@compileError(std.fmt.comptimePrint("{s} must contain a .{s} fn", .{
|
||||
@typeName(Loadable),
|
||||
load_fn_name,
|
||||
}));
|
||||
};
|
||||
|
||||
return handle;
|
||||
const all_args: [2]type = .{ Loadable, ona.App.Data };
|
||||
const loadable_type_name = @typeName(Loadable);
|
||||
|
||||
if (load_fn.params.len > all_args.len or !coral.meta.parametersMatch(load_fn.params, all_args[0..load_fn.params.len])) {
|
||||
@compileError(std.fmt.comptimePrint("Fn {s}.{s} must accept only {s} or ({s}, {s}) to be a valid loadable type", .{
|
||||
loadable_type_name,
|
||||
load_fn_name,
|
||||
loadable_type_name,
|
||||
loadable_type_name,
|
||||
@typeName(ona.App.Data),
|
||||
}));
|
||||
}
|
||||
|
||||
const handle = Handle{
|
||||
.type_id = type_id,
|
||||
.index = self.assets.items.len,
|
||||
const Result = coral.meta.UnwrappedError(load_fn.return_type.?);
|
||||
|
||||
if (Result != Asset) {
|
||||
const result_type_name = @typeName(Result);
|
||||
|
||||
@compileError(std.fmt.comptimePrint("Fn {s}.{s} must return {s} or !{s}, not {s}", .{
|
||||
loadable_type_name,
|
||||
load_fn_name,
|
||||
result_type_name,
|
||||
result_type_name,
|
||||
@typeName(load_fn.return_type.?),
|
||||
}));
|
||||
}
|
||||
|
||||
return struct {
|
||||
loadable: Loadable,
|
||||
future: ona.tasks.Future(?Result) = .unresolved,
|
||||
app_data: ona.App.Data,
|
||||
|
||||
pub fn run(self: *@This()) void {
|
||||
const loaded = switch (load_fn.params.len) {
|
||||
1 => self.loadable.load(),
|
||||
2 => self.loadable.load(self.app_data),
|
||||
else => unreachable,
|
||||
};
|
||||
|
||||
std.debug.assert(self.future.resolve(loaded catch |load_error| nullify: {
|
||||
std.log.err("Failed to resolve {s}: {s}", .{
|
||||
loadable_type_name,
|
||||
@errorName(load_error),
|
||||
});
|
||||
|
||||
break :nullify null;
|
||||
}));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn apply(self: *Self, app: *ona.App) void {
|
||||
if (!self.tasks.isEmpty()) {
|
||||
const store = app.getState(ResolvedStorage(Asset)).?;
|
||||
|
||||
for (self.tasks.items.slice()) |tasks| {
|
||||
std.debug.assert(store.resolve(tasks.handle, tasks.future.get()));
|
||||
}
|
||||
|
||||
self.tasks.clear();
|
||||
std.debug.assert(self.arena.reset(.free_all));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load(self: *Self, app_data: ona.App.Data, reserved_handle: AssetHandle, loadable: anytype) error{OutOfMemory}!void {
|
||||
try self.tasks.pushGrow(coral.heap.allocator, undefined);
|
||||
|
||||
errdefer {
|
||||
std.debug.assert(self.tasks.pop() != null);
|
||||
}
|
||||
|
||||
const resolve = try ona.tasks.create(self.arena.allocator(), .low_priority, ResolveTask(@TypeOf(loadable)){
|
||||
.loadable = loadable,
|
||||
.app_data = app_data,
|
||||
});
|
||||
|
||||
std.debug.assert(self.tasks.set(.{
|
||||
.future = &resolve.future,
|
||||
.handle = reserved_handle,
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
std.debug.assert(self.tasks.isEmpty());
|
||||
self.tasks.deinit(coral.heap.allocator);
|
||||
self.arena.deinit();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn ResolvedStorage(comptime Asset: type) type {
|
||||
return struct {
|
||||
free_index: u32 = 0,
|
||||
|
||||
asset_entries: coral.stack.Parallel(struct {
|
||||
value: Value,
|
||||
entry: Entry,
|
||||
|
||||
const Entry = struct {
|
||||
usage: enum { vacant, reserved, occupied, missing },
|
||||
salt: u32,
|
||||
};
|
||||
|
||||
const Value = union {
|
||||
asset: Asset,
|
||||
free_index: u32,
|
||||
};
|
||||
}) = .empty,
|
||||
|
||||
const AssetHandle = Handle(Asset);
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
// TODO: Cleanup assets.
|
||||
// for (self.asset_entries.items.slice(.entry), 0 .. self.asset_entries.items.len) |entry, i| {
|
||||
// if (entry != .occupied) {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// self.asset_entries.items.slice(.value)[i].asset.deinit();
|
||||
// }
|
||||
|
||||
self.asset_entries.deinit(coral.heap.allocator);
|
||||
}
|
||||
|
||||
pub fn get(self: *const Self, handle: AssetHandle) error{ExpiredHandle}!?Asset {
|
||||
const entry = &self.asset_entries.items.slice(.entry)[handle.index];
|
||||
|
||||
if (handle.salt != entry.salt) {
|
||||
return error.ExpiredHandle;
|
||||
}
|
||||
|
||||
const value = &self.asset_entries.items.slice(.value)[handle.index];
|
||||
|
||||
return switch (entry.usage) {
|
||||
.vacant => error.ExpiredHandle,
|
||||
.reserved, .missing => null,
|
||||
.occupied => value.asset,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn remove(self: *Self, handle: AssetHandle) ?Asset {
|
||||
const entry = &self.asset_entries.items.slice(.entry)[handle.index];
|
||||
|
||||
if (handle.salt != entry.salt) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (entry.usage != .occupied) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const value = &self.asset_entries.items.slice(.value)[handle.index];
|
||||
|
||||
defer {
|
||||
entry.usage = .vacant;
|
||||
entry.salt = @max(1, entry.salt +% 1);
|
||||
value.* = .{ .free_index = self.free_index };
|
||||
self.free_index = value.free_index;
|
||||
}
|
||||
|
||||
return value.asset;
|
||||
}
|
||||
|
||||
pub fn reserve(self: *Self) error{OutOfMemory}!AssetHandle {
|
||||
if (self.free_index < self.asset_entries.items.len) {
|
||||
const entry = &self.asset_entries.items.slice(.entry)[self.free_index];
|
||||
|
||||
defer {
|
||||
self.free_index = self.asset_entries.items.slice(.value)[self.free_index].free_index;
|
||||
entry.usage = .reserved;
|
||||
}
|
||||
|
||||
return .{
|
||||
.index = self.free_index,
|
||||
.salt = entry.salt,
|
||||
};
|
||||
}
|
||||
|
||||
const handle = AssetHandle{
|
||||
.index = self.asset_entries.items.len,
|
||||
.salt = 1,
|
||||
};
|
||||
|
||||
try self.assets.pushGrow(coral.heap.allocator, .{
|
||||
.asset = undefined,
|
||||
try self.asset_entries.pushGrow(coral.heap.allocator, .{
|
||||
.value = undefined,
|
||||
|
||||
.state = .{
|
||||
.entry = .{
|
||||
.usage = .reserved,
|
||||
.salt = handle.salt,
|
||||
},
|
||||
});
|
||||
|
||||
self.free_index += 1;
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
pub fn resolve(self: *Store, reserved_handle: Handle, asset: Asset) bool {
|
||||
const state = self.assets.items.slice(.state)[reserved_handle.index];
|
||||
pub fn resolve(self: *Self, reserved_handle: AssetHandle, resolved_asset: ?Asset) bool {
|
||||
const index = reserved_handle.index;
|
||||
const entry = &self.asset_entries.items.slice(.entry)[index];
|
||||
|
||||
if (state.usage != .reserved) {
|
||||
if (entry.usage != .reserved) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (reserved_handle.salt != state.salt) {
|
||||
if (reserved_handle.salt != entry.salt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.assets.items.slice(.asset)[reserved_handle.index] = asset;
|
||||
if (resolved_asset) |asset| {
|
||||
entry.usage = .occupied;
|
||||
self.asset_entries.items.slice(.value)[index] = .{ .asset = asset };
|
||||
} else {
|
||||
entry.usage = .missing;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const type_id = generateTypeId();
|
||||
};
|
||||
}
|
||||
|
||||
fn generateTypeId() error{Overflow}!u20 {
|
||||
const id = struct {
|
||||
var last_assigned: u20 = 0;
|
||||
};
|
||||
|
||||
id.last_assigned = try std.math.add(u20, id.last_assigned, 1);
|
||||
|
||||
return id.last_assigned;
|
||||
}
|
||||
pub const extension_len_max = 7;
|
||||
|
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 c = @cImport({
|
||||
@cInclude("SDL3/SDL.h");
|
||||
@cInclude("shaderc/shaderc.h");
|
||||
});
|
||||
const device = @import("gfx/device.zig");
|
||||
|
||||
const glsl = @import("./gfx/glsl.zig");
|
||||
const glsl = @import("gfx/glsl.zig");
|
||||
|
||||
const ona = @import("./ona.zig");
|
||||
const ona = @import("ona.zig");
|
||||
|
||||
const qoi = @import("gfx/qoi.zig");
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
pub const Commands = struct {
|
||||
pending: *Context.Queue,
|
||||
pub const CheckerImage = struct {
|
||||
square_size: u32,
|
||||
colors: [2]Color,
|
||||
width: u32,
|
||||
height: u32,
|
||||
|
||||
pub fn init(context: *const Context) !Commands {
|
||||
return .{
|
||||
.pending = try context.shared.acquireQueue(),
|
||||
pub fn load(self: CheckerImage) !device.Image {
|
||||
var image = try device.Image.init(self.width, self.height, .rgba8, .{ .is_input = true });
|
||||
|
||||
errdefer {
|
||||
image.deinit();
|
||||
}
|
||||
|
||||
const image_size = image.height * image.width * device.Image.Format.rgba8.stride();
|
||||
|
||||
{
|
||||
var image_memory = try device.Memory.init(image_size);
|
||||
|
||||
defer {
|
||||
image_memory.deinit();
|
||||
}
|
||||
|
||||
if (self.square_size == 0) {
|
||||
try coral.bytes.writeN(image_memory.writer(), &self.colors[0].toRgba8(), self.width * self.height);
|
||||
} else {
|
||||
for (0..self.width * self.height) |i| {
|
||||
const x = i % self.width;
|
||||
const y = i / self.width;
|
||||
const square_x = x / self.square_size;
|
||||
const square_y = y / self.square_size;
|
||||
const color_index = (square_x + square_y) % 2;
|
||||
|
||||
try coral.bytes.writeAll(image_memory.writer(), &self.colors[color_index].toRgba8());
|
||||
}
|
||||
}
|
||||
|
||||
image_memory.commit();
|
||||
|
||||
const commands = try device.acquireCommands();
|
||||
|
||||
commands.copyPass().uploadImage(image, image_memory).finish();
|
||||
commands.submit();
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
};
|
||||
|
||||
const Context = struct {
|
||||
swapchain: device.Swapchain,
|
||||
backbuffer: device.Target,
|
||||
swap_buffers: [2]Buffer = .{ .{}, .{} },
|
||||
is_swapped: bool = false,
|
||||
frame_arena: std.heap.ArenaAllocator = .init(coral.heap.allocator),
|
||||
renderer: ?*RenderTask = null,
|
||||
|
||||
const Buffer = struct {
|
||||
head: ?*Queue = null,
|
||||
tail: ?*Queue = null,
|
||||
|
||||
fn dequeue(self: *Buffer) ?*Queue {
|
||||
if (self.head) |head| {
|
||||
self.head = head.next;
|
||||
head.next = null;
|
||||
|
||||
if (self.head == null) {
|
||||
self.tail = null;
|
||||
}
|
||||
|
||||
return head;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
fn enqueue(self: *Buffer, node: *Queue) void {
|
||||
if (self.tail) |tail| {
|
||||
std.debug.assert(self.head != null);
|
||||
|
||||
tail.next = node;
|
||||
self.tail = node;
|
||||
} else {
|
||||
self.head = node;
|
||||
self.tail = node;
|
||||
}
|
||||
|
||||
node.next = null;
|
||||
}
|
||||
};
|
||||
|
||||
const RenderTask = struct {
|
||||
backbuffer: device.Target,
|
||||
submitted_buffer: *Buffer,
|
||||
finished: std.Thread.ResetEvent = .{},
|
||||
|
||||
pub fn run(self: *RenderTask) void {
|
||||
defer {
|
||||
self.finished.set();
|
||||
}
|
||||
|
||||
const commands = device.acquireCommands() catch |acquire_error| {
|
||||
return std.log.err("Render task failed: {s}", .{@errorName(acquire_error)});
|
||||
};
|
||||
|
||||
var render_pass = commands.renderPass(self.backbuffer);
|
||||
|
||||
while (self.submitted_buffer.dequeue()) |renderables| {
|
||||
defer {
|
||||
renderables.reset();
|
||||
}
|
||||
|
||||
for (renderables.commands.items.slice()) |command| {
|
||||
switch (command) {}
|
||||
}
|
||||
}
|
||||
|
||||
render_pass.finish();
|
||||
commands.submit();
|
||||
}
|
||||
};
|
||||
|
||||
pub fn deinit(self: *Context) void {
|
||||
if (self.renderer) |renderer| {
|
||||
renderer.finished.wait();
|
||||
}
|
||||
|
||||
self.frame_arena.deinit();
|
||||
self.backbuffer.deinit();
|
||||
self.swapchain.deinit();
|
||||
device.stop();
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn finish(self: *Context) void {
|
||||
if (self.renderer) |renderer| {
|
||||
renderer.finished.wait();
|
||||
}
|
||||
|
||||
if (!self.frame_arena.reset(.retain_capacity)) {
|
||||
std.log.warn("Failed to retain capacity of {s} frame allocator, this may impact performance", .{
|
||||
@typeName(Context),
|
||||
});
|
||||
}
|
||||
|
||||
self.renderer = null;
|
||||
self.is_swapped = !self.is_swapped;
|
||||
|
||||
self.swapchain.present(self.backbuffer);
|
||||
}
|
||||
|
||||
pub fn render(self: *Context) void {
|
||||
self.renderer = ona.tasks.create(self.frame_arena.allocator(), .multimedia, RenderTask{
|
||||
.submitted_buffer = &self.swap_buffers[@intFromBool(!self.is_swapped)],
|
||||
.backbuffer = self.backbuffer,
|
||||
}) catch |creation_error| {
|
||||
return std.log.warn("{s}", .{switch (creation_error) {
|
||||
error.OutOfMemory => "Not enough memory was avaialble to start a render, skipping frame...",
|
||||
}});
|
||||
};
|
||||
}
|
||||
|
||||
pub fn submit(self: *Context, renderables: *Queue) void {
|
||||
self.swap_buffers[@intFromBool(self.is_swapped)].enqueue(renderables);
|
||||
}
|
||||
};
|
||||
|
||||
pub const Display = struct {
|
||||
width: u16,
|
||||
height: u16,
|
||||
size: [2]u16,
|
||||
is_hidden: bool,
|
||||
};
|
||||
|
||||
pub fn synchronize(commands: ona.Commands) !void {
|
||||
const buffer_swap = struct {
|
||||
fn apply(app: *ona.App) void {
|
||||
const context = app.hasState(Context).?;
|
||||
const display = app.hasState(Display).?;
|
||||
pub const Effects = ona.Assets(device.Effect);
|
||||
|
||||
context.shared.swapBuffers(display.*);
|
||||
pub const Images = ona.Assets(device.Image);
|
||||
|
||||
pub const QoiImage = struct {
|
||||
path: ona.App.Path,
|
||||
|
||||
pub fn load(self: QoiImage, data: ona.App.Data) !device.Image {
|
||||
var qoi_data = coral.bytes.spanOf(try data.loadFile(self.path, coral.heap.allocator));
|
||||
|
||||
defer {
|
||||
coral.heap.allocator.free(qoi_data.bytes);
|
||||
}
|
||||
|
||||
const header = try qoi.Info.decode(qoi_data.reader());
|
||||
var image_memory = try device.Memory.init(header.width * header.height * device.Image.Format.rgba8.stride());
|
||||
|
||||
defer {
|
||||
image_memory.deinit();
|
||||
}
|
||||
|
||||
{
|
||||
var decoder = qoi.DecodeStream.init(header);
|
||||
|
||||
while (try decoder.fetch(qoi_data.reader())) |run| {
|
||||
try coral.bytes.writeN(image_memory.writer(), &run.pixel.bytes(), run.count);
|
||||
}
|
||||
}
|
||||
|
||||
image_memory.commit();
|
||||
|
||||
var image = try device.Image.init(header.width, header.height, .rgba8, .{
|
||||
.is_input = true,
|
||||
});
|
||||
|
||||
errdefer {
|
||||
image.deinit();
|
||||
}
|
||||
|
||||
const commands = try device.acquireCommands();
|
||||
|
||||
commands.copyPass().uploadImage(image, image_memory).finish();
|
||||
commands.submit();
|
||||
|
||||
return image;
|
||||
}
|
||||
};
|
||||
|
||||
const Queue = struct {
|
||||
commands: coral.stack.Sequential(Command) = .empty,
|
||||
next: ?*Queue = null,
|
||||
|
||||
const Command = union(enum) {};
|
||||
|
||||
pub fn apply(self: *Queue, app: *ona.App) void {
|
||||
if (!self.commands.isEmpty()) {
|
||||
const context = app.getState(Context).?;
|
||||
|
||||
context.submit(self);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(self: *Queue) void {
|
||||
self.commands.clear();
|
||||
}
|
||||
};
|
||||
|
||||
pub const Scene = struct {
|
||||
renderables: *Queue,
|
||||
image_store: *const Images.Store,
|
||||
effect_store: *const Effects.Store,
|
||||
|
||||
pub const Rect = struct {
|
||||
left: f32,
|
||||
top: f32,
|
||||
right: f32,
|
||||
bottom: f32,
|
||||
};
|
||||
|
||||
try commands.push(buffer_swap.apply);
|
||||
pub const SpriteDraw = struct {
|
||||
anchor: [2]f32 = @splat(0),
|
||||
size: ?@Vector(2, f32) = null,
|
||||
source: Rect = .{ .left = 0, .top = 0, .right = 1, .bottom = 1 },
|
||||
position: [2]f32 = @splat(0),
|
||||
tint: Color = .white,
|
||||
rotation: f32 = 0,
|
||||
depth: f32 = 0,
|
||||
effect: Effects.Handle = .default,
|
||||
};
|
||||
|
||||
pub fn bind() Queue {
|
||||
return .{};
|
||||
}
|
||||
|
||||
pub fn drawSprite(self: Scene, image: Images.Handle, draw: SpriteDraw) void {
|
||||
_ = self;
|
||||
_ = image;
|
||||
_ = draw;
|
||||
}
|
||||
|
||||
pub fn updateEffect(self: Scene, effect: Effects.Handle, properties: anytype) void {
|
||||
_ = self;
|
||||
_ = effect;
|
||||
_ = properties;
|
||||
}
|
||||
|
||||
pub fn updateImage(self: Scene, effect: Effects.Handle, properties: anytype) void {
|
||||
_ = self;
|
||||
_ = effect;
|
||||
_ = properties;
|
||||
}
|
||||
|
||||
pub fn init(renderables: *Queue, image_store: *const Images.Store, effect_store: *const Effects.Store) Scene {
|
||||
return .{
|
||||
.renderables = renderables,
|
||||
.image_store = image_store,
|
||||
.effect_store = effect_store,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const GlslEffect = struct {
|
||||
fragment: Source = .empty,
|
||||
vertex: Source = .empty,
|
||||
|
||||
pub const Source = glsl.Assembly.Source;
|
||||
|
||||
pub fn load(self: GlslEffect) !device.Effect {
|
||||
var arena = std.heap.ArenaAllocator.init(coral.heap.allocator);
|
||||
|
||||
defer {
|
||||
arena.deinit();
|
||||
}
|
||||
|
||||
var assembly = try glsl.Assembly.init(.spirv, switch (builtin.mode) {
|
||||
.ReleaseSafe, .Debug => .unoptimized,
|
||||
.ReleaseFast => .optimize_speed,
|
||||
.ReleaseSmall => .optimize_size,
|
||||
});
|
||||
|
||||
defer {
|
||||
assembly.deinit();
|
||||
}
|
||||
|
||||
const Effect = extern struct {
|
||||
time: f32,
|
||||
};
|
||||
|
||||
const Camera = extern struct {
|
||||
projection: [4]@Vector(4, f32),
|
||||
};
|
||||
|
||||
const arena_allocator = arena.allocator();
|
||||
const effect_uniform_block = glsl.nativeUniformBlock(0, Effect);
|
||||
const camera_uniform_block = glsl.nativeUniformBlock(1, Camera);
|
||||
const default_shader_name = "[unnamed shader]";
|
||||
const glsl_version = 430;
|
||||
|
||||
return device.Effect.init(.{
|
||||
.fragment_code = try assembly.assemble(arena_allocator, .fragment, .{
|
||||
.glsl = try glsl.inject(arena_allocator, switch (self.fragment.glsl.len) {
|
||||
0 => @embedFile("gfx/canvas.frag"),
|
||||
else => self.fragment.glsl,
|
||||
}, glsl_version, .{
|
||||
.vertex_uv = glsl.nativeInput(0, @Vector(2, f32)),
|
||||
.vertex_rgba = glsl.nativeInput(1, @Vector(4, f32)),
|
||||
.image = glsl.Sampler{ .sampler_2d = 0 },
|
||||
.color = glsl.nativeOutput(0, @Vector(4, f32)),
|
||||
.effect = effect_uniform_block,
|
||||
.camera = camera_uniform_block,
|
||||
}),
|
||||
|
||||
.name = switch (self.fragment.name.len) {
|
||||
0 => default_shader_name,
|
||||
else => self.fragment.name,
|
||||
},
|
||||
}),
|
||||
|
||||
.vertex_code = try assembly.assemble(arena_allocator, .vertex, .{
|
||||
.glsl = try glsl.inject(arena_allocator, switch (self.vertex.glsl.len) {
|
||||
0 => @embedFile("gfx/canvas.vert"),
|
||||
else => self.vertex.glsl,
|
||||
}, glsl_version, .{
|
||||
.model_xy = glsl.nativeInput(0, @Vector(2, f32)),
|
||||
.model_uv = glsl.nativeInput(1, @Vector(2, f32)),
|
||||
.model_rgba = glsl.nativeInput(2, @Vector(4, f32)),
|
||||
.instance_uv_offset = glsl.nativeInput(3, @Vector(2, f32)),
|
||||
.instance_uv_scale = glsl.nativeInput(4, @Vector(2, f32)),
|
||||
.instance_xbasis = glsl.nativeInput(5, @Vector(2, f32)),
|
||||
.instance_ybasis = glsl.nativeInput(6, @Vector(2, f32)),
|
||||
.instance_origin = glsl.nativeInput(7, @Vector(2, f32)),
|
||||
.instance_pivot = glsl.nativeInput(8, @Vector(2, f32)),
|
||||
.instance_rgb = glsl.nativeInput(9, @Vector(3, f32)),
|
||||
.instance_bits = glsl.nativeInput(10, u32),
|
||||
.uv = glsl.nativeOutput(0, @Vector(2, f32)),
|
||||
.rgba = glsl.nativeOutput(1, @Vector(4, f32)),
|
||||
.effect = effect_uniform_block,
|
||||
.camera = camera_uniform_block,
|
||||
}),
|
||||
|
||||
.name = switch (self.vertex.name.len) {
|
||||
0 => default_shader_name,
|
||||
else => self.vertex.name,
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
pub fn renderFrame(context: ona.Exclusive(Context), display: ona.Read(Display)) void {
|
||||
if (display.ptr.is_hidden) {
|
||||
_ = context.ptr.swapchain.hide();
|
||||
} else {
|
||||
_ = context.ptr.swapchain.show();
|
||||
}
|
||||
|
||||
context.ptr.render();
|
||||
}
|
||||
|
||||
pub fn setup(app: *ona.App) !void {
|
||||
if (!c.SDL_Init(c.SDL_INIT_VIDEO)) {
|
||||
return error.SystemFailure;
|
||||
try device.start();
|
||||
|
||||
errdefer {
|
||||
device.stop();
|
||||
}
|
||||
|
||||
const default_display = Display{
|
||||
.width = 1280,
|
||||
.height = 720,
|
||||
.is_hidden = false,
|
||||
};
|
||||
var arena = std.heap.ArenaAllocator.init(coral.heap.allocator);
|
||||
|
||||
try app.setState(default_display);
|
||||
defer {
|
||||
arena.deinit();
|
||||
}
|
||||
|
||||
// var shader_compiler = try glsl.Assembly.init(.spirv, switch (builtin.mode) {
|
||||
// .Debug, .ReleaseSafe => .unoptimized,
|
||||
// .ReleaseSmall => .optimize_size,
|
||||
// .ReleaseFast => .optimize_speed,
|
||||
// });
|
||||
|
||||
// errdefer {
|
||||
// shader_compiler.deinit();
|
||||
// }
|
||||
const default_width, const default_height = .{ 1280, 720 };
|
||||
|
||||
{
|
||||
var context = try Context.init(default_display);
|
||||
var swapchain = try device.Swapchain.init(default_width, default_height, "Ona");
|
||||
|
||||
errdefer {
|
||||
context.deinit();
|
||||
swapchain.deinit();
|
||||
}
|
||||
|
||||
try app.setState(context);
|
||||
var backbuffer = try device.Target.init(default_width, default_height);
|
||||
|
||||
errdefer {
|
||||
backbuffer.deinit();
|
||||
}
|
||||
|
||||
try app.setState(Context{
|
||||
.swapchain = swapchain,
|
||||
.backbuffer = backbuffer,
|
||||
});
|
||||
}
|
||||
|
||||
try app.on(.render, .of(synchronize));
|
||||
try app.setState(Display{
|
||||
.size = .{ default_width, default_height },
|
||||
.is_hidden = false,
|
||||
});
|
||||
|
||||
try ona.registerAsset(app, device.Image);
|
||||
try ona.registerAsset(app, device.Effect);
|
||||
try app.on(.pre_update, .of(renderFrame));
|
||||
try app.on(.post_update, .of(swapBuffers));
|
||||
}
|
||||
|
||||
pub fn swapBuffers(context: ona.Exclusive(Context)) void {
|
||||
context.ptr.finish();
|
||||
}
|
||||
|
34
src/ona/gfx/Color.zig
Normal file
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() {
|
||||
const vec2 local_xy = model_xy + instance_pivot;
|
||||
const vec2 world_position = instance_origin + (local_xy.x * instance_xbasis) + (local_xy.y * instance_ybasis);
|
||||
const vec2 projected = (camera.projection * vec4(world_position, 0, 1)).xy;
|
||||
const vec2 projected = (camera.projection * vec4(world_position, 0, 1)).xy;
|
||||
const vec2 depth_alpha = unpackHalf2x16(instance_bits);
|
||||
|
||||
gl_Position = vec4(projected, depth_alpha.x, 1.0);
|
||||
gl_Position = vec4(projected, depth_alpha.x, 1.0);
|
||||
|
||||
rgba = model_rgba * vec4(instance_rgb, depth_alpha.y);
|
||||
uv = instance_uv_offset + (model_uv * instance_uv_scale);
|
||||
rgba = model_rgba * vec4(instance_rgb, depth_alpha.y);
|
||||
uv = instance_uv_offset + (model_uv * instance_uv_scale);
|
||||
}
|
||||
|
@ -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 ona = @import("../ona.zig");
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
///
|
||||
@ -24,6 +22,27 @@ pub const Assembly = struct {
|
||||
optimize_size,
|
||||
};
|
||||
|
||||
///
|
||||
/// Textual GLSL source code with an associated name.
|
||||
///
|
||||
pub const Source = struct {
|
||||
name: [:0]const u8,
|
||||
glsl: []const u8,
|
||||
|
||||
pub const empty = Source{
|
||||
.name = "",
|
||||
.glsl = "",
|
||||
};
|
||||
};
|
||||
|
||||
///
|
||||
/// Different types of shader execution stages.
|
||||
///
|
||||
pub const Stage = enum {
|
||||
vertex,
|
||||
fragment,
|
||||
};
|
||||
|
||||
///
|
||||
/// Target format used for shader binaries.
|
||||
///
|
||||
@ -50,13 +69,12 @@ pub const Assembly = struct {
|
||||
self: Assembly,
|
||||
allocator: std.mem.Allocator,
|
||||
stage: Stage,
|
||||
name: [*:0]const u8,
|
||||
source: []const u8,
|
||||
) (std.mem.Allocator.Error || error{BadSyntax})![]const u8 {
|
||||
const result = c.shaderc_compile_into_spv(self.shader_compiler, source.ptr, source.len, switch (stage) {
|
||||
source: Source,
|
||||
) (std.mem.Allocator.Error || error{BadSyntax})![]u8 {
|
||||
const result = c.shaderc_compile_into_spv(self.compiler, source.glsl.ptr, source.glsl.len, switch (stage) {
|
||||
.fragment => c.shaderc_glsl_fragment_shader,
|
||||
.vertex => c.shaderc_glsl_vertex_shader,
|
||||
}, name, "main", self.spirv_options) orelse {
|
||||
}, source.name.ptr, "main", self.options) orelse {
|
||||
return error.OutOfMemory;
|
||||
};
|
||||
|
||||
@ -74,7 +92,7 @@ pub const Assembly = struct {
|
||||
|
||||
c.shaderc_compilation_status_compilation_error, c.shaderc_compilation_status_invalid_stage => {
|
||||
std.log.err("{s}", .{c.shaderc_result_get_error_message(result)});
|
||||
std.log.debug("problematic shader:\n{s}", .{source});
|
||||
std.log.debug("problematic shader:\n{s}", .{source.glsl});
|
||||
|
||||
return error.BadSyntax;
|
||||
},
|
||||
@ -85,8 +103,8 @@ pub const Assembly = struct {
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Assembly) void {
|
||||
c.shaderc_compile_options_release(self.spirv_options);
|
||||
c.shaderc_compiler_release(self.shader_compiler);
|
||||
c.shaderc_compile_options_release(self.options);
|
||||
c.shaderc_compiler_release(self.compiler);
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
@ -212,11 +230,6 @@ pub const Sampler = union(enum) {
|
||||
}
|
||||
};
|
||||
|
||||
pub const Stage = enum {
|
||||
vertex,
|
||||
fragment,
|
||||
};
|
||||
|
||||
pub const UniformBlock = struct {
|
||||
name: [:0]const u8,
|
||||
binding: usize,
|
||||
@ -321,7 +334,7 @@ pub fn inject(allocator: std.mem.Allocator, source_base: []const u8, version: us
|
||||
try field_value.serialize(field.name, output);
|
||||
}
|
||||
|
||||
try coral.bytes.writeAll(output, "#line 1\n");
|
||||
try coral.bytes.writeAll(output, "#line 0\n");
|
||||
}
|
||||
};
|
||||
|
||||
|
177
src/ona/gfx/qoi.zig
Normal file
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");
|
||||
|
||||
pub const gfx = @import("./gfx.zig");
|
||||
pub const gfx = @import("gfx.zig");
|
||||
|
||||
pub const hid = @import("./hid.zig");
|
||||
pub const hid = @import("hid.zig");
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
pub const AssetHandle = asset.Handle;
|
||||
|
||||
pub const AssetPath = asset.Path;
|
||||
pub const tasks = @import("tasks.zig");
|
||||
|
||||
pub fn Assets(comptime Asset: type) type {
|
||||
const Store = asset.Store(Asset);
|
||||
|
||||
return struct {
|
||||
cached: *Store,
|
||||
app_data: App.Data,
|
||||
resolved: *Store,
|
||||
resolves: *ResolveQueue,
|
||||
|
||||
pub const Handle = asset.Handle(Asset);
|
||||
|
||||
const ResolveQueue = asset.ResolveQueue(Asset);
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn init(store: *Store) Self {
|
||||
pub const Store = asset.ResolvedStorage(Asset);
|
||||
|
||||
pub fn bind() ResolveQueue {
|
||||
return .{};
|
||||
}
|
||||
|
||||
pub fn init(resolves: *ResolveQueue, resolved: *Store, app_data: *const App.Data) Self {
|
||||
return .{
|
||||
.store = store,
|
||||
.resolved = resolved,
|
||||
.resolves = resolves,
|
||||
.app_data = app_data.*,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn insert(self: Self, uncached_asset: Asset) std.mem.Allocator.Error!AssetHandle {
|
||||
const reserved_handle = try self.store.reserve();
|
||||
pub fn load(self: Self, loadable: anytype) error{OutOfMemory}!Handle {
|
||||
const reserved_handle = try self.resolved.reserve();
|
||||
|
||||
std.debug.assert(self.store.resolve(reserved_handle, uncached_asset));
|
||||
errdefer {
|
||||
std.debug.assert(self.resolved.resolve(reserved_handle, null));
|
||||
}
|
||||
|
||||
try self.resolves.load(self.app_data, reserved_handle, loadable);
|
||||
|
||||
return reserved_handle;
|
||||
}
|
||||
|
||||
pub fn load(self: Self, load_path: AssetPath) std.mem.Allocator.Error!AssetHandle {
|
||||
const reserved_handle = try self.store.reserve();
|
||||
|
||||
_ = load_path;
|
||||
|
||||
return reserved_handle;
|
||||
pub fn unload(self: Self, handle: Handle) void {
|
||||
// TODO: Implement.
|
||||
_ = self.resolved.remove(handle);
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -230,33 +245,20 @@ pub fn Write(comptime State: type) type {
|
||||
};
|
||||
}
|
||||
|
||||
pub const realtime_app = App.Setup.init(run_realtime_loop);
|
||||
pub const realtime_app = Setup.init(realtimeLoop);
|
||||
|
||||
pub fn registerAsset(app: *App, comptime Asset: type) std.mem.Allocator.Error!void {
|
||||
const AssetStore = asset.Store(Asset);
|
||||
|
||||
try app.setState(AssetStore{});
|
||||
}
|
||||
|
||||
pub fn registerChannel(app: *App, comptime Message: type) std.mem.Allocator.Error!void {
|
||||
const MessageChannel = Channel(Message);
|
||||
|
||||
try app.on(.post_update, .of(MessageChannel.swap));
|
||||
try app.setState(MessageChannel{});
|
||||
}
|
||||
|
||||
fn run_realtime_loop(app: *App) !void {
|
||||
// ext.SDL_SetLogPriorities(ext.SDL_LOG_PRIORITY_VERBOSE);
|
||||
// ext.SDL_SetLogOutputFunction(sdl_log, null);
|
||||
fn realtimeLoop(app: *App) !void {
|
||||
// c.SDL_SetLogPriorities(c.SDL_LOG_PRIORITY_VERBOSE);
|
||||
// c.SDL_SetLogOutputFunction(sdl_log, null);
|
||||
const updates_per_frame = 60.0;
|
||||
const target_frame_time = 1.0 / updates_per_frame;
|
||||
const time = app.hasState(App.Time).?;
|
||||
const exit_channel = app.hasState(Channel(App.Exit)).?;
|
||||
const time = app.getState(App.Time).?;
|
||||
const exit_channel = app.getState(Channel(App.Exit)).?;
|
||||
|
||||
try coral.tasks.start();
|
||||
try tasks.start();
|
||||
|
||||
defer {
|
||||
coral.tasks.stop();
|
||||
tasks.stop();
|
||||
}
|
||||
|
||||
try app.run(.load);
|
||||
@ -280,7 +282,6 @@ fn run_realtime_loop(app: *App) !void {
|
||||
}
|
||||
|
||||
try app.run(.post_update);
|
||||
try app.run(.render);
|
||||
|
||||
const exit_messages = exit_channel.messages();
|
||||
|
||||
@ -299,3 +300,15 @@ fn run_realtime_loop(app: *App) !void {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn registerAsset(app: *App, comptime Asset: type) error{OutOfMemory}!void {
|
||||
try app.setState(asset.ResolvedStorage(Asset){});
|
||||
try app.setState(asset.ResolveQueue(Asset){});
|
||||
}
|
||||
|
||||
pub fn registerChannel(app: *App, comptime Message: type) error{OutOfMemory}!void {
|
||||
const MessageChannel = Channel(Message);
|
||||
|
||||
try app.on(.post_update, .of(MessageChannel.swap));
|
||||
try app.setState(MessageChannel{});
|
||||
}
|
||||
|
192
src/ona/tasks.zig
Normal file
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");
|
||||
|
||||
threads_active: std.atomic.Value(usize) = .init(0),
|
||||
threads: []std.Thread = &.{},
|
||||
buffer: [max]Runnable = undefined,
|
||||
head: usize = 0,
|
||||
tail: usize = 0,
|
||||
@ -63,8 +63,6 @@ pub fn finish(self: *Self) void {
|
||||
}
|
||||
|
||||
fn main(self: *Self) void {
|
||||
_ = self.threads_active.fetchAdd(1, .monotonic);
|
||||
|
||||
while (true) {
|
||||
const runner = self.dequeue();
|
||||
|
||||
@ -75,14 +73,6 @@ fn main(self: *Self) void {
|
||||
runner.call(.{});
|
||||
self.waiting.finish();
|
||||
}
|
||||
|
||||
const previous_threads_active = self.threads_active.fetchSub(1, .monotonic);
|
||||
|
||||
std.debug.assert(previous_threads_active != 0);
|
||||
|
||||
if (previous_threads_active == 1) {
|
||||
coral.heap.allocator.destroy(self);
|
||||
}
|
||||
}
|
||||
|
||||
const max = 512;
|
||||
@ -90,12 +80,19 @@ const max = 512;
|
||||
fn poisonPill() void {}
|
||||
|
||||
pub fn stop(self: *Self) void {
|
||||
var threads_remaining = self.threads_active.load(.monotonic);
|
||||
var threads_remaining = self.threads.len;
|
||||
const poison_pill = Runnable.initFn(poisonPill);
|
||||
|
||||
while (threads_remaining != 0) : (threads_remaining -= 1) {
|
||||
self.enqueue(poison_pill);
|
||||
}
|
||||
|
||||
for (self.threads) |thread| {
|
||||
thread.join();
|
||||
}
|
||||
|
||||
coral.heap.allocator.free(self.threads);
|
||||
coral.heap.allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn spawn(thread_count: usize) std.Thread.SpawnError!*Self {
|
||||
@ -105,12 +102,20 @@ pub fn spawn(thread_count: usize) std.Thread.SpawnError!*Self {
|
||||
|
||||
const self = try coral.heap.allocator.create(Self);
|
||||
|
||||
self.* = .{};
|
||||
errdefer {
|
||||
coral.heap.allocator.destroy(self);
|
||||
}
|
||||
|
||||
var threads_spawned: usize = 0;
|
||||
self.* = .{
|
||||
.threads = try coral.heap.allocator.alloc(std.Thread, thread_count),
|
||||
};
|
||||
|
||||
while (threads_spawned < thread_count) : (threads_spawned += 1) {
|
||||
(try std.Thread.spawn(.{}, main, .{self})).detach();
|
||||
errdefer {
|
||||
coral.heap.allocator.free(self.threads);
|
||||
}
|
||||
|
||||
for (self.threads) |*thread| {
|
||||
thread.* = try .spawn(.{}, main, .{self});
|
||||
}
|
||||
|
||||
return self;
|
Loading…
x
Reference in New Issue
Block a user