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 (binding = 0) readonly buffer Effect {
layout (binding = 0) uniform Effect {
float effect_magnitude;
};

View File

@ -5,7 +5,8 @@ const std = @import("std");
const ona = @import("ona");
const ChromaticAberration = extern struct {
magnitude: f32,
effect_magnitude: f32,
padding: [12]u8 = undefined,
};
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 {
try commands.set_effect(actors.res.ca_effect, ChromaticAberration{
.magnitude = 15.0,
.effect_magnitude = 15.0,
});
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_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 {
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){};
rendering.enqueue_work(.{
.load_effect = .{
.desc = .{
.fragment_spirv = fragment_spirv,
.fragment_spirv_ops = fragment_spirv_ops,
},
.loaded = &loaded,
@ -127,20 +129,6 @@ pub const Assets = struct {
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;
};
@ -213,13 +201,6 @@ pub const Input = union (enum) {
pub const Effect = handles.Effect;
pub const Rect = struct {
left: f32,
top: f32,
right: f32,
bottom: f32,
};
pub const Texture = handles.Texture;
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");
pub const Effect = Handle(struct {
fragment_spirv: []const u32,
fragment_spirv_ops: []const u32,
});
fn Handle(comptime HandleDesc: type) type {

View File

@ -1,5 +1,3 @@
const gfx = @import("../gfx.zig");
pub const Color = @Vector(4, f32);
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 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 height = viewport.bottom - viewport.top;

View File

@ -185,7 +185,7 @@ const Frame = struct {
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| {
bindings.fs.images[0] = render.color_image;
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)).?;
sokol.gfx.applyPipeline(effect.pipeline);
if (effect.has_properties_buffer) |properties_buffer| {
bindings.fs.storage_buffers[0] = properties_buffer;
if (self.current_target_texture) |target_texture| {
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) {
const buffer_index = 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;
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));
}
if (pools.effects.get(@intFromEnum(self.current_effect))) |effect| {
@memcpy(effect.properties, command.properties);
}
}
@ -275,7 +281,7 @@ const Frame = struct {
}
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"),
.render => |render| render.attachments,
};
@ -376,7 +382,7 @@ const Pools = struct {
}
_ = 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{
@ -463,8 +469,6 @@ var quad_index_buffer: sokol.gfx.Buffer = undefined;
var quad_vertex_buffer: sokol.gfx.Buffer = undefined;
var view_buffer: sokol.gfx.Buffer = undefined;
const vertex_indices = .{
.mesh = 0,
.instance = 1,
@ -537,18 +541,6 @@ fn run(window: *ext.SDL_Window) !void {
.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_tail: ?*Commands = null;

View File

@ -13,29 +13,50 @@ const std = @import("std");
pub const Effect = struct {
shader: sokol.gfx.Shader,
pipeline: sokol.gfx.Pipeline,
has_properties_buffer: ?sokol.gfx.Buffer = null,
properties: []coral.io.Byte,
pub fn deinit(self: *Effect) void {
coral.heap.allocator.free(self.properties);
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);
pub fn init(desc: handles.Effect.Desc) spirv.Error!Effect {
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);
try spirv_unit.compile(desc.fragment_spirv, .fragment);
const spirv_program = try spirv.analyze(&spirv_arena, .{
.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: {
var pipeline_desc = sokol.gfx.PipelineDesc{
.label = "Effect pipeline",
@ -58,33 +79,106 @@ pub const Effect = struct {
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 {
coral.heap.allocator.free(properties);
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,
return .{
.shader = shader,
.pipeline = pipeline,
.properties = properties,
};
}
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 {
sokol.gfx.destroyBuffer(properties_buffer);
const stage_uniform_block = &stage.uniform_blocks[slot];
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 .{
.shader = shader,
.pipeline = pipeline,
.has_properties_buffer = properties_buffer,
};
} else {
return .{
.shader = shader,
.pipeline = pipeline,
for (0 .. spirv.Stage.max_images) |slot| {
const image = &(spirv_stage.has_images[slot] orelse {
continue;
});
stage.images[slot] = .{
.multisampled = image.is_multisampled,
.image_type = ._2D,
.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{
@ -144,113 +238,114 @@ pub const Effect = struct {
};
};
pub const Texture = union (enum) {
render: Render,
static: Static,
pub const Texture = struct {
width: u16,
height: u16,
access: Access,
const Render = struct {
pub const Access = union (enum) {
render: RenderAccess,
static: StaticAccess,
};
pub const RenderAccess = struct {
sampler: sokol.gfx.Sampler,
color_image: sokol.gfx.Image,
depth_image: sokol.gfx.Image,
attachments: sokol.gfx.Attachments,
view_buffer: sokol.gfx.Buffer,
};
fn deinit(self: *Render) void {
sokol.gfx.destroyImage(self.color_image);
sokol.gfx.destroyImage(self.depth_image);
sokol.gfx.destroySampler(self.sampler);
sokol.gfx.destroyAttachments(self.attachments);
sokol.gfx.destroyBuffer(self.view_buffer);
pub const StaticAccess = struct {
sampler: sokol.gfx.Sampler,
image: sokol.gfx.Image,
};
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;
}
fn init(format: handles.Texture.Desc.Format, access: handles.Texture.Desc.Access.Render) std.mem.Allocator.Error!Render {
const color_image = sokol.gfx.makeImage(.{
.pixel_format = switch (format) {
pub fn init(desc: handles.Texture.Desc) std.mem.Allocator.Error!Texture {
const pixel_format = switch (desc.format) {
.rgba8 => sokol.gfx.PixelFormat.RGBA8,
.bgra8 => sokol.gfx.PixelFormat.BGRA8,
},
};
.width = access.width,
.height = access.height,
switch (desc.access) {
.render => |render| {
const color_image = sokol.gfx.makeImage(.{
.pixel_format = pixel_format,
.width = render.width,
.height = render.height,
.render_target = true,
});
const depth_image = sokol.gfx.makeImage(.{
.width = access.width,
.height = access.height,
.width = render.width,
.height = render.height,
.render_target = true,
.pixel_format = .DEPTH_STENCIL,
});
const attachments = sokol.gfx.makeAttachments(attachments_desc: {
var desc = sokol.gfx.AttachmentsDesc{
var attachments_desc = sokol.gfx.AttachmentsDesc{
.depth_stencil = .{
.image = depth_image,
},
};
desc.colors[0] = .{
attachments_desc.colors[0] = .{
.image = color_image,
};
break: attachments_desc desc;
break: attachments_desc attachments_desc;
});
const sampler = sokol.gfx.makeSampler(.{});
const view_buffer = sokol.gfx.makeBuffer(.{
.data = sokol.gfx.asRange(&lina.orthographic_projection(-1.0, 1.0, .{
.left = 0,
.top = 0,
.right = @floatFromInt(access.width),
.bottom = @floatFromInt(access.height),
})),
.type = .STORAGEBUFFER,
.usage = .IMMUTABLE,
});
return .{
.width = render.width,
.height = render.height,
.access = .{
.render = .{
.attachments = attachments,
.sampler = sampler,
.color_image = color_image,
.depth_image = depth_image,
.view_buffer = view_buffer,
};
},
}
};
},
const Static = struct {
image: sokol.gfx.Image,
sampler: sokol.gfx.Sampler,
fn deinit(self: *Static) void {
sokol.gfx.destroyImage(self.image);
sokol.gfx.destroySampler(self.sampler);
self.* = undefined;
}
fn init(format: handles.Texture.Desc.Format, access: handles.Texture.Desc.Access.Static) std.mem.Allocator.Error!Static {
const image = sokol.gfx.makeImage(image_desc: {
var desc = sokol.gfx.ImageDesc{
.height = std.math.cast(i32, access.data.len / (access.width * format.byte_size())) orelse {
.static => |static| {
const height = std.math.cast(u16, static.data.len / (static.width * desc.format.byte_size())) orelse {
return error.OutOfMemory;
},
.pixel_format = switch (format) {
.rgba8 => sokol.gfx.PixelFormat.RGBA8,
.bgra8 => sokol.gfx.PixelFormat.BGRA8,
},
.width = access.width,
};
desc.data.subimage[0][0] = sokol.gfx.asRange(access.data);
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(.{});
@ -261,23 +356,17 @@ pub const Texture = union (enum) {
}
return .{
.width = static.width,
.height = height,
.access = .{
.static = .{
.image = image,
.sampler = sampler,
},
},
};
}
};
pub fn deinit(self: *Texture) void {
switch (self.*) {
.static => |*static| static.deinit(),
.render => |*render| render.deinit(),
},
}
}
pub fn init(desc: handles.Texture.Desc) std.mem.Allocator.Error!Texture {
return switch (desc.access) {
.static => |static| .{.static = try Static.init(desc.format, static)},
.render => |render| .{.render = try Render.init(desc.format, render)},
};
}
};

View File

@ -13,7 +13,7 @@ layout (location = 7) in vec4 instance_rect;
layout (location = 0) out vec4 color;
layout (location = 1) out vec2 uv;
layout (binding = 0) readonly buffer View {
layout (binding = 0) uniform View {
mat4 projection_matrix;
};

View File

@ -9,18 +9,33 @@ const sokol = @import("sokol");
const std = @import("std");
pub const Error = std.mem.Allocator.Error || error {
UnsupportedTarget,
InvalidSPIRV,
UnsupportedSPIRV,
UnsupportedBackend,
};
pub const Shader = struct {
decompiled_source: [:0]const u8 = "",
has_storage_buffer: [max_storage_buffers]?StorageBuffer = [_]?StorageBuffer{null} ** max_storage_buffers,
has_image: [max_images]?Image = [_]?Image{null} ** max_images,
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 Options = struct {
target: Target,
vertex_source: Source,
fragment_source: Source,
};
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 {
is_multisampled: bool,
@ -32,15 +47,65 @@ pub const Shader = struct {
name: [:0]const u8,
};
pub const Layout = enum {
std140,
};
pub const Sampler = enum {
filtering,
non_filtering,
comparison,
};
pub const StorageBuffer = struct {
minimum_size: usize,
is_readonly: bool,
pub const Uniform = struct {
name: [:0]const u8,
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;
@ -51,7 +116,10 @@ pub const Shader = struct {
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 = &.{};
try to_error(ext.spvc_resources_get_resource_list_for_type(
@ -82,196 +150,208 @@ pub const Shader = struct {
else => error.InvalidSPIRV,
};
self.has_image[i] = .{
self.has_images[i] = .{
.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] = .{
.name = std.mem.span(ext.spvc_compiler_get_name(compiler, sampled_image.id)),
self.has_image_sampler_pairs[i] = .{
.name = try arena_allocator.dupeZ(u8, 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 = &.{};
fn reflect_uniform_blocks(self: *Stage, arena: *std.heap.ArenaAllocator, compiler: ext.spvc_compiler, resources: ext.spvc_resources) Error!void {
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_UNIFORM_BUFFER,
@ptrCast(&uniform_buffers.ptr),
&uniform_buffers.len,
@ptrCast(&reflected_resources.ptr),
&reflected_resources.len,
));
if (uniform_buffers.len != 0) {
if (reflected_resources.len > max_uniform_blocks) {
return error.InvalidSPIRV;
}
// TODO: Support for older APIs?
_ = self;
_ = compiler;
for (0 .. reflected_resources.len, reflected_resources) |i, reflected_resource| {
const type_handle = ext.spvc_compiler_get_type_handle(compiler, reflected_resource.base_type_id);
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 {
var stage = sokol.gfx.ShaderStageDesc{
.entry = "main",
.source = self.decompiled_source,
};
const uniform_count = ext.spvc_type_get_num_member_types(type_handle);
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,
};
if (uniform_count > UniformBlock.max_uniforms) {
return error.UnsupportedSPIRV;
}
for (0 .. max_images) |slot| {
const image = &(self.has_image[slot] orelse {
continue;
});
const uniforms = try arena_allocator.alloc(Uniform, uniform_count);
stage.images[slot] = .{
.multisampled = image.is_multisampled,
.image_type = ._2D,
.sample_type = .FLOAT,
.used = true,
};
for (uniforms, 0 .. uniform_count) |*uniform, j| {
const member_index: c_uint = @intCast(j);
const member_type_handle = ext.spvc_compiler_get_type_handle(compiler, ext.spvc_type_get_member_type(type_handle, member_index));
if (ext.spvc_type_get_num_array_dimensions(member_type_handle) > 1) {
return error.UnsupportedSPIRV;
}
for (0 .. max_samplers) |slot| {
const sampler = &(self.has_sampler[slot] orelse {
continue;
});
uniform.* = .{
.name = try coral.utf8.alloc_formatted(arena_allocator, "_{id}.{member_name}", .{
.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] = .{
.sampler_type = switch (sampler.*) {
.filtering => .FILTERING,
.non_filtering => .NONFILTERING,
.comparison => .COMPARISON,
.type = try switch (ext.spvc_type_get_basetype(member_type_handle)) {
ext.SPVC_BASETYPE_FP32 => switch (ext.spvc_type_get_vector_size(member_type_handle)) {
4 => switch (ext.spvc_type_get_columns(member_type_handle)) {
4 => Uniform.Type.mat4,
1 => Uniform.Type.float4,
else => error.UnsupportedSPIRV,
},
.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 {
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)},
},
1 => Uniform.Type.float,
2 => Uniform.Type.float2,
3 => Uniform.Type.float3,
else => error.UnsupportedSPIRV,
},
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;
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;
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));
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));
const arena_allocator = self.arena.allocator();
const arena_allocator = arena.allocator();
var binding: u32 = 0;
for (combined_image_samplers) |combined_image_sampler| {
@ -286,83 +366,29 @@ pub const Unit = struct {
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;
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;
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)));
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});
return compiler;
}
fn to_error(result: ext.spvc_result) Error!void {