diff --git a/build.zig.zon b/build.zig.zon index eaa02f7..4e3ea45 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -32,8 +32,8 @@ // Soft dependencies .SPIRV_Interpreter = .{ - .url = "git+https://github.com/Kbz-8/SPIRV-Interpreter#b9b6087fef5a61a3d9e3187ff7c66dffc54603da", - .hash = "SPIRV_Interpreter-0.0.1-ajmpn9JxCADthSmyktTCV_jIA8iJU_aiM_kboXqfPDDD", + .url = "git+https://github.com/Kbz-8/SPIRV-Interpreter#fcf303a0c62a66ff32fc92fe9c92ca9294f72caf", + .hash = "SPIRV_Interpreter-0.0.1-ajmpnx3dCAAfuabTUWPWdTVqPGBPeSUCCTI-ncKowi5D", .lazy = true, }, //.SPIRV_Interpreter = .{ diff --git a/src/software/SoftPipeline.zig b/src/software/SoftPipeline.zig index 93edb43..1c81ef2 100644 --- a/src/software/SoftPipeline.zig +++ b/src/software/SoftPipeline.zig @@ -194,6 +194,7 @@ fn createShader( runtimes_count: usize, ) VkError!Shader { const entry = std.mem.span(stage.p_name); + const execution_model = executionModelForStage(stage.stage) orelse return VkError.Unknown; const runtimes = runtimes_allocator.alloc(Runtime, runtimes_count) catch return VkError.OutOfDeviceMemory; var initialized: usize = 0; var module_ref = false; @@ -212,7 +213,7 @@ fn createShader( const image_api = imageApi(); var cache_hit = false; if (cache) |pipeline_cache| { - if (try pipeline_cache.cloneRuntime(runtimes_allocator, module, entry, stage.p_specialization_info, image_api)) |runtime| { + if (try pipeline_cache.cloneRuntime(runtimes_allocator, module, entry, execution_model, stage.p_specialization_info, image_api)) |runtime| { runtimes[0] = .{ .mutex = .init, .rt = runtime, @@ -226,7 +227,7 @@ fn createShader( for (runtimes[initialized..]) |*runtime| { runtime.* = .{ .mutex = .init, - .rt = (try cache.?.cloneRuntime(runtimes_allocator, module, entry, stage.p_specialization_info, image_api)).?, + .rt = (try cache.?.cloneRuntime(runtimes_allocator, module, entry, execution_model, stage.p_specialization_info, image_api)).?, }; initialized += 1; } @@ -240,7 +241,7 @@ fn createShader( } if (cache) |pipeline_cache| { - try pipeline_cache.storeRuntimeTemplate(object_allocator, cache_allocator, module, entry, stage.p_specialization_info, image_api); + try pipeline_cache.storeRuntimeTemplate(object_allocator, cache_allocator, module, entry, execution_model, stage.p_specialization_info, image_api); } } @@ -258,10 +259,29 @@ fn initRuntime(allocator: std.mem.Allocator, module: *SoftShaderModule, stage: * }; errdefer runtime.deinit(allocator); + const entry = runtime.getEntryPointByNameAndExecutionModel(std.mem.span(stage.p_name), executionModelForStage(stage.stage) orelse return VkError.Unknown) catch |err| { + std.log.scoped(.SpvRuntimeInit).err("SPIR-V Runtime failed to select entry point, {s}", .{@errorName(err)}); + return VkError.Unknown; + }; + runtime.selectEntryPoint(entry) catch |err| { + std.log.scoped(.SpvRuntimeInit).err("SPIR-V Runtime failed to activate entry point, {s}", .{@errorName(err)}); + return VkError.Unknown; + }; + try applySpecialization(&runtime, allocator, stage.p_specialization_info); return runtime; } +fn executionModelForStage(stage: vk.ShaderStageFlags) ?spv.spv.SpvExecutionModel { + if (stage.vertex_bit) return .Vertex; + if (stage.tessellation_control_bit) return .TessellationControl; + if (stage.tessellation_evaluation_bit) return .TessellationEvaluation; + if (stage.geometry_bit) return .Geometry; + if (stage.fragment_bit) return .Fragment; + if (stage.compute_bit) return .GLCompute; + return null; +} + fn applySpecialization(runtime: *spv.Runtime, allocator: std.mem.Allocator, specialization: ?*const vk.SpecializationInfo) VkError!void { const info = specialization orelse return; const map = info.p_map_entries orelse return; diff --git a/src/software/SoftPipelineCache.zig b/src/software/SoftPipelineCache.zig index 982ba0c..f82be8d 100644 --- a/src/software/SoftPipelineCache.zig +++ b/src/software/SoftPipelineCache.zig @@ -26,6 +26,7 @@ const CacheRecord = extern struct { const CachedShader = struct { module: *SoftShaderModule, entry: []u8, + execution_model: spv.spv.SpvExecutionModel, specs: []SpecConstant, runtime: spv.Runtime, @@ -81,6 +82,7 @@ pub fn cloneRuntime( allocator: std.mem.Allocator, module: *SoftShaderModule, entry: []const u8, + execution_model: spv.spv.SpvExecutionModel, specialization: ?*const vk.SpecializationInfo, image_api: spv.Runtime.ImageAPI, ) VkError!?spv.Runtime { @@ -89,8 +91,14 @@ pub fn cloneRuntime( defer self.mutex.unlock(io); for (self.shaders.items) |*shader| { - if (shader.module == module and std.mem.eql(u8, shader.entry, entry) and specsEqual(shader.specs, specialization)) { - return spv.Runtime.initFrom(allocator, &shader.runtime, image_api) catch return VkError.OutOfDeviceMemory; + if (shader.module == module and shader.execution_model == execution_model and std.mem.eql(u8, shader.entry, entry) and specsEqual(shader.specs, specialization)) { + var runtime = spv.Runtime.initFrom(allocator, &shader.runtime, image_api) catch return VkError.OutOfDeviceMemory; + errdefer runtime.deinit(allocator); + + const entry_point = runtime.getEntryPointByNameAndExecutionModel(entry, execution_model) catch return VkError.Unknown; + runtime.selectEntryPoint(entry_point) catch return VkError.Unknown; + + return runtime; } } @@ -103,6 +111,7 @@ pub fn storeRuntimeTemplate( data_allocator: std.mem.Allocator, module: *SoftShaderModule, entry: []const u8, + execution_model: spv.spv.SpvExecutionModel, specialization: ?*const vk.SpecializationInfo, image_api: spv.Runtime.ImageAPI, ) VkError!void { @@ -112,6 +121,7 @@ pub fn storeRuntimeTemplate( for (self.shaders.items) |*shader| { if (shader.module == module and + shader.execution_model == execution_model and std.mem.eql(u8, shader.entry, entry) and specsEqual(shader.specs, specialization)) { @@ -122,6 +132,7 @@ pub fn storeRuntimeTemplate( var cached: CachedShader = .{ .module = module, .entry = data_allocator.dupe(u8, entry) catch return VkError.OutOfDeviceMemory, + .execution_model = execution_model, .specs = try cloneSpecs(data_allocator, specialization), .runtime = spv.Runtime.init(data_allocator, &module.module, image_api) catch return VkError.OutOfDeviceMemory, }; @@ -141,6 +152,8 @@ pub fn storeRuntimeTemplate( module.ref(); module_ref = true; + const entry_point = cached.runtime.getEntryPointByNameAndExecutionModel(entry, execution_model) catch return VkError.Unknown; + cached.runtime.selectEntryPoint(entry_point) catch return VkError.Unknown; try applySpecialization(&cached.runtime, data_allocator, specialization); try appendCacheRecord(&self.interface, module, entry, cached.specs); diff --git a/src/software/SoftSampler.zig b/src/software/SoftSampler.zig index db5670e..6f76d24 100644 --- a/src/software/SoftSampler.zig +++ b/src/software/SoftSampler.zig @@ -594,6 +594,9 @@ pub fn sampleImageInt4(image: *SoftImage, image_view: *SoftImageView, sampler: * const scale_v: f32 = if (sampler.interface.unnormalized_coordinates == .true) 1.0 else @floatFromInt(extent.height); const scale_w: f32 = if (sampler.interface.unnormalized_coordinates == .true) 1.0 else @floatFromInt(extent.depth); + const ix = @as(i32, @intFromFloat(@floor(coord.u * scale_u))) + offset.x; + const iy = @as(i32, @intFromFloat(@floor(coord.v * scale_v))) + offset.y; + const iz = @as(i32, @intFromFloat(@floor(coord.w * scale_w))) + offset.z; const color = try readSampledInt4( image, image_view, @@ -601,9 +604,9 @@ pub fn sampleImageInt4(image: *SoftImage, image_view: *SoftImageView, sampler: * dim, coord, mip_level, - @as(i32, @intFromFloat(@floor(coord.u * scale_u))) + offset.x, - @as(i32, @intFromFloat(@floor(coord.v * scale_v))) + offset.y, - @as(i32, @intFromFloat(@floor(coord.w * scale_w))) + offset.z, + ix, + iy, + iz, ); return swizzleInt4(color, image_view.interface.components); } diff --git a/src/software/device/ComputeDispatcher.zig b/src/software/device/ComputeDispatcher.zig index 3eba655..afb69de 100644 --- a/src/software/device/ComputeDispatcher.zig +++ b/src/software/device/ComputeDispatcher.zig @@ -73,7 +73,7 @@ pub fn dispatchBase(self: *Self, base_group_x: u32, base_group_y: u32, base_grou const pipeline = self.state.pipeline orelse return VkError.InvalidPipelineDrv; const shader = pipeline.stages.getPtr(.compute) orelse return VkError.InvalidPipelineDrv; const spv_module = &shader.module.module; - self.batch_size = shader.runtimes.len; + self.batch_size = if (spv_module.reflection_infos.has_atomics) 1 else shader.runtimes.len; const allocator = self.device.device_allocator.allocator(); const local_size = try getLocalSize(&shader.runtimes[0].rt, allocator, spv_module); @@ -131,7 +131,7 @@ inline fn run(data: RunData) !void { const rt = &shader.runtimes[data.batch_id].rt; const entry = try rt.getEntryPointByName(shader.entry); - const uses_control_barrier = rt.mod.reflection_infos.has_control_barriers; + const uses_control_barrier = rt.mod.reflection_infos.has_control_barriers or rt.mod.reflection_infos.has_atomics; var barrier_runtimes: []spv.Runtime = &.{}; var barrier_statuses: []spv.Runtime.EntryPointStatus = &.{}; @@ -146,17 +146,13 @@ inline fn run(data: RunData) !void { defer { for (barrier_runtimes) |*barrier_rt| { + barrier_rt.resetInvocation(allocator); barrier_rt.deinit(allocator); } allocator.free(barrier_runtimes); allocator.free(barrier_statuses); } - if (!uses_control_barrier) - try ExecutionDevice.writeDescriptorSets(data.self.state, rt); - - try rt.populatePushConstants(data.self.state.push_constant_blob[0..]); - var group_index: usize = data.batch_id; while (group_index < data.group_count) : (group_index += data.self.batch_size) { var modulo: usize = group_index; @@ -185,8 +181,16 @@ inline fn run(data: RunData) !void { continue; } + const workgroup_memory = try rt.createWorkgroupMemory(allocator); + defer rt.destroyWorkgroupMemory(allocator, workgroup_memory); + for (0..data.invocations_per_workgroup) |i| { rt.resetInvocation(allocator); + if (rt.specialization_constants.count() != 0) + try rt.applySpecializationInvocationLayout(allocator); + try ExecutionDevice.writeDescriptorSets(data.self.state, rt); + try rt.populatePushConstants(data.self.state.push_constant_blob[0..]); + try rt.bindWorkgroupMemory(workgroup_memory); try setupWorkgroupBuiltins(data.self, rt, data.local_size, group_count_vec, group_id_vec); const invocation_index = data.self.invocation_index.fetchAdd(1, .monotonic); @@ -208,13 +212,13 @@ inline fn run(data: RunData) !void { SpvRuntimeError.Killed => continue, else => return err, }; + try flushWorkgroupMemory(rt, workgroup_memory); + try rt.flushDescriptorSets(allocator); if (data.self.final_dump != null and data.self.final_dump.? == invocation_index) { @branchHint(.cold); try dumpResultsTable(allocator, io, rt, false); } - - try rt.flushDescriptorSets(allocator); } } } @@ -228,14 +232,19 @@ fn runBarrierWorkgroup( group_id: @Vector(3, u32), ) !void { const allocator = data.self.device.device_allocator.allocator(); - + const workgroup_memory = try runtimes[0].createWorkgroupMemory(allocator); + defer runtimes[0].destroyWorkgroupMemory(allocator, workgroup_memory); for (runtimes, 0..) |*rt, i| { rt.resetInvocation(allocator); + if (rt.specialization_constants.count() != 0) + try rt.applySpecializationInvocationLayout(allocator); try ExecutionDevice.writeDescriptorSets(data.self.state, rt); try rt.populatePushConstants(data.self.state.push_constant_blob[0..]); + try rt.bindWorkgroupMemory(workgroup_memory); try setupWorkgroupBuiltins(data.self, rt, data.local_size, group_count, group_id); try setupSubgroupBuiltins(data.self, rt, data.local_size, group_id, i); statuses[i] = try rt.beginEntryPoint(allocator, entry); + try flushWorkgroupMemory(rt, workgroup_memory); try rt.flushDescriptorSets(allocator); } @@ -253,12 +262,20 @@ fn runBarrierWorkgroup( for (runtimes, 0..) |*rt, i| { if (statuses[i] == .completed) continue; + try rt.bindWorkgroupMemory(workgroup_memory); statuses[i] = try rt.continueEntryPoint(allocator); + try flushWorkgroupMemory(rt, workgroup_memory); try rt.flushDescriptorSets(allocator); } } } +fn flushWorkgroupMemory(rt: *spv.Runtime, workgroup_memory: []const spv.Runtime.WorkgroupMemory) spv.Runtime.RuntimeError!void { + for (workgroup_memory) |memory| { + _ = try (try rt.results[memory.result].getValue()).read(memory.bytes); + } +} + fn dumpResultsTable(allocator: std.mem.Allocator, io: std.Io, rt: *spv.Runtime, comptime is_early: bool) !void { @branchHint(.cold); const file = try std.Io.Dir.cwd().createFile( diff --git a/src/software/device/Device.zig b/src/software/device/Device.zig index 518cdf9..b498e3d 100644 --- a/src/software/device/Device.zig +++ b/src/software/device/Device.zig @@ -101,7 +101,9 @@ fn writeDescriptorValue(value: anytype, payload: DescriptorPayload, descriptor_i }; switch (payload) { - .raw => |data| _ = try dst.write(data), + .raw => |data| { + _ = try dst.write(data); + }, .sampled_image => |data| switch (dst.*) { .Image => _ = try dst.write(std.mem.asBytes(&data.image)), .Sampler => _ = try dst.write(std.mem.asBytes(&data.sampler)), @@ -144,6 +146,10 @@ pub fn writeDescriptorSets(state: *PipelineState, rt: *spv.Runtime) !void { switch (binding) { .buffer => |buffer_data_array| for (buffer_data_array, 0..) |buffer_data, descriptor_index| { if (buffer_data.object) |buffer| { + const memory = buffer.interface.memory orelse continue; + if (@intFromPtr(memory.vtable) == 0) + continue; + const binding_layout = set.?.interface.layout.bindings[binding_index]; const dynamic_offset: vk.DeviceSize = switch (binding_layout.descriptor_type) { .uniform_buffer_dynamic, diff --git a/src/software/device/Renderer.zig b/src/software/device/Renderer.zig index 78a4fd1..43f8cd1 100644 --- a/src/software/device/Renderer.zig +++ b/src/software/device/Renderer.zig @@ -39,6 +39,8 @@ pub const IndexBuffer = struct { index_type: vk.IndexType, }; +pub const InterpolationType = enum { smooth, flat, noperspective }; + pub const DynamicState = struct { viewports: ?[]const vk.Viewport, scissor: ?[]const vk.Rect2D, @@ -57,7 +59,7 @@ pub const Vertex = struct { position: F32x4, point_size: f32, outputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS][4]?struct { - interpolation_type: enum { smooth, flat, noperspective }, + interpolation_type: InterpolationType, blob: []u8, size: usize, }, diff --git a/src/software/device/fragment.zig b/src/software/device/fragment.zig index 5e3c3ed..6197159 100644 --- a/src/software/device/fragment.zig +++ b/src/software/device/fragment.zig @@ -57,6 +57,9 @@ pub fn shaderInvocation( defer mutex.unlock(io); rt.resetInvocation(allocator); + if (rt.specialization_constants.count() != 0) + try rt.applySpecializationInvocationLayout(allocator); + try @import("Device.zig").writeDescriptorSets(draw_call.renderer.state, rt); try rt.populatePushConstants(draw_call.renderer.state.push_constant_blob[0..]); rt.writeBuiltIn(allocator, std.mem.asBytes(&position), .FragCoord) catch |err| switch (err) { SpvRuntimeError.NotFound => {}, @@ -96,8 +99,8 @@ pub fn shaderInvocation( var input = fragment_inputs[location][component]; const result_word = rt.getResultByLocationComponent(@intCast(location), @intCast(component), .input) catch |err| switch (err) { SpvRuntimeError.NotFound => { - if (input.blob.len != 0) { - rt.writeInputLocation(input.blob, @intCast(location)) catch |write_err| switch (write_err) { + if (input.size != 0) { + rt.writeInputLocation(input.blob[0..input.size], @intCast(location)) catch |write_err| switch (write_err) { SpvRuntimeError.NotFound => {}, else => return write_err, }; @@ -110,11 +113,11 @@ pub fn shaderInvocation( const has_result_value = rt.results[result_word].variant != null; const memory_size = if (has_result_value) try rt.getResultMemorySize(result_word) - else if (input.blob.len == 0) + else if (input.size == 0) try rt.getInputLocationMemorySize(@intCast(location)) else - input.blob.len; - if (input.blob.len == 0) { + input.size; + if (input.size == 0) { const zeroes = allocator.alloc(u8, memory_size + INTERFACE_BLOB_PADDING) catch return SpvRuntimeError.OutOfMemory; @memset(zeroes, 0); fragment_inputs[location][component] = .{ @@ -125,16 +128,17 @@ pub fn shaderInvocation( input = fragment_inputs[location][component]; } - if (input.blob.len != 0) { - if (!has_result_value or input.blob.len < memory_size) - try rt.writeInputLocation(input.blob, @intCast(location)) + if (input.size != 0) { + const input_bytes = input.blob[0..input.size]; + if (!has_result_value or input.size < memory_size) + try rt.writeInputLocation(input_bytes, @intCast(location)) else - try rt.writeInput(allocator, input.blob, result_word); + try rt.writeInput(allocator, input_bytes, result_word); if (derivatives) |derivative| { const dx = derivative.dx[location][component]; const dy = derivative.dy[location][component]; - if (dx.blob.len != 0 and dy.blob.len != 0) { - try rt.setDerivativeFromMemory(allocator, result_word, dx.blob, dy.blob); + if (dx.size != 0 and dy.size != 0) { + try rt.setDerivativeFromMemory(allocator, result_word, dx.blob[0..dx.size], dy.blob[0..dy.size]); } } } diff --git a/src/software/device/rasterizer.zig b/src/software/device/rasterizer.zig index e7dcb94..c90bdc0 100644 --- a/src/software/device/rasterizer.zig +++ b/src/software/device/rasterizer.zig @@ -431,6 +431,7 @@ fn rasterizeTransformedPoint( fragment_result.depth orelse vertex.position[2], null, fragment_result.sample_mask, + false, ); } } diff --git a/src/software/device/rasterizer/bresenham.zig b/src/software/device/rasterizer/bresenham.zig index 0c2b2f2..bad5334 100644 --- a/src/software/device/rasterizer/bresenham.zig +++ b/src/software/device/rasterizer/bresenham.zig @@ -283,6 +283,7 @@ inline fn run(data: RunData) !void { data.draw_call.renderer.state.pipeline.?.interface.mode.graphics.multisample.rasterization_samples.toInt(), ), fragment_result.sample_mask, + false, ); } } diff --git a/src/software/device/rasterizer/common.zig b/src/software/device/rasterizer/common.zig index 722c16e..c908ef8 100644 --- a/src/software/device/rasterizer/common.zig +++ b/src/software/device/rasterizer/common.zig @@ -172,6 +172,31 @@ pub fn depthTestAndUpdate(depth: *RenderTargetAccess, x: usize, y: usize, z: f32 return depthTestAndUpdateAtOffset(depth, offset, z, state); } +pub fn depthTestSampleAndUpdate( + io: std.Io, + depth: *RenderTargetAccess, + x: usize, + y: usize, + sample_index: usize, + z: f32, + state: ?vk.PipelineDepthStencilStateCreateInfo, +) VkError!bool { + const depth_offset = targetSampleOffset(depth.*, x, y, sample_index) orelse return false; + + depth.mutex.lock(io) catch return VkError.DeviceLost; + defer depth.mutex.unlock(io); + + if (state) |depth_state| { + return depthTestAndUpdateAtOffset(depth, depth_offset, z, depth_state); + } + + const depth_value = blitter.readFloat4(depth.base[depth_offset..], depth.format); + if (z >= depth_value[0]) + return false; + blitter.writeFloat4(zm.f32x4s(z), depth.base[depth_offset..], depth.format); + return true; +} + fn depthTestAndUpdateAtOffset(depth: *RenderTargetAccess, offset: usize, z: f32, state: vk.PipelineDepthStencilStateCreateInfo) bool { if (state.depth_test_enable == .false) return true; @@ -477,6 +502,7 @@ pub fn writeToTargets( z: f32, coverage_sample_mask: ?vk.SampleMask, fragment_sample_mask: ?vk.SampleMask, + depth_already_applied: bool, ) VkError!void { const io = draw_call.renderer.device.interface.io(); const pipeline_data = draw_call.renderer.state.pipeline.?.interface.mode.graphics; @@ -523,7 +549,8 @@ pub fn writeToTargets( // After work depth test to avoid overwritten depth pixels during fragment invocations. var depth_passed: ?bool = null; - if (depth_attachment_access) |depth| { + if (!depth_already_applied and depth_attachment_access != null) { + const depth = depth_attachment_access.?; const depth_offset = targetSampleOffset(depth.*, x, y, sample_index) orelse continue; depth.mutex.lock(io) catch return VkError.DeviceLost; diff --git a/src/software/device/rasterizer/edge_function.zig b/src/software/device/rasterizer/edge_function.zig index d2020f1..b2a6a41 100644 --- a/src/software/device/rasterizer/edge_function.zig +++ b/src/software/device/rasterizer/edge_function.zig @@ -36,6 +36,7 @@ const RunData = struct { stencil_attachment_access: ?*common.RenderTargetAccess, front_face: bool, has_fragment_shader: bool, + early_fragment_tests: bool, fragment_uses_derivatives: bool, fragment_uses_sample_id: bool, fragment_uses_centroid: bool, @@ -70,6 +71,10 @@ pub fn drawTriangle( stage.module.module.reflection_infos.needs_derivatives else false; + const early_fragment_tests = if (fragment_stage) |stage| + stage.module.module.reflection_infos.early_fragment_tests + else + false; const fragment_uses_sample_id = if (fragment_stage) |stage| stage.module.module.builtins.get(.SampleId) != null else @@ -130,6 +135,7 @@ pub fn drawTriangle( .stencil_attachment_access = stencil_attachment_access, .front_face = front_face, .has_fragment_shader = fragment_stage != null, + .early_fragment_tests = early_fragment_tests, .fragment_uses_derivatives = fragment_uses_derivatives, .fragment_uses_sample_id = fragment_uses_sample_id, .fragment_uses_centroid = fragment_uses_centroid, @@ -235,6 +241,33 @@ fn triangleCoverageMask(data: RunData, x: i32, y: i32, sample_count: usize) vk.S return mask; } +fn applyEarlyDepth(data: RunData, coverage_sample_mask: vk.SampleMask, x: i32, y: i32, z: f32, sample_count: usize) VkError!struct { + mask: vk.SampleMask, + applied: bool, +} { + if (!data.early_fragment_tests) + return .{ .mask = coverage_sample_mask, .applied = false }; + + const depth = data.depth_attachment_access orelse return .{ .mask = coverage_sample_mask, .applied = false }; + const pipeline_data = data.draw_call.renderer.state.pipeline.?.interface.mode.graphics; + const io = data.draw_call.renderer.device.interface.io(); + + var passed_mask: vk.SampleMask = 0; + for (0..sample_count) |sample_index| { + if (sample_index >= @bitSizeOf(vk.SampleMask)) + break; + + const bit = @as(vk.SampleMask, 1) << @as(u5, @intCast(sample_index)); + if ((coverage_sample_mask & bit) == 0) + continue; + + if (try common.depthTestSampleAndUpdate(io, depth, @intCast(x), @intCast(y), sample_index, z, pipeline_data.depth_stencil)) + passed_mask |= bit; + } + + return .{ .mask = passed_mask, .applied = true }; +} + fn runWrapper(data: RunData) void { @call(.always_inline, run, .{data}) catch |err| { std.log.scoped(.@"Rasterization stage").err("triangle fill mode catched a '{s}'", .{@errorName(err)}); @@ -272,8 +305,12 @@ inline fn run(data: RunData) !void { const b2 = w2 / data.area; const z = (b0 * data.v0.position[2]) + (b1 * data.v1.position[2]) + (b2 * data.v2.position[2]); const frag_w = (b0 / data.v0.position[3]) + (b1 / data.v1.position[3]) + (b2 / data.v2.position[3]); + const early_depth = try applyEarlyDepth(data, coverage_sample_mask, x, y, z, sample_count); + if (early_depth.mask == 0) + continue; + const interpolation_barycentrics = if (data.fragment_uses_centroid and sample_count > 1) blk: { - const sample_pos = firstCoveredSamplePosition(sample_count, coverage_sample_mask); + const sample_pos = firstCoveredSamplePosition(sample_count, early_depth.mask); const centroid_p = zm.f32x4( @as(f32, @floatFromInt(x)) + sample_pos.x, @as(f32, @floatFromInt(y)) + sample_pos.y, @@ -305,7 +342,7 @@ inline fn run(data: RunData) !void { const bit_index: u5 = @intCast(sample_index); const sample_coverage_mask = @as(vk.SampleMask, 1) << bit_index; - if ((coverage_sample_mask & sample_coverage_mask) == 0) + if ((early_depth.mask & sample_coverage_mask) == 0) continue; const inputs = try common.interpolateVertexOutputs(data.allocator, &data.v0, &data.v1, &data.v2, &data.provoking_vertex, input_b0, input_b1, input_b2); @@ -344,6 +381,7 @@ inline fn run(data: RunData) !void { sample_result.depth orelse z, sample_coverage_mask, sample_result.sample_mask, + early_depth.applied, ); } continue; @@ -423,8 +461,9 @@ inline fn run(data: RunData) !void { @intCast(x), @intCast(y), fragment_result.depth orelse z, - coverage_sample_mask, + early_depth.mask, fragment_result.sample_mask, + early_depth.applied, ); } } diff --git a/src/software/device/vertex_dispatcher.zig b/src/software/device/vertex_dispatcher.zig index d59f39f..b88cd0e 100644 --- a/src/software/device/vertex_dispatcher.zig +++ b/src/software/device/vertex_dispatcher.zig @@ -60,8 +60,10 @@ inline fn run(data: RunData) !void { continue; } } - rt.resetInvocation(data.allocator); + if (rt.specialization_constants.count() != 0) + try rt.applySpecializationInvocationLayout(data.allocator); + try @import("Device.zig").writeDescriptorSets(data.draw_call.renderer.state, rt); try rt.populatePushConstants(data.draw_call.renderer.state.push_constant_blob[0..]); const vertex_index_u32: u32 = if (data.indices) |indices| indices[invocation_index] else @intCast(data.first_vertex + invocation_index); @@ -107,30 +109,101 @@ inline fn run(data: RunData) !void { try readPointSize(rt, &output.point_size); for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| { - for (0..4) |component| { - const result_word = rt.getResultByLocationComponent(@intCast(location), @intCast(component), .output) catch |err| switch (err) { + const location_result = rt.getResultByLocation(@intCast(location), .output) catch |err| switch (err) { + SpvRuntimeError.NotFound => continue, + else => return err, + }; + + try readVertexOutput(data, output, rt, location, 0, location_result); + + for (1..4) |component| { + const component_result = rt.getResultByLocationComponent(@intCast(location), @intCast(component), .output) catch |err| switch (err) { SpvRuntimeError.NotFound => continue, else => return err, }; - const memory_size = try rt.getResultMemorySize(result_word); + if (component_result == location_result) + continue; - const result_is_integer = resultIsInteger(rt, result_word); - - output.outputs[location][component] = .{ - .interpolation_type = if (rt.hasResultDecoration(result_word, .Flat) or result_is_integer) .flat else .smooth, // TODO : handle noperspective - .blob = data.allocator.alloc(u8, memory_size + INTERFACE_BLOB_PADDING) catch return VkError.OutOfDeviceMemory, - .size = memory_size, - }; - @memset(output.outputs[location][component].?.blob, 0); - try rt.readOutput(output.outputs[location][component].?.blob, result_word); + try readVertexOutput(data, output, rt, location, component, component_result); } } + try readActiveInterfaceOutputs(data, output, rt, entry); + try rt.flushDescriptorSets(data.allocator); } } +fn readActiveInterfaceOutputs(data: RunData, output: *Renderer.Vertex, rt: *spv.Runtime, entry: spv.SpvWord) !void { + if (entry >= rt.mod.entry_points.items.len) + return; + + for (rt.mod.entry_points.items[entry].globals) |global| { + if (global >= rt.results.len) + continue; + + const variable = switch (rt.results[global].variant orelse continue) { + .Variable => |v| v, + else => continue, + }; + if (variable.storage_class != .Output) + continue; + + var location: ?usize = null; + var component: usize = 0; + for (rt.results[global].decorations.items) |decoration| switch (decoration.rtype) { + .Location => location = decoration.literal_1, + .Component => component = decoration.literal_1, + else => {}, + }; + + const target_location = location orelse continue; + if (target_location >= spv.SPIRV_MAX_OUTPUT_LOCATIONS or component >= 4) + continue; + if (output.outputs[target_location][component] != null) + continue; + + try readVertexOutput(data, output, rt, target_location, component, global); + } +} + +fn readVertexOutput(data: RunData, output: *Renderer.Vertex, rt: *spv.Runtime, location: usize, component: usize, result_word: spv.SpvWord) !void { + const memory_size = try rt.getResultMemorySize(result_word); + const interpolation_type = vertexOutputInterpolationType(data, rt, location, component, result_word); + + output.outputs[location][component] = .{ + .interpolation_type = interpolation_type, + .blob = data.allocator.alloc(u8, memory_size + INTERFACE_BLOB_PADDING) catch return VkError.OutOfDeviceMemory, + .size = memory_size, + }; + @memset(output.outputs[location][component].?.blob, 0); + try rt.readOutput(output.outputs[location][component].?.blob, result_word); +} + +fn vertexOutputInterpolationType(data: RunData, rt: *spv.Runtime, location: usize, component: usize, result_word: spv.SpvWord) Renderer.InterpolationType { + const result_is_integer = resultIsInteger(rt, result_word); + + const fragment_input_word = if (data.pipeline.stages.getPtr(.fragment)) |fragment_shader| + fragment_shader.runtimes[0].rt.getResultByLocationComponent(@intCast(location), @intCast(component), .input) catch null + else + null; + const fragment_input_is_flat = if (fragment_input_word) |input_word| + data.pipeline.stages.getPtrAssertContains(.fragment).runtimes[0].rt.hasResultOrMemberDecoration(input_word, .Flat) + else + false; + const fragment_input_is_noperspective = if (fragment_input_word) |input_word| + data.pipeline.stages.getPtrAssertContains(.fragment).runtimes[0].rt.hasResultOrMemberDecoration(input_word, .NoPerspective) + else + false; + + if (fragment_input_is_flat or result_is_integer) + return .flat; + if (fragment_input_is_noperspective) + return .noperspective; + return .smooth; +} + fn findBindingDescription(binding_descriptions: []const vk.VertexInputBindingDescription, binding: u32) ?vk.VertexInputBindingDescription { for (binding_descriptions) |description| { if (description.binding == binding)