diff --git a/debug/ca.frag b/debug/ca.frag index 1f579bf..74e9519 100644 --- a/debug/ca.frag +++ b/debug/ca.frag @@ -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; }; diff --git a/src/main.zig b/src/main.zig index 0a5b230..d9139d5 100644 --- a/src/main.zig +++ b/src/main.zig @@ -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| { diff --git a/src/ona/gfx.zig b/src/ona/gfx.zig index 4f0d7d5..99edf39 100644 --- a/src/ona/gfx.zig +++ b/src/ona/gfx.zig @@ -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 { diff --git a/src/ona/gfx/handles.zig b/src/ona/gfx/handles.zig index da4010a..942134f 100644 --- a/src/ona/gfx/handles.zig +++ b/src/ona/gfx/handles.zig @@ -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 { diff --git a/src/ona/gfx/lina.zig b/src/ona/gfx/lina.zig index 3020f76..5d3a599 100644 --- a/src/ona/gfx/lina.zig +++ b/src/ona/gfx/lina.zig @@ -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; diff --git a/src/ona/gfx/rendering.zig b/src/ona/gfx/rendering.zig index 6735fd5..4d53f11 100644 --- a/src/ona/gfx/rendering.zig +++ b/src/ona/gfx/rendering.zig @@ -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; diff --git a/src/ona/gfx/resources.zig b/src/ona/gfx/resources.zig index 1bf9c0f..7a34da9 100644 --- a/src/ona/gfx/resources.zig +++ b/src/ona/gfx/resources.zig @@ -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, + }, + }, + }; + }, + } } }; diff --git a/src/ona/gfx/shaders/2d_default.vert b/src/ona/gfx/shaders/2d_default.vert index 47cf412..4c4bb5e 100644 --- a/src/ona/gfx/shaders/2d_default.vert +++ b/src/ona/gfx/shaders/2d_default.vert @@ -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; }; diff --git a/src/ona/gfx/spirv.zig b/src/ona/gfx/spirv.zig index 222ed02..6405c68 100644 --- a/src/ona/gfx/spirv.zig +++ b/src/ona/gfx/spirv.zig @@ -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 => {},