From bfc5c3933c27bbab29d69ea78ede0fc9cd00fe53 Mon Sep 17 00:00:00 2001 From: Kbz-8 Date: Sat, 4 Jul 2026 01:17:18 +0200 Subject: [PATCH] adding BC compressed format support --- build.zig.zon | 4 +- src/software/SoftImage.zig | 58 ++++-- src/software/SoftPhysicalDevice.zig | 62 +++++-- src/software/device/compressed.zig | 273 ++++++++++++++++++++++++++++ src/software/device/fragment.zig | 8 +- src/vulkan/format.zig | 150 ++++++++++++++- 6 files changed, 514 insertions(+), 41 deletions(-) create mode 100644 src/software/device/compressed.zig diff --git a/build.zig.zon b/build.zig.zon index f961366..4db0e6c 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -32,8 +32,8 @@ // Soft dependencies .SPIRV_Interpreter = .{ - .url = "git+https://github.com/Kbz-8/SPIRV-Interpreter#50a2de5bb756f96f0e51277fb98825a6fe829433", - .hash = "SPIRV_Interpreter-0.0.1-ajmpnwGMCQBT5uWJ847m9Mpa-0NrGaugEN0FewoXgC_2", + .url = "git+https://github.com/Kbz-8/SPIRV-Interpreter#6e1fa9db32f3e596e30d60f94b2909f3609ec03a", + .hash = "SPIRV_Interpreter-0.0.1-ajmpn_CXCQB0GIcZz8ioOFOW9yLr5Ka-Qir3v3Y1r5Bi", .lazy = true, }, //.SPIRV_Interpreter = .{ diff --git a/src/software/SoftImage.zig b/src/software/SoftImage.zig index 925a02c..63bf4e5 100644 --- a/src/software/SoftImage.zig +++ b/src/software/SoftImage.zig @@ -55,6 +55,7 @@ const vk = @import("vulkan"); const base = @import("base"); const lib = @import("lib.zig"); const blitter = @import("device/blitter.zig"); +const compressed = @import("device/compressed.zig"); const F32x4 = blitter.F32x4; const U32x4 = blitter.U32x4; @@ -156,9 +157,13 @@ pub fn copyToImageSingleAspect(self: *const Self, dst: *Self, region: vk.ImageCo const src_format = self.interface.formatFromAspect(region.src_subresource.aspect_mask); const bytes_per_block = base.format.texelSize(src_format); + const block_width = base.format.blockWidth(src_format); + const block_height = base.format.blockHeight(src_format); const src_extent = self.getMipLevelExtent(region.src_subresource.mip_level); const dst_extent = dst.getMipLevelExtent(region.dst_subresource.mip_level); + const copy_block_width = base.format.blockCountX(src_format, region.extent.width); + const copy_block_height = base.format.blockCountY(src_format, region.extent.height); const one_is_3D = (self.interface.image_type == .@"3d") != (dst.interface.image_type == .@"3d"); const both_are_3D = (self.interface.image_type == .@"3d") and (dst.interface.image_type == .@"3d"); @@ -178,12 +183,16 @@ pub fn copyToImageSingleAspect(self: *const Self, dst: *Self, region: vk.ImageCo const slice_count = if (both_are_3D) region.extent.depth else self.interface.samples.toInt(); const is_single_slice = (slice_count == 1); - const is_single_row = (region.extent.height == 1) and is_single_slice; - const is_entire_row = (region.extent.width == src_extent.width) and (region.extent.width == dst_extent.width); + const is_single_row = (copy_block_height == 1) and is_single_slice; + const is_entire_row = (region.extent.width == src_extent.width) and (region.extent.width == dst_extent.width) and + (@mod(@as(usize, @intCast(region.src_offset.x)), block_width) == 0) and + (@mod(@as(usize, @intCast(region.dst_offset.x)), block_width) == 0); const is_entire_slice = is_entire_row and (region.extent.height == src_extent.height) and (region.extent.height == dst_extent.height) and + (@mod(@as(usize, @intCast(region.src_offset.y)), block_height) == 0) and + (@mod(@as(usize, @intCast(region.dst_offset.y)), block_height) == 0) and (src_depth_pitch_bytes == dst_depth_pitch_bytes); const src_texel_offset = try self.getTexelMemoryOffset(region.src_offset, .{ @@ -202,12 +211,12 @@ pub fn copyToImageSingleAspect(self: *const Self, dst: *Self, region: vk.ImageCo for (0..layer_count) |_| { if (is_single_row) { - const copy_size = region.extent.width * bytes_per_block; + const copy_size = copy_block_width * bytes_per_block; if (dst_map.len < copy_size or src_map.len < copy_size) break; @memcpy(dst_map[0..copy_size], src_map[0..copy_size]); } else if (is_entire_row and is_single_slice) { - const copy_size = region.extent.height * src_row_pitch_bytes; + const copy_size = copy_block_height * src_row_pitch_bytes; if (dst_map.len < copy_size or src_map.len < copy_size) break; @memcpy(dst_map[0..copy_size], src_map[0..copy_size]); @@ -217,7 +226,7 @@ pub fn copyToImageSingleAspect(self: *const Self, dst: *Self, region: vk.ImageCo break; @memcpy(dst_map[0..copy_size], src_map[0..copy_size]); } else if (is_entire_row) { - const slice_size = region.extent.height * src_row_pitch_bytes; + const slice_size = copy_block_height * src_row_pitch_bytes; var src_slice_memory = src_map[0..]; var dst_slice_memory = dst_map[0..]; @@ -229,7 +238,7 @@ pub fn copyToImageSingleAspect(self: *const Self, dst: *Self, region: vk.ImageCo dst_slice_memory = if (dst_slice_memory.len < dst_depth_pitch_bytes) break else dst_slice_memory[dst_depth_pitch_bytes..]; } } else { - const row_size = region.extent.width * bytes_per_block; + const row_size = copy_block_width * bytes_per_block; var src_slice_memory = src_map[0..]; var dst_slice_memory = dst_map[0..]; @@ -237,7 +246,7 @@ pub fn copyToImageSingleAspect(self: *const Self, dst: *Self, region: vk.ImageCo var src_row_memory = src_slice_memory[0..]; var dst_row_memory = dst_slice_memory[0..]; - for (0..region.extent.height) |_| { + for (0..copy_block_height) |_| { if (dst_row_memory.len < row_size or src_row_memory.len < row_size) break; @memcpy(dst_row_memory[0..row_size], src_row_memory[0..row_size]); @@ -323,8 +332,12 @@ pub fn copy( }; const bytes_per_block = base.format.texelSize(format); - const memory_row_pitch_bytes = extent.width * bytes_per_block; - const memory_slice_pitch_bytes = extent.height * memory_row_pitch_bytes; + const copy_block_width = base.format.blockCountX(format, image_extent.width); + const copy_block_height = base.format.blockCountY(format, image_extent.height); + const memory_block_width = base.format.blockCountX(format, extent.width); + const memory_block_height = base.format.blockCountY(format, extent.height); + const memory_row_pitch_bytes = memory_block_width * bytes_per_block; + const memory_slice_pitch_bytes = memory_block_height * memory_row_pitch_bytes; const image_texel_offset = try self.getTexelMemoryOffset(image_offset, .{ .aspect_mask = image_subresource.aspect_mask, @@ -346,7 +359,7 @@ pub fn copy( const layer_count = if (image_subresource.layer_count == vk.REMAINING_ARRAY_LAYERS) self.interface.array_layers - image_subresource.base_array_layer else image_subresource.layer_count; - const copy_size = image_extent.width * bytes_per_block; + const copy_size = copy_block_width * bytes_per_block; for (0..layer_count) |_| { var src_layer_memory = src_memory[0..]; @@ -356,7 +369,7 @@ pub fn copy( var src_slice_memory = src_layer_memory[0..]; var dst_slice_memory = dst_layer_memory[0..]; - for (0..image_extent.height) |_| { + for (0..copy_block_height) |_| { if (dst_slice_memory.len < copy_size or src_slice_memory.len < copy_size) break; @memcpy(dst_slice_memory[0..copy_size], src_slice_memory[0..copy_size]); @@ -375,6 +388,14 @@ pub fn readFloat4(self: *Self, offset: vk.Offset3D, subresource: vk.ImageSubreso const texel_size = base.format.texelSize(format); const texel_offset = try self.getTexelMemoryOffset(offset, subresource); const map = try self.mapAsSliceWithAddedOffset(u8, texel_offset, texel_size); + if (base.format.isCompressed(format)) { + return compressed.readFloat4( + map, + format, + @mod(@as(usize, @intCast(offset.x)), base.format.blockWidth(format)), + @mod(@as(usize, @intCast(offset.y)), base.format.blockHeight(format)), + ); + } return blitter.readFloat4(map, format); } @@ -389,6 +410,16 @@ pub fn writeFloat4(self: *Self, offset: vk.Offset3D, subresource: vk.ImageSubres const texel_size = base.format.texelSize(format); const texel_offset = try self.getTexelMemoryOffset(offset, subresource); const map = try self.mapAsSliceWithAddedOffset(u8, texel_offset, texel_size); + if (base.format.isCompressed(format)) { + compressed.writeFloat4( + map, + format, + @mod(@as(usize, @intCast(offset.x)), base.format.blockWidth(format)), + @mod(@as(usize, @intCast(offset.y)), base.format.blockHeight(format)), + pixel, + ); + return; + } blitter.writeFloat4(pixel, map, format); } @@ -400,9 +431,10 @@ pub fn writeInt4(self: *Self, offset: vk.Offset3D, subresource: vk.ImageSubresou } pub fn getTexelMemoryOffsetInSubresource(self: *const Self, offset: vk.Offset3D, subresource: vk.ImageSubresource) usize { + const format = base.format.fromAspect(self.interface.format, subresource.aspect_mask); return @as(usize, @intCast(offset.z)) * self.interface.getSliceMemSizeForMipLevel(subresource.aspect_mask, subresource.mip_level) + - @as(usize, @intCast(offset.y)) * self.interface.getRowPitchMemSizeForMipLevel(subresource.aspect_mask, subresource.mip_level) + - @as(usize, @intCast(offset.x)) * base.format.texelSize(base.format.fromAspect(self.interface.format, subresource.aspect_mask)); + @divFloor(@as(usize, @intCast(offset.y)), base.format.blockHeight(format)) * self.interface.getRowPitchMemSizeForMipLevel(subresource.aspect_mask, subresource.mip_level) + + @divFloor(@as(usize, @intCast(offset.x)), base.format.blockWidth(format)) * base.format.texelSize(format); } pub fn getTexelMemoryOffset(self: *const Self, offset: vk.Offset3D, subresource: vk.ImageSubresource) VkError!usize { diff --git a/src/software/SoftPhysicalDevice.zig b/src/software/SoftPhysicalDevice.zig index ab9b4aa..1dc6a16 100644 --- a/src/software/SoftPhysicalDevice.zig +++ b/src/software/SoftPhysicalDevice.zig @@ -335,22 +335,22 @@ pub fn getFormatProperties(interface: *Interface, format: vk.Format) VkError!vk. .r32g32b32a32_sfloat, .b10g11r11_ufloat_pack32, .e5b9g9r9_ufloat_pack32, - //.bc1_rgb_unorm_block, - //.bc1_rgb_srgb_block, - //.bc1_rgba_unorm_block, - //.bc1_rgba_srgb_block, - //.bc2_unorm_block, - //.bc2_srgb_block, - //.bc3_unorm_block, - //.bc3_srgb_block, - //.bc4_unorm_block, - //.bc4_snorm_block, - //.bc5_unorm_block, - //.bc5_snorm_block, - //.bc6h_ufloat_block, - //.bc6h_sfloat_block, - //.bc7_unorm_block, - //.bc7_srgb_block, + .bc1_rgb_unorm_block, + .bc1_rgb_srgb_block, + .bc1_rgba_unorm_block, + .bc1_rgba_srgb_block, + .bc2_unorm_block, + .bc2_srgb_block, + .bc3_unorm_block, + .bc3_srgb_block, + .bc4_unorm_block, + .bc4_snorm_block, + .bc5_unorm_block, + .bc5_snorm_block, + .bc6h_ufloat_block, + .bc6h_sfloat_block, + .bc7_unorm_block, + .bc7_srgb_block, //.etc2_r8g8b8_unorm_block, //.etc2_r8g8b8_srgb_block, //.etc2_r8g8b8a1_unorm_block, @@ -446,6 +446,33 @@ pub fn getFormatProperties(interface: *Interface, format: vk.Format) VkError!vk. else => {}, } + switch (format) { + .bc1_rgb_unorm_block, + .bc1_rgb_srgb_block, + .bc1_rgba_unorm_block, + .bc1_rgba_srgb_block, + .bc2_unorm_block, + .bc2_srgb_block, + .bc3_unorm_block, + .bc3_srgb_block, + .bc4_unorm_block, + .bc4_snorm_block, + .bc5_unorm_block, + .bc5_snorm_block, + .bc6h_ufloat_block, + .bc6h_sfloat_block, + .bc7_unorm_block, + .bc7_srgb_block, + => { + properties.optimal_tiling_features.blit_src_bit = true; + properties.optimal_tiling_features.sampled_image_bit = true; + properties.optimal_tiling_features.sampled_image_filter_linear_bit = true; + properties.optimal_tiling_features.transfer_dst_bit = true; + properties.optimal_tiling_features.transfer_src_bit = true; + }, + else => {}, + } + switch (format) { // Vulkan 1.0 mandatory storage image formats supporting atomic operations .r32_uint, @@ -819,6 +846,9 @@ fn isFormatSupported( tiling: vk.ImageTiling, usage: vk.ImageUsageFlags, ) VkError!bool { + if (base.format.isCompressed(format) and (usage.transfer_src_bit or usage.transfer_dst_bit)) + return false; + const format_properties = try self.interface.getFormatProperties(format); const format_features = switch (tiling) { .linear => format_properties.linear_tiling_features, diff --git a/src/software/device/compressed.zig b/src/software/device/compressed.zig new file mode 100644 index 0000000..683954f --- /dev/null +++ b/src/software/device/compressed.zig @@ -0,0 +1,273 @@ +const std = @import("std"); +const vk = @import("vulkan"); +const base = @import("base"); +const zm = base.zm; + +pub const F32x4 = zm.F32x4; + +pub fn isBc1(format: vk.Format) bool { + return switch (format) { + .bc1_rgb_unorm_block, + .bc1_rgb_srgb_block, + .bc1_rgba_unorm_block, + .bc1_rgba_srgb_block, + => true, + else => false, + }; +} + +pub fn readFloat4(block: []const u8, format: vk.Format, x_in_block: usize, y_in_block: usize) F32x4 { + return switch (format) { + .bc1_rgb_unorm_block, + .bc1_rgb_srgb_block, + .bc1_rgba_unorm_block, + .bc1_rgba_srgb_block, + => readBc1(block, format, x_in_block, y_in_block), + .bc4_unorm_block, + .bc4_snorm_block, + => .{ readBc4(block, format, x_in_block, y_in_block), 0.0, 0.0, 1.0 }, + .bc5_unorm_block, + .bc5_snorm_block, + => readBc5(block, format, x_in_block, y_in_block), + else => blk: { + base.unsupported("Compressed read from format {any}", .{format}); + break :blk .{ 0.0, 0.0, 0.0, 1.0 }; + }, + }; +} + +pub fn writeFloat4(block: []u8, format: vk.Format, x_in_block: usize, y_in_block: usize, color: F32x4) void { + switch (format) { + .bc1_rgb_unorm_block, + .bc1_rgb_srgb_block, + .bc1_rgba_unorm_block, + .bc1_rgba_srgb_block, + => writeBc1Texel(block, format, x_in_block, y_in_block, color), + else => base.unsupported("Compressed write to format {any}", .{format}), + } +} + +fn readBc1(block: []const u8, format: vk.Format, x_in_block: usize, y_in_block: usize) F32x4 { + std.debug.assert(block.len >= 8); + std.debug.assert(x_in_block < 4); + std.debug.assert(y_in_block < 4); + + const color_0 = std.mem.bytesToValue(u16, block[0..2]); + const color_1 = std.mem.bytesToValue(u16, block[2..4]); + const selectors = std.mem.bytesToValue(u32, block[4..8]); + const selector_shift: u5 = @intCast(2 * (y_in_block * 4 + x_in_block)); + const selector = (selectors >> selector_shift) & 0x3; + const has_alpha = format == .bc1_rgba_unorm_block or format == .bc1_rgba_srgb_block; + + var palette: [4]F32x4 = undefined; + palette[0] = decodeRgb565(color_0); + palette[1] = decodeRgb565(color_1); + + if (color_0 > color_1 or !has_alpha) { + palette[2] = mix(palette[0], palette[1], 2.0 / 3.0, 1.0 / 3.0, 1.0); + palette[3] = mix(palette[0], palette[1], 1.0 / 3.0, 2.0 / 3.0, 1.0); + } else { + palette[2] = mix(palette[0], palette[1], 0.5, 0.5, 1.0); + palette[3] = .{ 0.0, 0.0, 0.0, 0.0 }; + } + + return palette[@intCast(selector)]; +} + +fn readBc4(block: []const u8, format: vk.Format, x_in_block: usize, y_in_block: usize) f32 { + std.debug.assert(block.len >= 8); + std.debug.assert(x_in_block < 4); + std.debug.assert(y_in_block < 4); + + const selector_shift: u6 = @intCast(3 * (y_in_block * 4 + x_in_block)); + const selectors = std.mem.bytesToValue(u64, block[0..8]) >> 16; + const selector: usize = @intCast((selectors >> selector_shift) & 0x7); + + return switch (format) { + .bc4_unorm_block, + .bc5_unorm_block, + => readBc4Unorm(block, selector), + .bc4_snorm_block, + .bc5_snorm_block, + => readBc4Snorm(block, selector), + else => unreachable, + }; +} + +fn readBc5(block: []const u8, format: vk.Format, x_in_block: usize, y_in_block: usize) F32x4 { + std.debug.assert(block.len >= 16); + return .{ + readBc4(block[0..8], format, x_in_block, y_in_block), + readBc4(block[8..16], format, x_in_block, y_in_block), + 0.0, + 1.0, + }; +} + +fn readBc4Unorm(block: []const u8, selector: usize) f32 { + const endpoint_0 = @as(f32, @floatFromInt(block[0])) / 255.0; + const endpoint_1 = @as(f32, @floatFromInt(block[1])) / 255.0; + + var palette: [8]f32 = undefined; + palette[0] = endpoint_0; + palette[1] = endpoint_1; + + if (block[0] > block[1]) { + palette[2] = (6.0 * endpoint_0 + 1.0 * endpoint_1) / 7.0; + palette[3] = (5.0 * endpoint_0 + 2.0 * endpoint_1) / 7.0; + palette[4] = (4.0 * endpoint_0 + 3.0 * endpoint_1) / 7.0; + palette[5] = (3.0 * endpoint_0 + 4.0 * endpoint_1) / 7.0; + palette[6] = (2.0 * endpoint_0 + 5.0 * endpoint_1) / 7.0; + palette[7] = (1.0 * endpoint_0 + 6.0 * endpoint_1) / 7.0; + } else { + palette[2] = (4.0 * endpoint_0 + 1.0 * endpoint_1) / 5.0; + palette[3] = (3.0 * endpoint_0 + 2.0 * endpoint_1) / 5.0; + palette[4] = (2.0 * endpoint_0 + 3.0 * endpoint_1) / 5.0; + palette[5] = (1.0 * endpoint_0 + 4.0 * endpoint_1) / 5.0; + palette[6] = 0.0; + palette[7] = 1.0; + } + + return palette[selector]; +} + +fn readBc4Snorm(block: []const u8, selector: usize) f32 { + const raw_0: i8 = @bitCast(block[0]); + const raw_1: i8 = @bitCast(block[1]); + const endpoint_0 = decodeBcSnorm(raw_0); + const endpoint_1 = decodeBcSnorm(raw_1); + + var palette: [8]f32 = undefined; + palette[0] = endpoint_0; + palette[1] = endpoint_1; + + if (raw_0 > raw_1) { + palette[2] = (6.0 * endpoint_0 + 1.0 * endpoint_1) / 7.0; + palette[3] = (5.0 * endpoint_0 + 2.0 * endpoint_1) / 7.0; + palette[4] = (4.0 * endpoint_0 + 3.0 * endpoint_1) / 7.0; + palette[5] = (3.0 * endpoint_0 + 4.0 * endpoint_1) / 7.0; + palette[6] = (2.0 * endpoint_0 + 5.0 * endpoint_1) / 7.0; + palette[7] = (1.0 * endpoint_0 + 6.0 * endpoint_1) / 7.0; + } else { + palette[2] = (4.0 * endpoint_0 + 1.0 * endpoint_1) / 5.0; + palette[3] = (3.0 * endpoint_0 + 2.0 * endpoint_1) / 5.0; + palette[4] = (2.0 * endpoint_0 + 3.0 * endpoint_1) / 5.0; + palette[5] = (1.0 * endpoint_0 + 4.0 * endpoint_1) / 5.0; + palette[6] = -1.0; + palette[7] = 1.0; + } + + return palette[selector]; +} + +fn decodeBcSnorm(value: i8) f32 { + return @max(@as(f32, @floatFromInt(value)) / 127.0, -1.0); +} + +fn writeBc1Texel(block: []u8, format: vk.Format, x_in_block: usize, y_in_block: usize, color: F32x4) void { + var pixels: [16]F32x4 = undefined; + for (&pixels, 0..) |*pixel, i| { + pixel.* = readBc1(block, format, i & 3, i >> 2); + } + pixels[y_in_block * 4 + x_in_block] = color; + encodeBc1(block, format, pixels); +} + +pub fn encodeBc1(block: []u8, format: vk.Format, pixels: [16]F32x4) void { + std.debug.assert(block.len >= 8); + + var min_color: F32x4 = .{ 1.0, 1.0, 1.0, 1.0 }; + var max_color: F32x4 = .{ 0.0, 0.0, 0.0, 1.0 }; + var has_alpha = false; + + for (pixels) |pixel| { + if (pixel[3] < 0.5) { + has_alpha = true; + continue; + } + min_color[0] = @min(min_color[0], pixel[0]); + min_color[1] = @min(min_color[1], pixel[1]); + min_color[2] = @min(min_color[2], pixel[2]); + max_color[0] = @max(max_color[0], pixel[0]); + max_color[1] = @max(max_color[1], pixel[1]); + max_color[2] = @max(max_color[2], pixel[2]); + } + + var color_0 = encodeRgb565(max_color); + var color_1 = encodeRgb565(min_color); + const supports_alpha = format == .bc1_rgba_unorm_block or format == .bc1_rgba_srgb_block; + if (has_alpha and supports_alpha) { + if (color_0 > color_1) + std.mem.swap(u16, &color_0, &color_1); + } else if (color_0 <= color_1) { + std.mem.swap(u16, &color_0, &color_1); + } + + std.mem.bytesAsValue(u16, block[0..2]).* = color_0; + std.mem.bytesAsValue(u16, block[2..4]).* = color_1; + + var selectors: u32 = 0; + for (pixels, 0..) |pixel, i| { + const selector = nearestSelector(pixel, color_0, color_1, supports_alpha); + selectors |= @as(u32, selector) << @intCast(2 * i); + } + std.mem.bytesAsValue(u32, block[4..8]).* = selectors; +} + +fn nearestSelector(color: F32x4, color_0: u16, color_1: u16, supports_alpha: bool) u2 { + if (supports_alpha and color[3] < 0.5 and color_0 <= color_1) + return 3; + + var palette: [4]F32x4 = undefined; + palette[0] = decodeRgb565(color_0); + palette[1] = decodeRgb565(color_1); + if (color_0 > color_1 or !supports_alpha) { + palette[2] = mix(palette[0], palette[1], 2.0 / 3.0, 1.0 / 3.0, 1.0); + palette[3] = mix(palette[0], palette[1], 1.0 / 3.0, 2.0 / 3.0, 1.0); + } else { + palette[2] = mix(palette[0], palette[1], 0.5, 0.5, 1.0); + palette[3] = .{ 0.0, 0.0, 0.0, 0.0 }; + } + + var best_selector: u2 = 0; + var best_distance = std.math.floatMax(f32); + for (palette, 0..) |entry, i| { + if (i == 3 and supports_alpha and color_0 <= color_1 and color[3] >= 0.5) + continue; + const dr = color[0] - entry[0]; + const dg = color[1] - entry[1]; + const db = color[2] - entry[2]; + const distance = dr * dr + dg * dg + db * db; + if (distance < best_distance) { + best_distance = distance; + best_selector = @intCast(i); + } + } + return best_selector; +} + +fn decodeRgb565(value: u16) F32x4 { + return .{ + @as(f32, @floatFromInt((value >> 11) & 0x1f)) / 31.0, + @as(f32, @floatFromInt((value >> 5) & 0x3f)) / 63.0, + @as(f32, @floatFromInt(value & 0x1f)) / 31.0, + 1.0, + }; +} + +fn encodeRgb565(color: F32x4) u16 { + const clamped = std.math.clamp(color, zm.f32x4s(0.0), zm.f32x4s(1.0)); + const r: u16 = @intFromFloat(@round(clamped[0] * 31.0)); + const g: u16 = @intFromFloat(@round(clamped[1] * 63.0)); + const b: u16 = @intFromFloat(@round(clamped[2] * 31.0)); + return (r << 11) | (g << 5) | b; +} + +fn mix(a: F32x4, b: F32x4, a_weight: f32, b_weight: f32, alpha: f32) F32x4 { + return .{ + a[0] * a_weight + b[0] * b_weight, + a[1] * a_weight + b[1] * b_weight, + a[2] * a_weight + b[2] * b_weight, + alpha, + }; +} diff --git a/src/software/device/fragment.zig b/src/software/device/fragment.zig index 7a24b93..175df24 100644 --- a/src/software/device/fragment.zig +++ b/src/software/device/fragment.zig @@ -70,12 +70,8 @@ pub fn shaderInvocation( }; if (rt.getBuiltinResult(.FragCoord)) |frag_coord_word| { - const pixel_x: i32 = @intFromFloat(position[0]); - const pixel_y: i32 = @intFromFloat(position[1]); - const dx: f32 = if (@mod(pixel_x, 2) == 0) 1.0 else -1.0; - const dy: f32 = if (@mod(pixel_y, 2) == 0) 1.0 else -1.0; - const frag_coord_dx = zm.f32x4(dx, 0.0, 0.0, 0.0); - const frag_coord_dy = zm.f32x4(0.0, dy, 0.0, 0.0); + const frag_coord_dx = zm.f32x4(1.0, 0.0, 0.0, 0.0); + const frag_coord_dy = zm.f32x4(0.0, 1.0, 0.0, 0.0); try rt.setDerivativeFromMemory(allocator, frag_coord_word, std.mem.asBytes(&frag_coord_dx), std.mem.asBytes(&frag_coord_dy)); } diff --git a/src/vulkan/format.zig b/src/vulkan/format.zig index b0e411e..c202a9b 100644 --- a/src/vulkan/format.zig +++ b/src/vulkan/format.zig @@ -40,6 +40,150 @@ pub inline fn texelSize(format: vk.Format) usize { return lib.c.vkuFormatTexelBlockSize(@intCast(@intFromEnum(format))); } +pub inline fn isCompressed(format: vk.Format) bool { + return blockWidth(format) > 1 or blockHeight(format) > 1; +} + +pub inline fn blockWidth(format: vk.Format) usize { + return switch (format) { + .bc1_rgb_unorm_block, + .bc1_rgb_srgb_block, + .bc1_rgba_unorm_block, + .bc1_rgba_srgb_block, + .bc2_unorm_block, + .bc2_srgb_block, + .bc3_unorm_block, + .bc3_srgb_block, + .bc4_unorm_block, + .bc4_snorm_block, + .bc5_unorm_block, + .bc5_snorm_block, + .bc6h_ufloat_block, + .bc6h_sfloat_block, + .bc7_unorm_block, + .bc7_srgb_block, + .eac_r11_unorm_block, + .eac_r11_snorm_block, + .eac_r11g11_unorm_block, + .eac_r11g11_snorm_block, + .etc2_r8g8b8_unorm_block, + .etc2_r8g8b8_srgb_block, + .etc2_r8g8b8a1_unorm_block, + .etc2_r8g8b8a1_srgb_block, + .etc2_r8g8b8a8_unorm_block, + .etc2_r8g8b8a8_srgb_block, + .astc_4x_4_unorm_block, + .astc_4x_4_srgb_block, + => 4, + .astc_5x_4_unorm_block, + .astc_5x_5_unorm_block, + .astc_5x_4_srgb_block, + .astc_5x_5_srgb_block, + => 5, + .astc_6x_5_unorm_block, + .astc_6x_6_unorm_block, + .astc_6x_5_srgb_block, + .astc_6x_6_srgb_block, + => 6, + .astc_8x_5_unorm_block, + .astc_8x_6_unorm_block, + .astc_8x_8_unorm_block, + .astc_8x_5_srgb_block, + .astc_8x_6_srgb_block, + .astc_8x_8_srgb_block, + => 8, + .astc_1_0x_5_unorm_block, + .astc_1_0x_6_unorm_block, + .astc_1_0x_8_unorm_block, + .astc_1_0x_10_unorm_block, + .astc_1_0x_5_srgb_block, + .astc_1_0x_6_srgb_block, + .astc_1_0x_8_srgb_block, + .astc_1_0x_10_srgb_block, + => 10, + .astc_1_2x_10_unorm_block, + .astc_1_2x_12_unorm_block, + .astc_1_2x_10_srgb_block, + .astc_1_2x_12_srgb_block, + => 12, + else => 1, + }; +} + +pub inline fn blockHeight(format: vk.Format) usize { + return switch (format) { + .bc1_rgb_unorm_block, + .bc1_rgb_srgb_block, + .bc1_rgba_unorm_block, + .bc1_rgba_srgb_block, + .bc2_unorm_block, + .bc2_srgb_block, + .bc3_unorm_block, + .bc3_srgb_block, + .bc4_unorm_block, + .bc4_snorm_block, + .bc5_unorm_block, + .bc5_snorm_block, + .bc6h_ufloat_block, + .bc6h_sfloat_block, + .bc7_unorm_block, + .bc7_srgb_block, + .eac_r11_unorm_block, + .eac_r11_snorm_block, + .eac_r11g11_unorm_block, + .eac_r11g11_snorm_block, + .etc2_r8g8b8_unorm_block, + .etc2_r8g8b8_srgb_block, + .etc2_r8g8b8a1_unorm_block, + .etc2_r8g8b8a1_srgb_block, + .etc2_r8g8b8a8_unorm_block, + .etc2_r8g8b8a8_srgb_block, + .astc_4x_4_unorm_block, + .astc_5x_4_unorm_block, + .astc_4x_4_srgb_block, + .astc_5x_4_srgb_block, + => 4, + .astc_5x_5_unorm_block, + .astc_6x_5_unorm_block, + .astc_8x_5_unorm_block, + .astc_1_0x_5_unorm_block, + .astc_5x_5_srgb_block, + .astc_6x_5_srgb_block, + .astc_8x_5_srgb_block, + .astc_1_0x_5_srgb_block, + => 5, + .astc_6x_6_unorm_block, + .astc_8x_6_unorm_block, + .astc_1_0x_6_unorm_block, + .astc_6x_6_srgb_block, + .astc_8x_6_srgb_block, + .astc_1_0x_6_srgb_block, + => 6, + .astc_8x_8_unorm_block, + .astc_1_0x_8_unorm_block, + .astc_8x_8_srgb_block, + .astc_1_0x_8_srgb_block, + => 8, + .astc_1_0x_10_unorm_block, + .astc_1_2x_10_unorm_block, + .astc_1_0x_10_srgb_block, + .astc_1_2x_10_srgb_block, + => 10, + .astc_1_2x_12_unorm_block, + .astc_1_2x_12_srgb_block, + => 12, + else => 1, + }; +} + +pub inline fn blockCountX(format: vk.Format, width: usize) usize { + return std.math.divCeil(usize, width, blockWidth(format)) catch unreachable; +} + +pub inline fn blockCountY(format: vk.Format, height: usize) usize { + return std.math.divCeil(usize, height, blockHeight(format)) catch unreachable; +} + pub inline fn componentCount(format: vk.Format) usize { return @intCast(lib.c.vkuFormatComponentCount(@intCast(@intFromEnum(format)))); } @@ -83,13 +227,11 @@ pub fn supportsColorAttachemendBlend(format: vk.Format) bool { } pub inline fn pitchMemSize(format: vk.Format, width: usize) usize { - // To be updated for compressed formats handling - return texelSize(format) * width; + return texelSize(format) * blockCountX(format, width); } pub inline fn sliceMemSize(format: vk.Format, width: usize, height: usize) usize { - // To be updated for compressed formats handling - return pitchMemSize(format, width) * height; + return pitchMemSize(format, width) * blockCountY(format, height); } pub inline fn isDepthAndStencil(format: vk.Format) bool {