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");
|
const std = @import("std");
|
||||||
|
|
||||||
pub const DecimalFormat = struct {
|
pub const DecimalFormat = struct {
|
||||||
delimiter: []const coral.Byte,
|
delimiter: []const coral.io.Byte,
|
||||||
positive_prefix: enum {none, plus, space},
|
positive_prefix: enum {none, plus, space},
|
||||||
|
|
||||||
pub const default = DecimalFormat{
|
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) {
|
if (value == 0) {
|
||||||
return io.print(writer, switch (self.positive_prefix) {
|
return io.write_all(writer, switch (self.positive_prefix) {
|
||||||
.none => "0",
|
.none => "0",
|
||||||
.plus => "+0",
|
.plus => "+0",
|
||||||
.space => " 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| {
|
.Float => |float| {
|
||||||
if (value < 0) {
|
if (value < 0) {
|
||||||
try io.print(writer, "-");
|
try io.write_all(writer, "-");
|
||||||
}
|
}
|
||||||
|
|
||||||
const Float = @TypeOf(value);
|
const Float = @TypeOf(value);
|
||||||
const Int = std.meta.Int(.unsigned, float.bits);
|
const Int = std.meta.Int(.unsigned, float.bits);
|
||||||
const integer = @as(Int, @intFromFloat(value));
|
const integer = @as(Int, @intFromFloat(value));
|
||||||
|
|
||||||
try self.print(writer, integer);
|
try self.format(writer, integer);
|
||||||
try io.print(writer, ".");
|
try io.write_all(writer, ".");
|
||||||
try self.print(writer, @as(Int, @intFromFloat((value - @as(Float, @floatFromInt(integer))) * 100)));
|
try self.format(writer, @as(Int, @intFromFloat((value - @as(Float, @floatFromInt(integer))) * 100)));
|
||||||
},
|
},
|
||||||
|
|
||||||
else => @compileError("`" ++ @typeName(Value) ++ "` cannot be formatted to a decimal string"),
|
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 ascii = @import("./ascii.zig");
|
||||||
|
|
||||||
|
pub const asyncio = @import("./asyncio.zig");
|
||||||
|
|
||||||
pub const files = @import("./files.zig");
|
pub const files = @import("./files.zig");
|
||||||
|
|
||||||
pub const hashes = @import("./hashes.zig");
|
pub const hashes = @import("./hashes.zig");
|
||||||
|
@ -16,6 +18,108 @@ pub const slices = @import("./slices.zig");
|
||||||
|
|
||||||
pub const stack = @import("./stack.zig");
|
pub const stack = @import("./stack.zig");
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
pub const utf8 = @import("./utf8.zig");
|
pub const utf8 = @import("./utf8.zig");
|
||||||
|
|
||||||
pub const vectors = @import("./vectors.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");
|
const std = @import("std");
|
||||||
|
|
||||||
pub const Error = error {
|
pub const AccessError = error {
|
||||||
FileNotFound,
|
FileNotFound,
|
||||||
FileInaccessible,
|
FileInaccessible,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const ReadAllError = AccessError || error {
|
||||||
|
ReadIncomplete,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ReadAllOptions = struct {
|
||||||
|
offset: u64 = 0,
|
||||||
|
limit: u64 = std.math.maxInt(u64),
|
||||||
|
};
|
||||||
|
|
||||||
pub const Stat = struct {
|
pub const Stat = struct {
|
||||||
size: u64,
|
size: u64,
|
||||||
};
|
};
|
||||||
|
@ -18,15 +27,37 @@ pub const Storage = struct {
|
||||||
vtable: *const VTable,
|
vtable: *const VTable,
|
||||||
|
|
||||||
pub const VTable = struct {
|
pub const VTable = struct {
|
||||||
stat: *const fn (*anyopaque, []const u8) Error!Stat,
|
stat: *const fn (*anyopaque, []const u8) AccessError!Stat,
|
||||||
read: *const fn (*anyopaque, []const u8, usize, []io.Byte) Error!usize,
|
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);
|
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 {
|
const decoded = (try self.read_native(path, offset, Type)) orelse {
|
||||||
return null;
|
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);
|
var buffer = @as([@sizeOf(Type)]io.Byte, undefined);
|
||||||
|
|
||||||
if (try self.vtable.read(self.userdata, path, offset, &buffer) != buffer.len) {
|
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)).*;
|
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) {
|
pub const read_little = switch (native_endian) {
|
||||||
.little => read_native,
|
.little => read_native,
|
||||||
.big => read_foreign,
|
.big => read_foreign,
|
||||||
|
@ -60,7 +95,7 @@ pub const Storage = struct {
|
||||||
|
|
||||||
pub const bundle = init: {
|
pub const bundle = init: {
|
||||||
const Bundle = struct {
|
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;
|
var buffer = [_:0]u8{0} ** 4095;
|
||||||
|
|
||||||
_ = std.fs.cwd().realpath(path, &buffer) catch {
|
_ = std.fs.cwd().realpath(path, &buffer) catch {
|
||||||
|
@ -70,7 +105,7 @@ pub const bundle = init: {
|
||||||
return buffer;
|
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| {
|
var file = std.fs.openFileAbsoluteZ(&(try full_path(path)), .{.mode = .read_only}) catch |open_error| {
|
||||||
return switch (open_error) {
|
return switch (open_error) {
|
||||||
error.FileNotFound => error.FileNotFound,
|
error.FileNotFound => error.FileNotFound,
|
||||||
|
@ -89,7 +124,7 @@ pub const bundle = init: {
|
||||||
return file.read(output) catch error.FileInaccessible;
|
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: {
|
const file_stat = get: {
|
||||||
var file = std.fs.openFileAbsoluteZ(&(try full_path(path)), .{.mode = .read_only}) catch |open_error| {
|
var file = std.fs.openFileAbsoluteZ(&(try full_path(path)), .{.mode = .read_only}) catch |open_error| {
|
||||||
return switch (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 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 {
|
pub fn skip_n(input: Reader, distance: u64) Error!void {
|
||||||
var buffer = @as([512]coral.Byte, undefined);
|
var buffer = @as([512]coral.Byte, undefined);
|
||||||
var remaining = distance;
|
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 {
|
pub fn stream_all(input: Reader, output: Writer) Error!usize {
|
||||||
var buffer = @as([512]coral.Byte, undefined);
|
var buffer = @as([512]coral.Byte, undefined);
|
||||||
var copied = @as(usize, 0);
|
var copied = @as(usize, 0);
|
||||||
|
|
|
@ -147,7 +147,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime traits: Traits(
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_ptr(self: Self, key: Key) ?*Value {
|
pub fn get(self: Self, key: Key) ?*Value {
|
||||||
if (self.len == 0) {
|
if (self.len == 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -169,14 +169,6 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime traits: Traits(
|
||||||
return null;
|
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 {
|
pub fn emplace(self: *Self, key: Key, value: Value) std.mem.Allocator.Error!bool {
|
||||||
try self.rehash(load_max);
|
try self.rehash(load_max);
|
||||||
|
|
||||||
|
|
|
@ -115,24 +115,6 @@ pub fn get_ptr(slice: anytype, index: usize) ?ElementPtr(@TypeOf(slice)) {
|
||||||
return &slice[index];
|
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) {
|
pub fn parallel_alloc(comptime Element: type, allocator: std.mem.Allocator, n: usize) std.mem.Allocator.Error!Parallel(Element) {
|
||||||
const alignment = @alignOf(Element);
|
const alignment = @alignOf(Element);
|
||||||
const Slices = Parallel(Element);
|
const Slices = Parallel(Element);
|
||||||
|
|
|
@ -6,17 +6,14 @@ const io = @import("./io.zig");
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub fn alloc_formatted(allocator: std.mem.Allocator, comptime format: []const u8, args: anytype) std.mem.Allocator.Error![]coral.Byte {
|
pub fn alloc_formatted(allocator: std.mem.Allocator, comptime format: []const u8, args: anytype) std.mem.Allocator.Error![:0]u8 {
|
||||||
var buffer = coral.Stack(coral.Byte){.allocator = allocator};
|
|
||||||
const formatted_len = count_formatted(format, args);
|
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();
|
// TODO: This is dumb.
|
||||||
|
return @constCast(print_formatted(buffer, format, args) catch unreachable);
|
||||||
print_formatted(buffer.writer(), format, args) catch unreachable;
|
|
||||||
|
|
||||||
return buffer.to_allocation(formatted_len, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn count_formatted(comptime format: []const u8, args: anytype) usize {
|
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;
|
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 {
|
const Seekable = struct {
|
||||||
buffer: []coral.io.Byte,
|
buffer: []coral.io.Byte,
|
||||||
cursor: usize,
|
cursor: usize,
|
||||||
|
@ -143,8 +140,8 @@ noinline fn print_formatted_value(writer: io.Writer, value: anytype) io.Error!vo
|
||||||
const Value = @TypeOf(value);
|
const Value = @TypeOf(value);
|
||||||
|
|
||||||
return switch (@typeInfo(Value)) {
|
return switch (@typeInfo(Value)) {
|
||||||
.Int => ascii.DecimalFormat.default.print(writer, value),
|
.Int => ascii.DecimalFormat.default.format(writer, value),
|
||||||
.Float => ascii.DecimalFormat.default.print(writer, value),
|
.Float => ascii.DecimalFormat.default.format(writer, value),
|
||||||
.Enum => io.print(writer, @tagName(value)),
|
.Enum => io.print(writer, @tagName(value)),
|
||||||
|
|
||||||
.Pointer => |pointer| switch (pointer.size) {
|
.Pointer => |pointer| switch (pointer.size) {
|
||||||
|
|
|
@ -80,7 +80,7 @@ pub fn Graph(comptime Payload: type) type {
|
||||||
return false;
|
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);
|
try edges.push_grow(edge_node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ pub const Table = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(self: Table, comptime Resource: type) ?*Resource {
|
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));
|
return @ptrCast(@alignCast(entry.ptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ pub const Table = struct {
|
||||||
const Value = @TypeOf(value);
|
const Value = @TypeOf(value);
|
||||||
const value_id = type_id(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;
|
@as(*Value, @ptrCast(@alignCast(entry.ptr))).* = value;
|
||||||
} else {
|
} else {
|
||||||
const resource_allocator = self.arena.allocator();
|
const resource_allocator = self.arena.allocator();
|
||||||
|
|
|
@ -49,16 +49,16 @@ pub const BindContext = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
const read_write_resource_nodes = lazily_create: {
|
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, .{
|
std.debug.assert(try self.systems.read_write_resource_id_nodes.emplace(id, .{
|
||||||
.allocator = coral.heap.allocator,
|
.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);
|
try read_write_resource_nodes.push_grow(self.node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,16 +77,16 @@ pub const BindContext = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
const read_only_resource_nodes = lazily_create: {
|
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, .{
|
std.debug.assert(try self.systems.read_only_resource_id_nodes.emplace(id, .{
|
||||||
.allocator = coral.heap.allocator,
|
.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);
|
try read_only_resource_nodes.push_grow(self.node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -415,12 +415,12 @@ pub const Schedule = struct {
|
||||||
const nodes = lazily_create: {
|
const nodes = lazily_create: {
|
||||||
const system_id = @intFromPtr(info);
|
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, .{
|
std.debug.assert(try self.system_id_nodes.emplace(system_id, .{
|
||||||
.allocator = self.system_id_nodes.allocator,
|
.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 ona = @import("ona");
|
||||||
|
|
||||||
const Actors = struct {
|
const Actors = struct {
|
||||||
instances: coral.stack.Sequential(ona.gfx.Point2D) = .{.allocator = coral.heap.allocator},
|
instances: coral.stack.Sequential(@Vector(2, f32)) = .{.allocator = coral.heap.allocator},
|
||||||
quad_mesh_2d: ?*ona.gfx.Handle = null,
|
body_texture: ona.gfx.Texture = .default,
|
||||||
body_texture: ?*ona.gfx.Handle = null,
|
render_texture: ona.gfx.Texture = .default,
|
||||||
render_texture: ?*ona.gfx.Handle = null,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const Player = struct {
|
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 {
|
fn load(config: ona.Write(ona.gfx.Config), actors: ona.Write(Actors), assets: ona.Write(ona.gfx.Assets)) !void {
|
||||||
display.res.width, display.res.height = .{1280, 720};
|
config.res.width, config.res.height = .{1280, 720};
|
||||||
actors.res.body_texture = try assets.res.create_from_file(coral.files.bundle, "actor.bmp");
|
actors.res.body_texture = try assets.res.load_texture_file(coral.files.bundle, "actor.bmp");
|
||||||
actors.res.quad_mesh_2d = try assets.res.create_quad_mesh_2d(@splat(1));
|
|
||||||
|
|
||||||
actors.res.render_texture = try assets.res.context.create(.{
|
actors.res.render_texture = try assets.res.load_texture(.{
|
||||||
.texture = .{
|
.format = .rgba8,
|
||||||
.format = .rgba8,
|
|
||||||
|
|
||||||
.access = .{
|
.access = .{
|
||||||
.render = .{
|
.render = .{
|
||||||
.width = display.res.width,
|
.width = config.res.width,
|
||||||
.height = display.res.height,
|
.height = config.res.height,
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -48,41 +44,41 @@ fn exit(actors: ona.Write(Actors)) void {
|
||||||
actors.res.instances.deinit();
|
actors.res.instances.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(queue: ona.gfx.Queue, actors: ona.Write(Actors), display: ona.Read(ona.gfx.Display)) !void {
|
fn render(commands: ona.gfx.Commands, actors: ona.Write(Actors), config: ona.Read(ona.gfx.Config)) !void {
|
||||||
try queue.commands.append(.{.target = .{
|
try commands.set_target(.{
|
||||||
.texture = actors.res.render_texture.?,
|
.texture = actors.res.render_texture,
|
||||||
.clear_color = .{0, 0, 0, 0},
|
.clear_color = .{0, 0, 0, 0},
|
||||||
.clear_depth = 0,
|
.clear_depth = 0,
|
||||||
}});
|
.clear_stencil = 0,
|
||||||
|
});
|
||||||
|
|
||||||
for (actors.res.instances.values) |instance| {
|
for (actors.res.instances.values) |instance| {
|
||||||
try queue.commands.append(.{.instance_2d = .{
|
try commands.draw_texture(.{
|
||||||
.mesh_2d = actors.res.quad_mesh_2d.?,
|
.texture = actors.res.body_texture,
|
||||||
.texture = actors.res.body_texture.?,
|
|
||||||
|
|
||||||
.transform = .{
|
.transform = .{
|
||||||
.origin = instance,
|
.origin = instance,
|
||||||
.xbasis = .{64, 0},
|
.xbasis = .{64, 0},
|
||||||
.ybasis = .{0, 64},
|
.ybasis = .{0, 64},
|
||||||
},
|
},
|
||||||
}});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
try queue.commands.append(.{.target = .{
|
try commands.set_target(.{
|
||||||
.clear_color = null,
|
.clear_color = null,
|
||||||
.clear_depth = null,
|
.clear_depth = null,
|
||||||
}});
|
.clear_stencil = null,
|
||||||
|
});
|
||||||
|
|
||||||
try queue.commands.append(.{.instance_2d = .{
|
try commands.draw_texture(.{
|
||||||
.mesh_2d = actors.res.quad_mesh_2d.?,
|
.texture = actors.res.render_texture,
|
||||||
.texture = actors.res.render_texture.?,
|
|
||||||
|
|
||||||
.transform = .{
|
.transform = .{
|
||||||
.origin = .{@floatFromInt(display.res.width / 2), @floatFromInt(display.res.height / 2)},
|
.origin = .{@floatFromInt(config.res.width / 2), @floatFromInt(config.res.height / 2)},
|
||||||
.xbasis = .{@floatFromInt(display.res.width), 0},
|
.xbasis = .{@floatFromInt(config.res.width), 0},
|
||||||
.ybasis = .{0, @floatFromInt(display.res.height)},
|
.ybasis = .{0, @floatFromInt(config.res.height)},
|
||||||
},
|
},
|
||||||
}});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(player: ona.Read(Player), actors: ona.Write(Actors), mapping: ona.Read(ona.act.Mapping)) !void {
|
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");
|
const App = @import("./App.zig");
|
||||||
|
|
||||||
|
pub const colors = @import("./gfx/colors.zig");
|
||||||
|
|
||||||
const coral = @import("coral");
|
const coral = @import("coral");
|
||||||
|
|
||||||
const device = @import("./gfx/device.zig");
|
const bmp = @import("./gfx/bmp.zig");
|
||||||
|
|
||||||
const ext = @import("./ext.zig");
|
const ext = @import("./ext.zig");
|
||||||
|
|
||||||
const flow = @import("flow");
|
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 msg = @import("./msg.zig");
|
||||||
|
|
||||||
|
const spirv = @import("./gfx/spirv.zig");
|
||||||
|
|
||||||
|
const rendering = @import("./gfx/rendering.zig");
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub const AssetFormat = struct {
|
pub const Assets = struct {
|
||||||
extension: []const u8,
|
window: *ext.SDL_Window,
|
||||||
file_desc: *const FileDesc,
|
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,
|
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 {
|
fn deinit(self: *Assets) void {
|
||||||
context: device.Context,
|
rendering.enqueue_work(.shutdown);
|
||||||
formats: coral.stack.Sequential(AssetFormat),
|
self.staging_arena.deinit();
|
||||||
staging_arena: std.heap.ArenaAllocator,
|
self.texture_formats.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create_from_file(
|
fn init() !Assets {
|
||||||
self: *Assets,
|
const window = create: {
|
||||||
storage: coral.files.Storage,
|
const position = ext.SDL_WINDOWPOS_CENTERED;
|
||||||
path: []const u8,
|
const flags = ext.SDL_WINDOW_OPENGL;
|
||||||
) (OpenError || AssetFormat.Error)!*Handle {
|
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 {
|
defer {
|
||||||
const max_cache_size = 536870912;
|
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)) {
|
if (!std.mem.endsWith(u8, path, format.extension)) {
|
||||||
continue;
|
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;
|
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 thread_restriction = .main;
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Color = @Vector(4, f32);
|
pub const Commands = struct {
|
||||||
|
list: *rendering.Commands.List,
|
||||||
|
|
||||||
pub const Command = union (enum) {
|
pub const State = struct {
|
||||||
instance_2d: Instance2D,
|
commands: *rendering.Commands,
|
||||||
target: Target,
|
|
||||||
|
|
||||||
pub const Instance2D = struct {
|
|
||||||
texture: *Handle,
|
|
||||||
mesh_2d: *Handle,
|
|
||||||
transform: Transform2D,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Target = struct {
|
pub fn bind(_: flow.system.BindContext) std.mem.Allocator.Error!State {
|
||||||
texture: ?*Handle = null,
|
var created_commands = coral.asyncio.Future(std.mem.Allocator.Error!*rendering.Commands){};
|
||||||
clear_color: ?Color,
|
|
||||||
clear_depth: ?f32,
|
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) {
|
pub const Config = struct {
|
||||||
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 {
|
|
||||||
width: u16 = 1280,
|
width: u16 = 1280,
|
||||||
height: u16 = 720,
|
height: u16 = 720,
|
||||||
clear_color: Color = colors.black,
|
clear_color: lina.Color = colors.black,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Handle = opaque {};
|
|
||||||
|
|
||||||
pub const Input = union (enum) {
|
pub const Input = union (enum) {
|
||||||
key_up: Key,
|
key_up: Key,
|
||||||
key_down: 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 {
|
pub const Rect = struct {
|
||||||
left: f32,
|
left: f32,
|
||||||
top: f32,
|
top: f32,
|
||||||
|
@ -218,32 +171,7 @@ pub const Rect = struct {
|
||||||
bottom: f32,
|
bottom: f32,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Transform2D = extern struct {
|
pub const Texture = handles.Texture;
|
||||||
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 fn poll(app: flow.Write(App), inputs: msg.Send(Input)) !void {
|
pub fn poll(app: flow.Write(App), inputs: msg.Send(Input)) !void {
|
||||||
var event = @as(ext.SDL_Event, undefined);
|
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;
|
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);
|
errdefer {
|
||||||
std.debug.assert(registered_formats.push_all(&builtin_formats));
|
assets.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
try world.set_resource(Assets{
|
const builtin_texture_formats = [_]Assets.TextureFormat{
|
||||||
.staging_arena = std.heap.ArenaAllocator.init(coral.heap.allocator),
|
.{
|
||||||
.formats = registered_formats,
|
.extension = "bmp",
|
||||||
.context = context,
|
.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.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.exit, flow.system_fn(stop), .{.label = "stop gfx"});
|
||||||
try world.on_event(events.finish, flow.system_fn(synchronize), .{.label = "synchronize gfx"});
|
try world.on_event(events.finish, flow.system_fn(synchronize), .{.label = "synchronize gfx"});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stop(assets: flow.Write(Assets)) void {
|
pub fn stop(assets: flow.Write(Assets)) void {
|
||||||
assets.res.staging_arena.deinit();
|
assets.res.deinit();
|
||||||
assets.res.formats.deinit();
|
|
||||||
assets.res.context.deinit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn synchronize(assets: flow.Write(Assets), display: flow.Read(Display)) !void {
|
pub fn synchronize(assets: flow.Write(Assets), config: flow.Read(Config)) !void {
|
||||||
assets.res.context.submit(.{
|
assets.res.frame_rendered.wait();
|
||||||
.width = display.res.width,
|
assets.res.frame_rendered.reset();
|
||||||
.height = display.res.height,
|
|
||||||
.clear_color = display.res.clear_color,
|
var commands_swapped = std.Thread.ResetEvent{};
|
||||||
.renders = Queue.renders.values,
|
|
||||||
|
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 coral = @import("coral");
|
||||||
|
|
||||||
const gfx = @import("../gfx.zig");
|
const handles = @import("./handles.zig");
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub fn bmp_file_desc(
|
pub fn load_file(arena: *std.heap.ArenaAllocator, storage: coral.files.Storage, path: []const u8) !handles.Texture.Desc {
|
||||||
arena: *std.heap.ArenaAllocator,
|
|
||||||
storage: coral.files.Storage,
|
|
||||||
path: []const u8,
|
|
||||||
) gfx.AssetFormat.Error!gfx.Desc {
|
|
||||||
const header = try storage.read_little(path, 0, extern struct {
|
const header = try storage.read_little(path, 0, extern struct {
|
||||||
type: [2]u8 align (1),
|
type: [2]u8 align (1),
|
||||||
file_size: u32 align (1),
|
file_size: u32 align (1),
|
||||||
|
@ -51,7 +47,7 @@ pub fn bmp_file_desc(
|
||||||
while (buffer_offset < pixels.len) {
|
while (buffer_offset < pixels.len) {
|
||||||
const line = pixels[buffer_offset .. buffer_offset + byte_stride];
|
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;
|
return error.FormatUnsupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,15 +67,13 @@ pub fn bmp_file_desc(
|
||||||
}
|
}
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.texture = .{
|
.format = .rgba8,
|
||||||
.format = .rgba8,
|
|
||||||
|
|
||||||
.access = .{
|
.access = .{
|
||||||
.static = .{
|
.static = .{
|
||||||
.width = pixel_width,
|
.width = pixel_width,
|
||||||
.data = pixels,
|
.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 (location = 0) out vec4 texel;
|
||||||
|
|
||||||
|
layout (binding = 0) readonly buffer View {
|
||||||
|
mat4 projection_matrix;
|
||||||
|
};
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
texel = texture(sprite, uv) * color;
|
texel = texture(sprite, uv) * color;
|
||||||
|
|
|
@ -13,7 +13,7 @@ layout (location = 7) in vec4 instance_rect;
|
||||||
layout (location = 0) out vec4 color;
|
layout (location = 0) out vec4 color;
|
||||||
layout (location = 1) out vec2 uv;
|
layout (location = 1) out vec2 uv;
|
||||||
|
|
||||||
layout (binding = 0) uniform Projection {
|
layout (binding = 0) readonly buffer View {
|
||||||
mat4 projection_matrix;
|
mat4 projection_matrix;
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,14 +12,22 @@ pub const Error = std.mem.Allocator.Error || error {
|
||||||
UnsupportedTarget,
|
UnsupportedTarget,
|
||||||
InvalidSPIRV,
|
InvalidSPIRV,
|
||||||
UnsupportedSPIRV,
|
UnsupportedSPIRV,
|
||||||
|
UnsupportedBackend,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Unit = struct {
|
pub const Unit = struct {
|
||||||
|
arena: std.heap.ArenaAllocator,
|
||||||
context: ext.spvc_context,
|
context: ext.spvc_context,
|
||||||
shader_desc: sokol.gfx.ShaderDesc,
|
shader_desc: sokol.gfx.ShaderDesc,
|
||||||
attrs_used: u32 = 0,
|
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) {
|
const execution_model, const stage_desc = switch (stage) {
|
||||||
.vertex => .{ext.SpvExecutionModelVertex, &self.shader_desc.vs},
|
.vertex => .{ext.SpvExecutionModelVertex, &self.shader_desc.vs},
|
||||||
.fragment => .{ext.SpvExecutionModelFragment, &self.shader_desc.fs},
|
.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},
|
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 => .{
|
.GLCORE => .{
|
||||||
.target = ext.SPVC_BACKEND_GLSL,
|
.target = ext.SPVC_BACKEND_GLSL,
|
||||||
|
|
||||||
|
@ -43,13 +51,13 @@ pub const Unit = struct {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
else => @panic("Unimplemented"),
|
else => error.UnsupportedBackend,
|
||||||
};
|
};
|
||||||
|
|
||||||
const compiler = parse_and_configure: {
|
const compiler = parse_and_configure: {
|
||||||
var parsed_ir: ext.spvc_parsed_ir = null;
|
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;
|
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));
|
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;
|
var binding: u32 = 0;
|
||||||
|
|
||||||
for (combined_image_samplers) |combined_image_sampler| {
|
for (combined_image_samplers) |combined_image_sampler| {
|
||||||
var name_buffer = [_:0]u8{0} ** 255;
|
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))),
|
||||||
const name = coral.utf8.print_formatted(&name_buffer, "{image_name}_{sampler_name}", .{
|
.sampler_name = std.mem.span(@as([*:0]const u8, ext.spvc_compiler_get_name(compiler, combined_image_sampler.sampler_id))),
|
||||||
.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;
|
|
||||||
};
|
|
||||||
|
|
||||||
ext.spvc_compiler_set_name(compiler, combined_image_sampler.combined_id, name);
|
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);
|
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;
|
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 reflect_image_samplers(compiler, resources, stage_desc);
|
||||||
|
|
||||||
try to_error(ext.spvc_compiler_install_compiler_options(compiler, create: {
|
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)));
|
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 {
|
pub fn deinit(self: *Unit) void {
|
||||||
ext.spvc_context_destroy(self.context);
|
ext.spvc_context_destroy(self.context);
|
||||||
|
self.arena.deinit();
|
||||||
|
|
||||||
self.* = undefined;
|
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;
|
var context: ext.spvc_context = null;
|
||||||
|
|
||||||
if (ext.spvc_context_create(&context) != ext.SPVC_SUCCESS) {
|
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);
|
ext.spvc_context_set_error_callback(context, log_context_errors, null);
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
|
.arena = std.heap.ArenaAllocator.init(allocator),
|
||||||
.context = context,
|
.context = context,
|
||||||
|
|
||||||
.shader_desc = .{
|
.shader_desc = .{
|
||||||
|
@ -144,44 +154,33 @@ pub const Stage = enum {
|
||||||
vertex,
|
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 {
|
fn log_context_errors(userdata: ?*anyopaque, error_message: [*c]const u8) callconv(.C) void {
|
||||||
std.debug.assert(userdata == null);
|
std.debug.assert(userdata == null);
|
||||||
std.log.err("{s}", .{error_message});
|
std.log.err("{s}", .{error_message});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reflect_image_samplers(compiler: ext.spvc_compiler, resources: ext.spvc_resources, stage: *sokol.gfx.ShaderStageDesc) Error!void {
|
fn reflect_image_samplers(compiler: ext.spvc_compiler, resources: ext.spvc_resources, stage_desc: *sokol.gfx.ShaderStageDesc) Error!void {
|
||||||
var reflected_sampled_images: []const ext.spvc_reflected_resource = &.{};
|
var sampled_images: []const ext.spvc_reflected_resource = &.{};
|
||||||
|
|
||||||
try to_error(ext.spvc_resources_get_resource_list_for_type(
|
try to_error(ext.spvc_resources_get_resource_list_for_type(
|
||||||
resources,
|
resources,
|
||||||
ext.SPVC_RESOURCE_TYPE_SAMPLED_IMAGE,
|
ext.SPVC_RESOURCE_TYPE_SAMPLED_IMAGE,
|
||||||
@ptrCast(&reflected_sampled_images.ptr),
|
@ptrCast(&sampled_images.ptr),
|
||||||
&reflected_sampled_images.len,
|
&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;
|
return error.UnsupportedSPIRV;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (0 .. reflected_sampled_images.len, reflected_sampled_images) |i, reflected_sampled_image| {
|
for (0 .. sampled_images.len, sampled_images) |i, sampled_image| {
|
||||||
const sampled_image_type = ext.spvc_compiler_get_type_handle(compiler, reflected_sampled_image.type_id);
|
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) {
|
if (ext.spvc_type_get_basetype(sampled_image_type) != ext.SPVC_BASETYPE_SAMPLED_IMAGE) {
|
||||||
return error.InvalidSPIRV;
|
return error.InvalidSPIRV;
|
||||||
}
|
}
|
||||||
|
|
||||||
stage.images[i] = .{
|
stage_desc.images[i] = .{
|
||||||
.multisampled = ext.spvc_type_get_image_multisampled(sampled_image_type) != 0,
|
.multisampled = ext.spvc_type_get_image_multisampled(sampled_image_type) != 0,
|
||||||
|
|
||||||
.image_type = try switch (ext.spvc_type_get_image_dimension(sampled_image_type)) {
|
.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,
|
.used = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
stage.samplers[i] = .{
|
stage_desc.samplers[i] = .{
|
||||||
.sampler_type = .DEFAULT,
|
.sampler_type = .DEFAULT,
|
||||||
.used = true,
|
.used = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
stage.image_sampler_pairs[i] = .{
|
stage_desc.image_sampler_pairs[i] = .{
|
||||||
.glsl_name = ext.spvc_compiler_get_name(compiler, reflected_sampled_image.id),
|
.glsl_name = ext.spvc_compiler_get_name(compiler, sampled_image.id),
|
||||||
.image_slot = @intCast(i),
|
.image_slot = @intCast(i),
|
||||||
.sampler_slot = @intCast(i),
|
.sampler_slot = @intCast(i),
|
||||||
.used = true,
|
.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 {
|
fn reflect_storage_buffers(compiler: ext.spvc_compiler, resources: ext.spvc_resources, stage_desc: *sokol.gfx.ShaderStageDesc) Error!void {
|
||||||
var reflected_uniform_buffers: []const ext.spvc_reflected_resource = &.{};
|
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(
|
try to_error(ext.spvc_resources_get_resource_list_for_type(
|
||||||
resources,
|
resources,
|
||||||
ext.SPVC_RESOURCE_TYPE_UNIFORM_BUFFER,
|
ext.SPVC_RESOURCE_TYPE_UNIFORM_BUFFER,
|
||||||
@ptrCast(&reflected_uniform_buffers.ptr),
|
@ptrCast(&uniform_buffers.ptr),
|
||||||
&reflected_uniform_buffers.len,
|
&uniform_buffers.len,
|
||||||
));
|
));
|
||||||
|
|
||||||
if (reflected_uniform_buffers.len > stage.uniform_blocks.len) {
|
if (uniform_buffers.len != 0) {
|
||||||
return error.UnsupportedSPIRV;
|
return error.InvalidSPIRV;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (stage.uniform_blocks[0 .. reflected_uniform_buffers.len], reflected_uniform_buffers) |*uniform_block, reflected_uniform_buffer| {
|
// TODO: Support for older APIs?
|
||||||
const uniform_buffer_type = ext.spvc_compiler_get_type_handle(compiler, reflected_uniform_buffer.type_id);
|
_ = stage_desc;
|
||||||
|
_ = compiler;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_error(result: ext.spvc_result) Error!void {
|
fn to_error(result: ext.spvc_result) Error!void {
|
||||||
|
|
Loading…
Reference in New Issue