Asset system v2
This commit is contained in:
parent
be4972e75e
commit
a788a4fc25
22
build.zig
22
build.zig
@ -119,7 +119,24 @@ pub fn build(b: *std.Build) void {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
const shaderc_dependency = b.dependency("shaderc_zig", .{});
|
const spirv_reflect_dependency = b.dependency("SPIRV-Reflect", .{});
|
||||||
|
|
||||||
|
const spirv_reflect_lib = b.addLibrary(.{
|
||||||
|
.linkage = .static,
|
||||||
|
.name = "SPIRV-Reflect",
|
||||||
|
.root_module = b.createModule(.{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
spirv_reflect_lib.addCSourceFile(.{
|
||||||
|
.file = spirv_reflect_dependency.path("spirv_reflect.c"),
|
||||||
|
.flags = &.{ "-Wall", "-Werror" },
|
||||||
|
});
|
||||||
|
|
||||||
|
spirv_reflect_lib.installHeadersDirectory(spirv_reflect_dependency.path("./"), "spirv_reflect", .{});
|
||||||
|
spirv_reflect_lib.linkLibC();
|
||||||
|
|
||||||
const coral_module = b.addModule("coral", .{
|
const coral_module = b.addModule("coral", .{
|
||||||
.root_source_file = b.path("src/coral/coral.zig"),
|
.root_source_file = b.path("src/coral/coral.zig"),
|
||||||
@ -141,8 +158,11 @@ pub fn build(b: *std.Build) void {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const shaderc_dependency = b.dependency("shaderc_zig", .{});
|
||||||
|
|
||||||
ona_module.linkLibrary(shaderc_dependency.artifact("shaderc"));
|
ona_module.linkLibrary(shaderc_dependency.artifact("shaderc"));
|
||||||
ona_module.linkLibrary(config.sdl_dependency.artifact("SDL3"));
|
ona_module.linkLibrary(config.sdl_dependency.artifact("SDL3"));
|
||||||
|
ona_module.linkLibrary(spirv_reflect_lib);
|
||||||
|
|
||||||
// config.addShaders(ona_module, &.{
|
// config.addShaders(ona_module, &.{
|
||||||
// "./src/ona/gfx/effect_shader.zig",
|
// "./src/ona/gfx/effect_shader.zig",
|
||||||
|
|||||||
@ -7,6 +7,10 @@
|
|||||||
.url = "git+https://github.com/tiawl/shaderc.zig#69b67221988aa84c91447775ad6157e4e80bab00",
|
.url = "git+https://github.com/tiawl/shaderc.zig#69b67221988aa84c91447775ad6157e4e80bab00",
|
||||||
.hash = "shaderc_zig-1.0.0-mOl846VjAwDV8YlqQFVvFsWsBa6dLNSiskpTy7lC1hmD",
|
.hash = "shaderc_zig-1.0.0-mOl846VjAwDV8YlqQFVvFsWsBa6dLNSiskpTy7lC1hmD",
|
||||||
},
|
},
|
||||||
|
.@"SPIRV-Reflect" = .{
|
||||||
|
.url = "https://github.com/KhronosGroup/SPIRV-Reflect/archive/refs/tags/vulkan-sdk-1.3.290.0.tar.gz",
|
||||||
|
.hash = "N-V-__8AAPjZJwAYPrmeP82KPbIdkLRkudSrnIDw1La5q5pj",
|
||||||
|
},
|
||||||
.sdl = .{
|
.sdl = .{
|
||||||
.url = "git+https://github.com/castholm/SDL.git#b1913e7c31ad72ecfd3ab04aeac387027754cfaf",
|
.url = "git+https://github.com/castholm/SDL.git#b1913e7c31ad72ecfd3ab04aeac387027754cfaf",
|
||||||
.hash = "sdl-0.3.0+3.2.22-7uIn9Pg3fwGG2IyIOPxxOSVe-75nUng9clt7tXGFLzMr",
|
.hash = "sdl-0.3.0+3.2.22-7uIn9Pg3fwGG2IyIOPxxOSVe-75nUng9clt7tXGFLzMr",
|
||||||
|
|||||||
@ -2,6 +2,50 @@ const builtin = @import("builtin");
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn Arc(comptime Value: type) type {
|
||||||
|
const Payload = struct {
|
||||||
|
ref_count: std.atomic.Value(usize),
|
||||||
|
value: Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
return struct {
|
||||||
|
ptr: *Value,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn acquire(self: *Self) *Self {
|
||||||
|
const payload: Payload = @fieldParentPtr("value", self.ptr);
|
||||||
|
|
||||||
|
_ = payload.owners_referencing.fetchAdd(1, .monotonic);
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(value: Value) error{OutOfMemory}!Self {
|
||||||
|
const allocation = try allocator.create(Payload);
|
||||||
|
|
||||||
|
allocation.* = .{
|
||||||
|
.ref_count = .init(1),
|
||||||
|
.value = value,
|
||||||
|
};
|
||||||
|
|
||||||
|
return &allocation.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn release(self: *Self) ?Value {
|
||||||
|
const payload: Payload = @fieldParentPtr("value", self.ptr);
|
||||||
|
|
||||||
|
if (payload.ref_count.fetchSub(1, .monotonic) == 1) {
|
||||||
|
defer {
|
||||||
|
allocator.destroy(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub const allocator = switch (builtin.mode) {
|
pub const allocator = switch (builtin.mode) {
|
||||||
.ReleaseFast, .ReleaseSmall => std.heap.smp_allocator,
|
.ReleaseFast, .ReleaseSmall => std.heap.smp_allocator,
|
||||||
else => debug_allocator.allocator(),
|
else => debug_allocator.allocator(),
|
||||||
|
|||||||
@ -67,9 +67,13 @@ fn Generic(comptime Item: type, comptime Storage: type) type {
|
|||||||
|
|
||||||
const index = self.items.len;
|
const index = self.items.len;
|
||||||
|
|
||||||
self.items.len = new_len;
|
self.items.len = std.math.cast(u32, new_len) orelse {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
std.debug.assert(self.items.sliced(index, self.items.len).copy(items));
|
for (index..self.items.len, items) |i, item| {
|
||||||
|
std.debug.assert(self.items.set(i, item));
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -85,13 +89,13 @@ fn Generic(comptime Item: type, comptime Storage: type) type {
|
|||||||
pub fn pushMany(self: *Self, n: usize, item: Item) bool {
|
pub fn pushMany(self: *Self, n: usize, item: Item) bool {
|
||||||
const new_len = self.items.len + n;
|
const new_len = self.items.len + n;
|
||||||
|
|
||||||
if (new_len > self.cap) {
|
if (new_len > self.items.cap) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const offset = self.items.len;
|
const offset = self.items.len;
|
||||||
|
|
||||||
self.items.len = new_len;
|
self.items.len = @intCast(new_len);
|
||||||
|
|
||||||
for (offset..(offset + n)) |i| {
|
for (offset..(offset + n)) |i| {
|
||||||
std.debug.assert(self.items.set(i, item));
|
std.debug.assert(self.items.set(i, item));
|
||||||
@ -100,6 +104,14 @@ fn Generic(comptime Item: type, comptime Storage: type) type {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn reserve(self: *Self, allocator: std.mem.Allocator, n: usize) std.mem.Allocator.Error!void {
|
||||||
|
const grow_amount = std.math.sub(usize, n, self.items.cap) catch {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
try self.items.grow(allocator, grow_amount);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set(self: *Self, item: Item) bool {
|
pub fn set(self: *Self, item: Item) bool {
|
||||||
if (self.isEmpty()) {
|
if (self.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -2,64 +2,45 @@ const ona = @import("ona");
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const CRT = extern struct {
|
const CRT = struct {
|
||||||
width: f32,
|
|
||||||
height: f32,
|
|
||||||
time: f32,
|
time: f32,
|
||||||
padding: [4]u8 = undefined,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const State = struct {
|
const State = struct {
|
||||||
images: [2]ona.gfx.Images.Handle = [_]ona.gfx.Images.Handle{.default} ** 2,
|
images: [2]ona.asset.Handle(ona.gfx.Image) = [_]ona.asset.Handle(ona.gfx.Image){.none} ** 2,
|
||||||
last_time: f64 = 0,
|
last_time: f64 = 0,
|
||||||
image_index: usize = 0,
|
image_index: usize = 0,
|
||||||
crt_effect: ona.gfx.Effects.Handle = .default,
|
crt_effect: ona.asset.Handle(ona.gfx.Effect) = .none,
|
||||||
loaded_image: ona.gfx.Images.Handle = .default,
|
loaded_image: ona.asset.Handle(ona.gfx.Image) = .none,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn load(display: ona.Write(ona.gfx.Display), state: ona.Write(State), images: ona.gfx.Images, effects: ona.gfx.Effects) !void {
|
fn load(display: ona.Write(ona.gfx.Display), state: ona.Write(State), assets: ona.Read(ona.asset.Queue)) !void {
|
||||||
display.ptr.size = .{ 1280, 720 };
|
display.ptr.size = .{ 1280, 720 };
|
||||||
|
|
||||||
state.ptr.loaded_image = try images.load(ona.gfx.QoiImage{
|
state.ptr.loaded_image = try assets.ptr.load(ona.gfx.QoiImage{
|
||||||
.path = try .parse("graphics_image.qoi"),
|
.path = try .parse("graphics_image.qoi"),
|
||||||
});
|
});
|
||||||
|
|
||||||
errdefer {
|
state.ptr.crt_effect = try assets.ptr.load(ona.gfx.GlslEffect{
|
||||||
images.unload(state.ptr.images[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
state.ptr.crt_effect = try effects.load(ona.gfx.GlslEffect{
|
|
||||||
.fragment = .{
|
.fragment = .{
|
||||||
.glsl = @embedFile("crt.frag"),
|
.glsl = @embedFile("graphics_crt.frag"),
|
||||||
.name = "crt.frag",
|
.name = "graphics_crt.frag",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
errdefer {
|
state.ptr.images[0] = try assets.ptr.load(ona.gfx.CheckerImage{
|
||||||
effects.unload(state.ptr.crt_effect);
|
|
||||||
}
|
|
||||||
|
|
||||||
state.ptr.images[0] = try images.load(ona.gfx.CheckerImage{
|
|
||||||
.colors = .{ .black, .purple },
|
.colors = .{ .black, .purple },
|
||||||
.square_size = 4,
|
.square_size = 4,
|
||||||
.width = 8,
|
.width = 8,
|
||||||
.height = 8,
|
.height = 8,
|
||||||
});
|
});
|
||||||
|
|
||||||
errdefer {
|
state.ptr.images[1] = try assets.ptr.load(ona.gfx.CheckerImage{
|
||||||
images.unload(state.ptr.images[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
state.ptr.images[1] = try images.load(ona.gfx.CheckerImage{
|
|
||||||
.colors = .{ .black, .grey },
|
.colors = .{ .black, .grey },
|
||||||
.square_size = 4,
|
.square_size = 4,
|
||||||
.width = 8,
|
.width = 8,
|
||||||
.height = 8,
|
.height = 8,
|
||||||
});
|
});
|
||||||
|
|
||||||
errdefer {
|
|
||||||
images.unload(state.ptr.images[1]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main() void {
|
pub fn main() void {
|
||||||
@ -73,14 +54,14 @@ pub fn main() void {
|
|||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(scene: ona.gfx.Scene, state: ona.Read(State), display: ona.Write(ona.gfx.Display), time: ona.Read(ona.App.Time)) void {
|
fn render(scene: ona.gfx.Scene, state: ona.Read(State), display: ona.Write(ona.gfx.Display), time: ona.Read(ona.App.Time), effects: ona.Write(ona.asset.Store(ona.gfx.Effect))) !void {
|
||||||
const width, const height = display.ptr.size;
|
const width, const height = display.ptr.size;
|
||||||
|
|
||||||
scene.updateEffect(state.ptr.crt_effect, CRT{
|
if (effects.ptr.modify(state.ptr.crt_effect) catch unreachable) |effect| {
|
||||||
.width = @floatFromInt(width),
|
try effect.setProperties(.fragment, CRT{
|
||||||
.height = @floatFromInt(height),
|
.time = @floatCast(time.ptr.elapsed),
|
||||||
.time = @floatCast(time.ptr.elapsed),
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
scene.drawSprite(state.ptr.images[state.ptr.image_index], .{
|
scene.drawSprite(state.ptr.images[state.ptr.image_index], .{
|
||||||
.size = .{ @floatFromInt(width), @floatFromInt(height) },
|
.size = .{ @floatFromInt(width), @floatFromInt(height) },
|
||||||
|
|||||||
@ -1,4 +1,8 @@
|
|||||||
|
|
||||||
|
properties {
|
||||||
|
float time;
|
||||||
|
};
|
||||||
|
|
||||||
vec2 crt(vec2 coords, float bend) {
|
vec2 crt(vec2 coords, float bend) {
|
||||||
vec2 symmetrical_coords = (coords - 0.5) * 2.0;
|
vec2 symmetrical_coords = (coords - 0.5) * 2.0;
|
||||||
|
|
||||||
@ -13,7 +17,7 @@ vec2 crt(vec2 coords, float bend) {
|
|||||||
void main() {
|
void main() {
|
||||||
const vec2 crt_coords = crt(vertex_uv, 4.8);
|
const vec2 crt_coords = crt(vertex_uv, 4.8);
|
||||||
const float scanlineCount = 480.0;
|
const float scanlineCount = 480.0;
|
||||||
const float scan = sin(crt_coords.y * scanlineCount + effect.time * 29.0);
|
const float scan = sin(crt_coords.y * scanlineCount + time * 29.0);
|
||||||
|
|
||||||
const vec3 image_color = texture(image, crt_coords).rgb;
|
const vec3 image_color = texture(image, crt_coords).rgb;
|
||||||
const vec3 shaded = image_color - vec3(scan * 0.02);
|
const vec3 shaded = image_color - vec3(scan * 0.02);
|
||||||
@ -96,6 +96,22 @@ pub const Exit = union(enum) {
|
|||||||
failure: anyerror,
|
failure: anyerror,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const Frame = enum(u32) {
|
||||||
|
_,
|
||||||
|
|
||||||
|
pub fn init(value: u32) Frame {
|
||||||
|
return @enumFromInt(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn increment(self: *Frame) void {
|
||||||
|
self.* = @enumFromInt(@intFromEnum(self.*) +% 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn count(self: Frame) u32 {
|
||||||
|
return @intFromEnum(self);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
pub const Path = struct {
|
pub const Path = struct {
|
||||||
buffer: [max]u8,
|
buffer: [max]u8,
|
||||||
unused: u8,
|
unused: u8,
|
||||||
@ -173,8 +189,10 @@ fn scheduleName(comptime schedule: anytype) [:0]const u8 {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getState(self: *const Self, comptime State: type) ?*State {
|
pub fn getState(self: Self, comptime State: type) ?*State {
|
||||||
if (self.initialized_states.get(.of(State))) |boxed_state| {
|
const state_id = coral.TypeId.of(State);
|
||||||
|
|
||||||
|
if (self.initialized_states.get(state_id)) |boxed_state| {
|
||||||
return boxed_state.has(State).?;
|
return boxed_state.has(State).?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,8 +205,9 @@ pub fn init() error{ OutOfMemory, SystemResources }!Self {
|
|||||||
.named_systems = .empty,
|
.named_systems = .empty,
|
||||||
};
|
};
|
||||||
|
|
||||||
try self.setState(Data{});
|
|
||||||
try self.setState(Time{});
|
try self.setState(Time{});
|
||||||
|
try self.setState(Data{});
|
||||||
|
try self.setState(Frame.init(1));
|
||||||
try ona.registerChannel(&self, Exit);
|
try ona.registerChannel(&self, Exit);
|
||||||
|
|
||||||
const data = self.getState(Data).?;
|
const data = self.getState(Data).?;
|
||||||
|
|||||||
@ -110,9 +110,15 @@ pub fn of(comptime function: anytype) *const Self {
|
|||||||
@compileError(std.fmt.comptimePrint("{s} must have a .init fn to be used as a behavior param", .{@typeName(Param)}));
|
@compileError(std.fmt.comptimePrint("{s} must have a .init fn to be used as a behavior param", .{@typeName(Param)}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const has_bind_fn = coral.meta.hasFn(Param, "bind");
|
const bind_fn = coral.meta.hasFn(Param, "bind");
|
||||||
|
|
||||||
inline for (init_fn.params[@intFromBool(has_bind_fn != null)..]) |init_param| {
|
if (bind_fn) |@"fn"| {
|
||||||
|
if (@"fn".params.len != 0) {
|
||||||
|
@compileError(std.fmt.comptimePrint("{s}.bind fn may not accept any parameters to be used as a behavior param", .{@typeName(Param)}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline for (init_fn.params[@intFromBool(bind_fn != null)..]) |init_param| {
|
||||||
const InitParam = coral.meta.UnwrappedOptional(init_param.type orelse {
|
const InitParam = coral.meta.UnwrappedOptional(init_param.type orelse {
|
||||||
@compileError(std.fmt.comptimePrint("fn {s}.init may not have generic parameters", .{
|
@compileError(std.fmt.comptimePrint("fn {s}.init may not have generic parameters", .{
|
||||||
@typeName(Param),
|
@typeName(Param),
|
||||||
@ -269,7 +275,7 @@ fn getInitParams(comptime Param: type) []const std.builtin.Type.Fn.Param {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usesTrait(comptime Param: type, name: []const u8) bool {
|
fn usesTrait(comptime Param: type, name: []const u8) bool {
|
||||||
if (@hasDecl(Param, "traits")) {
|
if (coral.meta.isContainer(@typeInfo(Param)) and @hasDecl(Param, "traits")) {
|
||||||
const Traits = @TypeOf(Param.traits);
|
const Traits = @TypeOf(Param.traits);
|
||||||
|
|
||||||
const traits_struct = switch (@typeInfo(Traits)) {
|
const traits_struct = switch (@typeInfo(Traits)) {
|
||||||
|
|||||||
@ -4,13 +4,32 @@ const coral = @import("coral");
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub const GenericHandle = struct {
|
pub fn Handle(comptime Asset: type) type {
|
||||||
type_id: *const coral.TypeId,
|
return packed struct(u64) {
|
||||||
|
index: u32,
|
||||||
|
salt: u32,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub const none = std.mem.zeroes(Self);
|
||||||
|
|
||||||
|
pub fn generify(self: Self) HandleGeneric {
|
||||||
|
return .{
|
||||||
|
.asset_id = coral.TypeId.of(Asset),
|
||||||
|
.index = self.index,
|
||||||
|
.salt = self.salt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const HandleGeneric = struct {
|
||||||
index: u32,
|
index: u32,
|
||||||
salt: u32,
|
salt: u32,
|
||||||
|
asset_id: *const coral.TypeId,
|
||||||
|
|
||||||
pub fn reify(self: GenericHandle, comptime Asset: type) ?Handle(Asset) {
|
pub fn reify(self: HandleGeneric, comptime Asset: type) ?Handle(Asset) {
|
||||||
if (self.type_id != coral.TypeId.of(Asset)) {
|
if (self.asset_id != coral.TypeId.of(Asset)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,271 +40,188 @@ pub const GenericHandle = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn Handle(comptime Asset: type) type {
|
pub const Queue = struct {
|
||||||
return packed struct(u64) {
|
shared: *Shared,
|
||||||
index: u32,
|
|
||||||
salt: u32,
|
const HandlePool = struct {
|
||||||
|
states: [*]State = undefined,
|
||||||
|
salts: [*]u32 = undefined,
|
||||||
|
next: std.atomic.Value(u32) = .init(0),
|
||||||
|
asset_id: *const coral.TypeId,
|
||||||
|
cap: u32 = 0,
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
pub const asset_type_id = coral.TypeId.of(Asset);
|
const State = union(enum) {
|
||||||
|
vacant: u32,
|
||||||
|
pending,
|
||||||
|
occupied,
|
||||||
|
};
|
||||||
|
|
||||||
pub const default: Self = @bitCast(@as(u64, 0));
|
fn deinit(self: *Self) void {
|
||||||
|
coral.heap.allocator.free(self.states[0..self.cap]);
|
||||||
|
coral.heap.allocator.free(self.salts[0..self.cap]);
|
||||||
|
|
||||||
pub fn generic(self: Self) GenericHandle {
|
self.* = .{};
|
||||||
return .{
|
}
|
||||||
.type_id = asset_type_id,
|
|
||||||
.index = self.index,
|
pub fn isReady(self: Self, handle: HandleGeneric) error{InvalidHandle}!bool {
|
||||||
.salt = self.salt,
|
if (self.asset_id != handle.asset_id) {
|
||||||
|
return error.InvalidHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.cap < handle.index) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.salts[handle.index] != handle.salt) {
|
||||||
|
return error.InvalidHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
return switch (self.states[handle.index]) {
|
||||||
|
.vacant => error.InvalidHandle,
|
||||||
|
.pending => null,
|
||||||
|
.occupied => true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ResolveQueue(comptime Asset: type) type {
|
pub fn reserve(self: *Self) HandleGeneric {
|
||||||
return struct {
|
while (true) {
|
||||||
arena: std.heap.ArenaAllocator = .init(coral.heap.allocator),
|
const index = self.next.load(.acquire);
|
||||||
|
|
||||||
tasks: coral.stack.Sequential(struct {
|
if (index < self.cap) {
|
||||||
future: *ona.tasks.Future(?Asset),
|
// Previously-recycled free list available to be used.
|
||||||
handle: AssetHandle,
|
const next_free = self.states[index].vacant;
|
||||||
}) = .empty,
|
|
||||||
|
|
||||||
const AssetHandle = Handle(Asset);
|
if (self.next.cmpxchgWeak(index, next_free, .acq_rel, .acquire) != null) {
|
||||||
|
// CAS failed, loop again
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
fn ResolveTask(comptime Loadable: type) type {
|
self.states[index] = .pending;
|
||||||
const load_fn_name = "load";
|
|
||||||
|
|
||||||
const load_fn = coral.meta.hasFn(Loadable, load_fn_name) orelse {
|
return .{
|
||||||
@compileError(std.fmt.comptimePrint("{s} must contain a .{s} fn", .{
|
.salt = self.salts[index],
|
||||||
@typeName(Loadable),
|
.asset_id = self.asset_id,
|
||||||
load_fn_name,
|
.index = index,
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
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 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The memory for this resource will be created in the end-of-frame asset sync elsewhere, where the asset
|
||||||
|
// buffers are grown to equal next again.
|
||||||
return .{
|
return .{
|
||||||
.index = self.free_index,
|
.index = self.next.fetchAdd(1, .monotonic),
|
||||||
.salt = entry.salt,
|
.asset_id = self.asset_id,
|
||||||
|
.salt = 1,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handle = AssetHandle{
|
const Shared = struct {
|
||||||
.index = self.asset_entries.items.len,
|
asset_handle_pools: coral.tree.Binary(*const coral.TypeId, HandlePool, coral.tree.scalarTraits(*const coral.TypeId)) = .empty,
|
||||||
.salt = 1,
|
};
|
||||||
};
|
|
||||||
|
|
||||||
try self.asset_entries.pushGrow(coral.heap.allocator, .{
|
pub fn deinit(self: *Queue) void {
|
||||||
.value = undefined,
|
self.shared.asset_handle_pools.deinit(coral.heap.allocator);
|
||||||
|
}
|
||||||
|
|
||||||
.entry = .{
|
pub fn load(self: Queue, loadable: anytype) error{OutOfMemory}!Handle(coral.meta.UnwrappedError(@typeInfo(@TypeOf(@TypeOf(loadable).load)).@"fn".return_type.?)) {
|
||||||
.usage = .reserved,
|
const Asset = coral.meta.UnwrappedError(@typeInfo(@TypeOf(@TypeOf(loadable).load)).@"fn".return_type.?);
|
||||||
.salt = handle.salt,
|
|
||||||
},
|
const handles = self.shared.asset_handle_pools.get(.of(Asset)) orelse {
|
||||||
|
std.debug.panic("Asset type {s} must be registered first before attempting to load an instance", .{
|
||||||
|
@typeName(Asset),
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
self.free_index += 1;
|
const handle = handles.reserve().reify(Asset).?;
|
||||||
|
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register(self: *Queue, asset_id: *const coral.TypeId) error{OutOfMemory}!*HandlePool {
|
||||||
|
if (try self.shared.asset_handle_pools.insert(coral.heap.allocator, asset_id, .{ .asset_id = asset_id })) |handles| {
|
||||||
|
return handles;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.shared.asset_handle_pools.get(asset_id).?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn Store(comptime Asset: type) type {
|
||||||
|
return struct {
|
||||||
|
handles: *Queue.HandlePool,
|
||||||
|
assets: coral.stack.Sequential(Asset) = .empty,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
self.assets.deinit(coral.heap.allocator);
|
||||||
|
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(self: *Self, asset: Asset) error{OutOfMemory}!Handle(Asset) {
|
||||||
|
const handle = self.handles.reserve().reify(Asset).?;
|
||||||
|
|
||||||
|
if (handle.index < self.assets.items.len) {
|
||||||
|
self.assets.items[handle.index] = asset;
|
||||||
|
} else {
|
||||||
|
try self.assets.pushGrow(asset);
|
||||||
|
}
|
||||||
|
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resolve(self: *Self, reserved_handle: AssetHandle, resolved_asset: ?Asset) bool {
|
pub fn modify(self: *Self, handle: Handle(Asset)) error{InvalidHandle}!?*Asset {
|
||||||
const index = reserved_handle.index;
|
// TODO: Implement.
|
||||||
const entry = &self.asset_entries.items.slice(.entry)[index];
|
_ = self;
|
||||||
|
_ = handle;
|
||||||
|
|
||||||
if (entry.usage != .reserved) {
|
return null;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reserved_handle.salt != entry.salt) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resolved_asset) |asset| {
|
|
||||||
entry.usage = .occupied;
|
|
||||||
self.asset_entries.items.slice(.value)[index] = .{ .asset = asset };
|
|
||||||
} else {
|
|
||||||
entry.usage = .missing;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const extension_len_max = 7;
|
pub fn register(app: *ona.App, comptime Asset: type) !void {
|
||||||
|
const AssetStore = Store(Asset);
|
||||||
|
|
||||||
|
const assets = struct {
|
||||||
|
fn integrate(store: ona.Exclusive(AssetStore)) !void {
|
||||||
|
const required_cap = store.ptr.handles.next.load(.monotonic);
|
||||||
|
|
||||||
|
if (store.ptr.handles.cap < required_cap) {
|
||||||
|
store.ptr.handles.states = (try coral.heap.allocator.realloc(store.ptr.handles.states[0..store.ptr.handles.cap], required_cap)).ptr;
|
||||||
|
store.ptr.handles.salts = (try coral.heap.allocator.realloc(store.ptr.handles.salts[0..store.ptr.handles.cap], required_cap)).ptr;
|
||||||
|
|
||||||
|
@memset(store.ptr.handles.salts[store.ptr.handles.cap..required_cap], 1);
|
||||||
|
@memset(store.ptr.handles.states[store.ptr.handles.cap..required_cap], .pending);
|
||||||
|
|
||||||
|
store.ptr.handles.cap = required_cap;
|
||||||
|
}
|
||||||
|
|
||||||
|
try store.ptr.assets.reserve(coral.heap.allocator, required_cap);
|
||||||
|
|
||||||
|
std.debug.assert(store.ptr.assets.pushMany(required_cap - store.ptr.assets.items.len, undefined));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const queue = app.getState(Queue).?;
|
||||||
|
|
||||||
|
try app.setState(AssetStore{
|
||||||
|
.handles = try queue.register(.of(Asset)),
|
||||||
|
});
|
||||||
|
|
||||||
|
try app.on(.post_update, .of(assets.integrate));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setup(app: *ona.App) void {
|
||||||
|
var arc_queue = try coral.heap.Arc(Queue).init(.{});
|
||||||
|
|
||||||
|
errdefer {
|
||||||
|
arc_queue.release().?.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = app.setState(try Queue.init());
|
||||||
|
}
|
||||||
|
|||||||
642
src/ona/gfx.zig
642
src/ona/gfx.zig
@ -1,11 +1,13 @@
|
|||||||
pub const Color = @import("gfx/Color.zig");
|
pub const Color = @import("gfx/Color.zig");
|
||||||
|
|
||||||
|
const c = @cImport({
|
||||||
|
@cInclude("spirv_reflect/spirv_reflect.h");
|
||||||
|
});
|
||||||
|
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
const coral = @import("coral");
|
const coral = @import("coral");
|
||||||
|
|
||||||
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");
|
||||||
@ -15,337 +17,225 @@ const qoi = @import("gfx/qoi.zig");
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub const CheckerImage = struct {
|
pub const CheckerImage = struct {
|
||||||
square_size: u32,
|
square_size: usize,
|
||||||
colors: [2]Color,
|
colors: [2]Color,
|
||||||
width: u32,
|
width: usize,
|
||||||
height: u32,
|
height: usize,
|
||||||
|
|
||||||
pub fn load(self: CheckerImage) !device.Image {
|
pub fn load(self: CheckerImage) !Image {
|
||||||
var image = try device.Image.init(self.width, self.height, .rgba8, .{ .is_input = true });
|
var image = Image.init(self.width, self.height, .rgba8);
|
||||||
|
|
||||||
errdefer {
|
errdefer {
|
||||||
image.deinit();
|
image.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
const image_size = image.height * image.width * device.Image.Format.rgba8.stride();
|
if (self.square_size == 0) {
|
||||||
|
try image.fill(&self.colors[0].toRgba8());
|
||||||
|
} 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.writer(), &self.colors[color_index].toRgba8());
|
||||||
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;
|
return image;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const Context = struct {
|
|
||||||
swapchain: device.Swapchain,
|
|
||||||
backbuffer: device.Target,
|
|
||||||
swap_buffers: [2]Buffer = .{ .{}, .{} },
|
|
||||||
is_swapped: bool = false,
|
|
||||||
frame_arena: std.heap.ArenaAllocator = .init(coral.heap.allocator),
|
|
||||||
renderer: ?*RenderTask = null,
|
|
||||||
|
|
||||||
const Buffer = struct {
|
|
||||||
head: ?*Queue = null,
|
|
||||||
tail: ?*Queue = null,
|
|
||||||
|
|
||||||
fn dequeue(self: *Buffer) ?*Queue {
|
|
||||||
if (self.head) |head| {
|
|
||||||
self.head = head.next;
|
|
||||||
head.next = null;
|
|
||||||
|
|
||||||
if (self.head == null) {
|
|
||||||
self.tail = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return head;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enqueue(self: *Buffer, node: *Queue) void {
|
|
||||||
if (self.tail) |tail| {
|
|
||||||
std.debug.assert(self.head != null);
|
|
||||||
|
|
||||||
tail.next = node;
|
|
||||||
self.tail = node;
|
|
||||||
} else {
|
|
||||||
self.head = node;
|
|
||||||
self.tail = node;
|
|
||||||
}
|
|
||||||
|
|
||||||
node.next = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const RenderTask = struct {
|
|
||||||
backbuffer: device.Target,
|
|
||||||
submitted_buffer: *Buffer,
|
|
||||||
finished: std.Thread.ResetEvent = .{},
|
|
||||||
|
|
||||||
pub fn run(self: *RenderTask) void {
|
|
||||||
defer {
|
|
||||||
self.finished.set();
|
|
||||||
}
|
|
||||||
|
|
||||||
const commands = device.acquireCommands() catch |acquire_error| {
|
|
||||||
return std.log.err("Render task failed: {s}", .{@errorName(acquire_error)});
|
|
||||||
};
|
|
||||||
|
|
||||||
var render_pass = commands.renderPass(self.backbuffer);
|
|
||||||
|
|
||||||
while (self.submitted_buffer.dequeue()) |renderables| {
|
|
||||||
defer {
|
|
||||||
renderables.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (renderables.commands.items.slice()) |command| {
|
|
||||||
switch (command) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render_pass.finish();
|
|
||||||
commands.submit();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn deinit(self: *Context) void {
|
|
||||||
if (self.renderer) |renderer| {
|
|
||||||
renderer.finished.wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.frame_arena.deinit();
|
|
||||||
self.backbuffer.deinit();
|
|
||||||
self.swapchain.deinit();
|
|
||||||
device.stop();
|
|
||||||
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn finish(self: *Context) void {
|
|
||||||
if (self.renderer) |renderer| {
|
|
||||||
renderer.finished.wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!self.frame_arena.reset(.retain_capacity)) {
|
|
||||||
std.log.warn("Failed to retain capacity of {s} frame allocator, this may impact performance", .{
|
|
||||||
@typeName(Context),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
self.renderer = null;
|
|
||||||
self.is_swapped = !self.is_swapped;
|
|
||||||
|
|
||||||
self.swapchain.present(self.backbuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render(self: *Context) void {
|
|
||||||
self.renderer = ona.tasks.create(self.frame_arena.allocator(), .multimedia, RenderTask{
|
|
||||||
.submitted_buffer = &self.swap_buffers[@intFromBool(!self.is_swapped)],
|
|
||||||
.backbuffer = self.backbuffer,
|
|
||||||
}) catch |creation_error| {
|
|
||||||
return std.log.warn("{s}", .{switch (creation_error) {
|
|
||||||
error.OutOfMemory => "Not enough memory was avaialble to start a render, skipping frame...",
|
|
||||||
}});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn submit(self: *Context, renderables: *Queue) void {
|
|
||||||
self.swap_buffers[@intFromBool(self.is_swapped)].enqueue(renderables);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Display = struct {
|
pub const Display = struct {
|
||||||
size: [2]u16,
|
size: [2]u16,
|
||||||
is_hidden: bool,
|
is_hidden: bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Effects = ona.Assets(device.Effect);
|
pub const Effect = struct {
|
||||||
|
shaders: [stages.len]c.SpvReflectShaderModule,
|
||||||
|
properties_buffers: [stages.len][]u8,
|
||||||
|
|
||||||
pub const Images = ona.Assets(device.Image);
|
pub const Stage = enum {
|
||||||
|
vertex,
|
||||||
|
fragment,
|
||||||
|
};
|
||||||
|
|
||||||
pub const QoiImage = struct {
|
pub const Spirv = struct {
|
||||||
path: ona.App.Path,
|
vertex_code: []const u8 = &.{},
|
||||||
|
fragment_code: []const u8 = &.{},
|
||||||
|
};
|
||||||
|
|
||||||
pub fn load(self: QoiImage, data: ona.App.Data) !device.Image {
|
pub const SpirvError = error{
|
||||||
var qoi_data = coral.bytes.spanOf(try data.loadFile(self.path, coral.heap.allocator));
|
OutOfMemory,
|
||||||
|
InvalidVertexShader,
|
||||||
|
InvalidFragmentShader,
|
||||||
|
};
|
||||||
|
|
||||||
defer {
|
pub fn deinit(self: *Effect) void {
|
||||||
coral.heap.allocator.free(qoi_data.bytes);
|
for (&self.shaders, self.properties_buffers) |*shader, properties_buffer| {
|
||||||
}
|
c.spvReflectDestroyShaderModule(shader);
|
||||||
|
|
||||||
const header = try qoi.Info.decode(qoi_data.reader());
|
if (properties_buffer.len != 0) {
|
||||||
var image_memory = try device.Memory.init(header.width * header.height * device.Image.Format.rgba8.stride());
|
coral.heap.allocator.free(properties_buffer);
|
||||||
|
|
||||||
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();
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
var image = try device.Image.init(header.width, header.height, .rgba8, .{
|
pub fn init(spirv: Spirv) SpirvError!Effect {
|
||||||
.is_input = true,
|
var effect = Effect{
|
||||||
});
|
.shaders = .{ .{}, .{} },
|
||||||
|
.properties_buffers = .{ &.{}, &.{} },
|
||||||
|
};
|
||||||
|
|
||||||
errdefer {
|
errdefer {
|
||||||
image.deinit();
|
effect.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
const commands = try device.acquireCommands();
|
try switch (c.spvReflectCreateShaderModule(spirv.vertex_code.len, spirv.vertex_code.ptr, &effect.shaders[@intFromEnum(Stage.vertex)])) {
|
||||||
|
c.SPV_REFLECT_RESULT_SUCCESS => {},
|
||||||
commands.copyPass().uploadImage(image, image_memory).finish();
|
c.SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED => error.OutOfMemory,
|
||||||
commands.submit();
|
else => error.InvalidVertexShader,
|
||||||
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
|
|
||||||
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,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
try switch (c.spvReflectCreateShaderModule(spirv.fragment_code.len, spirv.fragment_code.ptr, &effect.shaders[@intFromEnum(Stage.fragment)])) {
|
||||||
|
c.SPV_REFLECT_RESULT_SUCCESS => {},
|
||||||
|
c.SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED => error.OutOfMemory,
|
||||||
|
else => error.InvalidFragmentShader,
|
||||||
|
};
|
||||||
|
|
||||||
|
return effect;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn setProperties(self: *Effect, stage: Stage, properties: anytype) error{ OutOfMemory, InvalidData }!void {
|
||||||
|
const Properties = @TypeOf(properties);
|
||||||
|
|
||||||
|
const properties_struct = switch (@typeInfo(Properties)) {
|
||||||
|
.@"struct" => |@"struct"| @"struct",
|
||||||
|
|
||||||
|
else => @compileError(std.fmt.comptimePrint("`properties` must be a struct type, not {s}", .{
|
||||||
|
@typeName(Properties),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (properties_struct.is_tuple) {
|
||||||
|
@compileError(std.fmt.comptimePrint("`properties` must be a non-type struct type, not {s}", .{
|
||||||
|
@typeName(Properties),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const stage_index = @intFromEnum(stage);
|
||||||
|
|
||||||
|
const properties_binding = spirvDescriptorBindingNamed(self.shaders[stage_index], "Effect") orelse {
|
||||||
|
return error.InvalidData;
|
||||||
|
};
|
||||||
|
|
||||||
|
const properties_buffer = &self.properties_buffers[stage_index];
|
||||||
|
|
||||||
|
if (properties_buffer.len != properties_binding.block.padded_size) {
|
||||||
|
properties_buffer.* = try coral.heap.allocator.realloc(properties_buffer.*, properties_binding.block.padded_size);
|
||||||
|
|
||||||
|
@memset(properties_buffer.*, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline for (properties_struct.fields) |properties_field| {
|
||||||
|
const properties_variable = spirvBlockVariableNamed(properties_binding.block, properties_field.name) orelse {
|
||||||
|
return error.InvalidData;
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (properties_field.type) {
|
||||||
|
f32 => {
|
||||||
|
if (properties_variable.type_description.*.op != c.SpvOpTypeFloat) {
|
||||||
|
return error.InvalidData;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (properties_variable.numeric.scalar.width != @bitSizeOf(f32)) {
|
||||||
|
return error.InvalidData;
|
||||||
|
}
|
||||||
|
|
||||||
|
const property_bytes = std.mem.asBytes(&@field(properties, properties_field.name));
|
||||||
|
|
||||||
|
@memcpy(properties_buffer.*[properties_variable.offset .. properties_variable.offset + property_bytes.len], property_bytes);
|
||||||
|
},
|
||||||
|
|
||||||
|
else => {
|
||||||
|
@compileError(std.fmt.comptimePrint("Unsupported property type for {s} field '{s}': {s}.", .{
|
||||||
|
@typeName(Properties),
|
||||||
|
properties_field.name,
|
||||||
|
@typeName(properties_field.type),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spirvBlockVariableNamed(block: c.SpvReflectBlockVariable, name: []const u8) ?c.SpvReflectBlockVariable {
|
||||||
|
var remaining = block.member_count;
|
||||||
|
|
||||||
|
while (remaining != 0) : (remaining -= 1) {
|
||||||
|
const block_variable = block.members[remaining];
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, std.mem.span(block_variable.name), name)) {
|
||||||
|
return block_variable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spirvDescriptorBindingNamed(shader: c.SpvReflectShaderModule, name: []const u8) ?c.SpvReflectDescriptorBinding {
|
||||||
|
var remaining = shader.descriptor_binding_count;
|
||||||
|
|
||||||
|
while (remaining != 0) : (remaining -= 1) {
|
||||||
|
const descriptor_binding = shader.descriptor_bindings[remaining];
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, std.mem.span(descriptor_binding.name), name)) {
|
||||||
|
return descriptor_binding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stages = std.enums.values(Stage);
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const GlslEffect = struct {
|
pub const GlslEffect = struct {
|
||||||
fragment: Source = .empty,
|
fragment: glsl.Source = .empty,
|
||||||
vertex: Source = .empty,
|
vertex: glsl.Source = .empty,
|
||||||
|
|
||||||
pub const Source = glsl.Assembly.Source;
|
const Camera = extern struct {
|
||||||
|
projection: [4]@Vector(4, f32),
|
||||||
|
};
|
||||||
|
|
||||||
pub fn load(self: GlslEffect) !device.Effect {
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn load(self: Self) !Effect {
|
||||||
var arena = std.heap.ArenaAllocator.init(coral.heap.allocator);
|
var arena = std.heap.ArenaAllocator.init(coral.heap.allocator);
|
||||||
|
|
||||||
defer {
|
defer {
|
||||||
arena.deinit();
|
arena.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
var assembly = try glsl.Assembly.init(.spirv, switch (builtin.mode) {
|
var target = try glsl.Target.init(.spirv, switch (builtin.mode) {
|
||||||
.ReleaseSafe, .Debug => .unoptimized,
|
.ReleaseSafe, .Debug => .unoptimized,
|
||||||
.ReleaseFast => .optimize_speed,
|
.ReleaseFast => .optimize_speed,
|
||||||
.ReleaseSmall => .optimize_size,
|
.ReleaseSmall => .optimize_size,
|
||||||
});
|
});
|
||||||
|
|
||||||
defer {
|
defer {
|
||||||
assembly.deinit();
|
target.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
const Effect = extern struct {
|
|
||||||
time: f32,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Camera = extern struct {
|
|
||||||
projection: [4]@Vector(4, f32),
|
|
||||||
};
|
|
||||||
|
|
||||||
const arena_allocator = arena.allocator();
|
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 default_shader_name = "[unnamed shader]";
|
||||||
const glsl_version = 430;
|
const glsl_version = 430;
|
||||||
|
const camera_block = glsl.nativeUniformBlock(1, Camera);
|
||||||
|
|
||||||
return device.Effect.init(.{
|
return .init(.{
|
||||||
.fragment_code = try assembly.assemble(arena_allocator, .fragment, .{
|
.fragment_code = try target.assemble(arena_allocator, .fragment, .{
|
||||||
.glsl = try glsl.inject(arena_allocator, switch (self.fragment.glsl.len) {
|
.glsl = try glsl.inject(arena_allocator, switch (self.fragment.glsl.len) {
|
||||||
0 => @embedFile("gfx/canvas.frag"),
|
0 => @embedFile("gfx/canvas.frag"),
|
||||||
else => self.fragment.glsl,
|
else => self.fragment.glsl,
|
||||||
@ -354,8 +244,8 @@ pub const GlslEffect = struct {
|
|||||||
.vertex_rgba = glsl.nativeInput(1, @Vector(4, f32)),
|
.vertex_rgba = glsl.nativeInput(1, @Vector(4, f32)),
|
||||||
.image = glsl.Sampler{ .sampler_2d = 0 },
|
.image = glsl.Sampler{ .sampler_2d = 0 },
|
||||||
.color = glsl.nativeOutput(0, @Vector(4, f32)),
|
.color = glsl.nativeOutput(0, @Vector(4, f32)),
|
||||||
.effect = effect_uniform_block,
|
.properties = glsl.PreProcessor{ .substitution = "layout (binding = 0) uniform Effects" },
|
||||||
.camera = camera_uniform_block,
|
.camera = camera_block,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
.name = switch (self.fragment.name.len) {
|
.name = switch (self.fragment.name.len) {
|
||||||
@ -364,7 +254,7 @@ pub const GlslEffect = struct {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
.vertex_code = try assembly.assemble(arena_allocator, .vertex, .{
|
.vertex_code = try target.assemble(arena_allocator, .vertex, .{
|
||||||
.glsl = try glsl.inject(arena_allocator, switch (self.vertex.glsl.len) {
|
.glsl = try glsl.inject(arena_allocator, switch (self.vertex.glsl.len) {
|
||||||
0 => @embedFile("gfx/canvas.vert"),
|
0 => @embedFile("gfx/canvas.vert"),
|
||||||
else => self.vertex.glsl,
|
else => self.vertex.glsl,
|
||||||
@ -382,8 +272,8 @@ pub const GlslEffect = struct {
|
|||||||
.instance_bits = glsl.nativeInput(10, u32),
|
.instance_bits = glsl.nativeInput(10, u32),
|
||||||
.uv = glsl.nativeOutput(0, @Vector(2, f32)),
|
.uv = glsl.nativeOutput(0, @Vector(2, f32)),
|
||||||
.rgba = glsl.nativeOutput(1, @Vector(4, f32)),
|
.rgba = glsl.nativeOutput(1, @Vector(4, f32)),
|
||||||
.effect = effect_uniform_block,
|
.properties = glsl.PreProcessor{ .substitution = "layout (binding = 0) uniform Effects" },
|
||||||
.camera = camera_uniform_block,
|
.camera = camera_block,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
.name = switch (self.vertex.name.len) {
|
.name = switch (self.vertex.name.len) {
|
||||||
@ -395,61 +285,161 @@ pub const GlslEffect = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn renderFrame(context: ona.Exclusive(Context), display: ona.Read(Display)) void {
|
pub const Image = struct {
|
||||||
if (display.ptr.is_hidden) {
|
data: coral.stack.Sequential(u8),
|
||||||
_ = context.ptr.swapchain.hide();
|
width: usize,
|
||||||
} else {
|
height: usize,
|
||||||
_ = context.ptr.swapchain.show();
|
format: Format,
|
||||||
|
|
||||||
|
///
|
||||||
|
/// The way that image data should be stored in memory.
|
||||||
|
///
|
||||||
|
pub const Format = enum {
|
||||||
|
rgba8,
|
||||||
|
|
||||||
|
pub fn stride(self: Format) usize {
|
||||||
|
return switch (self) {
|
||||||
|
.rgba8 => @sizeOf([4]u8),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Cleans up all resources associated with `self`.
|
||||||
|
///
|
||||||
|
pub fn deinit(self: *Image) void {
|
||||||
|
self.data.deinit(coral.heap.allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.ptr.render();
|
///
|
||||||
}
|
/// Attempts to fill `self` with `fill_data`.
|
||||||
|
///
|
||||||
|
/// **Note** passing a `fill_data.len` not equal to the format stride will result in `error.InvalidData`.
|
||||||
|
///
|
||||||
|
pub fn fill(self: *Image, fill_data: []const u8) error{ InvalidData, OutOfMemory }!void {
|
||||||
|
const format_stride = self.format.stride();
|
||||||
|
|
||||||
|
if (fill_data.len != format_stride) {
|
||||||
|
return error.InvalidData;
|
||||||
|
}
|
||||||
|
|
||||||
|
var unfilled = self.width * self.height * format_stride;
|
||||||
|
|
||||||
|
try self.data.reserve(coral.heap.allocator, unfilled);
|
||||||
|
|
||||||
|
self.data.clear();
|
||||||
|
|
||||||
|
while (unfilled != 0) : (unfilled -= 1) {
|
||||||
|
std.debug.assert(self.data.pushAll(fill_data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Return a new `Image` with the given `width`, `height`, and `format`.
|
||||||
|
///
|
||||||
|
pub fn init(width: usize, height: usize, format: Format) Image {
|
||||||
|
return .{
|
||||||
|
.data = .empty,
|
||||||
|
.width = width,
|
||||||
|
.height = height,
|
||||||
|
.format = format,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Write `data` into `self` from the last location written to, returning the number of bytes written.
|
||||||
|
///
|
||||||
|
/// If `data` overflows the image data buffer, zero bytes of data are written and `0` is always returned.
|
||||||
|
///
|
||||||
|
pub fn write(self: *Image, data: []const u8) usize {
|
||||||
|
self.data.reserve(coral.heap.allocator, self.width * self.height * self.format.stride()) catch {
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!self.data.pushAll(data)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.len;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Returns a `coral.bytes.Writable` for writing to `self`.
|
||||||
|
///
|
||||||
|
pub fn writer(self: *Image) coral.bytes.Writable {
|
||||||
|
return .initRef(self, write);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const QoiImage = struct {
|
||||||
|
path: ona.App.Path,
|
||||||
|
|
||||||
|
pub fn load(self: QoiImage, data: ona.App.Data) !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 = Image.init(header.width, header.height, .rgba8);
|
||||||
|
|
||||||
|
defer {
|
||||||
|
image.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var decoder = qoi.DecodeStream.init(header);
|
||||||
|
|
||||||
|
while (try decoder.fetch(qoi_data.reader())) |run| {
|
||||||
|
try coral.bytes.writeN(image.writer(), &run.pixel.bytes(), run.count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Scene = struct {
|
||||||
|
pub const Rect = struct {
|
||||||
|
left: f32,
|
||||||
|
top: f32,
|
||||||
|
right: f32,
|
||||||
|
bottom: f32,
|
||||||
|
};
|
||||||
|
|
||||||
|
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: ona.asset.Handle(Effect) = .none,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn drawSprite(self: Scene, image: ona.asset.Handle(Image), draw: SpriteDraw) void {
|
||||||
|
_ = self;
|
||||||
|
_ = image;
|
||||||
|
_ = draw;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init() Scene {
|
||||||
|
return .{};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
pub fn setup(app: *ona.App) !void {
|
pub fn setup(app: *ona.App) !void {
|
||||||
try device.start();
|
|
||||||
|
|
||||||
errdefer {
|
|
||||||
device.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
var arena = std.heap.ArenaAllocator.init(coral.heap.allocator);
|
|
||||||
|
|
||||||
defer {
|
|
||||||
arena.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
const default_width, const default_height = .{ 1280, 720 };
|
const default_width, const default_height = .{ 1280, 720 };
|
||||||
|
|
||||||
{
|
|
||||||
var swapchain = try device.Swapchain.init(default_width, default_height, "Ona");
|
|
||||||
|
|
||||||
errdefer {
|
|
||||||
swapchain.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
var backbuffer = try device.Target.init(default_width, default_height);
|
|
||||||
|
|
||||||
errdefer {
|
|
||||||
backbuffer.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
try app.setState(Context{
|
|
||||||
.swapchain = swapchain,
|
|
||||||
.backbuffer = backbuffer,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try app.setState(Display{
|
try app.setState(Display{
|
||||||
.size = .{ default_width, default_height },
|
.size = .{ default_width, default_height },
|
||||||
.is_hidden = false,
|
.is_hidden = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
try ona.registerAsset(app, device.Image);
|
try ona.asset.register(app, Image);
|
||||||
try ona.registerAsset(app, device.Effect);
|
try ona.asset.register(app, Effect);
|
||||||
try app.on(.pre_update, .of(renderFrame));
|
// try app.on(.pre_update, .of(renderFrame));
|
||||||
try app.on(.post_update, .of(swapBuffers));
|
//try app.on(.post_update, .of(swapBuffers));
|
||||||
}
|
|
||||||
|
|
||||||
pub fn swapBuffers(context: ona.Exclusive(Context)) void {
|
|
||||||
context.ptr.finish();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,435 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@ -6,10 +6,23 @@ const coral = @import("coral");
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
|
///
|
||||||
|
/// 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 = "",
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
///
|
///
|
||||||
/// State machine for compiling GLSL shader source code into targeted executable formats.
|
/// State machine for compiling GLSL shader source code into targeted executable formats.
|
||||||
///
|
///
|
||||||
pub const Assembly = struct {
|
pub const Target = struct {
|
||||||
compiler: c.shaderc_compiler_t,
|
compiler: c.shaderc_compiler_t,
|
||||||
options: c.shaderc_compile_options_t,
|
options: c.shaderc_compile_options_t,
|
||||||
|
|
||||||
@ -22,19 +35,6 @@ pub const Assembly = struct {
|
|||||||
optimize_size,
|
optimize_size,
|
||||||
};
|
};
|
||||||
|
|
||||||
///
|
|
||||||
/// Textual GLSL source code with an associated name.
|
|
||||||
///
|
|
||||||
pub const Source = struct {
|
|
||||||
name: [:0]const u8,
|
|
||||||
glsl: []const u8,
|
|
||||||
|
|
||||||
pub const empty = Source{
|
|
||||||
.name = "",
|
|
||||||
.glsl = "",
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Different types of shader execution stages.
|
/// Different types of shader execution stages.
|
||||||
///
|
///
|
||||||
@ -44,9 +44,9 @@ pub const Assembly = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Target format used for shader binaries.
|
/// Backend format used for shader binaries.
|
||||||
///
|
///
|
||||||
pub const Target = enum {
|
pub const Backend = enum {
|
||||||
spirv,
|
spirv,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ pub const Assembly = struct {
|
|||||||
/// **Note** the shader assumes the entry-point of the GLSL program matches the signature `void main()`
|
/// **Note** the shader assumes the entry-point of the GLSL program matches the signature `void main()`
|
||||||
///
|
///
|
||||||
pub fn assemble(
|
pub fn assemble(
|
||||||
self: Assembly,
|
self: Target,
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
stage: Stage,
|
stage: Stage,
|
||||||
source: Source,
|
source: Source,
|
||||||
@ -102,7 +102,7 @@ pub const Assembly = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Assembly) void {
|
pub fn deinit(self: *Target) void {
|
||||||
c.shaderc_compile_options_release(self.options);
|
c.shaderc_compile_options_release(self.options);
|
||||||
c.shaderc_compiler_release(self.compiler);
|
c.shaderc_compiler_release(self.compiler);
|
||||||
|
|
||||||
@ -110,14 +110,14 @@ pub const Assembly = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Returns an `Assembly` with `target` as the desired compilation format and `optimization` as the desired
|
/// Returns an `Target` with `backend` as the desired compilation format and `optimization` as the desired
|
||||||
/// optimization level.
|
/// optimization level.
|
||||||
///
|
///
|
||||||
/// Should not be enough memory be available, `error.OutOfMemory` is returned instead.
|
/// Should not be enough memory be available, `error.OutOfMemory` is returned instead.
|
||||||
///
|
///
|
||||||
/// **Note** optimizations are not guaranteed and will also depend on the `target` specified.
|
/// **Note** optimizations are not guaranteed and will also depend on the `target` specified.
|
||||||
///
|
///
|
||||||
pub fn init(target: Target, optimizations: Optimizations) error{OutOfMemory}!Assembly {
|
pub fn init(backend: Backend, optimizations: Optimizations) error{OutOfMemory}!Target {
|
||||||
const compiler = c.shaderc_compiler_initialize() orelse {
|
const compiler = c.shaderc_compiler_initialize() orelse {
|
||||||
return error.OutOfMemory;
|
return error.OutOfMemory;
|
||||||
};
|
};
|
||||||
@ -134,7 +134,7 @@ pub const Assembly = struct {
|
|||||||
c.shaderc_compile_options_release(options);
|
c.shaderc_compile_options_release(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (target) {
|
switch (backend) {
|
||||||
.spirv => {
|
.spirv => {
|
||||||
c.shaderc_compile_options_set_target_env(options, c.shaderc_target_env_vulkan, c.shaderc_env_version_vulkan_1_1);
|
c.shaderc_compile_options_set_target_env(options, c.shaderc_target_env_vulkan, c.shaderc_env_version_vulkan_1_1);
|
||||||
},
|
},
|
||||||
@ -207,6 +207,17 @@ pub const Output = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const PreProcessor = struct {
|
||||||
|
substitution: []const u8,
|
||||||
|
|
||||||
|
pub fn serialize(self: PreProcessor, name: []const u8, output: coral.bytes.Writable) coral.bytes.ReadWriteError!void {
|
||||||
|
try coral.bytes.writeFormatted(output, "#define {name} {substitution}\n", .{
|
||||||
|
.name = name,
|
||||||
|
.substitution = self.substitution,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
pub const Primitive = enum {
|
pub const Primitive = enum {
|
||||||
float,
|
float,
|
||||||
uint,
|
uint,
|
||||||
|
|||||||
@ -200,13 +200,13 @@ pub const KeyScancode = enum(u32) {
|
|||||||
_,
|
_,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn poll(exit: ona.Send(ona.App.Exit), hid_events: ona.Send(ona.hid.Event)) !void {
|
pub fn poll(exit: ona.Write(ona.Channel(ona.App.Exit)), hid_events: ona.Write(ona.Channel(ona.hid.Event))) !void {
|
||||||
var event: c.SDL_Event = undefined;
|
var event: c.SDL_Event = undefined;
|
||||||
|
|
||||||
while (c.SDL_PollEvent(&event)) {
|
while (c.SDL_PollEvent(&event)) {
|
||||||
try hid_events.push(switch (event.type) {
|
try hid_events.ptr.push(switch (event.type) {
|
||||||
c.SDL_EVENT_QUIT => {
|
c.SDL_EVENT_QUIT => {
|
||||||
return exit.push(.success);
|
return exit.ptr.push(.success);
|
||||||
},
|
},
|
||||||
|
|
||||||
c.SDL_EVENT_KEY_UP => .{ .key_up = @enumFromInt(event.key.scancode) },
|
c.SDL_EVENT_KEY_UP => .{ .key_up = @enumFromInt(event.key.scancode) },
|
||||||
|
|||||||
130
src/ona/ona.zig
130
src/ona/ona.zig
@ -4,7 +4,11 @@ pub const Setup = @import("Setup.zig");
|
|||||||
|
|
||||||
pub const System = @import("System.zig");
|
pub const System = @import("System.zig");
|
||||||
|
|
||||||
const asset = @import("asset.zig");
|
pub const asset = @import("asset.zig");
|
||||||
|
|
||||||
|
const c = @cImport({
|
||||||
|
@cInclude("SDL3/SDL.h");
|
||||||
|
});
|
||||||
|
|
||||||
const coral = @import("coral");
|
const coral = @import("coral");
|
||||||
|
|
||||||
@ -16,52 +20,7 @@ const std = @import("std");
|
|||||||
|
|
||||||
pub const tasks = @import("tasks.zig");
|
pub const tasks = @import("tasks.zig");
|
||||||
|
|
||||||
pub fn Assets(comptime Asset: type) type {
|
pub fn Channel(comptime Message: type) type {
|
||||||
return struct {
|
|
||||||
app_data: App.Data,
|
|
||||||
resolved: *Store,
|
|
||||||
resolves: *ResolveQueue,
|
|
||||||
|
|
||||||
pub const Handle = asset.Handle(Asset);
|
|
||||||
|
|
||||||
const ResolveQueue = asset.ResolveQueue(Asset);
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
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 .{
|
|
||||||
.resolved = resolved,
|
|
||||||
.resolves = resolves,
|
|
||||||
.app_data = app_data.*,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load(self: Self, loadable: anytype) error{OutOfMemory}!Handle {
|
|
||||||
const reserved_handle = try self.resolved.reserve();
|
|
||||||
|
|
||||||
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 unload(self: Self, handle: Handle) void {
|
|
||||||
// TODO: Implement.
|
|
||||||
_ = self.resolved.remove(handle);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn Channel(comptime Message: type) type {
|
|
||||||
return struct {
|
return struct {
|
||||||
buffers: [2]coral.stack.Sequential(Message) = .{ .empty, .empty },
|
buffers: [2]coral.stack.Sequential(Message) = .{ .empty, .empty },
|
||||||
swap_index: u1 = 0,
|
swap_index: u1 = 0,
|
||||||
@ -85,11 +44,11 @@ fn Channel(comptime Message: type) type {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn messages(self: Self) []const Message {
|
pub fn messages(self: Self) []const Message {
|
||||||
return self.buffers[self.swap_index ^ 1].items.slice();
|
return self.buffers[self.swap_index ^ 1].items.slice();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push(self: *Self, message: Message) std.mem.Allocator.Error!void {
|
pub fn push(self: *Self, message: Message) std.mem.Allocator.Error!void {
|
||||||
try self.buffers[self.swap_index].pushGrow(coral.heap.allocator, message);
|
try self.buffers[self.swap_index].pushGrow(coral.heap.allocator, message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -189,47 +148,29 @@ pub fn Read(comptime State: type) type {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn Receive(comptime Message: type) type {
|
pub const Ticks = struct {
|
||||||
const MessageChannel = Channel(Message);
|
last: u32,
|
||||||
|
now: u32,
|
||||||
|
|
||||||
return struct {
|
pub fn bind() App.Frame {
|
||||||
has_channel: ?*const MessageChannel,
|
return .init(0);
|
||||||
|
}
|
||||||
|
|
||||||
const Self = @This();
|
pub fn init(previous_frame: *App.Frame, current_frame: *const App.Frame) Ticks {
|
||||||
|
defer {
|
||||||
pub fn init(channel: ?*const MessageChannel) Self {
|
previous_frame.* = current_frame.*;
|
||||||
return .{
|
|
||||||
.channel = channel,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn messages(self: Self) []const Message {
|
return .{
|
||||||
if (self.has_channel) |channel| {
|
.last = previous_frame.count(),
|
||||||
return channel.messages();
|
.now = current_frame.count(),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn Send(comptime Message: type) type {
|
pub fn isNewer(self: Ticks, tick: u32) bool {
|
||||||
const MessageChannel = Channel(Message);
|
return tick > self.last and tick <= self.now;
|
||||||
|
}
|
||||||
return struct {
|
};
|
||||||
channel: *MessageChannel,
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
pub fn init(channel: *MessageChannel) Self {
|
|
||||||
return .{
|
|
||||||
.channel = channel,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push(self: Self, message: Message) std.mem.Allocator.Error!void {
|
|
||||||
try self.channel.push(message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn Write(comptime State: type) type {
|
pub fn Write(comptime State: type) type {
|
||||||
return struct {
|
return struct {
|
||||||
@ -253,6 +194,7 @@ fn realtimeLoop(app: *App) !void {
|
|||||||
const updates_per_frame = 60.0;
|
const updates_per_frame = 60.0;
|
||||||
const target_frame_time = 1.0 / updates_per_frame;
|
const target_frame_time = 1.0 / updates_per_frame;
|
||||||
const time = app.getState(App.Time).?;
|
const time = app.getState(App.Time).?;
|
||||||
|
const current_frame = app.getState(App.Frame).?;
|
||||||
const exit_channel = app.getState(Channel(App.Exit)).?;
|
const exit_channel = app.getState(Channel(App.Exit)).?;
|
||||||
|
|
||||||
try tasks.start();
|
try tasks.start();
|
||||||
@ -263,16 +205,15 @@ fn realtimeLoop(app: *App) !void {
|
|||||||
|
|
||||||
try app.run(.load);
|
try app.run(.load);
|
||||||
|
|
||||||
const ticks_initial = std.time.milliTimestamp();
|
var ticks_previous = c.SDL_GetTicks();
|
||||||
var ticks_previous = ticks_initial;
|
|
||||||
var accumulated_time = @as(f64, 0);
|
var accumulated_time = @as(f64, 0);
|
||||||
|
|
||||||
while (true) {
|
while (true) : (current_frame.increment()) {
|
||||||
const ticks_current = std.time.milliTimestamp();
|
const ticks_current = c.SDL_GetTicks();
|
||||||
const milliseconds_per_second = 1000.0;
|
const ticks_per_second = 1000.0;
|
||||||
|
|
||||||
accumulated_time += @as(f64, @floatFromInt(ticks_current - ticks_previous)) / milliseconds_per_second;
|
accumulated_time += @as(f64, @floatFromInt(ticks_current - ticks_previous)) / ticks_per_second;
|
||||||
time.elapsed = @as(f64, @floatFromInt(ticks_current - ticks_initial)) / milliseconds_per_second;
|
time.elapsed = @as(f64, @floatFromInt(ticks_current)) / ticks_per_second;
|
||||||
ticks_previous = ticks_current;
|
ticks_previous = ticks_current;
|
||||||
|
|
||||||
try app.run(.pre_update);
|
try app.run(.pre_update);
|
||||||
@ -301,11 +242,6 @@ fn realtimeLoop(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 {
|
pub fn registerChannel(app: *App, comptime Message: type) error{OutOfMemory}!void {
|
||||||
const MessageChannel = Channel(Message);
|
const MessageChannel = Channel(Message);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user