Compare commits

..

No commits in common. "56342d9d8e31088c995e5c61c7c25908471a4ebb" and "c47b334742a2349f2b3a75f2f16652a492c59728" have entirely different histories.

10 changed files with 461 additions and 555 deletions

View File

@ -7,7 +7,7 @@ layout (location = 1) in vec2 uv;
layout (location = 0) out vec4 texel;
layout (binding = 0) uniform Effect {
layout (binding = 0) readonly buffer 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`.
* SPIR-V shader compilation system utilities, namely `glslangValidator` and `spirv-link`.
* 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,8 +5,7 @@ const std = @import("std");
const ona = @import("ona");
const ChromaticAberration = extern struct {
effect_magnitude: f32,
padding: [12]u8 = undefined,
magnitude: f32,
};
const Actors = struct {
@ -54,7 +53,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{
.effect_magnitude = 15.0,
.magnitude = 15.0,
});
for (actors.res.instances.values) |instance| {

View File

@ -73,22 +73,20 @@ pub const Assets = struct {
}
const fragment_file_stat = try storage.stat(path);
const fragment_spirv_ops = try coral.heap.allocator.alloc(u32, fragment_file_stat.size / @alignOf(u32));
const fragment_spirv = try coral.heap.allocator.alloc(u32, fragment_file_stat.size / @alignOf(u32));
defer {
coral.heap.allocator.free(fragment_spirv_ops);
coral.heap.allocator.free(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);
_ = try storage.read_all(path, std.mem.sliceAsBytes(fragment_spirv), .{});
var loaded = coral.asyncio.Future(std.mem.Allocator.Error!Effect){};
rendering.enqueue_work(.{
.load_effect = .{
.desc = .{
.fragment_spirv_ops = fragment_spirv_ops,
.fragment_spirv = fragment_spirv,
},
.loaded = &loaded,
@ -129,6 +127,20 @@ 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;
};
@ -201,6 +213,13 @@ 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_ops: []const u32,
fragment_spirv: []const u32,
});
fn Handle(comptime HandleDesc: type) type {

View File

@ -1,3 +1,5 @@
const gfx = @import("../gfx.zig");
pub const Color = @Vector(4, f32);
pub fn EvenOrderMatrix(comptime n: usize, comptime Element: type) type {
@ -14,14 +16,7 @@ pub const Transform2D = extern struct {
pub const ProjectionMatrix = 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) {
pub fn orthographic_projection(near: f32, far: f32, viewport: gfx.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)).?.access) {
switch (pools.textures.get(@intFromEnum(self.current_source_texture)).?.*) {
.render => |render| {
bindings.fs.images[0] = render.color_image;
bindings.fs.samplers[0] = render.sampler;
@ -197,30 +197,22 @@ 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 (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),
})));
if (effect.has_properties_buffer) |properties_buffer| {
bindings.fs.storage_buffers[0] = properties_buffer;
}
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;
@ -246,8 +238,10 @@ const Frame = struct {
self.current_effect = command.effect;
if (pools.effects.get(@intFromEnum(self.current_effect))) |effect| {
@memcpy(effect.properties, command.properties);
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));
}
}
}
@ -281,7 +275,7 @@ const Frame = struct {
}
if (command.texture) |texture| {
pass.attachments = switch (pools.textures.get(@intFromEnum(texture)).?.access) {
pass.attachments = switch (pools.textures.get(@intFromEnum(texture)).?.*) {
.static => @panic("Cannot render to static textures"),
.render => |render| render.attachments,
};
@ -382,7 +376,7 @@ const Pools = struct {
}
_ = try pools.create_effect(.{
.fragment_spirv_ops = &spirv.to_ops(@embedFile("./shaders/2d_default.frag.spv")),
.fragment_spirv = &spirv.to_ops(@embedFile("./shaders/2d_default.frag.spv")),
});
const default_texture_data = [_]u32{
@ -469,6 +463,8 @@ 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,
@ -541,6 +537,18 @@ 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,50 +13,29 @@ const std = @import("std");
pub const Effect = struct {
shader: sokol.gfx.Shader,
pipeline: sokol.gfx.Pipeline,
properties: []coral.io.Byte,
has_properties_buffer: ?sokol.gfx.Buffer = null,
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) spirv.Error!Effect {
var spirv_arena = std.heap.ArenaAllocator.init(coral.heap.allocator);
pub fn init(desc: handles.Effect.Desc) !Effect {
var spirv_unit = try spirv.Unit.init(coral.heap.allocator);
defer {
spirv_arena.deinit();
}
defer spirv_unit.deinit();
const spirv_program = try spirv.analyze(&spirv_arena, .{
.target = try switch (sokol.gfx.queryBackend()) {
.GLCORE => spirv.Target.glsl,
else => error.InvalidSPIRV,
},
try spirv_unit.compile(&spirv.to_ops(@embedFile("./shaders/2d_default.vert.spv")), .vertex);
try spirv_unit.compile(desc.fragment_spirv, .fragment);
.vertex_source = .{
.ops = &spirv.to_ops(@embedFile("./shaders/2d_default.vert.spv")),
},
const shader = sokol.gfx.makeShader(spirv_unit.shader_desc());
.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",
@ -79,106 +58,33 @@ 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);
}
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;
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,
});
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,
};
errdefer {
sokol.gfx.destroyBuffer(properties_buffer);
}
}
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,
return .{
.shader = shader,
.pipeline = pipeline,
.has_properties_buffer = properties_buffer,
};
} else {
return .{
.shader = shader,
.pipeline = pipeline,
};
}
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{
@ -238,135 +144,140 @@ pub const Effect = struct {
};
};
pub const Texture = struct {
width: u16,
height: u16,
access: Access,
pub const Texture = union (enum) {
render: Render,
static: Static,
pub const Access = union (enum) {
render: RenderAccess,
static: StaticAccess,
};
pub const RenderAccess = struct {
const Render = 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);
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) {
.rgba8 => sokol.gfx.PixelFormat.RGBA8,
.bgra8 => sokol.gfx.PixelFormat.BGRA8,
},
.width = access.width,
.height = access.height,
.render_target = true,
});
const depth_image = sokol.gfx.makeImage(.{
.width = access.width,
.height = access.height,
.render_target = true,
.pixel_format = .DEPTH_STENCIL,
});
const attachments = sokol.gfx.makeAttachments(attachments_desc: {
var desc = sokol.gfx.AttachmentsDesc{
.depth_stencil = .{
.image = depth_image,
},
};
desc.colors[0] = .{
.image = color_image,
};
break: attachments_desc 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 .{
.attachments = attachments,
.sampler = sampler,
.color_image = color_image,
.depth_image = depth_image,
.view_buffer = view_buffer,
};
}
};
pub const StaticAccess = struct {
sampler: sokol.gfx.Sampler,
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 {
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);
break: image_desc desc;
});
const sampler = sokol.gfx.makeSampler(.{});
errdefer {
sokol.gfx.destroySampler(sampler);
sokol.gfx.destroyImage(image);
}
return .{
.image = image,
.sampler = sampler,
};
}
};
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);
},
switch (self.*) {
.static => |*static| static.deinit(),
.render => |*render| render.deinit(),
}
self.* = undefined;
}
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,
return switch (desc.access) {
.static => |static| .{.static = try Static.init(desc.format, static)},
.render => |render| .{.render = try Render.init(desc.format, render)},
};
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 = render.width,
.height = render.height,
.render_target = true,
.pixel_format = .DEPTH_STENCIL,
});
const attachments = sokol.gfx.makeAttachments(attachments_desc: {
var attachments_desc = sokol.gfx.AttachmentsDesc{
.depth_stencil = .{
.image = depth_image,
},
};
attachments_desc.colors[0] = .{
.image = color_image,
};
break: attachments_desc attachments_desc;
});
const sampler = sokol.gfx.makeSampler(.{});
return .{
.width = render.width,
.height = render.height,
.access = .{
.render = .{
.attachments = attachments,
.sampler = sampler,
.color_image = color_image,
.depth_image = depth_image,
},
}
};
},
.static => |static| {
const height = std.math.cast(u16, static.data.len / (static.width * desc.format.byte_size())) orelse {
return error.OutOfMemory;
};
const image = sokol.gfx.makeImage(image_desc: {
var image_desc = sokol.gfx.ImageDesc{
.height = height,
.pixel_format = pixel_format,
.width = static.width,
};
image_desc.data.subimage[0][0] = sokol.gfx.asRange(static.data);
break: image_desc image_desc;
});
const sampler = sokol.gfx.makeSampler(.{});
errdefer {
sokol.gfx.destroySampler(sampler);
sokol.gfx.destroyImage(image);
}
return .{
.width = static.width,
.height = height,
.access = .{
.static = .{
.image = image,
.sampler = sampler,
},
},
};
},
}
}
};

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

View File

@ -9,33 +9,18 @@ const sokol = @import("sokol");
const std = @import("std");
pub const Error = std.mem.Allocator.Error || error {
UnsupportedTarget,
InvalidSPIRV,
UnsupportedSPIRV,
UnsupportedBackend,
};
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 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 Image = struct {
is_multisampled: bool,
@ -47,65 +32,15 @@ pub const Stage = struct {
name: [:0]const u8,
};
pub const Layout = enum {
std140,
};
pub const Sampler = enum {
filtering,
non_filtering,
comparison,
};
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 StorageBuffer = struct {
minimum_size: usize,
is_readonly: bool,
};
pub const max_images = 12;
@ -116,10 +51,7 @@ pub const Stage = struct {
pub const max_storage_buffers = 8;
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();
fn reflect_image_samplers(self: *Shader, _: Stage, compiler: ext.spvc_compiler, resources: ext.spvc_resources) Error!void {
var sampled_images: []const ext.spvc_reflected_resource = &.{};
try to_error(ext.spvc_resources_get_resource_list_for_type(
@ -150,247 +82,289 @@ pub const Stage = struct {
else => error.InvalidSPIRV,
};
self.has_images[i] = .{
self.has_image[i] = .{
.is_multisampled = ext.spvc_type_get_image_multisampled(sampled_image_type) != 0,
};
self.has_samplers[i] = .filtering;
self.has_sampler[i] = .filtering;
self.has_image_sampler_pairs[i] = .{
.name = try arena_allocator.dupeZ(u8, std.mem.span(ext.spvc_compiler_get_name(compiler, sampled_image.id))),
self.has_image_sampler_pair[i] = .{
.name = std.mem.span(ext.spvc_compiler_get_name(compiler, sampled_image.id)),
.image_slot = @intCast(i),
.sampler_slot = @intCast(i),
};
}
}
fn reflect_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 = &.{};
fn reflect_storage_buffers(self: *Shader, stage: Stage, compiler: ext.spvc_compiler, resources: ext.spvc_resources) Error!void {
var storage_buffers: []const ext.spvc_reflected_resource = &.{};
try to_error(ext.spvc_resources_get_resource_list_for_type(
resources,
ext.SPVC_RESOURCE_TYPE_STORAGE_BUFFER,
@ptrCast(&storage_buffers.ptr),
&storage_buffers.len,
));
const binding_offset: c_uint = if (stage == .fragment) max_storage_buffers else 0;
for (storage_buffers) |storage_buffer| {
const binding = ext.spvc_compiler_get_decoration(compiler, storage_buffer.id, ext.SpvDecorationBinding);
if (binding >= max_storage_buffers) {
return error.InvalidSPIRV;
}
ext.spvc_compiler_set_decoration(compiler, storage_buffer.id, ext.SpvDecorationBinding, binding_offset + binding);
var block_decorations: []const ext.SpvDecoration = &.{};
try to_error(ext.spvc_compiler_get_buffer_block_decorations(
compiler,
storage_buffer.id,
@ptrCast(&block_decorations.ptr),
&block_decorations.len,
));
var minimum_size: usize = 0;
try to_error(ext.spvc_compiler_get_declared_struct_size(compiler, ext.spvc_compiler_get_type_handle(compiler, storage_buffer.base_type_id), &minimum_size));
self.has_storage_buffer[binding] = .{
.is_readonly = std.mem.indexOfScalar(ext.SpvDecoration, block_decorations, ext.SpvDecorationNonWritable) != null,
.minimum_size = minimum_size,
};
}
}
fn reflect_uniform_buffers(self: *Shader, _: Stage, compiler: ext.spvc_compiler, resources: ext.spvc_resources) Error!void {
var uniform_buffers: []const ext.spvc_reflected_resource = &.{};
try to_error(ext.spvc_resources_get_resource_list_for_type(
resources,
ext.SPVC_RESOURCE_TYPE_UNIFORM_BUFFER,
@ptrCast(&reflected_resources.ptr),
&reflected_resources.len,
@ptrCast(&uniform_buffers.ptr),
&uniform_buffers.len,
));
if (reflected_resources.len > max_uniform_blocks) {
if (uniform_buffers.len != 0) {
return error.InvalidSPIRV;
}
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;
// TODO: Support for older APIs?
_ = self;
_ = compiler;
}
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;
});
if (uniform_count > UniformBlock.max_uniforms) {
return error.UnsupportedSPIRV;
}
const uniforms = try arena_allocator.alloc(Uniform, uniform_count);
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;
}
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)),
}),
.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,
},
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;
},
};
}
self.has_uniform_blocks[i] = .{
.uniforms = uniforms,
.layout = .std140,
stage.storage_buffers[slot] = .{
.readonly = storage_buffer.is_readonly,
.used = true,
};
}
for (0 .. max_images) |slot| {
const image = &(self.has_image[slot] orelse {
continue;
});
stage.images[slot] = .{
.multisampled = image.is_multisampled,
.image_type = ._2D,
.sample_type = .FLOAT,
.used = true,
};
}
for (0 .. max_samplers) |slot| {
const sampler = &(self.has_sampler[slot] orelse {
continue;
});
stage.samplers[slot] = .{
.sampler_type = switch (sampler.*) {
.filtering => .FILTERING,
.non_filtering => .NONFILTERING,
.comparison => .COMPARISON,
},
.used = true,
};
}
for (0 .. max_image_sampler_pairs) |slot| {
const image_sampler_pair = &(self.has_image_sampler_pair[slot] orelse {
continue;
});
stage.image_sampler_pairs[slot] = .{
.glsl_name = image_sampler_pair.name,
.image_slot = @intCast(image_sampler_pair.image_slot),
.sampler_slot = @intCast(image_sampler_pair.sampler_slot),
.used = true,
};
}
return stage;
}
};
pub const Target = enum {
glsl,
pub const Stage = enum {
vertex,
fragment,
};
pub fn analyze(arena: *std.heap.ArenaAllocator, options: Options) Error!Program {
var context: ext.spvc_context = null;
pub const Unit = struct {
arena: std.heap.ArenaAllocator,
context: ext.spvc_context,
attrs_used: u32 = 0,
stages: [std.enums.values(Stage).len]Shader = .{.{}, .{}},
if (ext.spvc_context_create(&context) != ext.SPVC_SUCCESS) {
return error.OutOfMemory;
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,
};
const compiler = parse_and_configure: {
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));
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));
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();
var binding: u32 = 0;
for (combined_image_samplers) |combined_image_sampler| {
const name = try coral.utf8.alloc_formatted(arena_allocator, "{image_name}_{sampler_name}", .{
.image_name = std.mem.span(ext.spvc_compiler_get_name(compiler, combined_image_sampler.image_id)),
.sampler_name = std.mem.span(ext.spvc_compiler_get_name(compiler, combined_image_sampler.sampler_id)),
});
ext.spvc_compiler_set_name(compiler, combined_image_sampler.combined_id, name);
ext.spvc_compiler_set_decoration(compiler, combined_image_sampler.combined_id, ext.SpvDecorationBinding, binding);
binding += 1;
}
break: parse_and_configure compiler;
};
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| {
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_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});
}
defer {
ext.spvc_context_destroy(context);
pub fn deinit(self: *Unit) void {
ext.spvc_context_destroy(self.context);
self.arena.deinit();
self.* = undefined;
}
ext.spvc_context_set_error_callback(context, log_errors, null);
pub fn init(allocator: std.mem.Allocator) std.mem.Allocator.Error!Unit {
var context: ext.spvc_context = null;
const arena_allocator = arena.allocator();
if (ext.spvc_context_create(&context) != ext.SPVC_SUCCESS) {
return error.OutOfMemory;
}
return .{
.vertex_stage = vertex_stage: {
const compiler = try parse(arena, context, options.target, options.vertex_source);
errdefer ext.spvc_context_destroy(context);
var stage = Stage{
.entry_point = try arena_allocator.dupeZ(u8, options.vertex_source.entry_point),
.source = try compile(arena, compiler),
};
ext.spvc_context_set_error_callback(context, log_context_errors, null);
const resources = create: {
var resources: ext.spvc_resources = null;
return .{
.arena = std.heap.ArenaAllocator.init(allocator),
.context = context,
};
}
try to_error(ext.spvc_compiler_create_shader_resources(compiler, &resources));
pub fn shader_desc(self: Unit) sokol.gfx.ShaderDesc {
return .{
.vs = self.shader_stage(.vertex).stage_desc(),
.fs = self.shader_stage(.fragment).stage_desc(),
};
}
break: create resources;
};
pub fn shader_stage(self: *const Unit, stage: Stage) *const Shader {
return &self.stages[@intFromEnum(stage)];
}
};
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 {
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 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(context, source.ops.ptr, source.ops.len, &parsed_ir));
var compiler: ext.spvc_compiler = null;
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 = arena.allocator();
var binding: u32 = 0;
for (combined_image_samplers) |combined_image_sampler| {
const name = try coral.utf8.alloc_formatted(arena_allocator, "{image_name}_{sampler_name}", .{
.image_name = std.mem.span(ext.spvc_compiler_get_name(compiler, combined_image_sampler.image_id)),
.sampler_name = std.mem.span(ext.spvc_compiler_get_name(compiler, combined_image_sampler.sampler_id)),
});
ext.spvc_compiler_set_name(compiler, combined_image_sampler.combined_id, name);
ext.spvc_compiler_set_decoration(compiler, combined_image_sampler.combined_id, ext.SpvDecorationBinding, binding);
binding += 1;
}
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)},
},
};
var compiler_options: ext.spvc_compiler_options = null;
try to_error(ext.spvc_compiler_create_compiler_options(compiler, &compiler_options));
for (option_values) |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_install_compiler_options(compiler, compiler_options));
return compiler;
}
fn to_error(result: ext.spvc_result) Error!void {
return switch (result) {
ext.SPVC_SUCCESS => {},