Merge pull request #1383 from nisbenz/fix/shader-memtime-narrow-subgroup-clock
fix(shader): read s_memtime from the device clock where RADV's subgroup clock is 20 bits
This commit is contained in:
commit
e08c816abc
|
|
@ -126,6 +126,7 @@ set(agcDriverSources
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/GuestMemory.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/ShaderMemory.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/BdaFeatures.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/SubgroupClock.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/Pm4.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/VulkanDevice.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/AspectFit.cpp
|
||||
|
|
@ -273,6 +274,18 @@ target_link_libraries(agc_structurizer_tests PRIVATE shader_recompiler_agc_drive
|
|||
set_target_properties(agc_structurizer_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(agc_structurizer_tests)
|
||||
|
||||
add_test_executable(agc_subgroup_clock_tests EXCLUDE_FROM_ALL
|
||||
tests/SubgroupClock.cpp
|
||||
Execution/src/SubgroupClock.cpp
|
||||
)
|
||||
target_include_directories(agc_subgroup_clock_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/3rdparty/Vulkan-Headers/include)
|
||||
set_target_properties(agc_subgroup_clock_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(agc_subgroup_clock_tests)
|
||||
if(BUILD_TESTING)
|
||||
set_target_properties(agc_subgroup_clock_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
|
||||
add_test(NAME agc_subgroup_clock COMMAND agc_subgroup_clock_tests)
|
||||
endif()
|
||||
|
||||
add_test_executable(agc_spirv_module_tests EXCLUDE_FROM_ALL tests/SpirvModule.cpp)
|
||||
target_link_libraries(agc_spirv_module_tests PRIVATE shader_recompiler_agc_driver libc)
|
||||
set_target_properties(agc_spirv_module_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
|
|
@ -661,6 +674,7 @@ if(AGC_BUILD_VISUAL_TEST)
|
|||
Execution/src/PresentationScaler.cpp
|
||||
Execution/src/GuestMemory.cpp
|
||||
Execution/src/BdaFeatures.cpp
|
||||
Execution/src/SubgroupClock.cpp
|
||||
Graphics/src/Draw.cpp
|
||||
Graphics/src/DepthSurface.cpp
|
||||
Graphics/src/ColorTargetLayout.cpp
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_SUBGROUPCLOCK_HPP
|
||||
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_SUBGROUPCLOCK_HPP
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
#include <string_view>
|
||||
|
||||
namespace AgcDriver {
|
||||
|
||||
bool NarrowSubgroupClock(VkDriverId driver, std::string_view deviceName);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/SubgroupClock.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
namespace AgcDriver {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::array<std::string_view, 19> NarrowClockFamilies{
|
||||
"NAVI21", "NAVI22", "VANGOGH", "NAVI23", "NAVI24", "REMBRANDT", "RAPHAEL_MENDOCINO",
|
||||
"NAVI31", "NAVI32", "NAVI33", "PHOENIX", "PHOENIX2",
|
||||
"STRIX1", "STRIX_HALO", "KRACKAN1", "GFX1153", "GFX1156",
|
||||
"GFX1170", "GFX1171",
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
bool NarrowSubgroupClock(VkDriverId driver, std::string_view deviceName) {
|
||||
if (driver != VK_DRIVER_ID_MESA_RADV) return false;
|
||||
constexpr std::string_view prefix = "(RADV ";
|
||||
const auto start = deviceName.rfind(prefix);
|
||||
if (start == std::string_view::npos || deviceName.empty() || deviceName.back() != ')') return false;
|
||||
const auto family = deviceName.substr(start + prefix.size(), deviceName.size() - start - prefix.size() - 1u);
|
||||
return std::find(NarrowClockFamilies.begin(), NarrowClockFamilies.end(), family) != NarrowClockFamilies.end();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/BdaFeatures.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/SubgroupClock.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/PresentationScaler.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp"
|
||||
|
|
@ -193,6 +194,7 @@ struct VulkanDevice::State {
|
|||
bool geometryShader = false;
|
||||
bool sampleRateShading = false;
|
||||
bool shaderClock = false;
|
||||
bool narrowSubgroupClock = false;
|
||||
// VK_EXT_descriptor_indexing with non-uniform image array indexing (bindless image tables in
|
||||
// graphics stages, and compute workgroups wider than a wave).
|
||||
bool descriptorIndexing = false;
|
||||
|
|
@ -768,6 +770,13 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u
|
|||
state->InstanceFunction<PFN_vkGetPhysicalDeviceFeatures2>("vkGetPhysicalDeviceFeatures2")(selected, &features);
|
||||
state->shaderClock = clockFeatures.shaderSubgroupClock == VK_TRUE && clockFeatures.shaderDeviceClock == VK_TRUE;
|
||||
}
|
||||
if (state->shaderClock && hasExtension(VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME)) {
|
||||
VkPhysicalDeviceDriverProperties driver{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES};
|
||||
VkPhysicalDeviceProperties2 driverProperties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &driver};
|
||||
state->InstanceFunction<PFN_vkGetPhysicalDeviceProperties2>("vkGetPhysicalDeviceProperties2")(selected, &driverProperties);
|
||||
state->narrowSubgroupClock = NarrowSubgroupClock(driver.driverID, state->properties.deviceName);
|
||||
APS5_LOG_OUT("Shader clock driver=%d narrowSubgroupClock=%d", static_cast<int>(driver.driverID), state->narrowSubgroupClock ? 1 : 0);
|
||||
}
|
||||
std::vector<const char*> deviceExtensions;
|
||||
if (window != nullptr) deviceExtensions.assign(presentationExtensions.begin(), presentationExtensions.end());
|
||||
if (state->fragmentShaderBarycentric) {
|
||||
|
|
@ -2429,6 +2438,7 @@ ShaderRecompiler::SpirvTarget VulkanDevice::Target() const {
|
|||
ShaderRecompiler::SpirvTarget target{VK_API_VERSION_1_1, state->meshShader ? 0x00010400u : 0x00010300u, state->subgroup.subgroupSize, ShaderRecompiler::BdaAbi::Version, state->capabilities, state->spirvExtensions, false, {limits.maxComputeWorkGroupSize[0], limits.maxComputeWorkGroupSize[1], limits.maxComputeWorkGroupSize[2]}, limits.maxComputeWorkGroupInvocations, limits.maxComputeSharedMemorySize, {}, {}};
|
||||
target.fragmentShaderBarycentricEnabled = state->fragmentShaderBarycentric;
|
||||
target.nonConstantImageOffsets = state->maintenance8;
|
||||
target.narrowSubgroupClock = state->narrowSubgroupClock;
|
||||
target.srgbDecodeFormats = state->srgbDecodeFormats;
|
||||
if (state->meshShader) {
|
||||
const auto& mesh = state->meshLimits;
|
||||
|
|
|
|||
|
|
@ -333,6 +333,7 @@ void verifyKeySensitivity() {
|
|||
changes("barycentrics", [](SampleRequest& sample) { sample.request.target.fragmentShaderBarycentricEnabled = true; });
|
||||
changes("non-constant texel offsets", [](SampleRequest& sample) { sample.request.target.nonConstantImageOffsets = true; });
|
||||
changes("the sRGB formats decoded in the shader", [](SampleRequest& sample) { sample.request.target.srgbDecodeFormats = 2u; });
|
||||
changes("the narrow subgroup clock", [](SampleRequest& sample) { sample.request.target.narrowSubgroupClock = true; });
|
||||
changes("the workgroup size limit", [](SampleRequest& sample) { sample.request.target.maxWorkgroupSize[2] = 128; });
|
||||
changes("the invocation limit", [](SampleRequest& sample) { sample.request.target.maxWorkgroupInvocations = 512; });
|
||||
changes("the shared memory limit", [](SampleRequest& sample) { sample.request.target.maxWorkgroupSharedMemoryBytes = 32768; });
|
||||
|
|
|
|||
|
|
@ -876,12 +876,12 @@ void verifyPixelRequestSerialization() {
|
|||
minimal.context.waveSize = 64;
|
||||
minimal.context.pixel = ShaderPixelStageInfo{};
|
||||
const auto encoded = serializer.Serialize(minimal);
|
||||
require(requestPrefix(encoded, 8u) == "NVNQQQsAAAA=", "new requests did not use serialization version 11");
|
||||
require(requestPrefix(encoded, 8u) == "NVNQQQwAAAA=", "new requests did not use serialization version 12");
|
||||
constexpr std::size_t mappingOffset = 8u + 37u + 18u + 163u;
|
||||
for (std::size_t bytes = 0; bytes < 8u; ++bytes) {
|
||||
expectFailure([&] { static_cast<void>(serializer.Deserialize(requestPrefix(encoded, mappingOffset + bytes))); }, "truncated data", "a truncated version-11 pixel mapping was accepted");
|
||||
expectFailure([&] { static_cast<void>(serializer.Deserialize(requestPrefix(encoded, mappingOffset + bytes))); }, "truncated data", "a truncated version-12 pixel mapping was accepted");
|
||||
}
|
||||
for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQQwAAAA="}) {
|
||||
for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQQ0AAAA="}) {
|
||||
expectFailure([&] { static_cast<void>(serializer.Deserialize(unsupported)); }, "serialization version", "an unsupported request version was accepted");
|
||||
}
|
||||
}
|
||||
|
|
@ -953,7 +953,7 @@ void verifyLegacyPixelRequests() {
|
|||
require(!pixel.orderedPixelShader, "a legacy request became a primitive-ordered pixel shader");
|
||||
require(request.shader.stage == ShaderStage::Fragment && request.shader.code.size() == 1u && request.shader.code[0] == 0xbf810000u && !request.context.vertex.has_value() && request.context.memory.empty(), "legacy guest context was misaligned");
|
||||
require(request.target.vulkanVersion == 0x00401000u && request.target.spirvVersion == 0x00010300u && request.target.subgroupSize == 64u && request.layout.firstBinding == 11u && request.layout.pushConstantSizeBytes == 128u, "legacy target or binding layout was misaligned");
|
||||
require(request.useCache == (version == 1u) && request.target.nonConstantImageOffsets == (version >= 6u) && request.target.srgbDecodeFormats == 0u, "legacy request trailer was misread");
|
||||
require(request.useCache == (version == 1u) && request.target.nonConstantImageOffsets == (version >= 6u) && request.target.srgbDecodeFormats == 0u && !request.target.narrowSubgroupClock, "legacy request trailer was misread");
|
||||
const auto upgraded = serializer.Deserialize(serializer.Serialize(request));
|
||||
require(upgraded.request.context.pixel->targetExportMapping == pixel.targetExportMapping && RecompileCacheKey::ContextHash(upgraded.request) == RecompileCacheKey::ContextHash(request), "upgrading a legacy capture changed its pixel mapping");
|
||||
}
|
||||
|
|
@ -1198,6 +1198,53 @@ void verifyComputedTexelOffsets() {
|
|||
}
|
||||
}
|
||||
|
||||
void verifyShaderClockScopes() {
|
||||
using namespace ShaderRecompiler;
|
||||
static std::array<std::uint32_t, 64> output{};
|
||||
const auto outputBase = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(output.data()));
|
||||
const std::array<std::uint32_t, 4> userData{static_cast<std::uint32_t>(outputBase), static_cast<std::uint32_t>((outputBase >> 32u) & 0xffffu), 64u, 0xfacu};
|
||||
const std::array<std::uint32_t, 2> capabilities{1u, static_cast<std::uint32_t>(spv::CapabilityShaderClockKHR)};
|
||||
const std::array<std::string_view, 1> extensions{"SPV_KHR_shader_clock"};
|
||||
const auto scope = [&](std::uint32_t clockOpcode, bool narrow) {
|
||||
const std::vector<std::uint32_t> code{clockOpcode, 0x00000000u, 0xbf8cc07fu, 0x7e020204u, 0xe0700000u, 0x80000100u, 0xbf810000u};
|
||||
RecompileRequest request{};
|
||||
request.shader = {ShaderStage::Compute, 0x30000u, code, 0, {}};
|
||||
request.context.waveSize = 32;
|
||||
request.context.userDataBaseRegister = 0;
|
||||
request.context.userData = userData;
|
||||
request.context.compute = ShaderComputeStageInfo{{32u, 1u, 1u}, 0u, {false, false, false}, false, 1u};
|
||||
request.target.vulkanVersion = 0x00401000u;
|
||||
request.target.spirvVersion = 0x00010300u;
|
||||
request.target.subgroupSize = 32;
|
||||
request.target.supportedCapabilities = capabilities;
|
||||
request.target.supportedExtensions = extensions;
|
||||
request.target.narrowSubgroupClock = narrow;
|
||||
request.layout.pushConstantSizeBytes = 128;
|
||||
request.useCache = false;
|
||||
AgcDriver::ShaderMemory memory({});
|
||||
static_cast<void>(memory.Capture(request));
|
||||
request.context.memory = memory.Regions();
|
||||
const auto words = Recompile(request).spirv;
|
||||
std::map<std::uint32_t, std::uint32_t> constants;
|
||||
std::vector<std::uint32_t> scopeIds;
|
||||
for (std::size_t cursor = 5; cursor < words.size();) {
|
||||
const auto count = words[cursor] >> 16u;
|
||||
require(count != 0 && count <= words.size() - cursor, "shader clock: truncated SPIR-V instruction");
|
||||
const auto op = words[cursor] & 0xffffu;
|
||||
if (op == spv::OpConstant && count == 4u) constants[words[cursor + 2]] = words[cursor + 3];
|
||||
if (op == spv::OpReadClockKHR) scopeIds.push_back(words[cursor + 3]);
|
||||
cursor += count;
|
||||
}
|
||||
require(scopeIds.size() == 1u && constants.contains(scopeIds[0]), "shader clock: expected one OpReadClockKHR with a constant scope");
|
||||
return constants[scopeIds[0]];
|
||||
};
|
||||
constexpr std::uint32_t Memtime = 0xf4900100u;
|
||||
constexpr std::uint32_t Memrealtime = 0xf4940100u;
|
||||
require(scope(Memtime, false) == spv::ScopeSubgroup, "shader clock: s_memtime does not read the subgroup clock");
|
||||
require(scope(Memtime, true) == spv::ScopeDevice, "shader clock: s_memtime reads the narrow subgroup clock");
|
||||
require(scope(Memrealtime, false) == spv::ScopeDevice && scope(Memrealtime, true) == spv::ScopeDevice, "shader clock: s_memrealtime does not read the device clock");
|
||||
}
|
||||
|
||||
void verifyUnnormalizedSamplers() {
|
||||
using namespace ShaderRecompiler;
|
||||
constexpr std::uint32_t Format8888UNorm = 56;
|
||||
|
|
@ -1674,6 +1721,7 @@ int main() {
|
|||
verifyPixelExportReplay();
|
||||
verifyPixelParameterSlots();
|
||||
verifyComputedTexelOffsets();
|
||||
verifyShaderClockScopes();
|
||||
verifyUnnormalizedSamplers();
|
||||
verifyUnusedUnnormalizedSampler();
|
||||
verifyWaveUniformValues();
|
||||
|
|
@ -1776,6 +1824,9 @@ int main() {
|
|||
auto srgb = uncached;
|
||||
srgb.target.srgbDecodeFormats = 3u;
|
||||
require(RequestSerializer{}.Deserialize(RequestSerializer{}.Serialize(srgb)).request.target.srgbDecodeFormats == 3u, "the sRGB formats decoded in the shader were lost in serialization");
|
||||
auto narrowClock = uncached;
|
||||
narrowClock.target.narrowSubgroupClock = true;
|
||||
require(RequestSerializer{}.Deserialize(RequestSerializer{}.Serialize(narrowClock)).request.target.narrowSubgroupClock, "the narrow subgroup clock was lost in serialization");
|
||||
auto changedLayout = request;
|
||||
changedLayout.layout.pushConstantSizeBytes = 64;
|
||||
require(!Recompile(changedLayout).cacheHit, "binding layout change reused an incompatible variant");
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/SubgroupClock.hpp"
|
||||
#include <cstdio>
|
||||
#include <string_view>
|
||||
|
||||
namespace {
|
||||
|
||||
struct Case {
|
||||
VkDriverId driver;
|
||||
std::string_view name;
|
||||
bool narrow;
|
||||
};
|
||||
|
||||
constexpr Case Cases[]{
|
||||
{VK_DRIVER_ID_MESA_RADV, "AMD Radeon RX 6750 XT (RADV NAVI22)", true},
|
||||
{VK_DRIVER_ID_MESA_RADV, "AMD Ryzen 5 7600X 6-Core Processor (RADV RAPHAEL_MENDOCINO)", true},
|
||||
{VK_DRIVER_ID_MESA_RADV, "AMD Radeon RX 7900 XTX (RADV NAVI31)", true},
|
||||
{VK_DRIVER_ID_MESA_RADV, "AMD Radeon 890M (RADV STRIX1)", true},
|
||||
{VK_DRIVER_ID_MESA_RADV, "AMD Radeon RX 9070 XT (RADV GFX1201)", false},
|
||||
{VK_DRIVER_ID_MESA_RADV, "AMD Radeon RX 5700 XT (RADV NAVI10)", false},
|
||||
{VK_DRIVER_ID_MESA_RADV, "AMD BC-250 (RADV GFX1013)", false},
|
||||
{VK_DRIVER_ID_MESA_RADV, "AMD Radeon RX 6750 XT (RADV NAVI22", false},
|
||||
{VK_DRIVER_ID_MESA_RADV, "AMD Radeon RX 6750 XT (RADV NAVI2)", false},
|
||||
{VK_DRIVER_ID_MESA_RADV, "", false},
|
||||
{VK_DRIVER_ID_AMD_PROPRIETARY, "AMD Radeon RX 6750 XT (RADV NAVI22)", false},
|
||||
{VK_DRIVER_ID_AMD_OPEN_SOURCE, "AMD Radeon RX 6750 XT", false},
|
||||
{VK_DRIVER_ID_NVIDIA_PROPRIETARY, "NVIDIA GeForce RTX 5070 Ti", false},
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
int failures = 0;
|
||||
for (const auto& item : Cases) {
|
||||
if (AgcDriver::NarrowSubgroupClock(item.driver, item.name) != item.narrow) {
|
||||
std::fprintf(stderr, "driver %d \"%.*s\": expected %s subgroup clock\n", static_cast<int>(item.driver), static_cast<int>(item.name.size()), item.name.data(), item.narrow ? "a narrow" : "a full");
|
||||
++failures;
|
||||
}
|
||||
}
|
||||
return failures == 0 ? 0 : 1;
|
||||
}
|
||||
|
|
@ -57,9 +57,23 @@ void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, con
|
|||
Require(actual == expected, std::string("shader clock: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected));
|
||||
}
|
||||
|
||||
void Check() {
|
||||
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
|
||||
const std::uint32_t* in = &Input[tid * Inputs];
|
||||
const std::uint32_t* out = &Output[tid * Results];
|
||||
const std::uint32_t* first = &Output[(tid & ~7u) * Results];
|
||||
for (std::uint32_t index = 0; index < 8; ++index) Expect(tid, out[index], first[index], "clock read uniform over the wave");
|
||||
Require(Pair(out + 4) >= Pair(out), "memtime: lane " + std::to_string(tid) + " went back from " + std::to_string(Pair(out)) + " to " + std::to_string(Pair(out + 4)));
|
||||
Require(Pair(out + 6) >= Pair(out + 2), "memrealtime: lane " + std::to_string(tid) + " went back from " + std::to_string(Pair(out + 2)) + " to " + std::to_string(Pair(out + 6)));
|
||||
Require(Pair(out + 4) != 0u && Pair(out + 6) != 0u, "clocks read zero");
|
||||
std::uint32_t sum = 0u;
|
||||
for (std::uint32_t step = 0; step < 2000u; ++step) sum = (sum & 0xffffffu) * 3u + in[0];
|
||||
Expect(tid, out[8], sum, "loop result");
|
||||
}
|
||||
}
|
||||
|
||||
void Run(AgcDriver::VulkanDevice& device) {
|
||||
for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]);
|
||||
Output.fill(0xdeadbeefu);
|
||||
std::vector<std::uint32_t> userData(8, 0u);
|
||||
const auto input = BufferDescriptor(Input.data(), static_cast<std::uint32_t>(Input.size() * 4u));
|
||||
const auto output = BufferDescriptor(Output.data(), static_cast<std::uint32_t>(Output.size() * 4u));
|
||||
|
|
@ -76,22 +90,12 @@ void Run(AgcDriver::VulkanDevice& device) {
|
|||
};
|
||||
request.useCache = false;
|
||||
const auto result = ShaderRecompiler::Recompile(request);
|
||||
device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
|
||||
device.WaitIdle();
|
||||
}
|
||||
|
||||
void Check() {
|
||||
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
|
||||
const std::uint32_t* in = &Input[tid * Inputs];
|
||||
const std::uint32_t* out = &Output[tid * Results];
|
||||
const std::uint32_t* first = &Output[(tid & ~7u) * Results];
|
||||
for (std::uint32_t index = 0; index < 8; ++index) Expect(tid, out[index], first[index], "clock read uniform over the wave");
|
||||
Require(Pair(out + 4) >= Pair(out), "memtime: lane " + std::to_string(tid) + " went back from " + std::to_string(Pair(out)) + " to " + std::to_string(Pair(out + 4)));
|
||||
Require(Pair(out + 6) >= Pair(out + 2), "memrealtime: lane " + std::to_string(tid) + " went back from " + std::to_string(Pair(out + 2)) + " to " + std::to_string(Pair(out + 6)));
|
||||
Require(Pair(out + 4) != 0u && Pair(out + 6) != 0u, "clocks read zero");
|
||||
std::uint32_t sum = 0u;
|
||||
for (std::uint32_t step = 0; step < 2000u; ++step) sum = (sum & 0xffffffu) * 3u + in[0];
|
||||
Expect(tid, out[8], sum, "loop result");
|
||||
constexpr std::uint32_t Passes = 128;
|
||||
for (std::uint32_t pass = 0; pass < Passes; ++pass) {
|
||||
Output.fill(0xdeadbeefu);
|
||||
device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
|
||||
device.WaitIdle();
|
||||
Check();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -204,6 +204,7 @@ private:
|
|||
append(key, value.tessellation);
|
||||
append(key, value.nonConstantImageOffsets);
|
||||
append(key, value.srgbDecodeFormats);
|
||||
append(key, value.narrowSubgroupClock);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -699,7 +699,7 @@ std::string RequestSerializer::Serialize(const RecompileRequest& request) const
|
|||
std::string buffer;
|
||||
Writer writer(buffer);
|
||||
writer.WriteU32(0x41505335u);
|
||||
writer.WriteU32(11u);
|
||||
writer.WriteU32(12u);
|
||||
writeShaderBinary(writer, request.shader);
|
||||
writeGuestContext(writer, request.context);
|
||||
writeSpirvTarget(writer, request.target);
|
||||
|
|
@ -717,6 +717,7 @@ std::string RequestSerializer::Serialize(const RecompileRequest& request) const
|
|||
writer.WriteBool(request.target.nonConstantImageOffsets);
|
||||
writer.WriteU32(request.target.srgbDecodeFormats);
|
||||
if (request.context.compute.has_value()) writer.WriteU32(request.context.compute->scratchDwords);
|
||||
writer.WriteBool(request.target.narrowSubgroupClock);
|
||||
return base64Encode(buffer);
|
||||
}
|
||||
|
||||
|
|
@ -725,7 +726,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 > 11u) throw std::runtime_error("unsupported recompile request serialization version");
|
||||
if (version < 1u || version > 12u) 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);
|
||||
|
|
@ -744,6 +745,7 @@ DeserializedRequest RequestSerializer::Deserialize(std::string_view text) const
|
|||
if (version >= 6u) result.request.target.nonConstantImageOffsets = reader.ReadBool();
|
||||
if (version >= 9u) result.request.target.srgbDecodeFormats = reader.ReadU32();
|
||||
if (version >= 10u && result.request.context.compute.has_value()) result.request.context.compute->scratchDwords = reader.ReadU32();
|
||||
if (version >= 12u) result.request.target.narrowSubgroupClock = reader.ReadBool();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -331,6 +331,7 @@ CompiledVariant compileVariant(const RecompileRequest& request, IrProgram progra
|
|||
targetOptions.supportedCapabilities = request.target.supportedCapabilities;
|
||||
targetOptions.supportedExtensions = request.target.supportedExtensions;
|
||||
targetOptions.nonConstantImageOffsets = request.target.nonConstantImageOffsets;
|
||||
targetOptions.narrowSubgroupClock = request.target.narrowSubgroupClock;
|
||||
|
||||
constexpr SpirvEmitter spirvEmitter;
|
||||
RecompileResult result;
|
||||
|
|
|
|||
|
|
@ -200,6 +200,7 @@ struct SpirvTarget {
|
|||
std::optional<TessellationTargetLimits> tessellation;
|
||||
bool nonConstantImageOffsets = false;
|
||||
std::uint32_t srgbDecodeFormats = 0;
|
||||
bool narrowSubgroupClock = false;
|
||||
};
|
||||
|
||||
struct BindingLayout {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ struct SpirvTargetOptions {
|
|||
std::span<const std::uint32_t> supportedCapabilities;
|
||||
std::span<const std::string_view> supportedExtensions;
|
||||
bool nonConstantImageOffsets = false;
|
||||
bool narrowSubgroupClock = false;
|
||||
};
|
||||
|
||||
class SpirvEmitter {
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ struct SpirvEmitterState {
|
|||
std::uint32_t laneCount = 1;
|
||||
std::uint32_t hostSubgroupSize = 0;
|
||||
bool splitSubgroup = false;
|
||||
bool narrowSubgroupClock = false;
|
||||
std::unordered_set<const IrValue*> sharedLaneValues;
|
||||
std::uint32_t laneHalf = 0;
|
||||
// The target's SPIR-V version and what the device accepts, for capabilities an emitter adds
|
||||
|
|
|
|||
|
|
@ -248,6 +248,7 @@ std::vector<std::uint32_t> SpirvEmitter::Emit(const IrProgram& program, const Sh
|
|||
state.supportedCapabilities = target.supportedCapabilities;
|
||||
state.supportedExtensions = target.supportedExtensions;
|
||||
state.nonConstantImageOffsets = target.nonConstantImageOffsets;
|
||||
state.narrowSubgroupClock = target.narrowSubgroupClock;
|
||||
state.hostSubgroupSize = target.subgroupSize;
|
||||
state.splitSubgroup = program.WaveSize() == 32u && target.subgroupSize > 32u;
|
||||
if (state.splitSubgroup && (state.requirements.subgroupBallot || state.requirements.subgroupShuffle)) state.requirements.subgroupLocalInvocationId = true;
|
||||
|
|
|
|||
|
|
@ -1170,7 +1170,7 @@ std::uint32_t EmitReadClock(SpirvValueEmitContext& ctx, const IrValue& inst, spv
|
|||
}
|
||||
|
||||
std::uint32_t EmitShaderClock(SpirvValueEmitContext& ctx, const IrValue& inst) {
|
||||
return EmitReadClock(ctx, inst, spv::ScopeSubgroup);
|
||||
return EmitReadClock(ctx, inst, ctx.state.narrowSubgroupClock ? spv::ScopeDevice : spv::ScopeSubgroup);
|
||||
}
|
||||
|
||||
std::uint32_t EmitRealtimeClock(SpirvValueEmitContext& ctx, const IrValue& inst) {
|
||||
|
|
|
|||
|
|
@ -252,6 +252,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [Unnormalized S#s](../../core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp) are accepted only when direct and used only by explicit-LOD `image_sample` without offset, comparison, derivatives or `_a`, on a single-level, single-layer 1D or 2D view of mip 0, even when the image is also sampled through a normalized S#.
|
||||
- An [unnormalized S#](../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestSamplerResource.cpp) (libSceAgcDriver) throws with unequal minification and magnification filters, anisotropy, `TRUNC_COORD`, `MC_COORD_TRUNC` or X/Y clamping other than last-texel or border.
|
||||
- [Image atomics](../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) on 32_SINT and 32_FLOAT surfaces act on the raw dword through an R32_UINT storage view; their float results are assumed to match those measured on 32_UINT surfaces. Other formats throw.
|
||||
- [`s_memtime`](../../core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp) on RADV with a GFX10.3 to GFX11.7 GPU (RDNA 2, RDNA 3, RDNA 3.5) reads the Vulkan device clock instead of the subgroup clock, which [ACO](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/amd/compiler/instruction_selection/aco_select_nir_intrinsics.cpp) reads from the 20-bit `SHADER_CYCLES` register there and which wraps every 2^20 cycles. The device clock is a monotonic 64-bit counter, but it counts the 100 MHz reference clock (the same one as `s_memrealtime`), not shader cycles, so `s_memtime` deltas are about 20 times smaller than on hardware. The families are matched by the name RADV reports ([`NarrowSubgroupClock`](../../core/libs/prx/libSceAgcDriver/Execution/src/SubgroupClock.cpp)); a family added to Mesa later reads the subgroup clock until it is listed
|
||||
- [`ds_wrxchg2_rtn_b32`, `ds_wrxchg2st64_rtn_b32`, `ds_wrxchg2_rtn_b64` and `ds_wrxchg2st64_rtn_b64`](../../core/shader/recompiler/Translation/src/MemoryInstructions/SharedMemoryInstructions.cpp) are translated as two separate 32-bit or 64-bit atomic swaps per invocation. RDNA2 hardware (measured on gfx1035) performs every lane's first exchange before any lane's second one, so when lanes of the same wave or of different waves overlap addresses, the result can differ from hardware. `offset0 == offset1` throws, although on hardware it is two exchanges in order.
|
||||
- [`ds_condxchg32_rtn_b64`](../../core/shader/recompiler/Translation/src/MemoryInstructions/SharedMemoryInstructions.cpp) uses two 32-bit atomic conditional exchanges. RDNA2 (gfx1035) measurements confirmed aligned return values, conditional bit clearing and 16-bit offsets; out-of-range accesses return zero without modifying LDS. Misaligned accesses fault the wave on gfx1035; the recompiler does not model this fault. Radeon 780M (gfx1103, RDNA3) measurements confirmed aligned return values, conditional bit clearing, EXEC masking and operand overlap in wave32 and wave64; contended cases passed per-dword conservation checks. Contended lane ordering and gfx1013 remain unmeasured; pair-wide indivisibility is not assumed, and concurrent 64-bit LDS atomics use a separate lock. GDS throws. VGPR pairs extending beyond v255 throw; allocated-VGPR EXEC nullification is not emulated.
|
||||
- [64-bit image atomics](../../core/shader/recompiler/Translation/src/MemoryInstructions/ImageInstructions.cpp) (`image_atomic_*` with a two-dword data mask) need `VK_EXT_shader_image_atomic_int64` and act on 32_32 UINT, SINT and FLOAT surfaces through an R64_UINT storage view. 64-bit `image_atomic_inc`, `image_atomic_dec` and the float atomics throw, as does any other access in the same shader to an image it updates with 64-bit atomics.
|
||||
|
|
|
|||
Loading…
Reference in New Issue