Merge pull request #8697 from oneandonlydean/feat/f32-denorm-preserve

feat(shader): request DenormPreserve 32 where the device preserves f32 denormals
This commit is contained in:
Dean authored and GitHub committed 2026-10-21 13:29:41 +13:00
commit 57fdffd915
15 files changed
+169 -19

No files matched your search

@@ -805,6 +805,19 @@ if(BUILD_TESTING)
endif()
endif()
add_test_executable(agc_f32_denorm_preserve_tests EXCLUDE_FROM_ALL tests/F32DenormPreserve.cpp)
target_link_libraries(agc_f32_denorm_preserve_tests PRIVATE shader_recompiler_agc_driver libc)
set_target_properties(agc_f32_denorm_preserve_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_f32_denorm_preserve_tests)
if(BUILD_TESTING)
set_target_properties(agc_f32_denorm_preserve_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
add_test(NAME agc_f32_denorm_preserve COMMAND agc_f32_denorm_preserve_tests)
set_tests_properties(agc_f32_denorm_preserve PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1")
if(WIN32)
set_tests_properties(agc_f32_denorm_preserve PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
add_test_executable(agc_spirv_branch_targets_tests EXCLUDE_FROM_ALL tests/SpirvBranchTargets.cpp)
target_link_libraries(agc_spirv_branch_targets_tests PRIVATE shader_recompiler_agc_driver libc)
set_target_properties(agc_spirv_branch_targets_tests PROPERTIES CXX_EXTENSIONS OFF)
@@ -321,6 +321,7 @@ struct VulkanDevice::State {
bool float16DenormPreserve = false;
bool float32FlushToZeroCandidate = false;
bool float32FlushToZero = false;
bool float32DenormPreserve = false;
bool probingFloatControls = false;
bool demoteToHelperInvocation = false;
bool earlyFragmentSampleMaskTestBeforeSampleCounting = false;
@@ -1329,7 +1330,8 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
const auto float32Flush = Float32FlushControlFor(driverId, floatControls.shaderDenormFlushToZeroFloat32, floatControls.denormBehaviorIndependence);
state->float32FlushToZeroCandidate = float32Flush.probe && std::getenv("APS5_NO_HW_F32_FLUSH") == nullptr;
if (state->float32FlushToZeroCandidate && float32Flush.declared) state->capabilities.push_back(spv::CapabilityDenormFlushToZero);
if (state->float16DenormPreserve && std::find(state->capabilities.begin(), state->capabilities.end(), static_cast<std::uint32_t>(spv::CapabilityDenormPreserve)) == state->capabilities.end()) state->capabilities.push_back(spv::CapabilityDenormPreserve);
state->float32DenormPreserve = floatControls.shaderDenormPreserveFloat32 == VK_TRUE && floatControls.denormBehaviorIndependence != VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_NONE && std::getenv("APS5_NO_HW_F32_PRESERVE") == nullptr;
if ((state->float16DenormPreserve || state->float32DenormPreserve) && std::find(state->capabilities.begin(), state->capabilities.end(), static_cast<std::uint32_t>(spv::CapabilityDenormPreserve)) == state->capabilities.end()) state->capabilities.push_back(spv::CapabilityDenormPreserve);
deviceExtensions.push_back(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME);
deviceExtensions.push_back(VK_KHR_8BIT_STORAGE_EXTENSION_NAME);
state->capabilities.push_back(4448);
@@ -3642,6 +3644,7 @@ ShaderRecompiler::SpirvTarget VulkanDevice::Target() const {
require(state->shaderProfile != nullptr, "shader device profile is unavailable");
auto target = state->shaderProfile->Target();
target.float32FlushToZero = state->float32FlushToZero;
target.float32DenormPreserve = state->float32DenormPreserve;
return target;
}
@@ -0,0 +1,95 @@
#include "Recompiler.hpp"
#include <spirv/unified1/spirv.hpp>
#include <array>
#include <cstdint>
#include <cstdio>
#include <exception>
#include <span>
#include <stdexcept>
#include <string>
#include <string_view>
#include <vector>
namespace {
using namespace ShaderRecompiler;
void require(bool condition, const std::string& message) {
if (!condition) throw std::runtime_error(message);
}
constexpr std::array<std::uint32_t, 4> CvtF16F32Store{0x7e021500u, 0xe0700000u, 0x80000100u, 0xbf810000u};
std::vector<std::uint32_t> recompile(bool preserve, bool flush, bool flushCapability = true) {
const std::array<std::uint32_t, 4> capabilities{spv::CapabilityShader, spv::CapabilityGroupNonUniform, spv::CapabilityDenormPreserve, spv::CapabilityDenormFlushToZero};
const std::array<std::uint32_t, 4> userData{0x10000000u, 0x00100000u, 0x40u, 0x00027facu};
const std::array<std::string_view, 2> extensions{"SPV_KHR_storage_buffer_storage_class", "SPV_KHR_float_controls"};
RecompileRequest request{};
request.shader = {ShaderStage::Compute, 0x20000u, CvtF16F32Store, 0, {}};
request.context.waveSize = 64;
request.context.userDataBaseRegister = 0;
request.context.userData = userData;
request.context.compute = ShaderComputeStageInfo{{64u, 1u, 1u}, 0u, {false, false, false}, false, 1u};
request.target.vulkanVersion = 0x00403000u;
request.target.spirvVersion = 0x00010600u;
request.target.subgroupSize = 64;
request.target.bdaAbiVersion = 1;
request.target.supportedCapabilities = std::span<const std::uint32_t>(capabilities).first(flushCapability ? capabilities.size() : capabilities.size() - 1);
request.target.supportedExtensions = extensions;
request.target.maxWorkgroupSize = {1024u, 1024u, 64u};
request.target.maxWorkgroupInvocations = 1024;
request.target.maxWorkgroupSharedMemoryBytes = 49152;
request.target.float32DenormPreserve = preserve;
request.target.float32FlushToZero = flush;
request.layout = {0, 0, 0, 128};
request.useCache = false;
auto result = Recompile(request);
require(!result.spirv.empty(), "the shader did not recompile");
return result.spirv.Copy();
}
struct Modes {
bool preserve32 = false;
bool flush32 = false;
bool preserveCapability = false;
};
Modes scan(std::span<const std::uint32_t> words) {
Modes modes;
for (std::size_t cursor = 5; cursor < words.size();) {
const auto count = words[cursor] >> 16u;
const auto op = static_cast<spv::Op>(words[cursor] & 0xffffu);
require(count != 0 && cursor + count <= words.size(), "truncated SPIR-V instruction");
if (op == spv::OpCapability && words[cursor + 1] == spv::CapabilityDenormPreserve) modes.preserveCapability = true;
if (op == spv::OpExecutionMode && count == 4 && words[cursor + 3] == 32u) {
if (words[cursor + 2] == spv::ExecutionModeDenormPreserve) modes.preserve32 = true;
if (words[cursor + 2] == spv::ExecutionModeDenormFlushToZero) modes.flush32 = true;
}
cursor += count;
}
return modes;
}
void verify() {
const auto preserved = scan(recompile(true, false));
require(preserved.preserve32 && preserved.preserveCapability && !preserved.flush32, "float32DenormPreserve did not emit the 32-bit DenormPreserve execution mode and capability");
const auto plain = scan(recompile(false, false));
require(!plain.preserve32 && !plain.flush32, "a target without float32DenormPreserve or float32FlushToZero set a 32-bit denormal execution mode");
const auto flushed = scan(recompile(true, true));
require(flushed.flush32 && !flushed.preserve32, "the hardware 32-bit flush did not take precedence over the preserve flag");
const auto nativeFlush = scan(recompile(true, true, false));
require(!nativeFlush.preserve32 && !nativeFlush.flush32, "a hardware flush without the DenormFlushToZero capability declared a 32-bit denormal execution mode");
}
}
int main() {
try {
verify();
} catch (const std::exception& error) {
std::fprintf(stderr, "F32DenormPreserve: %s\n", error.what());
return 1;
}
std::puts("F32DenormPreserve: the 32-bit DenormPreserve execution mode follows the target flag and yields to the hardware flush");
return 0;
}
@@ -743,6 +743,7 @@ void verifyKeySensitivity() {
changes("the narrow subgroup clock", [](SampleRequest& sample) { sample.request.target.narrowSubgroupClock = true; });
changes("f64 denormal preservation", [](SampleRequest& sample) { sample.request.target.float64DenormPreserve = true; });
changes("f16 denormal preservation", [](SampleRequest& sample) { sample.request.target.float16DenormPreserve = true; });
changes("f32 denormal preservation", [](SampleRequest& sample) { sample.request.target.float32DenormPreserve = true; });
changes("spanned waves", [](SampleRequest& sample) { sample.request.target.spannedWaves = true; });
changes("robust buffer access 2", [](SampleRequest& sample) { sample.request.target.robustBufferAccess2 = true; });
changes("the workgroup size limit", [](SampleRequest& sample) { sample.request.target.maxWorkgroupSize[2] = 128; });
@@ -4848,7 +4848,7 @@ void verifyPixelRequestSerialization() {
minimal.context.pixel->layerExported = true;
minimal.context.pixel->ditheredAlphaToCoverage = true;
const auto encoded = serializer.Serialize(minimal);
require(requestPrefix(encoded, 8u) == "NVNQQScAAAA=", "new requests did not use serialization version 39");
require(requestPrefix(encoded, 8u) == "NVNQQSkAAAA=", "new requests did not use serialization version 41");
const std::array<std::uint32_t, 2> fallbacks{0x40u, 0x1ac4u};
auto withFallbacks = minimal;
withFallbacks.context.linkedCallFallbacks = fallbacks;
@@ -4966,7 +4966,7 @@ void verifyPixelRequestSerialization() {
for (std::size_t bytes = 0; bytes < 45u; ++bytes) {
expectFailure([&] { static_cast<void>(serializer.Deserialize(requestPrefix(encoded, mappingOffset + bytes))); }, "truncated data", "a truncated pixel mapping, packing, dual-source, alpha-to-coverage, stencil reference export flag, primitive class, layer export, dither or front-face flag, quad pixel mask or quad sample mask was accepted");
}
for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQSgAAAA="}) {
for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQSoAAAA="}) {
expectFailure([&] { static_cast<void>(serializer.Deserialize(unsupported)); }, "serialization version", "an unsupported request version was accepted");
}
static constexpr std::string_view version18Request{
@@ -5059,6 +5059,7 @@ void verifyVertexInfoVersion19() {
auto version20Vertex = flagged;
version20Vertex.pop_back();
version20Vertex.pop_back();
version20Vertex.pop_back();
version20Vertex[4] = 20u;
const auto replay20Vertex = serializer.Deserialize(encodeBase64(version20Vertex));
require(replay20Vertex.request.context.vertex.has_value() && replay20Vertex.request.context.vertex->paClVsOutCntl == 0x89abcdefu, "a main version20 request lost PA_CL_VS_OUT_CNTL");
@@ -5069,6 +5070,7 @@ void verifyVertexInfoVersion19() {
auto legacy = plain;
legacy.pop_back();
legacy.pop_back();
legacy.pop_back();
legacy.erase(legacy.begin() + offset, legacy.begin() + offset + 4u);
for (const auto version : {16u, 17u, 18u, 19u}) {
legacy[4] = static_cast<std::uint8_t>(version);
@@ -5120,6 +5122,7 @@ void verifyVertexInfoVersion19() {
version30.pop_back();
version30.pop_back();
version30.pop_back();
version30.pop_back();
version30.erase(version30.begin() + static_cast<std::ptrdiff_t>(maskOffset + 1u + sampleMaskBytes + 4u), version30.begin() + static_cast<std::ptrdiff_t>(maskOffset + 1u + sampleMaskBytes + 4u + iterationBytes));
version30[4] = 30u;
const auto replay30 = serializer.Deserialize(encodeBase64(version30));
@@ -5127,6 +5130,7 @@ void verifyVertexInfoVersion19() {
auto spanned = sampledPixel;
spanned.target.spannedWaves = true;
auto version32 = decodeBase64(serializer.Serialize(spanned));
version32.pop_back();
require(version32.size() > 2u && version32.back() == 0u && version32[version32.size() - 2u] == 0u && version32[version32.size() - 3u] == 1u, "the spanned-waves flag is not the byte before the depth remap flag");
version32.pop_back();
version32.pop_back();
@@ -5138,8 +5142,10 @@ void verifyVertexInfoVersion19() {
version29.pop_back();
version29.pop_back();
version29.pop_back();
version29.pop_back();
version29.erase(version29.begin() + static_cast<std::ptrdiff_t>(maskOffset + 1u + sampleMaskBytes), version29.begin() + static_cast<std::ptrdiff_t>(maskOffset + 1u + sampleMaskBytes + 4u + iterationBytes));
auto version36 = sampledBytes;
version36.pop_back();
version36.erase(version36.begin() + static_cast<std::ptrdiff_t>(maskOffset + 1u + sampleMaskBytes + 4u + iterationBytes - 1u));
version36[4] = 36u;
const auto replay36 = serializer.Deserialize(encodeBase64(version36));
@@ -5151,6 +5157,7 @@ void verifyVertexInfoVersion19() {
version28.pop_back();
version28.pop_back();
version28.pop_back();
version28.pop_back();
version28.erase(version28.begin() + static_cast<std::ptrdiff_t>(maskOffset + 1u), version28.begin() + static_cast<std::ptrdiff_t>(maskOffset + 1u + sampleMaskBytes + 4u + iterationBytes));
version28[4] = 28u;
const auto replay28 = serializer.Deserialize(encodeBase64(version28));
@@ -5159,6 +5166,7 @@ void verifyVertexInfoVersion19() {
version27.pop_back();
version27.pop_back();
version27.pop_back();
version27.pop_back();
version27.erase(version27.begin() + static_cast<std::ptrdiff_t>(maskOffset), version27.begin() + static_cast<std::ptrdiff_t>(maskOffset + 1u + sampleMaskBytes + 4u + iterationBytes));
version27[4] = 27u;
const auto replay27 = serializer.Deserialize(encodeBase64(version27));
@@ -5167,6 +5175,7 @@ void verifyVertexInfoVersion19() {
legacyPixel.pop_back();
legacyPixel.pop_back();
legacyPixel.pop_back();
legacyPixel.pop_back();
legacyPixel.erase(legacyPixel.begin() + static_cast<std::ptrdiff_t>(allBitsOffset), legacyPixel.begin() + static_cast<std::ptrdiff_t>(allBitsOffset + 2u + sampleMaskBytes + 4u + iterationBytes));
legacyPixel[4] = 26u;
const auto replay26 = serializer.Deserialize(encodeBase64(legacyPixel));
@@ -5216,9 +5225,10 @@ void verifyDepthRemapRequests() {
require(serializer.Serialize(replay.request) == remapped, "a version 35 depth remap request changed on re-serialization");
const auto plainBytes = decodeBase64(plain);
const auto remappedBytes = decodeBase64(remapped);
require(plainBytes.size() == remappedBytes.size() && plainBytes.size() > 5u && std::equal(plainBytes.begin(), plainBytes.end() - 6, remappedBytes.begin()), "the depth remap flag and mode are not the last 6 bytes before the f32 flush flag of a vertex request");
require(plainBytes.size() == remappedBytes.size() && plainBytes.size() > 5u && std::equal(plainBytes.begin(), plainBytes.end() - 7, remappedBytes.begin()), "the depth remap flag and mode are not the 6 bytes before the f32 flush and denormal preserve flags of a vertex request");
for (const std::uint8_t legacyVersion : {32u, 34u}) {
auto legacyBytes = remappedBytes;
legacyBytes.pop_back();
legacyBytes[4] = legacyVersion;
legacyBytes.resize(legacyBytes.size() - 6u);
const auto legacy = serializer.Deserialize(encodeBase64(legacyBytes));
@@ -5226,6 +5236,7 @@ void verifyDepthRemapRequests() {
require(serializer.Serialize(legacy.request) == plain, ("a version " + std::to_string(legacyVersion) + " vertex request misread the fields before the depth remap").c_str());
}
auto version35 = remappedBytes;
version35.pop_back();
version35[4] = 35u;
version35.pop_back();
const auto replay35 = serializer.Deserialize(encodeBase64(version35));
@@ -5272,6 +5283,7 @@ void verifyMergedHullConfiguration() {
require(replay.request.graphics.has_value() && replay.request.graphics->tessellation.has_value() && replay.request.graphics->tessellation->mergedHull, "a merged hull configuration was lost in serialization");
require(!serializer.Deserialize(separate).request.graphics->tessellation->mergedHull, "a separate hull configuration was read as merged");
auto legacy = decodeBase64(merged);
legacy.pop_back();
require(legacy.size() > 52u && legacy[legacy.size() - 52u] == 1u, "the merged hull flag is not the last byte of the tessellation configuration");
legacy.erase(legacy.end() - 52);
for (const auto version : {30u, 31u, 32u, 34u, 35u, 36u, 37u, 38u}) {
@@ -7650,6 +7662,13 @@ int main(int argc, char** argv) {
std::vector<std::uint64_t> float32FlushKey;
RecompileCacheKey::Build(float32Flush, float32FlushKey);
require(plainKey != float32FlushKey, "f32 hardware flush did not change the cache key");
auto float32Preserve = uncached;
float32Preserve.target.float32DenormPreserve = true;
require(RequestSerializer{}.Deserialize(RequestSerializer{}.Serialize(float32Preserve)).request.target.float32DenormPreserve, "f32 denormal preservation was lost in serialization");
require(!RequestSerializer{}.Deserialize(RequestSerializer{}.Serialize(uncached)).request.target.float32DenormPreserve, "f32 denormal preservation appeared from nowhere in serialization");
std::vector<std::uint64_t> float32PreserveKey;
RecompileCacheKey::Build(float32Preserve, float32PreserveKey);
require(plainKey != float32PreserveKey && float32FlushKey != float32PreserveKey, "f32 denormal preservation did not change the cache key");
auto changedLayout = request;
changedLayout.layout.pushConstantSizeBytes = 64;
require(!Recompile(changedLayout).cacheHit, "binding layout change reused an incompatible variant");
+1
View File
@@ -289,6 +289,7 @@ private:
append(key, value.spannedWaves);
append(key, value.robustBufferAccess2);
append(key, value.float32FlushToZero);
append(key, value.float32DenormPreserve);
}
};
@@ -827,7 +827,7 @@ std::string RequestSerializer::Serialize(const RecompileRequest& request) const
std::string buffer;
Writer writer(buffer);
writer.WriteU32(0x41505335u);
writer.WriteU32(39u);
writer.WriteU32(41u);
writeShaderBinary(writer, request.shader);
writeGuestContext(writer, request.context);
writeSpirvTarget(writer, request.target);
@@ -858,6 +858,7 @@ std::string RequestSerializer::Serialize(const RecompileRequest& request) const
writer.WriteU32(request.context.vertex->depthRemapMode);
}
writer.WriteBool(request.target.float32FlushToZero);
writer.WriteBool(request.target.float32DenormPreserve);
return base64Encode(buffer);
}
@@ -866,7 +867,7 @@ DeserializedRequest RequestSerializer::Deserialize(std::string_view text) const
Reader reader(decoded);
if (reader.ReadU32() != 0x41505335u) throw std::runtime_error("invalid recompile request signature");
const auto version = reader.ReadU32();
if (version < 1u || version > 39u) throw std::runtime_error("unsupported recompile request serialization version");
if (version < 1u || version > 41u) throw std::runtime_error("unsupported recompile request serialization version");
DeserializedRequest result{};
result.request.shader = readShaderBinary(reader, result.shaderCode, result.shaderHeader);
result.request.context = readGuestContext(reader, result, version);
@@ -911,6 +912,7 @@ DeserializedRequest RequestSerializer::Deserialize(std::string_view text) const
}
}
if (version >= 36u) result.request.target.float32FlushToZero = reader.ReadBool();
if (version >= 41u) result.request.target.float32DenormPreserve = reader.ReadBool();
if (version >= 21u && reader.Remaining() != 0u) throw std::runtime_error("RequestSerializer: unexpected trailing data");
return result;
}
+1
View File
@@ -603,6 +603,7 @@ CompiledVariant emitVariant(const RecompileRequest& request, IrProgram& program)
targetOptions.spannedWaves = request.target.spannedWaves;
targetOptions.robustBufferAccess2 = request.target.robustBufferAccess2;
targetOptions.float32FlushToZero = request.target.float32FlushToZero;
targetOptions.float32DenormPreserve = request.target.float32DenormPreserve;
targetOptions.meshBarycentricPixel = request.shader.stage == ShaderStage::Fragment && request.graphics && request.graphics->mesh;
if (request.shader.stage == ShaderStage::Mesh && request.graphics && request.graphics->meshBarycentrics) {
const auto& paired = *request.graphics->meshBarycentrics;
+1
View File
@@ -290,6 +290,7 @@ struct SpirvTarget {
bool robustBufferAccess2 = false;
bool depthRangeRemap = false;
bool float32FlushToZero = false;
bool float32DenormPreserve = false;
};
struct BindingLayout {
@@ -29,6 +29,7 @@ struct SpirvTargetOptions {
bool spannedWaves = false;
bool robustBufferAccess2 = false;
bool float32FlushToZero = false;
bool float32DenormPreserve = false;
bool meshBarycentricPixel = false;
bool meshBarycentricVertex = false;
std::uint32_t barycentricSmoothLocation = 0xffffffffu;
@@ -112,6 +112,7 @@ struct SpirvEmitterState {
bool float16DenormPreserve = false;
bool robustBufferAccess2 = false;
bool float32FlushToZero = false;
bool float32DenormPreserve = false;
bool meshBarycentricPixel = false;
bool meshBarycentricVertex = false;
std::uint32_t barycentricSmoothLocation = 0xffffffffu;
@@ -277,6 +277,7 @@ std::vector<std::uint32_t> SpirvEmitter::Emit(const IrProgram& program, const Sh
state.float16DenormPreserve = target.float16DenormPreserve;
state.robustBufferAccess2 = target.robustBufferAccess2;
state.float32FlushToZero = target.float32FlushToZero;
state.float32DenormPreserve = target.float32DenormPreserve;
state.hostSubgroupSize = target.subgroupSize;
if (state.requirements.ldsLock && LdsInDeviceMemory(state) && std::find(target.supportedCapabilities.begin(), target.supportedCapabilities.end(), static_cast<std::uint32_t>(spv::CapabilityInt64Atomics)) != target.supportedCapabilities.end()) {
state.requirements.ldsLock = false;
@@ -344,6 +344,9 @@ void DefineModule(SpirvEmitterState& state) {
if (state.float32FlushToZero && std::find(state.supportedCapabilities.begin(), state.supportedCapabilities.end(), static_cast<std::uint32_t>(spv::CapabilityDenormFlushToZero)) != state.supportedCapabilities.end()) {
state.module.EmitCapability(spv::CapabilityDenormFlushToZero);
state.module.AddExecutionMode(state.mainFunc, spv::ExecutionModeDenormFlushToZero, 32u);
} else if (state.float32DenormPreserve && !state.float32FlushToZero) {
state.module.EmitCapability(spv::CapabilityDenormPreserve);
state.module.AddExecutionMode(state.mainFunc, spv::ExecutionModeDenormPreserve, 32u);
}
const bool deviceFloat64 = std::find(state.supportedCapabilities.begin(), state.supportedCapabilities.end(), static_cast<std::uint32_t>(spv::CapabilityFloat64)) != state.supportedCapabilities.end();
state.integerInterpolation = state.requirements.float64Interpolation && !deviceFloat64;
@@ -214,12 +214,12 @@ int main() {
Require(request.graphics->draw.indexAddress == 0xabcdef3400u && request.graphics->draw.indexCount == 57u && request.graphics->draw.indexElementBytes == 4u && request.graphics->draw.instanceCount == 9u);
Require(request.target.float16DenormPreserve == preserve);
auto upgraded = bytes;
upgraded[4] = 39u;
upgraded[4] = 41u;
InsertMeshBarycentricFlag(upgraded);
if (fixture.pixel) upgraded.insert(upgraded.begin() + 258u, {0u, 0u, 0u});
if (fixture.pixel) InsertPixelDefaultsAfterFrontFace(upgraded);
if (fixture.tessellation) upgraded.insert(upgraded.end() - 35u, 6u, 0u);
upgraded.insert(upgraded.end(), sizeof(std::uint64_t) + 1u + 5u + 1u, 0u);
upgraded.insert(upgraded.end(), sizeof(std::uint64_t) + 1u + 5u + 1u + 1u, 0u);
Require(serializer.Serialize(request) == EncodeBase64(upgraded));
auto truncated = bytes;
truncated.pop_back();
@@ -254,12 +254,12 @@ int main() {
layerRejected = std::string_view(error.what()) == "RequestSerializer: truncated data";
}
Require(layerRejected);
layered[4] = 39u;
layered[4] = 41u;
InsertMeshBarycentricFlag(layered);
layered.insert(layered.begin() + 259u, {0u, 0u});
InsertPixelDefaultsAfterFrontFace(layered);
if (fixture.tessellation) layered.insert(layered.end() - 35u, 6u, 0u);
layered.insert(layered.end(), sizeof(std::uint64_t) + 1u + 5u + 1u, 0u);
layered.insert(layered.end(), sizeof(std::uint64_t) + 1u + 5u + 1u + 1u, 0u);
Require(serializer.Serialize(layerReplay.request) == EncodeBase64(layered));
}
}
@@ -280,11 +280,11 @@ int main() {
Require(replay.request.context.pixel->rasterPrimitive == ShaderRecompiler::RasterPrimitive::Rectangle);
Require(replay.request.context.vertex->paClVsOutCntl == 0x00050a05u);
Require(replay.request.target.float16DenormPreserve == preserve);
bytes[4] = 39u;
bytes[4] = 41u;
InsertMeshBarycentricFlag(bytes);
bytes.insert(bytes.begin() + 259u, {0u, 0u});
InsertPixelDefaultsAfterFrontFace(bytes);
bytes.insert(bytes.end(), sizeof(std::uint64_t) + 1u + 5u + 1u, 0u);
bytes.insert(bytes.end(), sizeof(std::uint64_t) + 1u + 5u + 1u + 1u, 0u);
Require(serializer.Serialize(replay.request) == EncodeBase64(bytes));
auto truncated23 = DecodeBase64(encoded23);
@@ -410,10 +410,10 @@ int main() {
if (fixture.pixel) main38.insert(main38.begin() + 259u, {0u, 0u});
if (fixture.pixel) InsertPixelDefaultsAfterFrontFace(main38);
auto upgraded = main38;
upgraded[4] = 39u;
upgraded[4] = 41u;
if (fixture.tessellation) upgraded.insert(upgraded.end() - 35u, 1u, 0u);
main38.insert(main38.end(), sizeof(std::uint64_t) + 1u + 5u + 1u, 0u);
upgraded.insert(upgraded.end(), sizeof(std::uint64_t) + 1u + 5u + 1u, 0u);
upgraded.insert(upgraded.end(), sizeof(std::uint64_t) + 1u + 5u + 1u + 1u, 0u);
Require(serializer.Serialize(request) == EncodeBase64(upgraded));
const auto replay38 = serializer.Deserialize(EncodeBase64(main38));
Require(replay38.request.graphics.has_value() && replay38.request.graphics->tessellation.has_value() == fixture.tessellation);
@@ -429,14 +429,14 @@ int main() {
const auto replay25 = serializer.Deserialize(EncodeBase64(main25));
Require(!fixture.pixel || (replay25.request.context.pixel->layerExported == layer && replay25.request.context.pixel->ditheredAlphaToCoverage == dither && replay25.request.context.pixel->reversedBlend == 0u));
Require(replay25.request.target.float16DenormPreserve == preserve && (!fixture.tessellation || replay25.request.graphics->tessellation->primitiveGroupSize == 37u));
main25[4] = 39u;
main25[4] = 41u;
InsertMeshBarycentricFlag(main25);
if (fixture.pixel) {
main25.insert(main25.begin() + 260u, 0u);
InsertPixelDefaultsAfterFrontFace(main25);
}
if (fixture.tessellation) main25.insert(main25.end() - 35u, 1u, 0u);
main25.insert(main25.end(), sizeof(std::uint64_t) + 1u + 5u + 1u, 0u);
main25.insert(main25.end(), sizeof(std::uint64_t) + 1u + 5u + 1u + 1u, 0u);
Require(serializer.Serialize(replay25.request) == EncodeBase64(main25));
}
}
@@ -447,13 +447,19 @@ int main() {
for (const bool preserve : {false, true}) {
request.target.float16DenormPreserve = preserve;
request.target.float32FlushToZero = !preserve;
request.target.float32DenormPreserve = preserve;
const auto encoded = serializer.Serialize(request);
const auto replay = serializer.Deserialize(encoded);
Require(replay.request.target.float16DenormPreserve == preserve && replay.request.target.float32FlushToZero == !preserve);
Require(replay.request.target.float16DenormPreserve == preserve && replay.request.target.float32FlushToZero == !preserve && replay.request.target.float32DenormPreserve == preserve);
auto bytes = DecodeBase64(encoded);
Require(bytes.size() >= 8u && bytes[4] == 39u && bytes[5] == 0u && bytes[6] == 0u && bytes[7] == 0u);
Require(bytes.size() >= 8u && bytes[4] == 41u && bytes[5] == 0u && bytes[6] == 0u && bytes[7] == 0u);
auto version40 = bytes;
version40.pop_back();
version40[4] = 40u;
const auto replay40 = serializer.Deserialize(EncodeBase64(version40));
Require(!replay40.request.target.float32DenormPreserve && replay40.request.target.float32FlushToZero == !preserve);
auto future = bytes;
future[4] = 40u;
future[4] = 42u;
bool futureRejected = false;
try {
static_cast<void>(serializer.Deserialize(EncodeBase64(future)));
@@ -496,6 +502,7 @@ int main() {
Require(serializer.Deserialize(serializer.Serialize(request)).request.target.spannedWaves == spanned);
request.target.float32FlushToZero = true;
auto version35 = DecodeBase64(serializer.Serialize(request));
version35.pop_back();
const auto stencilOperationValue = std::search(version35.begin(), version35.end(), pixel.sampleLocations.begin(), pixel.sampleLocations.begin() + 4) + pixel.sampleLocations.size();
Require(stencilOperationValue < version35.end() && *stencilOperationValue == 0u);
version35.erase(stencilOperationValue);
+1
View File
@@ -242,6 +242,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti
| `APS5_NO_HOOK_SNAPSHOT` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp` | | takes the GpuMutex on every access instead of using the pending-write snapshot |
| `APS5_NO_HOST_IMPORT` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | disables importing guest memory, so address-based shaders copy instead of using it in place |
| `APS5_NO_HW_F32_FLUSH` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | skips the startup f32 denormal flush probe and keeps the emulated flush in every shader |
| `APS5_NO_HW_F32_PRESERVE` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | stops requesting DenormPreserve for 32-bit floats, so the device keeps its default f32 denormal handling |
| `APS5_NO_IMAGE_PAGE_CACHE` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp` | | leaves the main guest image's page-protection table uncached |
| `APS5_NO_INDIRECT_DRAW` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp` | | rejects indirect draw packets at submission validation |
| `APS5_NO_JOIN_WAW_CHECK` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp` | | lets a contiguous store join the pending one without the write-after-write check |