fix(shader): keep sRGB comparisons in the emulated runtime mode

(cherry picked from commit 5377951c552f2313c8f7a52b57bd7a54c5f7fb7f)
This commit is contained in:
jweidenh 2026-10-11 08:47:28 -04:00 committed by MarcZX100
parent 9e8b5a21bc
commit 82c04e4027
5 changed files with 65 additions and 14 deletions

View File

@ -1014,6 +1014,15 @@ if(BUILD_TESTING)
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
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:$<TARGET_FILE_DIR:libc>")
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)

View File

@ -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<std::uint32_t> minimalSpirv{
0x07230203u, 0x00010000u, 0u, 5u, 0u,

View File

@ -9,6 +9,7 @@
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <span>
#include <stdexcept>
@ -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<const std::uint32_t> code = offsets ? std::span<const std::uint32_t>(OffsetCode) : array ? std::span<const std::uint32_t>(ArrayCode) : std::span<const std::uint32_t>(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<std::uintptr_t>(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);

View File

@ -66,12 +66,21 @@ constexpr std::array<std::uint32_t, 9> SamplerCode(std::uint32_t word0) {
};
}
constexpr std::array<std::uint32_t, 15> 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) {

View File

@ -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) {