diff --git a/build.zig.zon b/build.zig.zon index 30cf03a..1a97d42 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -22,12 +22,12 @@ .hash = "zmath-0.11.0-dev-wjwivdMsAwD-xaLj76YHUq3t9JDH-X16xuMTmnDzqbu2", }, .cts_bin = .{ - .url = "git+https://git.kbz8.me/kbz_8/Vulkan-CTS-bin.git#a5f787d80f14f136e3cb3e1185c35e298846c1d7", - .hash = "N-V-__8AAMpOQxkHCKTw9i-NwmmQ3ks1ndFDXcVLlic4KjK3", + .url = "git+https://git.kbz8.me/kbz_8/Vulkan-CTS-bin.git#b316a134bc0aa7ac21d9c57a1df588809824dcdc", + .hash = "N-V-__8AAF9uOh0I4P_99za7N822J3JwsDaqONrFVrcEQo59", }, .SPIRV_Interpreter = .{ - .url = "git+https://git.kbz8.me/kbz_8/SPIRV-Interpreter#fb6e5beefffe3608173bdda62dc0a922f02ba447", - .hash = "SPIRV_Interpreter-0.0.1-ajmpn3cFBgALga_tgLo12tFEVRlR8m6fIjFIv41KVCcn", + .url = "git+https://git.kbz8.me/kbz_8/SPIRV-Interpreter#7c6da62e3cc6e58260527abef6e2e161e45d7f84", + .hash = "SPIRV_Interpreter-0.0.1-ajmpnzALBgCxqYfFMsc_moezDHsaeKyImzHXuJ7dYffX", .lazy = true, }, //.SPIRV_Interpreter = .{ diff --git a/src/soft/SoftPipeline.zig b/src/soft/SoftPipeline.zig index af3bbeb..311997b 100644 --- a/src/soft/SoftPipeline.zig +++ b/src/soft/SoftPipeline.zig @@ -102,6 +102,7 @@ pub fn createCompute(device: *base.Device, allocator: std.mem.Allocator, cache: .writeImageFloat4 = writeImageFloat4, .writeImageInt4 = writeImageInt4, .sampleImageFloat4 = sampleImageFloat4, + .sampleImageInt4 = sampleImageInt4, .queryImageSize = queryImageSize, }, ) catch |err| { @@ -188,6 +189,7 @@ pub fn createGraphics(device: *base.Device, allocator: std.mem.Allocator, cache: .writeImageFloat4 = writeImageFloat4, .writeImageInt4 = writeImageInt4, .sampleImageFloat4 = sampleImageFloat4, + .sampleImageInt4 = sampleImageInt4, .queryImageSize = queryImageSize, }, ) catch |err| { @@ -378,7 +380,7 @@ fn writeImageInt4(context: *anyopaque, dim: spv.SpvDim, x: i32, y: i32, z: i32, } } -fn sampleImageFloat4(context: *anyopaque, context2: *anyopaque, dim: spv.SpvDim, x: f32, y: f32, z: f32) SpvRuntimeError!spv.Runtime.Vec4(f32) { +fn sampleImageFloat4(context: *anyopaque, context2: *anyopaque, dim: spv.SpvDim, x: f32, y: f32, z: f32, lod: ?f32) SpvRuntimeError!spv.Runtime.Vec4(f32) { var pixel = zm.f32x4s(0.0); if (dim == .Buffer) { @@ -391,7 +393,31 @@ fn sampleImageFloat4(context: *anyopaque, context2: *anyopaque, dim: spv.SpvDim, const image: *SoftImage = @alignCast(@fieldParentPtr("interface", image_view.interface.image)); const sampler: *SoftSampler = @ptrCast(@alignCast(context2)); - pixel = SoftSampler.sampleImageFloat4(image, image_view, sampler, dim, x, y, z) catch return SpvRuntimeError.Unknown; + pixel = SoftSampler.sampleImageFloat4(image, image_view, sampler, dim, x, y, z, lod) catch return SpvRuntimeError.Unknown; + } + + return .{ + .x = pixel[0], + .y = pixel[1], + .z = pixel[2], + .w = pixel[3], + }; +} + +fn sampleImageInt4(context: *anyopaque, context2: *anyopaque, dim: spv.SpvDim, x: f32, y: f32, z: f32, lod: ?f32) SpvRuntimeError!spv.Runtime.Vec4(u32) { + var pixel = @Vector(4, u32){ 0, 0, 0, 0 }; + + if (dim == .Buffer) { + const buffer_view: *SoftBufferView = @ptrCast(@alignCast(context)); + const buffer: *SoftBuffer = @alignCast(@fieldParentPtr("interface", buffer_view.interface.buffer)); + const map = buffer.mapAsSliceWithOffset(u8, buffer_view.interface.offset, buffer_view.interface.range) catch return SpvRuntimeError.Unknown; + _ = map; + } else { + const image_view: *SoftImageView = @ptrCast(@alignCast(context)); + const image: *SoftImage = @alignCast(@fieldParentPtr("interface", image_view.interface.image)); + + const sampler: *SoftSampler = @ptrCast(@alignCast(context2)); + pixel = SoftSampler.sampleImageInt4(image, image_view, sampler, dim, x, y, z, lod) catch return SpvRuntimeError.Unknown; } return .{ diff --git a/src/soft/SoftSampler.zig b/src/soft/SoftSampler.zig index 1a1fc34..d3d4a89 100644 --- a/src/soft/SoftSampler.zig +++ b/src/soft/SoftSampler.zig @@ -3,10 +3,12 @@ const vk = @import("vulkan"); const base = @import("base"); const spv = @import("spv"); const zm = base.zm; +const blitter = @import("device/blitter.zig"); const VkError = base.VkError; const Device = base.Device; const F32x4 = zm.F32x4; +const U32x4 = blitter.U32x4; const SoftImage = @import("SoftImage.zig"); const SoftImageView = @import("SoftImageView.zig"); @@ -27,6 +29,7 @@ const ImageSamplingContext = struct { sampler: *Self, dim: spv.SpvDim, coord: CubeCoordinate, + mip_level: u32, }; interface: Interface, @@ -138,12 +141,52 @@ fn sampleAddressOrBorder(coord: i32, extent: u32, mode: vk.SamplerAddressMode) ? }; } -fn samplerBorderColor(sampler: *Self) F32x4 { - return switch (sampler.interface.border_color) { +fn samplerBorderColor(sampler: *Self, format: vk.Format) F32x4 { + var color: F32x4 = 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 }, }; + + switch (base.format.componentCount(format)) { + 1 => { + color[1] = 0.0; + color[2] = 0.0; + color[3] = 1.0; + }, + 2 => { + color[2] = 0.0; + color[3] = 1.0; + }, + 3 => color[3] = 1.0, + else => {}, + } + + return color; +} + +fn samplerBorderColorInt(sampler: *Self, format: vk.Format) U32x4 { + var color: U32x4 = switch (sampler.interface.border_color) { + .float_opaque_white, .int_opaque_white => .{ 1, 1, 1, 1 }, + .float_opaque_black, .int_opaque_black => .{ 0, 0, 0, 1 }, + else => .{ 0, 0, 0, 0 }, + }; + + switch (base.format.componentCount(format)) { + 1 => { + color[1] = 0; + color[2] = 0; + color[3] = 1; + }, + 2 => { + color[2] = 0; + color[3] = 1; + }, + 3 => color[3] = 1, + else => {}, + } + + return color; } fn viewLayerCount(image: *SoftImage, range: vk.ImageSubresourceRange) u32 { @@ -153,23 +196,100 @@ fn viewLayerCount(image: *SoftImage, range: vk.ImageSubresourceRange) u32 { range.layer_count; } +fn viewMipCount(image: *SoftImage, range: vk.ImageSubresourceRange) u32 { + return if (range.level_count == vk.REMAINING_MIP_LEVELS) + image.interface.mip_levels - range.base_mip_level + else + range.level_count; +} + +fn sampleMipLevel(image: *SoftImage, image_view: *SoftImageView, sampler: *Self, lod: ?f32) u32 { + const range = image_view.interface.subresource_range; + const mip_count = viewMipCount(image, range); + if (mip_count <= 1) + return range.base_mip_level; + + const requested_lod = if (lod) |explicit_lod| + explicit_lod + sampler.interface.mip_lod_bias + else + sampler.interface.min_lod; + const clamped_lod = std.math.clamp(requested_lod, sampler.interface.min_lod, sampler.interface.max_lod); + const level_float = switch (sampler.interface.mipmap_mode) { + .nearest => @round(clamped_lod), + else => @floor(clamped_lod), + }; + const level: u32 = @intFromFloat(std.math.clamp(level_float, 0.0, @as(f32, @floatFromInt(mip_count - 1)))); + return range.base_mip_level + level; +} + fn sampleArrayLayer(coord: f32, layer_count: u32) u32 { const layer_coord: i32 = @intFromFloat(@floor(coord + 0.5)); return @intCast(sampleAddress(layer_coord, layer_count, .clamp_to_edge)); } +fn sampledFormat(image_view: *SoftImageView) vk.Format { + const range = image_view.interface.subresource_range; + return base.format.fromAspect(image_view.interface.format, range.aspect_mask); +} + +fn swizzleFloatComponent(color: F32x4, swizzle: vk.ComponentSwizzle, comptime identity_index: usize) f32 { + return switch (swizzle) { + .identity => color[identity_index], + .zero => 0.0, + .one => 1.0, + .r => color[0], + .g => color[1], + .b => color[2], + .a => color[3], + else => color[identity_index], + }; +} + +fn swizzleFloat4(color: F32x4, components: vk.ComponentMapping) F32x4 { + return .{ + swizzleFloatComponent(color, components.r, 0), + swizzleFloatComponent(color, components.g, 1), + swizzleFloatComponent(color, components.b, 2), + swizzleFloatComponent(color, components.a, 3), + }; +} + +fn swizzleIntComponent(color: U32x4, swizzle: vk.ComponentSwizzle, comptime identity_index: usize) u32 { + return switch (swizzle) { + .identity => color[identity_index], + .zero => 0, + .one => 1, + .r => color[0], + .g => color[1], + .b => color[2], + .a => color[3], + else => color[identity_index], + }; +} + +fn swizzleInt4(color: U32x4, components: vk.ComponentMapping) U32x4 { + return .{ + swizzleIntComponent(color, components.r, 0), + swizzleIntComponent(color, components.g, 1), + swizzleIntComponent(color, components.b, 2), + swizzleIntComponent(color, components.a, 3), + }; +} + fn readSampledFloat4( image: *SoftImage, image_view: *SoftImageView, sampler: *Self, dim: spv.SpvDim, coord: CubeCoordinate, + mip_level: u32, ix: i32, iy: i32, iz: i32, ) VkError!F32x4 { const range = image_view.interface.subresource_range; - const extent = image.getMipLevelExtent(range.base_mip_level); + const format = sampledFormat(image_view); + const extent = image.getMipLevelExtent(mip_level); const width_f: f32 = @floatFromInt(extent.width); const height_f: f32 = @floatFromInt(extent.height); @@ -186,7 +306,7 @@ fn readSampledFloat4( .@"1d_array" => .{ 0, range.base_array_layer + sampleArrayLayer(coord.v, viewLayerCount(image, range)) }, .@"2d_array" => .{ 0, range.base_array_layer + sampleArrayLayer(coord.w, viewLayerCount(image, range)) }, .cube_array => .{ 0, range.base_array_layer + sampleArrayLayer(coord.w, @divTrunc(viewLayerCount(image, range), 6)) * 6 + texel.face }, - .@"3d" => .{ sampleAddressOrBorder(iz, extent.depth, sampler.interface.address_mode_w) orelse return samplerBorderColor(sampler), range.base_array_layer }, + .@"3d" => .{ sampleAddressOrBorder(iz, extent.depth, sampler.interface.address_mode_w) orelse return samplerBorderColor(sampler, format), range.base_array_layer }, .cube => .{ 0, range.base_array_layer + texel.face }, else => .{ 0, range.base_array_layer }, }; @@ -194,13 +314,13 @@ fn readSampledFloat4( 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); + sampleAddressOrBorder(ix, extent.width, sampler.interface.address_mode_u) orelse return samplerBorderColor(sampler, format); 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); + sampleAddressOrBorder(iy, extent.height, sampler.interface.address_mode_v) orelse return samplerBorderColor(sampler, format); - return image.readFloat4( + const color = try image.readFloat4( .{ .x = sx, .y = sy, @@ -208,28 +328,91 @@ fn readSampledFloat4( }, .{ .aspect_mask = range.aspect_mask, - .mip_level = range.base_mip_level, + .mip_level = mip_level, .array_layer = layer, }, - image_view.interface.format, + format, ); + return if (base.format.isSrgb(format)) zm.srgbToRgb(color) else color; } fn readSampledFloat4At(context: *const ImageSamplingContext, ix: i32, iy: i32, iz: i32) VkError!F32x4 { - return readSampledFloat4( + const color = try readSampledFloat4( context.image, context.image_view, context.sampler, context.dim, context.coord, + context.mip_level, ix, iy, iz, ); + return swizzleFloat4(color, context.image_view.interface.components); } -pub fn sampleImageFloat4(image: *SoftImage, image_view: *SoftImageView, sampler: *Self, dim: spv.SpvDim, x: f32, y: f32, z: f32) VkError!F32x4 { - const extent = image.getMipLevelExtent(image_view.interface.subresource_range.base_mip_level); +fn readSampledInt4( + image: *SoftImage, + image_view: *SoftImageView, + sampler: *Self, + dim: spv.SpvDim, + coord: CubeCoordinate, + mip_level: u32, + ix: i32, + iy: i32, + iz: i32, +) VkError!U32x4 { + const range = image_view.interface.subresource_range; + const format = sampledFormat(image_view); + const extent = image.getMipLevelExtent(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( + coord.face, + (@as(f32, @floatFromInt(ix)) + 0.5) / width_f, + (@as(f32, @floatFromInt(iy)) + 0.5) / height_f, + ); + 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 + sampleArrayLayer(coord.v, viewLayerCount(image, range)) }, + .@"2d_array" => .{ 0, range.base_array_layer + sampleArrayLayer(coord.w, viewLayerCount(image, range)) }, + .cube_array => .{ 0, range.base_array_layer + sampleArrayLayer(coord.w, @divTrunc(viewLayerCount(image, range), 6)) * 6 + texel.face }, + .@"3d" => .{ sampleAddressOrBorder(iz, extent.depth, sampler.interface.address_mode_w) orelse return samplerBorderColorInt(sampler, format), 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 samplerBorderColorInt(sampler, format); + 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 samplerBorderColorInt(sampler, format); + + return image.readInt4( + .{ + .x = sx, + .y = sy, + .z = z, + }, + .{ + .aspect_mask = range.aspect_mask, + .mip_level = mip_level, + .array_layer = layer, + }, + format, + ); +} + +pub fn sampleImageFloat4(image: *SoftImage, image_view: *SoftImageView, sampler: *Self, dim: spv.SpvDim, x: f32, y: f32, z: f32, lod: ?f32) VkError!F32x4 { + const mip_level = sampleMipLevel(image, image_view, sampler, lod); + const extent = image.getMipLevelExtent(mip_level); const coord: CubeCoordinate = switch (image_view.interface.view_type) { .@"1d_array" => .{ .u = x, @@ -255,21 +438,25 @@ pub fn sampleImageFloat4(image: *SoftImage, image_view: *SoftImageView, sampler: .face = 0, }, }; + const scale_u: f32 = if (sampler.interface.unnormalized_coordinates == .true) 1.0 else @floatFromInt(extent.width); + 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 context: ImageSamplingContext = .{ .image = image, .image_view = image_view, .sampler = sampler, .dim = dim, .coord = coord, + .mip_level = mip_level, }; return sampleFloat4( *const ImageSamplingContext, &context, zm.f32x4( - coord.u * @as(f32, @floatFromInt(extent.width)), - coord.v * @as(f32, @floatFromInt(extent.height)), - coord.w * @as(f32, @floatFromInt(extent.depth)), + coord.u * scale_u, + coord.v * scale_v, + coord.w * scale_w, 0.0, ), switch (sampler.interface.mag_filter) { @@ -281,6 +468,52 @@ pub fn sampleImageFloat4(image: *SoftImage, image_view: *SoftImageView, sampler: ); } +pub fn sampleImageInt4(image: *SoftImage, image_view: *SoftImageView, sampler: *Self, dim: spv.SpvDim, x: f32, y: f32, z: f32, lod: ?f32) VkError!U32x4 { + const mip_level = sampleMipLevel(image, image_view, sampler, lod); + const extent = image.getMipLevelExtent(mip_level); + const coord: CubeCoordinate = switch (image_view.interface.view_type) { + .@"1d_array" => .{ + .u = x, + .v = y, + .face = 0, + }, + .@"1d" => .{ + .u = x, + .v = 0.0, + .face = 0, + }, + .@"2d_array" => .{ + .u = x, + .v = y, + .w = z, + .face = 0, + }, + .cube, .cube_array => resolveCubeCoordinate(x, y, z), + else => .{ + .u = x, + .v = y, + .w = z, + .face = 0, + }, + }; + const scale_u: f32 = if (sampler.interface.unnormalized_coordinates == .true) 1.0 else @floatFromInt(extent.width); + 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 color = try readSampledInt4( + image, + image_view, + sampler, + dim, + coord, + mip_level, + @intFromFloat(@floor(coord.u * scale_u)), + @intFromFloat(@floor(coord.v * scale_v)), + @intFromFloat(@floor(coord.w * scale_w)), + ); + return swizzleInt4(color, image_view.interface.components); +} + pub fn sampleFloat4( comptime Context: type, context: Context, @@ -292,9 +525,9 @@ pub fn sampleFloat4( if (filter == .nearest) { return read( context, - @intFromFloat(pos[0]), - @intFromFloat(pos[1]), - @intFromFloat(pos[2]), + @intFromFloat(@floor(pos[0])), + @intFromFloat(@floor(pos[1])), + @intFromFloat(@floor(pos[2])), ); } diff --git a/src/soft/device/Renderer.zig b/src/soft/device/Renderer.zig index 3f0ef24..8102125 100644 --- a/src/soft/device/Renderer.zig +++ b/src/soft/device/Renderer.zig @@ -53,6 +53,7 @@ pub const DynamicState = struct { }; pub const Vertex = struct { + primitive_restart: bool, position: F32x4, outputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS][4]?struct { interpolation_type: enum { smooth, flat, noperspective }, @@ -100,6 +101,7 @@ pub const DrawCall = struct { }; for (self.vertices) |*vertex| { + vertex.primitive_restart = false; for (&vertex.outputs) |*location| { @memset(location, null); } @@ -157,19 +159,19 @@ pub fn init(device: *SoftDevice, state: *PipelineState, active_occlusion_queries pub fn draw(self: *Self, vertex_count: usize, instance_count: usize, first_vertex: usize, first_instance: usize) VkError!void { var bounded_allocator: BoundedAllocator = .init(self.device.device_allocator.allocator(), @"1GiB"); - try self.drawCall(&bounded_allocator, vertex_count, instance_count, first_vertex, first_instance, null); + try self.drawCall(&bounded_allocator, vertex_count, instance_count, first_vertex, first_instance, null, null); } pub fn drawIndexed(self: *Self, index_count: usize, instance_count: usize, first_index: usize, first_instance: usize, vertex_offset: i32) VkError!void { var bounded_allocator: BoundedAllocator = .init(self.device.device_allocator.allocator(), @"1GiB"); const allocator = bounded_allocator.allocator(); - const indices = try self.readIndexBuffer(allocator, index_count, first_index, vertex_offset); + const indexed_draw = try self.readIndexBuffer(allocator, index_count, first_index, vertex_offset); - try self.drawCall(&bounded_allocator, index_count, instance_count, 0, first_instance, indices); + try self.drawCall(&bounded_allocator, index_count, instance_count, 0, first_instance, indexed_draw.indices, indexed_draw.primitive_restart); } -fn drawCall(self: *Self, bounded_allocator: *BoundedAllocator, vertex_count: usize, instance_count: usize, first_vertex: usize, first_instance: usize, indices: ?[]const i32) VkError!void { +fn drawCall(self: *Self, bounded_allocator: *BoundedAllocator, vertex_count: usize, instance_count: usize, first_vertex: usize, first_instance: usize, indices: ?[]const u32, primitive_restart: ?[]const bool) VkError!void { const io = self.device.interface.io(); const allocator = bounded_allocator.allocator(); @@ -215,7 +217,7 @@ fn drawCall(self: *Self, bounded_allocator: *BoundedAllocator, vertex_count: usi } } - self.vertexShaderStage(allocator, &draw_call, vertex_count, instance_count, first_vertex, first_instance, indices) catch |err| { + self.vertexShaderStage(allocator, &draw_call, vertex_count, instance_count, first_vertex, first_instance, indices, primitive_restart) catch |err| { std.log.scoped(.@"Vertex stage").err("catched a '{s}'", .{@errorName(err)}); if (comptime base.config.logs == .verbose) { if (@errorReturnTrace()) |trace| { @@ -232,7 +234,7 @@ fn drawCall(self: *Self, bounded_allocator: *BoundedAllocator, vertex_count: usi try rasterizer.processThenFragmentStage(self, allocator, &draw_call); } -fn vertexShaderStage(self: *Self, allocator: std.mem.Allocator, draw_call: *DrawCall, vertex_count: usize, instance_count: usize, first_vertex: usize, first_instance: usize, indices: ?[]const i32) !void { +fn vertexShaderStage(self: *Self, allocator: std.mem.Allocator, draw_call: *DrawCall, vertex_count: usize, instance_count: usize, first_vertex: usize, first_instance: usize, indices: ?[]const u32, primitive_restart: ?[]const bool) !void { const pipeline = self.state.pipeline orelse return; const batch_size = (pipeline.stages.getPtr(.vertex) orelse return).runtimes.len; @@ -248,6 +250,7 @@ fn vertexShaderStage(self: *Self, allocator: std.mem.Allocator, draw_call: *Draw .first_vertex = first_vertex, .first_instance = first_instance, .indices = indices, + .primitive_restart = primitive_restart, .instance_index = instance_index, .draw_call = draw_call, }; @@ -258,7 +261,12 @@ fn vertexShaderStage(self: *Self, allocator: std.mem.Allocator, draw_call: *Draw wg.await(self.device.interface.io()) catch return VkError.DeviceLost; } -fn readIndexBuffer(self: *Self, allocator: std.mem.Allocator, index_count: usize, first_index: usize, vertex_offset: i32) VkError![]i32 { +const IndexedDrawData = struct { + indices: []u32, + primitive_restart: ?[]bool, +}; + +fn readIndexBuffer(self: *Self, allocator: std.mem.Allocator, index_count: usize, first_index: usize, vertex_offset: i32) VkError!IndexedDrawData { const index_buffer = self.state.data.graphics.index_buffer; const buffer = index_buffer.buffer; const buffer_memory = if (buffer.interface.memory) |memory| memory else return VkError.InvalidDeviceMemoryDrv; @@ -271,7 +279,11 @@ fn readIndexBuffer(self: *Self, allocator: std.mem.Allocator, index_count: usize const byte_size = index_count * index_size; const index_memory: []const u8 = try buffer_memory.map(byte_offset, byte_size); - const indices = allocator.alloc(i32, index_count) catch return VkError.OutOfDeviceMemory; + const indices = allocator.alloc(u32, index_count) catch return VkError.OutOfDeviceMemory; + const restart_enabled = (self.state.pipeline orelse return VkError.InvalidPipelineDrv).interface.mode.graphics.input_assembly.primitive_restart_enable == .true; + const restart_index = primitiveRestartIndex(index_buffer.index_type); + const primitive_restart = if (restart_enabled) allocator.alloc(bool, index_count) catch return VkError.OutOfDeviceMemory else null; + for (indices, 0..) |*index, i| { const offset = i * index_size; const raw_index: u32 = switch (index_size) { @@ -280,10 +292,21 @@ fn readIndexBuffer(self: *Self, allocator: std.mem.Allocator, index_count: usize 4 => @intCast(std.mem.readInt(u32, index_memory[offset..][0..4], .little)), else => unreachable, }; - index.* = vertex_offset + @as(i32, @intCast(raw_index)); + if (primitive_restart) |restart| { + restart[i] = raw_index == restart_index; + if (restart[i]) { + index.* = 0; + continue; + } + } + const shifted = @as(i64, raw_index) + @as(i64, vertex_offset); + index.* = @as(u32, @truncate(@as(u64, @bitCast(shifted)))); } - return indices; + return .{ + .indices = indices, + .primitive_restart = primitive_restart, + }; } fn indexTypeSize(index_type: vk.IndexType) ?usize { @@ -295,6 +318,15 @@ fn indexTypeSize(index_type: vk.IndexType) ?usize { }; } +fn primitiveRestartIndex(index_type: vk.IndexType) u32 { + return switch (index_type) { + .uint8 => std.math.maxInt(u8), + .uint16 => std.math.maxInt(u16), + .uint32 => std.math.maxInt(u32), + else => unreachable, + }; +} + fn resolveViewport(self: *Self, viewport_index: usize) VkError!vk.Viewport { const pipeline_data = &(self.state.pipeline orelse return VkError.InvalidPipelineDrv).interface.mode.graphics; diff --git a/src/soft/device/clip.zig b/src/soft/device/clip.zig index 5702f7f..7224806 100644 --- a/src/soft/device/clip.zig +++ b/src/soft/device/clip.zig @@ -169,6 +169,7 @@ fn interpolateBlob(allocator: std.mem.Allocator, a: []const u8, b: []const u8, s fn interpolateVertexForClipping(allocator: std.mem.Allocator, a: *const Vertex, b: *const Vertex, t: f32) VkError!Vertex { var result: Vertex = .{ + .primitive_restart = false, .position = a.position + ((b.position - a.position) * zm.f32x4s(t)), .outputs = undefined, }; diff --git a/src/soft/device/rasterizer.zig b/src/soft/device/rasterizer.zig index 29a0123..7041782 100644 --- a/src/soft/device/rasterizer.zig +++ b/src/soft/device/rasterizer.zig @@ -121,6 +121,9 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato switch (topology) { .point_list => for (draw_call.vertices) |*vertex| { + if (vertex.primitive_restart) + continue; + try clipTransformAndRasterizePoint( allocator, draw_call, @@ -148,56 +151,71 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato if (stencil_attachment_access) |*access| access else null, ); }, - .triangle_fan => if (draw_call.vertices.len >= 3) { - const v0 = &draw_call.vertices[0]; - for (1..(draw_call.vertices.len - 1)) |vertex_index| { - const v1 = &draw_call.vertices[vertex_index]; - const v2 = &draw_call.vertices[vertex_index + 1]; + .triangle_fan => { + var segment_start = firstNonRestart(draw_call, 0); + while (segment_start < draw_call.vertices.len) { + const segment_end = nextRestart(draw_call, segment_start); + if (segment_end - segment_start >= 3) { + const v0 = &draw_call.vertices[segment_start]; + for ((segment_start + 1)..(segment_end - 1)) |vertex_index| { + const v1 = &draw_call.vertices[vertex_index]; + const v2 = &draw_call.vertices[vertex_index + 1]; - try clipTransformAndRasterizeTriangle( - renderer, - allocator, - draw_call, - v0, - v1, - v2, - color_attachment_access, - if (depth_attachment_access) |*access| access else null, - if (stencil_attachment_access) |*access| access else null, - ); + try clipTransformAndRasterizeTriangle( + renderer, + allocator, + draw_call, + v0, + v1, + v2, + color_attachment_access, + if (depth_attachment_access) |*access| access else null, + if (stencil_attachment_access) |*access| access else null, + ); + } + } + segment_start = firstNonRestart(draw_call, segment_end + 1); } }, - .triangle_strip => if (draw_call.vertices.len >= 3) { - for (0..(draw_call.vertices.len - 2)) |vertex_index| { - const v0 = &draw_call.vertices[vertex_index + 0]; - const v1 = &draw_call.vertices[vertex_index + 1]; - const v2 = &draw_call.vertices[vertex_index + 2]; + .triangle_strip => { + var segment_start = firstNonRestart(draw_call, 0); + while (segment_start < draw_call.vertices.len) { + const segment_end = nextRestart(draw_call, segment_start); + if (segment_end - segment_start >= 3) { + for (segment_start..(segment_end - 2)) |vertex_index| { + const local_index = vertex_index - segment_start; + const v0 = &draw_call.vertices[vertex_index + 0]; + const v1 = &draw_call.vertices[vertex_index + 1]; + const v2 = &draw_call.vertices[vertex_index + 2]; - if ((vertex_index & 1) == 0) { - try clipTransformAndRasterizeTriangle( - renderer, - allocator, - draw_call, - v0, - v1, - v2, - color_attachment_access, - if (depth_attachment_access) |*access| access else null, - if (stencil_attachment_access) |*access| access else null, - ); - } else { - try clipTransformAndRasterizeTriangle( - renderer, - allocator, - draw_call, - v1, - v0, - v2, - color_attachment_access, - if (depth_attachment_access) |*access| access else null, - if (stencil_attachment_access) |*access| access else null, - ); + if ((local_index & 1) == 0) { + try clipTransformAndRasterizeTriangle( + renderer, + allocator, + draw_call, + v0, + v1, + v2, + color_attachment_access, + if (depth_attachment_access) |*access| access else null, + if (stencil_attachment_access) |*access| access else null, + ); + } else { + try clipTransformAndRasterizeTriangle( + renderer, + allocator, + draw_call, + v1, + v0, + v2, + color_attachment_access, + if (depth_attachment_access) |*access| access else null, + if (stencil_attachment_access) |*access| access else null, + ); + } + } } + segment_start = firstNonRestart(draw_call, segment_end + 1); } }, .line_list => for (0..@divTrunc(draw_call.vertices.len, 2)) |line_index| { @@ -215,20 +233,27 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato if (stencil_attachment_access) |*access| access else null, ); }, - .line_strip => if (draw_call.vertices.len >= 2) { - for (0..(draw_call.vertices.len - 1)) |vertex_index| { - const v0 = &draw_call.vertices[vertex_index + 0]; - const v1 = &draw_call.vertices[vertex_index + 1]; + .line_strip => { + var segment_start = firstNonRestart(draw_call, 0); + while (segment_start < draw_call.vertices.len) { + const segment_end = nextRestart(draw_call, segment_start); + if (segment_end - segment_start >= 2) { + for (segment_start..(segment_end - 1)) |vertex_index| { + const v0 = &draw_call.vertices[vertex_index + 0]; + const v1 = &draw_call.vertices[vertex_index + 1]; - try clipTransformAndRasterizeLine( - allocator, - draw_call, - v0, - v1, - color_attachment_access, - if (depth_attachment_access) |*access| access else null, - if (stencil_attachment_access) |*access| access else null, - ); + try clipTransformAndRasterizeLine( + allocator, + draw_call, + v0, + v1, + color_attachment_access, + if (depth_attachment_access) |*access| access else null, + if (stencil_attachment_access) |*access| access else null, + ); + } + } + segment_start = firstNonRestart(draw_call, segment_end + 1); } }, else => base.unsupported("primitive topology {any}", .{topology}), @@ -237,6 +262,18 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato draw_call.rasterizer_wait_group.await(io) catch return VkError.DeviceLost; } +fn firstNonRestart(draw_call: *const DrawCall, start: usize) usize { + var index = start; + while (index < draw_call.vertices.len and draw_call.vertices[index].primitive_restart) : (index += 1) {} + return index; +} + +fn nextRestart(draw_call: *const DrawCall, start: usize) usize { + var index = start; + while (index < draw_call.vertices.len and !draw_call.vertices[index].primitive_restart) : (index += 1) {} + return index; +} + fn clipTransformAndRasterizePoint( allocator: std.mem.Allocator, draw_call: *DrawCall, @@ -253,10 +290,10 @@ fn clipTransformAndRasterizePoint( 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); + const min_x: i32 = @intFromFloat(@ceil(transformed.position[0] - (point_size / 2.0) - 0.5)); + const max_x: i32 = @intFromFloat(@ceil(transformed.position[0] + (point_size / 2.0) - 0.5) - 1.0); + const min_y: i32 = @intFromFloat(@ceil(transformed.position[1] - (point_size / 2.0) - 0.5)); + const max_y: i32 = @intFromFloat(@ceil(transformed.position[1] + (point_size / 2.0) - 0.5) - 1.0); const pipeline = draw_call.renderer.state.pipeline orelse return; const has_fragment_shader = pipeline.stages.getPtr(.fragment) != null; diff --git a/src/soft/device/rasterizer/common.zig b/src/soft/device/rasterizer/common.zig index 0320ad2..812ee31 100644 --- a/src/soft/device/rasterizer/common.zig +++ b/src/soft/device/rasterizer/common.zig @@ -268,27 +268,34 @@ inline fn blendOp(op: vk.BlendOp, src: F32x4, dst: F32x4) F32x4 { }; } -inline fn blendColor(src: F32x4, dst: F32x4, state: vk.PipelineColorBlendAttachmentState, constants: [4]f32) F32x4 { +inline fn blendColor(src: F32x4, dst: F32x4, state: vk.PipelineColorBlendAttachmentState, constants: [4]f32, format: vk.Format) F32x4 { if (state.blend_enable == .false) return src; - const constant = F32x4{ constants[0], constants[1], constants[2], constants[3] }; - const color_src = if (state.color_blend_op == .min or state.color_blend_op == .max) - src + const min_value = zm.f32x4s(base.format.minElementValue(format)); + const max_value = zm.f32x4s(base.format.maxElementValue(format)); + const clamped_src = if (base.format.isFloat(format)) src else std.math.clamp(src, min_value, max_value); + const constant = if (base.format.isFloat(format)) + F32x4{ constants[0], constants[1], constants[2], constants[3] } else - src * blendFactor(state.src_color_blend_factor, src, dst, constant); + std.math.clamp(F32x4{ constants[0], constants[1], constants[2], constants[3] }, min_value, max_value); + + const color_src = if (state.color_blend_op == .min or state.color_blend_op == .max) + clamped_src + else + clamped_src * blendFactor(state.src_color_blend_factor, clamped_src, dst, constant); const color_dst = if (state.color_blend_op == .min or state.color_blend_op == .max) dst else - dst * blendFactor(state.dst_color_blend_factor, src, dst, constant); + dst * blendFactor(state.dst_color_blend_factor, clamped_src, dst, constant); const alpha_src = if (state.alpha_blend_op == .min or state.alpha_blend_op == .max) - src + clamped_src else - src * blendFactor(state.src_alpha_blend_factor, src, dst, constant); + clamped_src * blendFactor(state.src_alpha_blend_factor, clamped_src, dst, constant); const alpha_dst = if (state.alpha_blend_op == .min or state.alpha_blend_op == .max) dst else - dst * blendFactor(state.dst_alpha_blend_factor, src, dst, constant); + dst * blendFactor(state.dst_alpha_blend_factor, clamped_src, dst, constant); var blended = blendOp(state.color_blend_op, color_src, color_dst); blended[3] = blendOp(state.alpha_blend_op, alpha_src, alpha_dst)[3]; @@ -389,7 +396,7 @@ pub fn writeToTargets( 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); + 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; diff --git a/src/soft/device/vertex_dispatcher.zig b/src/soft/device/vertex_dispatcher.zig index ada504a..02685ae 100644 --- a/src/soft/device/vertex_dispatcher.zig +++ b/src/soft/device/vertex_dispatcher.zig @@ -21,7 +21,8 @@ pub const RunData = struct { vertex_count: usize, first_vertex: usize, first_instance: usize, - indices: ?[]const i32, + indices: ?[]const u32, + primitive_restart: ?[]const bool, instance_index: usize, draw_call: *Renderer.DrawCall, }; @@ -51,13 +52,22 @@ inline fn run(data: RunData) !void { var invocation_index: usize = data.batch_id; while (invocation_index < data.vertex_count) : (invocation_index += data.batch_size) { + const output: *Renderer.Vertex = &data.draw_call.vertices[(data.instance_index * data.vertex_count) + invocation_index]; + if (data.primitive_restart) |primitive_restart| { + if (primitive_restart[invocation_index]) { + output.primitive_restart = true; + continue; + } + } + 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 vertex_index_u32: u32 = if (data.indices) |indices| indices[invocation_index] else @intCast(data.first_vertex + invocation_index); + const vertex_index: usize = vertex_index_u32; const instance_index = data.first_instance + data.instance_index; - setupBuiltins(rt, vertex_index, instance_index) catch |err| switch (err) { + setupBuiltins(rt, vertex_index_u32, instance_index) catch |err| switch (err) { SpvRuntimeError.NotFound => {}, else => return err, }; @@ -88,7 +98,6 @@ inline fn run(data: RunData) !void { else => return err, }; - const output: *Renderer.Vertex = &data.draw_call.vertices[(data.instance_index * data.vertex_count) + invocation_index]; try rt.readBuiltIn(std.mem.asBytes(&output.position), .Position); for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| { @@ -112,8 +121,7 @@ inline fn run(data: RunData) !void { } } -fn setupBuiltins(rt: *spv.Runtime, vertex_index: usize, instance_index: usize) !void { - const vertex_index_u32: u32 = @intCast(vertex_index); +fn setupBuiltins(rt: *spv.Runtime, vertex_index_u32: u32, instance_index: usize) !void { const instance_index_u32: u32 = @intCast(instance_index); try rt.writeBuiltIn(std.mem.asBytes(&vertex_index_u32), .VertexIndex); diff --git a/src/vulkan/Pipeline.zig b/src/vulkan/Pipeline.zig index 950c7a3..b8ab448 100644 --- a/src/vulkan/Pipeline.zig +++ b/src/vulkan/Pipeline.zig @@ -37,6 +37,7 @@ mode: union(enum) { binding_description: ?[]vk.VertexInputBindingDescription, attribute_description: ?[]vk.VertexInputAttributeDescription, topology: vk.PrimitiveTopology, + primitive_restart_enable: vk.Bool32, }, viewport_state: struct { viewports: ?[]vk.Viewport, @@ -122,6 +123,7 @@ pub fn initGraphics(device: *Device, allocator: std.mem.Allocator, cache: ?*Pipe break :blk null; }, .topology = if (info.p_input_assembly_state) |state| state.topology else return VkError.ValidationFailed, + .primitive_restart_enable = if (info.p_input_assembly_state) |state| state.primitive_restart_enable else return VkError.ValidationFailed, }, .viewport_state = .{ .viewports = blk: { @@ -169,7 +171,7 @@ pub fn initGraphics(device: *Device, allocator: std.mem.Allocator, cache: ?*Pipe if (info.p_dynamic_state) |dynamic_state| { if (dynamic_state.p_dynamic_states) |states| { - for (states[0..], 0..dynamic_state.dynamic_state_count) |info_state, _| { + for (states[0..dynamic_state.dynamic_state_count]) |info_state| { switch (info_state) { .viewport => state.viewport = true, .scissor => state.scissor = true, diff --git a/src/vulkan/Sampler.zig b/src/vulkan/Sampler.zig index 690f12e..a83e633 100644 --- a/src/vulkan/Sampler.zig +++ b/src/vulkan/Sampler.zig @@ -13,10 +13,15 @@ pub const ObjectType: vk.ObjectType = .sampler; owner: *Device, mag_filter: vk.Filter, min_filter: vk.Filter, +mipmap_mode: vk.SamplerMipmapMode, address_mode_u: vk.SamplerAddressMode, address_mode_v: vk.SamplerAddressMode, address_mode_w: vk.SamplerAddressMode, +mip_lod_bias: f32, +min_lod: f32, +max_lod: f32, border_color: vk.BorderColor, +unnormalized_coordinates: vk.Bool32, vtable: *const VTable, @@ -30,10 +35,15 @@ pub fn init(device: *Device, allocator: std.mem.Allocator, info: *const vk.Sampl .owner = device, .mag_filter = info.mag_filter, .min_filter = info.min_filter, + .mipmap_mode = info.mipmap_mode, .address_mode_u = info.address_mode_u, .address_mode_v = info.address_mode_v, .address_mode_w = info.address_mode_w, + .mip_lod_bias = info.mip_lod_bias, + .min_lod = info.min_lod, + .max_lod = info.max_lod, .border_color = info.border_color, + .unnormalized_coordinates = info.unnormalized_coordinates, .vtable = undefined, }; }