From 291c65ed1801fa43e77a382acb8ef95ad3abf6d4 Mon Sep 17 00:00:00 2001 From: Kbz-8 Date: Sat, 20 Jun 2026 02:41:37 +0200 Subject: [PATCH] improving renderpasses support --- src/software/SoftCommandBuffer.zig | 37 +++ src/software/SoftPipeline.zig | 18 +- src/software/device/Renderer.zig | 2 + src/software/device/fragment.zig | 13 +- src/software/device/rasterizer.zig | 9 +- src/software/device/rasterizer/bresenham.zig | 2 + src/software/device/rasterizer/common.zig | 224 +++++++++++++----- .../device/rasterizer/edge_function.zig | 2 + src/vulkan/Pipeline.zig | 28 +++ src/vulkan/lib_vulkan.zig | 1 - 10 files changed, 265 insertions(+), 71 deletions(-) diff --git a/src/software/SoftCommandBuffer.zig b/src/software/SoftCommandBuffer.zig index 720d710..88a30be 100644 --- a/src/software/SoftCommandBuffer.zig +++ b/src/software/SoftCommandBuffer.zig @@ -33,6 +33,38 @@ interface: Interface, command_allocator: std.heap.ArenaAllocator, commands: std.ArrayList(Command), +fn attachmentIsReferencedBySubpass(render_pass: *SoftRenderPass, attachment_index: u32) bool { + for (render_pass.interface.subpasses) |subpass| { + if (subpass.input_attachments) |attachments| { + for (attachments) |attachment| { + if (attachment.attachment == attachment_index) + return true; + } + } + + if (subpass.color_attachments) |attachments| { + for (attachments) |attachment| { + if (attachment.attachment == attachment_index) + return true; + } + } + + if (subpass.resolve_attachments) |attachments| { + for (attachments) |attachment| { + if (attachment.attachment == attachment_index) + return true; + } + } + + if (subpass.depth_stencil_attachments) |attachment| { + if (attachment.attachment == attachment_index) + return true; + } + } + + return false; +} + pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.CommandBufferAllocateInfo) VkError!*Self { const self = allocator.create(Self) catch return VkError.OutOfHostMemory; errdefer allocator.destroy(self); @@ -267,9 +299,13 @@ pub fn beginRenderPass(interface: *Interface, render_pass: *base.RenderPass, fra const impl: *Impl = @ptrCast(@alignCast(context)); device.renderer.render_pass = impl.render_pass; device.renderer.framebuffer = impl.framebuffer; + device.renderer.render_area = impl.render_area; device.renderer.subpass_index = 0; for (impl.render_pass.interface.attachments, impl.framebuffer.interface.attachments, 0..) |desc, attachment, index| { + if (!attachmentIsReferencedBySubpass(impl.render_pass, @intCast(index))) + continue; + const image: *SoftImage = @alignCast(@fieldParentPtr("interface", attachment.image)); var clear_mask: vk.ImageAspectFlags = .{}; @@ -987,6 +1023,7 @@ pub fn endRenderPass(interface: *Interface) VkError!void { device.renderer.render_pass = null; device.renderer.framebuffer = null; + device.renderer.render_area = null; } }; diff --git a/src/software/SoftPipeline.zig b/src/software/SoftPipeline.zig index 6b31872..230bcbc 100644 --- a/src/software/SoftPipeline.zig +++ b/src/software/SoftPipeline.zig @@ -303,6 +303,14 @@ fn imageMipLevel(image_view: *SoftImageView, lod: ?i32) u32 { return range.base_mip_level + @min(mip_lod, max_lod); } +fn imageReadAspect(image_view: *SoftImageView, comptime int_read: bool) vk.ImageAspectFlags { + const aspect = image_view.interface.subresource_range.aspect_mask; + if (aspect.depth_bit and aspect.stencil_bit) { + return if (int_read) .{ .stencil_bit = true } else .{ .depth_bit = true }; + } + return aspect; +} + fn subpassDataCoord(x: i32, y: i32, z: i32) SpvRuntimeError!vk.Offset3D { const coord = current_fragment_coord orelse return SpvRuntimeError.Unknown; return .{ .x = coord.x + x, .y = coord.y + y, .z = coord.z + z }; @@ -331,14 +339,15 @@ fn readImageFloat4(context: *anyopaque, dim: spv.SpvDim, x: i32, y: i32, z: i32, .cube => cube_face, else => 0, }; + const aspect_mask = imageReadAspect(image_view, false); pixel = SoftSampler.swizzleFloat4(image.readFloat4( image_coord, .{ - .aspect_mask = image_view.interface.subresource_range.aspect_mask, + .aspect_mask = aspect_mask, .mip_level = mip_level, .array_layer = array_layer, }, - image_view.interface.format, + base.format.fromAspect(image_view.interface.format, aspect_mask), ) catch return SpvRuntimeError.Unknown, image_view.interface.components); } return .{ @@ -372,14 +381,15 @@ fn readImageInt4(context: *anyopaque, dim: spv.SpvDim, x: i32, y: i32, z: i32, l .cube => cube_face, else => 0, }; + const aspect_mask = imageReadAspect(image_view, true); pixel = SoftSampler.swizzleInt4(image.readInt4( image_coord, .{ - .aspect_mask = image_view.interface.subresource_range.aspect_mask, + .aspect_mask = aspect_mask, .mip_level = mip_level, .array_layer = array_layer, }, - image_view.interface.format, + base.format.fromAspect(image_view.interface.format, aspect_mask), ) catch return SpvRuntimeError.Unknown, image_view.interface.components); } return .{ diff --git a/src/software/device/Renderer.zig b/src/software/device/Renderer.zig index 63bae1c..0848b5d 100644 --- a/src/software/device/Renderer.zig +++ b/src/software/device/Renderer.zig @@ -135,6 +135,7 @@ state: *PipelineState, render_pass: ?*SoftRenderPass, framebuffer: ?*SoftFramebuffer, +render_area: ?vk.Rect2D, dynamic_state: DynamicState, subpass_index: usize, @@ -146,6 +147,7 @@ pub fn init(device: *SoftDevice, state: *PipelineState, active_occlusion_queries .state = state, .render_pass = null, .framebuffer = null, + .render_area = null, .dynamic_state = .{ .viewports = null, .scissor = null, diff --git a/src/software/device/fragment.zig b/src/software/device/fragment.zig index 1e86aa3..d8e4c40 100644 --- a/src/software/device/fragment.zig +++ b/src/software/device/fragment.zig @@ -16,6 +16,7 @@ const INTERFACE_BLOB_PADDING = @sizeOf(zm.F32x4); pub const InvocationResult = struct { outputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(zm.F32x4)]u8, depth: ?f32, + sample_mask: ?vk.SampleMask, }; pub const DerivativeInputs = struct { @@ -76,7 +77,7 @@ pub fn shaderInvocation( SoftPipeline.current_fragment_coord = .{ .x = @intFromFloat(position[0]), .y = @intFromFloat(position[1]), - .z = @intFromFloat(position[2]), + .z = 0, }; defer SoftPipeline.current_fragment_coord = previous_fragment_coord; @@ -165,6 +166,15 @@ pub fn shaderInvocation( else => return err, } + var sample_mask: ?vk.SampleMask = null; + var frag_sample_mask: [1]vk.SampleMask = undefined; + if (rt.readBuiltIn(std.mem.asBytes(&frag_sample_mask), .SampleMask)) { + sample_mask = frag_sample_mask[0]; + } else |err| switch (err) { + SpvRuntimeError.InvalidSpirV, SpvRuntimeError.NotFound => {}, + else => return err, + } + try rt.flushDescriptorSets(allocator); freeOwnedInputs(allocator, fragment_inputs); if (derivatives) |owned_derivatives| { @@ -175,6 +185,7 @@ pub fn shaderInvocation( return .{ .outputs = outputs, .depth = depth, + .sample_mask = sample_mask, }; } diff --git a/src/software/device/rasterizer.zig b/src/software/device/rasterizer.zig index e0eb175..482fc80 100644 --- a/src/software/device/rasterizer.zig +++ b/src/software/device/rasterizer.zig @@ -63,6 +63,8 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato .base = try render_target.mapAsSliceWithAddedOffset(u8, color_attachment_subresource_offset, color_attachment_subresource_size), .row_pitch = render_target.getRowPitchMemSizeForMipLevelWithFormat(color_range.aspect_mask, color_range.base_mip_level, color_format), .texel_size = base.format.texelSize(color_format), + .sample_count = render_target.interface.samples.toInt(), + .sample_stride = render_target.getMipLevelSize(color_range.aspect_mask, color_range.base_mip_level), .width = color_extent.width, .height = color_extent.height, .format = color_format, @@ -96,6 +98,8 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato .base = try depth_attachment.?.mapAsSliceWithAddedOffset(u8, attachment_subresource_offset, attachment_subresource_size), .row_pitch = depth_attachment.?.getRowPitchMemSizeForMipLevelWithFormat(depth_aspect, depth_range.base_mip_level, depth_format), .texel_size = base.format.texelSize(depth_aspect_format), + .sample_count = depth_attachment.?.interface.samples.toInt(), + .sample_stride = depth_attachment.?.getMipLevelSize(depth_aspect, depth_range.base_mip_level), .width = depth_extent.width, .height = depth_extent.height, .format = depth_aspect_format, @@ -126,6 +130,8 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato .base = try depth_attachment.?.mapAsSliceWithAddedOffset(u8, attachment_subresource_offset, attachment_subresource_size), .row_pitch = depth_attachment.?.getRowPitchMemSizeForMipLevelWithFormat(stencil_aspect, stencil_range.base_mip_level, stencil_format), .texel_size = base.format.texelSize(stencil_aspect_format), + .sample_count = depth_attachment.?.interface.samples.toInt(), + .sample_stride = depth_attachment.?.getMipLevelSize(stencil_aspect, stencil_range.base_mip_level), .width = stencil_extent.width, .height = stencil_extent.height, .format = stencil_aspect_format, @@ -355,6 +361,7 @@ fn clipTransformAndRasterizePoint( var fragment_result: fragment.InvocationResult = .{ .outputs = std.mem.zeroes([spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(zm.F32x4)]u8), .depth = null, + .sample_mask = null, }; if (has_fragment_shader) { const frag_x = @as(f32, @floatFromInt(px)) + 0.5; @@ -387,7 +394,7 @@ fn clipTransformAndRasterizePoint( }; } - try common.writeToTargets(fragment_result.outputs, draw_call, color_attachment_access, depth_attachment_access, stencil_attachment_access, true, @intCast(px), @intCast(py), fragment_result.depth orelse transformed.position[2]); + try common.writeToTargets(fragment_result.outputs, draw_call, color_attachment_access, depth_attachment_access, stencil_attachment_access, true, @intCast(px), @intCast(py), fragment_result.depth orelse transformed.position[2], fragment_result.sample_mask); } } } diff --git a/src/software/device/rasterizer/bresenham.zig b/src/software/device/rasterizer/bresenham.zig index f85d139..908612b 100644 --- a/src/software/device/rasterizer/bresenham.zig +++ b/src/software/device/rasterizer/bresenham.zig @@ -156,6 +156,7 @@ inline fn run(data: RunData) !void { var fragment_result: fragment.InvocationResult = .{ .outputs = std.mem.zeroes([spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(F32x4)]u8), .depth = null, + .sample_mask = null, }; if (data.has_fragment_shader) { fragment_result = fragment.shaderInvocation( @@ -192,6 +193,7 @@ inline fn run(data: RunData) !void { @intCast(pixel_x), @intCast(pixel_y), fragment_result.depth orelse z, + fragment_result.sample_mask, ); } } diff --git a/src/software/device/rasterizer/common.zig b/src/software/device/rasterizer/common.zig index 404619b..422383c 100644 --- a/src/software/device/rasterizer/common.zig +++ b/src/software/device/rasterizer/common.zig @@ -16,6 +16,8 @@ pub const RenderTargetAccess = struct { base: []u8, row_pitch: usize, texel_size: usize, + sample_count: usize, + sample_stride: usize, width: u32, height: u32, format: vk.Format, @@ -45,6 +47,22 @@ pub fn scissorContainsPixel(scissor: vk.Rect2D, x: i32, y: i32) bool { pixel_y < max_y; } +pub fn rectContainsPixel(rect: vk.Rect2D, x: usize, y: usize) bool { + const min_x: i64 = @as(i64, rect.offset.x); + const min_y: i64 = @as(i64, rect.offset.y); + + const max_x: i64 = min_x + @as(i64, @intCast(rect.extent.width)); + const max_y: i64 = min_y + @as(i64, @intCast(rect.extent.height)); + + const pixel_x: i64 = @intCast(x); + const pixel_y: i64 = @intCast(y); + + return pixel_x >= min_x and + pixel_x < max_x and + pixel_y >= min_y and + pixel_y < max_y; +} + pub fn targetContainsPixel(target: RenderTargetAccess, x: i32, y: i32) bool { if (x < 0 or y < 0) return false; @@ -62,6 +80,14 @@ pub fn targetOffset(target: RenderTargetAccess, x: usize, y: usize) ?usize { return offset; } +pub fn targetSampleOffset(target: RenderTargetAccess, x: usize, y: usize, sample_index: usize) ?usize { + const base_offset = targetOffset(target, x, y) orelse return null; + const offset = base_offset + sample_index * target.sample_stride; + if (offset > target.base.len or target.texel_size > target.base.len - offset) + return null; + return offset; +} + pub fn compare(comptime T: type, op: vk.CompareOp, reference: T, value: T) bool { return switch (op) { .never => false, @@ -100,6 +126,10 @@ fn updateStencilValue(stencil: *RenderTargetAccess, offset: usize, state: vk.Ste pub fn stencilTestAndUpdate(stencil: *RenderTargetAccess, x: usize, y: usize, state: vk.StencilOpState, depth_passed: ?bool) bool { const offset = targetOffset(stencil.*, x, y) orelse return false; + return stencilTestAndUpdateAtOffset(stencil, offset, state, depth_passed); +} + +fn stencilTestAndUpdateAtOffset(stencil: *RenderTargetAccess, offset: usize, state: vk.StencilOpState, depth_passed: ?bool) bool { const current = blitter.readInt4(stencil.base[offset..], stencil.format)[0] & std.math.maxInt(u8); const reference = state.reference & std.math.maxInt(u8); const compare_mask = state.compare_mask & std.math.maxInt(u8); @@ -138,10 +168,14 @@ fn quantizeDepthForFormat(format: vk.Format, z: f32) f32 { } pub fn depthTestAndUpdate(depth: *RenderTargetAccess, x: usize, y: usize, z: f32, state: vk.PipelineDepthStencilStateCreateInfo) bool { + const offset = targetOffset(depth.*, x, y) orelse return false; + return depthTestAndUpdateAtOffset(depth, offset, z, state); +} + +fn depthTestAndUpdateAtOffset(depth: *RenderTargetAccess, offset: usize, z: f32, state: vk.PipelineDepthStencilStateCreateInfo) bool { if (state.depth_test_enable == .false) return true; - const offset = targetOffset(depth.*, x, y) orelse return false; const reference = quantizeDepthForFormat(depth.format, z); const depth_value = blitter.readFloat4(depth.base[offset..], depth.format); const passed = compare(f32, state.depth_compare_op, reference, depth_value[0]); @@ -373,86 +407,148 @@ pub fn writeToTargets( x: usize, y: usize, z: f32, + fragment_sample_mask: ?vk.SampleMask, ) VkError!void { const io = draw_call.renderer.device.interface.io(); - const depth_stencil_state = draw_call.renderer.state.pipeline.?.interface.mode.graphics.depth_stencil; + const pipeline_data = draw_call.renderer.state.pipeline.?.interface.mode.graphics; + const depth_stencil_state = pipeline_data.depth_stencil; + + if (!sampleMaskEnablesAnySample(pipeline_data.multisample, fragment_sample_mask)) + return; if (x >= draw_call.framebuffer.interface.width or y >= draw_call.framebuffer.interface.height) return; - var stencil_state: ?vk.StencilOpState = null; - var stencil_offset: ?usize = null; - if (stencil_attachment_access) |stencil| { - if (depth_stencil_state) |state| { - if (state.stencil_test_enable == .true) { - stencil_state = if (front_face) - resolveStencilState(draw_call, state.front, true) - else - resolveStencilState(draw_call, state.back, false); - stencil_offset = targetOffset(stencil.*, x, y) orelse return; - if (!stencilTest(stencil, stencil_offset.?, stencil_state.?)) { - updateStencilValue(stencil, stencil_offset.?, stencil_state.?, stencil_state.?.fail_op); - return; + if (draw_call.renderer.render_area) |render_area| { + if (!rectContainsPixel(render_area, x, y)) + return; + } + + const sample_count = pipeline_data.multisample.rasterization_samples.toInt(); + for (0..sample_count) |sample_index| { + if (!sampleMaskEnablesSample(pipeline_data.multisample, fragment_sample_mask, sample_index)) + continue; + + var stencil_state: ?vk.StencilOpState = null; + var stencil_offset: ?usize = null; + if (stencil_attachment_access) |stencil| { + if (depth_stencil_state) |state| { + if (state.stencil_test_enable == .true) { + stencil_state = if (front_face) + resolveStencilState(draw_call, state.front, true) + else + resolveStencilState(draw_call, state.back, false); + stencil_offset = targetSampleOffset(stencil.*, x, y, sample_index) orelse continue; + if (!stencilTest(stencil, stencil_offset.?, stencil_state.?)) { + updateStencilValue(stencil, stencil_offset.?, stencil_state.?, stencil_state.?.fail_op); + continue; + } } } } - } - // After work depth test to avoid overwritten depth pixels during fragment invocations. - var depth_passed: ?bool = null; - if (depth_attachment_access) |depth| { - const depth_offset = targetOffset(depth.*, x, y) orelse return; + // After work depth test to avoid overwritten depth pixels during fragment invocations. + var depth_passed: ?bool = null; + if (depth_attachment_access) |depth| { + const depth_offset = targetSampleOffset(depth.*, x, y, sample_index) orelse continue; - depth.mutex.lock(io) catch return VkError.DeviceLost; - defer depth.mutex.unlock(io); + depth.mutex.lock(io) catch return VkError.DeviceLost; + defer depth.mutex.unlock(io); - if (depth_stencil_state) |state| { - depth_passed = depthTestAndUpdate(depth, x, y, z, state); - if (!depth_passed.? and stencil_state == null) - return; - } else { - const depth_value = blitter.readFloat4(depth.base[depth_offset..], depth.format); - if (z >= depth_value[0]) - return; - blitter.writeFloat4(zm.f32x4s(z), depth.base[depth_offset..], depth.format); - depth_passed = true; - } - } - - if (stencil_attachment_access) |stencil| { - if (stencil_state) |state| { - if (depth_passed != null and !depth_passed.?) { - updateStencilValue(stencil, stencil_offset.?, state, state.depth_fail_op); - return; + if (depth_stencil_state) |state| { + depth_passed = depthTestAndUpdateAtOffset(depth, depth_offset, z, state); + if (!depth_passed.? and stencil_state == null) + continue; + } else { + const depth_value = blitter.readFloat4(depth.base[depth_offset..], depth.format); + if (z >= depth_value[0]) + continue; + blitter.writeFloat4(zm.f32x4s(z), depth.base[depth_offset..], depth.format); + depth_passed = true; } - updateStencilValue(stencil, stencil_offset.?, state, state.pass_op); } - } - for (draw_call.renderer.active_occlusion_queries.items) |active| { - try active.pool.addSamples(active.query, 1); - } + if (stencil_attachment_access) |stencil| { + if (stencil_state) |state| { + if (depth_passed != null and !depth_passed.?) { + updateStencilValue(stencil, stencil_offset.?, state, state.depth_fail_op); + continue; + } + updateStencilValue(stencil, stencil_offset.?, state, state.pass_op); + } + } - for (color_attachment_access, 0..) |maybe_color, location| { - const color = maybe_color orelse continue; - const color_offset = targetOffset(color, x, y) orelse continue; + for (draw_call.renderer.active_occlusion_queries.items) |active| { + try active.pool.addSamples(active.query, 1); + } - if (base.format.isUnnormalizedInteger(color.format)) { - blitter.writeInt4(std.mem.bytesToValue(U32x4, &outputs[location]), color.base[color_offset..], color.format); - } else { - const pipeline_data = draw_call.renderer.state.pipeline.?.interface.mode.graphics; - const src = fragmentOutputFloat4(outputs[location], color.format); - const encoded_dst = blitter.readFloat4(color.base[color_offset..], color.format); - const dst = if (base.format.isSrgb(color.format)) zm.srgbToRgb(encoded_dst) else encoded_dst; - const final_color = if (pipeline_data.color_blend.attachments) |attachments| blk: { - if (location >= attachments.len) - break :blk src; - const constants = draw_call.renderer.dynamic_state.blend_constants orelse pipeline_data.color_blend.constants; - const blended = blendColor(src, dst, attachments[location], constants, color.format); - break :blk applyColorWriteMask(blended, dst, attachments[location].color_write_mask); - } else src; - const encoded_color = if (base.format.isSrgb(color.format)) zm.rgbToSrgb(final_color) else final_color; - blitter.writeFloat4(encoded_color, color.base[color_offset..], color.format); + for (color_attachment_access, 0..) |maybe_color, location| { + const color = maybe_color orelse continue; + const color_offset = targetSampleOffset(color, x, y, sample_index) orelse continue; + if (base.format.isUnnormalizedInteger(color.format)) { + blitter.writeInt4(std.mem.bytesToValue(U32x4, &outputs[location]), color.base[color_offset..], color.format); + } else { + const src = fragmentOutputFloat4(outputs[location], color.format); + const encoded_dst = blitter.readFloat4(color.base[color_offset..], color.format); + const dst = if (base.format.isSrgb(color.format)) zm.srgbToRgb(encoded_dst) else encoded_dst; + const final_color = if (pipeline_data.color_blend.attachments) |attachments| blk: { + if (location >= attachments.len) + break :blk src; + const constants = draw_call.renderer.dynamic_state.blend_constants orelse pipeline_data.color_blend.constants; + const blended = blendColor(src, dst, attachments[location], constants, color.format); + break :blk applyColorWriteMask(blended, dst, attachments[location].color_write_mask); + } else src; + const encoded_color = if (base.format.isSrgb(color.format)) zm.rgbToSrgb(final_color) else final_color; + blitter.writeFloat4(encoded_color, color.base[color_offset..], color.format); + } } } } + +fn sampleMaskEnablesAnySample(multisample: anytype, fragment_sample_mask: ?vk.SampleMask) bool { + const sample_count = multisample.rasterization_samples.toInt(); + if (multisample.sample_mask == null and fragment_sample_mask == null) + return true; + + if (multisample.sample_mask) |pipeline_sample_mask| { + for (pipeline_sample_mask, 0..) |word, word_index| { + if (sampleMaskWordEnablesAnySample(sample_count, word_index, word, fragment_sample_mask)) + return true; + } + return false; + } + + return sampleMaskWordEnablesAnySample(sample_count, 0, std.math.maxInt(vk.SampleMask), fragment_sample_mask); +} + +fn sampleMaskWordEnablesAnySample(sample_count: usize, word_index: usize, pipeline_word: vk.SampleMask, fragment_sample_mask: ?vk.SampleMask) bool { + const remaining_samples = sample_count -| (word_index * 32); + const active_bits = @min(remaining_samples, 32); + if (active_bits == 0) + return false; + + const active_mask: vk.SampleMask = if (active_bits == 32) + std.math.maxInt(vk.SampleMask) + else + (@as(vk.SampleMask, 1) << @intCast(active_bits)) - 1; + + const fragment_word = if (word_index == 0) fragment_sample_mask orelse std.math.maxInt(vk.SampleMask) else std.math.maxInt(vk.SampleMask); + return ((pipeline_word & fragment_word) & active_mask) != 0; +} + +fn sampleMaskEnablesSample(multisample: anytype, fragment_sample_mask: ?vk.SampleMask, sample_index: usize) bool { + if (sample_index >= multisample.rasterization_samples.toInt()) + return false; + + const word_index = sample_index / 32; + const bit_index: u5 = @intCast(sample_index % 32); + const bit = @as(vk.SampleMask, 1) << bit_index; + + const pipeline_word = if (multisample.sample_mask) |pipeline_sample_mask| + if (word_index < pipeline_sample_mask.len) pipeline_sample_mask[word_index] else @as(vk.SampleMask, 0) + else + std.math.maxInt(vk.SampleMask); + + const fragment_word = if (word_index == 0) fragment_sample_mask orelse std.math.maxInt(vk.SampleMask) else std.math.maxInt(vk.SampleMask); + return (pipeline_word & fragment_word & bit) != 0; +} diff --git a/src/software/device/rasterizer/edge_function.zig b/src/software/device/rasterizer/edge_function.zig index 8546f37..4d276ad 100644 --- a/src/software/device/rasterizer/edge_function.zig +++ b/src/software/device/rasterizer/edge_function.zig @@ -182,6 +182,7 @@ inline fn run(data: RunData) !void { var fragment_result: fragment.InvocationResult = .{ .outputs = std.mem.zeroes([spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(F32x4)]u8), .depth = null, + .sample_mask = null, }; if (data.has_fragment_shader) { const inputs = try common.interpolateVertexOutputs(data.allocator, &data.v0, &data.v1, &data.v2, b0, b1, b2); @@ -251,6 +252,7 @@ inline fn run(data: RunData) !void { @intCast(x), @intCast(y), fragment_result.depth orelse z, + fragment_result.sample_mask, ); } } diff --git a/src/vulkan/Pipeline.zig b/src/vulkan/Pipeline.zig index af3ffe3..765c4ec 100644 --- a/src/vulkan/Pipeline.zig +++ b/src/vulkan/Pipeline.zig @@ -50,6 +50,10 @@ mode: union(enum) { front_face: vk.FrontFace, line_width: f32, }, + multisample: struct { + rasterization_samples: vk.SampleCountFlags, + sample_mask: ?[]vk.SampleMask, + }, color_blend: struct { attachments: ?[]vk.PipelineColorBlendAttachmentState, constants: [4]f32, @@ -106,6 +110,9 @@ pub fn initGraphics(device: *Device, allocator: std.mem.Allocator, cache: ?*Pipe var scissors: ?[]vk.Rect2D = null; errdefer if (scissors) |value| allocator.free(value); + var sample_mask: ?[]vk.SampleMask = null; + errdefer if (sample_mask) |value| allocator.free(value); + var color_attachments: ?[]vk.PipelineColorBlendAttachmentState = null; errdefer if (color_attachments) |value| allocator.free(value); @@ -186,6 +193,24 @@ pub fn initGraphics(device: *Device, allocator: std.mem.Allocator, cache: ?*Pipe .front_face = if (info.p_rasterization_state) |state| state.front_face else return VkError.ValidationFailed, .line_width = if (info.p_rasterization_state) |state| state.line_width else return VkError.ValidationFailed, }, + .multisample = blk: { + if (rasterizer_discard_enable) { + break :blk .{ + .rasterization_samples = .{ .@"1_bit" = true }, + .sample_mask = null, + }; + } + + const state = info.p_multisample_state orelse return VkError.ValidationFailed; + const mask_word_count: usize = @divTrunc(state.rasterization_samples.toInt() + 31, 32); + break :blk .{ + .rasterization_samples = state.rasterization_samples, + .sample_mask = if (state.p_sample_mask) |mask| blk_mask: { + sample_mask = allocator.dupe(vk.SampleMask, mask[0..mask_word_count]) catch return VkError.OutOfHostMemory; + break :blk_mask sample_mask; + } else null, + }; + }, .color_blend = blk: { if (rasterizer_discard_enable or !has_color_attachments) { break :blk .{ @@ -276,6 +301,9 @@ pub inline fn destroy(self: *Self, allocator: std.mem.Allocator) void { if (graphics.viewport_state.scissor) |scissor| { allocator.free(scissor); } + if (graphics.multisample.sample_mask) |sample_mask| { + allocator.free(sample_mask); + } if (graphics.color_blend.attachments) |attachments| { allocator.free(attachments); } diff --git a/src/vulkan/lib_vulkan.zig b/src/vulkan/lib_vulkan.zig index a53d92e..5ee295e 100644 --- a/src/vulkan/lib_vulkan.zig +++ b/src/vulkan/lib_vulkan.zig @@ -140,7 +140,6 @@ const device_pfn_map = block: { functionMapEntryPoint("vkAcquireNextImage2KHR"), functionMapEntryPoint("vkAllocateCommandBuffers"), functionMapEntryPoint("vkAllocateDescriptorSets"), - functionMapEntryPoint("vkAllocateDescriptorSets"), functionMapEntryPoint("vkAllocateMemory"), functionMapEntryPoint("vkBeginCommandBuffer"), functionMapEntryPoint("vkBindBufferMemory"),