Implement effects pipelining system
continuous-integration/drone/push Build is passing Details

This commit is contained in:
kayomn 2024-07-20 13:46:06 +01:00
parent 99c3818477
commit c47b334742
9 changed files with 569 additions and 282 deletions

View File

@ -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",

View File

@ -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;
}
} }

View File

@ -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 {

View File

@ -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,

View File

@ -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,

View File

@ -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");

View File

@ -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,

View File

@ -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;

View File

@ -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;
}
try switch (ext.spvc_type_get_image_dimension(sampled_image_type)) {
ext.SpvDim2D => {},
else => error.InvalidSPIRV,
};
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 => {},
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; return error.InvalidSPIRV;
} }
const spirv_ops: []const u32 = @alignCast(std.mem.bytesAsSlice(u32, spirv_data)); // TODO: Support for older APIs?
_ = self;
_ = compiler;
}
const execution_model, const stage_desc = switch (stage) { pub fn stage_desc(self: Shader) sokol.gfx.ShaderStageDesc {
.vertex => .{ext.SpvExecutionModelVertex, &self.shader_desc.vs}, var stage = sokol.gfx.ShaderStageDesc{
.fragment => .{ext.SpvExecutionModelFragment, &self.shader_desc.fs}, .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;
}