Use uniform blocks instead of storage buffers for common data
continuous-integration/drone/push Build is passing Details

This commit is contained in:
kayomn 2024-07-21 00:45:24 +01:00
parent f3efb60c05
commit 56342d9d8e
9 changed files with 559 additions and 465 deletions

View File

@ -7,7 +7,7 @@ layout (location = 1) in vec2 uv;
layout (location = 0) out vec4 texel; layout (location = 0) out vec4 texel;
layout (binding = 0) readonly buffer Effect { layout (binding = 0) uniform Effect {
float effect_magnitude; float effect_magnitude;
}; };

View File

@ -5,7 +5,8 @@ const std = @import("std");
const ona = @import("ona"); const ona = @import("ona");
const ChromaticAberration = extern struct { const ChromaticAberration = extern struct {
magnitude: f32, effect_magnitude: f32,
padding: [12]u8 = undefined,
}; };
const Actors = struct { const Actors = struct {
@ -53,7 +54,7 @@ fn exit(actors: ona.Write(Actors)) void {
fn render(commands: ona.gfx.Commands, actors: ona.Write(Actors)) !void { fn render(commands: ona.gfx.Commands, actors: ona.Write(Actors)) !void {
try commands.set_effect(actors.res.ca_effect, ChromaticAberration{ try commands.set_effect(actors.res.ca_effect, ChromaticAberration{
.magnitude = 15.0, .effect_magnitude = 15.0,
}); });
for (actors.res.instances.values) |instance| { for (actors.res.instances.values) |instance| {

View File

@ -73,20 +73,22 @@ pub const Assets = struct {
} }
const fragment_file_stat = try storage.stat(path); const fragment_file_stat = try storage.stat(path);
const fragment_spirv = try coral.heap.allocator.alloc(u32, fragment_file_stat.size / @alignOf(u32)); const fragment_spirv_ops = try coral.heap.allocator.alloc(u32, fragment_file_stat.size / @alignOf(u32));
defer { defer {
coral.heap.allocator.free(fragment_spirv); coral.heap.allocator.free(fragment_spirv_ops);
} }
_ = try storage.read_all(path, std.mem.sliceAsBytes(fragment_spirv), .{}); const bytes_read = try storage.read_all(path, std.mem.sliceAsBytes(fragment_spirv_ops), .{});
std.debug.assert(bytes_read.len == fragment_file_stat.size);
var loaded = coral.asyncio.Future(std.mem.Allocator.Error!Effect){}; var loaded = coral.asyncio.Future(std.mem.Allocator.Error!Effect){};
rendering.enqueue_work(.{ rendering.enqueue_work(.{
.load_effect = .{ .load_effect = .{
.desc = .{ .desc = .{
.fragment_spirv = fragment_spirv, .fragment_spirv_ops = fragment_spirv_ops,
}, },
.loaded = &loaded, .loaded = &loaded,
@ -127,20 +129,6 @@ pub const Assets = struct {
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;
}; };
@ -213,13 +201,6 @@ pub const Input = union (enum) {
pub const Effect = handles.Effect; pub const Effect = handles.Effect;
pub const Rect = struct {
left: f32,
top: f32,
right: f32,
bottom: f32,
};
pub const Texture = handles.Texture; pub const Texture = handles.Texture;
pub fn poll(app: flow.Write(App), inputs: msg.Send(Input)) !void { pub fn poll(app: flow.Write(App), inputs: msg.Send(Input)) !void {

View File

@ -3,7 +3,7 @@ const coral = @import("coral");
const std = @import("std"); const std = @import("std");
pub const Effect = Handle(struct { pub const Effect = Handle(struct {
fragment_spirv: []const u32, fragment_spirv_ops: []const u32,
}); });
fn Handle(comptime HandleDesc: type) type { fn Handle(comptime HandleDesc: type) type {

View File

@ -1,5 +1,3 @@
const gfx = @import("../gfx.zig");
pub const Color = @Vector(4, f32); pub const Color = @Vector(4, f32);
pub fn EvenOrderMatrix(comptime n: usize, comptime Element: type) type { pub fn EvenOrderMatrix(comptime n: usize, comptime Element: type) type {
@ -16,7 +14,14 @@ pub const Transform2D = extern struct {
pub const ProjectionMatrix = EvenOrderMatrix(4, f32); pub const ProjectionMatrix = EvenOrderMatrix(4, f32);
pub fn orthographic_projection(near: f32, far: f32, viewport: gfx.Rect) EvenOrderMatrix(4, f32) { pub const Rect = struct {
left: f32,
top: f32,
right: f32,
bottom: f32,
};
pub fn orthographic_projection(near: f32, far: f32, viewport: Rect) EvenOrderMatrix(4, f32) {
const width = viewport.right - viewport.left; const width = viewport.right - viewport.left;
const height = viewport.bottom - viewport.top; const height = viewport.bottom - viewport.top;

View File

@ -185,7 +185,7 @@ const Frame = struct {
bindings.vertex_buffers[vertex_indices.mesh] = quad_vertex_buffer; bindings.vertex_buffers[vertex_indices.mesh] = quad_vertex_buffer;
switch (pools.textures.get(@intFromEnum(self.current_source_texture)).?.*) { switch (pools.textures.get(@intFromEnum(self.current_source_texture)).?.access) {
.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;
@ -197,22 +197,30 @@ const Frame = struct {
}, },
} }
if (self.current_target_texture) |target_texture| {
const target_view_buffer = pools.textures.get(@intFromEnum(target_texture)).?.render.view_buffer;
bindings.vs.storage_buffers[0] = target_view_buffer;
} else {
bindings.vs.storage_buffers[0] = view_buffer;
}
const effect = pools.effects.get(@intFromEnum(self.current_effect)).?; const effect = pools.effects.get(@intFromEnum(self.current_effect)).?;
sokol.gfx.applyPipeline(effect.pipeline); sokol.gfx.applyPipeline(effect.pipeline);
if (effect.has_properties_buffer) |properties_buffer| { if (self.current_target_texture) |target_texture| {
bindings.fs.storage_buffers[0] = properties_buffer; const texture = pools.textures.get(@intFromEnum(target_texture)).?;
sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&lina.orthographic_projection(-1.0, 1.0, .{
.left = 0,
.top = 0,
.right = @floatFromInt(texture.width),
.bottom = @floatFromInt(texture.height),
})));
} else {
sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&lina.orthographic_projection(-1.0, 1.0, .{
.left = 0,
.top = 0,
.right = @floatFromInt(self.swapchain.width),
.bottom = @floatFromInt(self.swapchain.height),
})));
} }
sokol.gfx.applyUniforms(.FS, 0, sokol.gfx.asRange(effect.properties));
while (true) { while (true) {
const buffer_index = self.flushed_count / batches_per_buffer; const buffer_index = self.flushed_count / batches_per_buffer;
const buffer_offset = self.flushed_count % batches_per_buffer; const buffer_offset = self.flushed_count % batches_per_buffer;
@ -238,10 +246,8 @@ const Frame = struct {
self.current_effect = command.effect; self.current_effect = command.effect;
if (command.properties.len != 0) { if (pools.effects.get(@intFromEnum(self.current_effect))) |effect| {
if (pools.effects.get(@intFromEnum(self.current_effect)).?.has_properties_buffer) |properties_buffer| { @memcpy(effect.properties, command.properties);
sokol.gfx.updateBuffer(properties_buffer, sokol.gfx.asRange(command.properties));
}
} }
} }
@ -275,7 +281,7 @@ const Frame = struct {
} }
if (command.texture) |texture| { if (command.texture) |texture| {
pass.attachments = switch (pools.textures.get(@intFromEnum(texture)).?.*) { pass.attachments = switch (pools.textures.get(@intFromEnum(texture)).?.access) {
.static => @panic("Cannot render to static textures"), .static => @panic("Cannot render to static textures"),
.render => |render| render.attachments, .render => |render| render.attachments,
}; };
@ -376,7 +382,7 @@ const Pools = struct {
} }
_ = try pools.create_effect(.{ _ = try pools.create_effect(.{
.fragment_spirv = &spirv.to_ops(@embedFile("./shaders/2d_default.frag.spv")), .fragment_spirv_ops = &spirv.to_ops(@embedFile("./shaders/2d_default.frag.spv")),
}); });
const default_texture_data = [_]u32{ const default_texture_data = [_]u32{
@ -463,8 +469,6 @@ var quad_index_buffer: sokol.gfx.Buffer = undefined;
var quad_vertex_buffer: sokol.gfx.Buffer = undefined; var quad_vertex_buffer: sokol.gfx.Buffer = undefined;
var view_buffer: sokol.gfx.Buffer = undefined;
const vertex_indices = .{ const vertex_indices = .{
.mesh = 0, .mesh = 0,
.instance = 1, .instance = 1,
@ -537,18 +541,6 @@ fn run(window: *ext.SDL_Window) !void {
.type = .VERTEXBUFFER, .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,
});
var has_commands_head: ?*Commands = null; var has_commands_head: ?*Commands = null;
var has_commands_tail: ?*Commands = null; var has_commands_tail: ?*Commands = null;

View File

@ -13,29 +13,50 @@ const std = @import("std");
pub const Effect = struct { pub const Effect = struct {
shader: sokol.gfx.Shader, shader: sokol.gfx.Shader,
pipeline: sokol.gfx.Pipeline, pipeline: sokol.gfx.Pipeline,
has_properties_buffer: ?sokol.gfx.Buffer = null, properties: []coral.io.Byte,
pub fn deinit(self: *Effect) void { pub fn deinit(self: *Effect) void {
coral.heap.allocator.free(self.properties);
sokol.gfx.destroyPipeline(self.pipeline); sokol.gfx.destroyPipeline(self.pipeline);
sokol.gfx.destroyShader(self.shader); sokol.gfx.destroyShader(self.shader);
if (self.has_properties_buffer) |parameters_buffer| {
sokol.gfx.destroyBuffer(parameters_buffer);
}
self.* = undefined; self.* = undefined;
} }
pub fn init(desc: handles.Effect.Desc) !Effect { pub fn init(desc: handles.Effect.Desc) spirv.Error!Effect {
var spirv_unit = try spirv.Unit.init(coral.heap.allocator); var spirv_arena = std.heap.ArenaAllocator.init(coral.heap.allocator);
defer spirv_unit.deinit(); defer {
spirv_arena.deinit();
}
try spirv_unit.compile(&spirv.to_ops(@embedFile("./shaders/2d_default.vert.spv")), .vertex); const spirv_program = try spirv.analyze(&spirv_arena, .{
try spirv_unit.compile(desc.fragment_spirv, .fragment); .target = try switch (sokol.gfx.queryBackend()) {
.GLCORE => spirv.Target.glsl,
else => error.InvalidSPIRV,
},
const shader = sokol.gfx.makeShader(spirv_unit.shader_desc()); .vertex_source = .{
.ops = &spirv.to_ops(@embedFile("./shaders/2d_default.vert.spv")),
},
.fragment_source = .{
.ops = desc.fragment_spirv_ops,
},
});
const shader = sokol.gfx.makeShader(shader_desc: {
const shader_desc = sokol.gfx.ShaderDesc{
.vs = stage_desc(spirv_program.vertex_stage),
.fs = stage_desc(spirv_program.fragment_stage),
};
// TODO: Vertex attributes, for some reason they aren't needed?
break: shader_desc shader_desc;
});
// TODO: Review blending rules.
const pipeline = sokol.gfx.makePipeline(pipeline_desc: { const pipeline = sokol.gfx.makePipeline(pipeline_desc: {
var pipeline_desc = sokol.gfx.PipelineDesc{ var pipeline_desc = sokol.gfx.PipelineDesc{
.label = "Effect pipeline", .label = "Effect pipeline",
@ -58,33 +79,106 @@ pub const Effect = struct {
break: pipeline_desc pipeline_desc; break: pipeline_desc pipeline_desc;
}); });
const properties = try coral.heap.allocator.alloc(
coral.io.Byte,
if (spirv_program.fragment_stage.has_uniform_blocks[0]) |uniform_block| uniform_block.size() else 0,
);
errdefer { errdefer {
coral.heap.allocator.free(properties);
sokol.gfx.destroyPipeline(pipeline); sokol.gfx.destroyPipeline(pipeline);
sokol.gfx.destroyShader(shader); sokol.gfx.destroyShader(shader);
} }
if (spirv_unit.shader_stage(.fragment).has_storage_buffer[0]) |storage_buffer| { return .{
const properties_buffer = sokol.gfx.makeBuffer(.{ .shader = shader,
.size = storage_buffer.minimum_size, .pipeline = pipeline,
.type = .STORAGEBUFFER, .properties = properties,
.usage = .STREAM, };
}
fn stage_desc(spirv_stage: spirv.Stage) sokol.gfx.ShaderStageDesc {
var stage = sokol.gfx.ShaderStageDesc{
.entry = spirv_stage.entry_point,
.source = spirv_stage.source,
};
for (0 .. spirv.Stage.max_uniform_blocks) |slot| {
const uniform_block = &(spirv_stage.has_uniform_blocks[slot] orelse {
continue;
}); });
errdefer { const stage_uniform_block = &stage.uniform_blocks[slot];
sokol.gfx.destroyBuffer(properties_buffer);
stage_uniform_block.layout = switch (uniform_block.layout) {
.std140 => .STD140,
};
stage_uniform_block.size = uniform_block.size();
for (stage_uniform_block.uniforms[0 .. uniform_block.uniforms.len], uniform_block.uniforms) |*stage_uniform, uniform| {
stage_uniform.* = .{
.type = switch (uniform.type) {
.float => .FLOAT,
.float2 => .FLOAT2,
.float3 => .FLOAT3,
.float4 => .FLOAT4,
.int => .INT,
.int2 => .INT2,
.int3 => .INT3,
.int4 => .INT4,
.mat4 => .MAT4,
},
.name = uniform.name,
.array_count = uniform.len,
};
}
} }
return .{ for (0 .. spirv.Stage.max_images) |slot| {
.shader = shader, const image = &(spirv_stage.has_images[slot] orelse {
.pipeline = pipeline, continue;
.has_properties_buffer = properties_buffer, });
};
} else { stage.images[slot] = .{
return .{ .multisampled = image.is_multisampled,
.shader = shader, .image_type = ._2D,
.pipeline = pipeline, .sample_type = .FLOAT,
.used = true,
}; };
} }
for (0 .. spirv.Stage.max_samplers) |slot| {
const sampler = &(spirv_stage.has_samplers[slot] orelse {
continue;
});
stage.samplers[slot] = .{
.sampler_type = switch (sampler.*) {
.filtering => .FILTERING,
.non_filtering => .NONFILTERING,
.comparison => .COMPARISON,
},
.used = true,
};
}
for (0 .. spirv.Stage.max_image_sampler_pairs) |slot| {
const image_sampler_pair = &(spirv_stage.has_image_sampler_pairs[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;
} }
const vertex_layout_state = sokol.gfx.VertexLayoutState{ const vertex_layout_state = sokol.gfx.VertexLayoutState{
@ -144,113 +238,114 @@ pub const Effect = struct {
}; };
}; };
pub const Texture = union (enum) { pub const Texture = struct {
render: Render, width: u16,
static: Static, height: u16,
access: Access,
const Render = struct { pub const Access = union (enum) {
render: RenderAccess,
static: StaticAccess,
};
pub const RenderAccess = struct {
sampler: sokol.gfx.Sampler, sampler: sokol.gfx.Sampler,
color_image: sokol.gfx.Image, color_image: sokol.gfx.Image,
depth_image: sokol.gfx.Image, depth_image: sokol.gfx.Image,
attachments: sokol.gfx.Attachments, attachments: sokol.gfx.Attachments,
view_buffer: sokol.gfx.Buffer, };
fn deinit(self: *Render) void { pub const StaticAccess = struct {
sokol.gfx.destroyImage(self.color_image); sampler: sokol.gfx.Sampler,
sokol.gfx.destroyImage(self.depth_image); image: sokol.gfx.Image,
sokol.gfx.destroySampler(self.sampler); };
sokol.gfx.destroyAttachments(self.attachments);
sokol.gfx.destroyBuffer(self.view_buffer); pub fn deinit(self: *Texture) void {
switch (self.access) {
.render => |render| {
sokol.gfx.destroyImage(render.color_image);
sokol.gfx.destroyImage(render.depth_image);
sokol.gfx.destroySampler(render.sampler);
sokol.gfx.destroyAttachments(render.attachments);
},
.static => |static| {
sokol.gfx.destroyImage(static.image);
sokol.gfx.destroySampler(static.sampler);
},
}
self.* = undefined; self.* = undefined;
} }
fn init(format: handles.Texture.Desc.Format, access: handles.Texture.Desc.Access.Render) std.mem.Allocator.Error!Render { pub fn init(desc: handles.Texture.Desc) std.mem.Allocator.Error!Texture {
const color_image = sokol.gfx.makeImage(.{ const pixel_format = switch (desc.format) {
.pixel_format = switch (format) {
.rgba8 => sokol.gfx.PixelFormat.RGBA8, .rgba8 => sokol.gfx.PixelFormat.RGBA8,
.bgra8 => sokol.gfx.PixelFormat.BGRA8, .bgra8 => sokol.gfx.PixelFormat.BGRA8,
}, };
.width = access.width, switch (desc.access) {
.height = access.height, .render => |render| {
const color_image = sokol.gfx.makeImage(.{
.pixel_format = pixel_format,
.width = render.width,
.height = render.height,
.render_target = true, .render_target = true,
}); });
const depth_image = sokol.gfx.makeImage(.{ const depth_image = sokol.gfx.makeImage(.{
.width = access.width, .width = render.width,
.height = access.height, .height = render.height,
.render_target = true, .render_target = true,
.pixel_format = .DEPTH_STENCIL, .pixel_format = .DEPTH_STENCIL,
}); });
const attachments = sokol.gfx.makeAttachments(attachments_desc: { const attachments = sokol.gfx.makeAttachments(attachments_desc: {
var desc = sokol.gfx.AttachmentsDesc{ var attachments_desc = sokol.gfx.AttachmentsDesc{
.depth_stencil = .{ .depth_stencil = .{
.image = depth_image, .image = depth_image,
}, },
}; };
desc.colors[0] = .{ attachments_desc.colors[0] = .{
.image = color_image, .image = color_image,
}; };
break: attachments_desc desc; break: attachments_desc attachments_desc;
}); });
const sampler = sokol.gfx.makeSampler(.{}); 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 .{ return .{
.width = render.width,
.height = render.height,
.access = .{
.render = .{
.attachments = attachments, .attachments = attachments,
.sampler = sampler, .sampler = sampler,
.color_image = color_image, .color_image = color_image,
.depth_image = depth_image, .depth_image = depth_image,
.view_buffer = view_buffer, },
};
} }
}; };
},
const Static = struct { .static => |static| {
image: sokol.gfx.Image, const height = std.math.cast(u16, static.data.len / (static.width * desc.format.byte_size())) orelse {
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; 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); const image = sokol.gfx.makeImage(image_desc: {
var image_desc = sokol.gfx.ImageDesc{
.height = height,
.pixel_format = pixel_format,
.width = static.width,
};
break: image_desc desc; image_desc.data.subimage[0][0] = sokol.gfx.asRange(static.data);
break: image_desc image_desc;
}); });
const sampler = sokol.gfx.makeSampler(.{}); const sampler = sokol.gfx.makeSampler(.{});
@ -261,23 +356,17 @@ pub const Texture = union (enum) {
} }
return .{ return .{
.width = static.width,
.height = height,
.access = .{
.static = .{
.image = image, .image = image,
.sampler = sampler, .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)},
};
}
}; };

View File

@ -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) readonly buffer View { layout (binding = 0) uniform View {
mat4 projection_matrix; mat4 projection_matrix;
}; };

View File

@ -9,18 +9,33 @@ const sokol = @import("sokol");
const std = @import("std"); const std = @import("std");
pub const Error = std.mem.Allocator.Error || error { pub const Error = std.mem.Allocator.Error || error {
UnsupportedTarget,
InvalidSPIRV, InvalidSPIRV,
UnsupportedSPIRV, UnsupportedSPIRV,
UnsupportedBackend,
}; };
pub const Shader = struct { pub const Options = struct {
decompiled_source: [:0]const u8 = "", target: Target,
has_storage_buffer: [max_storage_buffers]?StorageBuffer = [_]?StorageBuffer{null} ** max_storage_buffers, vertex_source: Source,
has_image: [max_images]?Image = [_]?Image{null} ** max_images, fragment_source: Source,
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 const Program = struct {
vertex_stage: Stage,
fragment_stage: Stage,
};
pub const Source = struct {
entry_point: []const u8 = "main",
ops: []const u32,
};
pub const Stage = struct {
entry_point: [:0]const u8,
source: [:0]const u8,
has_image_sampler_pairs: [max_image_sampler_pairs]?ImageSamplerPair = [_]?ImageSamplerPair{null} ** max_image_sampler_pairs,
has_images: [max_images]?Image = [_]?Image{null} ** max_images,
has_samplers: [max_samplers]?Sampler = [_]?Sampler{null} ** max_samplers,
has_uniform_blocks: [max_uniform_blocks]?UniformBlock = [_]?UniformBlock{null} ** max_uniform_blocks,
pub const Image = struct { pub const Image = struct {
is_multisampled: bool, is_multisampled: bool,
@ -32,15 +47,65 @@ pub const Shader = struct {
name: [:0]const u8, name: [:0]const u8,
}; };
pub const Layout = enum {
std140,
};
pub const Sampler = enum { pub const Sampler = enum {
filtering, filtering,
non_filtering, non_filtering,
comparison, comparison,
}; };
pub const StorageBuffer = struct { pub const Uniform = struct {
minimum_size: usize, name: [:0]const u8,
is_readonly: bool, type: Type,
len: u16,
pub const Type = enum {
float,
float2,
float3,
float4,
int,
int2,
int3,
int4,
mat4,
};
};
pub const UniformBlock = struct {
layout: Layout,
uniforms: []const Uniform,
pub const max_uniforms = 16;
pub fn size(self: UniformBlock) usize {
const alignment: usize = switch (self.layout) {
.std140 => 16,
};
var accumulated_size: usize = 0;
for (self.uniforms) |uniform| {
const type_size = @max(1, uniform.len) * @as(usize, switch (uniform.type) {
.float => 4,
.float2 => 8,
.float3 => 12,
.float4 => 16,
.int => 4,
.int2 => 8,
.int3 => 12,
.int4 => 16,
.mat4 => 64,
});
accumulated_size += (type_size + (alignment - 1)) & ~(alignment - 1);
}
return accumulated_size;
}
}; };
pub const max_images = 12; pub const max_images = 12;
@ -51,7 +116,10 @@ pub const Shader = struct {
pub const max_storage_buffers = 8; pub const max_storage_buffers = 8;
fn reflect_image_samplers(self: *Shader, _: Stage, compiler: ext.spvc_compiler, resources: ext.spvc_resources) Error!void { pub const max_uniform_blocks = 8;
fn reflect_images_samplers(self: *Stage, arena: *std.heap.ArenaAllocator, compiler: ext.spvc_compiler, resources: ext.spvc_resources) Error!void {
const arena_allocator = arena.allocator();
var 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(
@ -82,196 +150,208 @@ pub const Shader = struct {
else => error.InvalidSPIRV, else => error.InvalidSPIRV,
}; };
self.has_image[i] = .{ self.has_images[i] = .{
.is_multisampled = ext.spvc_type_get_image_multisampled(sampled_image_type) != 0, .is_multisampled = ext.spvc_type_get_image_multisampled(sampled_image_type) != 0,
}; };
self.has_sampler[i] = .filtering; self.has_samplers[i] = .filtering;
self.has_image_sampler_pair[i] = .{ self.has_image_sampler_pairs[i] = .{
.name = std.mem.span(ext.spvc_compiler_get_name(compiler, sampled_image.id)), .name = try arena_allocator.dupeZ(u8, std.mem.span(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),
}; };
} }
} }
fn reflect_storage_buffers(self: *Shader, stage: Stage, compiler: ext.spvc_compiler, resources: ext.spvc_resources) Error!void { fn reflect_uniform_blocks(self: *Stage, arena: *std.heap.ArenaAllocator, compiler: ext.spvc_compiler, resources: ext.spvc_resources) Error!void {
var storage_buffers: []const ext.spvc_reflected_resource = &.{}; const arena_allocator = arena.allocator();
var reflected_resources: []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( 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(&uniform_buffers.ptr), @ptrCast(&reflected_resources.ptr),
&uniform_buffers.len, &reflected_resources.len,
)); ));
if (uniform_buffers.len != 0) { if (reflected_resources.len > max_uniform_blocks) {
return error.InvalidSPIRV; return error.InvalidSPIRV;
} }
// TODO: Support for older APIs? for (0 .. reflected_resources.len, reflected_resources) |i, reflected_resource| {
_ = self; const type_handle = ext.spvc_compiler_get_type_handle(compiler, reflected_resource.base_type_id);
_ = compiler; const is_std430 = ext.spvc_compiler_has_decoration(compiler, reflected_resource.id, ext.SpvDecorationBufferBlock) == ext.SPVC_TRUE;
if (is_std430) {
return error.UnsupportedSPIRV;
} }
pub fn stage_desc(self: Shader) sokol.gfx.ShaderStageDesc { const uniform_count = ext.spvc_type_get_num_member_types(type_handle);
var stage = sokol.gfx.ShaderStageDesc{
.entry = "main",
.source = self.decompiled_source,
};
for (0 .. max_storage_buffers) |slot| { if (uniform_count > UniformBlock.max_uniforms) {
const storage_buffer = &(self.has_storage_buffer[slot] orelse { return error.UnsupportedSPIRV;
continue;
});
stage.storage_buffers[slot] = .{
.readonly = storage_buffer.is_readonly,
.used = true,
};
} }
for (0 .. max_images) |slot| { const uniforms = try arena_allocator.alloc(Uniform, uniform_count);
const image = &(self.has_image[slot] orelse {
continue;
});
stage.images[slot] = .{ for (uniforms, 0 .. uniform_count) |*uniform, j| {
.multisampled = image.is_multisampled, const member_index: c_uint = @intCast(j);
.image_type = ._2D, const member_type_handle = ext.spvc_compiler_get_type_handle(compiler, ext.spvc_type_get_member_type(type_handle, member_index));
.sample_type = .FLOAT,
.used = true, if (ext.spvc_type_get_num_array_dimensions(member_type_handle) > 1) {
}; return error.UnsupportedSPIRV;
} }
for (0 .. max_samplers) |slot| { uniform.* = .{
const sampler = &(self.has_sampler[slot] orelse { .name = try coral.utf8.alloc_formatted(arena_allocator, "_{id}.{member_name}", .{
continue; .id = reflected_resource.id,
}); .member_name = std.mem.span(ext.spvc_compiler_get_member_name(compiler, reflected_resource.base_type_id, member_index)),
}),
stage.samplers[slot] = .{ .type = try switch (ext.spvc_type_get_basetype(member_type_handle)) {
.sampler_type = switch (sampler.*) { ext.SPVC_BASETYPE_FP32 => switch (ext.spvc_type_get_vector_size(member_type_handle)) {
.filtering => .FILTERING, 4 => switch (ext.spvc_type_get_columns(member_type_handle)) {
.non_filtering => .NONFILTERING, 4 => Uniform.Type.mat4,
.comparison => .COMPARISON, 1 => Uniform.Type.float4,
else => error.UnsupportedSPIRV,
}, },
.used = true, 1 => Uniform.Type.float,
}; 2 => Uniform.Type.float2,
} 3 => Uniform.Type.float3,
else => error.UnsupportedSPIRV,
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 {
target: ext.spvc_backend,
option_values: []const struct {ext.spvc_compiler_option, c_uint},
};
const backend: Backend = try switch (sokol.gfx.queryBackend()) {
.GLCORE => .{
.target = ext.SPVC_BACKEND_GLSL,
.option_values = &.{
.{ext.SPVC_COMPILER_OPTION_GLSL_VERSION, 430},
.{ext.SPVC_COMPILER_OPTION_GLSL_ES, @intFromBool(false)},
.{ext.SPVC_COMPILER_OPTION_GLSL_VULKAN_SEMANTICS, @intFromBool(false)},
.{ext.SPVC_COMPILER_OPTION_GLSL_EMIT_UNIFORM_BUFFER_AS_PLAIN_UNIFORMS, @intFromBool(true)},
.{ext.SPVC_COMPILER_OPTION_FLIP_VERTEX_Y, @intFromBool(true)},
},
}, },
else => error.UnsupportedBackend, ext.SPVC_BASETYPE_INT32 => try switch (ext.spvc_type_get_vector_size(member_type_handle)) {
1 => Uniform.Type.int,
2 => Uniform.Type.int2,
3 => Uniform.Type.int3,
4 => Uniform.Type.int4,
else => error.UnsupportedSPIRV,
},
else => error.UnsupportedSPIRV,
},
.len = std.math.cast(u16, ext.spvc_type_get_array_dimension(member_type_handle, 0)) orelse {
return error.UnsupportedSPIRV;
},
};
}
self.has_uniform_blocks[i] = .{
.uniforms = uniforms,
.layout = .std140,
};
}
}
}; };
const compiler = parse_and_configure: { pub const Target = enum {
glsl,
};
pub fn analyze(arena: *std.heap.ArenaAllocator, options: Options) Error!Program {
var context: ext.spvc_context = null;
if (ext.spvc_context_create(&context) != ext.SPVC_SUCCESS) {
return error.OutOfMemory;
}
defer {
ext.spvc_context_destroy(context);
}
ext.spvc_context_set_error_callback(context, log_errors, null);
const arena_allocator = arena.allocator();
return .{
.vertex_stage = vertex_stage: {
const compiler = try parse(arena, context, options.target, options.vertex_source);
var stage = Stage{
.entry_point = try arena_allocator.dupeZ(u8, options.vertex_source.entry_point),
.source = try compile(arena, compiler),
};
const resources = create: {
var resources: ext.spvc_resources = null;
try to_error(ext.spvc_compiler_create_shader_resources(compiler, &resources));
break: create resources;
};
try stage.reflect_images_samplers(arena, compiler, resources);
try stage.reflect_uniform_blocks(arena, compiler, resources);
std.log.info("{s}", .{stage.source});
break: vertex_stage stage;
},
.fragment_stage = fragment_stage: {
const compiler = try parse(arena, context, options.target, options.fragment_source);
var stage = Stage{
.entry_point = try arena_allocator.dupeZ(u8, options.fragment_source.entry_point),
.source = try compile(arena, compiler),
};
const resources = create: {
var resources: ext.spvc_resources = null;
try to_error(ext.spvc_compiler_create_shader_resources(compiler, &resources));
break: create resources;
};
try stage.reflect_images_samplers(arena, compiler, resources);
try stage.reflect_uniform_blocks(arena, compiler, resources);
std.log.info("{s}", .{stage.source});
break: fragment_stage stage;
},
};
}
fn compile(arena: *std.heap.ArenaAllocator, compiler: ext.spvc_compiler) Error![:0]const u8 {
const arena_allocator = arena.allocator();
var source: [*:0]const u8 = "";
try to_error(ext.spvc_compiler_compile(compiler, @ptrCast(&source)));
return arena_allocator.dupeZ(u8, std.mem.span(source));
}
fn log_errors(userdata: ?*anyopaque, error_message: [*c]const u8) callconv(.C) void {
std.debug.assert(userdata == null);
std.log.err("{s}", .{error_message});
}
fn parse(arena: *std.heap.ArenaAllocator, context: ext.spvc_context, target: Target, source: Source) Error!ext.spvc_compiler {
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_ops.ptr, spirv_ops.len, &parsed_ir)); try to_error(ext.spvc_context_parse_spirv(context, source.ops.ptr, source.ops.len, &parsed_ir));
var compiler: ext.spvc_compiler = null; var compiler: ext.spvc_compiler = null;
try to_error(ext.spvc_context_create_compiler(self.context, backend.target, parsed_ir, ext.SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, &compiler)); const spvc_target = switch (target) {
.glsl => ext.SPVC_BACKEND_GLSL,
};
try to_error(ext.spvc_context_create_compiler(context, spvc_target, parsed_ir, ext.SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, &compiler));
try to_error(ext.spvc_compiler_build_combined_image_samplers(compiler)); try to_error(ext.spvc_compiler_build_combined_image_samplers(compiler));
var combined_image_samplers: []const ext.spvc_combined_image_sampler = &.{}; var combined_image_samplers: []const ext.spvc_combined_image_sampler = &.{};
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(); const arena_allocator = arena.allocator();
var binding: u32 = 0; var binding: u32 = 0;
for (combined_image_samplers) |combined_image_sampler| { for (combined_image_samplers) |combined_image_sampler| {
@ -286,83 +366,29 @@ pub const Unit = struct {
binding += 1; binding += 1;
} }
break: parse_and_configure compiler; const option_values: []const struct {ext.spvc_compiler_option, c_uint} = switch (target) {
.glsl => &.{
.{ext.SPVC_COMPILER_OPTION_GLSL_VERSION, 430},
.{ext.SPVC_COMPILER_OPTION_GLSL_ES, @intFromBool(false)},
.{ext.SPVC_COMPILER_OPTION_GLSL_VULKAN_SEMANTICS, @intFromBool(false)},
.{ext.SPVC_COMPILER_OPTION_GLSL_EMIT_UNIFORM_BUFFER_AS_PLAIN_UNIFORMS, @intFromBool(true)},
.{ext.SPVC_COMPILER_OPTION_FLIP_VERTEX_Y, @intFromBool(true)},
},
}; };
try to_error(ext.spvc_compiler_set_entry_point(compiler, "main", @intCast(execution_model)));
const resources = create: {
var resources: ext.spvc_resources = null;
try to_error(ext.spvc_compiler_create_shader_resources(compiler, &resources));
break: create resources;
};
try shader.reflect_uniform_buffers(stage, compiler, resources);
try shader.reflect_storage_buffers(stage, compiler, resources);
try shader.reflect_image_samplers(stage, compiler, resources);
try to_error(ext.spvc_compiler_install_compiler_options(compiler, create: {
var compiler_options: ext.spvc_compiler_options = null; var compiler_options: ext.spvc_compiler_options = null;
try to_error(ext.spvc_compiler_create_compiler_options(compiler, &compiler_options)); try to_error(ext.spvc_compiler_create_compiler_options(compiler, &compiler_options));
for (backend.option_values) |option_value| { for (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(compiler_options, entry, value)); try to_error(ext.spvc_compiler_options_set_uint(compiler_options, entry, value));
} }
break: create compiler_options; try to_error(ext.spvc_compiler_install_compiler_options(compiler, compiler_options));
}));
try to_error(ext.spvc_compiler_compile(compiler, @ptrCast(&shader.decompiled_source.ptr))); return compiler;
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 {
ext.spvc_context_destroy(self.context);
self.arena.deinit();
self.* = undefined;
}
pub fn init(allocator: std.mem.Allocator) std.mem.Allocator.Error!Unit {
var context: ext.spvc_context = null;
if (ext.spvc_context_create(&context) != ext.SPVC_SUCCESS) {
return error.OutOfMemory;
}
errdefer ext.spvc_context_destroy(context);
ext.spvc_context_set_error_callback(context, log_context_errors, null);
return .{
.arena = std.heap.ArenaAllocator.init(allocator),
.context = context,
};
}
pub fn shader_desc(self: Unit) sokol.gfx.ShaderDesc {
return .{
.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 {
std.debug.assert(userdata == null);
std.log.err("{s}", .{error_message});
} }
fn to_error(result: ext.spvc_result) Error!void { fn to_error(result: ext.spvc_result) Error!void {