renderer-mvp/post-processing #56
14
build.zig
14
build.zig
|
@ -194,17 +194,7 @@ pub fn build(b: *std.Build) !void {
|
||||||
const vertex_binary_sub_path_utf8 = vertex_binary_sub_path.utf8();
|
const vertex_binary_sub_path_utf8 = vertex_binary_sub_path.utf8();
|
||||||
const fragment_binary_sub_path_utf8 = fragment_binary_sub_path.utf8();
|
const fragment_binary_sub_path_utf8 = fragment_binary_sub_path.utf8();
|
||||||
|
|
||||||
const link_command = b.addSystemCommand(&.{
|
exe.step.dependOn(compile_vertex: {
|
||||||
"spirv-link",
|
|
||||||
vertex_binary_sub_path_utf8,
|
|
||||||
fragment_binary_sub_path_utf8,
|
|
||||||
"-o",
|
|
||||||
pending_shader.binary_sub_path.utf8(),
|
|
||||||
});
|
|
||||||
|
|
||||||
exe.step.dependOn(&link_command.step);
|
|
||||||
|
|
||||||
link_command.step.dependOn(compile_vertex: {
|
|
||||||
const compile_command = b.addSystemCommand(&.{
|
const compile_command = b.addSystemCommand(&.{
|
||||||
"glslangValidator",
|
"glslangValidator",
|
||||||
"-V",
|
"-V",
|
||||||
|
@ -216,7 +206,7 @@ pub fn build(b: *std.Build) !void {
|
||||||
break: compile_vertex &compile_command.step;
|
break: compile_vertex &compile_command.step;
|
||||||
});
|
});
|
||||||
|
|
||||||
link_command.step.dependOn(compile_fragment: {
|
exe.step.dependOn(compile_fragment: {
|
||||||
const compile_command = b.addSystemCommand(&.{
|
const compile_command = b.addSystemCommand(&.{
|
||||||
"glslangValidator",
|
"glslangValidator",
|
||||||
"-V",
|
"-V",
|
||||||
|
|
|
@ -7,22 +7,19 @@ layout (location = 1) in vec2 uv;
|
||||||
|
|
||||||
layout (location = 0) out vec4 texel;
|
layout (location = 0) out vec4 texel;
|
||||||
|
|
||||||
layout (binding = 0) readonly buffer Camera {
|
layout (binding = 0) readonly buffer Effect {
|
||||||
mat4 projection_matrix;
|
|
||||||
};
|
|
||||||
|
|
||||||
layout (binding = 1) readonly buffer Material {
|
|
||||||
float effect_magnitude;
|
float effect_magnitude;
|
||||||
};
|
};
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
const vec2 red_channel_uv = uv + vec2(effect_magnitude, 0.0);
|
vec4 color1 = texture(sprite, uv) / 3.0;
|
||||||
const vec2 blue_channel_uv = uv + vec2(-effect_magnitude, 0.0);
|
vec4 color2 = texture(sprite, uv + 0.002 * effect_magnitude) / 3.0;
|
||||||
const vec4 original_texel = texture(sprite, uv);
|
vec4 color3 = texture(sprite, uv - 0.002 * effect_magnitude) / 3.0;
|
||||||
|
|
||||||
texel = vec4(texture(sprite, red_channel_uv).r, original_texel.g, texture(sprite, blue_channel_uv).b, original_texel.a) * color;
|
color1 *= 2.0;
|
||||||
|
color2.g = 0.0;
|
||||||
|
color2.b = 0.0;
|
||||||
|
color3.r = 0.0;
|
||||||
|
|
||||||
if (texel.a == 0) {
|
texel = color * (color1 + color2 + color3);
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
32
src/main.zig
32
src/main.zig
|
@ -4,10 +4,15 @@ const std = @import("std");
|
||||||
|
|
||||||
const ona = @import("ona");
|
const ona = @import("ona");
|
||||||
|
|
||||||
|
const ChromaticAberration = extern struct {
|
||||||
|
magnitude: f32,
|
||||||
|
};
|
||||||
|
|
||||||
const Actors = struct {
|
const Actors = struct {
|
||||||
instances: coral.stack.Sequential(@Vector(2, f32)) = .{.allocator = coral.heap.allocator},
|
instances: coral.stack.Sequential(@Vector(2, f32)) = .{.allocator = coral.heap.allocator},
|
||||||
body_texture: ona.gfx.Texture = .default,
|
body_texture: ona.gfx.Texture = .default,
|
||||||
render_texture: ona.gfx.Texture = .default,
|
render_texture: ona.gfx.Texture = .default,
|
||||||
|
ca_effect: ona.gfx.Effect = .default,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Player = struct {
|
const Player = struct {
|
||||||
|
@ -37,6 +42,8 @@ fn load(config: ona.Write(ona.gfx.Config), actors: ona.Write(Actors), assets: on
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
actors.res.ca_effect = try assets.res.load_effect_file(coral.files.bundle, "./ca.frag.spv");
|
||||||
|
|
||||||
try actors.res.instances.push_grow(.{0, 0});
|
try actors.res.instances.push_grow(.{0, 0});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,12 +51,9 @@ fn exit(actors: ona.Write(Actors)) void {
|
||||||
actors.res.instances.deinit();
|
actors.res.instances.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(commands: ona.gfx.Commands, actors: ona.Write(Actors), config: ona.Read(ona.gfx.Config)) !void {
|
fn render(commands: ona.gfx.Commands, actors: ona.Write(Actors)) !void {
|
||||||
try commands.set_target(.{
|
try commands.set_effect(actors.res.ca_effect, ChromaticAberration{
|
||||||
.texture = actors.res.render_texture,
|
.magnitude = 15.0,
|
||||||
.clear_color = .{0, 0, 0, 0},
|
|
||||||
.clear_depth = 0,
|
|
||||||
.clear_stencil = 0,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
for (actors.res.instances.values) |instance| {
|
for (actors.res.instances.values) |instance| {
|
||||||
|
@ -63,22 +67,6 @@ fn render(commands: ona.gfx.Commands, actors: ona.Write(Actors), config: ona.Rea
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
try commands.set_target(.{
|
|
||||||
.clear_color = null,
|
|
||||||
.clear_depth = null,
|
|
||||||
.clear_stencil = null,
|
|
||||||
});
|
|
||||||
|
|
||||||
try commands.draw_texture(.{
|
|
||||||
.texture = actors.res.render_texture,
|
|
||||||
|
|
||||||
.transform = .{
|
|
||||||
.origin = .{@floatFromInt(config.res.width / 2), @floatFromInt(config.res.height / 2)},
|
|
||||||
.xbasis = .{@floatFromInt(config.res.width), 0},
|
|
||||||
.ybasis = .{0, @floatFromInt(config.res.height)},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(player: ona.Read(Player), actors: ona.Write(Actors), mapping: ona.Read(ona.act.Mapping)) !void {
|
fn update(player: ona.Read(Player), actors: ona.Write(Actors), mapping: ona.Read(ona.act.Mapping)) !void {
|
||||||
|
|
|
@ -25,12 +25,11 @@ const std = @import("std");
|
||||||
pub const Assets = struct {
|
pub const Assets = struct {
|
||||||
window: *ext.SDL_Window,
|
window: *ext.SDL_Window,
|
||||||
texture_formats: coral.stack.Sequential(TextureFormat),
|
texture_formats: coral.stack.Sequential(TextureFormat),
|
||||||
staging_arena: std.heap.ArenaAllocator,
|
|
||||||
frame_rendered: std.Thread.ResetEvent = .{},
|
frame_rendered: std.Thread.ResetEvent = .{},
|
||||||
|
|
||||||
pub const LoadError = std.mem.Allocator.Error;
|
pub const LoadError = std.mem.Allocator.Error;
|
||||||
|
|
||||||
pub const LoadFileError = LoadError || coral.files.AccessError || error {
|
pub const LoadFileError = LoadError || coral.files.ReadAllError || error {
|
||||||
FormatUnsupported,
|
FormatUnsupported,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -41,7 +40,6 @@ pub const Assets = struct {
|
||||||
|
|
||||||
fn deinit(self: *Assets) void {
|
fn deinit(self: *Assets) void {
|
||||||
rendering.enqueue_work(.shutdown);
|
rendering.enqueue_work(.shutdown);
|
||||||
self.staging_arena.deinit();
|
|
||||||
self.texture_formats.deinit();
|
self.texture_formats.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,12 +62,40 @@ pub const Assets = struct {
|
||||||
try rendering.startup(window);
|
try rendering.startup(window);
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.staging_arena = std.heap.ArenaAllocator.init(coral.heap.allocator),
|
|
||||||
.texture_formats = .{.allocator = coral.heap.allocator},
|
.texture_formats = .{.allocator = coral.heap.allocator},
|
||||||
.window = window,
|
.window = window,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn load_effect_file(_: *Assets, storage: coral.files.Storage, path: []const u8) LoadFileError!Effect {
|
||||||
|
if (!std.mem.endsWith(u8, path, ".spv")) {
|
||||||
|
return error.FormatUnsupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fragment_file_stat = try storage.stat(path);
|
||||||
|
const fragment_spirv = try coral.heap.allocator.alloc(u32, fragment_file_stat.size / @alignOf(u32));
|
||||||
|
|
||||||
|
defer {
|
||||||
|
coral.heap.allocator.free(fragment_spirv);
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = try storage.read_all(path, std.mem.sliceAsBytes(fragment_spirv), .{});
|
||||||
|
|
||||||
|
var loaded = coral.asyncio.Future(std.mem.Allocator.Error!Effect){};
|
||||||
|
|
||||||
|
rendering.enqueue_work(.{
|
||||||
|
.load_effect = .{
|
||||||
|
.desc = .{
|
||||||
|
.fragment_spirv = fragment_spirv,
|
||||||
|
},
|
||||||
|
|
||||||
|
.loaded = &loaded,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return loaded.get().*;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn load_texture(_: *Assets, desc: Texture.Desc) std.mem.Allocator.Error!Texture {
|
pub fn load_texture(_: *Assets, desc: Texture.Desc) std.mem.Allocator.Error!Texture {
|
||||||
var loaded = coral.asyncio.Future(std.mem.Allocator.Error!Texture){};
|
var loaded = coral.asyncio.Future(std.mem.Allocator.Error!Texture){};
|
||||||
|
|
||||||
|
@ -84,12 +110,10 @@ pub const Assets = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_texture_file(self: *Assets, storage: coral.files.Storage, path: []const u8) LoadFileError!Texture {
|
pub fn load_texture_file(self: *Assets, storage: coral.files.Storage, path: []const u8) LoadFileError!Texture {
|
||||||
defer {
|
var arena = std.heap.ArenaAllocator.init(coral.heap.allocator);
|
||||||
const max_cache_size = 536870912;
|
|
||||||
|
|
||||||
if (!self.staging_arena.reset(.{.retain_with_limit = max_cache_size})) {
|
defer {
|
||||||
std.log.warn("failed to retain staging arena size of {} bytes", .{max_cache_size});
|
arena.deinit();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (self.texture_formats.values) |format| {
|
for (self.texture_formats.values) |format| {
|
||||||
|
@ -97,12 +121,26 @@ pub const Assets = struct {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.load_texture(try format.load_file(&self.staging_arena, storage, path));
|
return self.load_texture(try format.load_file(&arena, storage, path));
|
||||||
}
|
}
|
||||||
|
|
||||||
return error.FormatUnsupported;
|
return error.FormatUnsupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_effect(_: *Assets, effect: Effect, properties: anytype) bool {
|
||||||
|
var setted = coral.asyncio.Future(bool){};
|
||||||
|
|
||||||
|
rendering.enqueue_work(.{
|
||||||
|
.update_effect = .{
|
||||||
|
.properties = std.mem.asBytes(properties),
|
||||||
|
.effect = effect,
|
||||||
|
.setted = &setted,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return setted.get().*;
|
||||||
|
}
|
||||||
|
|
||||||
pub const thread_restriction = .main;
|
pub const thread_restriction = .main;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -137,6 +175,15 @@ pub const Commands = struct {
|
||||||
try self.list.append(.{.draw_texture = command});
|
try self.list.append(.{.draw_texture = command});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_effect(self: Commands, effect: handles.Effect, properties: anytype) std.mem.Allocator.Error!void {
|
||||||
|
try self.list.append(.{
|
||||||
|
.set_effect = .{
|
||||||
|
.properties = std.mem.asBytes(&properties),
|
||||||
|
.effect = effect,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_target(self: Commands, command: rendering.Command.SetTarget) std.mem.Allocator.Error!void {
|
pub fn set_target(self: Commands, command: rendering.Command.SetTarget) std.mem.Allocator.Error!void {
|
||||||
try self.list.append(.{.set_target = command});
|
try self.list.append(.{.set_target = command});
|
||||||
}
|
}
|
||||||
|
@ -164,6 +211,8 @@ pub const Input = union (enum) {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const Effect = handles.Effect;
|
||||||
|
|
||||||
pub const Rect = struct {
|
pub const Rect = struct {
|
||||||
left: f32,
|
left: f32,
|
||||||
top: f32,
|
top: f32,
|
||||||
|
|
|
@ -2,6 +2,10 @@ const coral = @import("coral");
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const Effect = Handle(struct {
|
||||||
|
fragment_spirv: []const u32,
|
||||||
|
});
|
||||||
|
|
||||||
fn Handle(comptime HandleDesc: type) type {
|
fn Handle(comptime HandleDesc: type) type {
|
||||||
return enum (u32) {
|
return enum (u32) {
|
||||||
default,
|
default,
|
||||||
|
|
|
@ -16,6 +16,7 @@ const std = @import("std");
|
||||||
|
|
||||||
pub const Command = union (enum) {
|
pub const Command = union (enum) {
|
||||||
draw_texture: DrawTexture,
|
draw_texture: DrawTexture,
|
||||||
|
set_effect: SetEffect,
|
||||||
set_target: SetTarget,
|
set_target: SetTarget,
|
||||||
|
|
||||||
pub const DrawTexture = struct {
|
pub const DrawTexture = struct {
|
||||||
|
@ -23,6 +24,11 @@ pub const Command = union (enum) {
|
||||||
transform: lina.Transform2D,
|
transform: lina.Transform2D,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const SetEffect = struct {
|
||||||
|
effect: handles.Effect,
|
||||||
|
properties: []const coral.io.Byte,
|
||||||
|
};
|
||||||
|
|
||||||
pub const SetTarget = struct {
|
pub const SetTarget = struct {
|
||||||
texture: ?handles.Texture = null,
|
texture: ?handles.Texture = null,
|
||||||
clear_color: ?lina.Color,
|
clear_color: ?lina.Color,
|
||||||
|
@ -31,10 +37,16 @@ pub const Command = union (enum) {
|
||||||
};
|
};
|
||||||
|
|
||||||
fn clone(self: Command, arena: *std.heap.ArenaAllocator) !Command {
|
fn clone(self: Command, arena: *std.heap.ArenaAllocator) !Command {
|
||||||
_ = arena;
|
|
||||||
|
|
||||||
return switch (self) {
|
return switch (self) {
|
||||||
.draw_texture => |draw_texture| .{.draw_texture = draw_texture},
|
.draw_texture => |draw_texture| .{.draw_texture = draw_texture},
|
||||||
|
|
||||||
|
.set_effect => |set_effect| .{
|
||||||
|
.set_effect = .{
|
||||||
|
.properties = try arena.allocator().dupe(coral.io.Byte, set_effect.properties),
|
||||||
|
.effect = set_effect.effect,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
.set_target => |set_target| .{.set_target = set_target},
|
.set_target => |set_target| .{.set_target = set_target},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -42,6 +54,7 @@ pub const Command = union (enum) {
|
||||||
fn process(self: Command, pools: *Pools, frame: *Frame) !void {
|
fn process(self: Command, pools: *Pools, frame: *Frame) !void {
|
||||||
try switch (self) {
|
try switch (self) {
|
||||||
.draw_texture => |draw_texture| frame.draw_texture(pools, draw_texture),
|
.draw_texture => |draw_texture| frame.draw_texture(pools, draw_texture),
|
||||||
|
.set_effect => |set_effect| frame.set_effect(pools, set_effect),
|
||||||
.set_target => |set_target| frame.set_target(pools, set_target),
|
.set_target => |set_target| frame.set_target(pools, set_target),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -123,6 +136,7 @@ const Frame = struct {
|
||||||
flushed_count: usize = 0,
|
flushed_count: usize = 0,
|
||||||
current_source_texture: handles.Texture = .default,
|
current_source_texture: handles.Texture = .default,
|
||||||
current_target_texture: ?handles.Texture = null,
|
current_target_texture: ?handles.Texture = null,
|
||||||
|
current_effect: handles.Effect = .default,
|
||||||
|
|
||||||
const DrawTexture = extern struct {
|
const DrawTexture = extern struct {
|
||||||
transform: lina.Transform2D,
|
transform: lina.Transform2D,
|
||||||
|
@ -175,29 +189,29 @@ const Frame = struct {
|
||||||
.render => |render| {
|
.render => |render| {
|
||||||
bindings.fs.images[0] = render.color_image;
|
bindings.fs.images[0] = render.color_image;
|
||||||
bindings.fs.samplers[0] = render.sampler;
|
bindings.fs.samplers[0] = render.sampler;
|
||||||
bindings.vs.images[0] = render.color_image;
|
|
||||||
bindings.vs.samplers[0] = render.sampler;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
.static => |static| {
|
.static => |static| {
|
||||||
bindings.fs.images[0] = static.image;
|
bindings.fs.images[0] = static.image;
|
||||||
bindings.fs.samplers[0] = static.sampler;
|
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| {
|
if (self.current_target_texture) |target_texture| {
|
||||||
const target_view_buffer = pools.textures.get(@intFromEnum(target_texture)).?.render.view_buffer;
|
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;
|
bindings.vs.storage_buffers[0] = target_view_buffer;
|
||||||
} else {
|
} else {
|
||||||
bindings.fs.storage_buffers[0] = view_buffer;
|
|
||||||
bindings.vs.storage_buffers[0] = view_buffer;
|
bindings.vs.storage_buffers[0] = view_buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
sokol.gfx.applyPipeline(texture_batching_pipeline);
|
const effect = pools.effects.get(@intFromEnum(self.current_effect)).?;
|
||||||
|
|
||||||
|
sokol.gfx.applyPipeline(effect.pipeline);
|
||||||
|
|
||||||
|
if (effect.has_properties_buffer) |properties_buffer| {
|
||||||
|
bindings.fs.storage_buffers[0] = properties_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const buffer_index = self.flushed_count / batches_per_buffer;
|
const buffer_index = self.flushed_count / batches_per_buffer;
|
||||||
|
@ -217,11 +231,29 @@ const Frame = struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_effect(self: *Frame, pools: *Pools, command: Command.SetEffect) void {
|
||||||
|
if (command.effect != self.current_effect) {
|
||||||
|
self.flush(pools);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.current_effect = command.effect;
|
||||||
|
|
||||||
|
if (command.properties.len != 0) {
|
||||||
|
if (pools.effects.get(@intFromEnum(self.current_effect)).?.has_properties_buffer) |properties_buffer| {
|
||||||
|
sokol.gfx.updateBuffer(properties_buffer, sokol.gfx.asRange(command.properties));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_target(self: *Frame, pools: *Pools, command: Command.SetTarget) void {
|
pub fn set_target(self: *Frame, pools: *Pools, command: Command.SetTarget) void {
|
||||||
sokol.gfx.endPass();
|
sokol.gfx.endPass();
|
||||||
|
|
||||||
var pass = sokol.gfx.Pass{
|
var pass = sokol.gfx.Pass{
|
||||||
.action = .{.stencil = .{.load_action = .CLEAR}},
|
.action = .{
|
||||||
|
.stencil = .{
|
||||||
|
.load_action = .CLEAR,
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (command.clear_color) |color| {
|
if (command.clear_color) |color| {
|
||||||
|
@ -265,15 +297,24 @@ const Frame = struct {
|
||||||
var texture_batch_buffers = coral.stack.Sequential(sokol.gfx.Buffer){.allocator = coral.heap.allocator};
|
var texture_batch_buffers = coral.stack.Sequential(sokol.gfx.Buffer){.allocator = coral.heap.allocator};
|
||||||
|
|
||||||
const batches_per_buffer = 512;
|
const batches_per_buffer = 512;
|
||||||
|
|
||||||
var texture_batching_pipeline: sokol.gfx.Pipeline = undefined;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const Pools = struct {
|
const Pools = struct {
|
||||||
|
effects: EffectPool,
|
||||||
textures: TexturePool,
|
textures: TexturePool,
|
||||||
|
|
||||||
|
const EffectPool = coral.Pool(resources.Effect);
|
||||||
|
|
||||||
const TexturePool = coral.Pool(resources.Texture);
|
const TexturePool = coral.Pool(resources.Texture);
|
||||||
|
|
||||||
|
fn create_effect(self: *Pools, desc: handles.Effect.Desc) !handles.Effect {
|
||||||
|
var effect = try resources.Effect.init(desc);
|
||||||
|
|
||||||
|
errdefer effect.deinit();
|
||||||
|
|
||||||
|
return @enumFromInt(try self.effects.insert(effect));
|
||||||
|
}
|
||||||
|
|
||||||
fn create_texture(self: *Pools, desc: handles.Texture.Desc) !handles.Texture {
|
fn create_texture(self: *Pools, desc: handles.Texture.Desc) !handles.Texture {
|
||||||
var texture = try resources.Texture.init(desc);
|
var texture = try resources.Texture.init(desc);
|
||||||
|
|
||||||
|
@ -292,12 +333,28 @@ const Pools = struct {
|
||||||
self.textures.deinit();
|
self.textures.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn destroy_texture(self: *Pools, texture_key: handles.Texture) bool {
|
fn destroy_effect(self: *Pools, handle: handles.Effect) bool {
|
||||||
switch (texture_key) {
|
switch (handle) {
|
||||||
.default => {},
|
.default => {},
|
||||||
|
|
||||||
else => {
|
else => {
|
||||||
var texture = self.textures.remove(@intFromEnum(texture_key)) orelse {
|
var effect = self.effects.remove(@intFromEnum(handle)) orelse {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
effect.deinit();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn destroy_texture(self: *Pools, handle: handles.Texture) bool {
|
||||||
|
switch (handle) {
|
||||||
|
.default => {},
|
||||||
|
|
||||||
|
else => {
|
||||||
|
var texture = self.textures.remove(@intFromEnum(handle)) orelse {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -310,10 +367,17 @@ const Pools = struct {
|
||||||
|
|
||||||
fn init(allocator: std.mem.Allocator) !Pools {
|
fn init(allocator: std.mem.Allocator) !Pools {
|
||||||
var pools = Pools{
|
var pools = Pools{
|
||||||
|
.effects = EffectPool.init(allocator),
|
||||||
.textures = TexturePool.init(allocator),
|
.textures = TexturePool.init(allocator),
|
||||||
};
|
};
|
||||||
|
|
||||||
errdefer pools.deinit();
|
errdefer {
|
||||||
|
pools.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = try pools.create_effect(.{
|
||||||
|
.fragment_spirv = &spirv.to_ops(@embedFile("./shaders/2d_default.frag.spv")),
|
||||||
|
});
|
||||||
|
|
||||||
const default_texture_data = [_]u32{
|
const default_texture_data = [_]u32{
|
||||||
0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF,
|
0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF,
|
||||||
|
@ -343,16 +407,23 @@ const Pools = struct {
|
||||||
|
|
||||||
pub const Work = union (enum) {
|
pub const Work = union (enum) {
|
||||||
create_commands: CreateCommandsWork,
|
create_commands: CreateCommandsWork,
|
||||||
|
load_effect: LoadEffectWork,
|
||||||
load_texture: LoadTextureWork,
|
load_texture: LoadTextureWork,
|
||||||
render_frame: RenderFrameWork,
|
render_frame: RenderFrameWork,
|
||||||
rotate_commands: RotateCommandsWork,
|
rotate_commands: RotateCommandsWork,
|
||||||
shutdown,
|
shutdown,
|
||||||
|
unload_effect: UnloadEffectWork,
|
||||||
unload_texture: UnloadTextureWork,
|
unload_texture: UnloadTextureWork,
|
||||||
|
|
||||||
pub const CreateCommandsWork = struct {
|
pub const CreateCommandsWork = struct {
|
||||||
created: *coral.asyncio.Future(std.mem.Allocator.Error!*Commands),
|
created: *coral.asyncio.Future(std.mem.Allocator.Error!*Commands),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const LoadEffectWork = struct {
|
||||||
|
desc: handles.Effect.Desc,
|
||||||
|
loaded: *coral.asyncio.Future(std.mem.Allocator.Error!handles.Effect),
|
||||||
|
};
|
||||||
|
|
||||||
pub const LoadTextureWork = struct {
|
pub const LoadTextureWork = struct {
|
||||||
desc: handles.Texture.Desc,
|
desc: handles.Texture.Desc,
|
||||||
loaded: *coral.asyncio.Future(std.mem.Allocator.Error!handles.Texture),
|
loaded: *coral.asyncio.Future(std.mem.Allocator.Error!handles.Texture),
|
||||||
|
@ -369,6 +440,10 @@ pub const Work = union (enum) {
|
||||||
finished: *std.Thread.ResetEvent,
|
finished: *std.Thread.ResetEvent,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const UnloadEffectWork = struct {
|
||||||
|
handle: handles.Effect,
|
||||||
|
};
|
||||||
|
|
||||||
pub const UnloadTextureWork = struct {
|
pub const UnloadTextureWork = struct {
|
||||||
handle: handles.Texture,
|
handle: handles.Texture,
|
||||||
};
|
};
|
||||||
|
@ -395,62 +470,6 @@ const vertex_indices = .{
|
||||||
.instance = 1,
|
.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 {
|
fn run(window: *ext.SDL_Window) !void {
|
||||||
const context = configure_and_create: {
|
const context = configure_and_create: {
|
||||||
var result = @as(c_int, 0);
|
var result = @as(c_int, 0);
|
||||||
|
@ -530,21 +549,6 @@ fn run(window: *ext.SDL_Window) !void {
|
||||||
.usage = .IMMUTABLE,
|
.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_head: ?*Commands = null;
|
||||||
var has_commands_tail: ?*Commands = null;
|
var has_commands_tail: ?*Commands = null;
|
||||||
|
|
||||||
|
@ -575,6 +579,14 @@ fn run(window: *ext.SDL_Window) !void {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
.load_effect => |load| {
|
||||||
|
const effect = try pools.create_effect(load.desc);
|
||||||
|
|
||||||
|
if (!load.loaded.resolve(effect)) {
|
||||||
|
std.debug.assert(pools.destroy_effect(effect));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
.load_texture => |load| {
|
.load_texture => |load| {
|
||||||
const texture = try pools.create_texture(load.desc);
|
const texture = try pools.create_texture(load.desc);
|
||||||
|
|
||||||
|
@ -650,6 +662,12 @@ fn run(window: *ext.SDL_Window) !void {
|
||||||
break;
|
break;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
.unload_effect => |unload| {
|
||||||
|
if (!pools.destroy_effect(unload.handle)) {
|
||||||
|
@panic("Attempt to unload a non-existent effect");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
.unload_texture => |unload| {
|
.unload_texture => |unload| {
|
||||||
if (!pools.destroy_texture(unload.handle)) {
|
if (!pools.destroy_texture(unload.handle)) {
|
||||||
@panic("Attempt to unload a non-existent texture");
|
@panic("Attempt to unload a non-existent texture");
|
||||||
|
|
|
@ -10,6 +10,140 @@ const spirv = @import("./spirv.zig");
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const Effect = struct {
|
||||||
|
shader: sokol.gfx.Shader,
|
||||||
|
pipeline: sokol.gfx.Pipeline,
|
||||||
|
has_properties_buffer: ?sokol.gfx.Buffer = null,
|
||||||
|
|
||||||
|
pub fn deinit(self: *Effect) void {
|
||||||
|
sokol.gfx.destroyPipeline(self.pipeline);
|
||||||
|
sokol.gfx.destroyShader(self.shader);
|
||||||
|
|
||||||
|
if (self.has_properties_buffer) |parameters_buffer| {
|
||||||
|
sokol.gfx.destroyBuffer(parameters_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(desc: handles.Effect.Desc) !Effect {
|
||||||
|
var spirv_unit = try spirv.Unit.init(coral.heap.allocator);
|
||||||
|
|
||||||
|
defer spirv_unit.deinit();
|
||||||
|
|
||||||
|
try spirv_unit.compile(&spirv.to_ops(@embedFile("./shaders/2d_default.vert.spv")), .vertex);
|
||||||
|
try spirv_unit.compile(desc.fragment_spirv, .fragment);
|
||||||
|
|
||||||
|
const shader = sokol.gfx.makeShader(spirv_unit.shader_desc());
|
||||||
|
|
||||||
|
const pipeline = sokol.gfx.makePipeline(pipeline_desc: {
|
||||||
|
var pipeline_desc = sokol.gfx.PipelineDesc{
|
||||||
|
.label = "Effect pipeline",
|
||||||
|
.layout = vertex_layout_state,
|
||||||
|
.shader = shader,
|
||||||
|
.index_type = .UINT16,
|
||||||
|
.blend_color = .{.r = 1.0, .g = 1.0, .b = 1.0, .a = 1.0},
|
||||||
|
};
|
||||||
|
|
||||||
|
pipeline_desc.colors[0] = .{
|
||||||
|
.write_mask = .RGBA,
|
||||||
|
|
||||||
|
.blend = .{
|
||||||
|
.enabled = true,
|
||||||
|
.src_factor_rgb = .SRC_ALPHA,
|
||||||
|
.dst_factor_rgb = .ONE_MINUS_SRC_ALPHA,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
break: pipeline_desc pipeline_desc;
|
||||||
|
});
|
||||||
|
|
||||||
|
errdefer {
|
||||||
|
sokol.gfx.destroyPipeline(pipeline);
|
||||||
|
sokol.gfx.destroyShader(shader);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spirv_unit.shader_stage(.fragment).has_storage_buffer[0]) |storage_buffer| {
|
||||||
|
const properties_buffer = sokol.gfx.makeBuffer(.{
|
||||||
|
.size = storage_buffer.minimum_size,
|
||||||
|
.type = .STORAGEBUFFER,
|
||||||
|
.usage = .STREAM,
|
||||||
|
});
|
||||||
|
|
||||||
|
errdefer {
|
||||||
|
sokol.gfx.destroyBuffer(properties_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.shader = shader,
|
||||||
|
.pipeline = pipeline,
|
||||||
|
.has_properties_buffer = properties_buffer,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return .{
|
||||||
|
.shader = shader,
|
||||||
|
.pipeline = pipeline,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const vertex_layout_state = sokol.gfx.VertexLayoutState{
|
||||||
|
.attrs = get: {
|
||||||
|
var attrs = [_]sokol.gfx.VertexAttrState{.{}} ** 16;
|
||||||
|
|
||||||
|
attrs[0] = .{
|
||||||
|
.format = .FLOAT2,
|
||||||
|
.buffer_index = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
attrs[1] = .{
|
||||||
|
.format = .FLOAT2,
|
||||||
|
.buffer_index = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
attrs[2] = .{
|
||||||
|
.format = .FLOAT2,
|
||||||
|
.buffer_index = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
attrs[3] = .{
|
||||||
|
.format = .FLOAT2,
|
||||||
|
.buffer_index = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
attrs[4] = .{
|
||||||
|
.format = .FLOAT2,
|
||||||
|
.buffer_index = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
attrs[5] = .{
|
||||||
|
.format = .UBYTE4N,
|
||||||
|
.buffer_index = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
attrs[6] = .{
|
||||||
|
.format = .FLOAT,
|
||||||
|
.buffer_index = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
attrs[7] = .{
|
||||||
|
.format = .FLOAT4,
|
||||||
|
.buffer_index = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
break: get attrs;
|
||||||
|
},
|
||||||
|
|
||||||
|
.buffers = get: {
|
||||||
|
var vertex_buffer_layouts = [_]sokol.gfx.VertexBufferLayoutState{.{}} ** 8;
|
||||||
|
|
||||||
|
vertex_buffer_layouts[1].step_func = .PER_INSTANCE;
|
||||||
|
|
||||||
|
break: get vertex_buffer_layouts;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
pub const Texture = union (enum) {
|
pub const Texture = union (enum) {
|
||||||
render: Render,
|
render: Render,
|
||||||
static: Static,
|
static: Static,
|
||||||
|
|
|
@ -7,10 +7,6 @@ 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;
|
||||||
|
|
||||||
|
|
|
@ -15,22 +15,225 @@ pub const Error = std.mem.Allocator.Error || error {
|
||||||
UnsupportedBackend,
|
UnsupportedBackend,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Unit = struct {
|
pub const Shader = struct {
|
||||||
arena: std.heap.ArenaAllocator,
|
decompiled_source: [:0]const u8 = "",
|
||||||
context: ext.spvc_context,
|
has_storage_buffer: [max_storage_buffers]?StorageBuffer = [_]?StorageBuffer{null} ** max_storage_buffers,
|
||||||
shader_desc: sokol.gfx.ShaderDesc,
|
has_image: [max_images]?Image = [_]?Image{null} ** max_images,
|
||||||
attrs_used: u32 = 0,
|
has_sampler: [max_samplers]?Sampler = [_]?Sampler{null} ** max_samplers,
|
||||||
|
has_image_sampler_pair: [max_image_sampler_pairs]?ImageSamplerPair = [_]?ImageSamplerPair{null} ** max_image_sampler_pairs,
|
||||||
|
|
||||||
pub fn compile(self: *Unit, spirv_data: []const u8, stage: Stage) Error!void {
|
pub const Image = struct {
|
||||||
if ((spirv_data.len % @alignOf(u32)) != 0) {
|
is_multisampled: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ImageSamplerPair = struct {
|
||||||
|
image_slot: usize,
|
||||||
|
sampler_slot: usize,
|
||||||
|
name: [:0]const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Sampler = enum {
|
||||||
|
filtering,
|
||||||
|
non_filtering,
|
||||||
|
comparison,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const StorageBuffer = struct {
|
||||||
|
minimum_size: usize,
|
||||||
|
is_readonly: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const max_images = 12;
|
||||||
|
|
||||||
|
pub const max_image_sampler_pairs = 12;
|
||||||
|
|
||||||
|
pub const max_samplers = 8;
|
||||||
|
|
||||||
|
pub const max_storage_buffers = 8;
|
||||||
|
|
||||||
|
fn reflect_image_samplers(self: *Shader, _: Stage, compiler: ext.spvc_compiler, resources: ext.spvc_resources) Error!void {
|
||||||
|
var sampled_images: []const ext.spvc_reflected_resource = &.{};
|
||||||
|
|
||||||
|
try to_error(ext.spvc_resources_get_resource_list_for_type(
|
||||||
|
resources,
|
||||||
|
ext.SPVC_RESOURCE_TYPE_SAMPLED_IMAGE,
|
||||||
|
@ptrCast(&sampled_images.ptr),
|
||||||
|
&sampled_images.len,
|
||||||
|
));
|
||||||
|
|
||||||
|
if (sampled_images.len > max_image_sampler_pairs) {
|
||||||
|
return error.UnsupportedSPIRV;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (0 .. sampled_images.len, sampled_images) |i, sampled_image| {
|
||||||
|
const sampled_image_type = ext.spvc_compiler_get_type_handle(compiler, sampled_image.type_id);
|
||||||
|
|
||||||
|
if (ext.spvc_type_get_basetype(sampled_image_type) != ext.SPVC_BASETYPE_SAMPLED_IMAGE) {
|
||||||
return error.InvalidSPIRV;
|
return error.InvalidSPIRV;
|
||||||
}
|
}
|
||||||
|
|
||||||
const spirv_ops: []const u32 = @alignCast(std.mem.bytesAsSlice(u32, spirv_data));
|
try switch (ext.spvc_type_get_image_dimension(sampled_image_type)) {
|
||||||
|
ext.SpvDim2D => {},
|
||||||
|
else => error.InvalidSPIRV,
|
||||||
|
};
|
||||||
|
|
||||||
const execution_model, const stage_desc = switch (stage) {
|
try switch (ext.spvc_type_get_basetype(ext.spvc_compiler_get_type_handle(compiler, ext.spvc_type_get_image_sampled_type(sampled_image_type)))) {
|
||||||
.vertex => .{ext.SpvExecutionModelVertex, &self.shader_desc.vs},
|
ext.SPVC_BASETYPE_FP32 => {},
|
||||||
.fragment => .{ext.SpvExecutionModelFragment, &self.shader_desc.fs},
|
else => error.InvalidSPIRV,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.has_image[i] = .{
|
||||||
|
.is_multisampled = ext.spvc_type_get_image_multisampled(sampled_image_type) != 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.has_sampler[i] = .filtering;
|
||||||
|
|
||||||
|
self.has_image_sampler_pair[i] = .{
|
||||||
|
.name = std.mem.span(ext.spvc_compiler_get_name(compiler, sampled_image.id)),
|
||||||
|
.image_slot = @intCast(i),
|
||||||
|
.sampler_slot = @intCast(i),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reflect_storage_buffers(self: *Shader, stage: Stage, compiler: ext.spvc_compiler, resources: ext.spvc_resources) Error!void {
|
||||||
|
var storage_buffers: []const ext.spvc_reflected_resource = &.{};
|
||||||
|
|
||||||
|
try to_error(ext.spvc_resources_get_resource_list_for_type(
|
||||||
|
resources,
|
||||||
|
ext.SPVC_RESOURCE_TYPE_STORAGE_BUFFER,
|
||||||
|
@ptrCast(&storage_buffers.ptr),
|
||||||
|
&storage_buffers.len,
|
||||||
|
));
|
||||||
|
|
||||||
|
const binding_offset: c_uint = if (stage == .fragment) max_storage_buffers else 0;
|
||||||
|
|
||||||
|
for (storage_buffers) |storage_buffer| {
|
||||||
|
const binding = ext.spvc_compiler_get_decoration(compiler, storage_buffer.id, ext.SpvDecorationBinding);
|
||||||
|
|
||||||
|
if (binding >= max_storage_buffers) {
|
||||||
|
return error.InvalidSPIRV;
|
||||||
|
}
|
||||||
|
|
||||||
|
ext.spvc_compiler_set_decoration(compiler, storage_buffer.id, ext.SpvDecorationBinding, binding_offset + binding);
|
||||||
|
|
||||||
|
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,
|
||||||
|
));
|
||||||
|
|
||||||
|
var minimum_size: usize = 0;
|
||||||
|
|
||||||
|
try to_error(ext.spvc_compiler_get_declared_struct_size(compiler, ext.spvc_compiler_get_type_handle(compiler, storage_buffer.base_type_id), &minimum_size));
|
||||||
|
|
||||||
|
self.has_storage_buffer[binding] = .{
|
||||||
|
.is_readonly = std.mem.indexOfScalar(ext.SpvDecoration, block_decorations, ext.SpvDecorationNonWritable) != null,
|
||||||
|
.minimum_size = minimum_size,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reflect_uniform_buffers(self: *Shader, _: Stage, compiler: ext.spvc_compiler, resources: ext.spvc_resources) Error!void {
|
||||||
|
var uniform_buffers: []const ext.spvc_reflected_resource = &.{};
|
||||||
|
|
||||||
|
try to_error(ext.spvc_resources_get_resource_list_for_type(
|
||||||
|
resources,
|
||||||
|
ext.SPVC_RESOURCE_TYPE_UNIFORM_BUFFER,
|
||||||
|
@ptrCast(&uniform_buffers.ptr),
|
||||||
|
&uniform_buffers.len,
|
||||||
|
));
|
||||||
|
|
||||||
|
if (uniform_buffers.len != 0) {
|
||||||
|
return error.InvalidSPIRV;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Support for older APIs?
|
||||||
|
_ = self;
|
||||||
|
_ = compiler;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stage_desc(self: Shader) sokol.gfx.ShaderStageDesc {
|
||||||
|
var stage = sokol.gfx.ShaderStageDesc{
|
||||||
|
.entry = "main",
|
||||||
|
.source = self.decompiled_source,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (0 .. max_storage_buffers) |slot| {
|
||||||
|
const storage_buffer = &(self.has_storage_buffer[slot] orelse {
|
||||||
|
continue;
|
||||||
|
});
|
||||||
|
|
||||||
|
stage.storage_buffers[slot] = .{
|
||||||
|
.readonly = storage_buffer.is_readonly,
|
||||||
|
.used = true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (0 .. max_images) |slot| {
|
||||||
|
const image = &(self.has_image[slot] orelse {
|
||||||
|
continue;
|
||||||
|
});
|
||||||
|
|
||||||
|
stage.images[slot] = .{
|
||||||
|
.multisampled = image.is_multisampled,
|
||||||
|
.image_type = ._2D,
|
||||||
|
.sample_type = .FLOAT,
|
||||||
|
.used = true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (0 .. max_samplers) |slot| {
|
||||||
|
const sampler = &(self.has_sampler[slot] orelse {
|
||||||
|
continue;
|
||||||
|
});
|
||||||
|
|
||||||
|
stage.samplers[slot] = .{
|
||||||
|
.sampler_type = switch (sampler.*) {
|
||||||
|
.filtering => .FILTERING,
|
||||||
|
.non_filtering => .NONFILTERING,
|
||||||
|
.comparison => .COMPARISON,
|
||||||
|
},
|
||||||
|
|
||||||
|
.used = true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (0 .. max_image_sampler_pairs) |slot| {
|
||||||
|
const image_sampler_pair = &(self.has_image_sampler_pair[slot] orelse {
|
||||||
|
continue;
|
||||||
|
});
|
||||||
|
|
||||||
|
stage.image_sampler_pairs[slot] = .{
|
||||||
|
.glsl_name = image_sampler_pair.name,
|
||||||
|
.image_slot = @intCast(image_sampler_pair.image_slot),
|
||||||
|
.sampler_slot = @intCast(image_sampler_pair.sampler_slot),
|
||||||
|
.used = true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return stage;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Stage = enum {
|
||||||
|
vertex,
|
||||||
|
fragment,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Unit = struct {
|
||||||
|
arena: std.heap.ArenaAllocator,
|
||||||
|
context: ext.spvc_context,
|
||||||
|
attrs_used: u32 = 0,
|
||||||
|
stages: [std.enums.values(Stage).len]Shader = .{.{}, .{}},
|
||||||
|
|
||||||
|
pub fn compile(self: *Unit, spirv_ops: []const u32, stage: Stage) Error!void {
|
||||||
|
const execution_model, const shader = switch (stage) {
|
||||||
|
.vertex => .{ext.SpvExecutionModelVertex, &self.stages[0]},
|
||||||
|
.fragment => .{ext.SpvExecutionModelFragment, &self.stages[1]},
|
||||||
};
|
};
|
||||||
|
|
||||||
const Backend = struct {
|
const Backend = struct {
|
||||||
|
@ -73,8 +276,8 @@ pub const Unit = struct {
|
||||||
|
|
||||||
for (combined_image_samplers) |combined_image_sampler| {
|
for (combined_image_samplers) |combined_image_sampler| {
|
||||||
const name = try coral.utf8.alloc_formatted(arena_allocator, "{image_name}_{sampler_name}", .{
|
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))),
|
.image_name = std.mem.span(ext.spvc_compiler_get_name(compiler, combined_image_sampler.image_id)),
|
||||||
.sampler_name = std.mem.span(@as([*:0]const u8, ext.spvc_compiler_get_name(compiler, combined_image_sampler.sampler_id))),
|
.sampler_name = std.mem.span(ext.spvc_compiler_get_name(compiler, combined_image_sampler.sampler_id)),
|
||||||
});
|
});
|
||||||
|
|
||||||
ext.spvc_compiler_set_name(compiler, combined_image_sampler.combined_id, name);
|
ext.spvc_compiler_set_name(compiler, combined_image_sampler.combined_id, name);
|
||||||
|
@ -86,7 +289,7 @@ pub const Unit = struct {
|
||||||
break: parse_and_configure compiler;
|
break: parse_and_configure compiler;
|
||||||
};
|
};
|
||||||
|
|
||||||
try to_error(ext.spvc_compiler_set_entry_point(compiler, stage_desc.entry, @intCast(execution_model)));
|
try to_error(ext.spvc_compiler_set_entry_point(compiler, "main", @intCast(execution_model)));
|
||||||
|
|
||||||
const resources = create: {
|
const resources = create: {
|
||||||
var resources: ext.spvc_resources = null;
|
var resources: ext.spvc_resources = null;
|
||||||
|
@ -96,27 +299,29 @@ pub const Unit = struct {
|
||||||
break: create resources;
|
break: create resources;
|
||||||
};
|
};
|
||||||
|
|
||||||
try reflect_uniform_buffers(compiler, resources, stage_desc);
|
try shader.reflect_uniform_buffers(stage, compiler, resources);
|
||||||
try reflect_storage_buffers(compiler, resources, stage_desc);
|
try shader.reflect_storage_buffers(stage, compiler, resources);
|
||||||
try reflect_image_samplers(compiler, resources, stage_desc);
|
try shader.reflect_image_samplers(stage, compiler, resources);
|
||||||
|
|
||||||
try to_error(ext.spvc_compiler_install_compiler_options(compiler, create: {
|
try to_error(ext.spvc_compiler_install_compiler_options(compiler, create: {
|
||||||
var options: ext.spvc_compiler_options = null;
|
var compiler_options: ext.spvc_compiler_options = null;
|
||||||
|
|
||||||
try to_error(ext.spvc_compiler_create_compiler_options(compiler, &options));
|
try to_error(ext.spvc_compiler_create_compiler_options(compiler, &compiler_options));
|
||||||
|
|
||||||
for (backend.option_values) |option_value| {
|
for (backend.option_values) |option_value| {
|
||||||
const entry, const value = option_value;
|
const entry, const value = option_value;
|
||||||
|
|
||||||
try to_error(ext.spvc_compiler_options_set_uint(options, entry, value));
|
try to_error(ext.spvc_compiler_options_set_uint(compiler_options, entry, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
break: create options;
|
break: create compiler_options;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
try to_error(ext.spvc_compiler_compile(compiler, @ptrCast(&stage_desc.source)));
|
try to_error(ext.spvc_compiler_compile(compiler, @ptrCast(&shader.decompiled_source.ptr)));
|
||||||
|
|
||||||
std.log.info("{s}", .{stage_desc.source});
|
shader.decompiled_source.len = std.mem.span(shader.decompiled_source.ptr).len;
|
||||||
|
|
||||||
|
std.log.info("{s}", .{shader.decompiled_source});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Unit) void {
|
pub fn deinit(self: *Unit) void {
|
||||||
|
@ -140,18 +345,19 @@ pub const Unit = struct {
|
||||||
return .{
|
return .{
|
||||||
.arena = std.heap.ArenaAllocator.init(allocator),
|
.arena = std.heap.ArenaAllocator.init(allocator),
|
||||||
.context = context,
|
.context = context,
|
||||||
|
|
||||||
.shader_desc = .{
|
|
||||||
.vs = .{.entry = "main"},
|
|
||||||
.fs = .{.entry = "main"},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
pub const Stage = enum {
|
pub fn shader_desc(self: Unit) sokol.gfx.ShaderDesc {
|
||||||
fragment,
|
return .{
|
||||||
vertex,
|
.vs = self.shader_stage(.vertex).stage_desc(),
|
||||||
|
.fs = self.shader_stage(.fragment).stage_desc(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shader_stage(self: *const Unit, stage: Stage) *const Shader {
|
||||||
|
return &self.stages[@intFromEnum(stage)];
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
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 {
|
||||||
|
@ -159,109 +365,6 @@ fn log_context_errors(userdata: ?*anyopaque, error_message: [*c]const u8) callco
|
||||||
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_desc: *sokol.gfx.ShaderStageDesc) Error!void {
|
|
||||||
var sampled_images: []const ext.spvc_reflected_resource = &.{};
|
|
||||||
|
|
||||||
try to_error(ext.spvc_resources_get_resource_list_for_type(
|
|
||||||
resources,
|
|
||||||
ext.SPVC_RESOURCE_TYPE_SAMPLED_IMAGE,
|
|
||||||
@ptrCast(&sampled_images.ptr),
|
|
||||||
&sampled_images.len,
|
|
||||||
));
|
|
||||||
|
|
||||||
if (sampled_images.len > stage_desc.image_sampler_pairs.len) {
|
|
||||||
return error.UnsupportedSPIRV;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (0 .. sampled_images.len, sampled_images) |i, sampled_image| {
|
|
||||||
const sampled_image_type = ext.spvc_compiler_get_type_handle(compiler, sampled_image.type_id);
|
|
||||||
|
|
||||||
if (ext.spvc_type_get_basetype(sampled_image_type) != ext.SPVC_BASETYPE_SAMPLED_IMAGE) {
|
|
||||||
return error.InvalidSPIRV;
|
|
||||||
}
|
|
||||||
|
|
||||||
stage_desc.images[i] = .{
|
|
||||||
.multisampled = ext.spvc_type_get_image_multisampled(sampled_image_type) != 0,
|
|
||||||
|
|
||||||
.image_type = try switch (ext.spvc_type_get_image_dimension(sampled_image_type)) {
|
|
||||||
ext.SpvDim2D => sokol.gfx.ImageType._2D,
|
|
||||||
else => error.InvalidSPIRV,
|
|
||||||
},
|
|
||||||
|
|
||||||
.sample_type = try switch (ext.spvc_type_get_basetype(ext.spvc_compiler_get_type_handle(compiler, ext.spvc_type_get_image_sampled_type(sampled_image_type)))) {
|
|
||||||
ext.SPVC_BASETYPE_FP32 => sokol.gfx.ImageSampleType.FLOAT,
|
|
||||||
else => error.InvalidSPIRV,
|
|
||||||
},
|
|
||||||
|
|
||||||
.used = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
stage_desc.samplers[i] = .{
|
|
||||||
.sampler_type = .DEFAULT,
|
|
||||||
.used = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
stage_desc.image_sampler_pairs[i] = .{
|
|
||||||
.glsl_name = ext.spvc_compiler_get_name(compiler, sampled_image.id),
|
|
||||||
.image_slot = @intCast(i),
|
|
||||||
.sampler_slot = @intCast(i),
|
|
||||||
.used = true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reflect_storage_buffers(compiler: ext.spvc_compiler, resources: ext.spvc_resources, stage_desc: *sokol.gfx.ShaderStageDesc) Error!void {
|
|
||||||
var storage_buffers: []const ext.spvc_reflected_resource = &.{};
|
|
||||||
|
|
||||||
try to_error(ext.spvc_resources_get_resource_list_for_type(
|
|
||||||
resources,
|
|
||||||
ext.SPVC_RESOURCE_TYPE_STORAGE_BUFFER,
|
|
||||||
@ptrCast(&storage_buffers.ptr),
|
|
||||||
&storage_buffers.len,
|
|
||||||
));
|
|
||||||
|
|
||||||
for (storage_buffers) |storage_buffer| {
|
|
||||||
const binding = ext.spvc_compiler_get_decoration(compiler, storage_buffer.id, ext.SpvDecorationBinding);
|
|
||||||
|
|
||||||
if (binding >= stage_desc.storage_buffers.len) {
|
|
||||||
return error.InvalidSPIRV;
|
|
||||||
}
|
|
||||||
|
|
||||||
var block_decorations: []const ext.SpvDecoration = &.{};
|
|
||||||
|
|
||||||
try to_error(ext.spvc_compiler_get_buffer_block_decorations(
|
|
||||||
compiler,
|
|
||||||
storage_buffer.id,
|
|
||||||
@ptrCast(&block_decorations.ptr),
|
|
||||||
&block_decorations.len,
|
|
||||||
));
|
|
||||||
|
|
||||||
stage_desc.storage_buffers[binding] = .{
|
|
||||||
.readonly = std.mem.indexOfScalar(ext.SpvDecoration, block_decorations, ext.SpvDecorationNonWritable) != null,
|
|
||||||
.used = true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reflect_uniform_buffers(compiler: ext.spvc_compiler, resources: ext.spvc_resources, stage_desc: *sokol.gfx.ShaderStageDesc) Error!void {
|
|
||||||
var uniform_buffers: []const ext.spvc_reflected_resource = &.{};
|
|
||||||
|
|
||||||
try to_error(ext.spvc_resources_get_resource_list_for_type(
|
|
||||||
resources,
|
|
||||||
ext.SPVC_RESOURCE_TYPE_UNIFORM_BUFFER,
|
|
||||||
@ptrCast(&uniform_buffers.ptr),
|
|
||||||
&uniform_buffers.len,
|
|
||||||
));
|
|
||||||
|
|
||||||
if (uniform_buffers.len != 0) {
|
|
||||||
return error.InvalidSPIRV;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Support for older APIs?
|
|
||||||
_ = stage_desc;
|
|
||||||
_ = compiler;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_error(result: ext.spvc_result) Error!void {
|
fn to_error(result: ext.spvc_result) Error!void {
|
||||||
return switch (result) {
|
return switch (result) {
|
||||||
ext.SPVC_SUCCESS => {},
|
ext.SPVC_SUCCESS => {},
|
||||||
|
@ -272,3 +375,11 @@ fn to_error(result: ext.spvc_result) Error!void {
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn to_ops(raw: anytype) [raw.len / @alignOf(u32)]u32 {
|
||||||
|
var ops: [raw.len / @alignOf(u32)]u32 = undefined;
|
||||||
|
|
||||||
|
@memcpy(std.mem.sliceAsBytes(&ops), raw);
|
||||||
|
|
||||||
|
return ops;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue