Simplify 2D rendering interfaces
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This commit is contained in:
parent
9c54ed4683
commit
99c3818477
|
@ -0,0 +1,28 @@
|
|||
#version 430
|
||||
|
||||
layout (binding = 0) uniform sampler2D sprite;
|
||||
|
||||
layout (location = 0) in vec4 color;
|
||||
layout (location = 1) in vec2 uv;
|
||||
|
||||
layout (location = 0) out vec4 texel;
|
||||
|
||||
layout (binding = 0) readonly buffer Camera {
|
||||
mat4 projection_matrix;
|
||||
};
|
||||
|
||||
layout (binding = 1) readonly buffer Material {
|
||||
float effect_magnitude;
|
||||
};
|
||||
|
||||
void main() {
|
||||
const vec2 red_channel_uv = uv + vec2(effect_magnitude, 0.0);
|
||||
const vec2 blue_channel_uv = uv + vec2(-effect_magnitude, 0.0);
|
||||
const vec4 original_texel = texture(sprite, uv);
|
||||
|
||||
texel = vec4(texture(sprite, red_channel_uv).r, original_texel.g, texture(sprite, blue_channel_uv).b, original_texel.a) * color;
|
||||
|
||||
if (texel.a == 0) {
|
||||
discard;
|
||||
}
|
||||
}
|
BIN
debug/test.bmp (Stored with Git LFS)
BIN
debug/test.bmp (Stored with Git LFS)
Binary file not shown.
|
@ -7,7 +7,7 @@ const scalars = @import("./scalars.zig");
|
|||
const std = @import("std");
|
||||
|
||||
pub const DecimalFormat = struct {
|
||||
delimiter: []const coral.Byte,
|
||||
delimiter: []const coral.io.Byte,
|
||||
positive_prefix: enum {none, plus, space},
|
||||
|
||||
pub const default = DecimalFormat{
|
||||
|
@ -115,9 +115,9 @@ pub const DecimalFormat = struct {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn format(self: DecimalFormat, writer: io.Writer, value: anytype) io.PrintError!void {
|
||||
pub fn format(self: DecimalFormat, writer: io.Writer, value: anytype) io.Error!void {
|
||||
if (value == 0) {
|
||||
return io.print(writer, switch (self.positive_prefix) {
|
||||
return io.write_all(writer, switch (self.positive_prefix) {
|
||||
.none => "0",
|
||||
.plus => "+0",
|
||||
.space => " 0",
|
||||
|
@ -151,21 +151,21 @@ pub const DecimalFormat = struct {
|
|||
}
|
||||
}
|
||||
|
||||
return io.print(writer, buffer[buffer_start ..]);
|
||||
return io.write_all(writer, buffer[buffer_start ..]);
|
||||
},
|
||||
|
||||
.Float => |float| {
|
||||
if (value < 0) {
|
||||
try io.print(writer, "-");
|
||||
try io.write_all(writer, "-");
|
||||
}
|
||||
|
||||
const Float = @TypeOf(value);
|
||||
const Int = std.meta.Int(.unsigned, float.bits);
|
||||
const integer = @as(Int, @intFromFloat(value));
|
||||
|
||||
try self.print(writer, integer);
|
||||
try io.print(writer, ".");
|
||||
try self.print(writer, @as(Int, @intFromFloat((value - @as(Float, @floatFromInt(integer))) * 100)));
|
||||
try self.format(writer, integer);
|
||||
try io.write_all(writer, ".");
|
||||
try self.format(writer, @as(Int, @intFromFloat((value - @as(Float, @floatFromInt(integer))) * 100)));
|
||||
},
|
||||
|
||||
else => @compileError("`" ++ @typeName(Value) ++ "` cannot be formatted to a decimal string"),
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
const std = @import("std");
|
||||
|
||||
pub fn BlockingQueue(comptime max: usize, comptime T: type) type {
|
||||
return struct {
|
||||
buffer: [max]T = undefined,
|
||||
head: usize = 0,
|
||||
tail: usize = 0,
|
||||
mutex: std.Thread.Mutex = .{},
|
||||
not_empty: std.Thread.Condition = .{},
|
||||
not_full: std.Thread.Condition = .{},
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn enqueue(self: *Self, item: T) void {
|
||||
self.mutex.lock();
|
||||
|
||||
defer self.mutex.unlock();
|
||||
|
||||
while (((self.tail + 1) % max) == self.head) {
|
||||
self.not_full.wait(&self.mutex);
|
||||
}
|
||||
|
||||
self.buffer[self.tail] = item;
|
||||
self.tail = (self.tail + 1) % max;
|
||||
|
||||
self.not_empty.signal();
|
||||
}
|
||||
|
||||
pub fn dequeue(self: *Self) T {
|
||||
self.mutex.lock();
|
||||
|
||||
defer self.mutex.unlock();
|
||||
|
||||
while (self.head == self.tail) {
|
||||
self.not_empty.wait(&self.mutex);
|
||||
}
|
||||
|
||||
const item = self.buffer[self.head];
|
||||
|
||||
self.buffer[self.head] = undefined;
|
||||
self.head = (self.head + 1) % max;
|
||||
|
||||
self.not_full.signal();
|
||||
|
||||
return item;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn Future(comptime T: type) type {
|
||||
return struct {
|
||||
value: ?T = null,
|
||||
resolved: std.Thread.ResetEvent = .{},
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn get(self: *Self) *T {
|
||||
// TODO: Make async.
|
||||
self.resolved.wait();
|
||||
|
||||
return &self.value.?;
|
||||
}
|
||||
|
||||
pub fn resolve(self: *Self, value: T) bool {
|
||||
if (self.resolved.isSet()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.value = value;
|
||||
|
||||
self.resolved.set();
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
pub const ascii = @import("./ascii.zig");
|
||||
|
||||
pub const asyncio = @import("./asyncio.zig");
|
||||
|
||||
pub const files = @import("./files.zig");
|
||||
|
||||
pub const hashes = @import("./hashes.zig");
|
||||
|
@ -16,6 +18,108 @@ pub const slices = @import("./slices.zig");
|
|||
|
||||
pub const stack = @import("./stack.zig");
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
pub const utf8 = @import("./utf8.zig");
|
||||
|
||||
pub const vectors = @import("./vectors.zig");
|
||||
|
||||
pub fn Pool(comptime Value: type) type {
|
||||
return struct {
|
||||
entries: stack.Sequential(Entry),
|
||||
first_free_index: usize = 0,
|
||||
|
||||
const Entry = union (enum) {
|
||||
free_index: usize,
|
||||
occupied: Value,
|
||||
};
|
||||
|
||||
pub const Values = struct {
|
||||
cursor: usize = 0,
|
||||
pool: *const Self,
|
||||
|
||||
pub fn next(self: *Values) ?*Value {
|
||||
while (self.cursor < self.pool.entries.len()) {
|
||||
defer self.cursor += 1;
|
||||
|
||||
switch (self.pool.entries.values[self.cursor]) {
|
||||
.free_index => {
|
||||
continue;
|
||||
},
|
||||
|
||||
.occupied => |*occupied| {
|
||||
return occupied;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.entries.deinit();
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn get(self: *Self, key: usize) ?*Value {
|
||||
return switch (self.entries.values[key]) {
|
||||
.free_index => null,
|
||||
.occupied => |*occupied| occupied,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator) Self {
|
||||
return .{
|
||||
.entries = .{.allocator = allocator},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn insert(self: *Self, value: Value) std.mem.Allocator.Error!usize {
|
||||
const entries_count = self.entries.len();
|
||||
|
||||
if (self.first_free_index == entries_count) {
|
||||
try self.entries.push_grow(.{.occupied = value});
|
||||
|
||||
self.first_free_index += 1;
|
||||
|
||||
return entries_count;
|
||||
}
|
||||
|
||||
const insersion_index = self.first_free_index;
|
||||
|
||||
self.first_free_index = self.entries.values[self.first_free_index].free_index;
|
||||
self.entries.values[insersion_index] = .{.occupied = value};
|
||||
|
||||
return insersion_index;
|
||||
}
|
||||
|
||||
pub fn remove(self: *Self, key: usize) ?Value {
|
||||
if (key >= self.entries.len()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (self.entries.values[key]) {
|
||||
.free_index => {
|
||||
return null;
|
||||
},
|
||||
|
||||
.occupied => |occupied| {
|
||||
self.entries.values[key] = .{.free_index = self.first_free_index};
|
||||
self.first_free_index = key;
|
||||
|
||||
return occupied;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn values(self: *const Self) Values {
|
||||
return .{
|
||||
.pool = self,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -4,11 +4,20 @@ const io = @import("./io.zig");
|
|||
|
||||
const std = @import("std");
|
||||
|
||||
pub const Error = error {
|
||||
pub const AccessError = error {
|
||||
FileNotFound,
|
||||
FileInaccessible,
|
||||
};
|
||||
|
||||
pub const ReadAllError = AccessError || error {
|
||||
ReadIncomplete,
|
||||
};
|
||||
|
||||
pub const ReadAllOptions = struct {
|
||||
offset: u64 = 0,
|
||||
limit: u64 = std.math.maxInt(u64),
|
||||
};
|
||||
|
||||
pub const Stat = struct {
|
||||
size: u64,
|
||||
};
|
||||
|
@ -18,15 +27,37 @@ pub const Storage = struct {
|
|||
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,
|
||||
stat: *const fn (*anyopaque, []const u8) AccessError!Stat,
|
||||
read: *const fn (*anyopaque, []const u8, usize, []io.Byte) AccessError!usize,
|
||||
};
|
||||
|
||||
pub fn read_bytes(self: Storage, path: []const u8, offset: usize, output: []io.Byte) Error!usize {
|
||||
pub fn read(self: Storage, path: []const u8, output: []io.Byte, offset: u64) AccessError!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 {
|
||||
pub fn read_all(self: Storage, path: []const u8, output: []io.Byte, options: ReadAllOptions) ReadAllError![]const io.Byte {
|
||||
const bytes_read = try self.vtable.read(self.userdata, path, options.offset, output);
|
||||
|
||||
if (try self.vtable.read(self.userdata, path, options.offset, output) != output.len) {
|
||||
return error.ReadIncomplete;
|
||||
}
|
||||
|
||||
return output[0 .. bytes_read];
|
||||
}
|
||||
|
||||
pub fn read_alloc(self: Storage, path: []const u8, allocator: std.mem.Allocator, options: ReadAllOptions) (std.mem.Allocator.Error || ReadAllError)![]io.Byte {
|
||||
const buffer = try allocator.alloc(io.Byte, @min((try self.stat(path)).size, options.limit));
|
||||
|
||||
errdefer allocator.free(buffer);
|
||||
|
||||
if (try self.vtable.read(self.userdata, path, options.offset, buffer) != buffer.len) {
|
||||
return error.ReadIncomplete;
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
pub fn read_foreign(self: Storage, path: []const u8, offset: u64, comptime Type: type) AccessError!?Type {
|
||||
const decoded = (try self.read_native(path, offset, Type)) orelse {
|
||||
return null;
|
||||
};
|
||||
|
@ -37,7 +68,7 @@ pub const Storage = struct {
|
|||
};
|
||||
}
|
||||
|
||||
pub fn read_native(self: Storage, path: []const u8, offset: usize, comptime Type: type) Error!?Type {
|
||||
pub fn read_native(self: Storage, path: []const u8, offset: u64, comptime Type: type) AccessError!?Type {
|
||||
var buffer = @as([@sizeOf(Type)]io.Byte, undefined);
|
||||
|
||||
if (try self.vtable.read(self.userdata, path, offset, &buffer) != buffer.len) {
|
||||
|
@ -47,6 +78,10 @@ pub const Storage = struct {
|
|||
return @as(*align(1) const Type, @ptrCast(&buffer)).*;
|
||||
}
|
||||
|
||||
pub fn stat(self: Storage, path: []const u8) AccessError!Stat {
|
||||
return self.vtable.stat(self.userdata, path);
|
||||
}
|
||||
|
||||
pub const read_little = switch (native_endian) {
|
||||
.little => read_native,
|
||||
.big => read_foreign,
|
||||
|
@ -60,7 +95,7 @@ pub const Storage = struct {
|
|||
|
||||
pub const bundle = init: {
|
||||
const Bundle = struct {
|
||||
fn full_path(path: []const u8) Error![4095:0]u8 {
|
||||
fn full_path(path: []const u8) AccessError![4095:0]u8 {
|
||||
var buffer = [_:0]u8{0} ** 4095;
|
||||
|
||||
_ = std.fs.cwd().realpath(path, &buffer) catch {
|
||||
|
@ -70,7 +105,7 @@ pub const bundle = init: {
|
|||
return buffer;
|
||||
}
|
||||
|
||||
fn read(_: *anyopaque, path: []const u8, offset: usize, output: []io.Byte) Error!usize {
|
||||
fn read(_: *anyopaque, path: []const u8, offset: usize, output: []io.Byte) AccessError!usize {
|
||||
var file = std.fs.openFileAbsoluteZ(&(try full_path(path)), .{.mode = .read_only}) catch |open_error| {
|
||||
return switch (open_error) {
|
||||
error.FileNotFound => error.FileNotFound,
|
||||
|
@ -89,7 +124,7 @@ pub const bundle = init: {
|
|||
return file.read(output) catch error.FileInaccessible;
|
||||
}
|
||||
|
||||
fn stat(_: *anyopaque, path: []const u8) Error!Stat {
|
||||
fn stat(_: *anyopaque, path: []const u8) AccessError!Stat {
|
||||
const file_stat = get: {
|
||||
var file = std.fs.openFileAbsoluteZ(&(try full_path(path)), .{.mode = .read_only}) catch |open_error| {
|
||||
return switch (open_error) {
|
||||
|
|
|
@ -138,16 +138,6 @@ pub fn alloc_read(input: coral.io.Reader, allocator: std.mem.Allocator) []coral.
|
|||
|
||||
pub const bits_per_byte = 8;
|
||||
|
||||
pub fn bytes_of(value: anytype) []const Byte {
|
||||
const pointer_info = @typeInfo(@TypeOf(value)).Pointer;
|
||||
|
||||
return switch (pointer_info.size) {
|
||||
.One => @as([*]const Byte, @ptrCast(value))[0 .. @sizeOf(pointer_info.child)],
|
||||
.Slice => @as([*]const Byte, @ptrCast(value.ptr))[0 .. @sizeOf(pointer_info.child) * value.len],
|
||||
else => @compileError("`value` must be single-element pointer or slice type"),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn skip_n(input: Reader, distance: u64) Error!void {
|
||||
var buffer = @as([512]coral.Byte, undefined);
|
||||
var remaining = distance;
|
||||
|
@ -163,16 +153,6 @@ pub fn skip_n(input: Reader, distance: u64) Error!void {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn slice_sentineled(comptime Sentinel: type, comptime sen: Sentinel, ptr: [*:sen]const Sentinel) [:sen]const Sentinel {
|
||||
var len = @as(usize, 0);
|
||||
|
||||
while (ptr[len] != sen) {
|
||||
len += 1;
|
||||
}
|
||||
|
||||
return ptr[0 .. len:sen];
|
||||
}
|
||||
|
||||
pub fn stream_all(input: Reader, output: Writer) Error!usize {
|
||||
var buffer = @as([512]coral.Byte, undefined);
|
||||
var copied = @as(usize, 0);
|
||||
|
|
|
@ -147,7 +147,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime traits: Traits(
|
|||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn get_ptr(self: Self, key: Key) ?*Value {
|
||||
pub fn get(self: Self, key: Key) ?*Value {
|
||||
if (self.len == 0) {
|
||||
return null;
|
||||
}
|
||||
|
@ -169,14 +169,6 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime traits: Traits(
|
|||
return null;
|
||||
}
|
||||
|
||||
pub fn get(self: Self, key: Key) ?Value {
|
||||
if (self.get_ptr(key)) |value| {
|
||||
return value.*;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn emplace(self: *Self, key: Key, value: Value) std.mem.Allocator.Error!bool {
|
||||
try self.rehash(load_max);
|
||||
|
||||
|
|
|
@ -115,24 +115,6 @@ pub fn get_ptr(slice: anytype, index: usize) ?ElementPtr(@TypeOf(slice)) {
|
|||
return &slice[index];
|
||||
}
|
||||
|
||||
pub fn index_of(haystack: anytype, offset: usize, needle: std.meta.Child(@TypeOf(haystack))) ?usize {
|
||||
for (offset .. haystack.len) |i| {
|
||||
if (haystack[i] == needle) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn index_of_any(haystack: anytype, offset: usize, needle: []const std.meta.Child(@TypeOf(haystack))) ?usize {
|
||||
return std.mem.indexOfAnyPos(std.meta.Child(@TypeOf(haystack)), haystack, offset, needle);
|
||||
}
|
||||
|
||||
pub fn index_of_seq(haystack: anytype, offset: usize, needle: []const std.meta.Child(@TypeOf(haystack))) ?usize {
|
||||
return std.mem.indexOfPos(std.meta.Child(@TypeOf(haystack)), haystack, offset, needle);
|
||||
}
|
||||
|
||||
pub fn parallel_alloc(comptime Element: type, allocator: std.mem.Allocator, n: usize) std.mem.Allocator.Error!Parallel(Element) {
|
||||
const alignment = @alignOf(Element);
|
||||
const Slices = Parallel(Element);
|
||||
|
|
|
@ -6,17 +6,14 @@ const io = @import("./io.zig");
|
|||
|
||||
const std = @import("std");
|
||||
|
||||
pub fn alloc_formatted(allocator: std.mem.Allocator, comptime format: []const u8, args: anytype) std.mem.Allocator.Error![]coral.Byte {
|
||||
var buffer = coral.Stack(coral.Byte){.allocator = allocator};
|
||||
pub fn alloc_formatted(allocator: std.mem.Allocator, comptime format: []const u8, args: anytype) std.mem.Allocator.Error![:0]u8 {
|
||||
const formatted_len = count_formatted(format, args);
|
||||
const buffer = try allocator.allocSentinel(u8, formatted_len, 0);
|
||||
|
||||
try buffer.grow(formatted_len);
|
||||
errdefer allocator.free(buffer);
|
||||
|
||||
errdefer buffer.deinit();
|
||||
|
||||
print_formatted(buffer.writer(), format, args) catch unreachable;
|
||||
|
||||
return buffer.to_allocation(formatted_len, 0);
|
||||
// TODO: This is dumb.
|
||||
return @constCast(print_formatted(buffer, format, args) catch unreachable);
|
||||
}
|
||||
|
||||
pub fn count_formatted(comptime format: []const u8, args: anytype) usize {
|
||||
|
@ -27,7 +24,7 @@ pub fn count_formatted(comptime format: []const u8, args: anytype) usize {
|
|||
return count.written;
|
||||
}
|
||||
|
||||
pub fn print_formatted(buffer: [:0]coral.io.Byte, comptime format: []const u8, args: anytype) io.Error![:0]u8 {
|
||||
pub fn print_formatted(buffer: [:0]coral.io.Byte, comptime format: []const u8, args: anytype) io.Error![:0]const u8 {
|
||||
const Seekable = struct {
|
||||
buffer: []coral.io.Byte,
|
||||
cursor: usize,
|
||||
|
@ -143,8 +140,8 @@ noinline fn print_formatted_value(writer: io.Writer, value: anytype) io.Error!vo
|
|||
const Value = @TypeOf(value);
|
||||
|
||||
return switch (@typeInfo(Value)) {
|
||||
.Int => ascii.DecimalFormat.default.print(writer, value),
|
||||
.Float => ascii.DecimalFormat.default.print(writer, value),
|
||||
.Int => ascii.DecimalFormat.default.format(writer, value),
|
||||
.Float => ascii.DecimalFormat.default.format(writer, value),
|
||||
.Enum => io.print(writer, @tagName(value)),
|
||||
|
||||
.Pointer => |pointer| switch (pointer.size) {
|
||||
|
|
|
@ -80,7 +80,7 @@ pub fn Graph(comptime Payload: type) type {
|
|||
return false;
|
||||
};
|
||||
|
||||
if (coral.slices.index_of(edges.values, 0, edge_node) == null) {
|
||||
if (std.mem.indexOfScalar(Node, edges.values, edge_node) == null) {
|
||||
try edges.push_grow(edge_node);
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ pub const Table = struct {
|
|||
}
|
||||
|
||||
pub fn get(self: Table, comptime Resource: type) ?*Resource {
|
||||
if (self.table.get_ptr(type_id(Resource))) |entry| {
|
||||
if (self.table.get(type_id(Resource))) |entry| {
|
||||
return @ptrCast(@alignCast(entry.ptr));
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,7 @@ pub const Table = struct {
|
|||
const Value = @TypeOf(value);
|
||||
const value_id = type_id(Value);
|
||||
|
||||
if (self.table.get_ptr(value_id)) |entry| {
|
||||
if (self.table.get(value_id)) |entry| {
|
||||
@as(*Value, @ptrCast(@alignCast(entry.ptr))).* = value;
|
||||
} else {
|
||||
const resource_allocator = self.arena.allocator();
|
||||
|
|
|
@ -49,16 +49,16 @@ pub const BindContext = struct {
|
|||
}
|
||||
|
||||
const read_write_resource_nodes = lazily_create: {
|
||||
break: lazily_create self.systems.read_write_resource_id_nodes.get_ptr(id) orelse insert: {
|
||||
break: lazily_create self.systems.read_write_resource_id_nodes.get(id) orelse insert: {
|
||||
std.debug.assert(try self.systems.read_write_resource_id_nodes.emplace(id, .{
|
||||
.allocator = coral.heap.allocator,
|
||||
}));
|
||||
|
||||
break: insert self.systems.read_write_resource_id_nodes.get_ptr(id).?;
|
||||
break: insert self.systems.read_write_resource_id_nodes.get(id).?;
|
||||
};
|
||||
};
|
||||
|
||||
if (coral.slices.index_of(read_write_resource_nodes.values, 0, self.node) == null) {
|
||||
if (std.mem.indexOfScalar(dag.Node, read_write_resource_nodes.values, self.node) == null) {
|
||||
try read_write_resource_nodes.push_grow(self.node);
|
||||
}
|
||||
|
||||
|
@ -77,16 +77,16 @@ pub const BindContext = struct {
|
|||
}
|
||||
|
||||
const read_only_resource_nodes = lazily_create: {
|
||||
break: lazily_create self.systems.read_only_resource_id_nodes.get_ptr(id) orelse insert: {
|
||||
break: lazily_create self.systems.read_only_resource_id_nodes.get(id) orelse insert: {
|
||||
std.debug.assert(try self.systems.read_only_resource_id_nodes.emplace(id, .{
|
||||
.allocator = coral.heap.allocator,
|
||||
}));
|
||||
|
||||
break: insert self.systems.read_only_resource_id_nodes.get_ptr(id).?;
|
||||
break: insert self.systems.read_only_resource_id_nodes.get(id).?;
|
||||
};
|
||||
};
|
||||
|
||||
if (coral.slices.index_of(read_only_resource_nodes.values, 0, self.node) == null) {
|
||||
if (std.mem.indexOfScalar(dag.Node, read_only_resource_nodes.values, self.node) == null) {
|
||||
try read_only_resource_nodes.push_grow(self.node);
|
||||
}
|
||||
|
||||
|
@ -415,12 +415,12 @@ pub const Schedule = struct {
|
|||
const nodes = lazily_create: {
|
||||
const system_id = @intFromPtr(info);
|
||||
|
||||
break: lazily_create self.system_id_nodes.get_ptr(system_id) orelse insert: {
|
||||
break: lazily_create self.system_id_nodes.get(system_id) orelse insert: {
|
||||
std.debug.assert(try self.system_id_nodes.emplace(system_id, .{
|
||||
.allocator = self.system_id_nodes.allocator,
|
||||
}));
|
||||
|
||||
break: insert self.system_id_nodes.get_ptr(system_id).?;
|
||||
break: insert self.system_id_nodes.get(system_id).?;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
62
src/main.zig
62
src/main.zig
|
@ -5,10 +5,9 @@ const std = @import("std");
|
|||
const ona = @import("ona");
|
||||
|
||||
const Actors = struct {
|
||||
instances: coral.stack.Sequential(ona.gfx.Point2D) = .{.allocator = coral.heap.allocator},
|
||||
quad_mesh_2d: ?*ona.gfx.Handle = null,
|
||||
body_texture: ?*ona.gfx.Handle = null,
|
||||
render_texture: ?*ona.gfx.Handle = null,
|
||||
instances: coral.stack.Sequential(@Vector(2, f32)) = .{.allocator = coral.heap.allocator},
|
||||
body_texture: ona.gfx.Texture = .default,
|
||||
render_texture: ona.gfx.Texture = .default,
|
||||
};
|
||||
|
||||
const Player = struct {
|
||||
|
@ -23,20 +22,17 @@ pub fn main() !void {
|
|||
});
|
||||
}
|
||||
|
||||
fn load(display: ona.Write(ona.gfx.Display), actors: ona.Write(Actors), assets: ona.Write(ona.gfx.Assets)) !void {
|
||||
display.res.width, display.res.height = .{1280, 720};
|
||||
actors.res.body_texture = try assets.res.create_from_file(coral.files.bundle, "actor.bmp");
|
||||
actors.res.quad_mesh_2d = try assets.res.create_quad_mesh_2d(@splat(1));
|
||||
fn load(config: ona.Write(ona.gfx.Config), actors: ona.Write(Actors), assets: ona.Write(ona.gfx.Assets)) !void {
|
||||
config.res.width, config.res.height = .{1280, 720};
|
||||
actors.res.body_texture = try assets.res.load_texture_file(coral.files.bundle, "actor.bmp");
|
||||
|
||||
actors.res.render_texture = try assets.res.context.create(.{
|
||||
.texture = .{
|
||||
.format = .rgba8,
|
||||
actors.res.render_texture = try assets.res.load_texture(.{
|
||||
.format = .rgba8,
|
||||
|
||||
.access = .{
|
||||
.render = .{
|
||||
.width = display.res.width,
|
||||
.height = display.res.height,
|
||||
},
|
||||
.access = .{
|
||||
.render = .{
|
||||
.width = config.res.width,
|
||||
.height = config.res.height,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -48,41 +44,41 @@ fn exit(actors: ona.Write(Actors)) void {
|
|||
actors.res.instances.deinit();
|
||||
}
|
||||
|
||||
fn render(queue: ona.gfx.Queue, actors: ona.Write(Actors), display: ona.Read(ona.gfx.Display)) !void {
|
||||
try queue.commands.append(.{.target = .{
|
||||
.texture = actors.res.render_texture.?,
|
||||
fn render(commands: ona.gfx.Commands, actors: ona.Write(Actors), config: ona.Read(ona.gfx.Config)) !void {
|
||||
try commands.set_target(.{
|
||||
.texture = actors.res.render_texture,
|
||||
.clear_color = .{0, 0, 0, 0},
|
||||
.clear_depth = 0,
|
||||
}});
|
||||
.clear_stencil = 0,
|
||||
});
|
||||
|
||||
for (actors.res.instances.values) |instance| {
|
||||
try queue.commands.append(.{.instance_2d = .{
|
||||
.mesh_2d = actors.res.quad_mesh_2d.?,
|
||||
.texture = actors.res.body_texture.?,
|
||||
try commands.draw_texture(.{
|
||||
.texture = actors.res.body_texture,
|
||||
|
||||
.transform = .{
|
||||
.origin = instance,
|
||||
.xbasis = .{64, 0},
|
||||
.ybasis = .{0, 64},
|
||||
},
|
||||
}});
|
||||
});
|
||||
}
|
||||
|
||||
try queue.commands.append(.{.target = .{
|
||||
try commands.set_target(.{
|
||||
.clear_color = null,
|
||||
.clear_depth = null,
|
||||
}});
|
||||
.clear_stencil = null,
|
||||
});
|
||||
|
||||
try queue.commands.append(.{.instance_2d = .{
|
||||
.mesh_2d = actors.res.quad_mesh_2d.?,
|
||||
.texture = actors.res.render_texture.?,
|
||||
try commands.draw_texture(.{
|
||||
.texture = actors.res.render_texture,
|
||||
|
||||
.transform = .{
|
||||
.origin = .{@floatFromInt(display.res.width / 2), @floatFromInt(display.res.height / 2)},
|
||||
.xbasis = .{@floatFromInt(display.res.width), 0},
|
||||
.ybasis = .{0, @floatFromInt(display.res.height)},
|
||||
.origin = .{@floatFromInt(config.res.width / 2), @floatFromInt(config.res.height / 2)},
|
||||
.xbasis = .{@floatFromInt(config.res.width), 0},
|
||||
.ybasis = .{0, @floatFromInt(config.res.height)},
|
||||
},
|
||||
}});
|
||||
});
|
||||
}
|
||||
|
||||
fn update(player: ona.Read(Player), actors: ona.Write(Actors), mapping: ona.Read(ona.act.Mapping)) !void {
|
||||
|
|
349
src/ona/gfx.zig
349
src/ona/gfx.zig
|
@ -1,40 +1,89 @@
|
|||
const App = @import("./App.zig");
|
||||
|
||||
pub const colors = @import("./gfx/colors.zig");
|
||||
|
||||
const coral = @import("coral");
|
||||
|
||||
const device = @import("./gfx/device.zig");
|
||||
const bmp = @import("./gfx/bmp.zig");
|
||||
|
||||
const ext = @import("./ext.zig");
|
||||
|
||||
const flow = @import("flow");
|
||||
|
||||
const formats = @import("./gfx/formats.zig");
|
||||
const handles = @import("./gfx/handles.zig");
|
||||
|
||||
pub const lina = @import("./gfx/lina.zig");
|
||||
|
||||
const msg = @import("./msg.zig");
|
||||
|
||||
const spirv = @import("./gfx/spirv.zig");
|
||||
|
||||
const rendering = @import("./gfx/rendering.zig");
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
pub const AssetFormat = struct {
|
||||
extension: []const u8,
|
||||
file_desc: *const FileDesc,
|
||||
pub const Assets = struct {
|
||||
window: *ext.SDL_Window,
|
||||
texture_formats: coral.stack.Sequential(TextureFormat),
|
||||
staging_arena: std.heap.ArenaAllocator,
|
||||
frame_rendered: std.Thread.ResetEvent = .{},
|
||||
|
||||
pub const Error = std.mem.Allocator.Error || coral.files.Error || error {
|
||||
pub const LoadError = std.mem.Allocator.Error;
|
||||
|
||||
pub const LoadFileError = LoadError || coral.files.AccessError || error {
|
||||
FormatUnsupported,
|
||||
};
|
||||
|
||||
pub const FileDesc = fn (*std.heap.ArenaAllocator, coral.files.Storage, []const u8) Error!Desc;
|
||||
};
|
||||
pub const TextureFormat = struct {
|
||||
extension: []const u8,
|
||||
load_file: *const fn (*std.heap.ArenaAllocator, coral.files.Storage, []const u8) LoadFileError!Texture.Desc,
|
||||
};
|
||||
|
||||
pub const Assets = struct {
|
||||
context: device.Context,
|
||||
formats: coral.stack.Sequential(AssetFormat),
|
||||
staging_arena: std.heap.ArenaAllocator,
|
||||
fn deinit(self: *Assets) void {
|
||||
rendering.enqueue_work(.shutdown);
|
||||
self.staging_arena.deinit();
|
||||
self.texture_formats.deinit();
|
||||
}
|
||||
|
||||
pub fn create_from_file(
|
||||
self: *Assets,
|
||||
storage: coral.files.Storage,
|
||||
path: []const u8,
|
||||
) (OpenError || AssetFormat.Error)!*Handle {
|
||||
fn init() !Assets {
|
||||
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);
|
||||
}
|
||||
|
||||
try rendering.startup(window);
|
||||
|
||||
return .{
|
||||
.staging_arena = std.heap.ArenaAllocator.init(coral.heap.allocator),
|
||||
.texture_formats = .{.allocator = coral.heap.allocator},
|
||||
.window = window,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn load_texture(_: *Assets, desc: Texture.Desc) std.mem.Allocator.Error!Texture {
|
||||
var loaded = coral.asyncio.Future(std.mem.Allocator.Error!Texture){};
|
||||
|
||||
rendering.enqueue_work(.{
|
||||
.load_texture = .{
|
||||
.desc = desc,
|
||||
.loaded = &loaded,
|
||||
},
|
||||
});
|
||||
|
||||
return loaded.get().*;
|
||||
}
|
||||
|
||||
pub fn load_texture_file(self: *Assets, storage: coral.files.Storage, path: []const u8) LoadFileError!Texture {
|
||||
defer {
|
||||
const max_cache_size = 536870912;
|
||||
|
||||
|
@ -43,107 +92,62 @@ pub const Assets = struct {
|
|||
}
|
||||
}
|
||||
|
||||
for (self.formats.values) |format| {
|
||||
for (self.texture_formats.values) |format| {
|
||||
if (!std.mem.endsWith(u8, path, format.extension)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return self.context.create(try format.file_desc(&self.staging_arena, storage, path));
|
||||
return self.load_texture(try format.load_file(&self.staging_arena, storage, path));
|
||||
}
|
||||
|
||||
return error.FormatUnsupported;
|
||||
}
|
||||
|
||||
pub fn create_quad_mesh_2d(self: *Assets, extents: Point2D) OpenError!*Handle {
|
||||
const width, const height = extents / @as(Point2D, @splat(2));
|
||||
|
||||
return self.context.create(.{
|
||||
.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 thread_restriction = .main;
|
||||
};
|
||||
|
||||
pub const Color = @Vector(4, f32);
|
||||
pub const Commands = struct {
|
||||
list: *rendering.Commands.List,
|
||||
|
||||
pub const Command = union (enum) {
|
||||
instance_2d: Instance2D,
|
||||
target: Target,
|
||||
|
||||
pub const Instance2D = struct {
|
||||
texture: *Handle,
|
||||
mesh_2d: *Handle,
|
||||
transform: Transform2D,
|
||||
pub const State = struct {
|
||||
commands: *rendering.Commands,
|
||||
};
|
||||
|
||||
pub const Target = struct {
|
||||
texture: ?*Handle = null,
|
||||
clear_color: ?Color,
|
||||
clear_depth: ?f32,
|
||||
};
|
||||
pub fn bind(_: flow.system.BindContext) std.mem.Allocator.Error!State {
|
||||
var created_commands = coral.asyncio.Future(std.mem.Allocator.Error!*rendering.Commands){};
|
||||
|
||||
rendering.enqueue_work(.{
|
||||
.create_commands = .{
|
||||
.created = &created_commands,
|
||||
},
|
||||
});
|
||||
|
||||
return .{
|
||||
.commands = try created_commands.get().*,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn init(state: *State) Commands {
|
||||
return .{
|
||||
.list = state.commands.pending_list(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn draw_texture(self: Commands, command: rendering.Command.DrawTexture) std.mem.Allocator.Error!void {
|
||||
try self.list.append(.{.draw_texture = command});
|
||||
}
|
||||
|
||||
pub fn set_target(self: Commands, command: rendering.Command.SetTarget) std.mem.Allocator.Error!void {
|
||||
try self.list.append(.{.set_target = command});
|
||||
}
|
||||
};
|
||||
|
||||
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 {
|
||||
format: Format,
|
||||
access: Access,
|
||||
|
||||
pub const Access = union (enum) {
|
||||
static: struct {
|
||||
width: u16,
|
||||
data: []const coral.io.Byte,
|
||||
},
|
||||
|
||||
render: struct {
|
||||
width: u16,
|
||||
height: u16,
|
||||
},
|
||||
};
|
||||
|
||||
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 Config = struct {
|
||||
width: u16 = 1280,
|
||||
height: u16 = 720,
|
||||
clear_color: Color = colors.black,
|
||||
clear_color: lina.Color = colors.black,
|
||||
};
|
||||
|
||||
pub const Handle = opaque {};
|
||||
|
||||
pub const Input = union (enum) {
|
||||
key_up: Key,
|
||||
key_down: Key,
|
||||
|
@ -160,57 +164,6 @@ pub const Input = union (enum) {
|
|||
};
|
||||
};
|
||||
|
||||
pub const OpenError = std.mem.Allocator.Error || error {
|
||||
|
||||
};
|
||||
|
||||
pub const Point2D = @Vector(2, f32);
|
||||
|
||||
pub const Queue = struct {
|
||||
commands: *device.RenderList,
|
||||
|
||||
pub const State = struct {
|
||||
command_index: usize,
|
||||
};
|
||||
|
||||
pub fn bind(_: flow.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 Rect = struct {
|
||||
left: f32,
|
||||
top: f32,
|
||||
|
@ -218,32 +171,7 @@ pub const Rect = struct {
|
|||
bottom: f32,
|
||||
};
|
||||
|
||||
pub const Transform2D = extern struct {
|
||||
xbasis: Point2D = .{1, 0},
|
||||
ybasis: Point2D = .{0, 1},
|
||||
origin: Point2D = @splat(0),
|
||||
};
|
||||
|
||||
const builtin_formats = [_]AssetFormat{
|
||||
.{
|
||||
.extension = "bmp",
|
||||
.file_desc = formats.bmp_file_desc,
|
||||
},
|
||||
};
|
||||
|
||||
pub const colors = struct {
|
||||
pub const black = greyscale(0);
|
||||
|
||||
pub const white = greyscale(1);
|
||||
|
||||
pub fn greyscale(v: f32) Color {
|
||||
return .{v, v, v, 1};
|
||||
}
|
||||
|
||||
pub fn rgb(r: f32, g: f32, b: f32) Color {
|
||||
return .{r, g, b, 1};
|
||||
}
|
||||
};
|
||||
pub const Texture = handles.Texture;
|
||||
|
||||
pub fn poll(app: flow.Write(App), inputs: msg.Send(Input)) !void {
|
||||
var event = @as(ext.SDL_Event, undefined);
|
||||
|
@ -263,40 +191,71 @@ pub fn setup(world: *flow.World, events: App.Events) (error {Unsupported} || std
|
|||
return error.Unsupported;
|
||||
}
|
||||
|
||||
var context = try device.Context.init();
|
||||
const assets = create: {
|
||||
var assets = try Assets.init();
|
||||
|
||||
errdefer context.deinit();
|
||||
errdefer {
|
||||
assets.deinit();
|
||||
}
|
||||
|
||||
var registered_formats = coral.stack.Sequential(AssetFormat){.allocator = coral.heap.allocator};
|
||||
break: create try world.set_get_resource(assets);
|
||||
};
|
||||
|
||||
errdefer registered_formats.deinit();
|
||||
assets.frame_rendered.set();
|
||||
|
||||
try registered_formats.grow(builtin_formats.len);
|
||||
std.debug.assert(registered_formats.push_all(&builtin_formats));
|
||||
errdefer {
|
||||
assets.deinit();
|
||||
}
|
||||
|
||||
try world.set_resource(Assets{
|
||||
.staging_arena = std.heap.ArenaAllocator.init(coral.heap.allocator),
|
||||
.formats = registered_formats,
|
||||
.context = context,
|
||||
});
|
||||
const builtin_texture_formats = [_]Assets.TextureFormat{
|
||||
.{
|
||||
.extension = "bmp",
|
||||
.load_file = bmp.load_file,
|
||||
},
|
||||
};
|
||||
|
||||
try world.set_resource(Display{});
|
||||
for (builtin_texture_formats) |format| {
|
||||
try assets.texture_formats.push_grow(format);
|
||||
}
|
||||
|
||||
try world.set_resource(Config{});
|
||||
try world.on_event(events.pre_update, flow.system_fn(poll), .{.label = "poll gfx"});
|
||||
try world.on_event(events.exit, flow.system_fn(stop), .{.label = "stop gfx"});
|
||||
try world.on_event(events.finish, flow.system_fn(synchronize), .{.label = "synchronize gfx"});
|
||||
}
|
||||
|
||||
pub fn stop(assets: flow.Write(Assets)) void {
|
||||
assets.res.staging_arena.deinit();
|
||||
assets.res.formats.deinit();
|
||||
assets.res.context.deinit();
|
||||
assets.res.deinit();
|
||||
}
|
||||
|
||||
pub fn synchronize(assets: flow.Write(Assets), display: flow.Read(Display)) !void {
|
||||
assets.res.context.submit(.{
|
||||
.width = display.res.width,
|
||||
.height = display.res.height,
|
||||
.clear_color = display.res.clear_color,
|
||||
.renders = Queue.renders.values,
|
||||
pub fn synchronize(assets: flow.Write(Assets), config: flow.Read(Config)) !void {
|
||||
assets.res.frame_rendered.wait();
|
||||
assets.res.frame_rendered.reset();
|
||||
|
||||
var commands_swapped = std.Thread.ResetEvent{};
|
||||
|
||||
rendering.enqueue_work(.{
|
||||
.rotate_commands = .{
|
||||
.finished = &commands_swapped,
|
||||
},
|
||||
});
|
||||
|
||||
var display_width, var display_height = [_]c_int{0, 0};
|
||||
|
||||
ext.SDL_GL_GetDrawableSize(assets.res.window, &display_width, &display_height);
|
||||
|
||||
if (config.res.width != display_width or config.res.height != display_height) {
|
||||
ext.SDL_SetWindowSize(assets.res.window, config.res.width, config.res.height);
|
||||
}
|
||||
|
||||
commands_swapped.wait();
|
||||
|
||||
rendering.enqueue_work(.{
|
||||
.render_frame = .{
|
||||
.width = config.res.width,
|
||||
.height = config.res.height,
|
||||
.clear_color = config.res.clear_color,
|
||||
.finished = &assets.res.frame_rendered,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
const coral = @import("coral");
|
||||
|
||||
const gfx = @import("../gfx.zig");
|
||||
const handles = @import("./handles.zig");
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
pub fn bmp_file_desc(
|
||||
arena: *std.heap.ArenaAllocator,
|
||||
storage: coral.files.Storage,
|
||||
path: []const u8,
|
||||
) gfx.AssetFormat.Error!gfx.Desc {
|
||||
pub fn load_file(arena: *std.heap.ArenaAllocator, storage: coral.files.Storage, path: []const u8) !handles.Texture.Desc {
|
||||
const header = try storage.read_little(path, 0, extern struct {
|
||||
type: [2]u8 align (1),
|
||||
file_size: u32 align (1),
|
||||
|
@ -51,7 +47,7 @@ pub fn bmp_file_desc(
|
|||
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) {
|
||||
if (try storage.read(path, line, file_offset) != byte_stride) {
|
||||
return error.FormatUnsupported;
|
||||
}
|
||||
|
||||
|
@ -71,15 +67,13 @@ pub fn bmp_file_desc(
|
|||
}
|
||||
|
||||
return .{
|
||||
.texture = .{
|
||||
.format = .rgba8,
|
||||
.format = .rgba8,
|
||||
|
||||
.access = .{
|
||||
.static = .{
|
||||
.width = pixel_width,
|
||||
.data = pixels,
|
||||
},
|
||||
.access = .{
|
||||
.static = .{
|
||||
.width = pixel_width,
|
||||
.data = pixels,
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
const lina = @import("./lina.zig");
|
||||
|
||||
pub const black = greyscale(0);
|
||||
|
||||
pub const white = greyscale(1);
|
||||
|
||||
pub fn greyscale(v: f32) lina.Color {
|
||||
return .{v, v, v, 1};
|
||||
}
|
||||
|
||||
pub fn rgb(r: f32, g: f32, b: f32) lina.Color {
|
||||
return .{r, g, b, 1};
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
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},
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -1,847 +0,0 @@
|
|||
const commands = @import("./commands.zig");
|
||||
|
||||
const coral = @import("coral");
|
||||
|
||||
const ext = @import("../ext.zig");
|
||||
|
||||
const gfx = @import("../gfx.zig");
|
||||
|
||||
const sokol = @import("sokol");
|
||||
|
||||
const spirv = @import("./spirv.zig");
|
||||
|
||||
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 close_commands = self.loop.closes.pending();
|
||||
|
||||
std.debug.assert(close_commands.stack.cap > close_commands.stack.len());
|
||||
close_commands.append(handle) catch unreachable;
|
||||
}
|
||||
|
||||
pub fn create(self: *Context, desc: gfx.Desc) gfx.OpenError!*gfx.Handle {
|
||||
const resource = try coral.heap.allocator.create(Resource);
|
||||
|
||||
errdefer coral.heap.allocator.destroy(resource);
|
||||
|
||||
try self.loop.creations.pending().append(.{
|
||||
.resource = resource,
|
||||
.desc = desc,
|
||||
});
|
||||
|
||||
const pending_destroys = self.loop.destroys.pending();
|
||||
|
||||
if (pending_destroys.stack.len() == pending_destroys.stack.cap) {
|
||||
try pending_destroys.stack.grow(1);
|
||||
}
|
||||
|
||||
return @ptrCast(resource);
|
||||
}
|
||||
|
||||
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 submit(self: *Context, submission: Submission) void {
|
||||
self.loop.finished.wait();
|
||||
|
||||
defer self.loop.ready.post();
|
||||
|
||||
for (submission.renders) |*render| {
|
||||
render.swap();
|
||||
}
|
||||
|
||||
self.loop.creations.swap();
|
||||
self.loop.destroys.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 Frame = struct {
|
||||
projection: Projection = .{@splat(0), @splat(0), @splat(0), @splat(0)},
|
||||
flushed_batch_count: usize = 0,
|
||||
pushed_batch_count: usize = 0,
|
||||
render_passes: usize = 0,
|
||||
mesh: ?*gfx.Handle = null,
|
||||
texture: ?*gfx.Handle = null,
|
||||
material: ?*gfx.Handle = null,
|
||||
};
|
||||
|
||||
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 = &.{},
|
||||
destroys: DestroyChain = DestroyChain.init(coral.heap.allocator),
|
||||
creations: CreateChain = CreateChain.init(coral.heap.allocator),
|
||||
has_resource_head: ?*Resource = null,
|
||||
has_resource_tail: ?*Resource = null,
|
||||
|
||||
const AtomicBool = std.atomic.Value(bool);
|
||||
|
||||
const CreateCommand = struct {
|
||||
resource: *Resource,
|
||||
desc: gfx.Desc,
|
||||
|
||||
fn clone(command: CreateCommand, arena: *std.heap.ArenaAllocator) std.mem.Allocator.Error!CreateCommand {
|
||||
const allocator = arena.allocator();
|
||||
|
||||
return .{
|
||||
.desc = switch (command.desc) {
|
||||
.texture => |texture| .{
|
||||
.texture = .{
|
||||
.access = switch (texture.access) {
|
||||
.static => |static| .{
|
||||
.static = .{
|
||||
.data = try allocator.dupe(coral.io.Byte, static.data),
|
||||
.width = static.width,
|
||||
},
|
||||
},
|
||||
|
||||
.render => |render| .{
|
||||
.render = .{
|
||||
.width = render.width,
|
||||
.height = render.height,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
.format = texture.format,
|
||||
},
|
||||
},
|
||||
|
||||
.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),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
.resource = command.resource,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const CreateChain = commands.Chain(CreateCommand, CreateCommand.clone);
|
||||
|
||||
const DestroyChain = commands.Chain(*Resource, null);
|
||||
|
||||
fn consume_creations(self: *Loop) std.mem.Allocator.Error!void {
|
||||
const create_commands = self.creations.submitted();
|
||||
|
||||
defer create_commands.clear();
|
||||
|
||||
for (create_commands.stack.values) |command| {
|
||||
errdefer coral.heap.allocator.destroy(command.resource);
|
||||
|
||||
command.resource.* = .{
|
||||
.payload = switch (command.desc) {
|
||||
.texture => |texture| init: {
|
||||
const pixel_format = switch (texture.format) {
|
||||
.rgba8 => sokol.gfx.PixelFormat.RGBA8,
|
||||
.bgra8 => sokol.gfx.PixelFormat.BGRA8,
|
||||
};
|
||||
|
||||
switch (texture.access) {
|
||||
.static => |static| {
|
||||
break: init .{
|
||||
.texture = .{
|
||||
.image = sokol.gfx.makeImage(.{
|
||||
.width = static.width,
|
||||
.height = @intCast(static.data.len / (static.width * texture.format.byte_size())),
|
||||
.pixel_format = pixel_format,
|
||||
|
||||
.data = .{
|
||||
.subimage = get: {
|
||||
var subimage = [_][16]sokol.gfx.Range{.{.{}} ** 16} ** 6;
|
||||
|
||||
subimage[0][0] = sokol.gfx.asRange(static.data);
|
||||
|
||||
break: get subimage;
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
.sampler = sokol.gfx.makeSampler(.{}),
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
.render => |render| {
|
||||
const color_image = sokol.gfx.makeImage(.{
|
||||
.width = render.width,
|
||||
.height = render.height,
|
||||
.render_target = true,
|
||||
.pixel_format = pixel_format,
|
||||
});
|
||||
|
||||
const depth_image = sokol.gfx.makeImage(.{
|
||||
.width = render.width,
|
||||
.height = render.height,
|
||||
.render_target = true,
|
||||
.pixel_format = .DEPTH_STENCIL,
|
||||
});
|
||||
|
||||
break: init .{
|
||||
.render_target = .{
|
||||
.attachments = sokol.gfx.makeAttachments(.{
|
||||
.colors = get: {
|
||||
var attachments = [_]sokol.gfx.AttachmentDesc{.{}} ** 4;
|
||||
|
||||
attachments[0] = .{
|
||||
.image = color_image,
|
||||
};
|
||||
|
||||
break: get attachments;
|
||||
},
|
||||
|
||||
.depth_stencil = .{
|
||||
.image = depth_image,
|
||||
},
|
||||
}),
|
||||
|
||||
.sampler = sokol.gfx.makeSampler(.{}),
|
||||
.color_image = color_image,
|
||||
.depth_image = depth_image,
|
||||
.width = render.width,
|
||||
.height = render.height,
|
||||
},
|
||||
};
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
.mesh_2d => |mesh_2d| init: {
|
||||
if (mesh_2d.indices.len > std.math.maxInt(u32)) {
|
||||
return error.OutOfMemory;
|
||||
}
|
||||
|
||||
break: init .{
|
||||
.mesh_2d = .{
|
||||
.index_buffer = sokol.gfx.makeBuffer(.{
|
||||
.data = sokol.gfx.asRange(mesh_2d.indices),
|
||||
.type = .INDEXBUFFER,
|
||||
}),
|
||||
|
||||
.vertex_buffer = sokol.gfx.makeBuffer(.{
|
||||
.data = sokol.gfx.asRange(mesh_2d.vertices),
|
||||
.type = .VERTEXBUFFER,
|
||||
}),
|
||||
|
||||
.index_count = @intCast(mesh_2d.indices.len),
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
.has_prev = self.has_resource_tail,
|
||||
.has_next = null,
|
||||
};
|
||||
|
||||
if (self.has_resource_tail) |resource_tail| {
|
||||
resource_tail.has_next = command.resource;
|
||||
} else {
|
||||
std.debug.assert(self.has_resource_head == null);
|
||||
|
||||
self.has_resource_head = command.resource;
|
||||
}
|
||||
|
||||
self.has_resource_tail = command.resource;
|
||||
}
|
||||
}
|
||||
|
||||
fn consume_destroys(self: *Loop) std.mem.Allocator.Error!void {
|
||||
const destroy_commands = self.destroys.submitted();
|
||||
|
||||
defer destroy_commands.clear();
|
||||
|
||||
for (destroy_commands.stack.values) |resource| {
|
||||
defer coral.heap.allocator.destroy(resource);
|
||||
|
||||
resource.destroy();
|
||||
|
||||
if (resource.has_prev) |resource_prev| {
|
||||
resource_prev.has_next = resource.has_next;
|
||||
}
|
||||
|
||||
if (resource.has_next) |resource_next| {
|
||||
resource_next.has_prev = resource.has_prev;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn deinit(self: *Loop) void {
|
||||
self.destroys.deinit();
|
||||
self.creations.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_2d = try Rendering2D.init();
|
||||
|
||||
defer rendering_2d.deinit();
|
||||
|
||||
self.finished.post();
|
||||
|
||||
while (self.is_running.load(.monotonic)) {
|
||||
self.ready.wait();
|
||||
|
||||
defer self.finished.post();
|
||||
|
||||
try self.consume_creations();
|
||||
|
||||
var frame = Frame{};
|
||||
|
||||
rendering_2d.set_target(&frame, window, .{
|
||||
.clear_color = self.clear_color,
|
||||
.clear_depth = 0,
|
||||
});
|
||||
|
||||
if (self.renders.len != 0) {
|
||||
const renders_tail_index = self.renders.len - 1;
|
||||
|
||||
for (self.renders[0 .. renders_tail_index]) |*render| {
|
||||
const render_commands = render.submitted();
|
||||
|
||||
defer render_commands.clear();
|
||||
|
||||
for (render_commands.stack.values) |command| {
|
||||
try switch (command) {
|
||||
.instance_2d => |instance_2d| rendering_2d.batch(&frame, instance_2d),
|
||||
.target => |target| rendering_2d.set_target(&frame, window, target),
|
||||
};
|
||||
}
|
||||
|
||||
rendering_2d.set_target(&frame, window, .{
|
||||
.clear_color = null,
|
||||
.clear_depth = null,
|
||||
});
|
||||
}
|
||||
|
||||
const render_commands = self.renders[renders_tail_index].submitted();
|
||||
|
||||
defer render_commands.clear();
|
||||
|
||||
for (render_commands.stack.values) |command| {
|
||||
try switch (command) {
|
||||
.instance_2d => |instance_2d| rendering_2d.batch(&frame, instance_2d),
|
||||
.target => |target| rendering_2d.set_target(&frame, window, target),
|
||||
};
|
||||
}
|
||||
|
||||
rendering_2d.flush(&frame);
|
||||
}
|
||||
|
||||
sokol.gfx.endPass();
|
||||
sokol.gfx.commit();
|
||||
ext.SDL_GL_SwapWindow(window);
|
||||
|
||||
try self.consume_destroys();
|
||||
}
|
||||
|
||||
var has_next_resource = self.has_resource_head;
|
||||
|
||||
while (has_next_resource) |resource| {
|
||||
std.log.info("destroying remaining gfx device resource {x}", .{@intFromPtr(resource)});
|
||||
resource.destroy();
|
||||
|
||||
has_next_resource = resource.has_next;
|
||||
|
||||
coral.heap.allocator.destroy(resource);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const Projection = [4]@Vector(4, f32);
|
||||
|
||||
pub const RenderChain = commands.Chain(gfx.Command, clone_command);
|
||||
|
||||
pub const RenderList = commands.List(gfx.Command, clone_command);
|
||||
|
||||
const Rendering2D = struct {
|
||||
batching_pipeline: sokol.gfx.Pipeline,
|
||||
batching_buffers: coral.stack.Sequential(sokol.gfx.Buffer),
|
||||
|
||||
const Instance = 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,
|
||||
};
|
||||
|
||||
fn batch(self: *Rendering2D, frame: *Frame, instance: gfx.Command.Instance2D) std.mem.Allocator.Error!void {
|
||||
if (instance.mesh_2d != frame.mesh or instance.texture != frame.texture) {
|
||||
self.flush(frame);
|
||||
}
|
||||
|
||||
frame.mesh = instance.mesh_2d;
|
||||
frame.texture = instance.texture;
|
||||
|
||||
const has_filled_buffer = (frame.pushed_batch_count % instances_per_buffer) == 0;
|
||||
const pushed_buffer_count = frame.pushed_batch_count / instances_per_buffer;
|
||||
|
||||
if (has_filled_buffer and pushed_buffer_count == self.batching_buffers.len()) {
|
||||
var name_buffer = [_:0]u8{0} ** 64;
|
||||
|
||||
const name_view = std.fmt.bufPrint(&name_buffer, "instance 2d buffer #{}", .{self.batching_buffers.len()}) catch {
|
||||
unreachable;
|
||||
};
|
||||
|
||||
const instance_buffer = sokol.gfx.makeBuffer(.{
|
||||
.label = name_view.ptr,
|
||||
.size = @sizeOf(Instance) * instances_per_buffer,
|
||||
.usage = .STREAM,
|
||||
});
|
||||
|
||||
errdefer sokol.gfx.destroyBuffer(instance_buffer);
|
||||
|
||||
try self.batching_buffers.push_grow(instance_buffer);
|
||||
}
|
||||
|
||||
_ = sokol.gfx.appendBuffer(self.batching_buffers.get().?, sokol.gfx.asRange(&Instance{
|
||||
.transform = instance.transform,
|
||||
}));
|
||||
|
||||
frame.pushed_batch_count += 1;
|
||||
}
|
||||
|
||||
fn deinit(self: *Rendering2D) void {
|
||||
for (self.batching_buffers.values) |buffer| {
|
||||
sokol.gfx.destroyBuffer(buffer);
|
||||
}
|
||||
|
||||
self.batching_buffers.deinit();
|
||||
sokol.gfx.destroyPipeline(self.batching_pipeline);
|
||||
}
|
||||
|
||||
fn flush(self: *Rendering2D, frame: *Frame) void {
|
||||
const unflushed_count = frame.pushed_batch_count - frame.flushed_batch_count;
|
||||
|
||||
if (unflushed_count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
sokol.gfx.applyPipeline(self.batching_pipeline);
|
||||
sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&frame.projection));
|
||||
|
||||
const mesh_2d = resource_cast(frame.mesh.?).payload.mesh_2d;
|
||||
|
||||
const texture_image, const texture_sampler = switch (resource_cast(frame.texture.?).payload) {
|
||||
.texture => |texture| .{texture.image, texture.sampler},
|
||||
.render_target => |render_target| .{render_target.color_image, render_target.sampler},
|
||||
else => unreachable,
|
||||
};
|
||||
|
||||
var bindings = sokol.gfx.Bindings{
|
||||
.vertex_buffers = get: {
|
||||
var buffers = [_]sokol.gfx.Buffer{.{}} ** 8;
|
||||
|
||||
buffers[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;
|
||||
},
|
||||
},
|
||||
|
||||
.vs = .{
|
||||
.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_batch_count < frame.pushed_batch_count) {
|
||||
const buffer_index = frame.flushed_batch_count / instances_per_buffer;
|
||||
const buffer_offset = frame.flushed_batch_count % instances_per_buffer;
|
||||
const instances_to_flush = @min(instances_per_buffer - buffer_offset, unflushed_count);
|
||||
|
||||
bindings.vertex_buffers[buffer_indices.instance] = self.batching_buffers.values[buffer_index];
|
||||
bindings.vertex_buffer_offsets[buffer_indices.instance] = @intCast(@sizeOf(Instance) * buffer_offset);
|
||||
|
||||
sokol.gfx.applyBindings(bindings);
|
||||
sokol.gfx.draw(0, mesh_2d.index_count, @intCast(instances_to_flush));
|
||||
|
||||
frame.flushed_batch_count += instances_to_flush;
|
||||
}
|
||||
}
|
||||
|
||||
fn init() spirv.Error!Rendering2D {
|
||||
sokol.gfx.setup(.{
|
||||
.environment = .{
|
||||
.defaults = .{
|
||||
.color_format = .RGBA8,
|
||||
.depth_format = .DEPTH_STENCIL,
|
||||
.sample_count = 1,
|
||||
},
|
||||
},
|
||||
|
||||
.logger = .{
|
||||
.func = sokol.log.func,
|
||||
},
|
||||
});
|
||||
|
||||
var spirv_unit = try spirv.Unit.init();
|
||||
|
||||
defer spirv_unit.deinit();
|
||||
|
||||
const shader_spirv = spirv.embed_shader("./shaders/render_2d.spv");
|
||||
|
||||
try spirv_unit.compile(shader_spirv, .vertex);
|
||||
|
||||
std.log.info("{s}\n", .{spirv_unit.shader_desc.vs.source});
|
||||
|
||||
try spirv_unit.compile(shader_spirv, .fragment);
|
||||
|
||||
std.log.info("{s}\n", .{spirv_unit.shader_desc.fs.source});
|
||||
|
||||
return .{
|
||||
.batching_pipeline = sokol.gfx.makePipeline(.{
|
||||
.label = "2D drawing pipeline",
|
||||
|
||||
.layout = .{
|
||||
.attrs = get: {
|
||||
var attrs = [_]sokol.gfx.VertexAttrState{.{}} ** 16;
|
||||
|
||||
attrs[0] = .{
|
||||
.format = .FLOAT2,
|
||||
.buffer_index = buffer_indices.mesh,
|
||||
};
|
||||
|
||||
attrs[1] = .{
|
||||
.format = .FLOAT2,
|
||||
.buffer_index = buffer_indices.mesh,
|
||||
};
|
||||
|
||||
attrs[2] = .{
|
||||
.format = .FLOAT2,
|
||||
.buffer_index = buffer_indices.instance,
|
||||
};
|
||||
|
||||
attrs[3] = .{
|
||||
.format = .FLOAT2,
|
||||
.buffer_index = buffer_indices.instance,
|
||||
};
|
||||
|
||||
attrs[4] = .{
|
||||
.format = .FLOAT2,
|
||||
.buffer_index = buffer_indices.instance,
|
||||
};
|
||||
|
||||
attrs[5] = .{
|
||||
.format = .UBYTE4N,
|
||||
.buffer_index = buffer_indices.instance,
|
||||
};
|
||||
|
||||
attrs[6] = .{
|
||||
.format = .FLOAT,
|
||||
.buffer_index = buffer_indices.instance,
|
||||
};
|
||||
|
||||
attrs[7] = .{
|
||||
.format = .FLOAT4,
|
||||
.buffer_index = buffer_indices.instance,
|
||||
};
|
||||
|
||||
break: get attrs;
|
||||
},
|
||||
|
||||
.buffers = get: {
|
||||
var buffers = [_]sokol.gfx.VertexBufferLayoutState{.{}} ** 8;
|
||||
|
||||
buffers[buffer_indices.instance].step_func = .PER_INSTANCE;
|
||||
|
||||
break: get buffers;
|
||||
},
|
||||
},
|
||||
|
||||
.shader = sokol.gfx.makeShader(spirv_unit.shader_desc),
|
||||
.index_type = .UINT16,
|
||||
}),
|
||||
|
||||
.batching_buffers = .{.allocator = coral.heap.allocator},
|
||||
};
|
||||
}
|
||||
|
||||
const instances_per_buffer = 512;
|
||||
|
||||
fn set_target(self: *Rendering2D, frame: *Frame, window: *ext.SDL_Window, target: gfx.Command.Target) void {
|
||||
defer frame.render_passes += 1;
|
||||
|
||||
if (frame.render_passes != 0) {
|
||||
self.flush(frame);
|
||||
sokol.gfx.endPass();
|
||||
}
|
||||
|
||||
var pass = sokol.gfx.Pass{
|
||||
.action = .{
|
||||
.stencil = .{.load_action = .CLEAR},
|
||||
},
|
||||
};
|
||||
|
||||
if (target.clear_color) |color| {
|
||||
pass.action.colors[0] = .{
|
||||
.load_action = .CLEAR,
|
||||
.clear_value = @bitCast(color),
|
||||
};
|
||||
} else {
|
||||
pass.action.colors[0] = .{.load_action = .LOAD};
|
||||
}
|
||||
|
||||
if (target.clear_depth) |depth| {
|
||||
pass.action.depth = .{
|
||||
.load_action = .CLEAR,
|
||||
.clear_value = depth,
|
||||
};
|
||||
} else {
|
||||
pass.action.depth = .{.load_action = .LOAD};
|
||||
}
|
||||
|
||||
if (target.texture) |render_texture| {
|
||||
const render_target = resource_cast(render_texture).payload.render_target;
|
||||
|
||||
pass.attachments = render_target.attachments;
|
||||
|
||||
frame.projection = orthographic_projection(-1.0, 1.0, .{
|
||||
.left = 0,
|
||||
.top = 0,
|
||||
.right = @floatFromInt(render_target.width),
|
||||
.bottom = @floatFromInt(render_target.height),
|
||||
});
|
||||
} else {
|
||||
var target_width, var target_height = [_]c_int{0, 0};
|
||||
|
||||
ext.SDL_GL_GetDrawableSize(window, &target_width, &target_height);
|
||||
std.debug.assert(target_width > 0 and target_height > 0);
|
||||
|
||||
pass.swapchain = .{
|
||||
.width = target_width,
|
||||
.height = target_height,
|
||||
.sample_count = 1,
|
||||
.color_format = .RGBA8,
|
||||
.depth_format = .DEPTH_STENCIL,
|
||||
.gl = .{.framebuffer = 0},
|
||||
};
|
||||
|
||||
frame.projection = orthographic_projection(-1.0, 1.0, .{
|
||||
.left = 0,
|
||||
.top = 0,
|
||||
.right = @floatFromInt(target_width),
|
||||
.bottom = @floatFromInt(target_height),
|
||||
});
|
||||
}
|
||||
|
||||
sokol.gfx.beginPass(pass);
|
||||
}
|
||||
};
|
||||
|
||||
const Resource = struct {
|
||||
has_prev: ?*Resource,
|
||||
has_next: ?*Resource,
|
||||
|
||||
payload: union (enum) {
|
||||
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,
|
||||
},
|
||||
|
||||
render_target: struct {
|
||||
sampler: sokol.gfx.Sampler,
|
||||
color_image: sokol.gfx.Image,
|
||||
depth_image: sokol.gfx.Image,
|
||||
attachments: sokol.gfx.Attachments,
|
||||
width: u16,
|
||||
height: u16,
|
||||
},
|
||||
},
|
||||
|
||||
fn destroy(self: Resource) void {
|
||||
switch (self.payload) {
|
||||
.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);
|
||||
},
|
||||
|
||||
.render_target => |render_target| {
|
||||
sokol.gfx.destroyImage(render_target.color_image);
|
||||
sokol.gfx.destroyImage(render_target.depth_image);
|
||||
sokol.gfx.destroySampler(render_target.sampler);
|
||||
sokol.gfx.destroyAttachments(render_target.attachments);
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fn clone_command(self: gfx.Command, arena: *std.heap.ArenaAllocator) std.mem.Allocator.Error!gfx.Command {
|
||||
_ = arena;
|
||||
|
||||
return switch (self) {
|
||||
.instance_2d => |instance_2d| .{.instance_2d = instance_2d},
|
||||
.target => |target| .{.target = target},
|
||||
};
|
||||
}
|
||||
|
||||
fn orthographic_projection(near: f32, far: f32, viewport: gfx.Rect) Projection {
|
||||
const width = viewport.right - viewport.left;
|
||||
const height = viewport.bottom - viewport.top;
|
||||
|
||||
return .{
|
||||
.{2 / width, 0, 0, 0},
|
||||
.{0, 2 / height, 0, 0},
|
||||
.{0, 0, 1 / (far - near), 0},
|
||||
.{-((viewport.left + viewport.right) / width), -((viewport.top + viewport.bottom) / height), near / (near - far), 1},
|
||||
};
|
||||
}
|
||||
|
||||
fn resource_cast(handle: *gfx.Handle) *Resource {
|
||||
return @ptrCast(@alignCast(handle));
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
const coral = @import("coral");
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
fn Handle(comptime HandleDesc: type) type {
|
||||
return enum (u32) {
|
||||
default,
|
||||
_,
|
||||
|
||||
pub const Desc = HandleDesc;
|
||||
|
||||
const Self = @This();
|
||||
};
|
||||
}
|
||||
|
||||
pub const Texture = Handle(struct {
|
||||
format: Format,
|
||||
access: Access,
|
||||
|
||||
pub const Access = union (enum) {
|
||||
static: Static,
|
||||
render: Render,
|
||||
|
||||
pub const Static = struct {
|
||||
width: u16,
|
||||
data: []const coral.io.Byte,
|
||||
};
|
||||
|
||||
pub const Render = struct {
|
||||
width: u16,
|
||||
height: u16,
|
||||
};
|
||||
};
|
||||
|
||||
pub const Format = enum {
|
||||
rgba8,
|
||||
bgra8,
|
||||
|
||||
pub fn byte_size(self: Format) usize {
|
||||
return switch (self) {
|
||||
.rgba8, .bgra8 => 4,
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
|
@ -0,0 +1,29 @@
|
|||
const gfx = @import("../gfx.zig");
|
||||
|
||||
pub const Color = @Vector(4, f32);
|
||||
|
||||
pub fn EvenOrderMatrix(comptime n: usize, comptime Element: type) type {
|
||||
return [n]@Vector(n, Element);
|
||||
}
|
||||
|
||||
pub const Transform2D = extern struct {
|
||||
xbasis: Vector = .{1, 0},
|
||||
ybasis: Vector = .{0, 1},
|
||||
origin: Vector = @splat(0),
|
||||
|
||||
const Vector = @Vector(2, f32);
|
||||
};
|
||||
|
||||
pub const ProjectionMatrix = EvenOrderMatrix(4, f32);
|
||||
|
||||
pub fn orthographic_projection(near: f32, far: f32, viewport: gfx.Rect) EvenOrderMatrix(4, f32) {
|
||||
const width = viewport.right - viewport.left;
|
||||
const height = viewport.bottom - viewport.top;
|
||||
|
||||
return .{
|
||||
.{2 / width, 0, 0, 0},
|
||||
.{0, 2 / height, 0, 0},
|
||||
.{0, 0, 1 / (far - near), 0},
|
||||
.{-((viewport.left + viewport.right) / width), -((viewport.top + viewport.bottom) / height), near / (near - far), 1},
|
||||
};
|
||||
}
|
|
@ -0,0 +1,662 @@
|
|||
const coral = @import("coral");
|
||||
|
||||
const ext = @import("../ext.zig");
|
||||
|
||||
const handles = @import("./handles.zig");
|
||||
|
||||
const lina = @import("./lina.zig");
|
||||
|
||||
const resources = @import("./resources.zig");
|
||||
|
||||
const spirv = @import("./spirv.zig");
|
||||
|
||||
const sokol = @import("sokol");
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
pub const Command = union (enum) {
|
||||
draw_texture: DrawTexture,
|
||||
set_target: SetTarget,
|
||||
|
||||
pub const DrawTexture = struct {
|
||||
texture: handles.Texture,
|
||||
transform: lina.Transform2D,
|
||||
};
|
||||
|
||||
pub const SetTarget = struct {
|
||||
texture: ?handles.Texture = null,
|
||||
clear_color: ?lina.Color,
|
||||
clear_depth: ?f32,
|
||||
clear_stencil: ?u8,
|
||||
};
|
||||
|
||||
fn clone(self: Command, arena: *std.heap.ArenaAllocator) !Command {
|
||||
_ = arena;
|
||||
|
||||
return switch (self) {
|
||||
.draw_texture => |draw_texture| .{.draw_texture = draw_texture},
|
||||
.set_target => |set_target| .{.set_target = set_target},
|
||||
};
|
||||
}
|
||||
|
||||
fn process(self: Command, pools: *Pools, frame: *Frame) !void {
|
||||
try switch (self) {
|
||||
.draw_texture => |draw_texture| frame.draw_texture(pools, draw_texture),
|
||||
.set_target => |set_target| frame.set_target(pools, set_target),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Commands = struct {
|
||||
swap_lists: [2]List,
|
||||
swap_state: u1 = 0,
|
||||
has_next: ?*Commands = null,
|
||||
has_prev: ?*Commands = null,
|
||||
|
||||
pub const List = struct {
|
||||
arena: std.heap.ArenaAllocator,
|
||||
stack: coral.stack.Sequential(Command),
|
||||
|
||||
pub fn append(self: *List, command: Command) std.mem.Allocator.Error!void {
|
||||
return self.stack.push_grow(try command.clone(&self.arena));
|
||||
}
|
||||
|
||||
pub fn clear(self: *List) 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", .{});
|
||||
}
|
||||
}
|
||||
|
||||
fn deinit(self: *List) void {
|
||||
self.arena.deinit();
|
||||
self.stack.deinit();
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
fn init(allocator: std.mem.Allocator) List {
|
||||
return .{
|
||||
.arena = std.heap.ArenaAllocator.init(allocator),
|
||||
.stack = .{.allocator = allocator},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
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 = .{List.init(allocator), List.init(allocator)},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn pending_list(self: *Self) *List {
|
||||
return &self.swap_lists[self.swap_state];
|
||||
}
|
||||
|
||||
fn rotate(self: *Self) void {
|
||||
const swapped_state = self.swap_state ^ 1;
|
||||
|
||||
self.swap_lists[swapped_state].clear();
|
||||
|
||||
self.swap_state = swapped_state;
|
||||
}
|
||||
|
||||
pub fn submitted_commands(self: *const Self) []const Command {
|
||||
return self.swap_lists[self.swap_state ^ 1].stack.values;
|
||||
}
|
||||
};
|
||||
|
||||
const Frame = struct {
|
||||
swapchain: sokol.gfx.Swapchain,
|
||||
drawn_count: usize = 0,
|
||||
flushed_count: usize = 0,
|
||||
current_source_texture: handles.Texture = .default,
|
||||
current_target_texture: ?handles.Texture = null,
|
||||
|
||||
const DrawTexture = extern struct {
|
||||
transform: lina.Transform2D,
|
||||
tint: @Vector(4, u8) = @splat(std.math.maxInt(u8)),
|
||||
depth: f32 = 0,
|
||||
texture_offset: @Vector(2, f32) = @splat(0),
|
||||
texture_size: @Vector(2, f32) = @splat(1),
|
||||
};
|
||||
|
||||
pub fn draw_texture(self: *Frame, pools: *Pools, command: Command.DrawTexture) !void {
|
||||
if (command.texture != self.current_source_texture) {
|
||||
self.flush(pools);
|
||||
}
|
||||
|
||||
self.current_source_texture = command.texture;
|
||||
|
||||
const has_filled_current_buffer = (self.drawn_count % batches_per_buffer) == 0;
|
||||
const buffer_count = self.drawn_count / batches_per_buffer;
|
||||
|
||||
if (has_filled_current_buffer and buffer_count == texture_batch_buffers.len()) {
|
||||
const instance_buffer = sokol.gfx.makeBuffer(.{
|
||||
.size = @sizeOf(DrawTexture) * batches_per_buffer,
|
||||
.usage = .STREAM,
|
||||
});
|
||||
|
||||
errdefer sokol.gfx.destroyBuffer(instance_buffer);
|
||||
|
||||
try texture_batch_buffers.push_grow(instance_buffer);
|
||||
}
|
||||
|
||||
_ = sokol.gfx.appendBuffer(texture_batch_buffers.get().?, sokol.gfx.asRange(&DrawTexture{
|
||||
.transform = command.transform,
|
||||
}));
|
||||
|
||||
self.drawn_count += 1;
|
||||
}
|
||||
|
||||
pub fn flush(self: *Frame, pools: *Pools) void {
|
||||
if (self.flushed_count == self.drawn_count) {
|
||||
return;
|
||||
}
|
||||
|
||||
var bindings = sokol.gfx.Bindings{
|
||||
.index_buffer = quad_index_buffer,
|
||||
};
|
||||
|
||||
bindings.vertex_buffers[vertex_indices.mesh] = quad_vertex_buffer;
|
||||
|
||||
switch (pools.textures.get(@intFromEnum(self.current_source_texture)).?.*) {
|
||||
.render => |render| {
|
||||
bindings.fs.images[0] = render.color_image;
|
||||
bindings.fs.samplers[0] = render.sampler;
|
||||
bindings.vs.images[0] = render.color_image;
|
||||
bindings.vs.samplers[0] = render.sampler;
|
||||
},
|
||||
|
||||
.static => |static| {
|
||||
bindings.fs.images[0] = static.image;
|
||||
bindings.fs.samplers[0] = static.sampler;
|
||||
bindings.vs.images[0] = static.image;
|
||||
bindings.vs.samplers[0] = static.sampler;
|
||||
},
|
||||
}
|
||||
|
||||
if (self.current_target_texture) |target_texture| {
|
||||
const target_view_buffer = pools.textures.get(@intFromEnum(target_texture)).?.render.view_buffer;
|
||||
|
||||
bindings.fs.storage_buffers[0] = target_view_buffer;
|
||||
bindings.vs.storage_buffers[0] = target_view_buffer;
|
||||
} else {
|
||||
bindings.fs.storage_buffers[0] = view_buffer;
|
||||
bindings.vs.storage_buffers[0] = view_buffer;
|
||||
}
|
||||
|
||||
sokol.gfx.applyPipeline(texture_batching_pipeline);
|
||||
|
||||
while (true) {
|
||||
const buffer_index = self.flushed_count / batches_per_buffer;
|
||||
const buffer_offset = self.flushed_count % batches_per_buffer;
|
||||
const instances_to_flush = @min(batches_per_buffer - buffer_offset, self.drawn_count - self.flushed_count);
|
||||
|
||||
self.flushed_count += instances_to_flush;
|
||||
bindings.vertex_buffers[vertex_indices.instance] = texture_batch_buffers.values[buffer_index];
|
||||
bindings.vertex_buffer_offsets[vertex_indices.instance] = @intCast(@sizeOf(DrawTexture) * buffer_offset);
|
||||
|
||||
sokol.gfx.applyBindings(bindings);
|
||||
sokol.gfx.draw(0, 6, @intCast(instances_to_flush));
|
||||
|
||||
if (self.flushed_count == self.drawn_count) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_target(self: *Frame, pools: *Pools, command: Command.SetTarget) void {
|
||||
sokol.gfx.endPass();
|
||||
|
||||
var pass = sokol.gfx.Pass{
|
||||
.action = .{.stencil = .{.load_action = .CLEAR}},
|
||||
};
|
||||
|
||||
if (command.clear_color) |color| {
|
||||
pass.action.colors[0] = .{
|
||||
.load_action = .CLEAR,
|
||||
.clear_value = @bitCast(color),
|
||||
};
|
||||
} else {
|
||||
pass.action.colors[0] = .{.load_action = .LOAD};
|
||||
}
|
||||
|
||||
if (command.clear_depth) |depth| {
|
||||
pass.action.depth = .{
|
||||
.load_action = .CLEAR,
|
||||
.clear_value = depth,
|
||||
};
|
||||
} else {
|
||||
pass.action.depth = .{.load_action = .LOAD};
|
||||
}
|
||||
|
||||
if (command.texture) |texture| {
|
||||
pass.attachments = switch (pools.textures.get(@intFromEnum(texture)).?.*) {
|
||||
.static => @panic("Cannot render to static textures"),
|
||||
.render => |render| render.attachments,
|
||||
};
|
||||
|
||||
self.current_target_texture = command.texture;
|
||||
} else {
|
||||
pass.swapchain = self.swapchain;
|
||||
self.current_target_texture = null;
|
||||
}
|
||||
|
||||
sokol.gfx.beginPass(pass);
|
||||
}
|
||||
|
||||
const storage_bindings = .{
|
||||
.engine = 0,
|
||||
.material = 1,
|
||||
};
|
||||
|
||||
var texture_batch_buffers = coral.stack.Sequential(sokol.gfx.Buffer){.allocator = coral.heap.allocator};
|
||||
|
||||
const batches_per_buffer = 512;
|
||||
|
||||
var texture_batching_pipeline: sokol.gfx.Pipeline = undefined;
|
||||
};
|
||||
|
||||
const Pools = struct {
|
||||
textures: TexturePool,
|
||||
|
||||
const TexturePool = coral.Pool(resources.Texture);
|
||||
|
||||
fn create_texture(self: *Pools, desc: handles.Texture.Desc) !handles.Texture {
|
||||
var texture = try resources.Texture.init(desc);
|
||||
|
||||
errdefer texture.deinit();
|
||||
|
||||
return @enumFromInt(try self.textures.insert(texture));
|
||||
}
|
||||
|
||||
fn deinit(self: *Pools) void {
|
||||
var textures = self.textures.values();
|
||||
|
||||
while (textures.next()) |texture| {
|
||||
texture.deinit();
|
||||
}
|
||||
|
||||
self.textures.deinit();
|
||||
}
|
||||
|
||||
fn destroy_texture(self: *Pools, texture_key: handles.Texture) bool {
|
||||
switch (texture_key) {
|
||||
.default => {},
|
||||
|
||||
else => {
|
||||
var texture = self.textures.remove(@intFromEnum(texture_key)) orelse {
|
||||
return false;
|
||||
};
|
||||
|
||||
texture.deinit();
|
||||
},
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fn init(allocator: std.mem.Allocator) !Pools {
|
||||
var pools = Pools{
|
||||
.textures = TexturePool.init(allocator),
|
||||
};
|
||||
|
||||
errdefer pools.deinit();
|
||||
|
||||
const default_texture_data = [_]u32{
|
||||
0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF,
|
||||
0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF,
|
||||
0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF,
|
||||
0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF,
|
||||
0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF,
|
||||
0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF,
|
||||
0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF,
|
||||
0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF,
|
||||
};
|
||||
|
||||
_ = try pools.create_texture(.{
|
||||
.format = .rgba8,
|
||||
|
||||
.access = .{
|
||||
.static = .{
|
||||
.data = std.mem.asBytes(&default_texture_data),
|
||||
.width = 8,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return pools;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Work = union (enum) {
|
||||
create_commands: CreateCommandsWork,
|
||||
load_texture: LoadTextureWork,
|
||||
render_frame: RenderFrameWork,
|
||||
rotate_commands: RotateCommandsWork,
|
||||
shutdown,
|
||||
unload_texture: UnloadTextureWork,
|
||||
|
||||
pub const CreateCommandsWork = struct {
|
||||
created: *coral.asyncio.Future(std.mem.Allocator.Error!*Commands),
|
||||
};
|
||||
|
||||
pub const LoadTextureWork = struct {
|
||||
desc: handles.Texture.Desc,
|
||||
loaded: *coral.asyncio.Future(std.mem.Allocator.Error!handles.Texture),
|
||||
};
|
||||
|
||||
pub const RotateCommandsWork = struct {
|
||||
finished: *std.Thread.ResetEvent,
|
||||
};
|
||||
|
||||
pub const RenderFrameWork = struct {
|
||||
clear_color: lina.Color,
|
||||
width: u16,
|
||||
height: u16,
|
||||
finished: *std.Thread.ResetEvent,
|
||||
};
|
||||
|
||||
pub const UnloadTextureWork = struct {
|
||||
handle: handles.Texture,
|
||||
};
|
||||
|
||||
var pending: coral.asyncio.BlockingQueue(1024, Work) = .{};
|
||||
};
|
||||
|
||||
pub fn enqueue_work(work: Work) void {
|
||||
Work.pending.enqueue(work);
|
||||
}
|
||||
|
||||
pub fn startup(window: *ext.SDL_Window) !void {
|
||||
work_thread = try std.Thread.spawn(.{}, run, .{window});
|
||||
}
|
||||
|
||||
var quad_index_buffer: sokol.gfx.Buffer = undefined;
|
||||
|
||||
var quad_vertex_buffer: sokol.gfx.Buffer = undefined;
|
||||
|
||||
var view_buffer: sokol.gfx.Buffer = undefined;
|
||||
|
||||
const vertex_indices = .{
|
||||
.mesh = 0,
|
||||
.instance = 1,
|
||||
};
|
||||
|
||||
const vertex_layout_state = sokol.gfx.VertexLayoutState{
|
||||
.attrs = get: {
|
||||
var attrs = [_]sokol.gfx.VertexAttrState{.{}} ** 16;
|
||||
|
||||
attrs[0] = .{
|
||||
.format = .FLOAT2,
|
||||
.buffer_index = vertex_indices.mesh,
|
||||
};
|
||||
|
||||
attrs[1] = .{
|
||||
.format = .FLOAT2,
|
||||
.buffer_index = vertex_indices.mesh,
|
||||
};
|
||||
|
||||
attrs[2] = .{
|
||||
.format = .FLOAT2,
|
||||
.buffer_index = vertex_indices.instance,
|
||||
};
|
||||
|
||||
attrs[3] = .{
|
||||
.format = .FLOAT2,
|
||||
.buffer_index = vertex_indices.instance,
|
||||
};
|
||||
|
||||
attrs[4] = .{
|
||||
.format = .FLOAT2,
|
||||
.buffer_index = vertex_indices.instance,
|
||||
};
|
||||
|
||||
attrs[5] = .{
|
||||
.format = .UBYTE4N,
|
||||
.buffer_index = vertex_indices.instance,
|
||||
};
|
||||
|
||||
attrs[6] = .{
|
||||
.format = .FLOAT,
|
||||
.buffer_index = vertex_indices.instance,
|
||||
};
|
||||
|
||||
attrs[7] = .{
|
||||
.format = .FLOAT4,
|
||||
.buffer_index = vertex_indices.instance,
|
||||
};
|
||||
|
||||
break: get attrs;
|
||||
},
|
||||
|
||||
.buffers = get: {
|
||||
var vertex_buffer_layouts = [_]sokol.gfx.VertexBufferLayoutState{.{}} ** 8;
|
||||
|
||||
vertex_buffer_layouts[vertex_indices.instance].step_func = .PER_INSTANCE;
|
||||
|
||||
break: get vertex_buffer_layouts;
|
||||
},
|
||||
};
|
||||
|
||||
fn run(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);
|
||||
};
|
||||
|
||||
defer ext.SDL_GL_DeleteContext(context);
|
||||
|
||||
sokol.gfx.setup(.{
|
||||
.environment = .{
|
||||
.defaults = .{
|
||||
.color_format = .RGBA8,
|
||||
.depth_format = .DEPTH_STENCIL,
|
||||
.sample_count = 1,
|
||||
},
|
||||
},
|
||||
|
||||
.logger = .{
|
||||
.func = sokol.log.func,
|
||||
},
|
||||
});
|
||||
|
||||
defer {
|
||||
sokol.gfx.shutdown();
|
||||
}
|
||||
|
||||
var pools = try Pools.init(coral.heap.allocator);
|
||||
|
||||
defer {
|
||||
pools.deinit();
|
||||
}
|
||||
|
||||
const Vertex = struct {
|
||||
xy: @Vector(2, f32),
|
||||
uv: @Vector(2, f32),
|
||||
};
|
||||
|
||||
const quad_indices = [_]u16{0, 1, 2, 0, 2, 3};
|
||||
|
||||
const quad_vertices = [_]Vertex{
|
||||
.{.xy = .{-0.5, -0.5}, .uv = .{0, 1}},
|
||||
.{.xy = .{0.5, -0.5}, .uv = .{1, 1}},
|
||||
.{.xy = .{0.5, 0.5}, .uv = .{1, 0}},
|
||||
.{.xy = .{-0.5, 0.5}, .uv = .{0, 0}},
|
||||
};
|
||||
|
||||
quad_index_buffer = sokol.gfx.makeBuffer(.{
|
||||
.data = sokol.gfx.asRange(&quad_indices),
|
||||
.type = .INDEXBUFFER,
|
||||
});
|
||||
|
||||
quad_vertex_buffer = sokol.gfx.makeBuffer(.{
|
||||
.data = sokol.gfx.asRange(&quad_vertices),
|
||||
.type = .VERTEXBUFFER,
|
||||
});
|
||||
|
||||
view_buffer = sokol.gfx.makeBuffer(.{
|
||||
.data = sokol.gfx.asRange(&lina.orthographic_projection(-1.0, 1.0, .{
|
||||
.left = 0,
|
||||
.top = 0,
|
||||
.right = @floatFromInt(1280),
|
||||
.bottom = @floatFromInt(720),
|
||||
})),
|
||||
|
||||
.type = .STORAGEBUFFER,
|
||||
.usage = .IMMUTABLE,
|
||||
});
|
||||
|
||||
const shader_spirv = @embedFile("./shaders/2d_default.spv");
|
||||
var spirv_unit = try spirv.Unit.init(coral.heap.allocator);
|
||||
|
||||
defer spirv_unit.deinit();
|
||||
|
||||
try spirv_unit.compile(shader_spirv, .vertex);
|
||||
try spirv_unit.compile(shader_spirv, .fragment);
|
||||
|
||||
Frame.texture_batching_pipeline = sokol.gfx.makePipeline(.{
|
||||
.label = "2D drawing pipeline",
|
||||
.layout = vertex_layout_state,
|
||||
.shader = sokol.gfx.makeShader(spirv_unit.shader_desc),
|
||||
.index_type = .UINT16,
|
||||
});
|
||||
|
||||
var has_commands_head: ?*Commands = null;
|
||||
var has_commands_tail: ?*Commands = null;
|
||||
|
||||
while (true) {
|
||||
switch (Work.pending.dequeue()) {
|
||||
.create_commands => |create_commands| {
|
||||
const commands = coral.heap.allocator.create(Commands) catch {
|
||||
_ = create_commands.created.resolve(error.OutOfMemory);
|
||||
|
||||
continue;
|
||||
};
|
||||
|
||||
commands.* = Commands.init(coral.heap.allocator);
|
||||
|
||||
if (create_commands.created.resolve(commands)) {
|
||||
if (has_commands_tail) |tail_commands| {
|
||||
tail_commands.has_next = commands;
|
||||
commands.has_prev = tail_commands;
|
||||
} else {
|
||||
std.debug.assert(has_commands_head == null);
|
||||
|
||||
has_commands_head = commands;
|
||||
}
|
||||
|
||||
has_commands_tail = commands;
|
||||
} else {
|
||||
coral.heap.allocator.destroy(commands);
|
||||
}
|
||||
},
|
||||
|
||||
.load_texture => |load| {
|
||||
const texture = try pools.create_texture(load.desc);
|
||||
|
||||
if (!load.loaded.resolve(texture)) {
|
||||
std.debug.assert(pools.destroy_texture(texture));
|
||||
}
|
||||
},
|
||||
|
||||
.render_frame => |render_frame| {
|
||||
var frame = Frame{
|
||||
.swapchain = .{
|
||||
.width = render_frame.width,
|
||||
.height = render_frame.height,
|
||||
.sample_count = 1,
|
||||
.color_format = .RGBA8,
|
||||
.depth_format = .DEPTH_STENCIL,
|
||||
.gl = .{.framebuffer = 0},
|
||||
}
|
||||
};
|
||||
|
||||
sokol.gfx.beginPass(swapchain_pass: {
|
||||
var pass = sokol.gfx.Pass{
|
||||
.swapchain = frame.swapchain,
|
||||
|
||||
.action = .{
|
||||
.stencil = .{.load_action = .CLEAR},
|
||||
.depth = .{.load_action = .CLEAR},
|
||||
},
|
||||
};
|
||||
|
||||
pass.action.colors[0] = .{
|
||||
.clear_value = @bitCast(render_frame.clear_color),
|
||||
.load_action = .CLEAR,
|
||||
};
|
||||
|
||||
break: swapchain_pass pass;
|
||||
});
|
||||
|
||||
var has_commands = has_commands_head;
|
||||
|
||||
while (has_commands) |commands| : (has_commands = commands.has_next) {
|
||||
for (commands.submitted_commands()) |command| {
|
||||
try command.process(&pools, &frame);
|
||||
}
|
||||
|
||||
if (frame.current_target_texture != .default) {
|
||||
frame.set_target(&pools, .{
|
||||
.clear_color = null,
|
||||
.clear_depth = null,
|
||||
.clear_stencil = null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
frame.flush(&pools);
|
||||
sokol.gfx.endPass();
|
||||
sokol.gfx.commit();
|
||||
ext.SDL_GL_SwapWindow(window);
|
||||
render_frame.finished.set();
|
||||
},
|
||||
|
||||
.rotate_commands => |rotate_commands| {
|
||||
var has_commands = has_commands_head;
|
||||
|
||||
while (has_commands) |commands| : (has_commands = commands.has_next) {
|
||||
commands.rotate();
|
||||
}
|
||||
|
||||
rotate_commands.finished.set();
|
||||
},
|
||||
|
||||
.shutdown => {
|
||||
break;
|
||||
},
|
||||
|
||||
.unload_texture => |unload| {
|
||||
if (!pools.destroy_texture(unload.handle)) {
|
||||
@panic("Attempt to unload a non-existent texture");
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var work_thread: std.Thread = undefined;
|
|
@ -0,0 +1,149 @@
|
|||
const coral = @import("coral");
|
||||
|
||||
const handles = @import("./handles.zig");
|
||||
|
||||
const lina = @import("./lina.zig");
|
||||
|
||||
const sokol = @import("sokol");
|
||||
|
||||
const spirv = @import("./spirv.zig");
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
pub const Texture = union (enum) {
|
||||
render: Render,
|
||||
static: Static,
|
||||
|
||||
const Render = struct {
|
||||
sampler: sokol.gfx.Sampler,
|
||||
color_image: sokol.gfx.Image,
|
||||
depth_image: sokol.gfx.Image,
|
||||
attachments: sokol.gfx.Attachments,
|
||||
view_buffer: sokol.gfx.Buffer,
|
||||
|
||||
fn deinit(self: *Render) void {
|
||||
sokol.gfx.destroyImage(self.color_image);
|
||||
sokol.gfx.destroyImage(self.depth_image);
|
||||
sokol.gfx.destroySampler(self.sampler);
|
||||
sokol.gfx.destroyAttachments(self.attachments);
|
||||
sokol.gfx.destroyBuffer(self.view_buffer);
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
fn init(format: handles.Texture.Desc.Format, access: handles.Texture.Desc.Access.Render) std.mem.Allocator.Error!Render {
|
||||
const color_image = sokol.gfx.makeImage(.{
|
||||
.pixel_format = switch (format) {
|
||||
.rgba8 => sokol.gfx.PixelFormat.RGBA8,
|
||||
.bgra8 => sokol.gfx.PixelFormat.BGRA8,
|
||||
},
|
||||
|
||||
.width = access.width,
|
||||
.height = access.height,
|
||||
.render_target = true,
|
||||
});
|
||||
|
||||
const depth_image = sokol.gfx.makeImage(.{
|
||||
.width = access.width,
|
||||
.height = access.height,
|
||||
.render_target = true,
|
||||
.pixel_format = .DEPTH_STENCIL,
|
||||
});
|
||||
|
||||
const attachments = sokol.gfx.makeAttachments(attachments_desc: {
|
||||
var desc = sokol.gfx.AttachmentsDesc{
|
||||
.depth_stencil = .{
|
||||
.image = depth_image,
|
||||
},
|
||||
};
|
||||
|
||||
desc.colors[0] = .{
|
||||
.image = color_image,
|
||||
};
|
||||
|
||||
break: attachments_desc desc;
|
||||
});
|
||||
|
||||
const sampler = sokol.gfx.makeSampler(.{});
|
||||
|
||||
const view_buffer = sokol.gfx.makeBuffer(.{
|
||||
.data = sokol.gfx.asRange(&lina.orthographic_projection(-1.0, 1.0, .{
|
||||
.left = 0,
|
||||
.top = 0,
|
||||
.right = @floatFromInt(access.width),
|
||||
.bottom = @floatFromInt(access.height),
|
||||
})),
|
||||
|
||||
.type = .STORAGEBUFFER,
|
||||
.usage = .IMMUTABLE,
|
||||
});
|
||||
|
||||
return .{
|
||||
.attachments = attachments,
|
||||
.sampler = sampler,
|
||||
.color_image = color_image,
|
||||
.depth_image = depth_image,
|
||||
.view_buffer = view_buffer,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const Static = struct {
|
||||
image: sokol.gfx.Image,
|
||||
sampler: sokol.gfx.Sampler,
|
||||
|
||||
fn deinit(self: *Static) void {
|
||||
sokol.gfx.destroyImage(self.image);
|
||||
sokol.gfx.destroySampler(self.sampler);
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
fn init(format: handles.Texture.Desc.Format, access: handles.Texture.Desc.Access.Static) std.mem.Allocator.Error!Static {
|
||||
const image = sokol.gfx.makeImage(image_desc: {
|
||||
var desc = sokol.gfx.ImageDesc{
|
||||
.height = std.math.cast(i32, access.data.len / (access.width * format.byte_size())) orelse {
|
||||
return error.OutOfMemory;
|
||||
},
|
||||
|
||||
.pixel_format = switch (format) {
|
||||
.rgba8 => sokol.gfx.PixelFormat.RGBA8,
|
||||
.bgra8 => sokol.gfx.PixelFormat.BGRA8,
|
||||
},
|
||||
|
||||
.width = access.width,
|
||||
};
|
||||
|
||||
desc.data.subimage[0][0] = sokol.gfx.asRange(access.data);
|
||||
|
||||
break: image_desc desc;
|
||||
});
|
||||
|
||||
const sampler = sokol.gfx.makeSampler(.{});
|
||||
|
||||
errdefer {
|
||||
sokol.gfx.destroySampler(sampler);
|
||||
sokol.gfx.destroyImage(image);
|
||||
}
|
||||
|
||||
return .{
|
||||
.image = image,
|
||||
.sampler = sampler,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub fn deinit(self: *Texture) void {
|
||||
switch (self.*) {
|
||||
.static => |*static| static.deinit(),
|
||||
.render => |*render| render.deinit(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(desc: handles.Texture.Desc) std.mem.Allocator.Error!Texture {
|
||||
return switch (desc.access) {
|
||||
.static => |static| .{.static = try Static.init(desc.format, static)},
|
||||
.render => |render| .{.render = try Render.init(desc.format, render)},
|
||||
};
|
||||
}
|
||||
};
|
|
@ -7,6 +7,10 @@ layout (location = 1) in vec2 uv;
|
|||
|
||||
layout (location = 0) out vec4 texel;
|
||||
|
||||
layout (binding = 0) readonly buffer View {
|
||||
mat4 projection_matrix;
|
||||
};
|
||||
|
||||
void main() {
|
||||
texel = texture(sprite, uv) * color;
|
||||
|
|
@ -13,7 +13,7 @@ layout (location = 7) in vec4 instance_rect;
|
|||
layout (location = 0) out vec4 color;
|
||||
layout (location = 1) out vec2 uv;
|
||||
|
||||
layout (binding = 0) uniform Projection {
|
||||
layout (binding = 0) readonly buffer View {
|
||||
mat4 projection_matrix;
|
||||
};
|
||||
|
|
@ -12,14 +12,22 @@ pub const Error = std.mem.Allocator.Error || error {
|
|||
UnsupportedTarget,
|
||||
InvalidSPIRV,
|
||||
UnsupportedSPIRV,
|
||||
UnsupportedBackend,
|
||||
};
|
||||
|
||||
pub const Unit = struct {
|
||||
arena: std.heap.ArenaAllocator,
|
||||
context: ext.spvc_context,
|
||||
shader_desc: sokol.gfx.ShaderDesc,
|
||||
attrs_used: u32 = 0,
|
||||
|
||||
pub fn compile(self: *Unit, spirv: []const u32, stage: Stage) Error!void {
|
||||
pub fn compile(self: *Unit, spirv_data: []const u8, stage: Stage) Error!void {
|
||||
if ((spirv_data.len % @alignOf(u32)) != 0) {
|
||||
return error.InvalidSPIRV;
|
||||
}
|
||||
|
||||
const spirv_ops: []const u32 = @alignCast(std.mem.bytesAsSlice(u32, spirv_data));
|
||||
|
||||
const execution_model, const stage_desc = switch (stage) {
|
||||
.vertex => .{ext.SpvExecutionModelVertex, &self.shader_desc.vs},
|
||||
.fragment => .{ext.SpvExecutionModelFragment, &self.shader_desc.fs},
|
||||
|
@ -30,7 +38,7 @@ pub const Unit = struct {
|
|||
option_values: []const struct {ext.spvc_compiler_option, c_uint},
|
||||
};
|
||||
|
||||
const backend: Backend = switch (sokol.gfx.queryBackend()) {
|
||||
const backend: Backend = try switch (sokol.gfx.queryBackend()) {
|
||||
.GLCORE => .{
|
||||
.target = ext.SPVC_BACKEND_GLSL,
|
||||
|
||||
|
@ -43,13 +51,13 @@ pub const Unit = struct {
|
|||
},
|
||||
},
|
||||
|
||||
else => @panic("Unimplemented"),
|
||||
else => error.UnsupportedBackend,
|
||||
};
|
||||
|
||||
const compiler = parse_and_configure: {
|
||||
var parsed_ir: ext.spvc_parsed_ir = null;
|
||||
|
||||
try to_error(ext.spvc_context_parse_spirv(self.context, spirv.ptr, spirv.len, &parsed_ir));
|
||||
try to_error(ext.spvc_context_parse_spirv(self.context, spirv_ops.ptr, spirv_ops.len, &parsed_ir));
|
||||
|
||||
var compiler: ext.spvc_compiler = null;
|
||||
|
||||
|
@ -60,17 +68,14 @@ pub const Unit = struct {
|
|||
|
||||
try to_error(ext.spvc_compiler_get_combined_image_samplers(compiler, @ptrCast(&combined_image_samplers.ptr), &combined_image_samplers.len));
|
||||
|
||||
const arena_allocator = self.arena.allocator();
|
||||
var binding: u32 = 0;
|
||||
|
||||
for (combined_image_samplers) |combined_image_sampler| {
|
||||
var name_buffer = [_:0]u8{0} ** 255;
|
||||
|
||||
const name = coral.utf8.print_formatted(&name_buffer, "{image_name}_{sampler_name}", .{
|
||||
.image_name = coral.io.slice_sentineled(u8, 0, ext.spvc_compiler_get_name(compiler, combined_image_sampler.image_id)),
|
||||
.sampler_name = coral.io.slice_sentineled(u8, 0, ext.spvc_compiler_get_name(compiler, combined_image_sampler.sampler_id)),
|
||||
}) catch {
|
||||
return error.InvalidSPIRV;
|
||||
};
|
||||
const name = try coral.utf8.alloc_formatted(arena_allocator, "{image_name}_{sampler_name}", .{
|
||||
.image_name = std.mem.span(@as([*:0]const u8, ext.spvc_compiler_get_name(compiler, combined_image_sampler.image_id))),
|
||||
.sampler_name = std.mem.span(@as([*:0]const u8, ext.spvc_compiler_get_name(compiler, combined_image_sampler.sampler_id))),
|
||||
});
|
||||
|
||||
ext.spvc_compiler_set_name(compiler, combined_image_sampler.combined_id, name);
|
||||
ext.spvc_compiler_set_decoration(compiler, combined_image_sampler.combined_id, ext.SpvDecorationBinding, binding);
|
||||
|
@ -91,7 +96,8 @@ pub const Unit = struct {
|
|||
break: create resources;
|
||||
};
|
||||
|
||||
try reflect_uniform_blocks(compiler, resources, stage_desc);
|
||||
try reflect_uniform_buffers(compiler, resources, stage_desc);
|
||||
try reflect_storage_buffers(compiler, resources, stage_desc);
|
||||
try reflect_image_samplers(compiler, resources, stage_desc);
|
||||
|
||||
try to_error(ext.spvc_compiler_install_compiler_options(compiler, create: {
|
||||
|
@ -109,15 +115,18 @@ pub const Unit = struct {
|
|||
}));
|
||||
|
||||
try to_error(ext.spvc_compiler_compile(compiler, @ptrCast(&stage_desc.source)));
|
||||
|
||||
std.log.info("{s}", .{stage_desc.source});
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Unit) void {
|
||||
ext.spvc_context_destroy(self.context);
|
||||
self.arena.deinit();
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn init() std.mem.Allocator.Error!Unit {
|
||||
pub fn init(allocator: std.mem.Allocator) std.mem.Allocator.Error!Unit {
|
||||
var context: ext.spvc_context = null;
|
||||
|
||||
if (ext.spvc_context_create(&context) != ext.SPVC_SUCCESS) {
|
||||
|
@ -129,6 +138,7 @@ pub const Unit = struct {
|
|||
ext.spvc_context_set_error_callback(context, log_context_errors, null);
|
||||
|
||||
return .{
|
||||
.arena = std.heap.ArenaAllocator.init(allocator),
|
||||
.context = context,
|
||||
|
||||
.shader_desc = .{
|
||||
|
@ -144,44 +154,33 @@ pub const Stage = enum {
|
|||
vertex,
|
||||
};
|
||||
|
||||
pub fn embed_shader(comptime path: []const u8) []const u32 {
|
||||
const shader = @embedFile(path);
|
||||
const alignment = @alignOf(u32);
|
||||
|
||||
if ((shader.len % alignment) != 0) {
|
||||
@compileError("size of file contents at " ++ path ++ " must be aligned to a multiple of 4");
|
||||
}
|
||||
|
||||
return @as(*const [shader.len / alignment]u32, @ptrCast(@alignCast(shader)));
|
||||
}
|
||||
|
||||
fn log_context_errors(userdata: ?*anyopaque, error_message: [*c]const u8) callconv(.C) void {
|
||||
std.debug.assert(userdata == null);
|
||||
std.log.err("{s}", .{error_message});
|
||||
}
|
||||
|
||||
fn reflect_image_samplers(compiler: ext.spvc_compiler, resources: ext.spvc_resources, stage: *sokol.gfx.ShaderStageDesc) Error!void {
|
||||
var reflected_sampled_images: []const ext.spvc_reflected_resource = &.{};
|
||||
fn reflect_image_samplers(compiler: ext.spvc_compiler, resources: ext.spvc_resources, stage_desc: *sokol.gfx.ShaderStageDesc) Error!void {
|
||||
var sampled_images: []const ext.spvc_reflected_resource = &.{};
|
||||
|
||||
try to_error(ext.spvc_resources_get_resource_list_for_type(
|
||||
resources,
|
||||
ext.SPVC_RESOURCE_TYPE_SAMPLED_IMAGE,
|
||||
@ptrCast(&reflected_sampled_images.ptr),
|
||||
&reflected_sampled_images.len,
|
||||
@ptrCast(&sampled_images.ptr),
|
||||
&sampled_images.len,
|
||||
));
|
||||
|
||||
if (reflected_sampled_images.len > stage.image_sampler_pairs.len) {
|
||||
if (sampled_images.len > stage_desc.image_sampler_pairs.len) {
|
||||
return error.UnsupportedSPIRV;
|
||||
}
|
||||
|
||||
for (0 .. reflected_sampled_images.len, reflected_sampled_images) |i, reflected_sampled_image| {
|
||||
const sampled_image_type = ext.spvc_compiler_get_type_handle(compiler, reflected_sampled_image.type_id);
|
||||
for (0 .. sampled_images.len, sampled_images) |i, sampled_image| {
|
||||
const sampled_image_type = ext.spvc_compiler_get_type_handle(compiler, sampled_image.type_id);
|
||||
|
||||
if (ext.spvc_type_get_basetype(sampled_image_type) != ext.SPVC_BASETYPE_SAMPLED_IMAGE) {
|
||||
return error.InvalidSPIRV;
|
||||
}
|
||||
|
||||
stage.images[i] = .{
|
||||
stage_desc.images[i] = .{
|
||||
.multisampled = ext.spvc_type_get_image_multisampled(sampled_image_type) != 0,
|
||||
|
||||
.image_type = try switch (ext.spvc_type_get_image_dimension(sampled_image_type)) {
|
||||
|
@ -197,13 +196,13 @@ fn reflect_image_samplers(compiler: ext.spvc_compiler, resources: ext.spvc_resou
|
|||
.used = true,
|
||||
};
|
||||
|
||||
stage.samplers[i] = .{
|
||||
stage_desc.samplers[i] = .{
|
||||
.sampler_type = .DEFAULT,
|
||||
.used = true,
|
||||
};
|
||||
|
||||
stage.image_sampler_pairs[i] = .{
|
||||
.glsl_name = ext.spvc_compiler_get_name(compiler, reflected_sampled_image.id),
|
||||
stage_desc.image_sampler_pairs[i] = .{
|
||||
.glsl_name = ext.spvc_compiler_get_name(compiler, sampled_image.id),
|
||||
.image_slot = @intCast(i),
|
||||
.sampler_slot = @intCast(i),
|
||||
.used = true,
|
||||
|
@ -211,85 +210,56 @@ fn reflect_image_samplers(compiler: ext.spvc_compiler, resources: ext.spvc_resou
|
|||
}
|
||||
}
|
||||
|
||||
fn reflect_uniform_blocks(compiler: ext.spvc_compiler, resources: ext.spvc_resources, stage: *sokol.gfx.ShaderStageDesc) Error!void {
|
||||
var reflected_uniform_buffers: []const ext.spvc_reflected_resource = &.{};
|
||||
fn reflect_storage_buffers(compiler: ext.spvc_compiler, resources: ext.spvc_resources, stage_desc: *sokol.gfx.ShaderStageDesc) Error!void {
|
||||
var storage_buffers: []const ext.spvc_reflected_resource = &.{};
|
||||
|
||||
try to_error(ext.spvc_resources_get_resource_list_for_type(
|
||||
resources,
|
||||
ext.SPVC_RESOURCE_TYPE_STORAGE_BUFFER,
|
||||
@ptrCast(&storage_buffers.ptr),
|
||||
&storage_buffers.len,
|
||||
));
|
||||
|
||||
for (storage_buffers) |storage_buffer| {
|
||||
const binding = ext.spvc_compiler_get_decoration(compiler, storage_buffer.id, ext.SpvDecorationBinding);
|
||||
|
||||
if (binding >= stage_desc.storage_buffers.len) {
|
||||
return error.InvalidSPIRV;
|
||||
}
|
||||
|
||||
var block_decorations: []const ext.SpvDecoration = &.{};
|
||||
|
||||
try to_error(ext.spvc_compiler_get_buffer_block_decorations(
|
||||
compiler,
|
||||
storage_buffer.id,
|
||||
@ptrCast(&block_decorations.ptr),
|
||||
&block_decorations.len,
|
||||
));
|
||||
|
||||
stage_desc.storage_buffers[binding] = .{
|
||||
.readonly = std.mem.indexOfScalar(ext.SpvDecoration, block_decorations, ext.SpvDecorationNonWritable) != null,
|
||||
.used = true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn reflect_uniform_buffers(compiler: ext.spvc_compiler, resources: ext.spvc_resources, stage_desc: *sokol.gfx.ShaderStageDesc) Error!void {
|
||||
var uniform_buffers: []const ext.spvc_reflected_resource = &.{};
|
||||
|
||||
try to_error(ext.spvc_resources_get_resource_list_for_type(
|
||||
resources,
|
||||
ext.SPVC_RESOURCE_TYPE_UNIFORM_BUFFER,
|
||||
@ptrCast(&reflected_uniform_buffers.ptr),
|
||||
&reflected_uniform_buffers.len,
|
||||
@ptrCast(&uniform_buffers.ptr),
|
||||
&uniform_buffers.len,
|
||||
));
|
||||
|
||||
if (reflected_uniform_buffers.len > stage.uniform_blocks.len) {
|
||||
return error.UnsupportedSPIRV;
|
||||
if (uniform_buffers.len != 0) {
|
||||
return error.InvalidSPIRV;
|
||||
}
|
||||
|
||||
for (stage.uniform_blocks[0 .. reflected_uniform_buffers.len], reflected_uniform_buffers) |*uniform_block, reflected_uniform_buffer| {
|
||||
const uniform_buffer_type = ext.spvc_compiler_get_type_handle(compiler, reflected_uniform_buffer.type_id);
|
||||
|
||||
if (ext.spvc_type_get_basetype(uniform_buffer_type) != ext.SPVC_BASETYPE_STRUCT) {
|
||||
return error.InvalidSPIRV;
|
||||
}
|
||||
|
||||
const member_count = ext.spvc_type_get_num_member_types(uniform_buffer_type);
|
||||
|
||||
if (member_count > uniform_block.uniforms.len) {
|
||||
return error.UnsupportedSPIRV;
|
||||
}
|
||||
|
||||
try to_error(ext.spvc_compiler_get_declared_struct_size(compiler, uniform_buffer_type, &uniform_block.size));
|
||||
|
||||
var uniform_blocks_used: u32 = 0;
|
||||
|
||||
while (uniform_blocks_used < member_count) : (uniform_blocks_used += 1) {
|
||||
const member_type_id = ext.spvc_type_get_member_type(uniform_buffer_type, uniform_blocks_used);
|
||||
const member_type = ext.spvc_compiler_get_type_handle(compiler, member_type_id);
|
||||
|
||||
uniform_block.uniforms[uniform_blocks_used] = .{
|
||||
// .name = ext.spvc_compiler_get_member_name(compiler, ext.spvc_type_get_base_type_id(uniform_buffer_type), uniform_blocks_used),
|
||||
|
||||
.array_count = @intCast(try switch (ext.spvc_type_get_num_array_dimensions(member_type)) {
|
||||
0 => 0,
|
||||
|
||||
1 => switch (ext.spvc_type_array_dimension_is_literal(member_type, 1) != 0) {
|
||||
true => ext.spvc_type_get_array_dimension(member_type, 1),
|
||||
false => error.InvalidSPIRV,
|
||||
},
|
||||
|
||||
else => error.InvalidSPIRV,
|
||||
}),
|
||||
|
||||
.type = try switch (ext.spvc_type_get_basetype(member_type)) {
|
||||
ext.SPVC_BASETYPE_INT32 => switch (ext.spvc_type_get_vector_size(member_type)) {
|
||||
1 => if (ext.spvc_type_get_columns(member_type) == 1) sokol.gfx.UniformType.INT else error.InvalidSPIRV,
|
||||
2 => if (ext.spvc_type_get_columns(member_type) == 1) sokol.gfx.UniformType.INT2 else error.InvalidSPIRV,
|
||||
3 => if (ext.spvc_type_get_columns(member_type) == 1) sokol.gfx.UniformType.INT3 else error.InvalidSPIRV,
|
||||
4 => if (ext.spvc_type_get_columns(member_type) == 1) sokol.gfx.UniformType.INT4 else error.InvalidSPIRV,
|
||||
else => error.InvalidSPIRV,
|
||||
},
|
||||
|
||||
ext.SPVC_BASETYPE_FP32 => switch (ext.spvc_type_get_vector_size(member_type)) {
|
||||
1 => if (ext.spvc_type_get_columns(member_type) == 1) sokol.gfx.UniformType.FLOAT else error.InvalidSPIRV,
|
||||
2 => if (ext.spvc_type_get_columns(member_type) == 1) sokol.gfx.UniformType.FLOAT2 else error.InvalidSPIRV,
|
||||
3 => if (ext.spvc_type_get_columns(member_type) == 1) sokol.gfx.UniformType.FLOAT3 else error.InvalidSPIRV,
|
||||
|
||||
4 => switch (ext.spvc_type_get_columns(member_type)) {
|
||||
1 => sokol.gfx.UniformType.FLOAT4,
|
||||
4 => sokol.gfx.UniformType.MAT4,
|
||||
else => error.InvalidSPIRV,
|
||||
},
|
||||
|
||||
else => error.InvalidSPIRV,
|
||||
},
|
||||
|
||||
else => error.InvalidSPIRV,
|
||||
},
|
||||
};
|
||||
|
||||
// uniform_block.uniforms[uniform_blocks_used].name = ext.spvc_compiler_get_member_name(compiler, ext.spvc_type_get_base_type_id(uniform_buffer_type), uniform_blocks_used);
|
||||
}
|
||||
}
|
||||
// TODO: Support for older APIs?
|
||||
_ = stage_desc;
|
||||
_ = compiler;
|
||||
}
|
||||
|
||||
fn to_error(result: ext.spvc_result) Error!void {
|
||||
|
|
Loading…
Reference in New Issue