From a305a6c1c6ee1f965825e3d6aae365bb1a40882a Mon Sep 17 00:00:00 2001 From: nmzik Date: Thu, 10 Sep 2026 13:45:08 +0400 Subject: [PATCH] graphics: share packed texture and render-target channel layouts Resolve PS5 5551 textures with alpha in bit 15 and compose host channel order with guest selectors. Share the existing render-target mapping with sampled textures so packed layouts cannot diverge. Add native GPU readback coverage for 5551 and 4444, arbitrary selectors, constants, and existing 1555 render-target mapping. Verified PPSA24515 through launch.json with Vulkan validation, advancing scenes and over 12,700 guest flips. Independent correctness and cleanliness audits passed. --- .../host_gpu/renderer/image/textureCommon.cpp | 49 +++--- .../host_gpu/renderer/image/textureCommon.h | 12 +- .../renderer/pipeline/descriptors.cpp | 15 +- src/graphics/host_gpu/vulkanCommon.cpp | 3 +- tests/ShaderRecompilerComputeTests.cpp | 140 +++++++++++++++++- 5 files changed, 181 insertions(+), 38 deletions(-) diff --git a/src/graphics/host_gpu/renderer/image/textureCommon.cpp b/src/graphics/host_gpu/renderer/image/textureCommon.cpp index 848fcadb1..f65d0774a 100644 --- a/src/graphics/host_gpu/renderer/image/textureCommon.cpp +++ b/src/graphics/host_gpu/renderer/image/textureCommon.cpp @@ -28,13 +28,13 @@ constexpr Prospero::ColorComponentMapping kRenderTargetColorMappings[4][4] = { Prospero::ColorMappingArgb}, }; -struct RenderTargetHostFormatInfo { +struct HostFormatInfo { vk::Format format = vk::Format::eUndefined; Prospero::ColorComponentMapping host_to_storage; }; -RenderTargetHostFormatInfo ResolveRenderTargetHostFormat(Prospero::BufferFormat guest_format, - Prospero::ChannelOrder order) { +HostFormatInfo ResolveHostFormat(Prospero::BufferFormat guest_format, + Prospero::ChannelOrder order) { if (order == Prospero::ChannelOrder::kAlt) { switch (guest_format) { case Prospero::BufferFormat::k8_8_8_8UNorm: @@ -48,14 +48,12 @@ RenderTargetHostFormatInfo ResolveRenderTargetHostFormat(Prospero::BufferFormat default: break; } } + const auto format = VulkanFormat(guest_format); switch (guest_format) { - case Prospero::BufferFormat::k5_5_5_1UNorm: - return {vk::Format::eA1R5G5B5UnormPack16, Prospero::ColorMappingBgra}; + case Prospero::BufferFormat::k5_5_5_1UNorm: return {format, Prospero::ColorMappingBgra}; case Prospero::BufferFormat::k1_5_5_5UNorm: - return {vk::Format::eR5G5B5A1UnormPack16, Prospero::ColorMappingAbgr}; - case Prospero::BufferFormat::k4_4_4_4UNorm: - return {vk::Format::eR4G4B4A4UnormPack16, Prospero::ColorMappingAbgr}; - default: return {VulkanFormat(guest_format), {}}; + case Prospero::BufferFormat::k4_4_4_4UNorm: return {format, Prospero::ColorMappingAbgr}; + default: return {format, {}}; } } @@ -66,7 +64,7 @@ RenderTargetFormatInfo TextureGetRenderTargetFormat(Prospero::ChannelLayout layo Prospero::ChannelOrder order) { const auto encoding = Prospero::ResolveRenderTargetFormat(layout, type); if (encoding.IsValid() && encoding.SupportsOrder(order)) { - const auto host = ResolveRenderTargetHostFormat(encoding.buffer_format, order); + const auto host = ResolveHostFormat(encoding.buffer_format, order); const auto bytes = Prospero::RenderTargetBytesPerElement(encoding.buffer_format); if (host.format != vk::Format::eUndefined && bytes != 0) { const auto order_mapping = @@ -92,7 +90,11 @@ static size_t GetTextureRegionCount(uint32_t depth, uint64_t levels, bool volume return count; } -vk::ComponentSwizzle TextureGetComponentSwizzle(uint8_t s) { +vk::ComponentSwizzle TextureGetComponentSwizzle(uint8_t s, + Prospero::ColorComponentMapping storage_to_host) { + if (s >= 4u && s <= 7u) { + s = static_cast(4u + storage_to_host.Map(s - 4u)); + } switch (static_cast(s)) { case Prospero::CompSwizzle::kZero: return vk::ComponentSwizzle::eZero; case Prospero::CompSwizzle::kOne: return vk::ComponentSwizzle::eOne; @@ -102,30 +104,33 @@ vk::ComponentSwizzle TextureGetComponentSwizzle(uint8_t s) { case Prospero::CompSwizzle::kAlpha: return vk::ComponentSwizzle::eA; default: EXIT("unknown swizzle: %d\n", static_cast(s)); } - return vk::ComponentSwizzle::eIdentity; } } // namespace -vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle) { +vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle, + Prospero::ColorComponentMapping host_to_storage) { + Prospero::ColorComponentMapping storage_to_host {0u}; + for (uint32_t host = 0; host < 4u; ++host) { + storage_to_host.packed |= static_cast(host << (host_to_storage.Map(host) * 2u)); + } vk::ComponentMapping components {}; - components.r = TextureGetComponentSwizzle(GetDstSel(swizzle, 0)); - components.g = TextureGetComponentSwizzle(GetDstSel(swizzle, 1)); - components.b = TextureGetComponentSwizzle(GetDstSel(swizzle, 2)); - components.a = TextureGetComponentSwizzle(GetDstSel(swizzle, 3)); + components.r = TextureGetComponentSwizzle(GetDstSel(swizzle, 0), storage_to_host); + components.g = TextureGetComponentSwizzle(GetDstSel(swizzle, 1), storage_to_host); + components.b = TextureGetComponentSwizzle(GetDstSel(swizzle, 2), storage_to_host); + components.a = TextureGetComponentSwizzle(GetDstSel(swizzle, 3), storage_to_host); return components; } SurfaceFormatInfo TextureGetSurfaceFormatInfo(Prospero::BufferFormat format) { - const auto backing_format = Prospero::RemapTextureFormat(format); - const auto vk_format = VulkanFormat(backing_format); + const auto backing_format = Prospero::RemapTextureFormat(format); + const auto host = ResolveHostFormat(backing_format, Prospero::ChannelOrder::kStandard); const auto conversion_format = backing_format != format ? format : Prospero::BufferFormat::kInvalid; - if (vk_format != vk::Format::eUndefined) { - return SurfaceFormatInfo(vk_format, conversion_format); + if (host.format != vk::Format::eUndefined) { + return {host.format, conversion_format, host.host_to_storage}; } EXIT("unknown format: fmt = %u\n", static_cast(format)); - return SurfaceFormatInfo(vk::Format::eUndefined, Prospero::BufferFormat::kInvalid); } namespace { diff --git a/src/graphics/host_gpu/renderer/image/textureCommon.h b/src/graphics/host_gpu/renderer/image/textureCommon.h index 849e27599..3ad6db634 100644 --- a/src/graphics/host_gpu/renderer/image/textureCommon.h +++ b/src/graphics/host_gpu/renderer/image/textureCommon.h @@ -20,12 +20,9 @@ struct RenderTargetFormatInfo { }; struct SurfaceFormatInfo { - explicit constexpr SurfaceFormatInfo(vk::Format format, - Prospero::BufferFormat conversion_format) - : vk_format(format), conversion_format(conversion_format) {} - - vk::Format vk_format; - Prospero::BufferFormat conversion_format; + vk::Format vk_format; + Prospero::BufferFormat conversion_format; + Prospero::ColorComponentMapping host_to_storage; }; struct TextureUploadMipLayout { @@ -43,7 +40,8 @@ struct TextureUploadLayout { TextureUploadMipLayout mips[16] = {}; }; -vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle); +vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle, + Prospero::ColorComponentMapping host_to_storage); SurfaceFormatInfo TextureGetSurfaceFormatInfo(Prospero::BufferFormat format); RenderTargetFormatInfo TextureGetRenderTargetFormat(Prospero::ChannelLayout layout, Prospero::ChannelType type, diff --git a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp index 95d11ed38..49198387d 100644 --- a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp @@ -483,17 +483,18 @@ static void PopulateTextureMipLayout(ImageInfo& info) { static ImageViewInfo TextureViewInfo(const ShaderRecompiler::IR::ImageResource& resource, const ShaderTextureResource& descriptor, vk::Format format, - bool shader_conversion, bool storage, uint32_t view_levels, - uint32_t image_layers) { + const SurfaceFormatInfo& surface_format, bool storage, + uint32_t view_levels, uint32_t image_layers) { ImageViewInfo view {}; view.format = format; view.aspect = vk::ImageAspectFlagBits::eColor; view.base_level = descriptor.BaseLevel(); view.level_count = view_levels; - view.usage = storage ? vk::ImageUsageFlagBits::eStorage : vk::ImageUsageFlagBits::eSampled; - view.mapping = storage || shader_conversion - ? vk::ComponentMapping {} - : TextureGetComponentMapping(descriptor.DstSelXYZW()); + view.usage = storage ? vk::ImageUsageFlagBits::eStorage : vk::ImageUsageFlagBits::eSampled; + view.mapping = + storage || surface_format.conversion_format != Prospero::BufferFormat::kInvalid + ? vk::ComponentMapping {} + : TextureGetComponentMapping(descriptor.DstSelXYZW(), surface_format.host_to_storage); switch (resource.dimension) { case ShaderRecompiler::Decoder::ImageDimension::Dim1D: view.type = vk::ImageViewType::e1D; @@ -672,7 +673,7 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR } else { PopulateTextureMipLayout(desc.info); } - desc.view_info = TextureViewInfo(resource, descriptor, view_format, shader_conversion, storage, + desc.view_info = TextureViewInfo(resource, descriptor, view_format, surface_format, storage, view_levels, desc.info.resources.layers); desc.type = storage ? TextureCache::BindingType::Storage : TextureCache::BindingType::Texture; diff --git a/src/graphics/host_gpu/vulkanCommon.cpp b/src/graphics/host_gpu/vulkanCommon.cpp index bdb84d413..2c8af0865 100644 --- a/src/graphics/host_gpu/vulkanCommon.cpp +++ b/src/graphics/host_gpu/vulkanCommon.cpp @@ -78,7 +78,8 @@ constexpr FormatMapping kFormatMappings[] = { {Prospero::BufferFormat::k8_8_8_8Srgb, vk::Format::eR8G8B8A8Srgb}, {Prospero::BufferFormat::k9_9_9_5Float, vk::Format::eE5B9G9R9UfloatPack32}, {Prospero::BufferFormat::k5_6_5UNorm, vk::Format::eB5G6R5UnormPack16}, - {Prospero::BufferFormat::k5_5_5_1UNorm, vk::Format::eR5G5B5A1UnormPack16}, + {Prospero::BufferFormat::k5_5_5_1UNorm, vk::Format::eA1R5G5B5UnormPack16}, + {Prospero::BufferFormat::k1_5_5_5UNorm, vk::Format::eR5G5B5A1UnormPack16}, {Prospero::BufferFormat::k4_4_4_4UNorm, vk::Format::eR4G4B4A4UnormPack16}, {Prospero::BufferFormat::kBc1UNorm, vk::Format::eBc1RgbaUnormBlock}, {Prospero::BufferFormat::kBc1Srgb, vk::Format::eBc1RgbaSrgbBlock}, diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index d3983a479..4db2a1955 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -8506,6 +8506,138 @@ public: std::printf("[gpu] %-32s ok\n", name); } + void CheckPackedTextureComponents() { + constexpr const char *name = "PackedTextureComponents"; + constexpr uintptr_t base = 0x0000000204200000ull; + constexpr uint64_t allocation_size = 0x10000; + struct FormatCase { + Prospero::BufferFormat format; + std::array words; + std::array, 4> channels; + std::array maxima; + }; + // Packed format channels start at the least significant bit. + // PPSA24515's blue, white, and masked black exposed the 5551 reversal. + constexpr std::array cases{ + FormatCase{Prospero::BufferFormat::k5_5_5_1UNorm, + {0x7c00, 0x7fff, 0x8000, 0x8c41}, + {{{0, 0, 31, 0}, {31, 31, 31, 0}, {0, 0, 0, 1}, {1, 2, 3, 1}}}, + {31, 31, 31, 1}}, + FormatCase{Prospero::BufferFormat::k4_4_4_4UNorm, + {0x000f, 0x00f0, 0x0f00, 0x4321}, + {{{15, 0, 0, 0}, {0, 15, 0, 0}, {0, 0, 15, 0}, {1, 2, 3, 4}}}, + {15, 15, 15, 15}}, + }; + const auto argb = TextureGetComponentMapping(DstSel(5, 1, 7, 0), + Prospero::ColorMappingArgb); + Require(name, "inverse mapping", + argb == vk::ComponentMapping{vk::ComponentSwizzle::eB, + vk::ComponentSwizzle::eOne, vk::ComponentSwizzle::eR, + vk::ComponentSwizzle::eZero}, + "cyclic host mapping reordered output slots or descriptor constants"); + const auto target = TextureGetRenderTargetFormat( + Prospero::ChannelLayout::k1_5_5_5, Prospero::ChannelType::kUNorm, + Prospero::ChannelOrder::kStandard); + Require(name, "1555 render target", + target.format == vk::Format::eR5G5B5A1UnormPack16 && + target.export_mapping == Prospero::ColorMappingAbgr, + "shared format resolution changed the existing 1555 render target"); + EnsureRuntimeContext(); + int64_t direct_offset = -1; + Require(name, "allocation", + Libs::LibKernel::Memory::KernelAllocateDirectMemory( + 0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(), + allocation_size, allocation_size, 0, &direct_offset) == 0, + "packed texture allocation failed"); + void *mapped = reinterpret_cast(base); + Require(name, "mapping", + Libs::LibKernel::Memory::KernelMapDirectMemory( + &mapped, allocation_size, 0x3, 0x10, direct_offset, + allocation_size) == 0 && mapped == reinterpret_cast(base), + "packed texture mapping failed"); + for (const auto &format : cases) { + std::memset(mapped, 0, allocation_size); + std::memcpy(mapped, format.words.data(), sizeof(format.words)); + RenderContext context(m_runtime_context); + auto &scheduler = context.GetCommandScheduler(); + HW::Context registers{}; + HW::UserConfig user_config{}; + HW::Shader shaders{}; + scheduler.Begin(registers, user_config, shaders); + auto &resources = context.GetGpuResources(); + auto &cache = resources.GetTextureCache(); + auto &executor = context.GetRenderExecutor(); + resources.MapMemory(base, allocation_size); + for (const auto swizzle : {DstSel(4, 5, 6, 7), DstSel(7, 1, 4, 0)}) { + ShaderTextureResource descriptor{{static_cast(base >> 8u), + (static_cast(format.format) << 20u) | (3u << 30u), 0, + swizzle | (static_cast(Prospero::ImageType::kColor2D) << 28u), + 0, 0x00700000u, 0, 0}}; + TestCase test; + test.name = name; + test.has_user_data = true; + test.image_descriptor_swizzle = swizzle; + std::copy_n(descriptor.fields, 8, test.user_data.begin()); + test.user_data[50] = 16 * sizeof(uint32_t); + test.opcodes = {ShaderOpcode::V_MOV_B32, ShaderOpcode::IMAGE_LOAD, + ShaderOpcode::BUFFER_STORE_DWORD, ShaderOpcode::S_ENDPGM}; + for (uint32_t pixel = 0; pixel < format.words.size(); ++pixel) { + AppendVMovU32(&test.code, 20, pixel); + AppendVMovU32(&test.code, 21, 0); + test.code.push_back(EncodeMimg0(0x00, 0xf)); + test.code.push_back(EncodeMimg1(0, 20)); + for (uint32_t channel = 0; channel < 4; ++channel) { + AppendStoreVgpr(&test.code, channel, pixel * 4 + channel); + } + } + AppendEnd(&test.code); + const auto compiled = CompileCase(test, SubgroupSize()); + ShaderRecompiler::IR::DescriptorValue value{}; + value.dword_count = 8; + std::copy_n(descriptor.fields, 8, value.dwords.begin()); + const auto binding = RenderExecutorTestAccess::ResolveTexture( + executor, compiled.program.info.images.at(0), value); + Image sampled; + sampled.view = cache.FindTexture(binding.image_id, binding.desc); + sampled.layout = cache.GetImage(binding.image_id).backing.state.layout; + scheduler.Finish(); + auto output = CreateStorageBuffer(name, {}, 16); + Dispatch(test, compiled, output, nullptr, &sampled); + const auto actual = ReadBuffer(name, output, 16); + for (uint32_t pixel = 0; pixel < format.words.size(); ++pixel) { + for (uint32_t channel = 0; channel < 4; ++channel) { + const auto selector = GetDstSel(swizzle, channel); + const auto maximum = selector < 4 ? 1u : format.maxima[selector - 4]; + const auto expected = selector < 4 ? selector : format.channels[pixel][selector - 4]; + const float observed = std::bit_cast(actual[pixel * 4 + channel]); + // Recover the source bits without testing the host's UNORM rounding precision. + const bool matches = maximum == 1 ? observed == expected + : observed >= 0.0f && observed <= 1.0f && + std::round(observed * maximum) == expected; + if (!matches) { + std::ostringstream message; + message << "format=" << static_cast(format.format) + << " swizzle=" << swizzle << " word=" << format.words[pixel] + << " channel=" << channel << " expected=" << expected + << " actual=" << observed; + Fail(name, "GPU components", message.str()); + } + } + } + DestroyBuffer(&output); + } + RenderExecutorTestAccess::ResetBindings(executor); + resources.UnmapMemory(base, allocation_size); + scheduler.Finish(); + } + Require(name, "release", + Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0 && + Libs::LibKernel::Memory::KernelReleaseDirectMemory( + direct_offset, allocation_size) == 0, + "packed texture backing release failed"); + std::printf("[gpu] %-32s ok\n", name); + } + void CheckComparisonDepthTexture() { constexpr const char *name = "ComparisonDepthTexture"; constexpr uintptr_t base = 0x0000000204200000ull; @@ -25465,7 +25597,7 @@ void CheckSampledColorViews() { "valid PS5 sampled mappings were rejected or reserved selectors were " "admitted"); const auto arbitrary = DstSel(5, 1, 7, 0); - const auto components = TextureGetComponentMapping(arbitrary); + const auto components = TextureGetComponentMapping(arbitrary, {}); Require("SampledColorViews", "generic Vulkan component mapping", SelectSampledColorView(vk::Format::eR8G8B8A8Unorm, vk::Format::eR8G8B8A8Unorm, @@ -29073,6 +29205,11 @@ int main(int argc, char **argv) { std::setvbuf(stdout, nullptr, _IONBF, 0); EnsureConfigInitialized(); CheckLeastRecentlyUsedCacheOrdering(); + if (argc == 2 && std::strcmp(argv[1], "--packed-texture-only") == 0) { + VulkanHarness vulkan; + vulkan.CheckPackedTextureComponents(); + return 0; + } if (argc == 2 && std::strcmp(argv[1], "--error-dialog-only") == 0) { CheckErrorDialogLifecycle(); return 0; @@ -29494,6 +29631,7 @@ int main(int argc, char **argv) { vulkan.CheckBufferCacheDirtyGarbageCollection(); #endif vulkan.CheckUnifiedImageViewCache(); + vulkan.CheckPackedTextureComponents(); const auto tests = MakeCases(); const auto graphics_tests = MakeGraphicsCases(); CheckOpcodeCoverage(tests, graphics_tests);