renderer-mvp/post-processing #56
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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| {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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];
|
||||
|
||||
return .{
|
||||
.shader = shader,
|
||||
.pipeline = pipeline,
|
||||
.has_properties_buffer = properties_buffer,
|
||||
stage_uniform_block.layout = switch (uniform_block.layout) {
|
||||
.std140 => .STD140,
|
||||
};
|
||||
} else {
|
||||
return .{
|
||||
.shader = shader,
|
||||
.pipeline = pipeline,
|
||||
|
||||
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{
|
||||
|
@ -144,140 +238,135 @@ 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);
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const Static = struct {
|
||||
image: sokol.gfx.Image,
|
||||
pub const StaticAccess = struct {
|
||||
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,
|
||||
};
|
||||
}
|
||||
image: sokol.gfx.Image,
|
||||
};
|
||||
|
||||
pub fn deinit(self: *Texture) void {
|
||||
switch (self.*) {
|
||||
.static => |*static| static.deinit(),
|
||||
.render => |*render| render.deinit(),
|
||||
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;
|
||||
}
|
||||
|
||||
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)},
|
||||
const pixel_format = switch (desc.format) {
|
||||
.rgba8 => sokol.gfx.PixelFormat.RGBA8,
|
||||
.bgra8 => sokol.gfx.PixelFormat.BGRA8,
|
||||
};
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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,289 +150,247 @@ 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;
|
||||
|
||||
pub fn stage_desc(self: Shader) sokol.gfx.ShaderStageDesc {
|
||||
var stage = sokol.gfx.ShaderStageDesc{
|
||||
.entry = "main",
|
||||
.source = self.decompiled_source,
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
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 Stage = enum {
|
||||
vertex,
|
||||
fragment,
|
||||
};
|
||||
|
||||
pub const Unit = struct {
|
||||
arena: std.heap.ArenaAllocator,
|
||||
context: ext.spvc_context,
|
||||
attrs_used: u32 = 0,
|
||||
stages: [std.enums.values(Stage).len]Shader = .{.{}, .{}},
|
||||
|
||||
pub fn compile(self: *Unit, spirv_ops: []const u32, stage: Stage) Error!void {
|
||||
const execution_model, const shader = switch (stage) {
|
||||
.vertex => .{ext.SpvExecutionModelVertex, &self.stages[0]},
|
||||
.fragment => .{ext.SpvExecutionModelFragment, &self.stages[1]},
|
||||
};
|
||||
|
||||
const Backend = struct {
|
||||
target: ext.spvc_backend,
|
||||
option_values: []const struct {ext.spvc_compiler_option, c_uint},
|
||||
};
|
||||
|
||||
const backend: Backend = try switch (sokol.gfx.queryBackend()) {
|
||||
.GLCORE => .{
|
||||
.target = ext.SPVC_BACKEND_GLSL,
|
||||
|
||||
.option_values = &.{
|
||||
.{ext.SPVC_COMPILER_OPTION_GLSL_VERSION, 430},
|
||||
.{ext.SPVC_COMPILER_OPTION_GLSL_ES, @intFromBool(false)},
|
||||
.{ext.SPVC_COMPILER_OPTION_GLSL_VULKAN_SEMANTICS, @intFromBool(false)},
|
||||
.{ext.SPVC_COMPILER_OPTION_GLSL_EMIT_UNIFORM_BUFFER_AS_PLAIN_UNIFORMS, @intFromBool(true)},
|
||||
.{ext.SPVC_COMPILER_OPTION_FLIP_VERTEX_Y, @intFromBool(true)},
|
||||
},
|
||||
},
|
||||
|
||||
else => error.UnsupportedBackend,
|
||||
};
|
||||
|
||||
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;
|
||||
if (is_std430) {
|
||||
return error.UnsupportedSPIRV;
|
||||
}
|
||||
|
||||
break: parse_and_configure compiler;
|
||||
};
|
||||
const uniform_count = ext.spvc_type_get_num_member_types(type_handle);
|
||||
|
||||
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));
|
||||
if (uniform_count > UniformBlock.max_uniforms) {
|
||||
return error.UnsupportedSPIRV;
|
||||
}
|
||||
|
||||
break: create compiler_options;
|
||||
}));
|
||||
const uniforms = try arena_allocator.alloc(Uniform, uniform_count);
|
||||
|
||||
try to_error(ext.spvc_compiler_compile(compiler, @ptrCast(&shader.decompiled_source.ptr)));
|
||||
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));
|
||||
|
||||
shader.decompiled_source.len = std.mem.span(shader.decompiled_source.ptr).len;
|
||||
if (ext.spvc_type_get_num_array_dimensions(member_type_handle) > 1) {
|
||||
return error.UnsupportedSPIRV;
|
||||
}
|
||||
|
||||
std.log.info("{s}", .{shader.decompiled_source});
|
||||
}
|
||||
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)),
|
||||
}),
|
||||
|
||||
pub fn deinit(self: *Unit) void {
|
||||
ext.spvc_context_destroy(self.context);
|
||||
self.arena.deinit();
|
||||
.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,
|
||||
},
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
1 => Uniform.Type.float,
|
||||
2 => Uniform.Type.float2,
|
||||
3 => Uniform.Type.float3,
|
||||
else => error.UnsupportedSPIRV,
|
||||
},
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator) std.mem.Allocator.Error!Unit {
|
||||
var context: ext.spvc_context = null;
|
||||
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,
|
||||
},
|
||||
|
||||
if (ext.spvc_context_create(&context) != ext.SPVC_SUCCESS) {
|
||||
return error.OutOfMemory;
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
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 {
|
||||
pub const Target = enum {
|
||||
glsl,
|
||||
};
|
||||
|
||||
pub fn analyze(arena: *std.heap.ArenaAllocator, options: Options) Error!Program {
|
||||
var context: ext.spvc_context = null;
|
||||
|
||||
if (ext.spvc_context_create(&context) != ext.SPVC_SUCCESS) {
|
||||
return error.OutOfMemory;
|
||||
}
|
||||
|
||||
defer {
|
||||
ext.spvc_context_destroy(context);
|
||||
}
|
||||
|
||||
ext.spvc_context_set_error_callback(context, log_errors, null);
|
||||
|
||||
const arena_allocator = arena.allocator();
|
||||
|
||||
return .{
|
||||
.vertex_stage = vertex_stage: {
|
||||
const compiler = try parse(arena, context, options.target, options.vertex_source);
|
||||
|
||||
var stage = Stage{
|
||||
.entry_point = try arena_allocator.dupeZ(u8, options.vertex_source.entry_point),
|
||||
.source = try compile(arena, compiler),
|
||||
};
|
||||
|
||||
const resources = create: {
|
||||
var resources: ext.spvc_resources = null;
|
||||
|
||||
try to_error(ext.spvc_compiler_create_shader_resources(compiler, &resources));
|
||||
|
||||
break: create resources;
|
||||
};
|
||||
|
||||
try stage.reflect_images_samplers(arena, compiler, resources);
|
||||
try stage.reflect_uniform_blocks(arena, compiler, resources);
|
||||
|
||||
std.log.info("{s}", .{stage.source});
|
||||
|
||||
break: vertex_stage stage;
|
||||
},
|
||||
|
||||
.fragment_stage = fragment_stage: {
|
||||
const compiler = try parse(arena, context, options.target, options.fragment_source);
|
||||
|
||||
var stage = Stage{
|
||||
.entry_point = try arena_allocator.dupeZ(u8, options.fragment_source.entry_point),
|
||||
.source = try compile(arena, compiler),
|
||||
};
|
||||
|
||||
const resources = create: {
|
||||
var resources: ext.spvc_resources = null;
|
||||
|
||||
try to_error(ext.spvc_compiler_create_shader_resources(compiler, &resources));
|
||||
|
||||
break: create resources;
|
||||
};
|
||||
|
||||
try stage.reflect_images_samplers(arena, compiler, resources);
|
||||
try stage.reflect_uniform_blocks(arena, compiler, resources);
|
||||
|
||||
std.log.info("{s}", .{stage.source});
|
||||
|
||||
break: fragment_stage stage;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn compile(arena: *std.heap.ArenaAllocator, compiler: ext.spvc_compiler) Error![:0]const u8 {
|
||||
const arena_allocator = arena.allocator();
|
||||
var source: [*:0]const u8 = "";
|
||||
|
||||
try to_error(ext.spvc_compiler_compile(compiler, @ptrCast(&source)));
|
||||
|
||||
return arena_allocator.dupeZ(u8, std.mem.span(source));
|
||||
}
|
||||
|
||||
fn log_errors(userdata: ?*anyopaque, error_message: [*c]const u8) callconv(.C) void {
|
||||
std.debug.assert(userdata == null);
|
||||
std.log.err("{s}", .{error_message});
|
||||
}
|
||||
|
||||
fn parse(arena: *std.heap.ArenaAllocator, context: ext.spvc_context, target: Target, source: Source) Error!ext.spvc_compiler {
|
||||
var parsed_ir: ext.spvc_parsed_ir = null;
|
||||
|
||||
try to_error(ext.spvc_context_parse_spirv(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 => {},
|
||||
|
|
Loading…
Reference in New Issue