diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index bef499a10..6f112764f 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -1014,6 +1014,15 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endforeach() + add_test(NAME agc_driver_emulated_color_compare_srgb_decode COMMAND agc_driver_emulated_color_compare_tests) + set_tests_properties(agc_driver_emulated_color_compare_srgb_decode PROPERTIES + ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_SRGB_SHADER_DECODE=1" + SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + set_tests_properties(agc_driver_emulated_color_compare agc_driver_emulated_color_compare_srgb_decode PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(agc_driver_emulated_color_compare_srgb_decode PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test(NAME agc_driver_f64_denorm_preserve_spirv COMMAND agc_driver_f64_fma_subnormals_tests --verify-spv-denorm-mode) set_tests_properties(agc_driver_f64_denorm_preserve_spirv PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") if(WIN32) diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp index 1a8234e8f..86779fda9 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp @@ -2912,6 +2912,24 @@ void verifyCubeDepthCompareMode() { require(modes[selected].cube && (modes[selected].emulatedCompare & EmulatedCompare::Enabled) != 0u, "cube color comparison did not select its emulated runtime mode"); } +void verifySrgbDepthCompareModes() { + using namespace ShaderRecompiler; + for (const auto decodeFormats : {0u, 1u, 2u, 3u}) { + ImageResource image{}; + image.resourceClass = ImageResourceClass::Sampled; + image.dimension = RdnaImageDimension::Dim2D; + image.depthCompare = true; + image.srgbDecodeCompatible = false; + image.srgbDecodeFormats = decodeFormats; + const auto modes = ResourceMaterializer::RuntimeImageModes(image); + for (const auto format : {128u, 129u, 130u}) { + const DescriptorValue descriptor{{0x100u, format << 20u, 0u, 0x90000facu, 0u, 0u, 0u, 0u}, 8u}; + const auto selected = ResourceMaterializer::RuntimeImageMode(image, descriptor, modes); + require(!modes[selected].srgbDecode && (modes[selected].emulatedCompare & EmulatedCompare::Enabled) != 0u, "sRGB color comparison did not select its emulated runtime mode independently of decode support"); + } + } +} + int main(int argc, char** argv) { try { using namespace ShaderRecompiler; @@ -2958,6 +2976,7 @@ int main(int argc, char** argv) { verifyFunctionLdsBound(); verifyGuardedNullPointers(); verifyCubeDepthCompareMode(); + verifySrgbDepthCompareModes(); #if ANYPS5_ENABLE_SPIRV_TOOLS const std::vector minimalSpirv{ 0x07230203u, 0x00010000u, 0u, 5u, 0u, diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/EmulatedColorCompare.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/EmulatedColorCompare.cpp index 8a4da6082..e53b5dc51 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/EmulatedColorCompare.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/EmulatedColorCompare.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -236,10 +237,11 @@ ShaderRecompiler::RecompileResult Compile(AgcDriver::VulkanDevice& device, std:: return ShaderRecompiler::Recompile(request); } -void Run(AgcDriver::VulkanDevice& device, const Sampler& sampler, const char* name, bool offsets = false, bool useCache = false, bool array = false) { +std::uint64_t Run(AgcDriver::VulkanDevice& device, const Sampler& sampler, const char* name, bool offsets = false, bool useCache = false, bool array = false, std::uint64_t expectedVariant = 0) { Output.fill(-1.0f); const std::span code = offsets ? std::span(OffsetCode) : array ? std::span(ArrayCode) : std::span(Code); const auto result = Compile(device, Format8888UNorm, sampler, code, useCache); + if (expectedVariant != 0) Require(result.cacheHit && result.variantId == expectedVariant, std::string(name) + " did not reuse the compiled comparison artifact"); for (const auto& binding : result.bindings) { Require(binding.role != ShaderRecompiler::DescriptorRole::GuestSamplers, "emulated comparison retained a sampler binding"); Require(std::all_of(binding.imageSamplers.begin(), binding.imageSamplers.end(), [](auto mask) { return mask == 0u; }), "emulated comparison retained an unused sampler association"); @@ -251,6 +253,7 @@ void Run(AgcDriver::VulkanDevice& device, const Sampler& sampler, const char* na const float tolerance = sampler.filter == FilterPoint ? 0.0f : 1e-4f; Require(std::fabs(Output[tid] - expected) <= tolerance, std::string(name) + ": thread " + std::to_string(tid) + " compared to " + std::to_string(Output[tid]) + ", expected " + std::to_string(expected)); } + return result.variantId; } constexpr std::uint32_t Select(std::uint32_t x, std::uint32_t y, std::uint32_t z, std::uint32_t w) { @@ -306,9 +309,10 @@ bool BindsDepthCompare(const ShaderRecompiler::RecompileResult& result) { }); } -void RunSrgbZero(AgcDriver::VulkanDevice& device, std::uint32_t format, const Sampler& sampler, const char* name) { +void RunSrgbZero(AgcDriver::VulkanDevice& device, std::uint32_t format, const Sampler& sampler, const char* name, bool useCache, std::uint64_t expectedVariant) { Output.fill(-1.0f); - const auto result = Compile(device, format, sampler); + const auto result = Compile(device, format, sampler, Code, useCache); + if (useCache) Require(result.cacheHit && result.variantId == expectedVariant, std::string(name) + " did not reuse the compiled comparison artifact"); Require(!BindsDepthCompare(result), std::string(name) + " kept a depth-compare binding"); device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(Code.data())); device.WaitIdle(); @@ -333,6 +337,8 @@ int main() { try { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; + const auto* forced = std::getenv("APS5_SRGB_SHADER_DECODE"); + if (forced != nullptr && std::string_view(forced) == "1") Require((device->Target().srgbDecodeFormats & 3u) == 3u, "forced R8 and RG8 sRGB shader decoding is unavailable"); FillTexels(); FillInput(); RunCube(*device, {ClampEdge, FilterPoint}, "point cube, clamp to edge"); @@ -344,16 +350,22 @@ int main() { FillTexels(); Require(BindsDepthCompare(Compile(*device, Format32Float, {ClampEdge, FilterBilinear})), "an R32 float texture left the native comparison path"); Require(!BindsDepthCompare(Compile(*device, Format8888UNorm, {ClampEdge, FilterBilinear})), "a color texture kept a depth-compare binding"); - for (std::uint32_t function = 0; function < 8u; ++function) { - const auto point = "point 8_8_8_8 srgb function " + std::to_string(function); - const auto bilinear = "bilinear 8_8_8_8 srgb function " + std::to_string(function); - RunSrgbZero(*device, Format8888Srgb, {ClampEdge, FilterPoint, BorderBlack, 0u, 0u, false, function}, point.c_str()); - RunSrgbZero(*device, Format8888Srgb, {ClampEdge, FilterBilinear, BorderBlack, 0u, 0u, false, function}, bilinear.c_str()); + for (const bool useCache : {false, true}) { + const auto variant = useCache ? Run(*device, {ClampEdge, FilterPoint}, "cached UNORM before sRGB", false, true) : 0u; + if (useCache) Require(variant != 0, "the cached comparison has no compiled artifact identity"); + for (const auto format : {Format8Srgb, Format8_8Srgb, Format8888Srgb}) { + for (std::uint32_t function = 0; function < 8u; ++function) { + for (const auto filter : {FilterPoint, FilterBilinear}) { + const auto name = std::string(useCache ? "cached" : "uncached") + " sRGB format " + std::to_string(format) + " function " + std::to_string(function) + (filter == FilterPoint ? " point" : " bilinear"); + RunSrgbZero(*device, format, {ClampEdge, filter, BorderBlack, 0u, 0u, false, function}, name.c_str(), useCache, variant); + } + } + } + if (useCache) Run(*device, {ClampEdge, FilterPoint}, "cached UNORM after sRGB", false, true, false, variant); + } + for (const auto format : {Format8Srgb, Format8_8Srgb, Format8888Srgb}) { + Reject(*device, format, {ClampEdge, FilterBilinear}, "bilinear cube comparison requires DISABLE_CUBE_WRAP", CubeCode, true, true); } - RunSrgbZero(*device, Format8Srgb, {ClampEdge, FilterPoint}, "point 8 srgb"); - RunSrgbZero(*device, Format8Srgb, {ClampEdge, FilterBilinear}, "bilinear 8 srgb"); - RunSrgbZero(*device, Format8_8Srgb, {ClampEdge, FilterPoint}, "point 8_8 srgb"); - RunSrgbZero(*device, Format8_8Srgb, {ClampEdge, FilterBilinear}, "bilinear 8_8 srgb"); Run(*device, {ClampEdge, FilterPoint}, "point, clamp to edge"); Run(*device, {ClampWrap, FilterPoint}, "point, wrap"); Run(*device, {ClampWrap, FilterPoint}, "point, wrap with negative offsets", true); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageSrgbLoad.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSrgbLoad.cpp index a1f35fc45..ec759a3f7 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ImageSrgbLoad.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSrgbLoad.cpp @@ -66,12 +66,21 @@ constexpr std::array SamplerCode(std::uint32_t word0) { }; } +constexpr std::array MixedSamplerCode(std::uint32_t word0) { + return { + 0x34060084u, 0x7e280280u, 0x7e2a0280u, 0x7e2c0280u, 0xf0bc0108u, 0x00610a14u, 0xbf8c3f70u, + 0xe0701000u, 0x80000a03u, word0, 0x00610a14u, 0xbf8c3f70u, 0xe0701004u, 0x80000a03u, 0xbf810000u, + }; +} + alignas(256) constexpr auto LoadXyzw = LoadCode(0xfu); alignas(256) constexpr auto LoadXy = LoadCode(0x3u); alignas(256) constexpr auto StoreX = StoreCode(0x1u); alignas(256) constexpr auto StoreXy = StoreCode(0x3u); alignas(256) constexpr auto SampleLz = SamplerCode(0xf09c0f08u); alignas(256) constexpr auto Gather4Lz = SamplerCode(0xf11c0108u); +alignas(256) constexpr auto MixedSampleLz = MixedSamplerCode(0xf09c0f08u); +alignas(256) constexpr auto MixedGather4Lz = MixedSamplerCode(0xf11c0108u); class GuestBlock { public: @@ -235,6 +244,8 @@ int main() { CheckStoredLoad(*device, block, LoadXyzw, 0xfu, Srgb8, SwizzleXXX1, "8_SRGB image_load dmask:0xf X X X 1 of 8_UNORM image_stores"); RequireRefused(*device, SampleLz, "image_sample_lz"); RequireRefused(*device, Gather4Lz, "image_gather4_lz"); + RequireRefused(*device, MixedSampleLz, "image_sample_c_lz followed by image_sample_lz"); + RequireRefused(*device, MixedGather4Lz, "image_sample_c_lz followed by image_gather4_lz"); std::puts("image sRGB load tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp b/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp index dd0aaded9..2822f4124 100644 --- a/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp +++ b/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp @@ -191,8 +191,8 @@ DecodedImage decodeImageDescriptor(const DescriptorValue& descriptor, const Imag if (((descriptorImageSwizzle(descriptor) >> (component * 3u)) & 7u) == 7u) throw std::runtime_error("selects a channel the converted image format does not have"); } } - decoded.srgbDecode = !storage && (srgbDecodeFormats & SrgbDecodeBit(format)) != 0u; - if (decoded.srgbDecode && !base.srgbDecodeCompatible && !base.depthCompare) { + decoded.srgbDecode = !storage && !base.depthCompare && (srgbDecodeFormats & SrgbDecodeBit(format)) != 0u; + if (decoded.srgbDecode && !base.srgbDecodeCompatible) { throw std::runtime_error("samples or gathers an sRGB image the device cannot sample, which is not implemented"); } if (storage || decoded.conversionFormat != IrBufferFormat::Invalid) {