Compare commits

...

2 Commits

Author SHA1 Message Date
56342d9d8e Use uniform blocks instead of storage buffers for common data
All checks were successful
continuous-integration/drone/push Build is passing
2024-07-21 00:45:24 +01:00
f3efb60c05 Update dependencies in readme 2024-07-20 15:23:47 +01:00
10 changed files with 560 additions and 466 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

@ -33,7 +33,7 @@ Ona is also the Catalan word for "wave".
Ona currently depends the following third-party tools to build it:
* Platform support for SDL2 at version 2.0.20 or above.
* SPIR-V shader compilation system utilities, namely `glslangValidator` and `spirv-link`.
* SPIR-V shader compilation system utilities, namely `glslangValidator`.
* Zig compiler toolchain.
As the project evolves, dependencies on libraries external to the project codebase will be minimized or removed outright to meet the goals of the project as closely as possible.

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,
1 => Uniform.Type.float,
2 => Uniform.Type.float2,
3 => Uniform.Type.float3,
else => error.UnsupportedSPIRV,
},
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;
},
};
}
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,
self.has_uniform_blocks[i] = .{
.uniforms = uniforms,
.layout = .std140,
};
}
return stage;
}
};
pub const Stage = enum {
vertex,
fragment,
pub const Target = enum {
glsl,
};
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 analyze(arena: *std.heap.ArenaAllocator, options: Options) Error!Program {
var context: ext.spvc_context = null;
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]},
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 Backend = struct {
target: ext.spvc_backend,
option_values: []const struct {ext.spvc_compiler_option, c_uint},
const resources = create: {
var resources: ext.spvc_resources = null;
try to_error(ext.spvc_compiler_create_shader_resources(compiler, &resources));
break: create resources;
};
const backend: Backend = try switch (sokol.gfx.queryBackend()) {
.GLCORE => .{
.target = ext.SPVC_BACKEND_GLSL,
try stage.reflect_images_samplers(arena, compiler, resources);
try stage.reflect_uniform_blocks(arena, compiler, resources);
.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)},
},
std.log.info("{s}", .{stage.source});
break: vertex_stage stage;
},
else => error.UnsupportedBackend,
.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 compiler = parse_and_configure: {
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 {