From 3a6ac6e418f0bfce4371e743750cdfe6fff22d2f Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Thu, 9 Jul 2026 05:29:50 +0200 Subject: [PATCH] [video_core] Implementation on ASTC HDR formats + Vulkan fixes (#4190) Based on the register of the ASTC implementation from Ameerj (yuzu), currently our conversion from ASTC on Desktop tends to duplicates the unpacking of ASTC converted textures, leading to bad performance; since there are some graphical effects that actually access on the band of 32bits (RGAF32) than the common 16bits (RGAF16) from our ASTC path decoding method, which commonly uses ARGB8 (degradation -> LDR) transformation to reduce the memory cost per conversion; meanwhile it could actually provide initially better performance; the engine tended to get stalled when this writted stage effect got skipped; mostly because CPU tends to look for an unimplemented texture/ format query (from which doesn't exist on our MaxwellToVK); unlike what's commonly believed HDR was a format to write enviroment effects even before their appearance on video output (since Switch doesn't have HDR, only SDR on certain engines). The test result on this PR showed games like Astral Chain and other heavy reliant on ASTC decoding improved their performance and stability by around 15%; meanwhile could it be tied to a hardware gain, it's clearly a gain for direct access on what's required on most games. This PR also contains few Vulkan bug fix I encountered along this implementation. Special Thanks: -> Mr. Smoly Gidolard (@gidoly) Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4190 --- src/video_core/host_shaders/astc_decoder.comp | 282 +++++++++++++- .../renderer_vulkan/vk_compute_pass.cpp | 1 + .../renderer_vulkan/vk_texture_cache.cpp | 71 +++- .../renderer_vulkan/vk_texture_cache.h | 2 + src/video_core/textures/astc.cpp | 354 +++++++++++++++++- src/video_core/vulkan_common/vulkan_device.h | 8 +- 6 files changed, 685 insertions(+), 33 deletions(-) diff --git a/src/video_core/host_shaders/astc_decoder.comp b/src/video_core/host_shaders/astc_decoder.comp index da21b4bde8..ce03e589ad 100644 --- a/src/video_core/host_shaders/astc_decoder.comp +++ b/src/video_core/host_shaders/astc_decoder.comp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -40,7 +43,11 @@ layout(binding = BINDING_INPUT_BUFFER, std430) readonly restrict buffer InputBuf uvec4 astc_data[]; }; +#ifdef VULKAN +layout(binding = BINDING_OUTPUT_IMAGE) uniform writeonly restrict image2DArray dest_image; +#else layout(binding = BINDING_OUTPUT_IMAGE, rgba8) uniform writeonly restrict image2DArray dest_image; +#endif const uint GOB_SIZE_X_SHIFT = 6; const uint GOB_SIZE_Y_SHIFT = 3; @@ -589,6 +596,158 @@ ivec4 BlueContract(int a, int r, int g, int b) { return ivec4(a, (r + b) >> 1, (g + b) >> 1, b); } +bool IsHDRColorEndpointMode(uint cem) { + return cem == 2u || cem == 3u || cem == 7u || cem == 11u || cem == 14u || cem == 15u; +} + +int SignExtend(int value, uint nbits) { + int sign_bit = 1 << (nbits - 1u); + return (value ^ sign_bit) - sign_bit; +} + +void DecodeHDREndpointMode7(uint v0, uint v1, uint v2, uint v3, out ivec3 e0, out ivec3 e1) { + uint modeval = ((v0 & 0xC0u) >> 6u) | ((v1 & 0x80u) >> 5u) | ((v2 & 0x80u) >> 4u); + + uint majcomp; + uint mode; + if ((modeval & 0xCu) != 0xCu) { + majcomp = modeval >> 2u; + mode = modeval & 3u; + } else if (modeval != 0xFu) { + majcomp = modeval & 3u; + mode = 4u; + } else { + majcomp = 0u; + mode = 5u; + } + + int red = int(v0 & 0x3Fu); + int green = int(v1 & 0x1Fu); + int blue = int(v2 & 0x1Fu); + int scale = int(v3 & 0x1Fu); + + uint x0 = (v1 >> 6u) & 1u; + uint x1 = (v1 >> 5u) & 1u; + uint x2 = (v2 >> 6u) & 1u; + uint x3 = (v2 >> 5u) & 1u; + uint x4 = (v3 >> 7u) & 1u; + uint x5 = (v3 >> 6u) & 1u; + uint x6 = (v3 >> 5u) & 1u; + + uint ohm = 1u << mode; + if ((ohm & 0x30u) != 0u) green |= int(x0 << 6u); + if ((ohm & 0x3Au) != 0u) green |= int(x1 << 5u); + if ((ohm & 0x30u) != 0u) blue |= int(x2 << 6u); + if ((ohm & 0x3Au) != 0u) blue |= int(x3 << 5u); + if ((ohm & 0x3Du) != 0u) scale |= int(x6 << 5u); + if ((ohm & 0x2Du) != 0u) scale |= int(x5 << 6u); + if ((ohm & 0x04u) != 0u) scale |= int(x4 << 7u); + if ((ohm & 0x3Bu) != 0u) red |= int(x4 << 6u); + if ((ohm & 0x04u) != 0u) red |= int(x3 << 6u); + if ((ohm & 0x10u) != 0u) red |= int(x5 << 7u); + if ((ohm & 0x0Fu) != 0u) red |= int(x2 << 7u); + if ((ohm & 0x05u) != 0u) red |= int(x1 << 8u); + if ((ohm & 0x0Au) != 0u) red |= int(x0 << 8u); + if ((ohm & 0x05u) != 0u) red |= int(x0 << 9u); + if ((ohm & 0x02u) != 0u) red |= int(x6 << 9u); + if ((ohm & 0x01u) != 0u) red |= int(x3 << 10u); + if ((ohm & 0x02u) != 0u) red |= int(x5 << 10u); + + int shamts[6] = int[](1, 1, 2, 3, 4, 5); + int shamt = shamts[mode]; + red <<= shamt; + green <<= shamt; + blue <<= shamt; + scale <<= shamt; + + if (mode != 5u) { + green = red - green; + blue = red - blue; + } + + if (majcomp == 1u) { + int t = red; red = green; green = t; + } + if (majcomp == 2u) { + int t = red; red = blue; blue = t; + } + + e1 = ivec3(clamp(red, 0, 0xFFF), clamp(green, 0, 0xFFF), clamp(blue, 0, 0xFFF)); + e0 = ivec3(clamp(red - scale, 0, 0xFFF), clamp(green - scale, 0, 0xFFF), + clamp(blue - scale, 0, 0xFFF)); +} + +void DecodeHDREndpointMode11(uint v0, uint v1, uint v2, uint v3, uint v4, uint v5, out ivec3 e0, + out ivec3 e1) { + uint majcomp = ((v4 & 0x80u) >> 7u) | ((v5 & 0x80u) >> 6u); + if (majcomp == 3u) { + e0 = ivec3(int(v0 << 4u), int(v2 << 4u), int((v4 & 0x7Fu) << 5u)); + e1 = ivec3(int(v1 << 4u), int(v3 << 4u), int((v5 & 0x7Fu) << 5u)); + return; + } + + uint mode = ((v1 & 0x80u) >> 7u) | ((v2 & 0x80u) >> 6u) | ((v3 & 0x80u) >> 5u); + int va = int(v0 | ((v1 & 0x40u) << 2u)); + int vb0 = int(v2 & 0x3Fu); + int vb1 = int(v3 & 0x3Fu); + int vc = int(v1 & 0x3Fu); + int vd0 = int(v4 & 0x7Fu); + int vd1 = int(v5 & 0x7Fu); + + int dbitstab[8] = int[](7, 6, 7, 6, 5, 6, 5, 6); + vd0 = SignExtend(vd0, uint(dbitstab[mode])); + vd1 = SignExtend(vd1, uint(dbitstab[mode])); + + uint x0 = (v2 >> 6u) & 1u; + uint x1 = (v3 >> 6u) & 1u; + uint x2 = (v4 >> 6u) & 1u; + uint x3 = (v5 >> 6u) & 1u; + uint x4 = (v4 >> 5u) & 1u; + uint x5 = (v5 >> 5u) & 1u; + + uint ohm = 1u << mode; + if ((ohm & 0xA4u) != 0u) va |= int(x0 << 9u); + if ((ohm & 0x08u) != 0u) va |= int(x2 << 9u); + if ((ohm & 0x50u) != 0u) va |= int(x4 << 9u); + if ((ohm & 0x50u) != 0u) va |= int(x5 << 10u); + if ((ohm & 0xA0u) != 0u) va |= int(x1 << 10u); + if ((ohm & 0xC0u) != 0u) va |= int(x2 << 11u); + if ((ohm & 0x04u) != 0u) vc |= int(x1 << 6u); + if ((ohm & 0xE8u) != 0u) vc |= int(x3 << 6u); + if ((ohm & 0x20u) != 0u) vc |= int(x2 << 7u); + if ((ohm & 0x5Bu) != 0u) vb0 |= int(x0 << 6u); + if ((ohm & 0x5Bu) != 0u) vb1 |= int(x1 << 6u); + if ((ohm & 0x12u) != 0u) vb0 |= int(x2 << 7u); + if ((ohm & 0x12u) != 0u) vb1 |= int(x3 << 7u); + + int shamt = (int(mode) >> 1) ^ 3; + va <<= shamt; + vb0 <<= shamt; + vb1 <<= shamt; + vc <<= shamt; + vd0 <<= shamt; + vd1 <<= shamt; + + int r1 = clamp(va, 0, 0xFFF); + int g1 = clamp(va - vb0, 0, 0xFFF); + int b1 = clamp(va - vb1, 0, 0xFFF); + int r0 = clamp(va - vc, 0, 0xFFF); + int g0 = clamp(va - vb0 - vc - vd0, 0, 0xFFF); + int b0 = clamp(va - vb1 - vc - vd1, 0, 0xFFF); + + if (majcomp == 1u) { + int t; + t = r0; r0 = g0; g0 = t; + t = r1; r1 = g1; g1 = t; + } else if (majcomp == 2u) { + int t; + t = r0; r0 = b0; b0 = t; + t = r1; r1 = b1; b1 = t; + } + e0 = ivec3(r0, g0, b0); + e1 = ivec3(r1, g1, b1); +} + void ComputeEndpoints(out uvec4 ep1, out uvec4 ep2, uint color_endpoint_mode, uint color_values[32], inout uint colvals_index) { #define READ_UINT_VALUES(N) \ @@ -715,8 +874,86 @@ void ComputeEndpoints(out uvec4 ep1, out uvec4 ep2, uint color_endpoint_mode, ui } break; } + case 2: { + READ_UINT_VALUES(2) + uint y0, y1; + if (V[0].y >= V[0].x) { + y0 = V[0].x << 4u; + y1 = V[0].y << 4u; + } else { + y0 = (V[0].y << 4u) + 8u; + y1 = (V[0].x << 4u) - 8u; + } + ep1 = uvec4(0x780u, y0, y0, y0); + ep2 = uvec4(0x780u, y1, y1, y1); + break; + } + case 3: { + READ_UINT_VALUES(2) + uint y0, d; + if ((V[0].x & 0x80u) != 0u) { + y0 = ((V[0].y & 0xE0u) << 4u) | ((V[0].x & 0x7Fu) << 2u); + d = (V[0].y & 0x1Fu) << 2u; + } else { + y0 = ((V[0].y & 0xF0u) << 4u) | ((V[0].x & 0x7Fu) << 1u); + d = (V[0].y & 0x0Fu) << 1u; + } + const uint y1 = min(y0 + d, 0xFFFu); + ep1 = uvec4(0x780u, y0, y0, y0); + ep2 = uvec4(0x780u, y1, y1, y1); + break; + } + case 7: { + READ_UINT_VALUES(4) + ivec3 e0, e1; + DecodeHDREndpointMode7(V[0].x, V[0].y, V[0].z, V[0].w, e0, e1); + ep1 = uvec4(0x780u, uint(e0.x), uint(e0.y), uint(e0.z)); + ep2 = uvec4(0x780u, uint(e1.x), uint(e1.y), uint(e1.z)); + break; + } + case 11: { + READ_UINT_VALUES(6) + ivec3 e0, e1; + DecodeHDREndpointMode11(V[0].x, V[0].y, V[0].z, V[0].w, V[1].x, V[1].y, e0, e1); + ep1 = uvec4(0x780u, uint(e0.x), uint(e0.y), uint(e0.z)); + ep2 = uvec4(0x780u, uint(e1.x), uint(e1.y), uint(e1.z)); + break; + } + case 14: { + READ_UINT_VALUES(8) + ivec3 e0, e1; + DecodeHDREndpointMode11(V[0].x, V[0].y, V[0].z, V[0].w, V[1].x, V[1].y, e0, e1); + ep1 = uvec4(V[1].z, uint(e0.x), uint(e0.y), uint(e0.z)); + ep2 = uvec4(V[1].w, uint(e1.x), uint(e1.y), uint(e1.z)); + break; + } + case 15: { + READ_UINT_VALUES(8) + ivec3 e0, e1; + DecodeHDREndpointMode11(V[0].x, V[0].y, V[0].z, V[0].w, V[1].x, V[1].y, e0, e1); + const uint mode = ((V[1].z >> 7u) & 1u) | ((V[1].w >> 6u) & 2u); + int a6 = int(V[1].z & 0x7Fu); + int a7 = int(V[1].w & 0x7Fu); + int alpha0, alpha1; + if (mode == 3u) { + alpha0 = a6 << 5; + alpha1 = a7 << 5; + } else { + a6 |= (a7 << int(mode + 1u)) & 0x780; + a7 &= int(0x3Fu >> mode); + a7 ^= int(0x20u >> mode); + a7 -= int(0x20u >> mode); + a6 <<= int(4u - mode); + a7 <<= int(4u - mode); + a7 += a6; + alpha0 = a6; + alpha1 = clamp(a7, 0, 0xFFF); + } + ep1 = uvec4(uint(alpha0), uint(e0.x), uint(e0.y), uint(e0.z)); + ep2 = uvec4(uint(alpha1), uint(e1.x), uint(e1.y), uint(e1.z)); + break; + } default: { - // HDR mode, or more likely a bug computing the color_endpoint_mode ep1 = uvec4(0xFF, 0xFF, 0, 0); ep2 = uvec4(0xFF, 0xFF, 0, 0); break; @@ -1112,13 +1349,46 @@ void DecompressBlock(ivec3 coord) { if (num_partitions > 1) { local_partition = Select2DPartition(partition_index, i, j, num_partitions); } - const uvec4 C0 = ReplicateByteTo16(endpoints0[local_partition]); - const uvec4 C1 = ReplicateByteTo16(endpoints1[local_partition]); + const uint local_cem = color_endpoint_mode[local_partition]; const uvec4 weight_vec = GetUnquantizedWeightVector(j, i, size_params, plane_index, dual_plane); - const vec4 Cf = - vec4((C0 * (uvec4(64) - weight_vec) + C1 * weight_vec + uvec4(32)) / 64); - const vec4 p = (Cf / 65535.0f); + + vec4 p; + if (IsHDRColorEndpointMode(local_cem)) { + const uvec4 C0 = endpoints0[local_partition] << 4u; + const uvec4 C1 = endpoints1[local_partition] << 4u; + const uvec4 C = (C0 * (uvec4(64) - weight_vec) + C1 * weight_vec + uvec4(32)) / 64u; + const uvec4 E = (C & uvec4(0xF800u)) >> 11u; + const uvec4 M = C & uvec4(0x7FFu); + const uvec4 Mt_lo = 3u * M; + const uvec4 Mt_mid = 4u * M - 512u; + const uvec4 Mt_hi = 5u * M - 2048u; + const uvec4 Mt = + mix(Mt_lo, mix(Mt_mid, Mt_hi, greaterThanEqual(M, uvec4(1536u))), + greaterThanEqual(M, uvec4(512u))); + const uvec4 Cf = (E << 10u) + (Mt >> 3u); + const uvec4 half_bits = mix(Cf, uvec4(0x7BFFu), greaterThanEqual(Cf, uvec4(0x7C00u))); + p = vec4(unpackHalf2x16(half_bits.x).x, unpackHalf2x16(half_bits.y).x, + unpackHalf2x16(half_bits.z).x, unpackHalf2x16(half_bits.w).x); + + if (local_cem == 14u) { + const uint a0 = ReplicateByteTo16(uvec4(endpoints0[local_partition].x)).x; + const uint a1 = ReplicateByteTo16(uvec4(endpoints1[local_partition].x)).x; + const uint Ca = (a0 * (64u - weight_vec.x) + a1 * weight_vec.x + 32u) / 64u; + p.x = float(Ca) / 65535.0f; + } + } else { + const uvec4 C0 = ReplicateByteTo16(endpoints0[local_partition]); + const uvec4 C1 = ReplicateByteTo16(endpoints1[local_partition]); + const vec4 Cf = + vec4((C0 * (uvec4(64) - weight_vec) + C1 * weight_vec + uvec4(32)) / 64); + p = Cf / 65535.0f; + } + +#ifdef VULKAN imageStore(dest_image, coord + ivec3(i, j, 0), p.gbar); +#else + imageStore(dest_image, coord + ivec3(i, j, 0), clamp(p, 0.0f, 1.0f).gbar); +#endif } } } diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp index 0011ec3bf5..f361c34f44 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp @@ -26,6 +26,7 @@ #include "video_core/host_shaders/vulkan_uint8_comp_spv.h" #include "video_core/host_shaders/block_linear_unswizzle_3d_bcn_comp_spv.h" #include "video_core/renderer_vulkan/vk_compute_pass.h" +#include "video_core/surface.h" #include "video_core/renderer_vulkan/vk_descriptor_pool.h" #include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 6307f8b93e..ac4bc29905 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -128,9 +129,32 @@ constexpr VkBorderColor ConvertBorderColor(const std::array& color) { return usage; } -[[nodiscard]] VkImageCreateInfo MakeImageCreateInfo(const Device& device, const ImageInfo& info) { - const auto format_info = +[[nodiscard]] bool WillUseAcceleratedAstcDecode(const Device& device, const ImageInfo& info) { + if (!IsPixelFormatASTC(info.format) || device.IsOptimalAstcSupported()) { + return false; + } + if (Settings::values.accelerate_astc.GetValue() != Settings::AstcDecodeMode::Gpu) { + return false; + } + return Settings::values.astc_recompression.GetValue() == + Settings::AstcRecompression::Uncompressed && + info.size.depth == 1; +} + +[[nodiscard]] bool WillUseWidenedAstcFormat(const Device& device, const ImageInfo& info) { + return WillUseAcceleratedAstcDecode(device, info) && + !VideoCore::Surface::IsPixelFormatSRGB(info.format); +} + +[[nodiscard]] VkImageCreateInfo MakeImageCreateInfo(const Device& device, const ImageInfo& info, + std::optional format_override = {}) { + auto format_info = MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, false, info.format); + if (format_override) { + format_info.format = *format_override; + format_info.attachable = false; + format_info.storage = true; + } VkImageCreateFlags flags{}; if (info.type == ImageType::e2D && info.resources.layers >= 6 && info.size.width == info.size.height && !device.HasBrokenCubeImageCompatibility()) { @@ -164,11 +188,12 @@ constexpr VkBorderColor ConvertBorderColor(const std::array& color) { } [[nodiscard]] vk::Image MakeImage(const Device& device, const MemoryAllocator& allocator, - const ImageInfo& info, std::span view_formats) { + const ImageInfo& info, std::span view_formats, + std::optional format_override = {}) { if (info.type == ImageType::Buffer) { return vk::Image{}; } - VkImageCreateInfo image_ci = MakeImageCreateInfo(device, info); + VkImageCreateInfo image_ci = MakeImageCreateInfo(device, info, format_override); const VkImageFormatListCreateInfo image_format_list = { .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO, .pNext = nullptr, @@ -1557,15 +1582,19 @@ void TextureCacheRuntime::TickFrame() {} Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu_addr_, VAddr cpu_addr_) : VideoCommon::ImageBase(info_, gpu_addr_, cpu_addr_), scheduler{&runtime_.scheduler}, - runtime{&runtime_}, original_image(MakeImage(runtime_.device, runtime_.memory_allocator, info, - runtime->ViewFormats(info.format))), + runtime{&runtime_}, + original_image(MakeImage(runtime_.device, runtime_.memory_allocator, info, + WillUseWidenedAstcFormat(runtime_.device, info) + ? std::span{} + : runtime->ViewFormats(info.format), + WillUseWidenedAstcFormat(runtime_.device, info) + ? std::make_optional(VK_FORMAT_R32G32B32A32_SFLOAT) + : std::nullopt)), aspect_mask(ImageAspectMask(info.format)) { if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported()) { switch (Settings::values.accelerate_astc.GetValue()) { case Settings::AstcDecodeMode::Gpu: - if (Settings::values.astc_recompression.GetValue() == - Settings::AstcRecompression::Uncompressed && - info.size.depth == 1) { + if (WillUseAcceleratedAstcDecode(runtime->device, info)) { flags |= VideoCommon::ImageFlagBits::AcceleratedUpload; } break; @@ -1591,9 +1620,12 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu Settings::values.astc_recompression.GetValue() == Settings::AstcRecompression::Uncompressed) { const auto& device = runtime->device.GetLogical(); + const VkFormat storage_format = WillUseWidenedAstcFormat(runtime->device, info) + ? VK_FORMAT_R32G32B32A32_SFLOAT + : VK_FORMAT_A8B8G8R8_UNORM_PACK32; for (s32 level = 0; level < info.resources.levels; ++level) { storage_image_views[level] = - MakeStorageView(device, level, *original_image, VK_FORMAT_A8B8G8R8_UNORM_PACK32); + MakeStorageView(device, level, *original_image, storage_format); } } } @@ -1942,8 +1974,13 @@ void Image::DownloadMemory(const StagingBufferRef& map, std::spandevice, FormatType::Optimal, true, info.format); + if (WillUseAcceleratedAstcDecode(runtime->device, info)) { + format_info.format = WillUseWidenedAstcFormat(runtime->device, info) + ? VK_FORMAT_R32G32B32A32_SFLOAT + : VK_FORMAT_A8B8G8R8_UNORM_PACK32; + } view = MakeStorageView(runtime->device.GetLogical(), level, *(this->*current_image), format_info.format); } @@ -2110,7 +2147,11 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI SanitizeDepthStencilSwizzle(swizzle, device->SupportsDepthStencilSwizzleOne()); } } - const auto format_info = MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format); + uses_widened_astc_format = WillUseWidenedAstcFormat(*device, image.info); + auto format_info = MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format); + if (uses_widened_astc_format) { + format_info.format = VK_FORMAT_R32G32B32A32_SFLOAT; + } const VkImageUsageFlags requested_view_usage = ImageUsageFlags(format_info, format); const VkImageUsageFlags image_usage = image.UsageFlags(); const VkImageUsageFlags clamped_view_usage = requested_view_usage & image_usage; @@ -2245,8 +2286,10 @@ VkImageView ImageView::StorageView(Shader::TextureType texture_type, if (image_handle) { if (image_format == Shader::ImageFormat::Typeless) { if (!typeless_storage_view) { - const auto& info = - MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format); + auto info = MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format); + if (uses_widened_astc_format) { + info.format = VK_FORMAT_R32G32B32A32_SFLOAT; + } typeless_storage_view = MakeView(info.format, VK_IMAGE_ASPECT_COLOR_BIT); } return *typeless_storage_view; diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 005ab94762..027143fdbc 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -386,6 +386,8 @@ private: VkImageView render_target = VK_NULL_HANDLE; VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT; u32 buffer_size = 0; + + bool uses_widened_astc_format = false; }; class ImageAlloc : public VideoCommon::ImageAllocBase {}; diff --git a/src/video_core/textures/astc.cpp b/src/video_core/textures/astc.cpp index b7b5f5bd4a..5f8fe3dfdf 100644 --- a/src/video_core/textures/astc.cpp +++ b/src/video_core/textures/astc.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2016 The University of North Carolina at Chapel Hill @@ -1269,6 +1269,209 @@ static inline u32 Select2DPartition(s32 seed, s32 x, s32 y, s32 partitionCount, return SelectPartition(seed, x, y, 0, partitionCount, smallBlock); } +static constexpr bool IsHDRColorEndpointMode(u32 cem) { + switch (cem) { + case 2: + case 3: + case 7: + case 11: + case 14: + case 15: + return true; + default: + return false; + } +} + +static constexpr s32 SignExtend(s32 value, u32 nbits) { + const s32 sign_bit = 1 << (nbits - 1); + return (value ^ sign_bit) - sign_bit; +} + +struct HDREndpointRGB { + s32 r0, g0, b0; + s32 r1, g1, b1; +}; + +static void DecodeHDREndpointMode7(u32 v0, u32 v1, u32 v2, u32 v3, s32& r0, s32& g0, s32& b0, + s32& r1, s32& g1, s32& b1) { + const u32 modeval = ((v0 & 0xC0) >> 6) | ((v1 & 0x80) >> 5) | ((v2 & 0x80) >> 4); + + u32 majcomp; + u32 mode; + if ((modeval & 0xC) != 0xC) { + majcomp = modeval >> 2; + mode = modeval & 3; + } else if (modeval != 0xF) { + majcomp = modeval & 3; + mode = 4; + } else { + majcomp = 0; + mode = 5; + } + + s32 red = static_cast(v0 & 0x3f); + s32 green = static_cast(v1 & 0x1f); + s32 blue = static_cast(v2 & 0x1f); + s32 scale = static_cast(v3 & 0x1f); + + const u32 x0 = (v1 >> 6) & 1; + const u32 x1 = (v1 >> 5) & 1; + const u32 x2 = (v2 >> 6) & 1; + const u32 x3 = (v2 >> 5) & 1; + const u32 x4 = (v3 >> 7) & 1; + const u32 x5 = (v3 >> 6) & 1; + const u32 x6 = (v3 >> 5) & 1; + + const u32 ohm = 1u << mode; + if (ohm & 0x30) + green |= static_cast(x0 << 6); + if (ohm & 0x3A) + green |= static_cast(x1 << 5); + if (ohm & 0x30) + blue |= static_cast(x2 << 6); + if (ohm & 0x3A) + blue |= static_cast(x3 << 5); + if (ohm & 0x3D) + scale |= static_cast(x6 << 5); + if (ohm & 0x2D) + scale |= static_cast(x5 << 6); + if (ohm & 0x04) + scale |= static_cast(x4 << 7); + if (ohm & 0x3B) + red |= static_cast(x4 << 6); + if (ohm & 0x04) + red |= static_cast(x3 << 6); + if (ohm & 0x10) + red |= static_cast(x5 << 7); + if (ohm & 0x0F) + red |= static_cast(x2 << 7); + if (ohm & 0x05) + red |= static_cast(x1 << 8); + if (ohm & 0x0A) + red |= static_cast(x0 << 8); + if (ohm & 0x05) + red |= static_cast(x0 << 9); + if (ohm & 0x02) + red |= static_cast(x6 << 9); + if (ohm & 0x01) + red |= static_cast(x3 << 10); + if (ohm & 0x02) + red |= static_cast(x5 << 10); + + static constexpr int shamts[6] = {1, 1, 2, 3, 4, 5}; + const s32 shamt = shamts[mode]; + red <<= shamt; + green <<= shamt; + blue <<= shamt; + scale <<= shamt; + + if (mode != 5) { + green = red - green; + blue = red - blue; + } + + if (majcomp == 1) + std::swap(red, green); + if (majcomp == 2) + std::swap(red, blue); + + r1 = std::clamp(red, 0, 0xFFF); + g1 = std::clamp(green, 0, 0xFFF); + b1 = std::clamp(blue, 0, 0xFFF); + + r0 = std::clamp(red - scale, 0, 0xFFF); + g0 = std::clamp(green - scale, 0, 0xFFF); + b0 = std::clamp(blue - scale, 0, 0xFFF); +} + +static HDREndpointRGB DecodeHDREndpointMode11(u32 v0, u32 v1, u32 v2, u32 v3, u32 v4, u32 v5) { + const u32 majcomp = ((v4 & 0x80) >> 7) | ((v5 & 0x80) >> 6); + if (majcomp == 3) { + HDREndpointRGB result; + result.r0 = static_cast(v0 << 4); + result.g0 = static_cast(v2 << 4); + result.b0 = static_cast((v4 & 0x7f) << 5); + result.r1 = static_cast(v1 << 4); + result.g1 = static_cast(v3 << 4); + result.b1 = static_cast((v5 & 0x7f) << 5); + return result; + } + + const u32 mode = ((v1 & 0x80) >> 7) | ((v2 & 0x80) >> 6) | ((v3 & 0x80) >> 5); + s32 va = static_cast(v0 | ((v1 & 0x40) << 2)); + s32 vb0 = static_cast(v2 & 0x3f); + s32 vb1 = static_cast(v3 & 0x3f); + s32 vc = static_cast(v1 & 0x3f); + s32 vd0 = static_cast(v4 & 0x7f); + s32 vd1 = static_cast(v5 & 0x7f); + + static constexpr int dbitstab[8] = {7, 6, 7, 6, 5, 6, 5, 6}; + vd0 = SignExtend(vd0, dbitstab[mode]); + vd1 = SignExtend(vd1, dbitstab[mode]); + + const u32 x0 = (v2 >> 6) & 1; + const u32 x1 = (v3 >> 6) & 1; + const u32 x2 = (v4 >> 6) & 1; + const u32 x3 = (v5 >> 6) & 1; + const u32 x4 = (v4 >> 5) & 1; + const u32 x5 = (v5 >> 5) & 1; + + const u32 ohm = 1u << mode; + if (ohm & 0xA4) + va |= static_cast(x0 << 9); + if (ohm & 0x08) + va |= static_cast(x2 << 9); + if (ohm & 0x50) + va |= static_cast(x4 << 9); + if (ohm & 0x50) + va |= static_cast(x5 << 10); + if (ohm & 0xA0) + va |= static_cast(x1 << 10); + if (ohm & 0xC0) + va |= static_cast(x2 << 11); + if (ohm & 0x04) + vc |= static_cast(x1 << 6); + if (ohm & 0xE8) + vc |= static_cast(x3 << 6); + if (ohm & 0x20) + vc |= static_cast(x2 << 7); + if (ohm & 0x5B) + vb0 |= static_cast(x0 << 6); + if (ohm & 0x5B) + vb1 |= static_cast(x1 << 6); + if (ohm & 0x12) + vb0 |= static_cast(x2 << 7); + if (ohm & 0x12) + vb1 |= static_cast(x3 << 7); + + const s32 shamt = (static_cast(mode) >> 1) ^ 3; + va <<= shamt; + vb0 <<= shamt; + vb1 <<= shamt; + vc <<= shamt; + vd0 <<= shamt; + vd1 <<= shamt; + + HDREndpointRGB result; + result.r1 = std::clamp(va, 0, 0xFFF); + result.g1 = std::clamp(va - vb0, 0, 0xFFF); + result.b1 = std::clamp(va - vb1, 0, 0xFFF); + + result.r0 = std::clamp(va - vc, 0, 0xFFF); + result.g0 = std::clamp(va - vb0 - vc - vd0, 0, 0xFFF); + result.b0 = std::clamp(va - vb1 - vc - vd1, 0, 0xFFF); + + if (majcomp == 1) { + std::swap(result.r0, result.g0); + std::swap(result.r1, result.g1); + } else if (majcomp == 2) { + std::swap(result.r0, result.b0); + std::swap(result.r1, result.b1); + } + return result; +} + // Section C.2.14 static void ComputeEndpoints(Pixel& ep1, Pixel& ep2, const u32*& colorValues, u32 colorEndpointMode) { @@ -1382,8 +1585,84 @@ static void ComputeEndpoints(Pixel& ep1, Pixel& ep2, const u32*& colorValues, ep2.ClampByte(); } break; + case 2: { + READ_UINT_VALUES(2) + u32 y0, y1; + if (v[1] >= v[0]) { + y0 = v[0] << 4; + y1 = v[1] << 4; + } else { + y0 = (v[1] << 4) + 8; + y1 = (v[0] << 4) - 8; + } + ep1 = Pixel(0x780, y0, y0, y0); + ep2 = Pixel(0x780, y1, y1, y1); + } break; + + case 3: { + READ_UINT_VALUES(2) + u32 y0, d; + if (v[0] & 0x80) { + y0 = ((v[1] & 0xE0) << 4) | ((v[0] & 0x7F) << 2); + d = (v[1] & 0x1F) << 2; + } else { + y0 = ((v[1] & 0xF0) << 4) | ((v[0] & 0x7F) << 1); + d = (v[1] & 0x0F) << 1; + } + const u32 y1 = (std::min)(y0 + d, 0xFFFU); + ep1 = Pixel(0x780, y0, y0, y0); + ep2 = Pixel(0x780, y1, y1, y1); + } break; + + case 7: { + READ_UINT_VALUES(4) + s32 r0, g0, b0, r1, g1, b1; + DecodeHDREndpointMode7(v[0], v[1], v[2], v[3], r0, g0, b0, r1, g1, b1); + ep1 = Pixel(0x780, r0, g0, b0); + ep2 = Pixel(0x780, r1, g1, b1); + } break; + + case 11: { + READ_UINT_VALUES(6) + const HDREndpointRGB rgb = DecodeHDREndpointMode11(v[0], v[1], v[2], v[3], v[4], v[5]); + ep1 = Pixel(0x780, rgb.r0, rgb.g0, rgb.b0); + ep2 = Pixel(0x780, rgb.r1, rgb.g1, rgb.b1); + } break; + + case 14: { + READ_UINT_VALUES(8) + const HDREndpointRGB rgb = DecodeHDREndpointMode11(v[0], v[1], v[2], v[3], v[4], v[5]); + ep1 = Pixel(v[6], rgb.r0, rgb.g0, rgb.b0); + ep2 = Pixel(v[7], rgb.r1, rgb.g1, rgb.b1); + } break; + + case 15: { + READ_UINT_VALUES(8) + const HDREndpointRGB rgb = DecodeHDREndpointMode11(v[0], v[1], v[2], v[3], v[4], v[5]); + const u32 mode = ((v[6] >> 7) & 1) | ((v[7] >> 6) & 2); + s32 a6 = static_cast(v[6] & 0x7F); + s32 a7 = static_cast(v[7] & 0x7F); + s32 alpha0, alpha1; + if (mode == 3) { + alpha0 = a6 << 5; + alpha1 = a7 << 5; + } else { + a6 |= (a7 << (mode + 1)) & 0x780; + a7 &= (0x3F >> mode); + a7 ^= 0x20 >> mode; + a7 -= 0x20 >> mode; + a6 <<= (4 - mode); + a7 <<= (4 - mode); + a7 += a6; + alpha0 = a6; + alpha1 = std::clamp(a7, 0, 0xFFF); + } + ep1 = Pixel(alpha0, rgb.r0, rgb.g0, rgb.b0); + ep2 = Pixel(alpha1, rgb.r1, rgb.g1, rgb.b1); + } break; + default: - assert(false && "Unsupported color endpoint mode (is it HDR?)"); + assert(false && "Unsupported color endpoint mode"); break; } @@ -1414,6 +1693,39 @@ static void FillVoidExtentLDR(InputBitStream& strm, std::span outBuf, u32 b } } +static float HalfToFloat(u16 h) { + const u32 sign = static_cast(h & 0x8000) << 16; + u32 exp = (h & 0x7C00) >> 10; + u32 mant = h & 0x3FF; + u32 bits; + if (exp == 0) { + if (mant == 0) { + bits = sign; + } else { + s32 e = 127 - 15 + 1; + while ((mant & 0x400) == 0) { + mant <<= 1; + --e; + } + mant &= 0x3FF; + bits = sign | (static_cast(e) << 23) | (mant << 13); + } + } else if (exp == 0x1F) { + bits = sign | 0x7F800000 | (mant << 13); + } else { + bits = sign | ((exp - 15 + 127) << 23) | (mant << 13); + } + float result; + std::memcpy(&result, &bits, sizeof(result)); + return result; +} + +static u16 HalfToClampedByte(u16 half_bits) { + const float value = HalfToFloat(half_bits); + const float clamped = std::clamp(value, 0.0f, 1.0f); + return static_cast(clamped * 255.0f + 0.5f); +} + static void FillError(std::span outBuf, u32 blockWidth, u32 blockHeight) { for (u32 j = 0; j < blockHeight; j++) { for (u32 i = 0; i < blockWidth; i++) { @@ -1631,22 +1943,44 @@ static void DecompressBlock(std::span inBuf, const u32 blockWidth, Pixel p; for (u32 c = 0; c < 4; c++) { u32 C0 = endpoints[partition][0].Component(c); - C0 = ReplicateByteTo16(C0); u32 C1 = endpoints[partition][1].Component(c); - C1 = ReplicateByteTo16(C1); u32 plane = 0; if (weightParams.m_bDualPlane && (((planeIdx + 1) & 3) == c)) { plane = 1; } - u32 weight = weights[plane][j * blockWidth + i]; - u32 C = (C0 * (64 - weight) + C1 * weight + 32) / 64; - if (C == 65535) { - p.Component(c) = 255; + + const bool is_hdr = IsHDRColorEndpointMode(colorEndpointMode[partition]) && + !(colorEndpointMode[partition] == 14 && c == 0); + + if (is_hdr) { + C0 <<= 4; + C1 <<= 4; + const u32 C = (C0 * (64 - weight) + C1 * weight + 32) / 64; + const u32 E = (C & 0xF800) >> 11; + const u32 M = C & 0x7FF; + u32 Mt; + if (M < 512) { + Mt = 3 * M; + } else if (M >= 1536) { + Mt = 5 * M - 2048; + } else { + Mt = 4 * M - 512; + } + const u32 Cf = (E << 10) + (Mt >> 3); + const u16 half_bits = (Cf >= 0x7C00) ? u16{0x7BFF} : static_cast(Cf); + p.Component(c) = HalfToClampedByte(half_bits); } else { - double Cf = static_cast(C); - p.Component(c) = static_cast(255.0 * (Cf / 65536.0) + 0.5); + C0 = ReplicateByteTo16(C0); + C1 = ReplicateByteTo16(C1); + const u32 C = (C0 * (64 - weight) + C1 * weight + 32) / 64; + if (C == 65535) { + p.Component(c) = 255; + } else { + double Cf = static_cast(C); + p.Component(c) = static_cast(255.0 * (Cf / 65536.0) + 0.5); + } } } diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index cf039768fd..a98717bc3e 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -72,7 +72,9 @@ VK_DEFINE_HANDLE(VmaAllocator) FEATURE(KHR, PipelineExecutableProperties, PIPELINE_EXECUTABLE_PROPERTIES, \ pipeline_executable_properties) \ FEATURE(KHR, WorkgroupMemoryExplicitLayout, WORKGROUP_MEMORY_EXPLICIT_LAYOUT, \ - workgroup_memory_explicit_layout) + workgroup_memory_explicit_layout) \ + FEATURE(EXT, TextureCompressionASTCHDR, TEXTURE_COMPRESSION_ASTC_HDR, \ + texture_compression_astc_hdr) // Define miscellaneous extensions which may be used by the implementation here. @@ -348,9 +350,9 @@ FN_MAX_LIMIT_LIST return properties.float_controls; } - /// Returns true if ASTC is natively supported. bool IsOptimalAstcSupported() const { - return features.features.textureCompressionASTC_LDR; + return features.features.textureCompressionASTC_LDR && + features.texture_compression_astc_hdr.textureCompressionASTC_HDR; } /// Returns true if BCn is natively supported.