renderer-mvp/asset-pipeline #53
|
@ -1,4 +1,4 @@
|
||||||
# Generated assets
|
# Generated assets
|
||||||
/zig-cache
|
/.zig-cache
|
||||||
/zig-out
|
/zig-out
|
||||||
*.glsl.zig
|
*.glsl.zig
|
||||||
|
|
|
@ -10,8 +10,8 @@
|
||||||
},
|
},
|
||||||
.dependencies = .{
|
.dependencies = .{
|
||||||
.sokol = .{
|
.sokol = .{
|
||||||
.url = "git+https://github.com/floooh/sokol-zig.git#796a3d3d54c22d20da9e91a9a9120d5423d1e700",
|
.url = "git+https://github.com/floooh/sokol-zig.git#7c25767e51aa06dd5fb0684e4a8f2200d182ef27",
|
||||||
.hash = "12209a187e071d76af00c02865677d170f844376866d062b1b5f82e4ecbd750c4e18",
|
.hash = "1220fa7f47fbaf2f1ed8c17fab2d23b6a85bcbbc4aa0b3802c90a3e8bf6fca1f8569",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ pub fn create_event(self: *Self, label: []const u8) std.mem.Allocator.Error!Even
|
||||||
|
|
||||||
const index = self.event_systems.len();
|
const index = self.event_systems.len();
|
||||||
|
|
||||||
try self.event_systems.push(systems);
|
try self.event_systems.push_grow(systems);
|
||||||
|
|
||||||
return @enumFromInt(index);
|
return @enumFromInt(index);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ pub const ascii = @import("./ascii.zig");
|
||||||
|
|
||||||
pub const dag = @import("./dag.zig");
|
pub const dag = @import("./dag.zig");
|
||||||
|
|
||||||
pub const debug = @import("./debug.zig");
|
pub const files = @import("./files.zig");
|
||||||
|
|
||||||
pub const hashes = @import("./hashes.zig");
|
pub const hashes = @import("./hashes.zig");
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ pub fn Graph(comptime Payload: type) type {
|
||||||
pub fn append(self: *Self, payload: Payload) std.mem.Allocator.Error!Node {
|
pub fn append(self: *Self, payload: Payload) std.mem.Allocator.Error!Node {
|
||||||
const node = @as(Node, @enumFromInt(self.table.len()));
|
const node = @as(Node, @enumFromInt(self.table.len()));
|
||||||
|
|
||||||
try self.table.push(.{
|
try self.table.push_grow(.{
|
||||||
.payload = payload,
|
.payload = payload,
|
||||||
.edges = .{.allocator = self.table.allocator},
|
.edges = .{.allocator = self.table.allocator},
|
||||||
});
|
});
|
||||||
|
@ -83,7 +83,7 @@ pub fn Graph(comptime Payload: type) type {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (slices.index_of(edges.values, 0, edge_node) == null) {
|
if (slices.index_of(edges.values, 0, edge_node) == null) {
|
||||||
try edges.push(edge_node);
|
try edges.push_grow(edge_node);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
pub fn assert_ok(error_union: anytype) @typeInfo(@TypeOf(error_union)).ErrorUnion.payload {
|
|
||||||
return error_union catch unreachable;
|
|
||||||
}
|
|
|
@ -0,0 +1,124 @@
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
|
const io = @import("./io.zig");
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const Error = error {
|
||||||
|
FileNotFound,
|
||||||
|
FileInaccessible,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Stat = struct {
|
||||||
|
size: u64,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Storage = struct {
|
||||||
|
userdata: *anyopaque,
|
||||||
|
vtable: *const VTable,
|
||||||
|
|
||||||
|
pub const VTable = struct {
|
||||||
|
stat: *const fn (*anyopaque, []const u8) Error!Stat,
|
||||||
|
read: *const fn (*anyopaque, []const u8, usize, []io.Byte) Error!usize,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn read_bytes(self: Storage, path: []const u8, offset: usize, output: []io.Byte) Error!usize {
|
||||||
|
return self.vtable.read(self.userdata, path, offset, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_foreign(self: Storage, path: []const u8, offset: usize, comptime Type: type) Error!?Type {
|
||||||
|
const decoded = (try self.read_native(path, offset, Type)) orelse {
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return switch (@typeInfo(Type)) {
|
||||||
|
.Struct => std.mem.byteSwapAllFields(Type, &decoded),
|
||||||
|
else => @byteSwap(decoded),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_native(self: Storage, path: []const u8, offset: usize, comptime Type: type) Error!?Type {
|
||||||
|
var buffer = @as([@sizeOf(Type)]io.Byte, undefined);
|
||||||
|
|
||||||
|
if (try self.vtable.read(self.userdata, path, offset, &buffer) != buffer.len) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return @as(*align(1) const Type, @ptrCast(&buffer)).*;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const read_little = switch (native_endian) {
|
||||||
|
.little => read_native,
|
||||||
|
.big => read_foreign,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const read_big = switch (native_endian) {
|
||||||
|
.little => read_foreign,
|
||||||
|
.big => read_native,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const bundle = init: {
|
||||||
|
const Bundle = struct {
|
||||||
|
fn full_path(path: []const u8) Error![4095:0]u8 {
|
||||||
|
var buffer = [_:0]u8{0} ** 4095;
|
||||||
|
|
||||||
|
_ = std.fs.cwd().realpath(path, &buffer) catch {
|
||||||
|
return error.FileInaccessible;
|
||||||
|
};
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read(_: *anyopaque, path: []const u8, offset: usize, output: []io.Byte) Error!usize {
|
||||||
|
var file = std.fs.openFileAbsoluteZ(&(try full_path(path)), .{.mode = .read_only}) catch |open_error| {
|
||||||
|
return switch (open_error) {
|
||||||
|
error.FileNotFound => error.FileNotFound,
|
||||||
|
else => error.FileInaccessible,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
defer file.close();
|
||||||
|
|
||||||
|
if (offset != 0) {
|
||||||
|
file.seekTo(offset) catch {
|
||||||
|
return error.FileInaccessible;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return file.read(output) catch error.FileInaccessible;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stat(_: *anyopaque, path: []const u8) Error!Stat {
|
||||||
|
const file_stat = get: {
|
||||||
|
var file = std.fs.openFileAbsoluteZ(&(try full_path(path)), .{.mode = .read_only}) catch |open_error| {
|
||||||
|
return switch (open_error) {
|
||||||
|
error.FileNotFound => error.FileNotFound,
|
||||||
|
else => error.FileInaccessible,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
defer file.close();
|
||||||
|
|
||||||
|
break: get file.stat() catch {
|
||||||
|
return error.FileInaccessible;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.size = file_stat.size,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
break: init Storage{
|
||||||
|
.userdata = undefined,
|
||||||
|
|
||||||
|
.vtable = &.{
|
||||||
|
.stat = Bundle.stat,
|
||||||
|
.read = Bundle.read,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const native_endian = builtin.cpu.arch.endian();
|
|
@ -8,21 +8,10 @@ const std = @import("std");
|
||||||
|
|
||||||
pub const Byte = u8;
|
pub const Byte = u8;
|
||||||
kayomn marked this conversation as resolved
|
|||||||
|
|
||||||
pub const Decoder = coral.io.Functor(coral.io.Error!void, &.{[]coral.Byte});
|
|
||||||
|
|
||||||
pub const Error = error {
|
pub const Error = error {
|
||||||
UnavailableResource,
|
UnavailableResource,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn FixedBuffer(comptime len: usize, comptime default_value: anytype) type {
|
|
||||||
const Value = @TypeOf(default_value);
|
|
||||||
|
|
||||||
return struct {
|
|
||||||
filled: usize = 0,
|
|
||||||
values: [len]Value = [_]Value{default_value} ** len,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn Functor(comptime Output: type, comptime input_types: []const type) type {
|
pub fn Functor(comptime Output: type, comptime input_types: []const type) type {
|
||||||
const InputTuple = std.meta.Tuple(input_types);
|
const InputTuple = std.meta.Tuple(input_types);
|
||||||
|
|
||||||
|
@ -119,20 +108,6 @@ pub fn Generator(comptime Output: type, comptime input_types: []const type) type
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const NullWritable = struct {
|
|
||||||
written: usize = 0,
|
|
||||||
|
|
||||||
pub fn writer(self: *NullWritable) Writer {
|
|
||||||
return Writer.bind(NullWritable, self, write);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write(self: *NullWritable, buffer: []const u8) !usize {
|
|
||||||
self.written += buffer.len;
|
|
||||||
|
|
||||||
return buffer.len;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const PrintError = Error || error {
|
pub const PrintError = Error || error {
|
||||||
IncompleteWrite,
|
IncompleteWrite,
|
||||||
};
|
};
|
||||||
|
@ -141,8 +116,6 @@ pub const Reader = Generator(Error!usize, &.{[]coral.Byte});
|
||||||
|
|
||||||
pub const Writer = Generator(Error!usize, &.{[]const coral.Byte});
|
pub const Writer = Generator(Error!usize, &.{[]const coral.Byte});
|
||||||
|
|
||||||
const native_endian = builtin.cpu.arch.endian();
|
|
||||||
|
|
||||||
pub fn alloc_read(input: coral.io.Reader, allocator: std.mem.Allocator) []coral.Byte {
|
pub fn alloc_read(input: coral.io.Reader, allocator: std.mem.Allocator) []coral.Byte {
|
||||||
const buffer = coral.Stack(coral.Byte){.allocator = allocator};
|
const buffer = coral.Stack(coral.Byte){.allocator = allocator};
|
||||||
|
|
||||||
|
@ -153,20 +126,6 @@ pub fn alloc_read(input: coral.io.Reader, allocator: std.mem.Allocator) []coral.
|
||||||
return buffer.to_allocation(streamed);
|
return buffer.to_allocation(streamed);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn are_equal(a: []const Byte, b: []const Byte) bool {
|
|
||||||
if (a.len != b.len) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (0 .. a.len) |i| {
|
|
||||||
if (a[i] != b[i]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const bits_per_byte = 8;
|
pub const bits_per_byte = 8;
|
||||||
|
|
||||||
pub fn bytes_of(value: anytype) []const Byte {
|
pub fn bytes_of(value: anytype) []const Byte {
|
||||||
|
@ -179,14 +138,6 @@ pub fn bytes_of(value: anytype) []const Byte {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ends_with(haystack: []const Byte, needle: []const Byte) bool {
|
|
||||||
if (needle.len > haystack.len) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return are_equal(haystack[haystack.len - needle.len ..], needle);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn print(writer: Writer, utf8: []const u8) PrintError!void {
|
pub fn print(writer: Writer, utf8: []const u8) PrintError!void {
|
||||||
if (try writer.yield(.{utf8}) != utf8.len) {
|
if (try writer.yield(.{utf8}) != utf8.len) {
|
||||||
return error.IncompleteWrite;
|
return error.IncompleteWrite;
|
||||||
|
@ -208,35 +159,6 @@ pub fn skip_n(input: Reader, distance: u64) Error!void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_foreign(input: Reader, comptime Type: type) Error!Type {
|
|
||||||
const decoded = try read_native(input, Type);
|
|
||||||
|
|
||||||
return switch (@typeInfo(input)) {
|
|
||||||
.Struct => std.mem.byteSwapAllFields(Type, &decoded),
|
|
||||||
else => @byteSwap(decoded),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_native(input: Reader, comptime Type: type) Error!Type {
|
|
||||||
var buffer = @as([@sizeOf(Type)]coral.Byte, undefined);
|
|
||||||
|
|
||||||
if (try input.yield(.{&buffer}) != buffer.len) {
|
|
||||||
return error.UnavailableResource;
|
|
||||||
}
|
|
||||||
|
|
||||||
return @as(*align(1) const Type, @ptrCast(&buffer)).*;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const read_little = switch (native_endian) {
|
|
||||||
.little => read_native,
|
|
||||||
.big => read_foreign,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const read_big = switch (native_endian) {
|
|
||||||
.little => read_foreign,
|
|
||||||
.big => read_native,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn slice_sentineled(comptime sen: anytype, ptr: [*:sen]const @TypeOf(sen)) [:sen]const @TypeOf(sen) {
|
pub fn slice_sentineled(comptime sen: anytype, ptr: [*:sen]const @TypeOf(sen)) [:sen]const @TypeOf(sen) {
|
||||||
var len = @as(usize, 0);
|
var len = @as(usize, 0);
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,10 @@ pub fn Sequential(comptime Value: type) type {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn grow(self: *Self, additional: usize) std.mem.Allocator.Error!void {
|
pub fn grow(self: *Self, additional: usize) std.mem.Allocator.Error!void {
|
||||||
|
if (additional == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const grown_capacity = self.cap + additional;
|
const grown_capacity = self.cap + additional;
|
||||||
const buffer = try self.allocator.alloc(Value, grown_capacity);
|
const buffer = try self.allocator.alloc(Value, grown_capacity);
|
||||||
|
|
||||||
|
@ -87,7 +91,39 @@ pub fn Sequential(comptime Value: type) type {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push(self: *Self, value: Value) std.mem.Allocator.Error!void {
|
pub fn push(self: *Self, value: Value) bool {
|
||||||
|
if (self.values.len == self.cap) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const offset_index = self.values.len;
|
||||||
|
|
||||||
|
self.values = self.values.ptr[0 .. self.values.len + 1];
|
||||||
|
|
||||||
|
self.values[offset_index] = value;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_all(self: *Self, values: []const Value) bool {
|
||||||
|
const new_length = self.values.len + values.len;
|
||||||
|
|
||||||
|
if (new_length > self.cap) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const offset_index = self.values.len;
|
||||||
|
|
||||||
|
self.values = self.values.ptr[0 .. new_length];
|
||||||
|
|
||||||
|
for (0 .. values.len) |index| {
|
||||||
|
self.values[offset_index + index] = values[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_grow(self: *Self, value: Value) std.mem.Allocator.Error!void {
|
||||||
if (self.values.len == self.cap) {
|
if (self.values.len == self.cap) {
|
||||||
try self.grow(@max(1, self.cap));
|
try self.grow(@max(1, self.cap));
|
||||||
}
|
}
|
||||||
|
@ -99,27 +135,11 @@ pub fn Sequential(comptime Value: type) type {
|
||||||
self.values[offset_index] = value;
|
self.values[offset_index] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_all(self: *Self, values: []const Value) std.mem.Allocator.Error!void {
|
|
||||||
const new_length = self.values.len + values.len;
|
|
||||||
|
|
||||||
if (new_length > self.cap) {
|
|
||||||
try self.grow(new_length);
|
|
||||||
}
|
|
||||||
|
|
||||||
const offset_index = self.values.len;
|
|
||||||
|
|
||||||
self.values = self.values.ptr[0 .. new_length];
|
|
||||||
|
|
||||||
for (0 .. values.len) |index| {
|
|
||||||
self.values[offset_index + index] = values[index];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_many(self: *Self, n: usize, value: Value) std.mem.Allocator.Error!void {
|
pub fn push_many(self: *Self, n: usize, value: Value) std.mem.Allocator.Error!void {
|
||||||
const new_length = self.values.len + n;
|
const new_length = self.values.len + n;
|
||||||
|
|
||||||
if (new_length > self.cap) {
|
if (new_length > self.cap) {
|
||||||
try self.grow(new_length);
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const offset_index = self.values.len;
|
const offset_index = self.values.len;
|
||||||
|
@ -129,6 +149,8 @@ pub fn Sequential(comptime Value: type) type {
|
||||||
for (0 .. n) |index| {
|
for (0 .. n) |index| {
|
||||||
self.values[offset_index + index] = value;
|
self.values[offset_index + index] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resize(self: *Self, size: usize, default_value: Value) std.mem.Allocator.Error!void {
|
pub fn resize(self: *Self, size: usize, default_value: Value) std.mem.Allocator.Error!void {
|
||||||
|
@ -237,7 +259,7 @@ pub fn Parallel(comptime Value: type) type {
|
||||||
return self.values.len;
|
return self.values.len;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push(self: *Self, value: Value) std.mem.Allocator.Error!void {
|
pub fn push_grow(self: *Self, value: Value) std.mem.Allocator.Error!void {
|
||||||
if (self.len() == self.cap) {
|
if (self.len() == self.cap) {
|
||||||
try self.grow(@max(1, self.cap));
|
try self.grow(@max(1, self.cap));
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,7 @@ pub const BindContext = struct {
|
||||||
const id = resource.type_id(Resource);
|
const id = resource.type_id(Resource);
|
||||||
|
|
||||||
if (!self.accesses_resource(.read_write, id)) {
|
if (!self.accesses_resource(.read_write, id)) {
|
||||||
try self.systems.graph.get_ptr(self.node).?.resource_accesses.push(.{.read_write = id});
|
try self.systems.graph.get_ptr(self.node).?.resource_accesses.push_grow(.{.read_write = id});
|
||||||
}
|
}
|
||||||
|
|
||||||
const read_write_resource_nodes = lazily_create: {
|
const read_write_resource_nodes = lazily_create: {
|
||||||
|
@ -65,7 +65,7 @@ pub const BindContext = struct {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (slices.index_of(read_write_resource_nodes.values, 0, self.node) == null) {
|
if (slices.index_of(read_write_resource_nodes.values, 0, self.node) == null) {
|
||||||
try read_write_resource_nodes.push(self.node);
|
try read_write_resource_nodes.push_grow(self.node);
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
|
@ -79,7 +79,7 @@ pub const BindContext = struct {
|
||||||
const id = resource.type_id(Resource);
|
const id = resource.type_id(Resource);
|
||||||
|
|
||||||
if (!self.accesses_resource(.read_only, id)) {
|
if (!self.accesses_resource(.read_only, id)) {
|
||||||
try self.systems.graph.get_ptr(self.node).?.resource_accesses.push(.{.read_only = id});
|
try self.systems.graph.get_ptr(self.node).?.resource_accesses.push_grow(.{.read_only = id});
|
||||||
}
|
}
|
||||||
|
|
||||||
const read_only_resource_nodes = lazily_create: {
|
const read_only_resource_nodes = lazily_create: {
|
||||||
|
@ -93,7 +93,7 @@ pub const BindContext = struct {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (slices.index_of(read_only_resource_nodes.values, 0, self.node) == null) {
|
if (slices.index_of(read_only_resource_nodes.values, 0, self.node) == null) {
|
||||||
try read_only_resource_nodes.push(self.node);
|
try read_only_resource_nodes.push_grow(self.node);
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
|
@ -291,7 +291,7 @@ pub const Schedule = struct {
|
||||||
try populate_bundle(bundle, graph, edge);
|
try populate_bundle(bundle, graph, edge);
|
||||||
}
|
}
|
||||||
|
|
||||||
try bundle.push(node);
|
try bundle.push_grow(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sort(schedule: *Schedule) !void {
|
fn sort(schedule: *Schedule) !void {
|
||||||
|
@ -304,7 +304,7 @@ pub const Schedule = struct {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
try schedule.parallel_work_bundles.push(.{.allocator = heap.allocator});
|
try schedule.parallel_work_bundles.push_grow(.{.allocator = heap.allocator});
|
||||||
|
|
||||||
const bundle = schedule.parallel_work_bundles.get_ptr().?;
|
const bundle = schedule.parallel_work_bundles.get_ptr().?;
|
||||||
|
|
||||||
|
@ -328,8 +328,9 @@ pub const Schedule = struct {
|
||||||
.main => {
|
.main => {
|
||||||
const extracted_work = work.values[index ..];
|
const extracted_work = work.values[index ..];
|
||||||
|
|
||||||
try schedule.blocking_work.push_all(extracted_work);
|
try schedule.blocking_work.grow(extracted_work.len);
|
||||||
|
|
||||||
|
std.debug.assert(schedule.blocking_work.push_all(extracted_work));
|
||||||
std.debug.assert(work.pop_many(extracted_work.len));
|
std.debug.assert(work.pop_many(extracted_work.len));
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -488,7 +489,7 @@ pub const Schedule = struct {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
try nodes.push(node);
|
try nodes.push_grow(node);
|
||||||
|
|
||||||
self.invalidate_work();
|
self.invalidate_work();
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,6 @@ const ascii = @import("./ascii.zig");
|
||||||
|
|
||||||
const coral = @import("./coral.zig");
|
const coral = @import("./coral.zig");
|
||||||
|
|
||||||
const debug = @import("./debug.zig");
|
|
||||||
|
|
||||||
const io = @import("./io.zig");
|
const io = @import("./io.zig");
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
@ -16,7 +14,7 @@ pub fn alloc_formatted(allocator: std.mem.Allocator, comptime format: []const u8
|
||||||
|
|
||||||
errdefer buffer.deinit();
|
errdefer buffer.deinit();
|
||||||
|
|
||||||
debug.assert_try(print_formatted(buffer.writer(), format, args));
|
print_formatted(buffer.writer(), format, args) catch unreachable;
|
||||||
|
|
||||||
return buffer.to_allocation(formatted_len, 0);
|
return buffer.to_allocation(formatted_len, 0);
|
||||||
}
|
}
|
||||||
|
@ -24,7 +22,7 @@ pub fn alloc_formatted(allocator: std.mem.Allocator, comptime format: []const u8
|
||||||
fn count_formatted(comptime format: []const u8, args: anytype) usize {
|
fn count_formatted(comptime format: []const u8, args: anytype) usize {
|
||||||
var count = io.defaultWritable{};
|
var count = io.defaultWritable{};
|
||||||
|
|
||||||
debug.assert_try(print_formatted(count.writer(), format, args));
|
print_formatted(count.writer(), format, args) catch unreachable;
|
||||||
|
|
||||||
return count.written;
|
return count.written;
|
||||||
}
|
}
|
||||||
|
|
37
src/main.zig
37
src/main.zig
|
@ -6,6 +6,7 @@ const ona = @import("ona");
|
||||||
|
|
||||||
const Actors = struct {
|
const Actors = struct {
|
||||||
instances: coral.stack.Sequential(ona.gfx.Point2D) = .{.allocator = coral.heap.allocator},
|
instances: coral.stack.Sequential(ona.gfx.Point2D) = .{.allocator = coral.heap.allocator},
|
||||||
|
quad_mesh_2d: ona.gfx.Handle = .none,
|
||||||
body_texture: ona.gfx.Handle = .none,
|
body_texture: ona.gfx.Handle = .none,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,45 +22,29 @@ pub fn main() !void {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load(display: coral.ReadBlocking(ona.gfx.Display), actors: coral.Write(Actors), gfx: ona.gfx.Work) !void {
|
fn load(display: coral.Write(ona.gfx.Display), actors: coral.Write(Actors), assets: coral.Write(ona.gfx.Assets)) !void {
|
||||||
display.res.resize(1280, 720);
|
display.res.width, display.res.height = .{1280, 720};
|
||||||
|
actors.res.body_texture = try assets.res.open_file(coral.files.bundle, "actor.bmp");
|
||||||
|
actors.res.quad_mesh_2d = try assets.res.open_quad_mesh_2d(@splat(1));
|
||||||
|
|
||||||
const crap = [_]u32{
|
try actors.res.instances.push_grow(.{0, 0});
|
||||||
0xFFFFFFFF, 0xFF000000, 0xFFFFFFFF, 0xFF000000,
|
|
||||||
0xFF000000, 0xFFFFFFFF, 0xFF000000, 0xFFFFFFFF,
|
|
||||||
0xFFFFFFFF, 0xFF000000, 0xFFFFFFFF, 0xFF000000,
|
|
||||||
0xFF000000, 0xFFFFFFFF, 0xFF000000, 0xFFFFFFFF,
|
|
||||||
};
|
|
||||||
|
|
||||||
actors.res.body_texture = try gfx.queue.open(.{
|
|
||||||
.resource = .{
|
|
||||||
.texture = .{
|
|
||||||
.data = coral.io.bytes_of(&crap),
|
|
||||||
.width = 4,
|
|
||||||
.access = .static,
|
|
||||||
.format = .bgra8888,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
try actors.res.instances.push(.{0, 0});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exit(actors: coral.Write(Actors)) void {
|
fn exit(actors: coral.Write(Actors)) void {
|
||||||
actors.res.instances.deinit();
|
actors.res.instances.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(gfx: ona.gfx.Work, actors: coral.Write(Actors)) !void {
|
fn render(queue: ona.gfx.Queue, actors: coral.Write(Actors)) !void {
|
||||||
for (actors.res.instances.values) |instance| {
|
for (actors.res.instances.values) |instance| {
|
||||||
try gfx.queue.draw(.{
|
try queue.commands.append(.{
|
||||||
.instance_2d = .{
|
.instance_2d = .{
|
||||||
.mesh_2d = gfx.primitives.quad_mesh,
|
.mesh_2d = actors.res.quad_mesh_2d,
|
||||||
.texture = actors.res.body_texture,
|
.texture = actors.res.body_texture,
|
||||||
|
|
||||||
.transform = .{
|
.transform = .{
|
||||||
.origin = instance,
|
.origin = instance,
|
||||||
.xbasis = .{100, 0},
|
.xbasis = .{64, 0},
|
||||||
.ybasis = .{0, 100},
|
.ybasis = .{0, 64},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
272
src/ona/gfx.zig
272
src/ona/gfx.zig
|
@ -1,37 +1,112 @@
|
||||||
const App = @import("./App.zig");
|
const App = @import("./App.zig");
|
||||||
|
|
||||||
pub const Queue = @import("./gfx/Queue.zig");
|
|
||||||
|
|
||||||
const coral = @import("coral");
|
const coral = @import("coral");
|
||||||
|
|
||||||
const Device = @import("./gfx/Device.zig");
|
const device = @import("./gfx/device.zig");
|
||||||
|
|
||||||
const ext = @import("./ext.zig");
|
const ext = @import("./ext.zig");
|
||||||
|
|
||||||
|
const formats = @import("./gfx/formats.zig");
|
||||||
|
|
||||||
const msg = @import("./msg.zig");
|
const msg = @import("./msg.zig");
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const Assets = struct {
|
||||||
|
context: device.Context,
|
||||||
|
formats: coral.stack.Sequential(Format),
|
||||||
|
staging_arena: std.heap.ArenaAllocator,
|
||||||
|
|
||||||
|
pub const Format = struct {
|
||||||
|
extension: []const u8,
|
||||||
|
file_desc: *const fn (*std.heap.ArenaAllocator, coral.files.Storage, []const u8) Error!Desc,
|
||||||
|
|
||||||
|
pub const Error = std.mem.Allocator.Error || coral.files.Error || error {
|
||||||
|
FormatUnsupported,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn open_file(self: *Assets, storage: coral.files.Storage, path: []const u8) (OpenError || Format.Error)!Handle {
|
||||||
|
defer {
|
||||||
|
const max_cache_size = 536870912;
|
||||||
|
|
||||||
|
if (!self.staging_arena.reset(.{.retain_with_limit = max_cache_size})) {
|
||||||
|
std.log.warn("failed to retain staging arena size of {} bytes", .{max_cache_size});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (self.formats.values) |format| {
|
||||||
|
if (!std.mem.endsWith(u8, path, format.extension)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.context.open(try format.file_desc(&self.staging_arena, storage, path));
|
||||||
|
}
|
||||||
|
|
||||||
|
return .none;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_quad_mesh_2d(self: *Assets, extents: Point2D) OpenError!Handle {
|
||||||
|
const width, const height = extents / @as(Point2D, @splat(2));
|
||||||
|
|
||||||
|
return self.context.open(.{
|
||||||
|
.mesh_2d = .{
|
||||||
|
.indices = &.{0, 1, 2, 0, 2, 3},
|
||||||
|
|
||||||
|
.vertices = &.{
|
||||||
|
.{.xy = .{-width, height}, .uv = .{0, 1}},
|
||||||
|
.{.xy = .{width, height}, .uv = .{1, 1}},
|
||||||
|
.{.xy = .{width, -height}, .uv = .{1, 0}},
|
||||||
|
.{.xy = .{-width, -height}, .uv = .{0, 0}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
pub const Color = @Vector(4, f32);
|
pub const Color = @Vector(4, f32);
|
||||||
|
|
||||||
|
pub const Desc = union (enum) {
|
||||||
|
texture: Texture,
|
||||||
|
mesh_2d: Mesh2D,
|
||||||
|
|
||||||
|
pub const Mesh2D = struct {
|
||||||
|
vertices: []const Vertex,
|
||||||
|
indices: []const u16,
|
||||||
|
|
||||||
|
pub const Vertex = struct {
|
||||||
|
xy: Point2D,
|
||||||
|
uv: Point2D,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Texture = struct {
|
||||||
|
data: []const coral.io.Byte,
|
||||||
|
width: u16,
|
||||||
|
format: Format,
|
||||||
|
access: Access,
|
||||||
|
|
||||||
|
pub const Access = enum {
|
||||||
|
static,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Format = enum {
|
||||||
|
rgba8,
|
||||||
|
bgra8,
|
||||||
|
|
||||||
|
pub fn byte_size(self: Format) usize {
|
||||||
|
return switch (self) {
|
||||||
|
.rgba8, .bgra8 => 4,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
pub const Display = struct {
|
pub const Display = struct {
|
||||||
sdl_window: *ext.SDL_Window,
|
width: u16 = 1280,
|
||||||
device: Device,
|
height: u16 = 720,
|
||||||
|
clear_color: Color = colors.black,
|
||||||
pub fn resize(self: Display, width: u16, height: u16) void {
|
|
||||||
ext.SDL_SetWindowSize(self.sdl_window, width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn retitle(self: Display, title: []const u8) void {
|
|
||||||
var sentineled_title = [_:0]u8{0} ** 255;
|
|
||||||
|
|
||||||
@memcpy(sentineled_title[0 .. @min(title.len, sentineled_title.len)], title);
|
|
||||||
ext.SDL_SetWindowTitle(self.sdl_window, &sentineled_title);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_resizable(self: Display, resizable: bool) void {
|
|
||||||
ext.SDL_SetWindowResizable(self.sdl_window, @intFromBool(resizable));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Handle = enum (usize) {
|
pub const Handle = enum (usize) {
|
||||||
|
@ -62,71 +137,68 @@ pub const Input = union (enum) {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const OpenError = std.mem.Allocator.Error || error {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
pub const Point2D = @Vector(2, f32);
|
pub const Point2D = @Vector(2, f32);
|
||||||
|
|
||||||
|
pub const Queue = struct {
|
||||||
|
commands: *device.RenderList,
|
||||||
|
|
||||||
|
pub const State = struct {
|
||||||
|
command_index: usize,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn bind(_: coral.system.BindContext) std.mem.Allocator.Error!State {
|
||||||
|
// TODO: Review how good of an idea this global state is, even if bind is guaranteed to always be ran on main.
|
||||||
|
if (renders.is_empty()) {
|
||||||
|
renders = .{.allocator = coral.heap.allocator};
|
||||||
|
}
|
||||||
|
|
||||||
|
const command_index = renders.len();
|
||||||
|
|
||||||
|
try renders.push_grow(device.RenderChain.init(coral.heap.allocator));
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.command_index = command_index,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(state: *State) Queue {
|
||||||
|
return .{
|
||||||
|
.commands = renders.values[state.command_index].pending(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unbind(state: *State) void {
|
||||||
|
std.debug.assert(!renders.is_empty());
|
||||||
|
|
||||||
|
const render = &renders.values[state.command_index];
|
||||||
|
|
||||||
|
render.deinit();
|
||||||
|
std.mem.swap(device.RenderChain, render, renders.get_ptr().?);
|
||||||
|
std.debug.assert(renders.pop());
|
||||||
|
|
||||||
|
if (renders.is_empty()) {
|
||||||
|
Queue.renders.deinit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var renders = coral.stack.Sequential(device.RenderChain){.allocator = coral.heap.allocator};
|
||||||
|
};
|
||||||
|
|
||||||
pub const Transform2D = extern struct {
|
pub const Transform2D = extern struct {
|
||||||
xbasis: Point2D = .{1, 0},
|
xbasis: Point2D = .{1, 0},
|
||||||
ybasis: Point2D = .{0, 1},
|
ybasis: Point2D = .{0, 1},
|
||||||
origin: Point2D = @splat(0),
|
origin: Point2D = @splat(0),
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Work = struct {
|
const builtin_formats = [_]Assets.Format{
|
||||||
queue: *Queue.Buffer,
|
.{
|
||||||
primitives: *const Primitives,
|
.extension = "bmp",
|
||||||
|
.file_desc = formats.bmp_file_desc,
|
||||||
const Primitives = struct {
|
|
||||||
quad_mesh: Handle,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const State = struct {
|
|
||||||
queue: *Queue,
|
|
||||||
primitives: *const Primitives,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn bind(context: coral.system.BindContext) std.mem.Allocator.Error!State {
|
|
||||||
const queue = try Queue.create();
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.primitives = (try context.register_read_only_resource_access(.none, Primitives)) orelse create: {
|
|
||||||
const buffer = queue.pending();
|
|
||||||
const half_extent = 0.5;
|
|
||||||
|
|
||||||
try context.world.set_resource(.none, Primitives{
|
|
||||||
.quad_mesh = try buffer.open(.{
|
|
||||||
.label = "quad mesh primitive",
|
|
||||||
|
|
||||||
.resource = .{
|
|
||||||
.mesh_2d = .{
|
|
||||||
.indices = &.{0, 1, 2, 0, 2, 3},
|
|
||||||
|
|
||||||
.vertices = &.{
|
|
||||||
.{.xy = .{-half_extent, half_extent}, .uv = .{0, 1}},
|
|
||||||
.{.xy = .{half_extent, half_extent}, .uv = .{1, 1}},
|
|
||||||
.{.xy = .{half_extent, -half_extent}, .uv = .{1, 0}},
|
|
||||||
.{.xy = .{-half_extent, -half_extent}, .uv = .{0, 0}},
|
|
||||||
},
|
},
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
break: create (try context.register_read_only_resource_access(.none, Primitives)).?;
|
|
||||||
},
|
|
||||||
|
|
||||||
.queue = queue,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(state: *State) Work {
|
|
||||||
return .{
|
|
||||||
.queue = state.queue.pending(),
|
|
||||||
.primitives = state.primitives,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unbind(state: *State) void {
|
|
||||||
state.queue.release();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const colors = struct {
|
pub const colors = struct {
|
||||||
|
@ -156,43 +228,45 @@ pub fn poll(app: coral.Write(App), inputs: msg.Send(Input)) !void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup(world: *coral.World, events: App.Events) (error {SDLError} || std.Thread.SpawnError || std.mem.Allocator.Error)!void {
|
pub fn setup(world: *coral.World, events: App.Events) (error {Unsupported} || std.Thread.SpawnError || std.mem.Allocator.Error)!void {
|
||||||
if (ext.SDL_Init(ext.SDL_INIT_VIDEO) != 0) {
|
if (ext.SDL_Init(ext.SDL_INIT_VIDEO) != 0) {
|
||||||
return error.SDLError;
|
return error.Unsupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sdl_window = create: {
|
var context = try device.Context.init();
|
||||||
const position = ext.SDL_WINDOWPOS_CENTERED;
|
|
||||||
const flags = ext.SDL_WINDOW_OPENGL;
|
|
||||||
const width = 640;
|
|
||||||
const height = 480;
|
|
||||||
|
|
||||||
break: create ext.SDL_CreateWindow("Ona", position, position, width, height, flags) orelse {
|
errdefer context.deinit();
|
||||||
return error.SDLError;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
errdefer ext.SDL_DestroyWindow(sdl_window);
|
var registered_formats = coral.stack.Sequential(Assets.Format){.allocator = coral.heap.allocator};
|
||||||
|
|
||||||
var device = try Device.init(sdl_window);
|
errdefer registered_formats.deinit();
|
||||||
|
|
||||||
errdefer device.deinit();
|
try registered_formats.grow(builtin_formats.len);
|
||||||
|
std.debug.assert(registered_formats.push_all(&builtin_formats));
|
||||||
|
|
||||||
try world.set_resource(.main, Display{
|
try world.set_resource(.none, Assets{
|
||||||
.device = device,
|
.staging_arena = std.heap.ArenaAllocator.init(coral.heap.allocator),
|
||||||
.sdl_window = sdl_window,
|
.formats = registered_formats,
|
||||||
|
.context = context,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
try world.set_resource(.none, Display{});
|
||||||
try world.on_event(events.pre_update, coral.system_fn(poll), .{.label = "poll gfx"});
|
try world.on_event(events.pre_update, coral.system_fn(poll), .{.label = "poll gfx"});
|
||||||
try world.on_event(events.exit, coral.system_fn(stop), .{.label = "stop gfx"});
|
try world.on_event(events.exit, coral.system_fn(stop), .{.label = "stop gfx"});
|
||||||
try world.on_event(events.finish, coral.system_fn(submit), .{.label = "submit gfx"});
|
try world.on_event(events.finish, coral.system_fn(synchronize), .{.label = "synchronize gfx"});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stop(display: coral.WriteBlocking(Display)) void {
|
pub fn stop(assets: coral.Write(Assets)) void {
|
||||||
display.res.device.deinit();
|
assets.res.staging_arena.deinit();
|
||||||
ext.SDL_DestroyWindow(display.res.sdl_window);
|
assets.res.formats.deinit();
|
||||||
|
assets.res.context.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn submit(display: coral.WriteBlocking(Display)) void {
|
pub fn synchronize(assets: coral.Write(Assets), display: coral.Read(Display)) !void {
|
||||||
display.res.device.submit();
|
assets.res.context.submit(.{
|
||||||
|
.width = display.res.width,
|
||||||
|
.height = display.res.height,
|
||||||
|
.clear_color = display.res.clear_color,
|
||||||
|
.renders = Queue.renders.values,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,534 +0,0 @@
|
||||||
const Queue = @import("./Queue.zig");
|
|
||||||
|
|
||||||
const coral = @import("coral");
|
|
||||||
|
|
||||||
const ext = @import("../ext.zig");
|
|
||||||
|
|
||||||
const gfx = @import("../gfx.zig");
|
|
||||||
|
|
||||||
const sokol = @import("sokol");
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
thread: std.Thread,
|
|
||||||
clear_color: gfx.Color = gfx.colors.black,
|
|
||||||
state: *State,
|
|
||||||
|
|
||||||
const AtomicBool = std.atomic.Value(bool);
|
|
||||||
|
|
||||||
const Frame = struct {
|
|
||||||
width: u16,
|
|
||||||
height: u16,
|
|
||||||
flushed_instance_2d_count: usize = 0,
|
|
||||||
pushed_instance_2d_count: usize = 0,
|
|
||||||
mesh_2d: gfx.Handle = .none,
|
|
||||||
texture: gfx.Handle = .none,
|
|
||||||
|
|
||||||
fn unflushed_instance_2d_count(self: Frame) usize {
|
|
||||||
return self.pushed_instance_2d_count - self.flushed_instance_2d_count;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const Render = struct {
|
|
||||||
resources: coral.stack.Sequential(Resource),
|
|
||||||
instance_2d_pipeline: sokol.gfx.Pipeline,
|
|
||||||
instance_2d_buffers: coral.stack.Sequential(sokol.gfx.Buffer),
|
|
||||||
|
|
||||||
const Instance2D = extern struct {
|
|
||||||
transform: gfx.Transform2D,
|
|
||||||
tint: @Vector(4, u8) = @splat(std.math.maxInt(u8)),
|
|
||||||
depth: f32 = 0,
|
|
||||||
texture_offset: gfx.Point2D = @splat(0),
|
|
||||||
texture_size: gfx.Point2D = @splat(1),
|
|
||||||
|
|
||||||
const buffer_indices = .{
|
|
||||||
.mesh = 0,
|
|
||||||
.instance = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
const instances_per_buffer = 512;
|
|
||||||
|
|
||||||
const shader = @import("./shaders/instance_2d.glsl.zig");
|
|
||||||
};
|
|
||||||
|
|
||||||
const Resource = union (enum) {
|
|
||||||
empty,
|
|
||||||
mesh_2d: Mesh2D,
|
|
||||||
texture: Texture,
|
|
||||||
|
|
||||||
const Mesh2D = struct {
|
|
||||||
index_count: u32,
|
|
||||||
vertex_buffer: sokol.gfx.Buffer,
|
|
||||||
index_buffer: sokol.gfx.Buffer,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Texture = struct {
|
|
||||||
image: sokol.gfx.Image,
|
|
||||||
sampler: sokol.gfx.Sampler,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
fn deinit(self: *Render) void {
|
|
||||||
for (self.instance_2d_buffers.values) |buffer| {
|
|
||||||
sokol.gfx.destroyBuffer(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.instance_2d_buffers.deinit();
|
|
||||||
sokol.gfx.destroyPipeline(self.instance_2d_pipeline);
|
|
||||||
self.resources.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init() Render {
|
|
||||||
sokol.gfx.setup(.{
|
|
||||||
.environment = .{
|
|
||||||
.defaults = .{
|
|
||||||
.color_format = .RGBA8,
|
|
||||||
.depth_format = .DEPTH_STENCIL,
|
|
||||||
.sample_count = 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
.logger = .{
|
|
||||||
.func = sokol.log.func,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.instance_2d_pipeline = sokol.gfx.makePipeline(.{
|
|
||||||
.label = "2D drawing pipeline",
|
|
||||||
|
|
||||||
.layout = .{
|
|
||||||
.attrs = get: {
|
|
||||||
var attrs = [_]sokol.gfx.VertexAttrState{.{}} ** 16;
|
|
||||||
|
|
||||||
attrs[Instance2D.shader.ATTR_vs_mesh_xy] = .{
|
|
||||||
.format = .FLOAT2,
|
|
||||||
.buffer_index = Instance2D.buffer_indices.mesh,
|
|
||||||
};
|
|
||||||
|
|
||||||
attrs[Instance2D.shader.ATTR_vs_mesh_uv] = .{
|
|
||||||
.format = .FLOAT2,
|
|
||||||
.buffer_index = Instance2D.buffer_indices.mesh,
|
|
||||||
};
|
|
||||||
|
|
||||||
attrs[Instance2D.shader.ATTR_vs_instance_xbasis] = .{
|
|
||||||
.format = .FLOAT2,
|
|
||||||
.buffer_index = Instance2D.buffer_indices.instance,
|
|
||||||
};
|
|
||||||
|
|
||||||
attrs[Instance2D.shader.ATTR_vs_instance_ybasis] = .{
|
|
||||||
.format = .FLOAT2,
|
|
||||||
.buffer_index = Instance2D.buffer_indices.instance,
|
|
||||||
};
|
|
||||||
|
|
||||||
attrs[Instance2D.shader.ATTR_vs_instance_origin] = .{
|
|
||||||
.format = .FLOAT2,
|
|
||||||
.buffer_index = Instance2D.buffer_indices.instance,
|
|
||||||
};
|
|
||||||
|
|
||||||
attrs[Instance2D.shader.ATTR_vs_instance_tint] = .{
|
|
||||||
.format = .UBYTE4N,
|
|
||||||
.buffer_index = Instance2D.buffer_indices.instance,
|
|
||||||
};
|
|
||||||
|
|
||||||
attrs[Instance2D.shader.ATTR_vs_instance_depth] = .{
|
|
||||||
.format = .FLOAT,
|
|
||||||
.buffer_index = Instance2D.buffer_indices.instance,
|
|
||||||
};
|
|
||||||
|
|
||||||
attrs[Instance2D.shader.ATTR_vs_instance_rect] = .{
|
|
||||||
.format = .FLOAT4,
|
|
||||||
.buffer_index = Instance2D.buffer_indices.instance,
|
|
||||||
};
|
|
||||||
|
|
||||||
break: get attrs;
|
|
||||||
},
|
|
||||||
|
|
||||||
.buffers = get: {
|
|
||||||
var buffers = [_]sokol.gfx.VertexBufferLayoutState{.{}} ** 8;
|
|
||||||
|
|
||||||
buffers[Instance2D.buffer_indices.instance].step_func = .PER_INSTANCE;
|
|
||||||
|
|
||||||
break: get buffers;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
.shader = sokol.gfx.makeShader(Instance2D.shader.draw2dShaderDesc(sokol.gfx.queryBackend())),
|
|
||||||
.index_type = .UINT16,
|
|
||||||
}),
|
|
||||||
|
|
||||||
.instance_2d_buffers = .{.allocator = coral.heap.allocator},
|
|
||||||
.resources = .{.allocator = coral.heap.allocator},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn insert_resource(self: *Render, handle: gfx.Handle, resource: Resource) !void {
|
|
||||||
const handle_index = handle.index() orelse {
|
|
||||||
return error.InvalidHandle;
|
|
||||||
};
|
|
||||||
|
|
||||||
const resource_count = self.resources.len();
|
|
||||||
|
|
||||||
if (handle_index < resource_count) {
|
|
||||||
const empty_resource = &self.resources.values[handle_index];
|
|
||||||
|
|
||||||
if (empty_resource.* != .empty) {
|
|
||||||
return error.InvalidHandle;
|
|
||||||
}
|
|
||||||
|
|
||||||
empty_resource.* = resource;
|
|
||||||
} else {
|
|
||||||
if (handle_index != resource_count) {
|
|
||||||
return error.InvalidIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
try self.resources.push(resource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush_instance_2ds(self: *Render, frame: *Frame) void {
|
|
||||||
const unflushed_count = frame.unflushed_instance_2d_count();
|
|
||||||
|
|
||||||
if (unflushed_count == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sokol.gfx.applyPipeline(self.instance_2d_pipeline);
|
|
||||||
|
|
||||||
sokol.gfx.applyUniforms(.VS, Instance2D.shader.SLOT_Screen, sokol.gfx.asRange(&Instance2D.shader.Screen{
|
|
||||||
.screen_size = .{@floatFromInt(frame.width), @floatFromInt(frame.height)},
|
|
||||||
}));
|
|
||||||
|
|
||||||
const mesh_2d = self.resources.values[frame.mesh_2d.index().?].mesh_2d;
|
|
||||||
const texture = self.resources.values[frame.texture.index().?].texture;
|
|
||||||
|
|
||||||
var bindings = sokol.gfx.Bindings{
|
|
||||||
.vertex_buffers = get: {
|
|
||||||
var buffers = [_]sokol.gfx.Buffer{.{}} ** 8;
|
|
||||||
|
|
||||||
buffers[Instance2D.buffer_indices.mesh] = mesh_2d.vertex_buffer;
|
|
||||||
|
|
||||||
break: get buffers;
|
|
||||||
},
|
|
||||||
|
|
||||||
.index_buffer = mesh_2d.index_buffer,
|
|
||||||
|
|
||||||
.fs = .{
|
|
||||||
.images = get: {
|
|
||||||
var images = [_]sokol.gfx.Image{.{}} ** 12;
|
|
||||||
|
|
||||||
images[0] = texture.image;
|
|
||||||
|
|
||||||
break: get images;
|
|
||||||
},
|
|
||||||
|
|
||||||
.samplers = get: {
|
|
||||||
var samplers = [_]sokol.gfx.Sampler{.{}} ** 8;
|
|
||||||
|
|
||||||
samplers[0] = texture.sampler;
|
|
||||||
|
|
||||||
break: get samplers;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
while (frame.flushed_instance_2d_count < frame.pushed_instance_2d_count) {
|
|
||||||
const buffer_index = frame.flushed_instance_2d_count / Instance2D.instances_per_buffer;
|
|
||||||
const buffer_offset = frame.flushed_instance_2d_count % Instance2D.instances_per_buffer;
|
|
||||||
const instances_to_flush = @min(Instance2D.instances_per_buffer - buffer_offset, unflushed_count);
|
|
||||||
|
|
||||||
bindings.vertex_buffers[Instance2D.buffer_indices.instance] = self.instance_2d_buffers.values[buffer_index];
|
|
||||||
bindings.vertex_buffer_offsets[Instance2D.buffer_indices.instance] = @intCast(buffer_offset);
|
|
||||||
|
|
||||||
sokol.gfx.applyBindings(bindings);
|
|
||||||
sokol.gfx.draw(0, mesh_2d.index_count, @intCast(instances_to_flush));
|
|
||||||
|
|
||||||
frame.flushed_instance_2d_count += instances_to_flush;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_instance_2d(self: *Render, frame: *Frame, command: Queue.DrawCommand.Instance) std.mem.Allocator.Error!void {
|
|
||||||
if (command.mesh_2d != frame.mesh_2d or command.texture != frame.texture) {
|
|
||||||
self.flush_instance_2ds(frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
frame.mesh_2d = command.mesh_2d;
|
|
||||||
frame.texture = command.texture;
|
|
||||||
|
|
||||||
const has_filled_buffer = (frame.pushed_instance_2d_count % Instance2D.instances_per_buffer) == 0;
|
|
||||||
const pushed_buffer_count = frame.pushed_instance_2d_count / Instance2D.instances_per_buffer;
|
|
||||||
|
|
||||||
if (has_filled_buffer and pushed_buffer_count == self.instance_2d_buffers.len()) {
|
|
||||||
const instance_buffer = sokol.gfx.makeBuffer(.{
|
|
||||||
.size = @sizeOf(Instance2D) * Instance2D.instances_per_buffer,
|
|
||||||
.usage = .STREAM,
|
|
||||||
.label = "2D drawing instance buffer",
|
|
||||||
});
|
|
||||||
|
|
||||||
errdefer sokol.gfx.destroyBuffer(instance_buffer);
|
|
||||||
|
|
||||||
try self.instance_2d_buffers.push(instance_buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = sokol.gfx.appendBuffer(self.instance_2d_buffers.get().?, sokol.gfx.asRange(&Instance2D{
|
|
||||||
.transform = command.transform,
|
|
||||||
}));
|
|
||||||
|
|
||||||
frame.pushed_instance_2d_count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_resource(self: *Render, handle: gfx.Handle) ?Resource {
|
|
||||||
if (handle.index()) |handle_index| {
|
|
||||||
const resource = self.resources.values[handle_index];
|
|
||||||
|
|
||||||
if (resource != .empty) {
|
|
||||||
self.resources.values[handle_index] = .empty;
|
|
||||||
|
|
||||||
return resource;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
const State = struct {
|
|
||||||
finished: std.Thread.Semaphore = .{},
|
|
||||||
is_running: AtomicBool = AtomicBool.init(true),
|
|
||||||
ready: std.Thread.Semaphore = .{},
|
|
||||||
clear_color: gfx.Color = gfx.colors.black,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
|
||||||
self.state.is_running.store(false, .monotonic);
|
|
||||||
self.state.ready.post();
|
|
||||||
self.thread.join();
|
|
||||||
coral.heap.allocator.destroy(self.state);
|
|
||||||
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(window: *ext.SDL_Window) (std.mem.Allocator.Error || std.Thread.SpawnError)!Self {
|
|
||||||
const state = try coral.heap.allocator.create(State);
|
|
||||||
|
|
||||||
errdefer coral.heap.allocator.destroy(state);
|
|
||||||
|
|
||||||
state.* = .{};
|
|
||||||
|
|
||||||
const thread = try std.Thread.spawn(.{}, run, .{window, state});
|
|
||||||
|
|
||||||
thread.setName("Ona Graphics") catch {
|
|
||||||
std.log.warn("failed to name the graphics thread", .{});
|
|
||||||
};
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.thread = thread,
|
|
||||||
.state = state,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_close_command(command: Queue.CloseCommand, rendering: *Render) !void {
|
|
||||||
const resource = &rendering.resources.values[command.handle.index().?];
|
|
||||||
|
|
||||||
switch (resource.*) {
|
|
||||||
.empty => {}, // TODO: Handle this.
|
|
||||||
|
|
||||||
.mesh_2d => |mesh_2d| {
|
|
||||||
sokol.gfx.destroyBuffer(mesh_2d.vertex_buffer);
|
|
||||||
sokol.gfx.destroyBuffer(mesh_2d.index_buffer);
|
|
||||||
},
|
|
||||||
|
|
||||||
.texture => |texture| {
|
|
||||||
sokol.gfx.destroyImage(texture.image);
|
|
||||||
sokol.gfx.destroySampler(texture.sampler);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
resource.* = .empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_draw_command(command: Queue.DrawCommand, render: *Render, frame: *Frame) !void {
|
|
||||||
switch (command) {
|
|
||||||
.instance_2d => |instance_2d| {
|
|
||||||
try render.push_instance_2d(frame, instance_2d);
|
|
||||||
},
|
|
||||||
|
|
||||||
.post_process => |post_process| {
|
|
||||||
render.flush_instance_2ds(frame);
|
|
||||||
// sokol.gfx.applyPipeline(self.post_process_pipeline);
|
|
||||||
|
|
||||||
_ = post_process;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
render.flush_instance_2ds(frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_open_command(command: Queue.OpenCommand, render: *Render) !void {
|
|
||||||
switch (command.resource) {
|
|
||||||
.texture => |texture| {
|
|
||||||
const stride = texture.width * texture.format.byte_size();
|
|
||||||
|
|
||||||
const image = sokol.gfx.makeImage(.{
|
|
||||||
.width = texture.width,
|
|
||||||
.height = @intCast(texture.data.len / stride),
|
|
||||||
|
|
||||||
.data = .{
|
|
||||||
.subimage = get: {
|
|
||||||
var subimage = [_][16]sokol.gfx.Range{.{.{}} ** 16} ** 6;
|
|
||||||
|
|
||||||
subimage[0][0] = sokol.gfx.asRange(texture.data);
|
|
||||||
|
|
||||||
break: get subimage;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
errdefer sokol.gfx.destroyImage(image);
|
|
||||||
|
|
||||||
const sampler = sokol.gfx.makeSampler(.{});
|
|
||||||
|
|
||||||
errdefer sokol.gfx.destroySampler(sampler);
|
|
||||||
|
|
||||||
try render.insert_resource(command.handle, .{
|
|
||||||
.texture = .{
|
|
||||||
.sampler = sampler,
|
|
||||||
.image = image,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
.mesh_2d => |mesh_2d| {
|
|
||||||
const index_buffer = sokol.gfx.makeBuffer(.{
|
|
||||||
.data = sokol.gfx.asRange(mesh_2d.indices),
|
|
||||||
.type = .INDEXBUFFER,
|
|
||||||
});
|
|
||||||
|
|
||||||
const vertex_buffer = sokol.gfx.makeBuffer(.{
|
|
||||||
.data = sokol.gfx.asRange(mesh_2d.vertices),
|
|
||||||
.type = .VERTEXBUFFER,
|
|
||||||
});
|
|
||||||
|
|
||||||
errdefer {
|
|
||||||
sokol.gfx.destroyBuffer(index_buffer);
|
|
||||||
sokol.gfx.destroyBuffer(vertex_buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mesh_2d.indices.len > std.math.maxInt(u32)) {
|
|
||||||
return error.OutOfMemory;
|
|
||||||
}
|
|
||||||
|
|
||||||
try render.insert_resource(command.handle, .{
|
|
||||||
.mesh_2d = .{
|
|
||||||
.index_buffer = index_buffer,
|
|
||||||
.vertex_buffer = vertex_buffer,
|
|
||||||
.index_count = @intCast(mesh_2d.indices.len),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(window: *ext.SDL_Window, state: *State) !void {
|
|
||||||
const context = configure_and_create: {
|
|
||||||
var result = @as(c_int, 0);
|
|
||||||
|
|
||||||
result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_FLAGS, ext.SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
|
|
||||||
result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_PROFILE_MASK, ext.SDL_GL_CONTEXT_PROFILE_CORE);
|
|
||||||
result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
|
||||||
result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_MINOR_VERSION, 3);
|
|
||||||
result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_DOUBLEBUFFER, 1);
|
|
||||||
|
|
||||||
if (result != 0) {
|
|
||||||
return error.Unsupported;
|
|
||||||
}
|
|
||||||
|
|
||||||
break: configure_and_create ext.SDL_GL_CreateContext(window);
|
|
||||||
};
|
|
||||||
|
|
||||||
sokol.gfx.setup(.{
|
|
||||||
.environment = .{
|
|
||||||
.defaults = .{
|
|
||||||
.color_format = .RGBA8,
|
|
||||||
.depth_format = .DEPTH_STENCIL,
|
|
||||||
.sample_count = 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
.logger = .{
|
|
||||||
.func = sokol.log.func,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
defer {
|
|
||||||
sokol.gfx.shutdown();
|
|
||||||
ext.SDL_GL_DeleteContext(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
var render = Render.init();
|
|
||||||
|
|
||||||
defer render.deinit();
|
|
||||||
|
|
||||||
state.finished.post();
|
|
||||||
|
|
||||||
while (state.is_running.load(.monotonic)) {
|
|
||||||
state.ready.wait();
|
|
||||||
|
|
||||||
defer state.finished.post();
|
|
||||||
|
|
||||||
var frame = init_frame: {
|
|
||||||
var width, var height = [_]c_int{0, 0};
|
|
||||||
|
|
||||||
ext.SDL_GL_GetDrawableSize(window, &width, &height);
|
|
||||||
std.debug.assert(width > 0 and height > 0);
|
|
||||||
|
|
||||||
break: init_frame Frame{
|
|
||||||
.width = @intCast(width),
|
|
||||||
.height = @intCast(height),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
sokol.gfx.beginPass(.{
|
|
||||||
.swapchain = .{
|
|
||||||
.width = frame.width,
|
|
||||||
.height = frame.height,
|
|
||||||
.sample_count = 1,
|
|
||||||
.color_format = .RGBA8,
|
|
||||||
.depth_format = .DEPTH_STENCIL,
|
|
||||||
.gl = .{.framebuffer = 0},
|
|
||||||
},
|
|
||||||
|
|
||||||
.action = .{
|
|
||||||
.colors = get: {
|
|
||||||
var actions = [_]sokol.gfx.ColorAttachmentAction{.{}} ** 4;
|
|
||||||
|
|
||||||
actions[0] = .{
|
|
||||||
.load_action = .CLEAR,
|
|
||||||
.clear_value = @as(sokol.gfx.Color, @bitCast(state.clear_color)),
|
|
||||||
};
|
|
||||||
|
|
||||||
break: get actions;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
try Queue.visit_open_commands(process_open_command, .{&render});
|
|
||||||
try Queue.visit_draw_commands(process_draw_command, .{&render, &frame});
|
|
||||||
try Queue.visit_close_commands(process_close_command, .{&render});
|
|
||||||
|
|
||||||
sokol.gfx.endPass();
|
|
||||||
sokol.gfx.commit();
|
|
||||||
ext.SDL_GL_SwapWindow(window);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn submit(self: *Self) void {
|
|
||||||
self.state.finished.wait();
|
|
||||||
|
|
||||||
self.state.clear_color = self.clear_color;
|
|
||||||
|
|
||||||
Queue.swap();
|
|
||||||
self.state.ready.post();
|
|
||||||
}
|
|
|
@ -1,309 +0,0 @@
|
||||||
const coral = @import("coral");
|
|
||||||
|
|
||||||
const gfx = @import("../gfx.zig");
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
buffers: [2]Buffer,
|
|
||||||
is_swapped: bool = false,
|
|
||||||
ref_count: AtomicCount = AtomicCount.init(1),
|
|
||||||
has_next: ?*Self = null,
|
|
||||||
has_prev: ?*Self = null,
|
|
||||||
|
|
||||||
const AtomicCount = std.atomic.Value(usize);
|
|
||||||
|
|
||||||
pub const Buffer = struct {
|
|
||||||
arena: std.heap.ArenaAllocator,
|
|
||||||
closed_handles: coral.stack.Sequential(usize),
|
|
||||||
open_commands: coral.stack.Sequential(OpenCommand),
|
|
||||||
draw_commands: coral.stack.Sequential(DrawCommand),
|
|
||||||
close_commands: coral.stack.Sequential(CloseCommand),
|
|
||||||
|
|
||||||
pub fn clear(self: *Buffer) void {
|
|
||||||
self.close_commands.clear();
|
|
||||||
self.draw_commands.clear();
|
|
||||||
self.open_commands.clear();
|
|
||||||
|
|
||||||
if (!self.arena.reset(.retain_capacity)) {
|
|
||||||
std.log.warn("failed to reset the buffer of a gfx queue with retained capacity", .{});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deinit(self: *Buffer) void {
|
|
||||||
self.arena.deinit();
|
|
||||||
self.closed_handles.deinit();
|
|
||||||
self.open_commands.deinit();
|
|
||||||
self.draw_commands.deinit();
|
|
||||||
self.close_commands.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init(allocator: std.mem.Allocator) Buffer {
|
|
||||||
return .{
|
|
||||||
.arena = std.heap.ArenaAllocator.init(allocator),
|
|
||||||
.closed_handles = .{.allocator = allocator},
|
|
||||||
.open_commands = .{.allocator = allocator},
|
|
||||||
.draw_commands = .{.allocator = allocator},
|
|
||||||
.close_commands = .{.allocator = allocator},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw(self: *Buffer, command: DrawCommand) std.mem.Allocator.Error!void {
|
|
||||||
try self.draw_commands.push(switch (command) {
|
|
||||||
.instance_2d => |instance_2d| .{.instance_2d = instance_2d},
|
|
||||||
.post_process => |post_process| .{.post_process = post_process},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn open(self: *Buffer, command: OpenCommand) std.mem.Allocator.Error!gfx.Handle {
|
|
||||||
const reserved_handle = @as(gfx.Handle, switch (command.handle) {
|
|
||||||
.none => @enumFromInt(reserve_handle: {
|
|
||||||
if (self.closed_handles.get()) |handle| {
|
|
||||||
std.debug.assert(self.closed_handles.pop());
|
|
||||||
|
|
||||||
break: reserve_handle handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
break: reserve_handle next_handle.fetchAdd(1, .monotonic);
|
|
||||||
}),
|
|
||||||
|
|
||||||
_ => |handle| handle,
|
|
||||||
});
|
|
||||||
|
|
||||||
std.debug.assert(reserved_handle != .none);
|
|
||||||
|
|
||||||
const arena_allocator = self.arena.allocator();
|
|
||||||
|
|
||||||
try self.open_commands.push(.{
|
|
||||||
.resource = switch (command.resource) {
|
|
||||||
.texture => |texture| .{
|
|
||||||
.texture = .{
|
|
||||||
.data = try arena_allocator.dupe(coral.io.Byte, texture.data),
|
|
||||||
.width = texture.width,
|
|
||||||
.format = texture.format,
|
|
||||||
.access = texture.access,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
.mesh_2d => |mesh_2d| .{
|
|
||||||
.mesh_2d = .{
|
|
||||||
.indices = try arena_allocator.dupe(u16, mesh_2d.indices),
|
|
||||||
.vertices = try arena_allocator.dupe(OpenCommand.Mesh2D.Vertex, mesh_2d.vertices),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
.label = if (command.label) |label| try arena_allocator.dupe(coral.io.Byte, label) else null,
|
|
||||||
.handle = reserved_handle,
|
|
||||||
});
|
|
||||||
|
|
||||||
return reserved_handle;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const CloseCommand = struct {
|
|
||||||
handle: gfx.Handle,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const DrawCommand = union (enum) {
|
|
||||||
instance_2d: Instance,
|
|
||||||
post_process: PostProcess,
|
|
||||||
|
|
||||||
pub const Instance = struct {
|
|
||||||
texture: gfx.Handle,
|
|
||||||
mesh_2d: gfx.Handle,
|
|
||||||
transform: gfx.Transform2D,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const PostProcess = struct {
|
|
||||||
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const OpenCommand = struct {
|
|
||||||
handle: gfx.Handle = .none,
|
|
||||||
label: ?[]const u8 = null,
|
|
||||||
|
|
||||||
resource: union (enum) {
|
|
||||||
texture: Texture,
|
|
||||||
mesh_2d: Mesh2D,
|
|
||||||
},
|
|
||||||
|
|
||||||
pub const Mesh2D = struct {
|
|
||||||
vertices: []const Vertex,
|
|
||||||
indices: []const u16,
|
|
||||||
|
|
||||||
pub const Vertex = struct {
|
|
||||||
xy: gfx.Point2D,
|
|
||||||
uv: gfx.Point2D,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Texture = struct {
|
|
||||||
data: []const coral.io.Byte,
|
|
||||||
width: u16,
|
|
||||||
format: Format,
|
|
||||||
access: Access,
|
|
||||||
|
|
||||||
pub const Access = enum {
|
|
||||||
static,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Format = enum {
|
|
||||||
rgba8888,
|
|
||||||
bgra8888,
|
|
||||||
argb8888,
|
|
||||||
rgb888,
|
|
||||||
bgr888,
|
|
||||||
|
|
||||||
pub fn byte_size(self: Format) usize {
|
|
||||||
return switch (self) {
|
|
||||||
.rgba8888, .bgra8888, .argb8888 => 4,
|
|
||||||
.rgb888, .bgr888 => 3,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn acquire(self: *Self) void {
|
|
||||||
self.ref_count.fetchAdd(1, .monotonic);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create() std.mem.Allocator.Error!*Self {
|
|
||||||
const queue = try coral.heap.allocator.create(Self);
|
|
||||||
|
|
||||||
errdefer coral.heap.allocator.destroy(queue);
|
|
||||||
|
|
||||||
queue.* = .{
|
|
||||||
.buffers = .{Buffer.init(coral.heap.allocator), Buffer.init(coral.heap.allocator)},
|
|
||||||
};
|
|
||||||
|
|
||||||
mutex.lock();
|
|
||||||
|
|
||||||
defer mutex.unlock();
|
|
||||||
|
|
||||||
if (has_tail) |tail| {
|
|
||||||
tail.has_next = queue;
|
|
||||||
queue.has_prev = tail;
|
|
||||||
} else {
|
|
||||||
std.debug.assert(has_head == null);
|
|
||||||
|
|
||||||
has_head = queue;
|
|
||||||
}
|
|
||||||
|
|
||||||
has_tail = queue;
|
|
||||||
|
|
||||||
return queue;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pending(self: *Self) *Buffer {
|
|
||||||
return &self.buffers[@intFromBool(self.is_swapped)];
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn release(self: *Self) void {
|
|
||||||
if (self.ref_count.fetchSub(1, .monotonic) == 1) {
|
|
||||||
mutex.lock();
|
|
||||||
|
|
||||||
defer mutex.unlock();
|
|
||||||
|
|
||||||
if (self.has_prev) |prev| {
|
|
||||||
prev.has_next = self.has_next;
|
|
||||||
} else {
|
|
||||||
has_head = self.has_next;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.has_next) |next| {
|
|
||||||
next.has_prev = self.has_prev;
|
|
||||||
} else {
|
|
||||||
has_tail = self.has_prev;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (&self.buffers) |*buffer| {
|
|
||||||
buffer.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
coral.heap.allocator.destroy(self);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn submitted(self: *Self) *Buffer {
|
|
||||||
return &self.buffers[@intFromBool(!self.is_swapped)];
|
|
||||||
}
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
var has_head = @as(?*Self, null);
|
|
||||||
|
|
||||||
var has_tail = @as(?*Self, null);
|
|
||||||
|
|
||||||
var mutex = std.Thread.Mutex{};
|
|
||||||
|
|
||||||
var next_handle = AtomicCount.init(1);
|
|
||||||
|
|
||||||
pub fn swap() void {
|
|
||||||
mutex.lock();
|
|
||||||
|
|
||||||
defer mutex.unlock();
|
|
||||||
|
|
||||||
var has_node = has_head;
|
|
||||||
|
|
||||||
while (has_node) |node| : (has_node = node.has_next) {
|
|
||||||
node.is_swapped = !node.is_swapped;
|
|
||||||
|
|
||||||
node.pending().clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn visit_close_commands(visit: anytype, args: anytype) !void {
|
|
||||||
mutex.lock();
|
|
||||||
|
|
||||||
defer mutex.unlock();
|
|
||||||
|
|
||||||
var has_node = has_head;
|
|
||||||
var iterations = @as(usize, 0);
|
|
||||||
|
|
||||||
while (has_node) |node| : ({
|
|
||||||
has_node = node.has_next;
|
|
||||||
iterations += 1;
|
|
||||||
}) {
|
|
||||||
for (node.submitted().close_commands.values) |command| {
|
|
||||||
try @call(.auto, visit, .{command} ++ args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn visit_draw_commands(visit: anytype, args: anytype) !void {
|
|
||||||
mutex.lock();
|
|
||||||
|
|
||||||
defer mutex.unlock();
|
|
||||||
|
|
||||||
var has_node = has_head;
|
|
||||||
var iterations = @as(usize, 0);
|
|
||||||
|
|
||||||
while (has_node) |node| : ({
|
|
||||||
has_node = node.has_next;
|
|
||||||
iterations += 1;
|
|
||||||
}) {
|
|
||||||
for (node.submitted().draw_commands.values) |command| {
|
|
||||||
try @call(.auto, visit, .{command} ++ args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn visit_open_commands(visit: anytype, args: anytype) !void {
|
|
||||||
mutex.lock();
|
|
||||||
|
|
||||||
defer mutex.unlock();
|
|
||||||
|
|
||||||
var has_node = has_head;
|
|
||||||
var iterations = @as(usize, 0);
|
|
||||||
|
|
||||||
while (has_node) |node| : ({
|
|
||||||
has_node = node.has_next;
|
|
||||||
iterations += 1;
|
|
||||||
}) {
|
|
||||||
for (node.submitted().open_commands.values) |command| {
|
|
||||||
try @call(.auto, visit, .{command} ++ args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
const coral = @import("coral");
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn Chain(comptime Command: type, comptime clone_command: ?Clone(Command)) type {
|
||||||
|
return struct {
|
||||||
|
swap_lists: [2]CommandList,
|
||||||
|
swap_state: u1 = 0,
|
||||||
|
|
||||||
|
const CommandList = List(Command, clone_command);
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
for (&self.swap_lists) |*list| {
|
||||||
|
list.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator) Self {
|
||||||
|
return .{
|
||||||
|
.swap_lists = .{CommandList.init(allocator), CommandList.init(allocator)},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pending(self: *Self) *CommandList {
|
||||||
|
return &self.swap_lists[self.swap_state];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn submitted(self: *Self) *CommandList {
|
||||||
|
return &self.swap_lists[self.swap_state ^ 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn swap(self: *Self) void {
|
||||||
|
self.swap_state ^= 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Clone(comptime Command: type) type {
|
||||||
|
return fn (Command, *std.heap.ArenaAllocator) std.mem.Allocator.Error!Command;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn List(comptime Command: type, comptime clone_command: ?Clone(Command)) type {
|
||||||
|
return struct {
|
||||||
|
arena: std.heap.ArenaAllocator,
|
||||||
|
stack: coral.stack.Sequential(Command),
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn append(self: *Self, command: Command) std.mem.Allocator.Error!void {
|
||||||
|
return self.stack.push_grow(if (clone_command) |clone| try clone(command, &self.arena) else command);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(self: *Self) void {
|
||||||
|
self.stack.clear();
|
||||||
|
|
||||||
|
if (!self.arena.reset(.retain_capacity)) {
|
||||||
|
std.log.warn("failed to reset the buffer of a gfx queue with retained capacity", .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
self.arena.deinit();
|
||||||
|
self.stack.deinit();
|
||||||
|
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator) Self {
|
||||||
|
return .{
|
||||||
|
.arena = std.heap.ArenaAllocator.init(allocator),
|
||||||
|
.stack = .{.allocator = allocator},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,691 @@
|
||||||
|
const commands = @import("./commands.zig");
|
||||||
|
|
||||||
|
const coral = @import("coral");
|
||||||
|
|
||||||
|
const ext = @import("../ext.zig");
|
||||||
|
|
||||||
|
const gfx = @import("../gfx.zig");
|
||||||
|
|
||||||
|
const sokol = @import("sokol");
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const Context = struct {
|
||||||
|
window: *ext.SDL_Window,
|
||||||
|
thread: std.Thread,
|
||||||
|
loop: *Loop,
|
||||||
|
|
||||||
|
pub const Submission = struct {
|
||||||
|
width: u16,
|
||||||
|
height: u16,
|
||||||
|
clear_color: gfx.Color = gfx.colors.black,
|
||||||
|
renders: []RenderChain,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn close(self: *Context, handle: gfx.Handle) void {
|
||||||
|
const handle_index = handle.index() orelse {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
const close_commands = self.loop.closes.pending();
|
||||||
|
|
||||||
|
std.debug.assert(close_commands.stack.cap > close_commands.stack.len());
|
||||||
|
close_commands.append(.{.index = handle_index}) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Context) void {
|
||||||
|
self.loop.is_running.store(false, .monotonic);
|
||||||
|
self.loop.ready.post();
|
||||||
|
self.thread.join();
|
||||||
|
self.loop.deinit();
|
||||||
|
coral.heap.allocator.destroy(self.loop);
|
||||||
|
ext.SDL_DestroyWindow(self.window);
|
||||||
|
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init() !Context {
|
||||||
|
const window = create: {
|
||||||
|
const position = ext.SDL_WINDOWPOS_CENTERED;
|
||||||
|
const flags = ext.SDL_WINDOW_OPENGL;
|
||||||
|
const width = 640;
|
||||||
|
const height = 480;
|
||||||
|
|
||||||
|
break: create ext.SDL_CreateWindow("Ona", position, position, width, height, flags) orelse {
|
||||||
|
return error.Unsupported;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
errdefer ext.SDL_DestroyWindow(window);
|
||||||
|
|
||||||
|
const loop = try coral.heap.allocator.create(Loop);
|
||||||
|
|
||||||
|
errdefer coral.heap.allocator.destroy(loop);
|
||||||
|
|
||||||
|
loop.* = .{};
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.loop = loop,
|
||||||
|
.window = window,
|
||||||
|
|
||||||
|
.thread = spawn: {
|
||||||
|
const thread = try std.Thread.spawn(.{}, Loop.run, .{loop, window});
|
||||||
|
|
||||||
|
thread.setName("Ona Graphics") catch {
|
||||||
|
std.log.warn("failed to name the graphics thread", .{});
|
||||||
|
};
|
||||||
|
|
||||||
|
break: spawn thread;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open(self: *Context, desc: gfx.Desc) gfx.OpenError!gfx.Handle {
|
||||||
|
const open_commands = self.loop.opens.pending();
|
||||||
|
const index = self.loop.closed_indices.get() orelse open_commands.stack.len();
|
||||||
|
|
||||||
|
try open_commands.append(.{
|
||||||
|
.index = index,
|
||||||
|
.desc = desc,
|
||||||
|
});
|
||||||
|
|
||||||
|
const pending_closes = self.loop.closes.pending();
|
||||||
|
|
||||||
|
if (pending_closes.stack.len() == pending_closes.stack.cap) {
|
||||||
|
try pending_closes.stack.grow(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = self.loop.closed_indices.pop();
|
||||||
|
|
||||||
|
return @enumFromInt(index + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn submit(self: *Context, submission: Submission) void {
|
||||||
|
self.loop.finished.wait();
|
||||||
|
|
||||||
|
defer self.loop.ready.post();
|
||||||
|
|
||||||
|
for (submission.renders) |*render| {
|
||||||
|
render.swap();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.loop.opens.swap();
|
||||||
|
self.loop.closes.swap();
|
||||||
|
|
||||||
|
var last_width, var last_height = [_]c_int{0, 0};
|
||||||
|
|
||||||
|
ext.SDL_GetWindowSize(self.window, &last_width, &last_height);
|
||||||
|
|
||||||
|
if (submission.width != last_width or submission.height != last_height) {
|
||||||
|
ext.SDL_SetWindowSize(self.window, submission.width, submission.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.loop.clear_color = submission.clear_color;
|
||||||
|
self.loop.renders = submission.renders;
|
||||||
|
|
||||||
|
self.loop.ready.post();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Loop = struct {
|
||||||
|
ready: std.Thread.Semaphore = .{},
|
||||||
|
finished: std.Thread.Semaphore = .{},
|
||||||
|
clear_color: gfx.Color = gfx.colors.black,
|
||||||
|
is_running: AtomicBool = AtomicBool.init(true),
|
||||||
|
renders: []RenderChain = &.{},
|
||||||
|
closes: CloseChain = CloseChain.init(coral.heap.allocator),
|
||||||
|
opens: OpenChain = OpenChain.init(coral.heap.allocator),
|
||||||
|
closed_indices: coral.stack.Sequential(usize) = .{.allocator = coral.heap.allocator},
|
||||||
|
|
||||||
|
const AtomicBool = std.atomic.Value(bool);
|
||||||
|
|
||||||
|
const CloseCommand = struct {
|
||||||
|
index: usize,
|
||||||
|
};
|
||||||
|
|
||||||
|
const OpenCommand = struct {
|
||||||
|
index: usize,
|
||||||
|
desc: gfx.Desc,
|
||||||
|
|
||||||
|
fn clone(command: OpenCommand, arena: *std.heap.ArenaAllocator) std.mem.Allocator.Error!OpenCommand {
|
||||||
|
const allocator = arena.allocator();
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.desc = switch (command.desc) {
|
||||||
|
.texture => |texture| .{
|
||||||
|
.texture = .{
|
||||||
|
.data = try allocator.dupe(coral.io.Byte, texture.data),
|
||||||
|
.width = texture.width,
|
||||||
|
.format = texture.format,
|
||||||
|
.access = texture.access,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
.mesh_2d => |mesh_2d| .{
|
||||||
|
.mesh_2d = .{
|
||||||
|
.vertices = try allocator.dupe(gfx.Desc.Mesh2D.Vertex, mesh_2d.vertices),
|
||||||
|
.indices = try allocator.dupe(u16, mesh_2d.indices),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
.index = command.index,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const CloseChain = commands.Chain(CloseCommand, null);
|
||||||
|
|
||||||
|
const OpenChain = commands.Chain(OpenCommand, OpenCommand.clone);
|
||||||
|
|
||||||
|
fn deinit(self: *Loop) void {
|
||||||
|
self.closes.deinit();
|
||||||
|
self.opens.deinit();
|
||||||
|
self.closed_indices.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(self: *Loop, window: *ext.SDL_Window) !void {
|
||||||
|
const context = configure_and_create: {
|
||||||
|
var result = @as(c_int, 0);
|
||||||
|
|
||||||
|
result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_FLAGS, ext.SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
|
||||||
|
result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_PROFILE_MASK, ext.SDL_GL_CONTEXT_PROFILE_CORE);
|
||||||
|
result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||||
|
result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_MINOR_VERSION, 3);
|
||||||
|
result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_DOUBLEBUFFER, 1);
|
||||||
|
|
||||||
|
if (result != 0) {
|
||||||
|
return error.Unsupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
break: configure_and_create ext.SDL_GL_CreateContext(window);
|
||||||
|
};
|
||||||
|
|
||||||
|
sokol.gfx.setup(.{
|
||||||
|
.environment = .{
|
||||||
|
.defaults = .{
|
||||||
|
.color_format = .RGBA8,
|
||||||
|
.depth_format = .DEPTH_STENCIL,
|
||||||
|
.sample_count = 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
.logger = .{
|
||||||
|
.func = sokol.log.func,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
defer {
|
||||||
|
sokol.gfx.shutdown();
|
||||||
|
ext.SDL_GL_DeleteContext(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
var rendering = Rendering.init();
|
||||||
|
|
||||||
|
defer rendering.deinit();
|
||||||
|
|
||||||
|
self.finished.post();
|
||||||
|
|
||||||
|
while (self.is_running.load(.monotonic)) {
|
||||||
|
self.ready.wait();
|
||||||
|
|
||||||
|
defer self.finished.post();
|
||||||
|
|
||||||
|
const open_commands = self.opens.submitted();
|
||||||
|
|
||||||
|
defer open_commands.clear();
|
||||||
|
|
||||||
|
for (open_commands.stack.values) |command| {
|
||||||
|
switch (command.desc) {
|
||||||
|
.texture => |texture| {
|
||||||
|
const stride = texture.width * texture.format.byte_size();
|
||||||
|
|
||||||
|
const image = sokol.gfx.makeImage(.{
|
||||||
|
.width = texture.width,
|
||||||
|
.height = @intCast(texture.data.len / stride),
|
||||||
|
|
||||||
|
.data = .{
|
||||||
|
.subimage = get: {
|
||||||
|
var subimage = [_][16]sokol.gfx.Range{.{.{}} ** 16} ** 6;
|
||||||
|
|
||||||
|
subimage[0][0] = sokol.gfx.asRange(texture.data);
|
||||||
|
|
||||||
|
break: get subimage;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
.pixel_format = switch (texture.format) {
|
||||||
|
.rgba8 => .RGBA8,
|
||||||
|
.bgra8 => .BGRA8,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
errdefer sokol.gfx.destroyImage(image);
|
||||||
|
|
||||||
|
const sampler = sokol.gfx.makeSampler(.{});
|
||||||
|
|
||||||
|
errdefer sokol.gfx.destroySampler(sampler);
|
||||||
|
|
||||||
|
try rendering.insert_object(command.index, .{
|
||||||
|
.texture = .{
|
||||||
|
.sampler = sampler,
|
||||||
|
.image = image,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
.mesh_2d => |mesh_2d| {
|
||||||
|
const index_buffer = sokol.gfx.makeBuffer(.{
|
||||||
|
.data = sokol.gfx.asRange(mesh_2d.indices),
|
||||||
|
.type = .INDEXBUFFER,
|
||||||
|
});
|
||||||
|
|
||||||
|
const vertex_buffer = sokol.gfx.makeBuffer(.{
|
||||||
|
.data = sokol.gfx.asRange(mesh_2d.vertices),
|
||||||
|
.type = .VERTEXBUFFER,
|
||||||
|
});
|
||||||
|
|
||||||
|
errdefer {
|
||||||
|
sokol.gfx.destroyBuffer(index_buffer);
|
||||||
|
sokol.gfx.destroyBuffer(vertex_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mesh_2d.indices.len > std.math.maxInt(u32)) {
|
||||||
|
return error.OutOfMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
try rendering.insert_object(command.index, .{
|
||||||
|
.mesh_2d = .{
|
||||||
|
.index_buffer = index_buffer,
|
||||||
|
.vertex_buffer = vertex_buffer,
|
||||||
|
.index_count = @intCast(mesh_2d.indices.len),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var frame = init_frame: {
|
||||||
|
var width, var height = [_]c_int{0, 0};
|
||||||
|
|
||||||
|
ext.SDL_GL_GetDrawableSize(window, &width, &height);
|
||||||
|
std.debug.assert(width > 0 and height > 0);
|
||||||
|
|
||||||
|
break: init_frame Rendering.Frame{
|
||||||
|
.width = @intCast(width),
|
||||||
|
.height = @intCast(height),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
sokol.gfx.beginPass(.{
|
||||||
|
.swapchain = .{
|
||||||
|
.width = frame.width,
|
||||||
|
.height = frame.height,
|
||||||
|
.sample_count = 1,
|
||||||
|
.color_format = .RGBA8,
|
||||||
|
.depth_format = .DEPTH_STENCIL,
|
||||||
|
.gl = .{.framebuffer = 0},
|
||||||
|
},
|
||||||
|
|
||||||
|
.action = .{
|
||||||
|
.colors = get: {
|
||||||
|
var actions = [_]sokol.gfx.ColorAttachmentAction{.{}} ** 4;
|
||||||
|
|
||||||
|
actions[0] = .{
|
||||||
|
.load_action = .CLEAR,
|
||||||
|
.clear_value = @as(sokol.gfx.Color, @bitCast(self.clear_color)),
|
||||||
|
};
|
||||||
|
|
||||||
|
break: get actions;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
for (self.renders) |*render| {
|
||||||
|
const render_commands = render.submitted();
|
||||||
|
|
||||||
|
defer render_commands.clear();
|
||||||
|
|
||||||
|
for (render_commands.stack.values) |command| {
|
||||||
|
switch (command) {
|
||||||
|
.instance_2d => |instance_2d| {
|
||||||
|
try rendering.push_instance_2d(&frame, instance_2d);
|
||||||
|
},
|
||||||
|
|
||||||
|
.post_process => |post_process| {
|
||||||
|
rendering.flush_instance_2ds(&frame);
|
||||||
|
// sokol.gfx.applyPipeline(self.post_process_pipeline);
|
||||||
|
|
||||||
|
_ = post_process;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rendering.flush_instance_2ds(&frame);
|
||||||
|
sokol.gfx.endPass();
|
||||||
|
sokol.gfx.commit();
|
||||||
|
ext.SDL_GL_SwapWindow(window);
|
||||||
|
|
||||||
|
const close_commands = self.closes.submitted();
|
||||||
|
|
||||||
|
defer close_commands.clear();
|
||||||
|
|
||||||
|
for (close_commands.stack.values) |command| {
|
||||||
|
const object = &rendering.objects.values[command.index];
|
||||||
|
|
||||||
|
switch (object.*) {
|
||||||
|
.empty => {}, // TODO: Handle double-closes.
|
||||||
|
|
||||||
|
.mesh_2d => |mesh_2d| {
|
||||||
|
sokol.gfx.destroyBuffer(mesh_2d.vertex_buffer);
|
||||||
|
sokol.gfx.destroyBuffer(mesh_2d.index_buffer);
|
||||||
|
},
|
||||||
|
|
||||||
|
.texture => |texture| {
|
||||||
|
sokol.gfx.destroyImage(texture.image);
|
||||||
|
sokol.gfx.destroySampler(texture.sampler);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
object.* = .empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Rendering = struct {
|
||||||
|
objects: coral.stack.Sequential(Object),
|
||||||
|
instance_2d_pipeline: sokol.gfx.Pipeline,
|
||||||
|
instance_2d_buffers: coral.stack.Sequential(sokol.gfx.Buffer),
|
||||||
|
|
||||||
|
const Instance2D = extern struct {
|
||||||
|
transform: gfx.Transform2D,
|
||||||
|
tint: @Vector(4, u8) = @splat(std.math.maxInt(u8)),
|
||||||
|
depth: f32 = 0,
|
||||||
|
texture_offset: gfx.Point2D = @splat(0),
|
||||||
|
texture_size: gfx.Point2D = @splat(1),
|
||||||
|
|
||||||
|
const buffer_indices = .{
|
||||||
|
.mesh = 0,
|
||||||
|
.instance = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
const instances_per_buffer = 512;
|
||||||
|
|
||||||
|
const shader = @import("./shaders/instance_2d.glsl.zig");
|
||||||
|
};
|
||||||
|
|
||||||
|
const Frame = struct {
|
||||||
|
width: u16,
|
||||||
|
height: u16,
|
||||||
|
flushed_instance_2d_count: usize = 0,
|
||||||
|
pushed_instance_2d_count: usize = 0,
|
||||||
|
mesh_2d: gfx.Handle = .none,
|
||||||
|
texture: gfx.Handle = .none,
|
||||||
|
|
||||||
|
fn unflushed_instance_2d_count(self: Frame) usize {
|
||||||
|
return self.pushed_instance_2d_count - self.flushed_instance_2d_count;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Object = union (enum) {
|
||||||
|
empty,
|
||||||
|
|
||||||
|
mesh_2d: struct {
|
||||||
|
index_count: u32,
|
||||||
|
vertex_buffer: sokol.gfx.Buffer,
|
||||||
|
index_buffer: sokol.gfx.Buffer,
|
||||||
|
},
|
||||||
|
|
||||||
|
texture: struct {
|
||||||
|
image: sokol.gfx.Image,
|
||||||
|
sampler: sokol.gfx.Sampler,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
fn deinit(self: *Rendering) void {
|
||||||
|
for (self.instance_2d_buffers.values) |buffer| {
|
||||||
|
sokol.gfx.destroyBuffer(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.instance_2d_buffers.deinit();
|
||||||
|
sokol.gfx.destroyPipeline(self.instance_2d_pipeline);
|
||||||
|
self.objects.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init() Rendering {
|
||||||
|
sokol.gfx.setup(.{
|
||||||
|
.environment = .{
|
||||||
|
.defaults = .{
|
||||||
|
.color_format = .RGBA8,
|
||||||
|
.depth_format = .DEPTH_STENCIL,
|
||||||
|
.sample_count = 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
.logger = .{
|
||||||
|
.func = sokol.log.func,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.instance_2d_pipeline = sokol.gfx.makePipeline(.{
|
||||||
|
.label = "2D drawing pipeline",
|
||||||
|
|
||||||
|
.layout = .{
|
||||||
|
.attrs = get: {
|
||||||
|
var attrs = [_]sokol.gfx.VertexAttrState{.{}} ** 16;
|
||||||
|
|
||||||
|
attrs[Instance2D.shader.ATTR_vs_mesh_xy] = .{
|
||||||
|
.format = .FLOAT2,
|
||||||
|
.buffer_index = Instance2D.buffer_indices.mesh,
|
||||||
|
};
|
||||||
|
|
||||||
|
attrs[Instance2D.shader.ATTR_vs_mesh_uv] = .{
|
||||||
|
.format = .FLOAT2,
|
||||||
|
.buffer_index = Instance2D.buffer_indices.mesh,
|
||||||
|
};
|
||||||
|
|
||||||
|
attrs[Instance2D.shader.ATTR_vs_instance_xbasis] = .{
|
||||||
|
.format = .FLOAT2,
|
||||||
|
.buffer_index = Instance2D.buffer_indices.instance,
|
||||||
|
};
|
||||||
|
|
||||||
|
attrs[Instance2D.shader.ATTR_vs_instance_ybasis] = .{
|
||||||
|
.format = .FLOAT2,
|
||||||
|
.buffer_index = Instance2D.buffer_indices.instance,
|
||||||
|
};
|
||||||
|
|
||||||
|
attrs[Instance2D.shader.ATTR_vs_instance_origin] = .{
|
||||||
|
.format = .FLOAT2,
|
||||||
|
.buffer_index = Instance2D.buffer_indices.instance,
|
||||||
|
};
|
||||||
|
|
||||||
|
attrs[Instance2D.shader.ATTR_vs_instance_tint] = .{
|
||||||
|
.format = .UBYTE4N,
|
||||||
|
.buffer_index = Instance2D.buffer_indices.instance,
|
||||||
|
};
|
||||||
|
|
||||||
|
attrs[Instance2D.shader.ATTR_vs_instance_depth] = .{
|
||||||
|
.format = .FLOAT,
|
||||||
|
.buffer_index = Instance2D.buffer_indices.instance,
|
||||||
|
};
|
||||||
|
|
||||||
|
attrs[Instance2D.shader.ATTR_vs_instance_rect] = .{
|
||||||
|
.format = .FLOAT4,
|
||||||
|
.buffer_index = Instance2D.buffer_indices.instance,
|
||||||
|
};
|
||||||
|
|
||||||
|
break: get attrs;
|
||||||
|
},
|
||||||
|
|
||||||
|
.buffers = get: {
|
||||||
|
var buffers = [_]sokol.gfx.VertexBufferLayoutState{.{}} ** 8;
|
||||||
|
|
||||||
|
buffers[Instance2D.buffer_indices.instance].step_func = .PER_INSTANCE;
|
||||||
|
|
||||||
|
break: get buffers;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
.shader = sokol.gfx.makeShader(Instance2D.shader.draw2dShaderDesc(sokol.gfx.queryBackend())),
|
||||||
|
.index_type = .UINT16,
|
||||||
|
}),
|
||||||
|
|
||||||
|
.instance_2d_buffers = .{.allocator = coral.heap.allocator},
|
||||||
|
.objects = .{.allocator = coral.heap.allocator},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush_instance_2ds(self: *Rendering, frame: *Frame) void {
|
||||||
|
const unflushed_count = frame.unflushed_instance_2d_count();
|
||||||
|
|
||||||
|
if (unflushed_count == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sokol.gfx.applyPipeline(self.instance_2d_pipeline);
|
||||||
|
|
||||||
|
sokol.gfx.applyUniforms(.VS, Instance2D.shader.SLOT_Screen, sokol.gfx.asRange(&Instance2D.shader.Screen{
|
||||||
|
.screen_size = .{@floatFromInt(frame.width), @floatFromInt(frame.height)},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const mesh_2d = self.objects.values[frame.mesh_2d.index().?].mesh_2d;
|
||||||
|
const texture = self.objects.values[frame.texture.index().?].texture;
|
||||||
|
|
||||||
|
var bindings = sokol.gfx.Bindings{
|
||||||
|
.vertex_buffers = get: {
|
||||||
|
var buffers = [_]sokol.gfx.Buffer{.{}} ** 8;
|
||||||
|
|
||||||
|
buffers[Instance2D.buffer_indices.mesh] = mesh_2d.vertex_buffer;
|
||||||
|
|
||||||
|
break: get buffers;
|
||||||
|
},
|
||||||
|
|
||||||
|
.index_buffer = mesh_2d.index_buffer,
|
||||||
|
|
||||||
|
.fs = .{
|
||||||
|
.images = get: {
|
||||||
|
var images = [_]sokol.gfx.Image{.{}} ** 12;
|
||||||
|
|
||||||
|
images[0] = texture.image;
|
||||||
|
|
||||||
|
break: get images;
|
||||||
|
},
|
||||||
|
|
||||||
|
.samplers = get: {
|
||||||
|
var samplers = [_]sokol.gfx.Sampler{.{}} ** 8;
|
||||||
|
|
||||||
|
samplers[0] = texture.sampler;
|
||||||
|
|
||||||
|
break: get samplers;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
while (frame.flushed_instance_2d_count < frame.pushed_instance_2d_count) {
|
||||||
|
const buffer_index = frame.flushed_instance_2d_count / Instance2D.instances_per_buffer;
|
||||||
|
const buffer_offset = frame.flushed_instance_2d_count % Instance2D.instances_per_buffer;
|
||||||
|
const instances_to_flush = @min(Instance2D.instances_per_buffer - buffer_offset, unflushed_count);
|
||||||
|
|
||||||
|
bindings.vertex_buffers[Instance2D.buffer_indices.instance] = self.instance_2d_buffers.values[buffer_index];
|
||||||
|
bindings.vertex_buffer_offsets[Instance2D.buffer_indices.instance] = @intCast(buffer_offset);
|
||||||
|
|
||||||
|
sokol.gfx.applyBindings(bindings);
|
||||||
|
sokol.gfx.draw(0, mesh_2d.index_count, @intCast(instances_to_flush));
|
||||||
|
|
||||||
|
frame.flushed_instance_2d_count += instances_to_flush;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_object(self: *Rendering, index: usize, object: Object) !void {
|
||||||
|
const resource_count = self.objects.len();
|
||||||
|
|
||||||
|
if (index < resource_count) {
|
||||||
|
const empty_object = &self.objects.values[index];
|
||||||
|
|
||||||
|
if (empty_object.* != .empty) {
|
||||||
|
return error.InvalidHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
empty_object.* = object;
|
||||||
|
} else {
|
||||||
|
if (index != resource_count) {
|
||||||
|
return error.InvalidIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.objects.push_grow(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_instance_2d(self: *Rendering, frame: *Frame, command: RenderCommand.Instance) std.mem.Allocator.Error!void {
|
||||||
|
if (command.mesh_2d != frame.mesh_2d or command.texture != frame.texture) {
|
||||||
|
self.flush_instance_2ds(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
frame.mesh_2d = command.mesh_2d;
|
||||||
|
frame.texture = command.texture;
|
||||||
|
|
||||||
|
const has_filled_buffer = (frame.pushed_instance_2d_count % Instance2D.instances_per_buffer) == 0;
|
||||||
|
const pushed_buffer_count = frame.pushed_instance_2d_count / Instance2D.instances_per_buffer;
|
||||||
|
|
||||||
|
if (has_filled_buffer and pushed_buffer_count == self.instance_2d_buffers.len()) {
|
||||||
|
const instance_buffer = sokol.gfx.makeBuffer(.{
|
||||||
|
.size = @sizeOf(Instance2D) * Instance2D.instances_per_buffer,
|
||||||
|
.usage = .STREAM,
|
||||||
|
.label = "2D drawing instance buffer",
|
||||||
|
});
|
||||||
|
|
||||||
|
errdefer sokol.gfx.destroyBuffer(instance_buffer);
|
||||||
|
|
||||||
|
try self.instance_2d_buffers.push_grow(instance_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = sokol.gfx.appendBuffer(self.instance_2d_buffers.get().?, sokol.gfx.asRange(&Instance2D{
|
||||||
|
.transform = command.transform,
|
||||||
|
}));
|
||||||
|
|
||||||
|
frame.pushed_instance_2d_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_object(self: *Rendering, index: usize) ?Object {
|
||||||
|
const object = self.objects.values[index];
|
||||||
|
|
||||||
|
if (object != .empty) {
|
||||||
|
self.objects.values[index] = .empty;
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const RenderCommand = union (enum) {
|
||||||
|
instance_2d: Instance,
|
||||||
|
post_process: PostProcess,
|
||||||
|
|
||||||
|
pub const Instance = struct {
|
||||||
|
texture: gfx.Handle,
|
||||||
|
mesh_2d: gfx.Handle,
|
||||||
|
transform: gfx.Transform2D,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const PostProcess = struct {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
fn clone(self: RenderCommand, arena: *std.heap.ArenaAllocator) std.mem.Allocator.Error!RenderCommand {
|
||||||
|
_ = arena;
|
||||||
|
|
||||||
|
return switch (self) {
|
||||||
|
.instance_2d => |instance_2d| .{.instance_2d = instance_2d},
|
||||||
|
.post_process => |post_process| .{.post_process = post_process},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const RenderChain = commands.Chain(RenderCommand, RenderCommand.clone);
|
||||||
|
|
||||||
|
pub const RenderList = commands.List(RenderCommand, RenderCommand.clone);
|
|
@ -0,0 +1,81 @@
|
||||||
|
const coral = @import("coral");
|
||||||
|
|
||||||
|
const gfx = @import("../gfx.zig");
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn bmp_file_desc(
|
||||||
|
arena: *std.heap.ArenaAllocator,
|
||||||
|
storage: coral.files.Storage,
|
||||||
|
path: []const u8,
|
||||||
|
) gfx.Assets.Format.Error!gfx.Desc {
|
||||||
|
const header = try storage.read_little(path, 0, extern struct {
|
||||||
|
type: [2]u8 align (1),
|
||||||
|
file_size: u32 align (1),
|
||||||
|
reserved: [2]u16 align (1),
|
||||||
|
image_offset: u32 align (1),
|
||||||
|
header_size: u32 align (1),
|
||||||
|
pixel_width: i32 align (1),
|
||||||
|
pixel_height: i32 align (1),
|
||||||
|
color_planes: u16 align (1),
|
||||||
|
bits_per_pixel: u16 align (1),
|
||||||
|
compression_method: u32 align (1),
|
||||||
|
image_size: u32 align(1),
|
||||||
|
pixels_per_meter_x: i32 align (1),
|
||||||
|
pixels_per_meter_y: i32 align (1),
|
||||||
|
palette_colors_used: u32 align (1),
|
||||||
|
important_colors_used: u32 align (1),
|
||||||
|
}) orelse {
|
||||||
|
return error.FormatUnsupported;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!std.mem.eql(u8, &header.type, "BM")) {
|
||||||
|
return error.FormatUnsupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pixel_width = std.math.cast(u16, header.pixel_width) orelse {
|
||||||
|
return error.FormatUnsupported;
|
||||||
|
};
|
||||||
|
|
||||||
|
const pixels = try arena.allocator().alloc(coral.io.Byte, header.image_size);
|
||||||
|
const bytes_per_pixel = header.bits_per_pixel / coral.io.bits_per_byte;
|
||||||
|
const alignment = 4;
|
||||||
|
const byte_stride = pixel_width * bytes_per_pixel;
|
||||||
|
const padded_byte_stride = alignment * @divTrunc((byte_stride + alignment - 1), alignment);
|
||||||
|
const byte_padding = coral.scalars.sub(padded_byte_stride, byte_stride) orelse 0;
|
||||||
|
var buffer_offset: usize = 0;
|
||||||
|
var file_offset = @as(usize, header.image_offset);
|
||||||
|
|
||||||
|
switch (header.bits_per_pixel) {
|
||||||
|
32 => {
|
||||||
|
while (buffer_offset < pixels.len) {
|
||||||
|
const line = pixels[buffer_offset .. buffer_offset + byte_stride];
|
||||||
|
|
||||||
|
if (try storage.read_bytes(path, file_offset, line) != byte_stride) {
|
||||||
|
return error.FormatUnsupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (0 .. pixel_width) |i| {
|
||||||
|
const line_offset = i * 4;
|
||||||
|
const pixel = line[line_offset .. line_offset + 4];
|
||||||
|
|
||||||
|
std.mem.swap(u8, &pixel[0], &pixel[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
file_offset += line.len + byte_padding;
|
||||||
|
buffer_offset += padded_byte_stride;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
else => return error.FormatUnsupported,
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.texture = .{
|
||||||
|
.width = pixel_width,
|
||||||
|
.data = pixels,
|
||||||
|
.format = .rgba8,
|
||||||
|
.access = .static,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -51,6 +51,10 @@ out vec4 texel;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
texel = texture(sampler2D(tex, smp), uv) * color;
|
texel = texture(sampler2D(tex, smp), uv) * color;
|
||||||
|
|
||||||
|
if (texel.a == 0) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ const std = @import("std");
|
||||||
fn Channel(comptime Message: type) type {
|
fn Channel(comptime Message: type) type {
|
||||||
return struct {
|
return struct {
|
||||||
buffers: [2]coral.stack.Sequential(Message),
|
buffers: [2]coral.stack.Sequential(Message),
|
||||||
swapped: bool = false,
|
swap_index: u1 = 0,
|
||||||
ticks: u1 = 0,
|
ticks: u1 = 0,
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
@ -28,9 +28,9 @@ fn Channel(comptime Message: type) type {
|
||||||
channel.res.ticks = coral.scalars.add(channel.res.ticks, 1) orelse 0;
|
channel.res.ticks = coral.scalars.add(channel.res.ticks, 1) orelse 0;
|
||||||
|
|
||||||
if (channel.res.ticks == 0) {
|
if (channel.res.ticks == 0) {
|
||||||
channel.res.swapped = !channel.res.swapped;
|
channel.res.swap_index ^= 1;
|
||||||
|
|
||||||
channel.res.buffers[@intFromBool(channel.res.swapped)].clear();
|
channel.res.buffers[channel.res.swap_index].clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,11 +44,11 @@ fn Channel(comptime Message: type) type {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn messages(self: Self) []const Message {
|
fn messages(self: Self) []const Message {
|
||||||
return self.buffers[@intFromBool(!self.swapped)].values;
|
return self.buffers[self.swap_index ^ 1].values;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push(self: *Self, message: Message) std.mem.Allocator.Error!void {
|
fn push(self: *Self, message: Message) std.mem.Allocator.Error!void {
|
||||||
try self.buffers[@intFromBool(self.swapped)].push(message);
|
try self.buffers[self.swap_index].push_grow(message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Unused addition.