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", .{
|
||||
.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(config.sdl_dependency.artifact("SDL3"));
|
||||
ona_module.linkLibrary(spirv_reflect_lib);
|
||||
|
||||
// config.addShaders(ona_module, &.{
|
||||
// "./src/ona/gfx/effect_shader.zig",
|
||||
|
||||
@ -7,6 +7,10 @@
|
||||
.url = "git+https://github.com/tiawl/shaderc.zig#69b67221988aa84c91447775ad6157e4e80bab00",
|
||||
.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 = .{
|
||||
.url = "git+https://github.com/castholm/SDL.git#b1913e7c31ad72ecfd3ab04aeac387027754cfaf",
|
||||
.hash = "sdl-0.3.0+3.2.22-7uIn9Pg3fwGG2IyIOPxxOSVe-75nUng9clt7tXGFLzMr",
|
||||
|
||||
@ -2,6 +2,50 @@ const builtin = @import("builtin");
|
||||
|
||||
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) {
|
||||
.ReleaseFast, .ReleaseSmall => std.heap.smp_allocator,
|
||||
else => debug_allocator.allocator(),
|
||||
|
||||
@ -67,9 +67,13 @@ fn Generic(comptime Item: type, comptime Storage: type) type {
|
||||
|
||||
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;
|
||||
}
|
||||
@ -85,13 +89,13 @@ fn Generic(comptime Item: type, comptime Storage: type) type {
|
||||
pub fn pushMany(self: *Self, n: usize, item: Item) bool {
|
||||
const new_len = self.items.len + n;
|
||||
|
||||
if (new_len > self.cap) {
|
||||
if (new_len > self.items.cap) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const offset = self.items.len;
|
||||
|
||||
self.items.len = new_len;
|
||||
self.items.len = @intCast(new_len);
|
||||
|
||||
for (offset..(offset + n)) |i| {
|
||||
std.debug.assert(self.items.set(i, item));
|
||||
@ -100,6 +104,14 @@ fn Generic(comptime Item: type, comptime Storage: type) type {
|
||||
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 {
|
||||
if (self.isEmpty()) {
|
||||
return false;
|
||||
|
||||
@ -2,64 +2,45 @@ const ona = @import("ona");
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const CRT = extern struct {
|
||||
width: f32,
|
||||
height: f32,
|
||||
const CRT = struct {
|
||||
time: f32,
|
||||
padding: [4]u8 = undefined,
|
||||
};
|
||||
|
||||
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,
|
||||
image_index: usize = 0,
|
||||
crt_effect: ona.gfx.Effects.Handle = .default,
|
||||
loaded_image: ona.gfx.Images.Handle = .default,
|
||||
crt_effect: ona.asset.Handle(ona.gfx.Effect) = .none,
|
||||
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 };
|
||||
|
||||
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"),
|
||||
});
|
||||
|
||||
errdefer {
|
||||
images.unload(state.ptr.images[1]);
|
||||
}
|
||||
|
||||
state.ptr.crt_effect = try effects.load(ona.gfx.GlslEffect{
|
||||
state.ptr.crt_effect = try assets.ptr.load(ona.gfx.GlslEffect{
|
||||
.fragment = .{
|
||||
.glsl = @embedFile("crt.frag"),
|
||||
.name = "crt.frag",
|
||||
.glsl = @embedFile("graphics_crt.frag"),
|
||||
.name = "graphics_crt.frag",
|
||||
},
|
||||
});
|
||||
|
||||
errdefer {
|
||||
effects.unload(state.ptr.crt_effect);
|
||||
}
|
||||
|
||||
state.ptr.images[0] = try images.load(ona.gfx.CheckerImage{
|
||||
state.ptr.images[0] = try assets.ptr.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{
|
||||
state.ptr.images[1] = try assets.ptr.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 {
|
||||
@ -73,14 +54,14 @@ pub fn main() void {
|
||||
.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;
|
||||
|
||||
scene.updateEffect(state.ptr.crt_effect, CRT{
|
||||
.width = @floatFromInt(width),
|
||||
.height = @floatFromInt(height),
|
||||
if (effects.ptr.modify(state.ptr.crt_effect) catch unreachable) |effect| {
|
||||
try effect.setProperties(.fragment, CRT{
|
||||
.time = @floatCast(time.ptr.elapsed),
|
||||
});
|
||||
}
|
||||
|
||||
scene.drawSprite(state.ptr.images[state.ptr.image_index], .{
|
||||
.size = .{ @floatFromInt(width), @floatFromInt(height) },
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
|
||||
properties {
|
||||
float time;
|
||||
};
|
||||
|
||||
vec2 crt(vec2 coords, float bend) {
|
||||
vec2 symmetrical_coords = (coords - 0.5) * 2.0;
|
||||
|
||||
@ -13,7 +17,7 @@ vec2 crt(vec2 coords, float bend) {
|
||||
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 float scan = sin(crt_coords.y * scanlineCount + time * 29.0);
|
||||
|
||||
const vec3 image_color = texture(image, crt_coords).rgb;
|
||||
const vec3 shaded = image_color - vec3(scan * 0.02);
|
||||
@ -96,6 +96,22 @@ pub const Exit = union(enum) {
|
||||
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 {
|
||||
buffer: [max]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 {
|
||||
if (self.initialized_states.get(.of(State))) |boxed_state| {
|
||||
pub fn getState(self: Self, comptime State: type) ?*State {
|
||||
const state_id = coral.TypeId.of(State);
|
||||
|
||||
if (self.initialized_states.get(state_id)) |boxed_state| {
|
||||
return boxed_state.has(State).?;
|
||||
}
|
||||
|
||||
@ -187,8 +205,9 @@ pub fn init() error{ OutOfMemory, SystemResources }!Self {
|
||||
.named_systems = .empty,
|
||||
};
|
||||
|
||||
try self.setState(Data{});
|
||||
try self.setState(Time{});
|
||||
try self.setState(Data{});
|
||||
try self.setState(Frame.init(1));
|
||||
try ona.registerChannel(&self, Exit);
|
||||
|
||||
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)}));
|
||||
};
|
||||
|
||||
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 {
|
||||
@compileError(std.fmt.comptimePrint("fn {s}.init may not have generic parameters", .{
|
||||
@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 {
|
||||
if (@hasDecl(Param, "traits")) {
|
||||
if (coral.meta.isContainer(@typeInfo(Param)) and @hasDecl(Param, "traits")) {
|
||||
const Traits = @TypeOf(Param.traits);
|
||||
|
||||
const traits_struct = switch (@typeInfo(Traits)) {
|
||||
|
||||
@ -4,23 +4,6 @@ const coral = @import("coral");
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
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 fn Handle(comptime Asset: type) type {
|
||||
return packed struct(u64) {
|
||||
index: u32,
|
||||
@ -28,13 +11,11 @@ pub fn Handle(comptime Asset: type) type {
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub const asset_type_id = coral.TypeId.of(Asset);
|
||||
pub const none = std.mem.zeroes(Self);
|
||||
|
||||
pub const default: Self = @bitCast(@as(u64, 0));
|
||||
|
||||
pub fn generic(self: Self) GenericHandle {
|
||||
pub fn generify(self: Self) HandleGeneric {
|
||||
return .{
|
||||
.type_id = asset_type_id,
|
||||
.asset_id = coral.TypeId.of(Asset),
|
||||
.index = self.index,
|
||||
.salt = self.salt,
|
||||
};
|
||||
@ -42,250 +23,205 @@ pub fn Handle(comptime Asset: type) type {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn ResolveQueue(comptime Asset: type) type {
|
||||
return struct {
|
||||
arena: std.heap.ArenaAllocator = .init(coral.heap.allocator),
|
||||
|
||||
tasks: coral.stack.Sequential(struct {
|
||||
future: *ona.tasks.Future(?Asset),
|
||||
handle: AssetHandle,
|
||||
}) = .empty,
|
||||
|
||||
const AssetHandle = Handle(Asset);
|
||||
|
||||
fn ResolveTask(comptime Loadable: type) type {
|
||||
const load_fn_name = "load";
|
||||
|
||||
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,
|
||||
}));
|
||||
};
|
||||
|
||||
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 },
|
||||
const HandleGeneric = struct {
|
||||
index: u32,
|
||||
salt: u32,
|
||||
};
|
||||
asset_id: *const coral.TypeId,
|
||||
|
||||
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) {
|
||||
pub fn reify(self: HandleGeneric, comptime Asset: type) ?Handle(Asset) {
|
||||
if (self.asset_id != coral.TypeId.of(Asset)) {
|
||||
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,
|
||||
.index = self.index,
|
||||
.salt = self.salt,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Queue = struct {
|
||||
shared: *Shared,
|
||||
|
||||
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 State = union(enum) {
|
||||
vacant: u32,
|
||||
pending,
|
||||
occupied,
|
||||
};
|
||||
|
||||
fn deinit(self: *Self) void {
|
||||
coral.heap.allocator.free(self.states[0..self.cap]);
|
||||
coral.heap.allocator.free(self.salts[0..self.cap]);
|
||||
|
||||
self.* = .{};
|
||||
}
|
||||
|
||||
pub fn isReady(self: Self, handle: HandleGeneric) error{InvalidHandle}!bool {
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
const handle = AssetHandle{
|
||||
.index = self.asset_entries.items.len,
|
||||
pub fn reserve(self: *Self) HandleGeneric {
|
||||
while (true) {
|
||||
const index = self.next.load(.acquire);
|
||||
|
||||
if (index < self.cap) {
|
||||
// Previously-recycled free list available to be used.
|
||||
const next_free = self.states[index].vacant;
|
||||
|
||||
if (self.next.cmpxchgWeak(index, next_free, .acq_rel, .acquire) != null) {
|
||||
// CAS failed, loop again
|
||||
continue;
|
||||
}
|
||||
|
||||
self.states[index] = .pending;
|
||||
|
||||
return .{
|
||||
.salt = self.salts[index],
|
||||
.asset_id = self.asset_id,
|
||||
.index = index,
|
||||
};
|
||||
}
|
||||
|
||||
// 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 .{
|
||||
.index = self.next.fetchAdd(1, .monotonic),
|
||||
.asset_id = self.asset_id,
|
||||
.salt = 1,
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try self.asset_entries.pushGrow(coral.heap.allocator, .{
|
||||
.value = undefined,
|
||||
const Shared = struct {
|
||||
asset_handle_pools: coral.tree.Binary(*const coral.TypeId, HandlePool, coral.tree.scalarTraits(*const coral.TypeId)) = .empty,
|
||||
};
|
||||
|
||||
.entry = .{
|
||||
.usage = .reserved,
|
||||
.salt = handle.salt,
|
||||
},
|
||||
pub fn deinit(self: *Queue) void {
|
||||
self.shared.asset_handle_pools.deinit(coral.heap.allocator);
|
||||
}
|
||||
|
||||
pub fn load(self: Queue, loadable: anytype) error{OutOfMemory}!Handle(coral.meta.UnwrappedError(@typeInfo(@TypeOf(@TypeOf(loadable).load)).@"fn".return_type.?)) {
|
||||
const Asset = coral.meta.UnwrappedError(@typeInfo(@TypeOf(@TypeOf(loadable).load)).@"fn".return_type.?);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 (entry.usage != .reserved) {
|
||||
return false;
|
||||
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;
|
||||
}
|
||||
|
||||
if (reserved_handle.salt != entry.salt) {
|
||||
return false;
|
||||
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;
|
||||
}
|
||||
|
||||
if (resolved_asset) |asset| {
|
||||
entry.usage = .occupied;
|
||||
self.asset_entries.items.slice(.value)[index] = .{ .asset = asset };
|
||||
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 {
|
||||
entry.usage = .missing;
|
||||
try self.assets.pushGrow(asset);
|
||||
}
|
||||
|
||||
return true;
|
||||
return handle;
|
||||
}
|
||||
|
||||
pub fn modify(self: *Self, handle: Handle(Asset)) error{InvalidHandle}!?*Asset {
|
||||
// TODO: Implement.
|
||||
_ = self;
|
||||
_ = handle;
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
574
src/ona/gfx.zig
574
src/ona/gfx.zig
@ -1,11 +1,13 @@
|
||||
pub const Color = @import("gfx/Color.zig");
|
||||
|
||||
const c = @cImport({
|
||||
@cInclude("spirv_reflect/spirv_reflect.h");
|
||||
});
|
||||
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const coral = @import("coral");
|
||||
|
||||
const device = @import("gfx/device.zig");
|
||||
|
||||
const glsl = @import("gfx/glsl.zig");
|
||||
|
||||
const ona = @import("ona.zig");
|
||||
@ -15,29 +17,20 @@ const qoi = @import("gfx/qoi.zig");
|
||||
const std = @import("std");
|
||||
|
||||
pub const CheckerImage = struct {
|
||||
square_size: u32,
|
||||
square_size: usize,
|
||||
colors: [2]Color,
|
||||
width: u32,
|
||||
height: u32,
|
||||
width: usize,
|
||||
height: usize,
|
||||
|
||||
pub fn load(self: CheckerImage) !device.Image {
|
||||
var image = try device.Image.init(self.width, self.height, .rgba8, .{ .is_input = true });
|
||||
pub fn load(self: CheckerImage) !Image {
|
||||
var image = Image.init(self.width, self.height, .rgba8);
|
||||
|
||||
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);
|
||||
try image.fill(&self.colors[0].toRgba8());
|
||||
} else {
|
||||
for (0..self.width * self.height) |i| {
|
||||
const x = i % self.width;
|
||||
@ -46,306 +39,203 @@ pub const CheckerImage = struct {
|
||||
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());
|
||||
try coral.bytes.writeAll(image.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 {
|
||||
size: [2]u16,
|
||||
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 {
|
||||
path: ona.App.Path,
|
||||
pub const Spirv = struct {
|
||||
vertex_code: []const u8 = &.{},
|
||||
fragment_code: []const u8 = &.{},
|
||||
};
|
||||
|
||||
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));
|
||||
pub const SpirvError = error{
|
||||
OutOfMemory,
|
||||
InvalidVertexShader,
|
||||
InvalidFragmentShader,
|
||||
};
|
||||
|
||||
defer {
|
||||
coral.heap.allocator.free(qoi_data.bytes);
|
||||
}
|
||||
pub fn deinit(self: *Effect) void {
|
||||
for (&self.shaders, self.properties_buffers) |*shader, properties_buffer| {
|
||||
c.spvReflectDestroyShaderModule(shader);
|
||||
|
||||
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);
|
||||
if (properties_buffer.len != 0) {
|
||||
coral.heap.allocator.free(properties_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
image_memory.commit();
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
var image = try device.Image.init(header.width, header.height, .rgba8, .{
|
||||
.is_input = true,
|
||||
});
|
||||
pub fn init(spirv: Spirv) SpirvError!Effect {
|
||||
var effect = Effect{
|
||||
.shaders = .{ .{}, .{} },
|
||||
.properties_buffers = .{ &.{}, &.{} },
|
||||
};
|
||||
|
||||
errdefer {
|
||||
image.deinit();
|
||||
effect.deinit();
|
||||
}
|
||||
|
||||
const commands = try device.acquireCommands();
|
||||
|
||||
commands.copyPass().uploadImage(image, image_memory).finish();
|
||||
commands.submit();
|
||||
|
||||
return image;
|
||||
}
|
||||
try switch (c.spvReflectCreateShaderModule(spirv.vertex_code.len, spirv.vertex_code.ptr, &effect.shaders[@intFromEnum(Stage.vertex)])) {
|
||||
c.SPV_REFLECT_RESULT_SUCCESS => {},
|
||||
c.SPV_REFLECT_RESULT_ERROR_ALLOC_FAILED => error.OutOfMemory,
|
||||
else => error.InvalidVertexShader,
|
||||
};
|
||||
|
||||
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();
|
||||
}
|
||||
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,
|
||||
};
|
||||
|
||||
pub const Scene = struct {
|
||||
renderables: *Queue,
|
||||
image_store: *const Images.Store,
|
||||
effect_store: *const Effects.Store,
|
||||
return effect;
|
||||
}
|
||||
|
||||
pub const Rect = struct {
|
||||
left: f32,
|
||||
top: f32,
|
||||
right: f32,
|
||||
bottom: f32,
|
||||
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),
|
||||
})),
|
||||
};
|
||||
|
||||
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,
|
||||
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;
|
||||
};
|
||||
|
||||
pub fn bind() Queue {
|
||||
return .{};
|
||||
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);
|
||||
}
|
||||
|
||||
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,
|
||||
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 {
|
||||
fragment: Source = .empty,
|
||||
vertex: Source = .empty,
|
||||
fragment: glsl.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);
|
||||
|
||||
defer {
|
||||
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,
|
||||
.ReleaseFast => .optimize_speed,
|
||||
.ReleaseSmall => .optimize_size,
|
||||
});
|
||||
|
||||
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 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;
|
||||
const camera_block = glsl.nativeUniformBlock(1, Camera);
|
||||
|
||||
return device.Effect.init(.{
|
||||
.fragment_code = try assembly.assemble(arena_allocator, .fragment, .{
|
||||
return .init(.{
|
||||
.fragment_code = try target.assemble(arena_allocator, .fragment, .{
|
||||
.glsl = try glsl.inject(arena_allocator, switch (self.fragment.glsl.len) {
|
||||
0 => @embedFile("gfx/canvas.frag"),
|
||||
else => self.fragment.glsl,
|
||||
@ -354,8 +244,8 @@ pub const GlslEffect = struct {
|
||||
.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,
|
||||
.properties = glsl.PreProcessor{ .substitution = "layout (binding = 0) uniform Effects" },
|
||||
.camera = camera_block,
|
||||
}),
|
||||
|
||||
.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) {
|
||||
0 => @embedFile("gfx/canvas.vert"),
|
||||
else => self.vertex.glsl,
|
||||
@ -382,8 +272,8 @@ pub const GlslEffect = struct {
|
||||
.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,
|
||||
.properties = glsl.PreProcessor{ .substitution = "layout (binding = 0) uniform Effects" },
|
||||
.camera = camera_block,
|
||||
}),
|
||||
|
||||
.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 {
|
||||
if (display.ptr.is_hidden) {
|
||||
_ = context.ptr.swapchain.hide();
|
||||
} else {
|
||||
_ = context.ptr.swapchain.show();
|
||||
pub const Image = struct {
|
||||
data: coral.stack.Sequential(u8),
|
||||
width: usize,
|
||||
height: usize,
|
||||
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;
|
||||
}
|
||||
|
||||
pub fn setup(app: *ona.App) !void {
|
||||
try device.start();
|
||||
var unfilled = self.width * self.height * format_stride;
|
||||
|
||||
errdefer {
|
||||
device.stop();
|
||||
try self.data.reserve(coral.heap.allocator, unfilled);
|
||||
|
||||
self.data.clear();
|
||||
|
||||
while (unfilled != 0) : (unfilled -= 1) {
|
||||
std.debug.assert(self.data.pushAll(fill_data));
|
||||
}
|
||||
}
|
||||
|
||||
var arena = std.heap.ArenaAllocator.init(coral.heap.allocator);
|
||||
///
|
||||
/// 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 {
|
||||
arena.deinit();
|
||||
coral.heap.allocator.free(qoi_data.bytes);
|
||||
}
|
||||
|
||||
const default_width, const default_height = .{ 1280, 720 };
|
||||
const header = try qoi.Info.decode(qoi_data.reader());
|
||||
var image = Image.init(header.width, header.height, .rgba8);
|
||||
|
||||
defer {
|
||||
image.deinit();
|
||||
}
|
||||
|
||||
{
|
||||
var swapchain = try device.Swapchain.init(default_width, default_height, "Ona");
|
||||
var decoder = qoi.DecodeStream.init(header);
|
||||
|
||||
errdefer {
|
||||
swapchain.deinit();
|
||||
while (try decoder.fetch(qoi_data.reader())) |run| {
|
||||
try coral.bytes.writeN(image.writer(), &run.pixel.bytes(), run.count);
|
||||
}
|
||||
}
|
||||
|
||||
var backbuffer = try device.Target.init(default_width, default_height);
|
||||
return image;
|
||||
}
|
||||
};
|
||||
|
||||
errdefer {
|
||||
backbuffer.deinit();
|
||||
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;
|
||||
}
|
||||
|
||||
try app.setState(Context{
|
||||
.swapchain = swapchain,
|
||||
.backbuffer = backbuffer,
|
||||
});
|
||||
pub fn init() Scene {
|
||||
return .{};
|
||||
}
|
||||
};
|
||||
|
||||
pub fn setup(app: *ona.App) !void {
|
||||
const default_width, const default_height = .{ 1280, 720 };
|
||||
|
||||
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();
|
||||
try ona.asset.register(app, Image);
|
||||
try ona.asset.register(app, Effect);
|
||||
// try app.on(.pre_update, .of(renderFrame));
|
||||
//try app.on(.post_update, .of(swapBuffers));
|
||||
}
|
||||
|
||||
@ -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,22 +6,6 @@ const coral = @import("coral");
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
///
|
||||
/// State machine for compiling GLSL shader source code into targeted executable formats.
|
||||
///
|
||||
pub const Assembly = struct {
|
||||
compiler: c.shaderc_compiler_t,
|
||||
options: c.shaderc_compile_options_t,
|
||||
|
||||
///
|
||||
/// Optimization preference.
|
||||
///
|
||||
pub const Optimizations = enum {
|
||||
unoptimized,
|
||||
optimize_speed,
|
||||
optimize_size,
|
||||
};
|
||||
|
||||
///
|
||||
/// Textual GLSL source code with an associated name.
|
||||
///
|
||||
@ -35,6 +19,22 @@ pub const Assembly = struct {
|
||||
};
|
||||
};
|
||||
|
||||
///
|
||||
/// State machine for compiling GLSL shader source code into targeted executable formats.
|
||||
///
|
||||
pub const Target = struct {
|
||||
compiler: c.shaderc_compiler_t,
|
||||
options: c.shaderc_compile_options_t,
|
||||
|
||||
///
|
||||
/// Optimization preference.
|
||||
///
|
||||
pub const Optimizations = enum {
|
||||
unoptimized,
|
||||
optimize_speed,
|
||||
optimize_size,
|
||||
};
|
||||
|
||||
///
|
||||
/// 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,
|
||||
};
|
||||
|
||||
@ -66,7 +66,7 @@ pub const Assembly = struct {
|
||||
/// **Note** the shader assumes the entry-point of the GLSL program matches the signature `void main()`
|
||||
///
|
||||
pub fn assemble(
|
||||
self: Assembly,
|
||||
self: Target,
|
||||
allocator: std.mem.Allocator,
|
||||
stage: Stage,
|
||||
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_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.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
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 {
|
||||
return error.OutOfMemory;
|
||||
};
|
||||
@ -134,7 +134,7 @@ pub const Assembly = struct {
|
||||
c.shaderc_compile_options_release(options);
|
||||
}
|
||||
|
||||
switch (target) {
|
||||
switch (backend) {
|
||||
.spirv => {
|
||||
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 {
|
||||
float,
|
||||
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;
|
||||
|
||||
while (c.SDL_PollEvent(&event)) {
|
||||
try hid_events.push(switch (event.type) {
|
||||
try hid_events.ptr.push(switch (event.type) {
|
||||
c.SDL_EVENT_QUIT => {
|
||||
return exit.push(.success);
|
||||
return exit.ptr.push(.success);
|
||||
},
|
||||
|
||||
c.SDL_EVENT_KEY_UP => .{ .key_up = @enumFromInt(event.key.scancode) },
|
||||
|
||||
122
src/ona/ona.zig
122
src/ona/ona.zig
@ -4,7 +4,11 @@ pub const Setup = @import("Setup.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");
|
||||
|
||||
@ -16,52 +20,7 @@ const std = @import("std");
|
||||
|
||||
pub const tasks = @import("tasks.zig");
|
||||
|
||||
pub fn Assets(comptime Asset: 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 {
|
||||
pub fn Channel(comptime Message: type) type {
|
||||
return struct {
|
||||
buffers: [2]coral.stack.Sequential(Message) = .{ .empty, .empty },
|
||||
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();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
@ -189,47 +148,29 @@ pub fn Read(comptime State: type) type {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn Receive(comptime Message: type) type {
|
||||
const MessageChannel = Channel(Message);
|
||||
pub const Ticks = struct {
|
||||
last: u32,
|
||||
now: u32,
|
||||
|
||||
return struct {
|
||||
has_channel: ?*const MessageChannel,
|
||||
pub fn bind() App.Frame {
|
||||
return .init(0);
|
||||
}
|
||||
|
||||
const Self = @This();
|
||||
pub fn init(previous_frame: *App.Frame, current_frame: *const App.Frame) Ticks {
|
||||
defer {
|
||||
previous_frame.* = current_frame.*;
|
||||
}
|
||||
|
||||
pub fn init(channel: ?*const MessageChannel) Self {
|
||||
return .{
|
||||
.channel = channel,
|
||||
.last = previous_frame.count(),
|
||||
.now = current_frame.count(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn messages(self: Self) []const Message {
|
||||
if (self.has_channel) |channel| {
|
||||
return channel.messages();
|
||||
}
|
||||
pub fn isNewer(self: Ticks, tick: u32) bool {
|
||||
return tick > self.last and tick <= self.now;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn Send(comptime Message: type) type {
|
||||
const MessageChannel = Channel(Message);
|
||||
|
||||
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 {
|
||||
return struct {
|
||||
@ -253,6 +194,7 @@ fn realtimeLoop(app: *App) !void {
|
||||
const updates_per_frame = 60.0;
|
||||
const target_frame_time = 1.0 / updates_per_frame;
|
||||
const time = app.getState(App.Time).?;
|
||||
const current_frame = app.getState(App.Frame).?;
|
||||
const exit_channel = app.getState(Channel(App.Exit)).?;
|
||||
|
||||
try tasks.start();
|
||||
@ -263,16 +205,15 @@ fn realtimeLoop(app: *App) !void {
|
||||
|
||||
try app.run(.load);
|
||||
|
||||
const ticks_initial = std.time.milliTimestamp();
|
||||
var ticks_previous = ticks_initial;
|
||||
var ticks_previous = c.SDL_GetTicks();
|
||||
var accumulated_time = @as(f64, 0);
|
||||
|
||||
while (true) {
|
||||
const ticks_current = std.time.milliTimestamp();
|
||||
const milliseconds_per_second = 1000.0;
|
||||
while (true) : (current_frame.increment()) {
|
||||
const ticks_current = c.SDL_GetTicks();
|
||||
const ticks_per_second = 1000.0;
|
||||
|
||||
accumulated_time += @as(f64, @floatFromInt(ticks_current - ticks_previous)) / milliseconds_per_second;
|
||||
time.elapsed = @as(f64, @floatFromInt(ticks_current - ticks_initial)) / milliseconds_per_second;
|
||||
accumulated_time += @as(f64, @floatFromInt(ticks_current - ticks_previous)) / ticks_per_second;
|
||||
time.elapsed = @as(f64, @floatFromInt(ticks_current)) / ticks_per_second;
|
||||
ticks_previous = ticks_current;
|
||||
|
||||
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 {
|
||||
const MessageChannel = Channel(Message);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user