From 90b93a69e0f3db422c58cdffdbb3f3ac4c1345ad Mon Sep 17 00:00:00 2001 From: Kbz-8 Date: Fri, 5 Jun 2026 18:18:38 +0200 Subject: [PATCH] implementing queries --- README.md | 11 +- build.zig.zon | 4 +- src/soft/SoftCommandBuffer.zig | 153 ++++++++++ src/soft/SoftPipeline.zig | 242 +-------------- src/soft/SoftQueryPool.zig | 1 + src/soft/SoftQueue.zig | 1 + src/soft/SoftSampler.zig | 304 +++++++++++++++++++ src/soft/device/Device.zig | 13 +- src/soft/device/Renderer.zig | 6 +- src/soft/device/blitter.zig | 15 +- src/soft/device/fragment.zig | 9 +- src/soft/device/rasterizer/bresenham.zig | 12 +- src/soft/device/rasterizer/common.zig | 31 +- src/soft/device/rasterizer/edge_function.zig | 12 +- src/soft/device/vertex_dispatcher.zig | 49 +-- src/vulkan/CommandBuffer.zig | 21 ++ src/vulkan/QueryPool.zig | 114 ++++++- src/vulkan/lib_vulkan.zig | 56 +--- 18 files changed, 707 insertions(+), 347 deletions(-) diff --git a/README.md b/README.md index 0c81804..b6a2058 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ vkAllocateMemory | ✅ Implemented vkBeginCommandBuffer | ✅ Implemented vkBindBufferMemory | ✅ Implemented vkBindImageMemory | ✅ Implemented -vkCmdBeginQuery | ⚙️ WIP +vkCmdBeginQuery | ✅ Implemented vkCmdBeginRenderPass | ✅ Implemented vkCmdBindDescriptorSets | ✅ Implemented vkCmdBindIndexBuffer | ✅ Implemented @@ -64,14 +64,14 @@ vkCmdCopyBuffer | ✅ Implemented vkCmdCopyBufferToImage | ✅ Implemented vkCmdCopyImage | ✅ Implemented vkCmdCopyImageToBuffer | ✅ Implemented -vkCmdCopyQueryPoolResults | ⚙️ WIP +vkCmdCopyQueryPoolResults | ✅ Implemented vkCmdDispatch | ✅ Implemented vkCmdDispatchIndirect | ✅ Implemented vkCmdDraw | ✅ Implemented vkCmdDrawIndexed | ✅ Implemented vkCmdDrawIndexedIndirect | ✅ Implemented vkCmdDrawIndirect | ✅ Implemented -vkCmdEndQuery | ⚙️ WIP +vkCmdEndQuery | ✅ Implemented vkCmdEndRenderPass | ✅ Implemented vkCmdExecuteCommands | ✅ Implemented vkCmdFillBuffer | ✅ Implemented @@ -79,7 +79,7 @@ vkCmdNextSubpass | ✅ Implemented vkCmdPipelineBarrier | ✅ Implemented vkCmdPushConstants | ✅ Implemented vkCmdResetEvent | ✅ Implemented -vkCmdResetQueryPool | ⚙️ WIP +vkCmdResetQueryPool | ✅ Implemented vkCmdResolveImage | ✅ Implemented vkCmdSetBlendConstants | ✅ Implemented vkCmdSetDepthBias | ⚙️ WIP @@ -110,7 +110,7 @@ vkCreateImageView | ✅ Implemented vkCreateInstance | ✅ Implemented vkCreatePipelineCache | ⚙️ WIP vkCreatePipelineLayout | ✅ Implemented -vkCreateQueryPool | ⚙️ WIP +vkCreateQueryPool | ✅ Implemented vkCreateRenderPass | ✅ Implemented vkCreateSampler | ✅ Implemented vkCreateSemaphore | ⚙️ WIP @@ -194,6 +194,7 @@ vkResetCommandPool | ✅ Implemented vkResetDescriptorPool | ✅ Implemented vkResetEvent | ✅ Implemented vkResetFences | ✅ Implemented +vkResetQueryPool | ✅ Implemented vkSetEvent | ✅ Implemented vkUnmapMemory | ✅ Implemented vkUpdateDescriptorSets | ✅ Implemented diff --git a/build.zig.zon b/build.zig.zon index 5fed4cc..30cf03a 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#ee5a400010b3a5852606b2f8593bd5c745bf0a26", - .hash = "SPIRV_Interpreter-0.0.1-ajmpn4f_BQB4RtnZdTQZgWOBNcE7-J2Ij3zbV0qI0SKy", + .url = "git+https://git.kbz8.me/kbz_8/SPIRV-Interpreter#fb6e5beefffe3608173bdda62dc0a922f02ba447", + .hash = "SPIRV_Interpreter-0.0.1-ajmpn3cFBgALga_tgLo12tFEVRlR8m6fIjFIv41KVCcn", .lazy = true, }, //.SPIRV_Interpreter = .{ diff --git a/src/soft/SoftCommandBuffer.zig b/src/soft/SoftCommandBuffer.zig index 832474a..87456e3 100644 --- a/src/soft/SoftCommandBuffer.zig +++ b/src/soft/SoftCommandBuffer.zig @@ -45,6 +45,7 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v interface.dispatch_table = &.{ .begin = begin, + .beginQuery = beginQuery, .beginRenderPass = beginRenderPass, .bindDescriptorSets = bindDescriptorSets, .bindPipeline = bindPipeline, @@ -58,6 +59,7 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v .copyBufferToImage = copyBufferToImage, .copyImage = copyImage, .copyImageToBuffer = copyImageToBuffer, + .copyQueryPoolResults = copyQueryPoolResults, .dispatch = dispatch, .dispatchIndirect = dispatchIndirect, .draw = draw, @@ -65,6 +67,7 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v .drawIndexedIndirect = drawIndexedIndirect, .drawIndirect = drawIndirect, .end = end, + .endQuery = endQuery, .endRenderPass = endRenderPass, .executeCommands = executeCommands, .fillBuffer = fillBuffer, @@ -72,6 +75,7 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v .pipelineBarrier = pipelineBarrier, .pushConstants = pushConstants, .reset = reset, + .resetQueryPool = resetQueryPool, .resetEvent = resetEvent, .resolveImage = resolveImage, .setEvent = setEvent, @@ -134,6 +138,116 @@ pub fn reset(interface: *Interface, _: vk.CommandBufferResetFlags) VkError!void // Commands ==================================================================================================== +pub fn beginQuery(interface: *Interface, pool: *base.QueryPool, query: u32, _: vk.QueryControlFlags) VkError!void { + const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); + const allocator = self.command_allocator.allocator(); + + const CommandImpl = struct { + const Impl = @This(); + + pool: *base.QueryPool, + query: u32, + + pub fn execute(context: *anyopaque, device: *ExecutionDevice) VkError!void { + const impl: *Impl = @ptrCast(@alignCast(context)); + try impl.pool.begin(impl.query); + if (impl.pool.query_type == .occlusion) { + for (device.active_occlusion_queries.items) |active| { + if (active.pool == impl.pool and active.query == impl.query) + return; + } + device.active_occlusion_queries.append(device.renderer.device.device_allocator.allocator(), .{ + .pool = impl.pool, + .query = impl.query, + }) catch return VkError.OutOfDeviceMemory; + } + } + }; + + const cmd = allocator.create(CommandImpl) catch return VkError.OutOfHostMemory; + errdefer allocator.destroy(cmd); + cmd.* = .{ + .pool = pool, + .query = query, + }; + self.commands.append(allocator, .{ .ptr = cmd, .vtable = &.{ .execute = CommandImpl.execute } }) catch return VkError.OutOfHostMemory; +} + +pub fn endQuery(interface: *Interface, pool: *base.QueryPool, query: u32) VkError!void { + const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); + const allocator = self.command_allocator.allocator(); + + const CommandImpl = struct { + const Impl = @This(); + + pool: *base.QueryPool, + query: u32, + + pub fn execute(context: *anyopaque, device: *ExecutionDevice) VkError!void { + const impl: *Impl = @ptrCast(@alignCast(context)); + try impl.pool.end(impl.query); + + var i: usize = 0; + while (i < device.active_occlusion_queries.items.len) { + const active = device.active_occlusion_queries.items[i]; + if (active.pool == impl.pool and active.query == impl.query) { + _ = device.active_occlusion_queries.swapRemove(i); + continue; + } + i += 1; + } + } + }; + + const cmd = allocator.create(CommandImpl) catch return VkError.OutOfHostMemory; + errdefer allocator.destroy(cmd); + cmd.* = .{ + .pool = pool, + .query = query, + }; + self.commands.append(allocator, .{ .ptr = cmd, .vtable = &.{ .execute = CommandImpl.execute } }) catch return VkError.OutOfHostMemory; +} + +pub fn resetQueryPool(interface: *Interface, pool: *base.QueryPool, first: u32, count: u32) VkError!void { + const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); + const allocator = self.command_allocator.allocator(); + + const CommandImpl = struct { + const Impl = @This(); + + pool: *base.QueryPool, + first: u32, + count: u32, + + pub fn execute(context: *anyopaque, device: *ExecutionDevice) VkError!void { + const impl: *Impl = @ptrCast(@alignCast(context)); + try impl.pool.reset(impl.first, impl.count); + + var i: usize = 0; + while (i < device.active_occlusion_queries.items.len) { + const active = device.active_occlusion_queries.items[i]; + if (active.pool == impl.pool and + active.query >= impl.first and + active.query < impl.first + impl.count) + { + _ = device.active_occlusion_queries.swapRemove(i); + continue; + } + i += 1; + } + } + }; + + const cmd = allocator.create(CommandImpl) catch return VkError.OutOfHostMemory; + errdefer allocator.destroy(cmd); + cmd.* = .{ + .pool = pool, + .first = first, + .count = count, + }; + self.commands.append(allocator, .{ .ptr = cmd, .vtable = &.{ .execute = CommandImpl.execute } }) catch return VkError.OutOfHostMemory; +} + pub fn beginRenderPass(interface: *Interface, render_pass: *base.RenderPass, framebuffer: *base.Framebuffer, render_area: vk.Rect2D, clear_values: ?[]const vk.ClearValue) VkError!void { const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); const allocator = self.command_allocator.allocator(); @@ -627,6 +741,45 @@ pub fn copyImageToBuffer(interface: *Interface, src: *base.Image, src_layout: vk self.commands.append(allocator, .{ .ptr = cmd, .vtable = &.{ .execute = CommandImpl.execute } }) catch return VkError.OutOfHostMemory; } +pub fn copyQueryPoolResults(interface: *Interface, pool: *base.QueryPool, first: u32, count: u32, dst: *base.Buffer, offset: vk.DeviceSize, stride: vk.DeviceSize, flags: vk.QueryResultFlags) VkError!void { + const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); + const allocator = self.command_allocator.allocator(); + + const CommandImpl = struct { + const Impl = @This(); + + pool: *base.QueryPool, + dst: *SoftBuffer, + first: u32, + count: u32, + offset: vk.DeviceSize, + stride: vk.DeviceSize, + flags: vk.QueryResultFlags, + + pub fn execute(context: *anyopaque, _: *ExecutionDevice) VkError!void { + const impl: *Impl = @ptrCast(@alignCast(context)); + const value_size: vk.DeviceSize = if (impl.flags.@"64_bit") 8 else 4; + const item_size = value_size * (1 + @as(vk.DeviceSize, @intFromBool(impl.flags.with_availability_bit))); + const byte_size = if (impl.count == 0) 0 else (impl.count - 1) * impl.stride + item_size; + const map = try impl.dst.mapAsSliceWithAddedOffset(u8, impl.offset, byte_size); + try impl.pool.copyResults(impl.first, impl.count, map, impl.stride, impl.flags); + } + }; + + const cmd = allocator.create(CommandImpl) catch return VkError.OutOfHostMemory; + errdefer allocator.destroy(cmd); + cmd.* = .{ + .pool = pool, + .dst = @alignCast(@fieldParentPtr("interface", dst)), + .first = first, + .count = count, + .offset = offset, + .stride = stride, + .flags = flags, + }; + self.commands.append(allocator, .{ .ptr = cmd, .vtable = &.{ .execute = CommandImpl.execute } }) catch return VkError.OutOfHostMemory; +} + pub fn dispatch(interface: *Interface, group_count_x: u32, group_count_y: u32, group_count_z: u32) VkError!void { const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); const allocator = self.command_allocator.allocator(); diff --git a/src/soft/SoftPipeline.zig b/src/soft/SoftPipeline.zig index 7dcb7d1..af3bbeb 100644 --- a/src/soft/SoftPipeline.zig +++ b/src/soft/SoftPipeline.zig @@ -378,228 +378,6 @@ fn writeImageInt4(context: *anyopaque, dim: spv.SpvDim, x: i32, y: i32, z: i32, } } -const CubeCoordinate = struct { - face: u32, - u: f32, - v: f32, - w: f32 = 0.0, -}; - -fn resolveCubeCoordinate(x: f32, y: f32, z: f32) CubeCoordinate { - const ax = @abs(x); - const ay = @abs(y); - const az = @abs(z); - - var face: u32 = 0; - var sc: f32 = 0.0; - var tc: f32 = 0.0; - var ma: f32 = 1.0; - - if (ax >= ay and ax >= az) { - ma = ax; - if (x >= 0.0) { - face = 0; - sc = -z; - tc = -y; - } else { - face = 1; - sc = z; - tc = -y; - } - } else if (ay >= ax and ay >= az) { - ma = ay; - if (y >= 0.0) { - face = 2; - sc = x; - tc = z; - } else { - face = 3; - sc = x; - tc = -z; - } - } else { - ma = az; - if (z >= 0.0) { - face = 4; - sc = x; - tc = -y; - } else { - face = 5; - sc = -x; - tc = -y; - } - } - - const inv_ma = if (ma == 0.0) 0.0 else 1.0 / ma; - return .{ - .face = face, - .u = (sc * inv_ma + 1.0) * 0.5, - .v = (tc * inv_ma + 1.0) * 0.5, - }; -} - -fn cubeDirection(face: u32, u: f32, v: f32) struct { x: f32, y: f32, z: f32 } { - const sc = u * 2.0 - 1.0; - const tc = v * 2.0 - 1.0; - - return switch (face) { - 0 => .{ .x = 1.0, .y = -tc, .z = -sc }, - 1 => .{ .x = -1.0, .y = -tc, .z = sc }, - 2 => .{ .x = sc, .y = 1.0, .z = tc }, - 3 => .{ .x = sc, .y = -1.0, .z = -tc }, - 4 => .{ .x = sc, .y = -tc, .z = 1.0 }, - 5 => .{ .x = -sc, .y = -tc, .z = -1.0 }, - else => .{ .x = 0.0, .y = 0.0, .z = 0.0 }, - }; -} - -fn readSampledFloat4( - image: *SoftImage, - image_view: *SoftImageView, - sampler: *SoftSampler, - dim: spv.SpvDim, - coord: CubeCoordinate, - ix: i32, - iy: i32, - iz: i32, -) VkError!zm.F32x4 { - 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( - 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 + @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 = sx, - .y = sy, - .z = z, - }, - .{ - .aspect_mask = range.aspect_mask, - .mip_level = range.base_mip_level, - .array_layer = layer, - }, - image_view.interface.format, - ); - return result; -} - -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, 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 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 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) { var pixel = zm.f32x4s(0.0); @@ -613,25 +391,7 @@ 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)); - - if (dim == .Cube) { - const coord = resolveCubeCoordinate(x, y, z); - 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; - } else { - 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; - } + pixel = SoftSampler.sampleImageFloat4(image, image_view, sampler, dim, x, y, z) catch return SpvRuntimeError.Unknown; } return .{ diff --git a/src/soft/SoftQueryPool.zig b/src/soft/SoftQueryPool.zig index 2fe0d41..eca39ba 100644 --- a/src/soft/SoftQueryPool.zig +++ b/src/soft/SoftQueryPool.zig @@ -28,5 +28,6 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void { const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); + allocator.free(interface.queries); allocator.destroy(self); } diff --git a/src/soft/SoftQueue.zig b/src/soft/SoftQueue.zig index 80d671c..6d27283 100644 --- a/src/soft/SoftQueue.zig +++ b/src/soft/SoftQueue.zig @@ -96,6 +96,7 @@ fn taskRunner(self: *Self, info: Interface.SubmitInfo, p_fence: ?*base.Fence, ru var execution_device: ExecutionDevice = undefined; execution_device.setup(soft_device); + defer execution_device.deinit(soft_device.device_allocator.allocator()); for (info.command_buffers.items) |command_buffer| { const soft_command_buffer: *SoftCommandBuffer = @alignCast(@fieldParentPtr("interface", command_buffer)); diff --git a/src/soft/SoftSampler.zig b/src/soft/SoftSampler.zig index a9c85df..1a1fc34 100644 --- a/src/soft/SoftSampler.zig +++ b/src/soft/SoftSampler.zig @@ -1,13 +1,34 @@ const std = @import("std"); const vk = @import("vulkan"); const base = @import("base"); +const spv = @import("spv"); +const zm = base.zm; const VkError = base.VkError; const Device = base.Device; +const F32x4 = zm.F32x4; + +const SoftImage = @import("SoftImage.zig"); +const SoftImageView = @import("SoftImageView.zig"); const Self = @This(); pub const Interface = base.Sampler; +const CubeCoordinate = struct { + face: u32, + u: f32, + v: f32, + w: f32 = 0.0, +}; + +const ImageSamplingContext = struct { + image: *SoftImage, + image_view: *SoftImageView, + sampler: *Self, + dim: spv.SpvDim, + coord: CubeCoordinate, +}; + interface: Interface, pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.SamplerCreateInfo) VkError!*Self { @@ -30,3 +51,286 @@ pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void { const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); allocator.destroy(self); } + +fn resolveCubeCoordinate(x: f32, y: f32, z: f32) CubeCoordinate { + const ax = @abs(x); + const ay = @abs(y); + const az = @abs(z); + + var face: u32 = 0; + var sc: f32 = 0.0; + var tc: f32 = 0.0; + var ma: f32 = 1.0; + + if (ax >= ay and ax >= az) { + ma = ax; + if (x >= 0.0) { + face = 0; + sc = -z; + tc = -y; + } else { + face = 1; + sc = z; + tc = -y; + } + } else if (ay >= ax and ay >= az) { + ma = ay; + if (y >= 0.0) { + face = 2; + sc = x; + tc = z; + } else { + face = 3; + sc = x; + tc = -z; + } + } else { + ma = az; + if (z >= 0.0) { + face = 4; + sc = x; + tc = -y; + } else { + face = 5; + sc = -x; + tc = -y; + } + } + + const inv_ma = if (ma == 0.0) 0.0 else 1.0 / ma; + return .{ + .face = face, + .u = (sc * inv_ma + 1.0) * 0.5, + .v = (tc * inv_ma + 1.0) * 0.5, + }; +} + +fn cubeDirection(face: u32, u: f32, v: f32) struct { x: f32, y: f32, z: f32 } { + const sc = u * 2.0 - 1.0; + const tc = v * 2.0 - 1.0; + + return switch (face) { + 0 => .{ .x = 1.0, .y = -tc, .z = -sc }, + 1 => .{ .x = -1.0, .y = -tc, .z = sc }, + 2 => .{ .x = sc, .y = 1.0, .z = tc }, + 3 => .{ .x = sc, .y = -1.0, .z = -tc }, + 4 => .{ .x = sc, .y = -tc, .z = 1.0 }, + 5 => .{ .x = -sc, .y = -tc, .z = -1.0 }, + else => .{ .x = 0.0, .y = 0.0, .z = 0.0 }, + }; +} + +inline 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: *Self) 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 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 readSampledFloat4( + image: *SoftImage, + image_view: *SoftImageView, + sampler: *Self, + dim: spv.SpvDim, + coord: CubeCoordinate, + ix: i32, + iy: i32, + iz: i32, +) VkError!F32x4 { + 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( + 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 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); + + return image.readFloat4( + .{ + .x = sx, + .y = sy, + .z = z, + }, + .{ + .aspect_mask = range.aspect_mask, + .mip_level = range.base_mip_level, + .array_layer = layer, + }, + image_view.interface.format, + ); +} + +fn readSampledFloat4At(context: *const ImageSamplingContext, ix: i32, iy: i32, iz: i32) VkError!F32x4 { + return readSampledFloat4( + context.image, + context.image_view, + context.sampler, + context.dim, + context.coord, + ix, + iy, + iz, + ); +} + +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); + 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 context: ImageSamplingContext = .{ + .image = image, + .image_view = image_view, + .sampler = sampler, + .dim = dim, + .coord = coord, + }; + + 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)), + 0.0, + ), + switch (sampler.interface.mag_filter) { + .linear => .linear, + else => .nearest, + }, + image_view.interface.view_type == .@"3d", + readSampledFloat4At, + ); +} + +pub fn sampleFloat4( + comptime Context: type, + context: Context, + pos: F32x4, + filter: vk.Filter, + filter_3D: bool, + comptime read: fn (Context, i32, i32, i32) VkError!F32x4, +) VkError!F32x4 { + if (filter == .nearest) { + return read( + context, + @intFromFloat(pos[0]), + @intFromFloat(pos[1]), + @intFromFloat(pos[2]), + ); + } + + const x = pos[0] - 0.5; + const y = pos[1] - 0.5; + const z = pos[2] - 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 p000 = try read(context, x0, y0, z0); + const p100 = try read(context, x1, y0, z0); + const p010 = try read(context, x0, y1, z0); + const p110 = try read(context, x1, y1, z0); + + 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 (!filter_3D) + return slice0; + + const p001 = try read(context, x0, y0, z1); + const p101 = try read(context, x1, y0, z1); + const p011 = try read(context, x0, y1, z1); + const p111 = try read(context, 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); +} diff --git a/src/soft/device/Device.zig b/src/soft/device/Device.zig index b807037..c06a51e 100644 --- a/src/soft/device/Device.zig +++ b/src/soft/device/Device.zig @@ -21,6 +21,11 @@ pub const GRAPHICS_PIPELINE_STATE = 0; pub const COMPUTE_PIPELINE_STATE = 1; pub const MAX_DYNAMIC_DESCRIPTORS_PER_SET = 64; +pub const ActiveOcclusionQuery = struct { + pool: *base.QueryPool, + query: u32, +}; + pub const PipelineState = struct { pipeline: ?*SoftPipeline, sets: [base.VULKAN_MAX_DESCRIPTOR_SETS]?*SoftDescriptorSet, @@ -39,6 +44,7 @@ compute: ComputeDispatcher, renderer: Renderer, pipeline_states: [2]PipelineState, +active_occlusion_queries: std.ArrayList(ActiveOcclusionQuery), /// Initializating an execution device and /// not creating one to avoid dangling pointers @@ -61,8 +67,13 @@ pub fn setup(self: *Self, device: *SoftDevice) void { }, }; } + self.active_occlusion_queries = .empty; self.compute = .init(device, &self.pipeline_states[@intFromEnum(vk.PipelineBindPoint.compute)]); - self.renderer = .init(device, &self.pipeline_states[@intFromEnum(vk.PipelineBindPoint.graphics)]); + self.renderer = .init(device, &self.pipeline_states[@intFromEnum(vk.PipelineBindPoint.graphics)], &self.active_occlusion_queries); +} + +pub fn deinit(self: *Self, allocator: std.mem.Allocator) void { + self.active_occlusion_queries.deinit(allocator); } pub fn writeDescriptorSets(state: *PipelineState, rt: *spv.Runtime) !void { diff --git a/src/soft/device/Renderer.zig b/src/soft/device/Renderer.zig index c357b7e..3f0ef24 100644 --- a/src/soft/device/Renderer.zig +++ b/src/soft/device/Renderer.zig @@ -74,7 +74,6 @@ pub const DrawCall = struct { render_pass: *SoftRenderPass, framebuffer: *SoftFramebuffer, - allocator_mutex: std.Io.Mutex, rasterizer_wait_group: std.Io.Group, stats: struct { @@ -94,7 +93,6 @@ pub const DrawCall = struct { .depth_attachment = if (render_pass.interface.subpasses[renderer.subpass_index].depth_stencil_attachments) |desc| framebuffer.interface.attachments[desc.attachment] else null, .render_pass = render_pass, .framebuffer = framebuffer, - .allocator_mutex = .init, .rasterizer_wait_group = .init, .stats = .{ .polygons_drawn = 0, @@ -132,8 +130,9 @@ framebuffer: ?*SoftFramebuffer, dynamic_state: DynamicState, subpass_index: usize, +active_occlusion_queries: *std.ArrayList(ExecutionDevice.ActiveOcclusionQuery), -pub fn init(device: *SoftDevice, state: *PipelineState) Self { +pub fn init(device: *SoftDevice, state: *PipelineState, active_occlusion_queries: *std.ArrayList(ExecutionDevice.ActiveOcclusionQuery)) Self { return .{ .device = device, .state = state, @@ -152,6 +151,7 @@ pub fn init(device: *SoftDevice, state: *PipelineState) Self { .stencil_back_reference = null, }, .subpass_index = 0, + .active_occlusion_queries = active_occlusion_queries, }; } diff --git a/src/soft/device/blitter.zig b/src/soft/device/blitter.zig index dd1cdc4..f0a6194 100644 --- a/src/soft/device/blitter.zig +++ b/src/soft/device/blitter.zig @@ -574,7 +574,12 @@ pub fn readFloat4(map: []const u8, src_format: vk.Format) F32x4 { => c[0] = normalizedI8(map[0]), .r16_snorm => c[0] = normalizedI16(std.mem.bytesToValue(u16, map)), - .r16_unorm => c[0] = @as(f32, @floatFromInt(std.mem.bytesToValue(u16, map))) / std.math.maxInt(u16), + .r16_unorm, + .d16_unorm, + => c[0] = @as(f32, @floatFromInt(std.mem.bytesToValue(u16, map))) / std.math.maxInt(u16), + .x8_d24_unorm_pack32, + .d24_unorm_s8_uint, + => c[0] = @as(f32, @floatFromInt(std.mem.bytesToValue(u32, map) & 0x00ff_ffff)) / @as(f32, @floatFromInt(0x00ff_ffff)), .r8g8b8a8_sint, .r8g8b8a8_uint, @@ -856,6 +861,14 @@ pub fn writeFloat4(c: F32x4, map: []u8, dst_format: vk.Format) void { .d16_unorm, => std.mem.bytesAsValue(u16, map).* = @intFromFloat(@round(color[0] * std.math.maxInt(u16))), + .x8_d24_unorm_pack32, + .d24_unorm_s8_uint, + => { + const depth: u32 = @intFromFloat(@round(color[0] * @as(f32, @floatFromInt(0x00ff_ffff)))); + const preserved: u32 = std.mem.bytesToValue(u32, map) & 0xff00_0000; + std.mem.bytesAsValue(u32, map).* = preserved | depth; + }, + .r16_sfloat => std.mem.bytesAsValue(f16, map).* = @floatCast(color[0]), .r32_sint, diff --git a/src/soft/device/fragment.zig b/src/soft/device/fragment.zig index 33a9724..3ac84e7 100644 --- a/src/soft/device/fragment.zig +++ b/src/soft/device/fragment.zig @@ -72,9 +72,12 @@ pub fn shaderInvocation( rt.callEntryPoint(allocator, entry) catch |err| switch (err) { // Some errors can be safely ignored - SpvRuntimeError.OutOfBounds, - SpvRuntimeError.Killed, - => {}, + SpvRuntimeError.OutOfBounds => {}, + SpvRuntimeError.Killed => { + try rt.flushDescriptorSets(allocator); + freeOwnedInputs(allocator, fragment_inputs); + return undefined; // FIXME + }, else => return err, }; diff --git a/src/soft/device/rasterizer/bresenham.zig b/src/soft/device/rasterizer/bresenham.zig index 229d7dd..5b9e5a4 100644 --- a/src/soft/device/rasterizer/bresenham.zig +++ b/src/soft/device/rasterizer/bresenham.zig @@ -173,6 +173,16 @@ inline fn run(data: RunData) !void { }; } - try common.writeToTargets(outputs, data.draw_call, data.color_attachment_access, data.depth_attachment_access, data.stencil_attachment_access, true, @intCast(pixel_x), @intCast(pixel_y), z); + try common.writeToTargets( + outputs, + data.draw_call, + data.color_attachment_access, + data.depth_attachment_access, + data.stencil_attachment_access, + true, + @intCast(pixel_x), + @intCast(pixel_y), + z, + ); } } diff --git a/src/soft/device/rasterizer/common.zig b/src/soft/device/rasterizer/common.zig index b9ae163..0320ad2 100644 --- a/src/soft/device/rasterizer/common.zig +++ b/src/soft/device/rasterizer/common.zig @@ -98,13 +98,7 @@ fn updateStencilValue(stencil: *RenderTargetAccess, offset: usize, state: vk.Ste blitter.writeInt4(@splat(new_value), stencil.base[offset..], stencil.format); } -pub fn stencilTestAndUpdate( - stencil: *RenderTargetAccess, - x: usize, - y: usize, - state: vk.StencilOpState, - depth_passed: ?bool, -) bool { +pub fn stencilTestAndUpdate(stencil: *RenderTargetAccess, x: usize, y: usize, state: vk.StencilOpState, depth_passed: ?bool) bool { const offset = targetOffset(stencil.*, x, y) orelse return false; const current = blitter.readInt4(stencil.base[offset..], stencil.format)[0] & std.math.maxInt(u8); const reference = state.reference & std.math.maxInt(u8); @@ -132,15 +126,27 @@ fn stencilTest(stencil: *RenderTargetAccess, offset: usize, state: vk.StencilOpS return compare(u32, state.compare_op, reference & compare_mask, current & compare_mask); } +fn quantizeDepthForFormat(format: vk.Format, z: f32) f32 { + const clamped = std.math.clamp(z, 0.0, 1.0); + return switch (format) { + .d16_unorm => @as(f32, @floatFromInt(@as(u16, @intFromFloat(@round(clamped * std.math.maxInt(u16)))))) / std.math.maxInt(u16), + .x8_d24_unorm_pack32, + .d24_unorm_s8_uint, + => @as(f32, @floatFromInt(@as(u32, @intFromFloat(@round(clamped * @as(f32, @floatFromInt(0x00ff_ffff))))))) / @as(f32, @floatFromInt(0x00ff_ffff)), + else => z, + }; +} + pub fn depthTestAndUpdate(depth: *RenderTargetAccess, x: usize, y: usize, z: f32, state: vk.PipelineDepthStencilStateCreateInfo) bool { if (state.depth_test_enable == .false) return true; const offset = targetOffset(depth.*, x, y) orelse return false; + const reference = quantizeDepthForFormat(depth.format, z); const depth_value = blitter.readFloat4(depth.base[offset..], depth.format); - const passed = compare(f32, state.depth_compare_op, z, depth_value[0]); + const passed = compare(f32, state.depth_compare_op, reference, depth_value[0]); if (passed and state.depth_write_enable == .true) - blitter.writeFloat4(zm.f32x4s(z), depth.base[offset..], depth.format); + blitter.writeFloat4(zm.f32x4s(reference), depth.base[offset..], depth.format); return passed; } @@ -312,6 +318,9 @@ pub fn writeToTargets( const io = draw_call.renderer.device.interface.io(); const depth_stencil_state = draw_call.renderer.state.pipeline.?.interface.mode.graphics.depth_stencil; + if (x >= draw_call.framebuffer.interface.width or y >= draw_call.framebuffer.interface.height) + return; + var stencil_state: ?vk.StencilOpState = null; var stencil_offset: ?usize = null; if (stencil_attachment_access) |stencil| { @@ -361,6 +370,10 @@ pub fn writeToTargets( } } + for (draw_call.renderer.active_occlusion_queries.items) |active| { + try active.pool.addSamples(active.query, 1); + } + for (color_attachment_access, 0..) |maybe_color, location| { const color = maybe_color orelse continue; const color_offset = targetOffset(color, x, y) orelse continue; diff --git a/src/soft/device/rasterizer/edge_function.zig b/src/soft/device/rasterizer/edge_function.zig index 4d96f5d..cb8b672 100644 --- a/src/soft/device/rasterizer/edge_function.zig +++ b/src/soft/device/rasterizer/edge_function.zig @@ -191,7 +191,17 @@ inline fn run(data: RunData) !void { }; } - try common.writeToTargets(outputs, data.draw_call, data.color_attachment_access, data.depth_attachment_access, data.stencil_attachment_access, data.front_face, @intCast(x), @intCast(y), z); + try common.writeToTargets( + outputs, + data.draw_call, + data.color_attachment_access, + data.depth_attachment_access, + data.stencil_attachment_access, + data.front_face, + @intCast(x), + @intCast(y), + z, + ); } } } diff --git a/src/soft/device/vertex_dispatcher.zig b/src/soft/device/vertex_dispatcher.zig index d43ad3f..ada504a 100644 --- a/src/soft/device/vertex_dispatcher.zig +++ b/src/soft/device/vertex_dispatcher.zig @@ -39,16 +39,18 @@ 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; + const runtime = &shader.runtimes[data.batch_id]; + const mutex = &runtime.mutex; + const rt = &runtime.rt; + + const io = data.draw_call.renderer.device.interface.io(); + mutex.lock(io) catch return VkError.DeviceLost; + defer mutex.unlock(io); 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) { - const io = data.draw_call.renderer.device.interface.io(); - data.draw_call.allocator_mutex.lock(io) catch return VkError.DeviceLost; - defer data.draw_call.allocator_mutex.unlock(io); - rt.resetInvocation(data.allocator); try rt.populatePushConstants(data.draw_call.renderer.state.push_constant_blob[0..]); @@ -78,9 +80,11 @@ inline fn run(data: RunData) !void { rt.callEntryPoint(data.allocator, entry) catch |err| switch (err) { // Some errors can be safely ignored - SpvRuntimeError.OutOfBounds, - SpvRuntimeError.Killed, - => {}, + SpvRuntimeError.OutOfBounds => {}, + SpvRuntimeError.Killed => { + try rt.flushDescriptorSets(data.allocator); + return; + }, else => return err, }; @@ -95,7 +99,7 @@ inline fn run(data: RunData) !void { }; const memory_size = try rt.getResultMemorySize(result_word); output.outputs[location][component] = .{ - .interpolation_type = if (rt.hasResultDecoration(result_word, .Flat) or resultIsInteger(rt, result_word)) .flat else .smooth, // TODO : handle noperspective + .interpolation_type = if (rt.hasResultDecoration(result_word, .Flat) or rt.resultIsInteger(result_word)) .flat else .smooth, // TODO : handle noperspective .blob = data.allocator.alloc(u8, memory_size + INTERFACE_BLOB_PADDING) catch return VkError.OutOfDeviceMemory, .size = memory_size, }; @@ -116,32 +120,7 @@ fn setupBuiltins(rt: *spv.Runtime, vertex_index: usize, instance_index: usize) ! try rt.writeBuiltIn(std.mem.asBytes(&instance_index_u32), .InstanceIndex); } -fn resultIsInteger(rt: *spv.Runtime, result_word: spv.SpvWord) bool { - const value = rt.results[result_word].getConstValue() catch return false; - return switch (value.*) { - .Int, - .Vector2i32, - .Vector3i32, - .Vector4i32, - .Vector2u32, - .Vector3u32, - .Vector4u32, - => true, - .Vector => |lanes| lanes.len != 0 and switch (lanes[0]) { - .Int => true, - else => false, - }, - else => false, - }; -} - -fn writeVertexInput( - rt: *spv.Runtime, - allocator: std.mem.Allocator, - raw_input: []const u8, - format: vk.Format, - location: u32, -) !void { +fn writeVertexInput(rt: *spv.Runtime, allocator: std.mem.Allocator, raw_input: []const u8, format: vk.Format, location: u32) !void { var has_split_components = false; for (1..4) |component| { _ = rt.getResultByLocationComponent(location, @intCast(component), .input) catch |err| switch (err) { diff --git a/src/vulkan/CommandBuffer.zig b/src/vulkan/CommandBuffer.zig index dfdc930..43db33b 100644 --- a/src/vulkan/CommandBuffer.zig +++ b/src/vulkan/CommandBuffer.zig @@ -14,6 +14,7 @@ const Event = @import("Event.zig"); const Framebuffer = @import("Framebuffer.zig"); const Image = @import("Image.zig"); const Pipeline = @import("Pipeline.zig"); +const QueryPool = @import("QueryPool.zig"); const RenderPass = @import("RenderPass.zig"); const State = enum { @@ -39,6 +40,7 @@ dispatch_table: *const DispatchTable, pub const DispatchTable = struct { begin: *const fn (*Self, *const vk.CommandBufferBeginInfo) VkError!void, + beginQuery: *const fn (*Self, *QueryPool, u32, vk.QueryControlFlags) VkError!void, beginRenderPass: *const fn (*Self, *RenderPass, *Framebuffer, vk.Rect2D, ?[]const vk.ClearValue) VkError!void, bindDescriptorSets: *const fn (*Self, vk.PipelineBindPoint, u32, [lib.VULKAN_MAX_DESCRIPTOR_SETS]?*DescriptorSet, []const u32) VkError!void, bindPipeline: *const fn (*Self, vk.PipelineBindPoint, *Pipeline) VkError!void, @@ -52,6 +54,7 @@ pub const DispatchTable = struct { copyBufferToImage: *const fn (*Self, *Buffer, *Image, vk.ImageLayout, []const vk.BufferImageCopy) VkError!void, copyImage: *const fn (*Self, *Image, vk.ImageLayout, *Image, vk.ImageLayout, []const vk.ImageCopy) VkError!void, copyImageToBuffer: *const fn (*Self, *Image, vk.ImageLayout, *Buffer, []const vk.BufferImageCopy) VkError!void, + copyQueryPoolResults: *const fn (*Self, *QueryPool, u32, u32, *Buffer, vk.DeviceSize, vk.DeviceSize, vk.QueryResultFlags) VkError!void, dispatch: *const fn (*Self, u32, u32, u32) VkError!void, dispatchIndirect: *const fn (*Self, *Buffer, vk.DeviceSize) VkError!void, draw: *const fn (*Self, usize, usize, usize, usize) VkError!void, @@ -59,6 +62,7 @@ pub const DispatchTable = struct { drawIndexedIndirect: *const fn (*Self, *Buffer, usize, usize, usize) VkError!void, drawIndirect: *const fn (*Self, *Buffer, usize, usize, usize) VkError!void, end: *const fn (*Self) VkError!void, + endQuery: *const fn (*Self, *QueryPool, u32) VkError!void, endRenderPass: *const fn (*Self) VkError!void, executeCommands: *const fn (*Self, *Self) VkError!void, fillBuffer: *const fn (*Self, *Buffer, vk.DeviceSize, vk.DeviceSize, u32) VkError!void, @@ -66,6 +70,7 @@ pub const DispatchTable = struct { pipelineBarrier: *const fn (*Self, vk.PipelineStageFlags, vk.PipelineStageFlags, vk.DependencyFlags, []const vk.MemoryBarrier, []const vk.BufferMemoryBarrier, []const vk.ImageMemoryBarrier) VkError!void, pushConstants: *const fn (*Self, vk.ShaderStageFlags, u32, []const u8) VkError!void, reset: *const fn (*Self, vk.CommandBufferResetFlags) VkError!void, + resetQueryPool: *const fn (*Self, *QueryPool, u32, u32) VkError!void, resetEvent: *const fn (*Self, *Event, vk.PipelineStageFlags) VkError!void, resolveImage: *const fn (*Self, *Image, vk.ImageLayout, *Image, vk.ImageLayout, vk.ImageResolve) VkError!void, setEvent: *const fn (*Self, *Event, vk.PipelineStageFlags) VkError!void, @@ -165,6 +170,10 @@ pub inline fn beginRenderPass(self: *Self, render_pass: *RenderPass, framebuffer try self.dispatch_table.beginRenderPass(self, render_pass, framebuffer, render_area, clear_values); } +pub inline fn beginQuery(self: *Self, pool: *QueryPool, query: u32, flags: vk.QueryControlFlags) VkError!void { + try self.dispatch_table.beginQuery(self, pool, query, flags); +} + pub fn bindDescriptorSets(self: *Self, bind_point: vk.PipelineBindPoint, first_set: u32, sets: []const vk.DescriptorSet, dynamic_offsets: []const u32) VkError!void { if (sets.len > lib.VULKAN_MAX_DESCRIPTOR_SETS or first_set > lib.VULKAN_MAX_DESCRIPTOR_SETS or first_set + sets.len > lib.VULKAN_MAX_DESCRIPTOR_SETS) return VkError.ValidationFailed; @@ -224,6 +233,10 @@ pub inline fn copyImageToBuffer(self: *Self, src: *Image, src_layout: vk.ImageLa try self.dispatch_table.copyImageToBuffer(self, src, src_layout, dst, regions); } +pub inline fn copyQueryPoolResults(self: *Self, pool: *QueryPool, first: u32, count: u32, dst: *Buffer, offset: vk.DeviceSize, stride: vk.DeviceSize, flags: vk.QueryResultFlags) VkError!void { + try self.dispatch_table.copyQueryPoolResults(self, pool, first, count, dst, offset, stride, flags); +} + pub inline fn dispatch(self: *Self, group_count_x: u32, group_count_y: u32, group_count_z: u32) VkError!void { try self.dispatch_table.dispatch(self, group_count_x, group_count_y, group_count_z); } @@ -252,6 +265,10 @@ pub inline fn endRenderPass(self: *Self) VkError!void { try self.dispatch_table.endRenderPass(self); } +pub inline fn endQuery(self: *Self, pool: *QueryPool, query: u32) VkError!void { + try self.dispatch_table.endQuery(self, pool, query); +} + pub inline fn executeCommands(self: *Self, commands: *Self) VkError!void { try self.dispatch_table.executeCommands(self, commands); } @@ -280,6 +297,10 @@ pub inline fn pushConstants(self: *Self, stages: vk.ShaderStageFlags, offset: u3 try self.dispatch_table.pushConstants(self, stages, offset, blob); } +pub inline fn resetQueryPool(self: *Self, pool: *QueryPool, first: u32, count: u32) VkError!void { + try self.dispatch_table.resetQueryPool(self, pool, first, count); +} + pub inline fn resetEvent(self: *Self, event: *Event, stage: vk.PipelineStageFlags) VkError!void { try self.dispatch_table.resetEvent(self, event, stage); } diff --git a/src/vulkan/QueryPool.zig b/src/vulkan/QueryPool.zig index 61a33de..5c958f9 100644 --- a/src/vulkan/QueryPool.zig +++ b/src/vulkan/QueryPool.zig @@ -11,18 +11,32 @@ const Self = @This(); pub const ObjectType: vk.ObjectType = .query_pool; owner: *Device, +query_type: vk.QueryType, +queries: []Query, vtable: *const VTable, +const Query = struct { + value: std.atomic.Value(u64) = std.atomic.Value(u64).init(0), + available: bool = false, + active: bool = false, +}; + pub const VTable = struct { destroy: *const fn (*Self, std.mem.Allocator) void, }; pub fn init(device: *Device, allocator: std.mem.Allocator, info: *const vk.QueryPoolCreateInfo) VkError!Self { - _ = allocator; - _ = info; + const queries = allocator.alloc(Query, info.query_count) catch return VkError.OutOfHostMemory; + errdefer allocator.free(queries); + for (queries) |*query| { + query.* = .{}; + } + return .{ .owner = device, + .query_type = info.query_type, + .queries = queries, .vtable = undefined, }; } @@ -30,3 +44,99 @@ pub fn init(device: *Device, allocator: std.mem.Allocator, info: *const vk.Query pub inline fn destroy(self: *Self, allocator: std.mem.Allocator) void { self.vtable.destroy(self, allocator); } + +pub fn reset(self: *Self, first: u32, count: u32) VkError!void { + const range = try self.queryRange(first, count); + for (range) |*query| { + query.value.store(0, .seq_cst); + query.available = false; + query.active = false; + } +} + +pub fn begin(self: *Self, query: u32) VkError!void { + if (self.query_type != .occlusion) + return VkError.FeatureNotPresent; + const q = try self.queryAt(query); + q.value.store(0, .seq_cst); + q.available = false; + q.active = true; +} + +pub fn end(self: *Self, query: u32) VkError!void { + const q = try self.queryAt(query); + q.active = false; + q.available = true; +} + +pub fn addSamples(self: *Self, query: u32, samples: u64) VkError!void { + const q = try self.queryAt(query); + if (q.active) + _ = q.value.fetchAdd(samples, .seq_cst); +} + +pub fn writeResults(self: *Self, first: u32, count: u32, bytes: []u8, stride: vk.DeviceSize, flags: vk.QueryResultFlags) VkError!void { + return self.writeResultsImpl(first, count, bytes, stride, flags, true); +} + +pub fn copyResults(self: *Self, first: u32, count: u32, bytes: []u8, stride: vk.DeviceSize, flags: vk.QueryResultFlags) VkError!void { + return self.writeResultsImpl(first, count, bytes, stride, flags, false); +} + +fn writeResultsImpl(self: *Self, first: u32, count: u32, bytes: []u8, stride: vk.DeviceSize, flags: vk.QueryResultFlags, report_not_ready: bool) VkError!void { + _ = try self.queryRange(first, count); + if (count == 0) + return; + + const value_size: usize = if (flags.@"64_bit") 8 else 4; + const item_size = value_size * (1 + @as(usize, @intFromBool(flags.with_availability_bit))); + if (count > 1 and stride < item_size) + return VkError.ValidationFailed; + + var not_ready = false; + for (0..count) |i| { + const query = &self.queries[first + i]; + if (flags.wait_bit) { + while (!query.available) { + std.Thread.yield() catch std.atomic.spinLoopHint(); + } + } + + const offset: usize = @intCast(@as(vk.DeviceSize, @intCast(i)) * stride); + if (offset + item_size > bytes.len) + return VkError.Incomplete; + + if (query.available or flags.partial_bit) { + writeInt(bytes[offset..][0..value_size], query.value.load(.seq_cst), flags); + } else { + not_ready = true; + } + + if (flags.with_availability_bit) { + writeInt(bytes[offset + value_size ..][0..value_size], @intFromBool(query.available), flags); + } + } + + if (not_ready and report_not_ready) + return VkError.NotReady; +} + +fn writeInt(bytes: []u8, value: u64, flags: vk.QueryResultFlags) void { + if (flags.@"64_bit") { + std.mem.writeInt(u64, bytes[0..8], value, .little); + } else { + std.mem.writeInt(u32, bytes[0..4], @truncate(value), .little); + } +} + +fn queryAt(self: *Self, query: u32) VkError!*Query { + if (query >= self.queries.len) + return VkError.ValidationFailed; + return &self.queries[query]; +} + +fn queryRange(self: *Self, first: u32, count: u32) VkError![]Query { + if (first > self.queries.len or count > self.queries.len - first) + return VkError.ValidationFailed; + return self.queries[first .. first + count]; +} diff --git a/src/vulkan/lib_vulkan.zig b/src/vulkan/lib_vulkan.zig index e9b2a82..85e3b55 100644 --- a/src/vulkan/lib_vulkan.zig +++ b/src/vulkan/lib_vulkan.zig @@ -1451,18 +1451,11 @@ pub export fn apeGetQueryPoolResults( defer entryPointEndLogTrace(); Dispatchable(Device).checkHandleValidity(p_device) catch |err| return toVkResult(err); + const pool = NonDispatchable(QueryPool).fromHandleObject(p_pool) catch |err| return toVkResult(err); - notImplementedWarning(); - - _ = p_pool; - _ = first; - _ = count; - _ = size; - _ = data; - _ = stride; - _ = flags; - - return .error_unknown; + const bytes = @as([*]u8, @ptrCast(data))[0..size]; + pool.writeResults(first, count, bytes, stride, flags) catch |err| return toVkResult(err); + return .success; } pub export fn apeGetRenderAreaGranularity(p_device: vk.Device, p_pass: vk.RenderPass, granularity: *vk.Extent2D) callconv(vk.vulkan_call_conv) void { @@ -1630,13 +1623,8 @@ pub export fn apeCmdBeginQuery(p_cmd: vk.CommandBuffer, p_pool: vk.QueryPool, qu defer entryPointEndLogTrace(); const cmd = Dispatchable(CommandBuffer).fromHandleObject(p_cmd) catch |err| return errorLogger(err); - - notImplementedWarning(); - - _ = cmd; - _ = p_pool; - _ = query; - _ = flags; + const pool = NonDispatchable(QueryPool).fromHandleObject(p_pool) catch |err| return errorLogger(err); + cmd.beginQuery(pool, query, flags) catch |err| return errorLogger(err); } pub export fn apeCmdBeginRenderPass(p_cmd: vk.CommandBuffer, info: *const vk.RenderPassBeginInfo, contents: vk.SubpassContents) callconv(vk.vulkan_call_conv) void { @@ -1799,18 +1787,9 @@ pub export fn apeCmdCopyQueryPoolResults(p_cmd: vk.CommandBuffer, p_pool: vk.Que defer entryPointEndLogTrace(); const cmd = Dispatchable(CommandBuffer).fromHandleObject(p_cmd) catch |err| return errorLogger(err); - const dst = NonDispatchable(Image).fromHandleObject(p_dst) catch |err| return errorLogger(err); - - notImplementedWarning(); - - _ = cmd; - _ = p_pool; - _ = first; - _ = count; - _ = dst; - _ = offset; - _ = stride; - _ = flags; + const pool = NonDispatchable(QueryPool).fromHandleObject(p_pool) catch |err| return errorLogger(err); + const dst = NonDispatchable(Buffer).fromHandleObject(p_dst) catch |err| return errorLogger(err); + cmd.copyQueryPoolResults(pool, first, count, dst, offset, stride, flags) catch |err| return errorLogger(err); } pub export fn apeCmdDispatch(p_cmd: vk.CommandBuffer, group_count_x: u32, group_count_y: u32, group_count_z: u32) callconv(vk.vulkan_call_conv) void { @@ -1869,12 +1848,8 @@ pub export fn apeCmdEndQuery(p_cmd: vk.CommandBuffer, p_pool: vk.QueryPool, quer defer entryPointEndLogTrace(); const cmd = Dispatchable(CommandBuffer).fromHandleObject(p_cmd) catch |err| return errorLogger(err); - - notImplementedWarning(); - - _ = cmd; - _ = p_pool; - _ = query; + const pool = NonDispatchable(QueryPool).fromHandleObject(p_pool) catch |err| return errorLogger(err); + cmd.endQuery(pool, query) catch |err| return errorLogger(err); } pub export fn apeCmdEndRenderPass(p_cmd: vk.CommandBuffer) callconv(vk.vulkan_call_conv) void { @@ -1954,13 +1929,8 @@ pub export fn apeCmdResetQueryPool(p_cmd: vk.CommandBuffer, p_pool: vk.QueryPool defer entryPointEndLogTrace(); const cmd = Dispatchable(CommandBuffer).fromHandleObject(p_cmd) catch |err| return errorLogger(err); - - notImplementedWarning(); - - _ = cmd; - _ = p_pool; - _ = first; - _ = count; + const pool = NonDispatchable(QueryPool).fromHandleObject(p_pool) catch |err| return errorLogger(err); + cmd.resetQueryPool(pool, first, count) catch |err| return errorLogger(err); } pub export fn apeCmdResetEvent(p_cmd: vk.CommandBuffer, p_event: vk.Event, stage_mask: vk.PipelineStageFlags) callconv(vk.vulkan_call_conv) void {