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 (location = 0) out vec4 texel;
layout (binding = 0) uniform Effect { layout (binding = 0) readonly buffer Effect {
float effect_magnitude; 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: Ona currently depends the following third-party tools to build it:
* Platform support for SDL2 at version 2.0.20 or above. * 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. * 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. 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 ona = @import("ona");
const ChromaticAberration = extern struct { const ChromaticAberration = extern struct {
effect_magnitude: f32, magnitude: f32,
padding: [12]u8 = undefined,
}; };
const Actors = struct { 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 { fn render(commands: ona.gfx.Commands, actors: ona.Write(Actors)) !void {
try commands.set_effect(actors.res.ca_effect, ChromaticAberration{ try commands.set_effect(actors.res.ca_effect, ChromaticAberration{
.effect_magnitude = 15.0, .magnitude = 15.0,
}); });
for (actors.res.instances.values) |instance| { 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_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 { 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), .{}); _ = try storage.read_all(path, std.mem.sliceAsBytes(fragment_spirv), .{});
std.debug.assert(bytes_read.len == fragment_file_stat.size);
var loaded = coral.asyncio.Future(std.mem.Allocator.Error!Effect){}; var loaded = coral.asyncio.Future(std.mem.Allocator.Error!Effect){};
rendering.enqueue_work(.{ rendering.enqueue_work(.{
.load_effect = .{ .load_effect = .{
.desc = .{ .desc = .{
.fragment_spirv_ops = fragment_spirv_ops, .fragment_spirv = fragment_spirv,
}, },
.loaded = &loaded, .loaded = &loaded,
@ -129,6 +127,20 @@ pub const Assets = struct {
return error.FormatUnsupported; return error.FormatUnsupported;
} }
pub fn update_effect(_: *Assets, effect: Effect, properties: anytype) bool {
var setted = coral.asyncio.Future(bool){};
rendering.enqueue_work(.{
.update_effect = .{
.properties = std.mem.asBytes(properties),
.effect = effect,
.setted = &setted,
},
});
return setted.get().*;
}
pub const thread_restriction = .main; pub const thread_restriction = .main;
}; };
@ -201,6 +213,13 @@ pub const Input = union (enum) {
pub const Effect = handles.Effect; pub const Effect = handles.Effect;
pub const Rect = struct {
left: f32,
top: f32,
right: f32,
bottom: f32,
};
pub const Texture = handles.Texture; pub const Texture = handles.Texture;
pub fn poll(app: flow.Write(App), inputs: msg.Send(Input)) !void { pub fn poll(app: flow.Write(App), inputs: msg.Send(Input)) !void {

View File

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

View File

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

View File

@ -185,7 +185,7 @@ const Frame = struct {
bindings.vertex_buffers[vertex_indices.mesh] = quad_vertex_buffer; bindings.vertex_buffers[vertex_indices.mesh] = quad_vertex_buffer;
switch (pools.textures.get(@intFromEnum(self.current_source_texture)).?.access) { switch (pools.textures.get(@intFromEnum(self.current_source_texture)).?.*) {
.render => |render| { .render => |render| {
bindings.fs.images[0] = render.color_image; bindings.fs.images[0] = render.color_image;
bindings.fs.samplers[0] = render.sampler; bindings.fs.samplers[0] = render.sampler;
@ -197,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)).?; const effect = pools.effects.get(@intFromEnum(self.current_effect)).?;
sokol.gfx.applyPipeline(effect.pipeline); sokol.gfx.applyPipeline(effect.pipeline);
if (self.current_target_texture) |target_texture| { if (effect.has_properties_buffer) |properties_buffer| {
const texture = pools.textures.get(@intFromEnum(target_texture)).?; bindings.fs.storage_buffers[0] = properties_buffer;
sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&lina.orthographic_projection(-1.0, 1.0, .{
.left = 0,
.top = 0,
.right = @floatFromInt(texture.width),
.bottom = @floatFromInt(texture.height),
})));
} else {
sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&lina.orthographic_projection(-1.0, 1.0, .{
.left = 0,
.top = 0,
.right = @floatFromInt(self.swapchain.width),
.bottom = @floatFromInt(self.swapchain.height),
})));
} }
sokol.gfx.applyUniforms(.FS, 0, sokol.gfx.asRange(effect.properties));
while (true) { while (true) {
const buffer_index = self.flushed_count / batches_per_buffer; const buffer_index = self.flushed_count / batches_per_buffer;
const buffer_offset = self.flushed_count % batches_per_buffer; const buffer_offset = self.flushed_count % batches_per_buffer;
@ -246,8 +238,10 @@ const Frame = struct {
self.current_effect = command.effect; self.current_effect = command.effect;
if (pools.effects.get(@intFromEnum(self.current_effect))) |effect| { if (command.properties.len != 0) {
@memcpy(effect.properties, command.properties); 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| { 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"), .static => @panic("Cannot render to static textures"),
.render => |render| render.attachments, .render => |render| render.attachments,
}; };
@ -382,7 +376,7 @@ const Pools = struct {
} }
_ = try pools.create_effect(.{ _ = 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{ 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 quad_vertex_buffer: sokol.gfx.Buffer = undefined;
var view_buffer: sokol.gfx.Buffer = undefined;
const vertex_indices = .{ const vertex_indices = .{
.mesh = 0, .mesh = 0,
.instance = 1, .instance = 1,
@ -541,6 +537,18 @@ fn run(window: *ext.SDL_Window) !void {
.type = .VERTEXBUFFER, .type = .VERTEXBUFFER,
}); });
view_buffer = sokol.gfx.makeBuffer(.{
.data = sokol.gfx.asRange(&lina.orthographic_projection(-1.0, 1.0, .{
.left = 0,
.top = 0,
.right = @floatFromInt(1280),
.bottom = @floatFromInt(720),
})),
.type = .STORAGEBUFFER,
.usage = .IMMUTABLE,
});
var has_commands_head: ?*Commands = null; var has_commands_head: ?*Commands = null;
var has_commands_tail: ?*Commands = null; var has_commands_tail: ?*Commands = null;

View File

@ -13,50 +13,29 @@ const std = @import("std");
pub const Effect = struct { pub const Effect = struct {
shader: sokol.gfx.Shader, shader: sokol.gfx.Shader,
pipeline: sokol.gfx.Pipeline, pipeline: sokol.gfx.Pipeline,
properties: []coral.io.Byte, has_properties_buffer: ?sokol.gfx.Buffer = null,
pub fn deinit(self: *Effect) void { pub fn deinit(self: *Effect) void {
coral.heap.allocator.free(self.properties);
sokol.gfx.destroyPipeline(self.pipeline); sokol.gfx.destroyPipeline(self.pipeline);
sokol.gfx.destroyShader(self.shader); sokol.gfx.destroyShader(self.shader);
if (self.has_properties_buffer) |parameters_buffer| {
sokol.gfx.destroyBuffer(parameters_buffer);
}
self.* = undefined; self.* = undefined;
} }
pub fn init(desc: handles.Effect.Desc) spirv.Error!Effect { pub fn init(desc: handles.Effect.Desc) !Effect {
var spirv_arena = std.heap.ArenaAllocator.init(coral.heap.allocator); var spirv_unit = try spirv.Unit.init(coral.heap.allocator);
defer { defer spirv_unit.deinit();
spirv_arena.deinit();
}
const spirv_program = try spirv.analyze(&spirv_arena, .{ try spirv_unit.compile(&spirv.to_ops(@embedFile("./shaders/2d_default.vert.spv")), .vertex);
.target = try switch (sokol.gfx.queryBackend()) { try spirv_unit.compile(desc.fragment_spirv, .fragment);
.GLCORE => spirv.Target.glsl,
else => error.InvalidSPIRV,
},
.vertex_source = .{ const shader = sokol.gfx.makeShader(spirv_unit.shader_desc());
.ops = &spirv.to_ops(@embedFile("./shaders/2d_default.vert.spv")),
},
.fragment_source = .{
.ops = desc.fragment_spirv_ops,
},
});
const shader = sokol.gfx.makeShader(shader_desc: {
const shader_desc = sokol.gfx.ShaderDesc{
.vs = stage_desc(spirv_program.vertex_stage),
.fs = stage_desc(spirv_program.fragment_stage),
};
// TODO: Vertex attributes, for some reason they aren't needed?
break: shader_desc shader_desc;
});
// TODO: Review blending rules.
const pipeline = sokol.gfx.makePipeline(pipeline_desc: { const pipeline = sokol.gfx.makePipeline(pipeline_desc: {
var pipeline_desc = sokol.gfx.PipelineDesc{ var pipeline_desc = sokol.gfx.PipelineDesc{
.label = "Effect pipeline", .label = "Effect pipeline",
@ -79,106 +58,33 @@ pub const Effect = struct {
break: pipeline_desc pipeline_desc; break: pipeline_desc pipeline_desc;
}); });
const properties = try coral.heap.allocator.alloc(
coral.io.Byte,
if (spirv_program.fragment_stage.has_uniform_blocks[0]) |uniform_block| uniform_block.size() else 0,
);
errdefer { errdefer {
coral.heap.allocator.free(properties);
sokol.gfx.destroyPipeline(pipeline); sokol.gfx.destroyPipeline(pipeline);
sokol.gfx.destroyShader(shader); sokol.gfx.destroyShader(shader);
} }
if (spirv_unit.shader_stage(.fragment).has_storage_buffer[0]) |storage_buffer| {
const properties_buffer = sokol.gfx.makeBuffer(.{
.size = storage_buffer.minimum_size,
.type = .STORAGEBUFFER,
.usage = .STREAM,
});
errdefer {
sokol.gfx.destroyBuffer(properties_buffer);
}
return .{
.shader = shader,
.pipeline = pipeline,
.has_properties_buffer = properties_buffer,
};
} else {
return .{ return .{
.shader = shader, .shader = shader,
.pipeline = pipeline, .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;
});
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,
};
}
}
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{ const vertex_layout_state = sokol.gfx.VertexLayoutState{
@ -238,114 +144,113 @@ pub const Effect = struct {
}; };
}; };
pub const Texture = struct { pub const Texture = union (enum) {
width: u16, render: Render,
height: u16, static: Static,
access: Access,
pub const Access = union (enum) { const Render = struct {
render: RenderAccess,
static: StaticAccess,
};
pub const RenderAccess = struct {
sampler: sokol.gfx.Sampler, sampler: sokol.gfx.Sampler,
color_image: sokol.gfx.Image, color_image: sokol.gfx.Image,
depth_image: sokol.gfx.Image, depth_image: sokol.gfx.Image,
attachments: sokol.gfx.Attachments, attachments: sokol.gfx.Attachments,
}; view_buffer: sokol.gfx.Buffer,
pub const StaticAccess = struct { fn deinit(self: *Render) void {
sampler: sokol.gfx.Sampler, sokol.gfx.destroyImage(self.color_image);
image: sokol.gfx.Image, sokol.gfx.destroyImage(self.depth_image);
}; sokol.gfx.destroySampler(self.sampler);
sokol.gfx.destroyAttachments(self.attachments);
pub fn deinit(self: *Texture) void { sokol.gfx.destroyBuffer(self.view_buffer);
switch (self.access) {
.render => |render| {
sokol.gfx.destroyImage(render.color_image);
sokol.gfx.destroyImage(render.depth_image);
sokol.gfx.destroySampler(render.sampler);
sokol.gfx.destroyAttachments(render.attachments);
},
.static => |static| {
sokol.gfx.destroyImage(static.image);
sokol.gfx.destroySampler(static.sampler);
},
}
self.* = undefined; self.* = undefined;
} }
pub fn init(desc: handles.Texture.Desc) std.mem.Allocator.Error!Texture { fn init(format: handles.Texture.Desc.Format, access: handles.Texture.Desc.Access.Render) std.mem.Allocator.Error!Render {
const pixel_format = switch (desc.format) { const color_image = sokol.gfx.makeImage(.{
.pixel_format = switch (format) {
.rgba8 => sokol.gfx.PixelFormat.RGBA8, .rgba8 => sokol.gfx.PixelFormat.RGBA8,
.bgra8 => sokol.gfx.PixelFormat.BGRA8, .bgra8 => sokol.gfx.PixelFormat.BGRA8,
}; },
switch (desc.access) { .width = access.width,
.render => |render| { .height = access.height,
const color_image = sokol.gfx.makeImage(.{
.pixel_format = pixel_format,
.width = render.width,
.height = render.height,
.render_target = true, .render_target = true,
}); });
const depth_image = sokol.gfx.makeImage(.{ const depth_image = sokol.gfx.makeImage(.{
.width = render.width, .width = access.width,
.height = render.height, .height = access.height,
.render_target = true, .render_target = true,
.pixel_format = .DEPTH_STENCIL, .pixel_format = .DEPTH_STENCIL,
}); });
const attachments = sokol.gfx.makeAttachments(attachments_desc: { const attachments = sokol.gfx.makeAttachments(attachments_desc: {
var attachments_desc = sokol.gfx.AttachmentsDesc{ var desc = sokol.gfx.AttachmentsDesc{
.depth_stencil = .{ .depth_stencil = .{
.image = depth_image, .image = depth_image,
}, },
}; };
attachments_desc.colors[0] = .{ desc.colors[0] = .{
.image = color_image, .image = color_image,
}; };
break: attachments_desc attachments_desc; break: attachments_desc desc;
}); });
const sampler = sokol.gfx.makeSampler(.{}); const sampler = sokol.gfx.makeSampler(.{});
return .{ const view_buffer = sokol.gfx.makeBuffer(.{
.width = render.width, .data = sokol.gfx.asRange(&lina.orthographic_projection(-1.0, 1.0, .{
.height = render.height, .left = 0,
.top = 0,
.right = @floatFromInt(access.width),
.bottom = @floatFromInt(access.height),
})),
.access = .{ .type = .STORAGEBUFFER,
.render = .{ .usage = .IMMUTABLE,
});
return .{
.attachments = attachments, .attachments = attachments,
.sampler = sampler, .sampler = sampler,
.color_image = color_image, .color_image = color_image,
.depth_image = depth_image, .depth_image = depth_image,
}, .view_buffer = view_buffer,
};
} }
}; };
const Static = struct {
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;
}, },
.static => |static| { .pixel_format = switch (format) {
const height = std.math.cast(u16, static.data.len / (static.width * desc.format.byte_size())) orelse { .rgba8 => sokol.gfx.PixelFormat.RGBA8,
return error.OutOfMemory; .bgra8 => sokol.gfx.PixelFormat.BGRA8,
},
.width = access.width,
}; };
const image = sokol.gfx.makeImage(image_desc: { desc.data.subimage[0][0] = sokol.gfx.asRange(access.data);
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 desc;
break: image_desc image_desc;
}); });
const sampler = sokol.gfx.makeSampler(.{}); const sampler = sokol.gfx.makeSampler(.{});
@ -356,17 +261,23 @@ pub const Texture = struct {
} }
return .{ return .{
.width = static.width,
.height = height,
.access = .{
.static = .{
.image = image, .image = image,
.sampler = sampler, .sampler = sampler,
},
},
}; };
},
} }
};
pub fn deinit(self: *Texture) void {
switch (self.*) {
.static => |*static| static.deinit(),
.render => |*render| render.deinit(),
}
}
pub fn init(desc: handles.Texture.Desc) std.mem.Allocator.Error!Texture {
return switch (desc.access) {
.static => |static| .{.static = try Static.init(desc.format, static)},
.render => |render| .{.render = try Render.init(desc.format, render)},
};
} }
}; };

View File

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

View File

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