diff --git a/src/graphics/guest_gpu/gpu_format.cpp b/src/graphics/guest_gpu/gpu_format.cpp index 6cb11db6e..6b6d4bed6 100644 --- a/src/graphics/guest_gpu/gpu_format.cpp +++ b/src/graphics/guest_gpu/gpu_format.cpp @@ -46,6 +46,7 @@ constexpr FormatInfo kFormatInfo[] = { {BufferFormat::k11_11_10UInt, 4, 0, 4, true, true}, {BufferFormat::k11_11_10Float, 4, 0, 4, true, false}, {BufferFormat::k10_11_11Float, 4, 0, 4, true, false}, + {BufferFormat::k2_10_10_10UNorm, 4, 0, 4, true, false}, {BufferFormat::k10_10_10_2UNorm, 4, 0, 4, true, false}, {BufferFormat::k10_10_10_2UInt, 4, 0, 4, true, true}, {BufferFormat::k8_8_8_8UNorm, 4, 0, 4, true, false}, diff --git a/src/graphics/host_gpu/renderer/image/imageInfo.h b/src/graphics/host_gpu/renderer/image/imageInfo.h index cff92688b..f2cb265fd 100644 --- a/src/graphics/host_gpu/renderer/image/imageInfo.h +++ b/src/graphics/host_gpu/renderer/image/imageInfo.h @@ -17,7 +17,7 @@ namespace Libs::Graphics { -enum class ColorTransform : uint32_t { None, SwapBgra16, Reverse10_11_11 }; +enum class ColorTransform : uint32_t { None, SwapBgra16, Reverse10_11_11, Rotate2_10_10_10 }; enum class VideoOutCompression : uint8_t { Uncompressed, @@ -105,6 +105,8 @@ struct ImageInfo { switch (guest_format) { case Prospero::BufferFormat::k10_11_11Float: return ColorTransform::Reverse10_11_11; + case Prospero::BufferFormat::k2_10_10_10UNorm: + return ColorTransform::Rotate2_10_10_10; default: return ColorTransform::None; } } diff --git a/src/graphics/host_gpu/renderer/image/textureCommon.cpp b/src/graphics/host_gpu/renderer/image/textureCommon.cpp index b6ac817fa..6c39dfafd 100644 --- a/src/graphics/host_gpu/renderer/image/textureCommon.cpp +++ b/src/graphics/host_gpu/renderer/image/textureCommon.cpp @@ -32,8 +32,12 @@ struct HostFormatInfo { HostFormatInfo ResolveHostFormat(Prospero::BufferFormat guest_format, Prospero::ChannelOrder order) { - if (guest_format == Prospero::BufferFormat::k10_11_11Float) { - return {vk::Format::eB10G11R11UfloatPack32, Prospero::ColorMappingBgra}; + switch (guest_format) { + case Prospero::BufferFormat::k10_11_11Float: + return {vk::Format::eB10G11R11UfloatPack32, Prospero::ColorMappingBgra}; + case Prospero::BufferFormat::k2_10_10_10UNorm: + return {vk::Format::eA2R10G10B10UnormPack32, Prospero::ColorMappingAbgr}; + default: break; } if (order == Prospero::ChannelOrder::kAlt) { switch (guest_format) { diff --git a/src/graphics/host_gpu/renderer/image/tiler.cpp b/src/graphics/host_gpu/renderer/image/tiler.cpp index 7d3c1d4b0..06e74c7a7 100644 --- a/src/graphics/host_gpu/renderer/image/tiler.cpp +++ b/src/graphics/host_gpu/renderer/image/tiler.cpp @@ -104,7 +104,8 @@ TileManager::Tiling TileManager::Prepare(bool tile, uint64_t tiled_capacity, block.block_depth == 0 || block.block_size == 0 || info.tiling.texel_width == 0 || info.tiling.texel_height == 0 || info.tiled_slice_stride == 0); EXIT_IF((transform == ColorTransform::SwapBgra16 && block.bytes_per_element != 8u) || - (transform == ColorTransform::Reverse10_11_11 && block.bytes_per_element != 4u)); + ((transform == ColorTransform::Reverse10_11_11 || + transform == ColorTransform::Rotate2_10_10_10) && block.bytes_per_element != 4u)); const auto checked_multiply = [](uint64_t left, uint64_t right, uint64_t& result) { return (left == 0 || right <= UINT64_MAX / left) && (result = left * right, true); }; @@ -560,7 +561,8 @@ void TileManager::TransformColor(Result input, Result output, ColorTransform tra uint32_t element_bytes = 0; switch (transform) { case ColorTransform::SwapBgra16: element_bytes = 8; break; - case ColorTransform::Reverse10_11_11: element_bytes = 4; break; + case ColorTransform::Reverse10_11_11: + case ColorTransform::Rotate2_10_10_10: element_bytes = 4; break; default: EXIT("invalid color transform\n"); } EXIT_IF(input.size == 0 || input.size % element_bytes != 0 || diff --git a/src/graphics/host_gpu/shaders/gpu_tiler_color.inc b/src/graphics/host_gpu/shaders/gpu_tiler_color.inc index 4c3882308..8e55aaaf1 100644 --- a/src/graphics/host_gpu/shaders/gpu_tiler_color.inc +++ b/src/graphics/host_gpu/shaders/gpu_tiler_color.inc @@ -1,4 +1,4 @@ -// ColorTransform values match imageInfo.h. Preserve the floating-point bit fields exactly. +// ColorTransform values match imageInfo.h. Preserve the component bit fields exactly. uvec2 transform_color(uvec2 value, uint transform, bool to_host) { switch (transform) { case 1u: @@ -15,6 +15,13 @@ uvec2 transform_color(uvec2 value, uint transform, bool to_host) { } break; } + case 3u: + if (to_host) { + value.x = (value.x >> 2u) | (value.x << 30u); + } else { + value.x = (value.x << 2u) | (value.x >> 30u); + } + break; } return value; } diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index d66a79569..869af69e1 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -13808,133 +13808,166 @@ public: resources.UnmapMemory(base, allocation_size); scheduler.Finish(); } - for (const auto tile : {Prospero::TileMode::kLinear, Prospero::TileMode::kRenderTarget}) { - constexpr std::array words{0x883005e1u, 0x7c0e81c0u, - 0x800f0000u, 0x98120280u}; - constexpr std::array, 4> channels{{ - {1.03125f, 2.03125f, 4.0625f, 1.f}, {0.5f, 0.75f, 1.5f, 1.f}, - {0.f, 1.f, 2.f, 1.f}, {32.f, 8.f, 16.f, 1.f}}}; - const auto target = TextureGetRenderTargetFormat( - Prospero::ChannelLayout::k10_11_11, Prospero::ChannelType::kFloat, - Prospero::ChannelOrder::kReversed); - Require(name, "10_11_11 reversed render target", - target.format == vk::Format::eB10G11R11UfloatPack32 && - target.guest_format == Prospero::BufferFormat::k10_11_11Float && - target.export_mapping == Prospero::ColorMappingRgba, - "PPSA24156 reversed packed-float target lost its physical encoding"); - std::memset(mapped, 0, allocation_size); - std::array offsets{0, 4, 8, 12}; - if (tile == Prospero::TileMode::kRenderTarget) { - TileBlockLayout block{}; - u32 block_xor = 0; - Require(name, "10_11_11 tile layout", - TileGetBlockLayout(TileBlockFamily::RenderTarget64KB, 4, block) && - TileGetBlockXor(block, 0, 0, 0, block_xor), - "packed-float render-target layout is unavailable"); - for (u32 pixel = 0; pixel < words.size(); ++pixel) { - Require(name, "10_11_11 tile offset", - TileGetBlockOffset(block, pixel, 0, 0, offsets[pixel]), - "packed-float texel offset is unavailable"); - offsets[pixel] ^= block_xor; + struct TransferCase { + Prospero::ChannelLayout layout; + Prospero::ChannelType type; + Prospero::BufferFormat format; + Prospero::BufferFormat alias_format; + vk::Format native_format; + u32 swizzle; + std::array words; + std::array, 4> channels; + std::array clear; + u32 clear_word; + std::array alias_clear; + u32 alias_word; + }; + constexpr std::array transfers{ + TransferCase{Prospero::ChannelLayout::k10_11_11, Prospero::ChannelType::kFloat, + Prospero::BufferFormat::k10_11_11Float, Prospero::BufferFormat::k11_11_10Float, + vk::Format::eB10G11R11UfloatPack32, DstSel(4, 5, 6, 1), + {0x883005e1u, 0x7c0e81c0u, 0x800f0000u, 0x98120280u}, + {{{1.03125f, 2.03125f, 4.0625f, 1.f}, {0.5f, 0.75f, 1.5f, 1.f}, + {0.f, 1.f, 2.f, 1.f}, {32.f, 8.f, 16.f, 1.f}}}, + {8.f, 4.f, 2.f, 1.f}, 0x90110200u, + {16.f, 8.f, 4.f, 1.f}, 0x98120220u}, + TransferCase{Prospero::ChannelLayout::k2_10_10_10, Prospero::ChannelType::kUNorm, + Prospero::BufferFormat::k2_10_10_10UNorm, Prospero::BufferFormat::k10_10_10_2UNorm, + vk::Format::eA2R10G10B10UnormPack32, DstSel(4, 5, 6, 7), + {0x0540d015u, 0x00000fffu, 0x003ff000u, 0xffe01406u}, + {{{1.f / 3, 5.f / 1023, 13.f / 1023, 21.f / 1023}, {1.f, 1.f, 0.f, 0.f}, + {0.f, 0.f, 1.f, 0.f}, {2.f / 3, 257.f / 1023, 513.f / 1023, 1.f}}}, + {21.f / 1023, 13.f / 1023, 5.f / 1023, 1.f / 3}, 0x0540d015u, + {33.f / 1023, 65.f / 1023, 129.f / 1023, 2.f / 3}, 0x08441206u}, + }; + for (const auto &format : transfers) { + for (const auto tile : {Prospero::TileMode::kLinear, Prospero::TileMode::kRenderTarget}) { + const auto target = TextureGetRenderTargetFormat( + format.layout, format.type, Prospero::ChannelOrder::kReversed); + Require(name, "reversed packed render target", + target.format == format.native_format && target.guest_format == format.format && + target.export_mapping == Prospero::ColorMappingRgba, + "PPSA24156 reversed packed target lost its physical encoding"); + std::memset(mapped, 0, allocation_size); + std::array offsets{0, 4, 8, 12}; + if (tile == Prospero::TileMode::kRenderTarget) { + TileBlockLayout block{}; + u32 block_xor = 0; + Require(name, "packed tile layout", + TileGetBlockLayout(TileBlockFamily::RenderTarget64KB, 4, block) && + TileGetBlockXor(block, 0, 0, 0, block_xor), + "packed render-target layout is unavailable"); + for (u32 pixel = 0; pixel < format.words.size(); ++pixel) { + Require(name, "packed tile offset", + TileGetBlockOffset(block, pixel, 0, 0, offsets[pixel]), + "packed texel offset is unavailable"); + offsets[pixel] ^= block_xor; + } } - } - for (u32 pixel = 0; pixel < words.size(); ++pixel) { - std::memcpy(static_cast(mapped) + offsets[pixel], &words[pixel], sizeof(u32)); - } - 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); - context.MapMemory(base, allocation_size); - auto &cache = context.GetTextureCache(); - auto &executor = context.GetRenderExecutor(); - constexpr u32 swizzle = DstSel(4, 5, 6, 1); - ShaderTextureResource descriptor{{static_cast(base >> 8u), - (static_cast(Prospero::BufferFormat::k10_11_11Float) << 20u) | - (3u << 30u), 0, - swizzle | (static_cast(tile) << 20u) | - (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(u32); - for (u32 pixel = 0; pixel < 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 (u32 channel = 0; channel < 4; ++channel) { - AppendStoreVgpr(&test.code, channel, pixel * 4 + channel); - test.expected.push_back(std::bit_cast(channels[pixel][channel])); + for (u32 pixel = 0; pixel < format.words.size(); ++pixel) { + std::memcpy(static_cast(mapped) + offsets[pixel], &format.words[pixel], sizeof(u32)); } - } - 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); - Require(name, "10_11_11 uploaded components", - ReadBuffer(name, output, 16) == test.expected, - "packed-float upload or sample changed a component's width or order"); - DestroyBuffer(&output); + 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); + context.MapMemory(base, allocation_size); + auto &cache = context.GetTextureCache(); + auto &executor = context.GetRenderExecutor(); + ShaderTextureResource descriptor{{static_cast(base >> 8u), + (static_cast(format.format) << 20u) | (3u << 30u), 0, + format.swizzle | (static_cast(tile) << 20u) | + (static_cast(Prospero::ImageType::kColor2D) << 28u), + 0, 0x00700000u, 0, 0}}; + TestCase test; + test.name = name; + test.has_user_data = true; + test.image_descriptor_swizzle = format.swizzle; + std::copy_n(descriptor.fields, 8, test.user_data.begin()); + test.user_data[50] = 16 * sizeof(u32); + for (u32 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 (u32 channel = 0; channel < 4; ++channel) { + AppendStoreVgpr(&test.code, channel, pixel * 4 + channel); + test.expected.push_back(std::bit_cast(format.channels[pixel][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 (u32 component = 0; component < actual.size(); ++component) { + bool matches = actual[component] == test.expected[component]; + if (format.type == Prospero::ChannelType::kUNorm) { + const float observed = std::bit_cast(actual[component]); + const float expected = std::bit_cast(test.expected[component]); + matches = std::abs(observed - expected) < 0.000001f; + } + Require(name, "packed uploaded components", matches, + "packed upload or sample changed a component's width or order"); + } + DestroyBuffer(&output); - const auto clear = [&](std::array channels) { - auto &native = cache.GetImage(binding.image_id); - native.Transit(vk::ImageLayout::eTransferDstOptimal, - vk::AccessFlagBits2::eTransferWrite, {}, - scheduler.Current().Handle()); - const vk::ImageSubresourceRange range{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}; - scheduler.Current().Handle().clearColorImage(native.backing.image, - vk::ImageLayout::eTransferDstOptimal, vk::ClearColorValue{channels}, range); - cache.MarkGpuWritten(binding.image_id); - }; - clear({8.f, 4.f, 2.f, 1.f}); - Require(name, "10_11_11 guest download", - TextureCacheTestAccess::TryDownload(cache, binding.image_id), - "packed-float native contents could not be downloaded"); - scheduler.Finish(); - scheduler.DrainPriorityOperations(); - std::array guest{}; - for (u32 pixel = 0; pixel < words.size(); ++pixel) { - Require(name, "10_11_11 guest texel", - Libs::LibKernel::Memory::TryReadBacking(base + offsets[pixel], - &guest[pixel], sizeof(u32)), - "packed-float guest texel is inaccessible"); - } - Require(name, "10_11_11 exact guest words", - guest == std::array{0x90110200u, 0x90110200u, 0x90110200u, 0x90110200u}, - "native packed-float write lost the guest 10/11/11 bit layout"); + const auto clear = [&](std::array channels) { + auto &native = cache.GetImage(binding.image_id); + native.Transit(vk::ImageLayout::eTransferDstOptimal, + vk::AccessFlagBits2::eTransferWrite, {}, + scheduler.Current().Handle()); + const vk::ImageSubresourceRange range{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}; + scheduler.Current().Handle().clearColorImage(native.backing.image, + vk::ImageLayout::eTransferDstOptimal, vk::ClearColorValue{channels}, range); + cache.MarkGpuWritten(binding.image_id); + }; + clear(format.clear); + Require(name, "packed guest download", + TextureCacheTestAccess::TryDownload(cache, binding.image_id), + "packed native contents could not be downloaded"); + scheduler.Finish(); + scheduler.DrainPriorityOperations(); + std::array guest{}; + for (u32 pixel = 0; pixel < format.words.size(); ++pixel) { + Require(name, "packed guest texel", + Libs::LibKernel::Memory::TryReadBacking(base + offsets[pixel], + &guest[pixel], sizeof(u32)), + "packed guest texel is inaccessible"); + } + Require(name, "exact packed guest words", + std::all_of(guest.begin(), guest.end(), [&](u32 word) { return word == format.clear_word; }), + "native packed write lost the guest bit layout"); - clear({16.f, 8.f, 4.f, 1.f}); - auto alias_desc = binding.desc; - alias_desc.info.guest_format = Prospero::BufferFormat::k11_11_10Float; - alias_desc.view_info.mapping = {}; - Require(name, "same-format transform compatibility", - !alias_desc.info.IsCompatible(binding.desc.info), - "equal Vulkan formats hid incompatible guest color transforms"); - const auto alias = cache.FindImage(alias_desc); - (void)cache.FindTexture(alias, alias_desc); - Require(name, "same-format transformed alias", - alias != binding.image_id && - ReadCachedTexel(name, context, alias, {}, {4, 1, 1}) == - std::vector(4, 0x98120220u), - "packed-float alias reused a transformed image or bypassed guest bytes"); - RenderExecutorTestAccess::ResetBindings(executor); - context.UnmapMemory(base, allocation_size); - scheduler.Finish(); + clear(format.alias_clear); + auto alias_desc = binding.desc; + alias_desc.info.guest_format = format.alias_format; + alias_desc.view_info.mapping = {}; + Require(name, "same-format transform compatibility", + !alias_desc.info.IsCompatible(binding.desc.info), + "equal Vulkan formats hid incompatible guest color transforms"); + const auto alias = cache.FindImage(alias_desc); + (void)cache.FindTexture(alias, alias_desc); + Require(name, "same-format transformed alias", + alias != binding.image_id && + ReadCachedTexel(name, context, alias, {}, {4, 1, 1}) == + std::vector(4, format.alias_word), + "packed alias reused a transformed image or bypassed guest bytes"); + RenderExecutorTestAccess::ResetBindings(executor); + context.UnmapMemory(base, allocation_size); + scheduler.Finish(); + } } Require(name, "release", Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0 &&