From 1181e5623613d4f165ebf5315e26aafd973c270d Mon Sep 17 00:00:00 2001 From: sergeypdev Date: Sun, 15 Dec 2024 20:09:37 +0400 Subject: [PATCH] Post processing shader, extended .prog format to include attachment format and depth info --- assets/shaders/post_process.glsl | 45 +++++++-- assets/shaders/post_process.prog | 7 ++ assets/shaders/post_process.prog1 | 7 -- assets/shaders/triangle.glsl | 11 +- assets/shaders/triangle.prog | 4 +- src/AssetManager.zig | 10 +- src/Render2.zig | 161 ++++++++++++++++++++++++------ src/formats.zig | 20 +++- tools/asset_compiler.zig | 46 +++++---- 9 files changed, 229 insertions(+), 82 deletions(-) create mode 100644 assets/shaders/post_process.prog delete mode 100644 assets/shaders/post_process.prog1 diff --git a/assets/shaders/post_process.glsl b/assets/shaders/post_process.glsl index a46b74e..7b27b9d 100644 --- a/assets/shaders/post_process.glsl +++ b/assets/shaders/post_process.glsl @@ -1,20 +1,40 @@ -// Input, output blocks -VERTEX_EXPORT VertexData { - vec2 uv; -} VertexOut; +#extension GL_EXT_scalar_block_layout : require +#extension GL_EXT_nonuniform_qualifier : require + +#include "global.glsl" #if VERTEX_SHADER -layout(location = 0) in vec3 aPos; +// Input, output blocks +layout(location = 0) out VertexData { + vec2 uv; +} VertexOut; + +// QUAD +vec2 positions[6] = vec2[]( + vec2(-1, -1), + vec2(-1, 1), + vec2(1, 1), + + vec2(1, 1), + vec2(1, -1), + vec2(-1, -1) +); + void main() { - gl_Position = vec4(aPos, 1); - VertexOut.uv = aPos.xy * 0.5 + 0.5; + gl_Position = vec4(positions[gl_VertexIndex], 0, 1); + VertexOut.uv = positions[gl_VertexIndex].xy * 0.5 + 0.5; } #endif // VERTEX_SHADER #if FRAGMENT_SHADER +// Input, output blocks +layout(location = 0) in VertexData { + vec2 uv; +} VertexOut; + // Translated from https://github.com/TheRealMJP/BakingLab/blob/master/BakingLab/ACES.hlsl // sRGB => XYZ => D65_2_D60 => AP1 => RRT_SAT const mat3 ACESInputMat = mat3( @@ -51,9 +71,10 @@ vec3 ACESFitted(vec3 color) return color; } -layout(binding = 0) uniform sampler2D screen_sampler; - -out vec4 FragColor; +layout(push_constant, std430) uniform constants { + uint scene_color_texture; + uint scene_color_sampler; +} PushConstants; vec3 linearToSRGB(vec3 color) { vec3 x = color * 12.92f; @@ -67,8 +88,10 @@ vec3 linearToSRGB(vec3 color) { return clr; } +layout(location = 0) out vec4 FragColor; + void main() { - vec3 hdr_color = texture(screen_sampler, VertexOut.uv).rgb; + vec3 hdr_color = texture(sampler2D(global_textures2d[PushConstants.scene_color_texture], global_samplers[PushConstants.scene_color_sampler]), VertexOut.uv).rgb; hdr_color = ACESFitted(hdr_color); FragColor.rgb = hdr_color; diff --git a/assets/shaders/post_process.prog b/assets/shaders/post_process.prog new file mode 100644 index 0000000..0562204 --- /dev/null +++ b/assets/shaders/post_process.prog @@ -0,0 +1,7 @@ +{ + "vertex": "post_process.glsl", + "fragment": "post_process.glsl", + "compute": null, + "color_attachment_type": "swapchain", + "depth_stencil_attachment": false +} diff --git a/assets/shaders/post_process.prog1 b/assets/shaders/post_process.prog1 deleted file mode 100644 index 6038a08..0000000 --- a/assets/shaders/post_process.prog1 +++ /dev/null @@ -1,7 +0,0 @@ - -{ - "shader": "post_process.glsl", - "vertex": true, - "fragment": true, - "compute": false -} diff --git a/assets/shaders/triangle.glsl b/assets/shaders/triangle.glsl index 4d87bd2..3d29eb8 100644 --- a/assets/shaders/triangle.glsl +++ b/assets/shaders/triangle.glsl @@ -19,15 +19,6 @@ vec3 colors[3] = vec3[]( layout(location = 0) out vec3 VertexColor; -layout(push_constant) uniform constants { - vec3 my_vec; - float my_float; - mat4x4 my_mat; - uint tex_index1; - uint tex_index2; - uint tex_index3; -} PushConstants; - void main() { VertexColor = colors[gl_VertexIndex]; @@ -43,7 +34,7 @@ layout(location = 0) in vec3 VertexColor; layout(location = 0) out vec4 FragColor; void main() { - FragColor = vec4(VertexColor, 1.0); + FragColor = vec4(VertexColor * 10, 1.0); } #endif diff --git a/assets/shaders/triangle.prog b/assets/shaders/triangle.prog index 0c7f579..12b5455 100644 --- a/assets/shaders/triangle.prog +++ b/assets/shaders/triangle.prog @@ -1,5 +1,7 @@ { "vertex": "triangle.glsl", "fragment": "triangle.glsl", - "compute": null + "compute": null, + "color_attachment_type": "main", + "depth_stencil_attachment": true } diff --git a/src/AssetManager.zig b/src/AssetManager.zig index b789bdf..e6181df 100644 --- a/src/AssetManager.zig +++ b/src/AssetManager.zig @@ -369,6 +369,7 @@ fn loadShaderProgramErr(self: *AssetManager, id: AssetId) !LoadedShaderProgram { var push_constant_ranges_buf: [2]vk.PushConstantRange = undefined; const push_constant_ranges = getPushConstantRanges(program, &push_constant_ranges_buf); + std.debug.print("push constant ranges: {any}\n", .{push_constant_ranges}); // TODO: parse from shaders or something const pipeline_layout = try self.gc.device.createPipelineLayout(&.{ @@ -405,9 +406,12 @@ fn loadShaderProgramErr(self: *AssetManager, id: AssetId) !LoadedShaderProgram { vk.GraphicsPipelineCreateInfo{ .p_next = &vk.PipelineRenderingCreateInfo{ .color_attachment_count = 1, - .p_color_attachment_formats = &[_]vk.Format{.r16g16b16a16_sfloat}, - .depth_attachment_format = .d24_unorm_s8_uint, - .stencil_attachment_format = .d24_unorm_s8_uint, + .p_color_attachment_formats = &[_]vk.Format{switch (graphics_pipeline.color_attachment_type) { + .main => .r16g16b16a16_sfloat, + .swapchain => .r8g8b8a8_unorm, + }}, + .depth_attachment_format = if (graphics_pipeline.depth_stencil_attachment) .d24_unorm_s8_uint else .undefined, + .stencil_attachment_format = if (graphics_pipeline.depth_stencil_attachment) .d24_unorm_s8_uint else .undefined, .view_mask = 0, }, .base_pipeline_index = 0, diff --git a/src/Render2.zig b/src/Render2.zig index 45bccbb..8ffbdcd 100644 --- a/src/Render2.zig +++ b/src/Render2.zig @@ -400,7 +400,7 @@ fn allocateRenderTarget(self: *Render2) !MainRenderTarget { .mip_count = 1, .sync_state = .{}, }, - .color_descriptor = self.createPerFrameImageDescriptor(color_image_view, .read_only_optimal), + .color_descriptor = self.createPerFrameImageDescriptor(color_image_view, .shader_read_only_optimal), }; } @@ -453,6 +453,10 @@ fn createPerFrameImageDescriptor(self: *Render2, view: vk.ImageView, layout: vk. return result; } +fn pushConstants(cmds: GraphicsContext.CommandBuffer, layout: vk.PipelineLayout, stage_flags: vk.ShaderStageFlags, value: anytype) void { + cmds.pushConstants(layout, stage_flags, 0, @sizeOf(@TypeOf(value)), &value); +} + pub fn draw(self: *Render2) !void { const gc = self.gc; const device = gc.device; @@ -504,7 +508,7 @@ pub fn draw(self: *Render2) !void { const global_descriptor_set = self.global_descriptor_set; - device.updateDescriptorSets(1, &.{ + device.updateDescriptorSets(2, &.{ vk.WriteDescriptorSet{ .dst_set = global_descriptor_set, .dst_binding = 0, @@ -521,6 +525,22 @@ pub fn draw(self: *Render2) !void { .p_image_info = &[_]vk.DescriptorImageInfo{}, .p_texel_buffer_view = &[_]vk.BufferView{}, }, + vk.WriteDescriptorSet{ + .dst_set = global_descriptor_set, + .dst_binding = 1, + .dst_array_element = 0, + .descriptor_type = .sampler, + .descriptor_count = 1, + .p_image_info = &.{ + vk.DescriptorImageInfo{ + .sampler = self.screen_color_sampler, + .image_view = .null_handle, + .image_layout = .undefined, + }, + }, + .p_buffer_info = &[_]vk.DescriptorBufferInfo{}, + .p_texel_buffer_view = &[_]vk.BufferView{}, + }, }, 0, null); // TODO: move this into descriptorman? @@ -642,38 +662,112 @@ pub fn draw(self: *Render2) !void { cmds.draw(3, 2, 0, 0); } - try color_image.sync(cmds, .{ .stage_mask = .{ .blit_bit = true }, .access_mask = .{ .transfer_read_bit = true } }, .transfer_src_optimal, .{ .color_bit = true }); - try swapchain_image.sync(cmds, .{ .stage_mask = .{ .blit_bit = true }, .access_mask = .{ .transfer_write_bit = true } }, .transfer_dst_optimal, .{ .color_bit = true }); - cmds.blitImage( - color_image.handle, - color_image.sync_state.layout, - swapchain_image.handle, - swapchain_image.sync_state.layout, - 1, - &.{vk.ImageBlit{ - .src_subresource = vk.ImageSubresourceLayers{ - .aspect_mask = .{ .color_bit = true }, - .mip_level = 0, - .base_array_layer = 0, - .layer_count = 1, + // Post process and convert from f16 to rgba8_unorm + { + try swapchain_image.sync( + cmds, + .{ + .stage_mask = .{ .color_attachment_output_bit = true }, + .access_mask = .{ .color_attachment_write_bit = true }, }, - .src_offsets = .{ - vk.Offset3D{ .x = 0, .y = 0, .z = 0 }, - vk.Offset3D{ .x = @intCast(gc.swapchain_extent.width), .y = @intCast(gc.swapchain_extent.height), .z = 1 }, + .color_attachment_optimal, + .{ .color_bit = true }, + ); + try color_image.sync( + cmds, + .{ + .stage_mask = .{ .fragment_shader_bit = true }, + .access_mask = .{ .shader_sampled_read_bit = true }, }, - .dst_subresource = vk.ImageSubresourceLayers{ - .aspect_mask = .{ .color_bit = true }, - .mip_level = 0, - .base_array_layer = 0, - .layer_count = 1, + .shader_read_only_optimal, + .{ .color_bit = true }, + ); + cmds.beginRendering(&.{ + .render_area = vk.Rect2D{ .offset = .{ .x = 0, .y = 0 }, .extent = gc.swapchain_extent }, + .layer_count = 1, + .view_mask = 0, + .color_attachment_count = 1, + .p_color_attachments = &.{ + vk.RenderingAttachmentInfo{ + .clear_value = .{ .color = .{ .float_32 = .{ 0.8, 0.7, 0.6, 1.0 } } }, + .load_op = .dont_care, + .store_op = .store, + .image_layout = .color_attachment_optimal, + .image_view = swapchain_image_view, + .resolve_image_layout = .color_attachment_optimal, + .resolve_mode = .{}, + }, }, - .dst_offsets = .{ - vk.Offset3D{ .x = 0, .y = 0, .z = 0 }, - vk.Offset3D{ .x = @intCast(gc.swapchain_extent.width), .y = @intCast(gc.swapchain_extent.height), .z = 1 }, - }, - }}, - .nearest, - ); + .p_depth_attachment = null, + .p_stencil_attachment = null, + }); + defer cmds.endRendering(); + + cmds.setDepthTestEnable(vk.FALSE); + cmds.setDepthWriteEnable(vk.FALSE); + + const post_process = self.assetman.resolveShaderProgram(a.ShaderPrograms.shaders.post_process); + + cmds.bindPipeline(.graphics, post_process.pipeline); + cmds.bindDescriptorSets(.graphics, post_process.layout, 0, 1, &.{global_descriptor_set}, 0, null); + + pushConstants(cmds, post_process.layout, .{ .fragment_bit = true }, PostProcessPushConstants{ + .scene_color_texture = main_render_target.color_descriptor.index(), + .scene_color_sampler = 0, + }); + + cmds.setViewportWithCount(1, &.{vk.Viewport{ + .x = 0, + .y = 0, + .width = @floatFromInt(gc.swapchain_extent.width), + .height = @floatFromInt(gc.swapchain_extent.height), + .min_depth = 0, + .max_depth = 1, + }}); + cmds.setScissorWithCount(1, &.{vk.Rect2D{ + .offset = .{ .x = 0, .y = 0 }, + .extent = gc.swapchain_extent, + }}); + + cmds.draw(6, 1, 0, 0); + } + + // Direct blit without PP + // if (false) { + // try color_image.sync(cmds, .{ .stage_mask = .{ .blit_bit = true }, .access_mask = .{ .transfer_read_bit = true } }, .transfer_src_optimal, .{ .color_bit = true }); + // try swapchain_image.sync(cmds, .{ .stage_mask = .{ .blit_bit = true }, .access_mask = .{ .transfer_write_bit = true } }, .transfer_dst_optimal, .{ .color_bit = true }); + // cmds.blitImage( + // color_image.handle, + // color_image.sync_state.layout, + // swapchain_image.handle, + // swapchain_image.sync_state.layout, + // 1, + // &.{vk.ImageBlit{ + // .src_subresource = vk.ImageSubresourceLayers{ + // .aspect_mask = .{ .color_bit = true }, + // .mip_level = 0, + // .base_array_layer = 0, + // .layer_count = 1, + // }, + // .src_offsets = .{ + // vk.Offset3D{ .x = 0, .y = 0, .z = 0 }, + // vk.Offset3D{ .x = @intCast(gc.swapchain_extent.width), .y = @intCast(gc.swapchain_extent.height), .z = 1 }, + // }, + // .dst_subresource = vk.ImageSubresourceLayers{ + // .aspect_mask = .{ .color_bit = true }, + // .mip_level = 0, + // .base_array_layer = 0, + // .layer_count = 1, + // }, + // .dst_offsets = .{ + // vk.Offset3D{ .x = 0, .y = 0, .z = 0 }, + // vk.Offset3D{ .x = @intCast(gc.swapchain_extent.width), .y = @intCast(gc.swapchain_extent.height), .z = 1 }, + // }, + // }}, + // .nearest, + // ); + // } + try swapchain_image.sync(cmds, .{ .stage_mask = .{}, .access_mask = .{} }, .present_src_khr, .{ .color_bit = true }); } try cmds.endCommandBuffer(); @@ -837,3 +931,8 @@ const GlobalUniform = extern struct { view: View, }; + +const PostProcessPushConstants = extern struct { + scene_color_texture: u32, + scene_color_sampler: u32, +}; diff --git a/src/formats.zig b/src/formats.zig index 731b84c..49c6d17 100644 --- a/src/formats.zig +++ b/src/formats.zig @@ -129,7 +129,7 @@ pub const ShaderProgram = union(ShaderProgramPipelineType) { }; pub const ShaderStage = struct { - source: []u8, + source: []align(4) u8, push_constant_range: PushConstantRange, pub fn serialize(self: *ShaderStage, serializer: *Serializer) !void { @@ -140,10 +140,17 @@ pub const ShaderProgram = union(ShaderProgramPipelineType) { } }; + pub const ColorAttachmentType = enum(u8) { + main = 0, + swapchain = 1, + }; + pub const GraphicsPipeline = struct { // TODO: extend to support tesselation, geometry, mesh shaders and etc vertex: ShaderStage, fragment: ShaderStage, + color_attachment_type: ColorAttachmentType, + depth_stencil_attachment: bool, }; pub const ComputePipeline = struct { @@ -169,6 +176,8 @@ pub const ShaderProgram = union(ShaderProgramPipelineType) { try self.graphics.vertex.serialize(serializer); try self.graphics.fragment.serialize(serializer); + try serializer.serializeEnum(ColorAttachmentType, &self.graphics.color_attachment_type); + try serializer.serializeBool(&self.graphics.depth_stencil_attachment); }, .compute => { if (!serializer.write) { @@ -198,6 +207,15 @@ pub const Serializer = struct { } } + pub fn serializeEnum(self: *Serializer, comptime T: type, data: *T) !void { + if (@typeInfo(T) != .Enum) { + @compileError("serializeEnum expects enum"); + } + + const IntType = @typeInfo(T).Enum.tag_type; + try self.serializeInt(IntType, @ptrCast(data)); + } + pub fn serializeBool(self: *Serializer, data: *bool) !void { var buf: [1]u8 = undefined; if (self.write) { diff --git a/tools/asset_compiler.zig b/tools/asset_compiler.zig index f7017fe..bbc4f94 100644 --- a/tools/asset_compiler.zig +++ b/tools/asset_compiler.zig @@ -626,6 +626,7 @@ fn processShader(allocator: std.mem.Allocator, flags: []const []const u8, input: }, } + // TODO: align pointer to 4 bytes var result = ProcessedShader{ .spirv = compile_result.stdout }; { @@ -633,25 +634,20 @@ fn processShader(allocator: std.mem.Allocator, flags: []const []const u8, input: try spvReflectTry(c.spvReflectCreateShaderModule(compile_result.stdout.len, compile_result.stdout.ptr, &shader_module)); defer c.spvReflectDestroyShaderModule(&shader_module); - if (shader_module.push_constant_blocks != null) { + var spv_result: c.SpvReflectResult = c.SPV_REFLECT_RESULT_SUCCESS; + const push_constant_block: ?*const c.SpvReflectBlockVariable = @ptrCast(c.spvReflectGetEntryPointPushConstantBlock(&shader_module, "main", &spv_result)); + spvReflectTry(spv_result) catch |err| switch (err) { + error.SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND => {}, + else => return err, + }; + + if (push_constant_block) |block| { // Assuming single push constant block per stage, this is what glslc enforces std.debug.assert(shader_module.push_constant_block_count == 1); - const block = shader_module.push_constant_blocks[0]; result.push_constant_range = .{ .offset = block.offset, .size = block.size }; } } - // NOTE: Dep file is technically incorrect, but zig build system doesn't care, it will collect all dependencies after colon - // even if they are not for the same file it's processing - - // TODO: figure out a better way to handle depfile - // if (maybe_dep_file) |dep_file| { - // const file = try std.fs.cwd().openFile(dep_file, .{ .mode = .read_write }); - // defer file.close(); - // try file.seekFromEnd(0); - // try file.writeAll(old_depfile_contents); - // } - return result; } @@ -709,6 +705,8 @@ fn processShaderProgram(allocator: std.mem.Allocator, input: []const u8, output_ vertex: ?[]const u8, fragment: ?[]const u8, compute: ?[]const u8, + color_attachment_type: []const u8, + depth_stencil_attachment: bool, }; const program = try std.json.parseFromSlice(InputShaderProgram, allocator, file_contents, .{}); defer program.deinit(); @@ -717,14 +715,26 @@ fn processShaderProgram(allocator: std.mem.Allocator, input: []const u8, output_ if (program.value.vertex != null and program.value.fragment != null) { result = .{ .graphics = undefined }; + + result.graphics.color_attachment_type = blk: { + if (std.mem.eql(u8, program.value.color_attachment_type, "main")) { + break :blk .main; + } + if (std.mem.eql(u8, program.value.color_attachment_type, "swapchain")) { + break :blk .swapchain; + } + + return error.UnsupportedColorAttachmentType; + }; + result.graphics.depth_stencil_attachment = program.value.depth_stencil_attachment; // TODO: remove duplication { const stage = program.value.vertex.?; const shader_source_path = try std.fs.path.resolve(allocator, &.{ input_dir, stage }); const relative_path = try std.fs.path.relative(allocator, try std.fs.cwd().realpathAlloc(allocator, "."), shader_source_path); - const shader = try processShader(allocator, &.{ "-DVERTEX_SHADER=1", "-fshader-stage=vert" }, relative_path, dep_file); - result.graphics.vertex.source = shader.spirv; + const shader = try processShader(allocator, &.{ "-DVERTEX_SHADER=1", "-fshader-stage=vert", "-fpreserve-bindings" }, relative_path, dep_file); + result.graphics.vertex.source = @alignCast(shader.spirv); result.graphics.vertex.push_constant_range = .{ .offset = shader.push_constant_range.offset, .size = shader.push_constant_range.size }; } { @@ -732,8 +742,8 @@ fn processShaderProgram(allocator: std.mem.Allocator, input: []const u8, output_ const shader_source_path = try std.fs.path.resolve(allocator, &.{ input_dir, stage }); const relative_path = try std.fs.path.relative(allocator, try std.fs.cwd().realpathAlloc(allocator, "."), shader_source_path); - const shader = try processShader(allocator, &.{ "-DFRAGMENT_SHADER=1", "-fshader-stage=frag" }, relative_path, dep_file); - result.graphics.fragment.source = shader.spirv; + const shader = try processShader(allocator, &.{ "-DFRAGMENT_SHADER=1", "-fshader-stage=frag", "-fpreserve-bindings" }, relative_path, dep_file); + result.graphics.fragment.source = @alignCast(shader.spirv); result.graphics.fragment.push_constant_range = .{ .offset = shader.push_constant_range.offset, .size = shader.push_constant_range.size }; } } else if (program.value.compute != null) { @@ -744,7 +754,7 @@ fn processShaderProgram(allocator: std.mem.Allocator, input: []const u8, output_ const relative_path = try std.fs.path.relative(allocator, try std.fs.cwd().realpathAlloc(allocator, "."), shader_source_path); const shader = try processShader(allocator, &.{ "-DCOMPUTE_SHADER=1", "-fshader-stage=compute" }, relative_path, dep_file); - result.compute.compute.source = shader.spirv; + result.compute.compute.source = @alignCast(shader.spirv); result.compute.compute.push_constant_range = .{ .offset = shader.push_constant_range.offset, .size = shader.push_constant_range.size }; } else { std.log.err("Provide vertex and fragment shaders for a graphics pipeline or a compute shader for a compute pipeline\n", .{});