diff --git a/README.md b/README.md index 83b67b8..b358406 100644 --- a/README.md +++ b/README.md @@ -180,7 +180,7 @@ vkGetPhysicalDeviceXcbPresentationSupportKHR | ⚙️ WIP vkGetPhysicalDeviceXlibPresentationSupportKHR | ⚙️ WIP vkGetPipelineCacheData | ⚙️ WIP vkGetQueryPoolResults | ⚙️ WIP -vkGetRenderAreaGranularity | ⚙️ WIP +vkGetRenderAreaGranularity | ✅ Implemented vkGetSwapchainImagesKHR | ✅ Implemented vkInvalidateMappedMemoryRanges | ✅ Implemented vkMapMemory | ✅ Implemented @@ -200,7 +200,7 @@ vkUpdateDescriptorSets | ✅ Implemented vkWaitForFences | ✅ Implemented -[Here](https://vulkan-driver-cts-report.kbz8.me/) shalt thou find a most meticulous account of the Vulkan 1.0 conformance trials, set forth for thy scrutiny. +[Here](https://vulkan-driver.kbz8.me/cts/soft/) shalt thou find a most meticulous account of the Vulkan 1.0 conformance trials, set forth for thy scrutiny. ## License diff --git a/build.zig.zon b/build.zig.zon index add2050..e6cc9fb 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -26,8 +26,8 @@ .hash = "N-V-__8AAMpOQxkHCKTw9i-NwmmQ3ks1ndFDXcVLlic4KjK3", }, .SPIRV_Interpreter = .{ - .url = "git+https://git.kbz8.me/kbz_8/SPIRV-Interpreter#9c355fe126d0142ac6d1fae48633993864c90d61", - .hash = "SPIRV_Interpreter-0.0.1-ajmpn867BQA-J2PA4fGQGnb3WcNtqd_3_W7-goaEocC5", + .url = "git+https://git.kbz8.me/kbz_8/SPIRV-Interpreter#8677e8a6833d39e9169460fd75de2d7c70fab4b8", + .hash = "SPIRV_Interpreter-0.0.1-ajmpn_HtBQBR82M2KlR_yuUvdHUVdXrYVGRh5YNqVFqQ", .lazy = true, }, //.SPIRV_Interpreter = .{ diff --git a/src/soft/SoftCommandBuffer.zig b/src/soft/SoftCommandBuffer.zig index 4570ea1..544a3b5 100644 --- a/src/soft/SoftCommandBuffer.zig +++ b/src/soft/SoftCommandBuffer.zig @@ -231,21 +231,39 @@ pub fn bindDescriptorSets(interface: *Interface, bind_point: vk.PipelineBindPoin pub fn execute(context: *anyopaque, device: *ExecutionDevice) VkError!void { const impl: *Impl = @ptrCast(@alignCast(context)); + var dynamic_offset_index: usize = 0; for (impl.first_set.., impl.sets[0..]) |i, set| { if (set == null) break; - device.pipeline_states[@intCast(@intFromEnum(impl.bind_point))].sets[i] = @alignCast(@fieldParentPtr("interface", set.?)); + const state = &device.pipeline_states[@intCast(@intFromEnum(impl.bind_point))]; + const soft_set: *SoftDescriptorSet = @alignCast(@fieldParentPtr("interface", set.?)); + state.sets[i] = soft_set; + + const dynamic_count = soft_set.interface.layout.dynamic_descriptor_count; + if (dynamic_count > ExecutionDevice.MAX_DYNAMIC_DESCRIPTORS_PER_SET or + dynamic_offset_index + dynamic_count > impl.dynamic_offsets.len) + { + return VkError.ValidationFailed; + } + @memcpy( + state.dynamic_offsets[i][0..dynamic_count], + impl.dynamic_offsets[dynamic_offset_index .. dynamic_offset_index + dynamic_count], + ); + dynamic_offset_index += dynamic_count; } } }; + const dynamic_offsets_copy = allocator.dupe(u32, dynamic_offsets) catch return VkError.OutOfHostMemory; + errdefer allocator.free(dynamic_offsets_copy); + const cmd = allocator.create(CommandImpl) catch return VkError.OutOfHostMemory; errdefer allocator.destroy(cmd); cmd.* = .{ .bind_point = bind_point, .first_set = first_set, .sets = sets, - .dynamic_offsets = dynamic_offsets, + .dynamic_offsets = dynamic_offsets_copy, }; self.commands.append(allocator, .{ .ptr = cmd, .vtable = &.{ .execute = CommandImpl.execute } }) catch return VkError.OutOfHostMemory; } diff --git a/src/soft/SoftDescriptorSet.zig b/src/soft/SoftDescriptorSet.zig index beb7a39..302a249 100644 --- a/src/soft/SoftDescriptorSet.zig +++ b/src/soft/SoftDescriptorSet.zig @@ -34,6 +34,10 @@ const DescriptorImage = struct { object: ?*SoftImageView, }; +const DescriptorSampler = struct { + object: ?*SoftSampler, +}; + const DescriptorTexel = struct { object: ?*SoftBufferView, }; @@ -42,6 +46,7 @@ const Descriptor = union(enum) { buffer: []DescriptorBuffer, texture: []DescriptorTexture, image: []DescriptorImage, + sampler: []DescriptorSampler, texel_buffer: []DescriptorTexel, unsupported: struct {}, }; @@ -70,14 +75,19 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, layout: *base. for (layout.bindings) |binding| { const struct_size: usize = switch (binding.descriptor_type) { .uniform_buffer, + .uniform_buffer_dynamic, .storage_buffer, .storage_buffer_dynamic, => @sizeOf(DescriptorBuffer), + .sampled_image, .storage_image, .input_attachment, => @sizeOf(DescriptorImage), + .sampler, + => @sizeOf(DescriptorSampler), + .storage_texel_buffer, .uniform_texel_buffer, => @sizeOf(DescriptorTexel), @@ -103,6 +113,7 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, layout: *base. for (descriptors, layout.bindings) |*descriptor, binding| { switch (binding.descriptor_type) { .uniform_buffer, + .uniform_buffer_dynamic, .storage_buffer, .storage_buffer_dynamic, => descriptor.* = blk: { @@ -119,6 +130,7 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, layout: *base. break :blk desc; }, + .sampled_image, .storage_image, .input_attachment, => descriptor.* = blk: { @@ -131,6 +143,22 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, layout: *base. break :blk desc; }, + .sampler, + => descriptor.* = blk: { + const desc: Descriptor = .{ + .sampler = local_allocator.alloc(DescriptorSampler, binding.array_size) catch return VkError.OutOfHostMemory, + }; + for (desc.sampler[0..], 0..) |*d, i| { + d.* = .{ + .object = if (i < binding.immutable_samplers.len) + @as(*SoftSampler, @alignCast(@fieldParentPtr("interface", @constCast(binding.immutable_samplers[i])))) + else + null, + }; + } + break :blk desc; + }, + .storage_texel_buffer, .uniform_texel_buffer, => descriptor.* = blk: { @@ -148,9 +176,12 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, layout: *base. const desc: Descriptor = .{ .texture = local_allocator.alloc(DescriptorTexture, binding.array_size) catch return VkError.OutOfHostMemory, }; - for (desc.texture[0..]) |*d| { + for (desc.texture[0..], 0..) |*d, i| { d.* = .{ - .sampler = null, + .sampler = if (i < binding.immutable_samplers.len) + @as(*SoftSampler, @alignCast(@fieldParentPtr("interface", @constCast(binding.immutable_samplers[i])))) + else + null, .view = null, }; } @@ -179,6 +210,7 @@ fn descriptorLen(descriptor: Descriptor) usize { return switch (descriptor) { .buffer => |buffer| buffer.len, .image => |image| image.len, + .sampler => |sampler| sampler.len, .texel_buffer => |texel_buffer| texel_buffer.len, .texture => |texture| texture.len, .unsupported => 0, @@ -227,6 +259,17 @@ fn copyDescriptorRange(dst_desc: *Descriptor, dst_array_element: usize, src_desc ); }, + .sampler => |dst_sampler| { + const src_sampler = switch (src_desc) { + .sampler => |sampler| sampler, + else => return false, + }; + @memcpy( + dst_sampler[dst_array_element .. dst_array_element + descriptor_count], + src_sampler[src_array_element .. src_array_element + descriptor_count], + ); + }, + .texel_buffer => |dst_texel_buffer| { const src_texel_buffer = switch (src_desc) { .texel_buffer => |texel_buffer| texel_buffer, @@ -255,6 +298,33 @@ fn copyDescriptorRange(dst_desc: *Descriptor, dst_array_element: usize, src_desc return true; } +fn copyDescriptorRangePreservingImmutableSamplers(self: *Self, dst_binding: usize, dst_desc: *Descriptor, dst_array_element: usize, src_desc: Descriptor, src_array_element: usize, descriptor_count: usize) bool { + const immutable_samplers = self.interface.layout.bindings[dst_binding].immutable_samplers; + if (immutable_samplers.len == 0) { + return copyDescriptorRange(dst_desc, dst_array_element, src_desc, src_array_element, descriptor_count); + } + + const dst_texture = switch (dst_desc.*) { + .texture => |texture| texture, + else => return copyDescriptorRange(dst_desc, dst_array_element, src_desc, src_array_element, descriptor_count), + }; + const src_texture = switch (src_desc) { + .texture => |texture| texture, + else => return false, + }; + + for (0..descriptor_count) |i| { + const dst_index = dst_array_element + i; + const src_index = src_array_element + i; + dst_texture[dst_index].view = src_texture[src_index].view; + if (dst_index < immutable_samplers.len) { + dst_texture[dst_index].sampler = @as(*SoftSampler, @alignCast(@fieldParentPtr("interface", @constCast(immutable_samplers[dst_index])))); + } + } + + return true; +} + pub fn copy(interface: *Interface, src_interface: *const Interface, data: vk.CopyDescriptorSet) VkError!void { const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); const src: *const Self = @alignCast(@fieldParentPtr("interface", src_interface)); @@ -285,7 +355,7 @@ pub fn copy(interface: *Interface, src_interface: *const Interface, data: vk.Cop const src_remaining = src_len - src_array_element; const copy_count = @min(descriptor_count, dst_remaining, src_remaining); - if (!copyDescriptorRange(dst_desc, dst_array_element, src_desc, src_array_element, copy_count)) { + if (!self.copyDescriptorRangePreservingImmutableSamplers(dst_binding, dst_desc, dst_array_element, src_desc, src_array_element, copy_count)) { base.unsupported("descriptor type for copy", .{}); return; } @@ -301,11 +371,12 @@ pub fn write(interface: *Interface, write_data: vk.WriteDescriptorSet) VkError!v switch (write_data.descriptor_type) { .uniform_buffer, + .uniform_buffer_dynamic, .storage_buffer, .storage_buffer_dynamic, => { for (write_data.p_buffer_info, 0..write_data.descriptor_count) |buffer_info, i| { - const desc = &self.descriptors[write_data.dst_binding].buffer[i]; + const desc = &self.descriptors[write_data.dst_binding].buffer[write_data.dst_array_element + i]; desc.* = .{ .object = null, .offset = buffer_info.offset, @@ -321,11 +392,12 @@ pub fn write(interface: *Interface, write_data: vk.WriteDescriptorSet) VkError!v } }, + .sampled_image, .storage_image, .input_attachment, => { for (write_data.p_image_info, 0..write_data.descriptor_count) |image_info, i| { - const desc = &self.descriptors[write_data.dst_binding].image[i]; + const desc = &self.descriptors[write_data.dst_binding].image[write_data.dst_array_element + i]; desc.* = .{ .object = null }; if (image_info.image_view != .null_handle) { const image_view = try NonDispatchable(ImageView).fromHandleObject(image_info.image_view); @@ -334,11 +406,30 @@ pub fn write(interface: *Interface, write_data: vk.WriteDescriptorSet) VkError!v } }, + .sampler, + => { + for (write_data.p_image_info, 0..write_data.descriptor_count) |image_info, i| { + const desc = &self.descriptors[write_data.dst_binding].sampler[write_data.dst_array_element + i]; + const immutable_samplers = self.interface.layout.bindings[write_data.dst_binding].immutable_samplers; + const array_element = write_data.dst_array_element + i; + desc.* = .{ + .object = if (array_element < immutable_samplers.len) + @as(*SoftSampler, @alignCast(@fieldParentPtr("interface", @constCast(immutable_samplers[array_element])))) + else + null, + }; + if (immutable_samplers.len == 0 and image_info.sampler != .null_handle) { + const sampler = try NonDispatchable(Sampler).fromHandleObject(image_info.sampler); + desc.object = @as(*SoftSampler, @alignCast(@fieldParentPtr("interface", sampler))); + } + } + }, + .storage_texel_buffer, .uniform_texel_buffer, => { for (write_data.p_texel_buffer_view, 0..write_data.descriptor_count) |view, i| { - const desc = &self.descriptors[write_data.dst_binding].texel_buffer[i]; + const desc = &self.descriptors[write_data.dst_binding].texel_buffer[write_data.dst_array_element + i]; desc.* = .{ .object = null }; if (view != .null_handle) { const buffer_view = try NonDispatchable(BufferView).fromHandleObject(view); @@ -350,16 +441,21 @@ pub fn write(interface: *Interface, write_data: vk.WriteDescriptorSet) VkError!v .combined_image_sampler, => { for (write_data.p_image_info, 0..write_data.descriptor_count) |image_info, i| { - const desc = &self.descriptors[write_data.dst_binding].texture[i]; + const desc = &self.descriptors[write_data.dst_binding].texture[write_data.dst_array_element + i]; + const immutable_samplers = self.interface.layout.bindings[write_data.dst_binding].immutable_samplers; + const array_element = write_data.dst_array_element + i; desc.* = .{ - .sampler = null, + .sampler = if (array_element < immutable_samplers.len) + @as(*SoftSampler, @alignCast(@fieldParentPtr("interface", @constCast(immutable_samplers[array_element])))) + else + null, .view = null, }; if (image_info.image_view != .null_handle) { const image_view = try NonDispatchable(ImageView).fromHandleObject(image_info.image_view); desc.view = @as(*SoftImageView, @alignCast(@fieldParentPtr("interface", image_view))); } - if (image_info.sampler != .null_handle) { + if (immutable_samplers.len == 0 and image_info.sampler != .null_handle) { const sampler = try NonDispatchable(Sampler).fromHandleObject(image_info.sampler); desc.sampler = @as(*SoftSampler, @alignCast(@fieldParentPtr("interface", sampler))); } diff --git a/src/soft/SoftPipeline.zig b/src/soft/SoftPipeline.zig index 5fcc7fe..7dcb7d1 100644 --- a/src/soft/SoftPipeline.zig +++ b/src/soft/SoftPipeline.zig @@ -382,6 +382,7 @@ const CubeCoordinate = struct { face: u32, u: f32, v: f32, + w: f32 = 0.0, }; fn resolveCubeCoordinate(x: f32, y: f32, z: f32) CubeCoordinate { @@ -455,13 +456,17 @@ fn cubeDirection(face: u32, u: f32, v: f32) struct { x: f32, y: f32, z: f32 } { fn readSampledFloat4( image: *SoftImage, image_view: *SoftImageView, + sampler: *SoftSampler, dim: spv.SpvDim, coord: CubeCoordinate, ix: i32, iy: i32, + iz: i32, ) VkError!zm.F32x4 { - const width_f: f32 = @floatFromInt(image.interface.extent.width); - const height_f: f32 = @floatFromInt(image.interface.extent.height); + const range = image_view.interface.subresource_range; + const extent = image.getMipLevelExtent(range.base_mip_level); + const width_f: f32 = @floatFromInt(extent.width); + const height_f: f32 = @floatFromInt(extent.height); const texel = if (dim == .Cube) blk: { const dir = cubeDirection( @@ -472,61 +477,127 @@ fn readSampledFloat4( break :blk resolveCubeCoordinate(dir.x, dir.y, dir.z); } else coord; + const z: i32, const layer: u32 = switch (image_view.interface.view_type) { + .@"1d_array" => .{ 0, range.base_array_layer + @as(u32, @intCast(sampleAddress(@intFromFloat(coord.v), viewLayerCount(image, range), .clamp_to_edge))) }, + .@"2d_array", .cube_array => .{ 0, range.base_array_layer + @as(u32, @intCast(sampleAddress(@intFromFloat(coord.w), viewLayerCount(image, range), .clamp_to_edge))) }, + .@"3d" => .{ sampleAddressOrBorder(iz, extent.depth, sampler.interface.address_mode_w) orelse return samplerBorderColor(sampler), range.base_array_layer }, + .cube => .{ 0, range.base_array_layer + texel.face }, + else => .{ 0, range.base_array_layer }, + }; + + const sx = if (dim == .Cube) + std.math.clamp(@as(i32, @intFromFloat(texel.u * width_f)), 0, @as(i32, @intCast(extent.width)) - 1) + else + sampleAddressOrBorder(ix, extent.width, sampler.interface.address_mode_u) orelse return samplerBorderColor(sampler); + const sy = if (dim == .Cube) + std.math.clamp(@as(i32, @intFromFloat(texel.v * height_f)), 0, @as(i32, @intCast(extent.height)) - 1) + else + sampleAddressOrBorder(iy, extent.height, sampler.interface.address_mode_v) orelse return samplerBorderColor(sampler); + const result = try image.readFloat4( .{ - .x = if (dim == .Cube) - std.math.clamp(@as(i32, @intFromFloat(texel.u * width_f)), 0, image.interface.extent.width - 1) - else - std.math.clamp(ix, 0, image.interface.extent.width - 1), - .y = if (dim == .Cube) - std.math.clamp(@as(i32, @intFromFloat(texel.v * height_f)), 0, image.interface.extent.height - 1) - else - std.math.clamp(iy, 0, image.interface.extent.height - 1), - .z = 0, + .x = sx, + .y = sy, + .z = z, }, .{ - .aspect_mask = image_view.interface.subresource_range.aspect_mask, - .mip_level = image_view.interface.subresource_range.base_mip_level, - .array_layer = image_view.interface.subresource_range.base_array_layer + texel.face, + .aspect_mask = range.aspect_mask, + .mip_level = range.base_mip_level, + .array_layer = layer, }, image_view.interface.format, ); return result; } -fn sampleNearestFloat4(image: *SoftImage, image_view: *SoftImageView, dim: spv.SpvDim, coord: CubeCoordinate) VkError!zm.F32x4 { - const width_f: f32 = @floatFromInt(image.interface.extent.width); - const height_f: f32 = @floatFromInt(image.interface.extent.height); +fn sampleAddress(coord: i32, extent: u32, mode: vk.SamplerAddressMode) i32 { + return sampleAddressOrBorder(coord, extent, mode).?; +} + +fn sampleAddressOrBorder(coord: i32, extent: u32, mode: vk.SamplerAddressMode) ?i32 { + const extent_i: i32 = @intCast(extent); + return switch (mode) { + .repeat => @mod(coord, extent_i), + .mirrored_repeat => blk: { + const period = extent_i * 2; + const mirrored = @mod(coord, period); + break :blk if (mirrored < extent_i) mirrored else period - mirrored - 1; + }, + .clamp_to_border => if (coord < 0 or coord >= extent_i) null else coord, + else => std.math.clamp(coord, 0, extent_i - 1), + }; +} + +fn samplerBorderColor(sampler: *SoftSampler) zm.F32x4 { + return switch (sampler.interface.border_color) { + .float_opaque_white, .int_opaque_white => .{ 1.0, 1.0, 1.0, 1.0 }, + .float_opaque_black, .int_opaque_black => .{ 0.0, 0.0, 0.0, 1.0 }, + else => .{ 0.0, 0.0, 0.0, 0.0 }, + }; +} + +fn viewLayerCount(image: *SoftImage, range: vk.ImageSubresourceRange) u32 { + return if (range.layer_count == vk.REMAINING_ARRAY_LAYERS) + image.interface.array_layers - range.base_array_layer + else + range.layer_count; +} + +fn sampleNearestFloat4(image: *SoftImage, image_view: *SoftImageView, sampler: *SoftSampler, dim: spv.SpvDim, coord: CubeCoordinate) VkError!zm.F32x4 { + const extent = image.getMipLevelExtent(image_view.interface.subresource_range.base_mip_level); + const width_f: f32 = @floatFromInt(extent.width); + const height_f: f32 = @floatFromInt(extent.height); return readSampledFloat4( image, image_view, + sampler, dim, coord, @intFromFloat(coord.u * width_f), @intFromFloat(coord.v * height_f), + @intFromFloat(coord.w * @as(f32, @floatFromInt(extent.depth))), ); } -fn sampleLinearFloat4(image: *SoftImage, image_view: *SoftImageView, dim: spv.SpvDim, coord: CubeCoordinate) VkError!zm.F32x4 { - const width_f: f32 = @floatFromInt(image.interface.extent.width); - const height_f: f32 = @floatFromInt(image.interface.extent.height); +fn sampleLinearFloat4(image: *SoftImage, image_view: *SoftImageView, sampler: *SoftSampler, dim: spv.SpvDim, coord: CubeCoordinate) VkError!zm.F32x4 { + const extent = image.getMipLevelExtent(image_view.interface.subresource_range.base_mip_level); + const width_f: f32 = @floatFromInt(extent.width); + const height_f: f32 = @floatFromInt(extent.height); const x = coord.u * width_f - 0.5; const y = coord.v * height_f - 0.5; + const z = coord.w * @as(f32, @floatFromInt(extent.depth)) - 0.5; const x0: i32 = @intFromFloat(@floor(x)); const y0: i32 = @intFromFloat(@floor(y)); + const z0: i32 = @intFromFloat(@floor(z)); const x1 = x0 + 1; const y1 = y0 + 1; + const z1 = z0 + 1; const wx = x - @as(f32, @floatFromInt(x0)); const wy = y - @as(f32, @floatFromInt(y0)); + const wz = z - @as(f32, @floatFromInt(z0)); - const p00 = try readSampledFloat4(image, image_view, dim, coord, x0, y0); - const p10 = try readSampledFloat4(image, image_view, dim, coord, x1, y0); - const p01 = try readSampledFloat4(image, image_view, dim, coord, x0, y1); - const p11 = try readSampledFloat4(image, image_view, dim, coord, x1, y1); + const p000 = try readSampledFloat4(image, image_view, sampler, dim, coord, x0, y0, z0); + const p100 = try readSampledFloat4(image, image_view, sampler, dim, coord, x1, y0, z0); + const p010 = try readSampledFloat4(image, image_view, sampler, dim, coord, x0, y1, z0); + const p110 = try readSampledFloat4(image, image_view, sampler, dim, coord, x1, y1, z0); - const row0 = p00 * zm.f32x4s(1.0 - wx) + p10 * zm.f32x4s(wx); - const row1 = p01 * zm.f32x4s(1.0 - wx) + p11 * zm.f32x4s(wx); - return row0 * zm.f32x4s(1.0 - wy) + row1 * zm.f32x4s(wy); + const row00 = p000 * zm.f32x4s(1.0 - wx) + p100 * zm.f32x4s(wx); + const row10 = p010 * zm.f32x4s(1.0 - wx) + p110 * zm.f32x4s(wx); + const slice0 = row00 * zm.f32x4s(1.0 - wy) + row10 * zm.f32x4s(wy); + + if (image_view.interface.view_type != .@"3d") + return slice0; + + const p001 = try readSampledFloat4(image, image_view, sampler, dim, coord, x0, y0, z1); + const p101 = try readSampledFloat4(image, image_view, sampler, dim, coord, x1, y0, z1); + const p011 = try readSampledFloat4(image, image_view, sampler, dim, coord, x0, y1, z1); + const p111 = try readSampledFloat4(image, image_view, sampler, dim, coord, x1, y1, z1); + + const row01 = p001 * zm.f32x4s(1.0 - wx) + p101 * zm.f32x4s(wx); + const row11 = p011 * zm.f32x4s(1.0 - wx) + p111 * zm.f32x4s(wx); + const slice1 = row01 * zm.f32x4s(1.0 - wy) + row11 * zm.f32x4s(wy); + + return slice0 * zm.f32x4s(1.0 - wz) + slice1 * zm.f32x4s(wz); } fn sampleImageFloat4(context: *anyopaque, context2: *anyopaque, dim: spv.SpvDim, x: f32, y: f32, z: f32) SpvRuntimeError!spv.Runtime.Vec4(f32) { @@ -546,23 +617,20 @@ fn sampleImageFloat4(context: *anyopaque, context2: *anyopaque, dim: spv.SpvDim, if (dim == .Cube) { const coord = resolveCubeCoordinate(x, y, z); pixel = switch (sampler.interface.mag_filter) { - .linear => sampleLinearFloat4(image, image_view, dim, coord), - else => sampleNearestFloat4(image, image_view, dim, coord), + .linear => sampleLinearFloat4(image, image_view, sampler, dim, coord), + else => sampleNearestFloat4(image, image_view, sampler, dim, coord), } catch return SpvRuntimeError.Unknown; } else { - pixel = image.readFloat4( - .{ - .x = std.math.clamp(@as(i32, @intFromFloat(x * @as(f32, @floatFromInt(image.interface.extent.width)))), 0, image.interface.extent.width - 1), - .y = std.math.clamp(@as(i32, @intFromFloat(y * @as(f32, @floatFromInt(image.interface.extent.height)))), 0, image.interface.extent.height - 1), - .z = std.math.clamp(@as(i32, @intFromFloat(z * @as(f32, @floatFromInt(image.interface.extent.depth)))), 0, image.interface.extent.depth - 1), - }, - .{ - .aspect_mask = image_view.interface.subresource_range.aspect_mask, - .mip_level = image_view.interface.subresource_range.base_mip_level, - .array_layer = image_view.interface.subresource_range.base_array_layer, - }, - image_view.interface.format, - ) catch return SpvRuntimeError.Unknown; + const coord: CubeCoordinate = .{ + .u = x, + .v = y, + .w = z, + .face = 0, + }; + pixel = switch (sampler.interface.mag_filter) { + .linear => sampleLinearFloat4(image, image_view, sampler, dim, coord), + else => sampleNearestFloat4(image, image_view, sampler, dim, coord), + } catch return SpvRuntimeError.Unknown; } } diff --git a/src/soft/SoftRenderPass.zig b/src/soft/SoftRenderPass.zig index cab27b3..cf56459 100644 --- a/src/soft/SoftRenderPass.zig +++ b/src/soft/SoftRenderPass.zig @@ -18,6 +18,7 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v interface.vtable = &.{ .destroy = destroy, + .getRenderAreaGranularity = getRenderAreaGranularity, }; self.* = .{ @@ -30,3 +31,10 @@ pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void { const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); allocator.destroy(self); } + +pub fn getRenderAreaGranularity(_: *Interface) vk.Extent2D { + return .{ + .width = 1, + .height = 1, + }; +} diff --git a/src/soft/device/ComputeDispatcher.zig b/src/soft/device/ComputeDispatcher.zig index 9dd16d1..f686c6b 100644 --- a/src/soft/device/ComputeDispatcher.zig +++ b/src/soft/device/ComputeDispatcher.zig @@ -157,9 +157,10 @@ inline fn run(data: RunData) !void { continue; } - try setupWorkgroupBuiltins(data.self, rt, group_count_vec, group_id_vec); - for (0..data.invocations_per_workgroup) |i| { + rt.resetInvocation(allocator); + try setupWorkgroupBuiltins(data.self, rt, group_count_vec, group_id_vec); + const invocation_index = data.self.invocation_index.fetchAdd(1, .monotonic); try setupSubgroupBuiltins(data.self, rt, .{ @@ -202,6 +203,7 @@ fn runBarrierWorkgroup( const allocator = data.self.device.device_allocator.allocator(); for (runtimes, 0..) |*rt, i| { + rt.resetInvocation(allocator); try ExecutionDevice.writeDescriptorSets(data.self.state, rt); try setupWorkgroupBuiltins(data.self, rt, group_count, group_id); try setupSubgroupBuiltins(data.self, rt, group_id, i); diff --git a/src/soft/device/Device.zig b/src/soft/device/Device.zig index b3fc2ea..b807037 100644 --- a/src/soft/device/Device.zig +++ b/src/soft/device/Device.zig @@ -19,10 +19,12 @@ const Self = @This(); pub const GRAPHICS_PIPELINE_STATE = 0; pub const COMPUTE_PIPELINE_STATE = 1; +pub const MAX_DYNAMIC_DESCRIPTORS_PER_SET = 64; pub const PipelineState = struct { pipeline: ?*SoftPipeline, sets: [base.VULKAN_MAX_DESCRIPTOR_SETS]?*SoftDescriptorSet, + dynamic_offsets: [base.VULKAN_MAX_DESCRIPTOR_SETS][MAX_DYNAMIC_DESCRIPTORS_PER_SET]u32, push_constant_blob: [lib.PUSH_CONSTANT_SIZE]u8, data: union { compute: struct {}, @@ -45,6 +47,7 @@ pub fn setup(self: *Self, device: *SoftDevice) void { state.* = .{ .pipeline = null, .sets = [_]?*SoftDescriptorSet{null} ** base.VULKAN_MAX_DESCRIPTOR_SETS, + .dynamic_offsets = [_][MAX_DYNAMIC_DESCRIPTORS_PER_SET]u32{[_]u32{0} ** MAX_DYNAMIC_DESCRIPTORS_PER_SET} ** base.VULKAN_MAX_DESCRIPTOR_SETS, .push_constant_blob = @splat(0), .data = switch (i) { GRAPHICS_PIPELINE_STATE => .{ @@ -71,37 +74,66 @@ 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 map = buffer.mapAsSliceWithAddedOffset(u8, buffer_data.offset, buffer_data.size) catch continue :bindings; - try rt.writeDescriptorSet( + const binding_layout = set.?.interface.layout.bindings[binding_index]; + const dynamic_offset: vk.DeviceSize = switch (binding_layout.descriptor_type) { + .uniform_buffer_dynamic, .storage_buffer_dynamic => state.dynamic_offsets[set_index][binding_layout.dynamic_index + descriptor_index], + else => 0, + }; + const map = buffer.mapAsSliceWithAddedOffset(u8, buffer_data.offset + dynamic_offset, buffer_data.size) catch continue :bindings; + rt.writeDescriptorSet( map, @as(u32, @intCast(set_index)), @as(u32, @intCast(binding_index)), @as(u32, @intCast(descriptor_index)), - ); + ) catch |err| switch (err) { + error.NotFound => {}, + else => return err, + }; } }, .image => |image_data_array| for (image_data_array, 0..) |image_data, descriptor_index| { if (image_data.object) |image_view| { const addr: usize = @intFromPtr(image_view); - try rt.writeDescriptorSet( + rt.writeDescriptorSet( std.mem.asBytes(&addr), @as(u32, @intCast(set_index)), @as(u32, @intCast(binding_index)), @as(u32, @intCast(descriptor_index)), - ); + ) catch |err| switch (err) { + error.NotFound => {}, + else => return err, + }; + } + }, + + .sampler => |sampler_data_array| for (sampler_data_array, 0..) |sampler_data, descriptor_index| { + if (sampler_data.object) |sampler| { + const addr: usize = @intFromPtr(sampler); + rt.writeDescriptorSet( + std.mem.asBytes(&addr), + @as(u32, @intCast(set_index)), + @as(u32, @intCast(binding_index)), + @as(u32, @intCast(descriptor_index)), + ) catch |err| switch (err) { + error.NotFound => {}, + else => return err, + }; } }, .texel_buffer => |texel_data_array| for (texel_data_array, 0..) |texel_data, descriptor_index| { if (texel_data.object) |buffer_view| { const addr: usize = @intFromPtr(buffer_view); - try rt.writeDescriptorSet( + rt.writeDescriptorSet( std.mem.asBytes(&addr), @as(u32, @intCast(set_index)), @as(u32, @intCast(binding_index)), @as(u32, @intCast(descriptor_index)), - ); + ) catch |err| switch (err) { + error.NotFound => {}, + else => return err, + }; } }, @@ -111,7 +143,10 @@ pub fn writeDescriptorSets(state: *PipelineState, rt: *spv.Runtime) !void { sampler: usize, }; - var data: SampledImage = undefined; + var data: SampledImage = .{ + .image = 0, + .sampler = 0, + }; if (texture_data.view) |image_view| { const addr: usize = @intFromPtr(image_view); @@ -122,12 +157,15 @@ pub fn writeDescriptorSets(state: *PipelineState, rt: *spv.Runtime) !void { data.sampler = addr; } - try rt.writeDescriptorSet( + rt.writeDescriptorSet( std.mem.asBytes(&data), @as(u32, @intCast(set_index)), @as(u32, @intCast(binding_index)), @as(u32, @intCast(descriptor_index)), - ); + ) catch |err| switch (err) { + error.NotFound => {}, + else => return err, + }; }, else => {}, diff --git a/src/soft/device/Renderer.zig b/src/soft/device/Renderer.zig index dd17d34..be5d604 100644 --- a/src/soft/device/Renderer.zig +++ b/src/soft/device/Renderer.zig @@ -50,6 +50,7 @@ pub const Vertex = struct { outputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS]?struct { interpolation_type: enum { smooth, flat, noperspective }, blob: []u8, + size: usize, }, }; diff --git a/src/soft/device/clip.zig b/src/soft/device/clip.zig index 217fd80..7799bed 100644 --- a/src/soft/device/clip.zig +++ b/src/soft/device/clip.zig @@ -10,6 +10,7 @@ const Renderer = @import("Renderer.zig"); const Vertex = Renderer.Vertex; const VkError = base.VkError; +const INTERFACE_BLOB_PADDING = @sizeOf(F32x4); const ClipPlane = enum { Left, @@ -142,9 +143,10 @@ fn isVertexInsidePlane(vertex: *const Vertex, plane: ClipPlane) bool { return clipDistance(vertex.position, plane) >= 0.0; } -fn interpolateBlob(allocator: std.mem.Allocator, a: []const u8, b: []const u8, t: f32) VkError![]u8 { - const len = @min(a.len, b.len); - const result = allocator.alloc(u8, len) catch return VkError.OutOfDeviceMemory; +fn interpolateBlob(allocator: std.mem.Allocator, a: []const u8, b: []const u8, size: usize, t: f32) VkError![]u8 { + const len = @min(size, a.len, b.len); + const result = allocator.alloc(u8, len + INTERFACE_BLOB_PADDING) catch return VkError.OutOfDeviceMemory; + @memset(result, 0); var byte_index: usize = 0; while (byte_index + @sizeOf(F32x4) <= len) : (byte_index += @sizeOf(F32x4)) { @@ -160,7 +162,7 @@ fn interpolateBlob(allocator: std.mem.Allocator, a: []const u8, b: []const u8, t } if (byte_index < len) - @memcpy(result[byte_index..], a[byte_index..len]); + @memcpy(result[byte_index..len], a[byte_index..len]); return result; } @@ -182,7 +184,8 @@ fn interpolateVertexForClipping(allocator: std.mem.Allocator, a: *const Vertex, .blob = if (out_a.interpolation_type == .flat) allocator.dupe(u8, out_a.blob) catch return VkError.OutOfDeviceMemory else - try interpolateBlob(allocator, out_a.blob, out_b.blob, t), + try interpolateBlob(allocator, out_a.blob, out_b.blob, @min(out_a.size, out_b.size), t), + .size = @min(out_a.size, out_b.size), }; } diff --git a/src/soft/device/fragment.zig b/src/soft/device/fragment.zig index aeefd47..8f3713f 100644 --- a/src/soft/device/fragment.zig +++ b/src/soft/device/fragment.zig @@ -11,6 +11,7 @@ const SoftImage = @import("../SoftImage.zig"); const VkError = base.VkError; const SpvRuntimeError = spv.Runtime.RuntimeError; +const INTERFACE_BLOB_PADDING = @sizeOf(zm.F32x4); pub fn shaderInvocation( allocator: std.mem.Allocator, @@ -19,9 +20,11 @@ pub fn shaderInvocation( position: zm.F32x4, inputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolation, ) SpvRuntimeError![spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(zm.F32x4)]u8 { + var fragment_inputs = inputs; + errdefer freeOwnedInputs(allocator, fragment_inputs); + const io = draw_call.renderer.device.interface.io(); - _ = position; const pipeline = draw_call.renderer.state.pipeline orelse return undefined; const shader = pipeline.stages.getPtr(.fragment) orelse return undefined; @@ -32,7 +35,12 @@ pub fn shaderInvocation( mutex.lock(io) catch return SpvRuntimeError.Unknown; defer mutex.unlock(io); + rt.resetInvocation(allocator); try rt.populatePushConstants(draw_call.renderer.state.push_constant_blob[0..]); + rt.writeBuiltIn(std.mem.asBytes(&position), .FragCoord) catch |err| switch (err) { + SpvRuntimeError.NotFound => {}, + else => return err, + }; const entry = try rt.getEntryPointByName(shader.entry); @@ -41,9 +49,23 @@ pub fn shaderInvocation( SpvRuntimeError.NotFound => continue, else => return err, }; - try rt.writeInput(inputs[location].blob, result_word); - if (inputs[location].free_responsability) - allocator.free(inputs[location].blob); + + var input = fragment_inputs[location]; + if (input.blob.len == 0) { + const memory_size = try rt.getResultMemorySize(result_word); + const zeroes = allocator.alloc(u8, memory_size + INTERFACE_BLOB_PADDING) catch return SpvRuntimeError.OutOfMemory; + @memset(zeroes, 0); + fragment_inputs[location] = .{ + .blob = zeroes, + .size = memory_size, + .free_responsability = true, + }; + input = fragment_inputs[location]; + } + + if (input.blob.len != 0) { + try rt.writeInput(input.blob, result_word); + } } rt.callEntryPoint(allocator, entry) catch |err| switch (err) { @@ -66,6 +88,14 @@ pub fn shaderInvocation( } try rt.flushDescriptorSets(allocator); + freeOwnedInputs(allocator, fragment_inputs); return outputs; } + +fn freeOwnedInputs(allocator: std.mem.Allocator, inputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolation) void { + for (inputs) |input| { + if (input.free_responsability) + allocator.free(input.blob); + } +} diff --git a/src/soft/device/rasterizer.zig b/src/soft/device/rasterizer.zig index 2369739..d23676d 100644 --- a/src/soft/device/rasterizer.zig +++ b/src/soft/device/rasterizer.zig @@ -1,12 +1,15 @@ const std = @import("std"); const vk = @import("vulkan"); const base = @import("base"); +const zm = base.zm; const clip = @import("clip.zig"); const bresenham = @import("rasterizer/bresenham.zig"); const edge_function = @import("rasterizer/edge_function.zig"); const common = @import("rasterizer/common.zig"); +const fragment = @import("fragment.zig"); +const blitter = @import("blitter.zig"); const Renderer = @import("Renderer.zig"); const Vertex = Renderer.Vertex; @@ -21,7 +24,7 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato const pipeline_data = (renderer.state.pipeline orelse return VkError.InvalidHandleDrv).interface.mode.graphics; const topology = pipeline_data.input_assembly.topology; - const color_attachments = draw_call.render_pass.interface.subpasses[renderer.subpass_index].color_attachments orelse return VkError.InvalidAttachmentDrv; + const color_attachments = draw_call.render_pass.interface.subpasses[renderer.subpass_index].color_attachments orelse &.{}; const color_attachment_access = allocator.alloc(?common.RenderTargetAccess, color_attachments.len) catch return VkError.OutOfDeviceMemory; @memset(color_attachment_access, null); @@ -76,6 +79,15 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato }; switch (topology) { + .point_list => for (draw_call.vertices) |*vertex| { + try clipTransformAndRasterizePoint( + allocator, + draw_call, + vertex, + color_attachment_access, + if (depth_attachment_access) |*access| access else null, + ); + }, .triangle_list => for (0..@divTrunc(draw_call.vertices.len, 3)) |triangle_index| { const first_vertex = triangle_index * 3; const v0 = &draw_call.vertices[first_vertex + 0]; @@ -177,6 +189,61 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato draw_call.rasterizer_wait_group.await(io) catch return VkError.DeviceLost; } +fn clipTransformAndRasterizePoint( + allocator: std.mem.Allocator, + draw_call: *DrawCall, + vertex: *Vertex, + color_attachment_access: []const ?common.RenderTargetAccess, + depth_attachment_access: ?*common.RenderTargetAccess, +) VkError!void { + const x, const y, const z, const w = vertex.position; + if (w == 0.0 or x < -w or x > w or y < -w or y > w or z < 0.0 or z > w) + return; + + var transformed = vertex.*; + clip.viewportTransformVertex(draw_call.viewport, &transformed); + + const point_size = 1.0; + const min_x: i32 = @intFromFloat(@floor(transformed.position[0] - (point_size / 2.0))); + const max_x: i32 = @intFromFloat(@ceil(transformed.position[0] + (point_size / 2.0)) - 1.0); + const min_y: i32 = @intFromFloat(@floor(transformed.position[1] - (point_size / 2.0))); + const max_y: i32 = @intFromFloat(@ceil(transformed.position[1] + (point_size / 2.0)) - 1.0); + + var py = min_y; + while (py <= max_y) : (py += 1) { + var px = min_x; + while (px <= max_x) : (px += 1) { + if (!common.scissorContainsPixel(draw_call.scissor, px, py)) + continue; + + if (depth_attachment_access) |depth| { + const offset = @as(usize, @intCast(px)) * depth.texel_size + @as(usize, @intCast(py)) * depth.row_pitch; + const depth_value = blitter.readFloat4(depth.base[offset..], depth.format); + if (transformed.position[2] >= depth_value[0]) + continue; + } + + const outputs = fragment.shaderInvocation( + allocator, + draw_call, + 0, + zm.f32x4(@floatFromInt(px), @floatFromInt(py), transformed.position[2], 1.0), + try common.interpolateVertexOutputs(allocator, &transformed, &transformed, &transformed, 1.0, 0.0, 0.0), + ) catch |err| { + std.log.scoped(.@"Fragment stage").err("catched a '{s}'", .{@errorName(err)}); + if (comptime base.config.logs == .verbose) { + if (@errorReturnTrace()) |trace| { + std.debug.dumpErrorReturnTrace(trace); + } + } + return; + }; + + try common.writeToTargets(outputs, draw_call, color_attachment_access, depth_attachment_access, @intCast(px), @intCast(py), transformed.position[2]); + } + } +} + fn clipTransformAndRasterizeLine( allocator: std.mem.Allocator, draw_call: *DrawCall, diff --git a/src/soft/device/rasterizer/common.zig b/src/soft/device/rasterizer/common.zig index 26a0a33..07e9809 100644 --- a/src/soft/device/rasterizer/common.zig +++ b/src/soft/device/rasterizer/common.zig @@ -21,6 +21,7 @@ pub const RenderTargetAccess = struct { pub const VertexInterpolation = struct { blob: []const u8, + size: usize, free_responsability: bool, }; @@ -49,20 +50,25 @@ pub fn interpolateVertexOutputs( b1: f32, b2: f32, ) VkError![spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolation { - var inputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolation = undefined; + var inputs = [_]VertexInterpolation{.{ + .blob = &.{}, + .size = 0, + .free_responsability = false, + }} ** spv.SPIRV_MAX_OUTPUT_LOCATIONS; for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| { const out0 = v0.outputs[location] orelse continue; const out1 = v1.outputs[location] orelse continue; const out2 = v2.outputs[location] orelse continue; - if (out0.interpolation_type == .flat or out0.blob.len == 0) { - inputs[location] = .{ .blob = out0.blob, .free_responsability = false }; + if (out0.interpolation_type == .flat or out0.size == 0) { + inputs[location] = .{ .blob = out0.blob, .size = out0.size, .free_responsability = false }; continue; } - const len = @min(out0.blob.len, out1.blob.len, out2.blob.len); - const input = allocator.alloc(u8, len) catch return VkError.OutOfDeviceMemory; + const len = @min(out0.size, out1.size, out2.size); + const input = allocator.alloc(u8, len + @sizeOf(F32x4)) catch return VkError.OutOfDeviceMemory; + @memset(input, 0); var byte_index: usize = 0; while (byte_index + @sizeOf(F32x4) <= len) : (byte_index += @sizeOf(F32x4)) { @@ -80,9 +86,9 @@ pub fn interpolateVertexOutputs( } if (byte_index < len) - @memcpy(input[byte_index..], out0.blob[byte_index..len]); + @memcpy(input[byte_index..len], out0.blob[byte_index..len]); - inputs[location] = .{ .blob = input, .free_responsability = true }; + inputs[location] = .{ .blob = input, .size = len, .free_responsability = true }; } return inputs; diff --git a/src/soft/device/vertex_dispatcher.zig b/src/soft/device/vertex_dispatcher.zig index d90f12f..e70600f 100644 --- a/src/soft/device/vertex_dispatcher.zig +++ b/src/soft/device/vertex_dispatcher.zig @@ -1,8 +1,9 @@ const std = @import("std"); const spv = @import("spv"); const base = @import("base"); +const vk = @import("vulkan"); -const F32x4 = Renderer.F32x4; +const F32x4 = base.zm.F32x4; const SpvRuntimeError = spv.Runtime.RuntimeError; @@ -10,6 +11,7 @@ const Renderer = @import("Renderer.zig"); const SoftPipeline = @import("../SoftPipeline.zig"); const VkError = base.VkError; +const INTERFACE_BLOB_PADDING = @sizeOf(F32x4); pub const RunData = struct { allocator: std.mem.Allocator, @@ -38,12 +40,14 @@ pub fn runWrapper(data: RunData) void { inline fn run(data: RunData) !void { const shader = data.pipeline.stages.getPtrAssertContains(.vertex); const rt = &shader.runtimes[data.batch_id].rt; - try rt.populatePushConstants(data.draw_call.renderer.state.push_constant_blob[0..]); const entry = try rt.getEntryPointByName(shader.entry); var invocation_index: usize = data.batch_id; while (invocation_index < data.vertex_count) : (invocation_index += data.batch_size) { + rt.resetInvocation(data.allocator); + try rt.populatePushConstants(data.draw_call.renderer.state.push_constant_blob[0..]); + const vertex_index: usize = if (data.indices) |indices| @intCast(indices[invocation_index]) else data.first_vertex + invocation_index; const instance_index = data.first_instance + data.instance_index; @@ -54,8 +58,6 @@ inline fn run(data: RunData) !void { if (data.pipeline.interface.mode.graphics.input_assembly.attribute_description) |attributes| { for (attributes) |attribute| { - const location_result = try rt.getResultByLocation(attribute.location, .input); - const binding_info = (data.pipeline.interface.mode.graphics.input_assembly.binding_description orelse return)[attribute.binding]; const vertex_buffer = data.draw_call.renderer.state.data.graphics.vertex_buffers[attribute.binding]; @@ -66,7 +68,7 @@ inline fn run(data: RunData) !void { const buffer_memory_map: []u8 = try buffer_memory.map(offset, buffer_memory_size); - try rt.writeInput(buffer_memory_map, location_result); + try writeVertexInput(rt, data.allocator, buffer_memory_map, attribute.format, attribute.location); } } @@ -86,10 +88,13 @@ inline fn run(data: RunData) !void { SpvRuntimeError.NotFound => continue, else => return err, }; + const memory_size = try rt.getResultMemorySize(result_word); output.outputs[location] = .{ .interpolation_type = if (rt.hasResultDecoration(result_word, .Flat)) .flat else .smooth, // TODO : handle noperspective - .blob = data.allocator.alloc(u8, try rt.getResultMemorySize(result_word)) catch return VkError.OutOfDeviceMemory, + .blob = data.allocator.alloc(u8, memory_size + INTERFACE_BLOB_PADDING) catch return VkError.OutOfDeviceMemory, + .size = memory_size, }; + @memset(output.outputs[location].?.blob, 0); try rt.readOutput(output.outputs[location].?.blob, result_word); } @@ -104,3 +109,45 @@ fn setupBuiltins(rt: *spv.Runtime, vertex_index: usize, instance_index: usize) ! try rt.writeBuiltIn(std.mem.asBytes(&vertex_index_u32), .VertexIndex); try rt.writeBuiltIn(std.mem.asBytes(&instance_index_u32), .InstanceIndex); } + +fn writeVertexInput( + rt: *spv.Runtime, + allocator: std.mem.Allocator, + raw_input: []const u8, + format: vk.Format, + location: u32, +) !void { + const input_memory_size = try rt.getInputLocationMemorySize(location); + + if (raw_input.len >= input_memory_size) { + try rt.writeInputLocation(raw_input[0..input_memory_size], location); + return; + } + + const input = allocator.alloc(u8, input_memory_size) catch return VkError.OutOfDeviceMemory; + defer allocator.free(input); + + @memset(input, 0); + @memcpy(input[0..raw_input.len], raw_input); + + fillMissingVertexComponents(input, raw_input.len, format); + try rt.writeInputLocation(input, location); +} + +fn fillMissingVertexComponents(input: []u8, raw_input_size: usize, format: vk.Format) void { + if (input.len < @sizeOf(F32x4) or raw_input_size > 3 * @sizeOf(f32)) + return; + + const component_count = base.format.componentCount(format); + if (component_count >= 4) + return; + + const alpha_offset = 3 * @sizeOf(f32); + if (base.format.isUnnormalizedInteger(format)) { + const one: u32 = 1; + @memcpy(input[alpha_offset .. alpha_offset + @sizeOf(u32)], std.mem.asBytes(&one)); + } else { + const one: f32 = 1.0; + @memcpy(input[alpha_offset .. alpha_offset + @sizeOf(f32)], std.mem.asBytes(&one)); + } +} diff --git a/src/vulkan/CommandBuffer.zig b/src/vulkan/CommandBuffer.zig index 547eacb..194ea05 100644 --- a/src/vulkan/CommandBuffer.zig +++ b/src/vulkan/CommandBuffer.zig @@ -108,12 +108,14 @@ pub inline fn destroy(self: *Self, allocator: std.mem.Allocator) void { } pub fn begin(self: *Self, info: *const vk.CommandBufferBeginInfo) VkError!void { - if (!self.pool.flags.reset_command_buffer_bit) { - self.transitionState(.Recording, &.{.Initial}) catch return VkError.ValidationFailed; - } else { - try self.reset(.{}); - self.transitionState(.Recording, &.{ .Initial, .Recording, .Executable, .Invalid }) catch return VkError.ValidationFailed; + const implicitly_reset = self.state == .Executable; + + self.transitionState(.Recording, &.{ .Initial, .Executable }) catch return VkError.ValidationFailed; + if (implicitly_reset) { + try self.dispatch_table.reset(self, .{}); + self.begin_info = null; } + try self.dispatch_table.begin(self, info); self.begin_info = info.*; } diff --git a/src/vulkan/DescriptorSetLayout.zig b/src/vulkan/DescriptorSetLayout.zig index a565c5a..a70ecbd 100644 --- a/src/vulkan/DescriptorSetLayout.zig +++ b/src/vulkan/DescriptorSetLayout.zig @@ -3,6 +3,7 @@ const vk = @import("vulkan"); const VulkanAllocator = @import("VulkanAllocator.zig"); +const NonDispatchable = @import("NonDispatchable.zig").NonDispatchable; const VkError = @import("error_set.zig").VkError; const Device = @import("Device.zig"); const Sampler = @import("Sampler.zig"); @@ -16,7 +17,7 @@ const BindingLayout = struct { array_size: usize, /// This slice points to an array located after the binding layouts array - immutable_samplers: []*const Sampler, + immutable_samplers: []const *const Sampler, driver_data: *anyopaque, }; @@ -68,8 +69,21 @@ pub fn init(device: *Device, allocator: std.mem.Allocator, info: *const vk.Descr const bindings = local_allocator.alloc(BindingLayout, binding_count) catch return VkError.OutOfHostMemory; const immutable_samplers = local_allocator.alloc(*const Sampler, immutable_samplers_count) catch return VkError.OutOfHostMemory; + var immutable_samplers_offset: usize = 0; + + for (bindings) |*binding| { + binding.* = .{ + .descriptor_type = .sampler, + .array_size = 0, + .dynamic_index = 0, + .immutable_samplers = &.{}, + .driver_data = undefined, + }; + } var stages: vk.ShaderStageFlags = .{}; + var dynamic_descriptor_count: usize = 0; + var dynamic_offset_count: usize = 0; if (info.p_bindings) |binding_infos| { const sorted_bindings = command_allocator.dupe(vk.DescriptorSetLayoutBinding, binding_infos[0..info.binding_count]) catch return VkError.OutOfHostMemory; @@ -84,11 +98,27 @@ pub fn init(device: *Device, allocator: std.mem.Allocator, info: *const vk.Descr else => binding_info.descriptor_count, }; + const binding_immutable_samplers = if (bindingHasImmutableSamplers(binding_info)) blk: { + const base = binding_info.p_immutable_samplers orelse return VkError.ValidationFailed; + const binding_immutable_samplers = immutable_samplers[immutable_samplers_offset .. immutable_samplers_offset + descriptor_count]; + immutable_samplers_offset += descriptor_count; + for (binding_immutable_samplers, base[0..descriptor_count]) |*dst, src| { + dst.* = try NonDispatchable(Sampler).fromHandleObject(src); + } + break :blk binding_immutable_samplers; + } else &.{}; + + const dynamic_index = dynamic_descriptor_count; + if (bindingHasDynamicOffset(binding_info)) { + dynamic_descriptor_count += descriptor_count; + dynamic_offset_count += descriptor_count; + } + bindings[binding_index] = .{ .descriptor_type = binding_info.descriptor_type, .array_size = descriptor_count, - .dynamic_index = 0, - .immutable_samplers = immutable_samplers[0..], + .dynamic_index = dynamic_index, + .immutable_samplers = binding_immutable_samplers, .driver_data = undefined, }; @@ -100,8 +130,8 @@ pub fn init(device: *Device, allocator: std.mem.Allocator, info: *const vk.Descr .owner = device, .heap = heap, .bindings = bindings, - .dynamic_offset_count = 0, - .dynamic_descriptor_count = 0, + .dynamic_offset_count = dynamic_offset_count, + .dynamic_descriptor_count = dynamic_descriptor_count, .stages = stages, .ref_count = std.atomic.Value(usize).init(1), .vtable = undefined, @@ -119,6 +149,13 @@ inline fn bindingHasImmutableSamplers(binding: vk.DescriptorSetLayoutBinding) bo }; } +inline fn bindingHasDynamicOffset(binding: vk.DescriptorSetLayoutBinding) bool { + return switch (binding.descriptor_type) { + .uniform_buffer_dynamic, .storage_buffer_dynamic => true, + else => false, + }; +} + pub inline fn destroy(self: *Self, allocator: std.mem.Allocator) void { self.unref(allocator); } diff --git a/src/vulkan/Framebuffer.zig b/src/vulkan/Framebuffer.zig index 2591a3c..005b3e0 100644 --- a/src/vulkan/Framebuffer.zig +++ b/src/vulkan/Framebuffer.zig @@ -34,7 +34,7 @@ pub fn init(device: *Device, allocator: std.mem.Allocator, info: *const vk.Frame for (base_attachements, attachments, 0..info.attachment_count) |base_attachment, *attachment, _| { attachment.* = try NonDispatchable(ImageView).fromHandleObject(base_attachment); } - } else { + } else if (info.attachment_count != 0) { return VkError.ValidationFailed; } diff --git a/src/vulkan/RenderPass.zig b/src/vulkan/RenderPass.zig index 0b1354c..5f14c7d 100644 --- a/src/vulkan/RenderPass.zig +++ b/src/vulkan/RenderPass.zig @@ -28,6 +28,7 @@ vtable: *const VTable, pub const VTable = struct { destroy: *const fn (*Self, std.mem.Allocator) void, + getRenderAreaGranularity: *const fn (*Self) vk.Extent2D, }; pub fn init(device: *Device, allocator: std.mem.Allocator, info: *const vk.RenderPassCreateInfo) VkError!Self { @@ -40,7 +41,7 @@ pub fn init(device: *Device, allocator: std.mem.Allocator, info: *const vk.Rende for (base_attachements, attachments, 0..info.attachment_count) |base_attachment, *attachment, _| { attachment.* = base_attachment; } - } else { + } else if (info.attachment_count != 0) { return VkError.ValidationFailed; } @@ -93,3 +94,7 @@ pub fn destroy(self: *Self, allocator: std.mem.Allocator) void { allocator.free(self.subpasses); self.vtable.destroy(self, allocator); } + +pub inline fn getRenderAreaGranularity(self: *Self) vk.Extent2D { + return self.vtable.getRenderAreaGranularity(self); +} diff --git a/src/vulkan/Sampler.zig b/src/vulkan/Sampler.zig index a7b51cc..690f12e 100644 --- a/src/vulkan/Sampler.zig +++ b/src/vulkan/Sampler.zig @@ -16,6 +16,7 @@ min_filter: vk.Filter, address_mode_u: vk.SamplerAddressMode, address_mode_v: vk.SamplerAddressMode, address_mode_w: vk.SamplerAddressMode, +border_color: vk.BorderColor, vtable: *const VTable, @@ -32,6 +33,7 @@ pub fn init(device: *Device, allocator: std.mem.Allocator, info: *const vk.Sampl .address_mode_u = info.address_mode_u, .address_mode_v = info.address_mode_v, .address_mode_w = info.address_mode_w, + .border_color = info.border_color, .vtable = undefined, }; } diff --git a/src/vulkan/format.zig b/src/vulkan/format.zig index e957bb3..b0e411e 100644 --- a/src/vulkan/format.zig +++ b/src/vulkan/format.zig @@ -40,6 +40,10 @@ pub inline fn texelSize(format: vk.Format) usize { return lib.c.vkuFormatTexelBlockSize(@intCast(@intFromEnum(format))); } +pub inline fn componentCount(format: vk.Format) usize { + return @intCast(lib.c.vkuFormatComponentCount(@intCast(@intFromEnum(format)))); +} + pub fn supportsColorAttachemendBlend(format: vk.Format) bool { return switch (format) { // Vulkan 1.1 mandatory diff --git a/src/vulkan/lib_vulkan.zig b/src/vulkan/lib_vulkan.zig index 4fa74a0..8659d3a 100644 --- a/src/vulkan/lib_vulkan.zig +++ b/src/vulkan/lib_vulkan.zig @@ -1470,11 +1470,9 @@ pub export fn apeGetRenderAreaGranularity(p_device: vk.Device, p_pass: vk.Render defer entryPointEndLogTrace(); Dispatchable(Device).checkHandleValidity(p_device) catch |err| return errorLogger(err); + const pass = NonDispatchable(RenderPass).fromHandleObject(p_pass) catch |err| return errorLogger(err); - notImplementedWarning(); - - _ = p_pass; - _ = granularity; + granularity.* = pass.getRenderAreaGranularity(); } pub export fn apeInvalidateMappedMemoryRanges(p_device: vk.Device, count: u32, p_ranges: [*]const vk.MappedMemoryRange) callconv(vk.vulkan_call_conv) vk.Result {