From 8a641adb8e7f8191fc699e0c16f30e4aabb9ab55 Mon Sep 17 00:00:00 2001 From: Kbz-8 Date: Sat, 20 Dec 2025 00:00:42 +0100 Subject: [PATCH] adding software blitter base --- src/soft/Executor.zig | 20 ++++---------------- src/soft/SoftCommandBuffer.zig | 4 ++-- src/soft/SoftDevice.zig | 3 +++ src/soft/SoftImage.zig | 19 +++++++++++++++++++ src/soft/device/Blitter.zig | 27 +++++++++++++++++++++++++++ src/vulkan/CommandBuffer.zig | 19 ++++++++++--------- src/vulkan/Image.zig | 20 ++++++++++++++++++++ src/vulkan/commands.zig | 2 +- src/vulkan/lib.zig | 9 +++++++++ 9 files changed, 95 insertions(+), 28 deletions(-) create mode 100644 src/soft/device/Blitter.zig diff --git a/src/soft/Executor.zig b/src/soft/Executor.zig index 1b148ec..c595c7d 100644 --- a/src/soft/Executor.zig +++ b/src/soft/Executor.zig @@ -2,6 +2,8 @@ const std = @import("std"); const vk = @import("vulkan"); const base = @import("base"); +const SoftImage = @import("SoftImage.zig"); + const cmd = base.commands; const VkError = base.VkError; @@ -27,22 +29,8 @@ pub fn dispatch(self: *Self, command: *const cmd.Command) VkError!void { } fn clearColorImage(data: *const cmd.CommandClearColorImage) VkError!void { - // TODO: use a blitter - - const image = data.image; - for (data.ranges) |range| { - const image_size = image.getTotalSize(); - - const memory = if (image.memory) |memory| memory else return VkError.ValidationFailed; - var memory_map: []u32 = @as([*]u32, @ptrCast(@alignCast(try memory.map(0, image_size))))[0..image_size]; - - _ = range; - _ = &memory_map; - - base.logger.fixme("Implement image clear", .{}); - - memory.unmap(); - } + const soft_image: *SoftImage = @alignCast(@fieldParentPtr("interface", data.image)); + soft_image.clearRange(data.clear_color, data.range); } fn copyBuffer(data: *const cmd.CommandCopyBuffer) VkError!void { diff --git a/src/soft/SoftCommandBuffer.zig b/src/soft/SoftCommandBuffer.zig index ba02448..36e7dbf 100644 --- a/src/soft/SoftCommandBuffer.zig +++ b/src/soft/SoftCommandBuffer.zig @@ -63,13 +63,13 @@ pub fn reset(interface: *Interface, flags: vk.CommandBufferResetFlags) VkError!v // Commands ==================================================================================================== -pub fn clearColorImage(interface: *Interface, image: *base.Image, layout: vk.ImageLayout, color: *const vk.ClearColorValue, ranges: []const vk.ImageSubresourceRange) VkError!void { +pub fn clearColorImage(interface: *Interface, image: *base.Image, layout: vk.ImageLayout, color: *const vk.ClearColorValue, range: vk.ImageSubresourceRange) VkError!void { // No-op _ = interface; _ = image; _ = layout; _ = color; - _ = ranges; + _ = range; } pub fn fillBuffer(interface: *Interface, buffer: *base.Buffer, offset: vk.DeviceSize, size: vk.DeviceSize, data: u32) VkError!void { diff --git a/src/soft/SoftDevice.zig b/src/soft/SoftDevice.zig index f69d545..752ca7a 100644 --- a/src/soft/SoftDevice.zig +++ b/src/soft/SoftDevice.zig @@ -6,6 +6,7 @@ const builtin = @import("builtin"); const Debug = std.builtin.OptimizeMode.Debug; const SoftQueue = @import("SoftQueue.zig"); +const Blitter = @import("device/Blitter.zig"); pub const SoftBinarySemaphore = @import("SoftBinarySemaphore.zig"); pub const SoftBuffer = @import("SoftBuffer.zig"); @@ -38,6 +39,7 @@ const SpawnError = std.Thread.SpawnError; interface: Interface, device_allocator: if (builtin.mode == Debug) std.heap.DebugAllocator(.{}) else std.heap.ThreadSafeAllocator, workers: std.Thread.Pool, +blitter: Blitter, pub fn create(physical_device: *base.PhysicalDevice, allocator: std.mem.Allocator, info: *const vk.DeviceCreateInfo) VkError!*Self { const self = allocator.create(Self) catch return VkError.OutOfHostMemory; @@ -78,6 +80,7 @@ pub fn create(physical_device: *base.PhysicalDevice, allocator: std.mem.Allocato .interface = interface, .device_allocator = if (builtin.mode == Debug) .init else .{ .child_allocator = std.heap.c_allocator }, // TODO: better device allocator .workers = undefined, + .blitter = .init, }; self.workers.init(.{ .allocator = self.device_allocator.allocator() }) catch |err| return switch (err) { diff --git a/src/soft/SoftImage.zig b/src/soft/SoftImage.zig index c092999..e652070 100644 --- a/src/soft/SoftImage.zig +++ b/src/soft/SoftImage.zig @@ -7,6 +7,8 @@ const lib = @import("lib.zig"); const VkError = base.VkError; const Device = base.Device; +const SoftDevice = @import("SoftDevice.zig"); + const Self = @This(); pub const Interface = base.Image; @@ -38,3 +40,20 @@ pub fn getMemoryRequirements(interface: *Interface, requirements: *vk.MemoryRequ _ = interface; 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 { + const soft_device: *SoftDevice = @alignCast(@fieldParentPtr("interface", self.interface.owner)); + soft_device.blitter.clear(pixel, format, self, view_format, range, area); +} + +pub fn clearRange(self: *Self, color: vk.ClearColorValue, range: vk.ImageSubresourceRange) void { + std.debug.assert(range.aspect_mask == vk.ImageAspectFlags{ .color_bit = true }); + + const clear_format: vk.Format = if (base.vku.vkuFormatIsSINT(@intCast(@intFromEnum(self.interface.format)))) + .r32g32b32a32_sint + else if (base.vku.vkuFormatIsUINT(@intCast(@intFromEnum(self.interface.format)))) + .r32g32b32a32_uint + else + .r32g32b32a32_sfloat; + self.clear(@ptrCast(&color.float_32), clear_format, self.interface.format, range, null); +} diff --git a/src/soft/device/Blitter.zig b/src/soft/device/Blitter.zig new file mode 100644 index 0000000..6dc11d6 --- /dev/null +++ b/src/soft/device/Blitter.zig @@ -0,0 +1,27 @@ +const std = @import("std"); +const vk = @import("vulkan"); +const base = @import("base"); + +pub const SoftImage = @import("../SoftImage.zig"); +pub const SoftImageView = @import("../SoftImageView.zig"); + +const Self = @This(); + +blit_mutex: std.Thread.Mutex, + +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 { + const dst_format = base.Image.formatFromAspect(view_format, range.aspect_mask); + if (dst_format == .undefined) { + return; + } + + _ = self; + _ = pixel; + _ = format; + _ = dest; + _ = area; +} diff --git a/src/vulkan/CommandBuffer.zig b/src/vulkan/CommandBuffer.zig index 88aac56..328c366 100644 --- a/src/vulkan/CommandBuffer.zig +++ b/src/vulkan/CommandBuffer.zig @@ -40,7 +40,7 @@ dispatch_table: *const DispatchTable, pub const DispatchTable = struct { begin: *const fn (*Self, *const vk.CommandBufferBeginInfo) VkError!void, - clearColorImage: *const fn (*Self, *Image, vk.ImageLayout, *const vk.ClearColorValue, []const vk.ImageSubresourceRange) VkError!void, + 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, end: *const fn (*Self) VkError!void, @@ -122,7 +122,6 @@ fn cleanCommandList(self: *Self) void { const allocator = self.host_allocator.allocator(); for (self.commands.items) |command| { switch (command) { - .ClearColorImage => |data| allocator.free(data.ranges), .CopyBuffer => |data| allocator.free(data.regions), .CopyImage => |data| allocator.free(data.regions), else => {}, @@ -134,13 +133,15 @@ fn cleanCommandList(self: *Self) void { pub inline fn clearColorImage(self: *Self, image: *Image, layout: vk.ImageLayout, color: *const vk.ClearColorValue, ranges: []const vk.ImageSubresourceRange) VkError!void { const allocator = self.host_allocator.allocator(); - self.commands.append(allocator, .{ .ClearColorImage = .{ - .image = image, - .layout = layout, - .clear_color = color.*, - .ranges = allocator.dupe(vk.ImageSubresourceRange, ranges) catch return VkError.OutOfHostMemory, - } }) catch return VkError.OutOfHostMemory; - try self.dispatch_table.clearColorImage(self, image, layout, color, ranges); + for (ranges) |range| { + self.commands.append(allocator, .{ .ClearColorImage = .{ + .image = image, + .layout = layout, + .clear_color = color.*, + .range = range, + } }) catch return VkError.OutOfHostMemory; + try self.dispatch_table.clearColorImage(self, image, layout, color, range); + } } pub inline fn copyBuffer(self: *Self, src: *Buffer, dst: *Buffer, regions: []const vk.BufferCopy) VkError!void { diff --git a/src/vulkan/Image.zig b/src/vulkan/Image.zig index d913de0..7aa5c9f 100644 --- a/src/vulkan/Image.zig +++ b/src/vulkan/Image.zig @@ -125,3 +125,23 @@ pub fn formatSupportsColorAttachemendBlend(format: vk.Format) bool { else => false, }; } + +pub fn formatFromAspect(base_format: vk.Format, aspect: vk.ImageAspectFlags) vk.Format { + if (aspect.color_bit or (aspect.color_bit and aspect.stencil_bit)) { + return base_format; + } else if (aspect.depth_bit) { + if (base_format == .d16_unorm or base_format == .d16_unorm_s8_uint) { + return .d16_unorm; + } else if (base_format == .d24_unorm_s8_uint) { + return .x8_d24_unorm_pack32; + } else if (base_format == .d32_sfloat or base_format == .d32_sfloat_s8_uint) { + return .d32_sfloat; + } + } else if (aspect.stencil_bit) { + if (base_format == .s8_uint or base_format == .d16_unorm_s8_uint or base_format == .d24_unorm_s8_uint or base_format == .d32_sfloat_s8_uint) { + return .s8_uint; + } + } + lib.unsupported("format {d}", .{@intFromEnum(base_format)}); + return base_format; +} diff --git a/src/vulkan/commands.zig b/src/vulkan/commands.zig index 273405a..aa49fa5 100644 --- a/src/vulkan/commands.zig +++ b/src/vulkan/commands.zig @@ -29,7 +29,7 @@ pub const CommandClearColorImage = struct { image: *Image, layout: vk.ImageLayout, clear_color: vk.ClearColorValue, - ranges: []const vk.ImageSubresourceRange, + range: vk.ImageSubresourceRange, }; pub const CommandCopyBuffer = struct { src: *Buffer, diff --git a/src/vulkan/lib.zig b/src/vulkan/lib.zig index 7cf67f7..d597482 100644 --- a/src/vulkan/lib.zig +++ b/src/vulkan/lib.zig @@ -1,6 +1,7 @@ //! Here lies the documentation of the common internal API that backends need to implement const std = @import("std"); +const builtin = @import("builtin"); const vk = @import("vulkan"); pub const vku = @cImport({ @cInclude("vulkan/utility/vk_format_utils.h"); @@ -79,6 +80,14 @@ pub inline fn getLogVerboseLevel() LogVerboseLevel { .Standard; } +pub fn unsupported(comptime fmt: []const u8, args: anytype) void { + if (builtin.mode == std.builtin.OptimizeMode.Debug) { + std.debug.panic("UNSUPPORTED " ++ fmt, args); + } else { + std.log.scoped(.UNSUPPORTED).warn(fmt, args); + } +} + comptime { _ = lib_vulkan; }