diff --git a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp index 0b6705f8a..d85623975 100644 --- a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp @@ -252,10 +252,10 @@ static bool IsSupportedStorageTextureDescriptor(const ShaderRecompiler::IR::Imag (is_color_2d_array && descriptor.BaseArray5() <= descriptor.Depth()); const bool is_2d = resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D && valid_2d_slice; + // Storage cube coordinates address individual faces, including partial cube views. const bool is_cube = resource.cube && descriptor.Type() == Prospero::ImageType::kCube && descriptor.Width5() == descriptor.Height5() && - descriptor.BaseArray5() <= descriptor.Depth() && - (descriptor.Depth() - descriptor.BaseArray5() + 1u) % 6u == 0; + descriptor.BaseArray5() <= descriptor.Depth(); const bool is_2d_array = resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray && ((!resource.cube && is_color_2d_array && descriptor.BaseArray5() <= descriptor.Depth()) || diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 593486ea6..6e7d418a5 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -9405,6 +9405,114 @@ public: } + void CheckCubeFaceStorageExpansion() { + constexpr const char *name = "CubeFaceStorageExpansion"; + constexpr uintptr_t base = 0x0000000204700000ull; + constexpr uint64_t allocation_size = 0x10000; + constexpr std::array half_red{0x3c00, 0x4000, 0x4200, + 0x4400, 0x4500, 0x4600}; + EnsureRuntimeContext(); + int64_t direct_offset = -1; + Require(name, "direct allocation", + Libs::LibKernel::Memory::KernelAllocateDirectMemory( + 0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(), + allocation_size, allocation_size, 0, &direct_offset) == 0, + "cube-face allocation failed"); + void *mapped = reinterpret_cast(base); + Require(name, "direct mapping", + Libs::LibKernel::Memory::KernelMapDirectMemory( + &mapped, allocation_size, 0x3, 0x10, direct_offset, + allocation_size) == 0 && mapped == reinterpret_cast(base), + "cube-face mapping failed"); + std::memset(mapped, 0, allocation_size); + { + 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(); + for (uint32_t face = 0; face < half_red.size(); ++face) { + // PPSA25380 clears six single-face cube views at one guest address. + ShaderTextureResource descriptor{{static_cast(base >> 8u), + 0xc4700000u, 0x0001c001u, 0xb0000facu, + face | (face << 16u), 0x00700000u, 0, 0}}; + TestCase test; + test.name = name; + test.has_user_data = true; + std::copy_n(descriptor.fields, 8, test.user_data.begin()); + test.has_compute_info = true; + test.compute_info.threads_num[0] = test.compute_info.threads_num[1] = 8; + test.compute_info.threads_num[2] = 1; + test.compute_info.thread_ids_num = 2; + test.opcodes = {ShaderOpcode::V_MOV_B32, ShaderOpcode::IMAGE_STORE, + ShaderOpcode::S_ENDPGM}; + test.code = {EncodeVop1(0x01, 20, 256), EncodeVop1(0x01, 21, 257)}; + AppendVMovU32(&test.code, 22, 0); + AppendVMovLiteral(&test.code, 0, + std::bit_cast(static_cast(face + 1))); + AppendVMovLiteral(&test.code, 1, std::bit_cast(0.5f)); + AppendVMovLiteral(&test.code, 2, std::bit_cast(0.25f)); + AppendVMovLiteral(&test.code, 3, std::bit_cast(1.0f)); + test.code.push_back(EncodeMimg0(0x08, 0xf, 0, false, 5)); + test.code.push_back(EncodeMimg1(0, 20)); + AppendEnd(&test.code); + const auto compiled = CompileCase(test, SubgroupSize()); + const auto &resource = compiled.program.info.images.at(0); + Require(name, "cube storage specialization", resource.cube && + resource.written && resource.dimension == + ShaderRecompiler::Decoder::ImageDimension::Dim2DArray, + "single-face cube store lost its array coordinates"); + ShaderRecompiler::IR::DescriptorValue value{}; + value.dword_count = 8; + std::copy_n(descriptor.fields, 8, value.dwords.begin()); + const auto binding = RenderExecutorTestAccess::ResolveTexture(executor, resource, value); + Image storage; + storage.view = cache.FindTexture(binding.image_id, binding.desc); + auto &image = cache.GetImage(binding.image_id); + Require(name, "face view and backing", storage.view != nullptr && + image.info.pixel_format == vk::Format::eR16G16B16A16Sfloat && + image.info.bytes_per_block == 8 && + image.info.resources.layers == face + 1 && + image.backing.layers == face + 1 && + binding.desc.view_info.type == vk::ImageViewType::e2DArray && + binding.desc.view_info.base_layer == face && + binding.desc.view_info.layer_count == 1, + "cube face did not expand the array or select its own layer"); + image.Transit(vk::ImageLayout::eGeneral, vk::AccessFlagBits2::eShaderWrite, + {}, scheduler.Current().Handle()); + storage.layout = image.backing.state.layout; + scheduler.Finish(); + Dispatch(test, compiled, {}, nullptr, nullptr, &storage); + cache.MarkGpuWritten(binding.image_id); + for (uint32_t prior = 0; prior <= face; ++prior) { + const auto pixels = ReadCachedTexel(name, context, binding.image_id, + {}, {8, 8, 1}, prior); + for (uint32_t pixel = 0; pixel < 64; ++pixel) { + Require(name, "stored and preserved faces", + pixels[pixel * 2] == (0x38000000u | half_red[prior]) && + pixels[pixel * 2 + 1] == 0x3c003400u, + "face " + std::to_string(prior) + " pixel " + + std::to_string(pixel) + " changed after writing face " + + std::to_string(face)); + } + } + } + RenderExecutorTestAccess::ResetBindings(executor); + context.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, + "cube-face backing release failed"); + std::printf("[gpu] %-32s ok\n", name); + } + void CheckRenderExecutorColorStandardTileDiscovery() { constexpr const char *name = "RenderExecutorColorStandardTile"; constexpr uintptr_t base = 0x0000000203b00000ull; @@ -12389,15 +12497,14 @@ public: const auto &image = compiled.program.info.images.at(binding.resources.front()); bool supported = - image.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D; + image.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D || + image.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray; if (resource_class == ShaderRecompiler::IR::ImageResourceClass::Sampled) { supported = supported || image.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1D || image.dimension == - ShaderRecompiler::Decoder::ImageDimension::Dim1DArray || - image.dimension == - ShaderRecompiler::Decoder::ImageDimension::Dim2DArray; + ShaderRecompiler::Decoder::ImageDimension::Dim1DArray; } Require(test.name, "dispatch", supported, "unsupported image dimension needs a matching Vulkan test view"); @@ -32429,6 +32536,7 @@ int main(int argc, char **argv) { if (argc == 2 && std::strcmp(argv[1], "--layered-image-only") == 0) { VulkanHarness vulkan; vulkan.CheckUnifiedImageViewCache(); + vulkan.CheckCubeFaceStorageExpansion(); return 0; } if (argc == 2 && std::strcmp(argv[1], "--image-view-cache-only") == 0) { @@ -32619,6 +32727,7 @@ int main(int argc, char **argv) { #endif vulkan.CheckUnifiedImageViewCache(); vulkan.CheckPackedTextureComponents(); + vulkan.CheckCubeFaceStorageExpansion(); const auto tests = MakeCases(); const auto graphics_tests = MakeGraphicsCases(); CheckOpcodeCoverage(tests, graphics_tests);