From 7d5eb1553b098e58910c838e77dfdac84a1ac05c Mon Sep 17 00:00:00 2001 From: Kbz-8 Date: Mon, 22 Dec 2025 16:57:41 +0100 Subject: [PATCH] working on commands --- src/soft/SoftCommandBuffer.zig | 10 +++ src/soft/SoftImage.zig | 4 +- src/soft/SoftQueue.zig | 8 +- src/soft/device/Blitter.zig | 89 ++++++++++++++++++-- src/soft/{Executor.zig => device/Device.zig} | 39 ++++++++- src/soft/lib.zig | 4 +- src/vulkan/CommandBuffer.zig | 13 +++ src/vulkan/Image.zig | 9 ++ src/vulkan/commands.zig | 8 ++ src/vulkan/lib_vulkan.zig | 10 +-- 10 files changed, 170 insertions(+), 24 deletions(-) rename src/soft/{Executor.zig => device/Device.zig} (50%) diff --git a/src/soft/SoftCommandBuffer.zig b/src/soft/SoftCommandBuffer.zig index 36e7dbf..ce2804a 100644 --- a/src/soft/SoftCommandBuffer.zig +++ b/src/soft/SoftCommandBuffer.zig @@ -25,6 +25,7 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v .clearColorImage = clearColorImage, .copyBuffer = copyBuffer, .copyImage = copyImage, + .copyImageToBuffer = copyImageToBuffer, .end = end, .fillBuffer = fillBuffer, .reset = reset, @@ -99,6 +100,15 @@ pub fn copyImage(interface: *Interface, src: *base.Image, src_layout: vk.ImageLa _ = regions; } +pub fn copyImageToBuffer(interface: *Interface, src: *base.Image, src_layout: vk.ImageLayout, dst: *base.Buffer, regions: []const vk.BufferImageCopy) VkError!void { + // No-op + _ = interface; + _ = src; + _ = src_layout; + _ = dst; + _ = regions; +} + pub fn resetEvent(interface: *Interface, event: *base.Event, stage: vk.PipelineStageFlags) VkError!void { // No-op _ = interface; diff --git a/src/soft/SoftImage.zig b/src/soft/SoftImage.zig index e652070..85449d6 100644 --- a/src/soft/SoftImage.zig +++ b/src/soft/SoftImage.zig @@ -41,7 +41,7 @@ pub fn getMemoryRequirements(interface: *Interface, requirements: *vk.MemoryRequ requirements.alignment = lib.MEMORY_REQUIREMENTS_IMAGE_ALIGNMENT; } -inline fn clear(self: *Self, pixel: *const anyopaque, format: vk.Format, view_format: vk.Format, range: vk.ImageSubresourceRange, area: ?vk.Rect2D) void { +inline fn clear(self: *Self, pixel: vk.ClearValue, format: vk.Format, view_format: vk.Format, range: vk.ImageSubresourceRange, area: ?vk.Rect2D) void { const soft_device: *SoftDevice = @alignCast(@fieldParentPtr("interface", self.interface.owner)); soft_device.blitter.clear(pixel, format, self, view_format, range, area); } @@ -55,5 +55,5 @@ pub fn clearRange(self: *Self, color: vk.ClearColorValue, range: vk.ImageSubreso .r32g32b32a32_uint else .r32g32b32a32_sfloat; - self.clear(@ptrCast(&color.float_32), clear_format, self.interface.format, range, null); + self.clear(.{ .color = color }, clear_format, self.interface.format, range, null); } diff --git a/src/soft/SoftQueue.zig b/src/soft/SoftQueue.zig index 404917f..955fb96 100644 --- a/src/soft/SoftQueue.zig +++ b/src/soft/SoftQueue.zig @@ -4,7 +4,7 @@ const base = @import("base"); const RefCounter = base.RefCounter; -const Executor = @import("Executor.zig"); +const Device = @import("device/Device.zig"); const Dispatchable = base.Dispatchable; const CommandBuffer = base.CommandBuffer; @@ -97,13 +97,13 @@ fn taskRunner(self: *Self, info: Interface.SubmitInfo, p_fence: ?*base.Fence, ru command_buffers.deinit(soft_device.device_allocator.allocator()); } - var executor = Executor.init(); - defer executor.deinit(); + var device = Device.init(); + defer device.deinit(); loop: for (info.command_buffers.items) |command_buffer| { command_buffer.submit() catch continue :loop; for (command_buffer.commands.items) |command| { - executor.dispatch(&command) catch |err| base.errors.errorLoggerContext(err, "the software command dispatcher"); + device.dispatch(&command) catch |err| base.errors.errorLoggerContext(err, "the software command dispatcher"); } } diff --git a/src/soft/device/Blitter.zig b/src/soft/device/Blitter.zig index 6dc11d6..44d237e 100644 --- a/src/soft/device/Blitter.zig +++ b/src/soft/device/Blitter.zig @@ -1,3 +1,5 @@ +//! This software blitter is highly inspired by SwiftShaders one + const std = @import("std"); const vk = @import("vulkan"); const base = @import("base"); @@ -13,15 +15,90 @@ pub const init: Self = .{ .blit_mutex = .{}, }; -pub fn clear(self: *Self, pixel: *const anyopaque, format: vk.Format, dest: *SoftImage, view_format: vk.Format, range: vk.ImageSubresourceRange, area: ?vk.Rect2D) void { +pub fn clear(self: *Self, pixel: vk.ClearValue, format: vk.Format, dest: *SoftImage, view_format: vk.Format, range: vk.ImageSubresourceRange, area: ?vk.Rect2D) void { const dst_format = base.Image.formatFromAspect(view_format, range.aspect_mask); if (dst_format == .undefined) { return; } - _ = self; - _ = pixel; - _ = format; - _ = dest; - _ = area; + const view_format_value: c_uint = @intCast(@intFromEnum(view_format)); + + var clamped_pixel: vk.ClearValue = pixel; + if (base.vku.vkuFormatIsSINT(view_format_value) or base.vku.vkuFormatIsUINT(view_format_value)) { + const min_value: f32 = if (base.vku.vkuFormatIsSNORM(view_format_value)) -1.0 else 0.0; + + if (range.aspect_mask.color_bit) { + clamped_pixel.color.float_32[0] = std.math.clamp(pixel.color.float_32[0], min_value, 1.0); + clamped_pixel.color.float_32[1] = std.math.clamp(pixel.color.float_32[1], min_value, 1.0); + clamped_pixel.color.float_32[2] = std.math.clamp(pixel.color.float_32[2], min_value, 1.0); + clamped_pixel.color.float_32[3] = std.math.clamp(pixel.color.float_32[3], min_value, 1.0); + } + + // Stencil never requires clamping, so we can check for Depth only + if (range.aspect_mask.depth_bit) { + clamped_pixel.depth_stencil.depth = std.math.clamp(pixel.depth_stencil.depth, min_value, 1.0); + } + } + + if (self.fastClear(clamped_pixel, format, dest, dst_format, range, area)) { + return; + } + base.logger.fixme("implement slow clear", .{}); +} + +fn fastClear(self: *Self, clear_value: vk.ClearValue, clear_format: vk.Format, dest: *SoftImage, view_format: vk.Format, range: vk.ImageSubresourceRange, render_area: ?vk.Rect2D) bool { + _ = self; + _ = render_area; + _ = range; + + if (clear_format != .r32g32b32a32_sfloat and clear_format != .d32_sfloat and clear_format != .s8_uint) { + return false; + } + + const ClearValue = union { + rgba: struct { r: f32, g: f32, b: f32, a: f32 }, + rgb: [3]f32, + d: f32, + d_as_u32: u32, + s: u32, + }; + + const c: *const ClearValue = @ptrCast(&clear_value); + + var pack: u32 = 0; + switch (view_format) { + .r5g6b5_unorm_pack16 => pack = @as(u16, @intFromFloat(31.0 * c.rgba.b + 0.5)) | (@as(u16, @intFromFloat(63.0 * c.rgba.g + 0.5)) << 5) | (@as(u16, @intFromFloat(31.0 * c.rgba.r + 0.5)) << 11), + .b5g6r5_unorm_pack16 => pack = @as(u16, @intFromFloat(31.0 * c.rgba.r + 0.5)) | (@as(u16, @intFromFloat(63.0 * c.rgba.g + 0.5)) << 5) | (@as(u16, @intFromFloat(31.0 * c.rgba.b + 0.5)) << 11), + + .a8b8g8r8_uint_pack32, + .a8b8g8r8_unorm_pack32, + .r8g8b8a8_unorm, + => pack = (@as(u32, @intFromFloat(255.0 * c.rgba.a + 0.5)) << 24) | (@as(u32, @intFromFloat(255.0 * c.rgba.b + 0.5)) << 16) | (@as(u32, @intFromFloat(255.0 * c.rgba.g + 0.5)) << 8) | @as(u32, @intFromFloat(255.0 * c.rgba.r + 0.5)), + + .b8g8r8a8_unorm => pack = (@as(u32, @intFromFloat(255.0 * c.rgba.a + 0.5)) << 24) | (@as(u32, @intFromFloat(255.0 * c.rgba.r + 0.5)) << 16) | (@as(u32, @intFromFloat(255.0 * c.rgba.g + 0.5)) << 8) | @as(u32, @intFromFloat(255.0 * c.rgba.b + 0.5)), + //.b10g11r11_ufloat_pack32 => pack = R11G11B10F(c.rgb), + //.e5b9g9r9_ufloat_pack32 => pack = RGB9E5(c.rgb), + .d32_sfloat => { + std.debug.assert(clear_format == .d32_sfloat); + pack = c.d_as_u32; // float reinterpreted as uint32 + }, + .s8_uint => { + std.debug.assert(clear_format == .s8_uint); + pack = @as(u8, @intCast(c.s)); + }, + else => return false, + } + + if (dest.interface.memory) |memory| { + const image_size = dest.interface.getTotalSize(); + const memory_map = memory.map(dest.interface.memory_offset, image_size) catch return false; + defer memory.unmap(); + + const memory_map_as_u32: []u32 = @as([*]u32, @ptrCast(@alignCast(memory_map)))[0..@divExact(image_size, 4)]; + + @memset(memory_map_as_u32, pack); + + return true; + } + return false; } diff --git a/src/soft/Executor.zig b/src/soft/device/Device.zig similarity index 50% rename from src/soft/Executor.zig rename to src/soft/device/Device.zig index c595c7d..931d062 100644 --- a/src/soft/Executor.zig +++ b/src/soft/device/Device.zig @@ -2,7 +2,7 @@ const std = @import("std"); const vk = @import("vulkan"); const base = @import("base"); -const SoftImage = @import("SoftImage.zig"); +const SoftImage = @import("../SoftImage.zig"); const cmd = base.commands; const VkError = base.VkError; @@ -23,6 +23,7 @@ pub fn dispatch(self: *Self, command: *const cmd.Command) VkError!void { .ClearColorImage => |data| try clearColorImage(&data), .CopyBuffer => |data| try copyBuffer(&data), .CopyImage => |data| try copyImage(&data), + .CopyImageToBuffer => |data| try copyImageToBuffer(&data), .FillBuffer => |data| try fillBuffer(&data), else => {}, } @@ -53,6 +54,42 @@ fn copyImage(data: *const cmd.CommandCopyImage) VkError!void { std.log.scoped(.commandExecutor).warn("FIXME: implement image to image copy", .{}); } +fn copyImageToBuffer(data: *const cmd.CommandCopyImageToBuffer) VkError!void { + for (data.regions) |region| { + const src_memory = if (data.src.memory) |memory| memory else return VkError.ValidationFailed; + const dst_memory = if (data.dst.memory) |memory| memory else return VkError.ValidationFailed; + + const pixel_size: u32 = @intCast(data.src.getPixelSize()); + const image_row_pitch: u32 = data.src.extent.width * pixel_size; + const image_size: u32 = @intCast(data.src.getTotalSize()); + + const buffer_row_length: u32 = if (region.buffer_row_length != 0) region.buffer_row_length else region.image_extent.width; + const buffer_row_pitch: u32 = buffer_row_length * pixel_size; + const buffer_size: u32 = buffer_row_pitch * region.image_extent.height * region.image_extent.depth; + + const src_map: []u8 = @as([*]u8, @ptrCast(try src_memory.map(0, image_size)))[0..image_size]; + const dst_map: []u8 = @as([*]u8, @ptrCast(try dst_memory.map(region.buffer_offset, buffer_size)))[0..buffer_size]; + + const row_size = region.image_extent.width * pixel_size; + for (0..data.src.extent.depth) |z| { + for (0..data.src.extent.height) |y| { + const z_as_u32: u32 = @intCast(z); + const y_as_u32: u32 = @intCast(y); + + const src_offset = ((@as(u32, @intCast(region.image_offset.z)) + z_as_u32) * data.src.extent.height + @as(u32, @intCast(region.image_offset.y)) + y_as_u32) * image_row_pitch + @as(u32, @intCast(region.image_offset.x)) * pixel_size; + const dst_offset = (z_as_u32 * buffer_row_length * region.image_extent.height + y_as_u32 * buffer_row_length) * pixel_size; + + const src_slice = src_map[src_offset..(src_offset + row_size)]; + const dst_slice = dst_map[dst_offset..(dst_offset + row_size)]; + @memcpy(dst_slice, src_slice); + } + } + + src_memory.unmap(); + dst_memory.unmap(); + } +} + fn fillBuffer(data: *const cmd.CommandFillBuffer) VkError!void { const memory = if (data.buffer.memory) |memory| memory else return VkError.ValidationFailed; var memory_map: []u32 = @as([*]u32, @ptrCast(@alignCast(try memory.map(data.offset, data.size))))[0..data.size]; diff --git a/src/soft/lib.zig b/src/soft/lib.zig index 135a4c1..d7a9afa 100644 --- a/src/soft/lib.zig +++ b/src/soft/lib.zig @@ -2,7 +2,7 @@ const std = @import("std"); const vk = @import("vulkan"); pub const base = @import("base"); -pub const Executor = @import("Executor.zig"); +pub const Device = @import("device/Device.zig"); pub const SoftInstance = @import("SoftInstance.zig"); pub const SoftDevice = @import("SoftDevice.zig"); @@ -62,7 +62,7 @@ comptime { } test { - std.testing.refAllDecls(Executor); + std.testing.refAllDecls(Device); std.testing.refAllDecls(SoftBinarySemaphore); std.testing.refAllDecls(SoftBuffer); std.testing.refAllDecls(SoftBufferView); diff --git a/src/vulkan/CommandBuffer.zig b/src/vulkan/CommandBuffer.zig index 328c366..7914a11 100644 --- a/src/vulkan/CommandBuffer.zig +++ b/src/vulkan/CommandBuffer.zig @@ -43,6 +43,7 @@ pub const DispatchTable = struct { clearColorImage: *const fn (*Self, *Image, vk.ImageLayout, *const vk.ClearColorValue, vk.ImageSubresourceRange) VkError!void, copyBuffer: *const fn (*Self, *Buffer, *Buffer, []const vk.BufferCopy) 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, end: *const fn (*Self) VkError!void, fillBuffer: *const fn (*Self, *Buffer, vk.DeviceSize, vk.DeviceSize, u32) VkError!void, reset: *const fn (*Self, vk.CommandBufferResetFlags) VkError!void, @@ -124,6 +125,7 @@ fn cleanCommandList(self: *Self) void { switch (command) { .CopyBuffer => |data| allocator.free(data.regions), .CopyImage => |data| allocator.free(data.regions), + .CopyImageToBuffer => |data| allocator.free(data.regions), else => {}, } } @@ -166,6 +168,17 @@ pub inline fn copyImage(self: *Self, src: *Image, src_layout: vk.ImageLayout, ds try self.dispatch_table.copyImage(self, src, src_layout, dst, dst_layout, regions); } +pub inline fn copyImageToBuffer(self: *Self, src: *Image, src_layout: vk.ImageLayout, dst: *Buffer, regions: []const vk.BufferImageCopy) VkError!void { + const allocator = self.host_allocator.allocator(); + self.commands.append(allocator, .{ .CopyImageToBuffer = .{ + .src = src, + .src_layout = src_layout, + .dst = dst, + .regions = allocator.dupe(vk.BufferImageCopy, regions) catch return VkError.OutOfHostMemory, + } }) catch return VkError.OutOfHostMemory; + try self.dispatch_table.copyImageToBuffer(self, src, src_layout, dst, regions); +} + pub inline fn fillBuffer(self: *Self, buffer: *Buffer, offset: vk.DeviceSize, size: vk.DeviceSize, data: u32) VkError!void { const allocator = self.host_allocator.allocator(); self.commands.append(allocator, .{ .FillBuffer = .{ diff --git a/src/vulkan/Image.zig b/src/vulkan/Image.zig index 7aa5c9f..70458b6 100644 --- a/src/vulkan/Image.zig +++ b/src/vulkan/Image.zig @@ -88,6 +88,15 @@ pub inline fn getTotalSize(self: *Self) usize { return self.extent.width * self.extent.height * self.extent.depth * pixel_size; } +pub inline fn getFormatPixelSize(format: vk.Format) usize { + return lib.vku.vkuFormatTexelBlockSize(@intCast(@intFromEnum(format))); +} + +pub inline fn getFormatTotalSize(self: *Self, format: vk.Format) usize { + const pixel_size = self.getFormatPixelSize(format); + return self.extent.width * self.extent.height * self.extent.depth * pixel_size; +} + pub fn formatSupportsColorAttachemendBlend(format: vk.Format) bool { return switch (format) { // Vulkan 1.1 mandatory diff --git a/src/vulkan/commands.zig b/src/vulkan/commands.zig index aa49fa5..b172500 100644 --- a/src/vulkan/commands.zig +++ b/src/vulkan/commands.zig @@ -10,6 +10,7 @@ pub const CommandType = enum { ClearColorImage, CopyBuffer, CopyImage, + CopyImageToBuffer, Draw, DrawIndexed, DrawIndexedIndirect, @@ -43,6 +44,12 @@ pub const CommandCopyImage = struct { dst_layout: vk.ImageLayout, regions: []const vk.ImageCopy, }; +pub const CommandCopyImageToBuffer = struct { + src: *Image, + src_layout: vk.ImageLayout, + dst: *Buffer, + regions: []const vk.BufferImageCopy, +}; pub const CommandDraw = struct { vertex_count: u32, instance_count: u32, @@ -81,6 +88,7 @@ pub const Command = union(CommandType) { ClearColorImage: CommandClearColorImage, CopyBuffer: CommandCopyBuffer, CopyImage: CommandCopyImage, + CopyImageToBuffer: CommandCopyImageToBuffer, Draw: CommandDraw, DrawIndexed: CommandDrawIndexed, DrawIndexedIndirect: CommandDrawIndexedIndirect, diff --git a/src/vulkan/lib_vulkan.zig b/src/vulkan/lib_vulkan.zig index 9290d1f..a286fb4 100644 --- a/src/vulkan/lib_vulkan.zig +++ b/src/vulkan/lib_vulkan.zig @@ -1839,15 +1839,7 @@ pub export fn strollCmdCopyImageToBuffer(p_cmd: vk.CommandBuffer, p_src: vk.Imag const cmd = Dispatchable(CommandBuffer).fromHandleObject(p_cmd) catch |err| return errorLogger(err); const src = NonDispatchable(Image).fromHandleObject(p_src) catch |err| return errorLogger(err); const dst = NonDispatchable(Buffer).fromHandleObject(p_dst) catch |err| return errorLogger(err); - - notImplementedWarning(); - - _ = cmd; - _ = src; - _ = dst; - _ = layout; - _ = count; - _ = regions; + cmd.copyImageToBuffer(src, layout, dst, regions[0..count]) catch |err| return errorLogger(err); } pub export fn strollCmdCopyQueryPoolResults(p_cmd: vk.CommandBuffer, p_pool: vk.QueryPool, first: u32, count: u32, p_dst: vk.Buffer, offset: vk.DeviceSize, stride: vk.DeviceSize, flags: vk.QueryResultFlags) callconv(vk.vulkan_call_conv) void {