mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-27 00:28:14 +00:00
feat(agc): carry spanned waves in the target and require full subgroups
APS5_SPANNED_WAVE is now read once by the driver, which sets SpirvTarget::spannedWaves only on a device with 32-lane compute subgroup control and computeFullSubgroups. The recompiler no longer reads the environment. The field is part of the cache key and of the request serialization (version 32), so a cached or replayed module keeps the mode it was built in. A spanned module numbers its lanes SubgroupId * 32 + SubgroupLocalInvocationId, which needs two full 32-lane subgroups. The artifact records this as fullSubgroups (disk cache and pipeline identity included), and the driver creates those pipelines with REQUIRE_FULL_SUBGROUPS. Compute pipeline captures accept the flag with a required subgroup size. The two-lane fallback now catches only SpannedWaveUnsupported, thrown for a shuffle wider than 32 bits; other emit errors surface as before. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
adf516f6f1
commit
7a6cc71786
15 files changed
+94
-31
No files matched your search
@@ -58,6 +58,7 @@ public:
|
||||
const std::uint32_t* WorkGroupCountLimit() const;
|
||||
std::uint32_t VendorId() const;
|
||||
ShaderRecompiler::SpirvTarget ComputeTarget(std::uint32_t waveSize) const;
|
||||
bool ComputeFullSubgroups() const;
|
||||
// Distinguishes this device from every earlier one in the process (a memo keyed by the device
|
||||
// cannot rely on the pointer, which a replacement may reuse).
|
||||
std::uint64_t Serial() const { return serial; }
|
||||
@@ -329,6 +330,7 @@ private:
|
||||
Graphics::Context graphicsContext() const;
|
||||
VkPipelineCreateFlags computePipelineFlags() const;
|
||||
std::uint32_t computeSubgroupSize(const ShaderRecompiler::RecompileResult& shader) const;
|
||||
VkPipelineShaderStageCreateFlags computeStageFlags(const ShaderRecompiler::RecompileResult& shader) const;
|
||||
std::shared_ptr<ComputePipelineObjects> createComputePipeline(const Graphics::Context& context, const ShaderRecompiler::RecompileResult& shader, VkDescriptorSetLayout setLayout, std::span<const std::uint32_t> layoutKey, VkShaderStageFlags pushStages, std::uint64_t programAddress);
|
||||
ShaderRecompiler::SpirvTarget buildTarget() const;
|
||||
Graphics::Context buildContext() const;
|
||||
|
||||
@@ -94,7 +94,7 @@ void ComputePipelineCapture::Validate() const {
|
||||
RequireCapture(mainEntry, "compute main entry point missing");
|
||||
RequireCapture(deviceName.size() < VK_MAX_PHYSICAL_DEVICE_NAME_SIZE && deviceName.find('\0') == std::string::npos, "invalid device name");
|
||||
RequireCapture((flags & ~(VK_PIPELINE_CREATE_DISPATCH_BASE_BIT | VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR)) == 0, "unsupported pipeline flags");
|
||||
RequireCapture(stageFlags == 0, "unsupported shader stage flags");
|
||||
RequireCapture(stageFlags == 0 || (stageFlags == VK_PIPELINE_SHADER_STAGE_CREATE_REQUIRE_FULL_SUBGROUPS_BIT && requiredSubgroupSize != 0), "unsupported shader stage flags");
|
||||
RequireCapture(requiredSubgroupSize == 0 || (requiredSubgroupSize <= 128 && (requiredSubgroupSize & (requiredSubgroupSize - 1u)) == 0), "invalid required subgroup size");
|
||||
RequireCapture(sets.size() <= 8, "too many descriptor sets");
|
||||
std::size_t totalBindings = 0;
|
||||
|
||||
@@ -353,6 +353,8 @@ struct VulkanDevice::State {
|
||||
bool computeWave32 = false;
|
||||
bool meshWave32 = false;
|
||||
std::uint32_t maxComputeSubgroupSize = 0;
|
||||
bool computeFullSubgroups = false;
|
||||
bool spannedWaves = false;
|
||||
// Indirect draw features enabled (see Graphics::Context).
|
||||
bool drawIndirectFirstInstance = false;
|
||||
bool multiDrawIndirect = false;
|
||||
@@ -821,6 +823,7 @@ ComputePipelineReplayResult VulkanDevice::ReplayComputePipeline(const ComputePip
|
||||
VkPhysicalDeviceSubgroupSizeControlPropertiesEXT subgroup{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_SIZE_CONTROL_PROPERTIES_EXT};
|
||||
VkPhysicalDeviceProperties2 properties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &subgroup};
|
||||
state->InstanceFunction<PFN_vkGetPhysicalDeviceProperties2>("vkGetPhysicalDeviceProperties2")(state->physical, &properties);
|
||||
require(capture.stageFlags == 0 || state->computeFullSubgroups, "captured full compute subgroups are unsupported");
|
||||
require(state->computeWave32 && capture.requiredSubgroupSize >= subgroup.minSubgroupSize && capture.requiredSubgroupSize <= subgroup.maxSubgroupSize && (subgroup.requiredSubgroupSizeStages & VK_SHADER_STAGE_COMPUTE_BIT) != 0, "captured required subgroup size is unsupported");
|
||||
}
|
||||
const auto target = Target();
|
||||
@@ -1747,10 +1750,12 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
|
||||
state->meshWave32 = state->meshShader && subgroupSizeFeatures.subgroupSizeControl == VK_TRUE && subgroupSize.minSubgroupSize <= 32u && subgroupSize.maxSubgroupSize >= 32u &&
|
||||
(subgroupSize.requiredSubgroupSizeStages & VK_SHADER_STAGE_MESH_BIT_EXT) != 0;
|
||||
state->maxComputeSubgroupSize = subgroupSize.maxSubgroupSize;
|
||||
state->computeFullSubgroups = state->computeWave32 && subgroupSizeFeatures.computeFullSubgroups == VK_TRUE;
|
||||
}
|
||||
if (state->computeWave32 || state->meshWave32) {
|
||||
subgroupSizeFeatures = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_SIZE_CONTROL_FEATURES_EXT};
|
||||
subgroupSizeFeatures.subgroupSizeControl = VK_TRUE;
|
||||
subgroupSizeFeatures.computeFullSubgroups = state->computeFullSubgroups ? VK_TRUE : VK_FALSE;
|
||||
deviceExtensions.push_back(VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME);
|
||||
deviceInfo.enabledExtensionCount = static_cast<std::uint32_t>(deviceExtensions.size());
|
||||
deviceInfo.ppEnabledExtensionNames = deviceExtensions.data();
|
||||
@@ -1758,6 +1763,10 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
|
||||
byteFeatures.pNext = &subgroupSizeFeatures;
|
||||
APS5_LOG_OUT("Wave32 programs run on subgroups of %u: compute %d, mesh %d", 32u, state->computeWave32, state->meshWave32);
|
||||
}
|
||||
if (std::getenv("APS5_SPANNED_WAVE") != nullptr) {
|
||||
state->spannedWaves = state->computeFullSubgroups;
|
||||
APS5_LOG_OUT("APS5_SPANNED_WAVE: single-wave wave64 compute spans two full 32-lane subgroups: %d", state->spannedWaves);
|
||||
}
|
||||
timelineFeatures = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES_KHR};
|
||||
timelineFeatures.timelineSemaphore = VK_TRUE;
|
||||
if (state->timelineSemaphores) {
|
||||
@@ -1988,6 +1997,16 @@ std::uint32_t VulkanDevice::computeSubgroupSize(const ShaderRecompiler::Recompil
|
||||
return state->computeWave32 && (shader.hostSubgroupSize == 32u || shader.hostSubgroupSize > state->maxComputeSubgroupSize) ? std::min(shader.hostSubgroupSize, state->maxComputeSubgroupSize) : 0u;
|
||||
}
|
||||
|
||||
VkPipelineShaderStageCreateFlags VulkanDevice::computeStageFlags(const ShaderRecompiler::RecompileResult& shader) const {
|
||||
if (!shader.fullSubgroups) return 0;
|
||||
require(state->computeFullSubgroups && computeSubgroupSize(shader) == 32u, "a spanned wave64 program needs full 32-lane compute subgroups");
|
||||
return VK_PIPELINE_SHADER_STAGE_CREATE_REQUIRE_FULL_SUBGROUPS_BIT;
|
||||
}
|
||||
|
||||
bool VulkanDevice::ComputeFullSubgroups() const {
|
||||
return state->computeFullSubgroups;
|
||||
}
|
||||
|
||||
void VulkanDevice::WaitForPipelineJournalReplay() {
|
||||
startJournalReplay();
|
||||
if (state->journalReplay != nullptr) state->journalReplay->Wait();
|
||||
@@ -3456,6 +3475,7 @@ ShaderRecompiler::SpirvTarget VulkanDevice::buildTarget() const {
|
||||
target.narrowSubgroupClock = state->narrowSubgroupClock;
|
||||
target.float64DenormPreserve = state->float64DenormPreserve;
|
||||
target.float16DenormPreserve = state->float16DenormPreserve;
|
||||
target.spannedWaves = state->spannedWaves;
|
||||
target.fixedPushSlots = state->graphicsPipelineLibrary;
|
||||
target.registrationModules = state->graphicsPipelineLibrary;
|
||||
target.srgbDecodeFormats = state->srgbDecodeFormats;
|
||||
@@ -4538,6 +4558,7 @@ std::shared_ptr<ComputePipelineObjects> VulkanDevice::createComputePipeline(cons
|
||||
const VkPipelineCreateFlags pipelineFlags = computePipelineFlags();
|
||||
const Graphics::PipelineSpecialization specialization(shader);
|
||||
const auto subgroupSize = computeSubgroupSize(shader);
|
||||
const auto stageFlags = computeStageFlags(shader);
|
||||
if (const char* directory = std::getenv("APS5_DUMP_COMPUTE_PIPELINES"); directory != nullptr && *directory != '\0' && CaptureComputePipeline(shader.spirv.size())) {
|
||||
ComputePipelineCapture capture;
|
||||
capture.spirv = shader.spirv.Copy();
|
||||
@@ -4553,6 +4574,7 @@ std::shared_ptr<ComputePipelineObjects> VulkanDevice::createComputePipeline(cons
|
||||
if (pushStages != 0) capture.pushConstants.push_back(push);
|
||||
for (const auto& value : shader.specialization) capture.specialization.push_back({value.id, value.value});
|
||||
capture.requiredSubgroupSize = subgroupSize;
|
||||
capture.stageFlags = stageFlags;
|
||||
capture.flags = pipelineFlags;
|
||||
capture.deviceName = state->properties.deviceName;
|
||||
capture.vendorId = state->properties.vendorID;
|
||||
@@ -4564,7 +4586,7 @@ std::shared_ptr<ComputePipelineObjects> VulkanDevice::createComputePipeline(cons
|
||||
std::fprintf(stderr, "[compute-capture] %s\n", path.string().c_str());
|
||||
}
|
||||
if (profile && shader.spirv.size() > 100000) std::fprintf(stderr, "[dispatch] creating a pipeline for %zu SPIR-V words (program 0x%llx)\n", shader.spirv.size(), static_cast<unsigned long long>(programAddress));
|
||||
CreateComputePipeline(state->device, state->DeviceFunction<PFN_vkCreateComputePipelines>("vkCreateComputePipelines"), context.pipelineCache, objects->module, objects->layout, pipelineFlags, 0, specialization.Info(), subgroupSize, &objects->pipeline, context.pipelineCacheSynchronization);
|
||||
CreateComputePipeline(state->device, state->DeviceFunction<PFN_vkCreateComputePipelines>("vkCreateComputePipelines"), context.pipelineCache, objects->module, objects->layout, pipelineFlags, stageFlags, specialization.Info(), subgroupSize, &objects->pipeline, context.pipelineCacheSynchronization);
|
||||
objects->destroyModule(objects->device, objects->module, nullptr);
|
||||
objects->module = VK_NULL_HANDLE;
|
||||
computePipelinesCreated.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
@@ -62,6 +62,10 @@ void CheckRoundTrip() {
|
||||
changed = decoded;
|
||||
changed.requiredSubgroupSize = 64;
|
||||
Require(changed.Identity() != capture.Identity(), "subgroup size did not affect capture identity");
|
||||
changed = decoded;
|
||||
changed.stageFlags = VK_PIPELINE_SHADER_STAGE_CREATE_REQUIRE_FULL_SUBGROUPS_BIT;
|
||||
Require(AgcDriver::ComputePipelineCapture::Decode(changed.Encode()).stageFlags == changed.stageFlags, "full subgroups were lost in the capture");
|
||||
Require(changed.Identity() != capture.Identity(), "full subgroups did not affect capture identity");
|
||||
Directory directory;
|
||||
const auto file = capture.Write(directory.path);
|
||||
Require(AgcDriver::ComputePipelineCapture::Read(file).Encode() == encoded, "capture file changed pipeline inputs");
|
||||
@@ -144,7 +148,11 @@ void CheckMalformedRecords() {
|
||||
invalid([](auto& copy) { copy.spirv[11] = 4; });
|
||||
invalid([](auto& copy) { copy.spirv[13] = 0; });
|
||||
invalid([](auto& copy) { copy.flags = VK_PIPELINE_CREATE_DERIVATIVE_BIT; });
|
||||
invalid([](auto& copy) { copy.stageFlags = VK_PIPELINE_SHADER_STAGE_CREATE_REQUIRE_FULL_SUBGROUPS_BIT; });
|
||||
invalid([](auto& copy) { copy.stageFlags = VK_PIPELINE_SHADER_STAGE_CREATE_ALLOW_VARYING_SUBGROUP_SIZE_BIT; });
|
||||
invalid([](auto& copy) {
|
||||
copy.stageFlags = VK_PIPELINE_SHADER_STAGE_CREATE_REQUIRE_FULL_SUBGROUPS_BIT;
|
||||
copy.requiredSubgroupSize = 0;
|
||||
});
|
||||
invalid([](auto& copy) { copy.requiredSubgroupSize = 3; });
|
||||
invalid([](auto& copy) { copy.specialization.push_back(copy.specialization[0]); });
|
||||
invalid([](auto& copy) { copy.pushConstants = {{VK_SHADER_STAGE_VERTEX_BIT, 0, 4}}; });
|
||||
|
||||
@@ -75,7 +75,7 @@ void requireSameArtifact(const CompiledShaderArtifact& left, const CompiledShade
|
||||
require(left.runtimeAbiVersion == right.runtimeAbiVersion, prefix + "runtime ABI version differs");
|
||||
require(left.memoryOffsetDword == right.memoryOffsetDword, prefix + "memory offset differs");
|
||||
require(left.shaderDataDwords == right.shaderDataDwords && left.imageMetadataDword == right.imageMetadataDword && left.runtimeImageCount == right.runtimeImageCount && left.runtimeImageResources == right.runtimeImageResources, prefix + "compact data layout differs");
|
||||
require(left.hostSubgroupSize == right.hostSubgroupSize, prefix + "host subgroup size differs");
|
||||
require(left.hostSubgroupSize == right.hostSubgroupSize && left.fullSubgroups == right.fullSubgroups, prefix + "host subgroup size differs");
|
||||
require(left.vertexInputs == right.vertexInputs, prefix + "vertex inputs differ");
|
||||
require(left.vertexInputPatches == right.vertexInputPatches, prefix + "vertex type patches differ");
|
||||
require(left.registrationModule == right.registrationModule, prefix + "registration module flags differ");
|
||||
@@ -170,6 +170,7 @@ RecompileResult sampleResult() {
|
||||
result.bdaAbiVersion = 3;
|
||||
result.memoryOffsetDword = 7;
|
||||
result.hostSubgroupSize = 32;
|
||||
result.fullSubgroups = true;
|
||||
result.workgroupMemoryDwords = 32769u;
|
||||
result.vertexInputs = {{1, 4, 2}, {5, 2, 0}};
|
||||
result.vertexInputPatches = {{1, 20, {7, 8, 9}}, {5, 40, {10, 11, 12}}};
|
||||
@@ -620,6 +621,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("spanned waves", [](SampleRequest& sample) { sample.request.target.spannedWaves = 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; });
|
||||
|
||||
@@ -3067,7 +3067,7 @@ void verifyPixelRequestSerialization() {
|
||||
minimal.context.pixel->layerExported = true;
|
||||
minimal.context.pixel->ditheredAlphaToCoverage = true;
|
||||
const auto encoded = serializer.Serialize(minimal);
|
||||
require(requestPrefix(encoded, 8u) == "NVNQQR8AAAA=", "new requests did not use serialization version 31");
|
||||
require(requestPrefix(encoded, 8u) == "NVNQQSAAAAA=", "new requests did not use serialization version 32");
|
||||
const std::array<std::uint32_t, 2> fallbacks{0x40u, 0x1ac4u};
|
||||
auto withFallbacks = minimal;
|
||||
withFallbacks.context.linkedCallFallbacks = fallbacks;
|
||||
@@ -3185,7 +3185,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=", "NVNQQSAAAAA="}) {
|
||||
for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQSEAAAA="}) {
|
||||
expectFailure([&] { static_cast<void>(serializer.Deserialize(unsupported)); }, "serialization version", "an unsupported request version was accepted");
|
||||
}
|
||||
static constexpr std::string_view version18Request{
|
||||
@@ -3276,6 +3276,7 @@ void verifyVertexInfoVersion19() {
|
||||
require(offset + 4u <= plain.size() && flagged[offset] == 0xefu && flagged[offset + 1u] == 0xcdu && flagged[offset + 2u] == 0xabu && flagged[offset + 3u] == 0x89u, "PA_CL_VS_OUT_CNTL is not the four bytes the version 20 vertex info adds");
|
||||
require(std::equal(plain.begin() + offset + 4u, plain.end(), flagged.begin() + offset + 4u), "PA_CL_VS_OUT_CNTL moved the fields after it");
|
||||
auto version20Vertex = flagged;
|
||||
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");
|
||||
@@ -3284,6 +3285,7 @@ void verifyVertexInfoVersion19() {
|
||||
const auto layered = serializer.Deserialize(serializer.Serialize(vertex));
|
||||
require(layered.request.context.vertex.has_value() && layered.request.context.vertex->paClVsOutCntl == vertex.context.vertex->paClVsOutCntl, "a layered vertex info lost its clip distances or render target index");
|
||||
auto legacy = plain;
|
||||
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);
|
||||
@@ -3331,21 +3333,25 @@ void verifyVertexInfoVersion19() {
|
||||
const auto sampledBytes = decodeBase64(serializer.Serialize(sampledPixel));
|
||||
require(std::equal(maskedBytes.begin(), maskedBytes.begin() + static_cast<std::ptrdiff_t>(maskOffset + 1u), sampledBytes.begin()) && sampledBytes[maskOffset + 1u] == 0x1u && sampledBytes[maskOffset + 5u] == 0x2u && sampledBytes[maskOffset + 9u] == 0x4u && sampledBytes[maskOffset + 13u] == 0x8u, "the quad sample masks are not the four words after the quad pixel mask");
|
||||
auto version29 = sampledBytes;
|
||||
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));
|
||||
version29[4] = 29u;
|
||||
const auto replay29 = serializer.Deserialize(encodeBase64(version29));
|
||||
require(replay29.request.context.pixel.has_value() && replay29.request.context.pixel->quadSampleMask == std::array<std::uint16_t, 4>{0x1u, 0x2u, 0x4u, 0x8u} && replay29.request.context.pixel->reversedBlend == 0u && decodeBase64(serializer.Serialize(replay29.request)) == sampledBytes, "a version 29 request read a reversed-order blend code or misread the fields after the quad sample masks");
|
||||
auto version28 = sampledBytes;
|
||||
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));
|
||||
version28[4] = 28u;
|
||||
const auto replay28 = serializer.Deserialize(encodeBase64(version28));
|
||||
require(replay28.request.context.pixel.has_value() && replay28.request.context.pixel->quadPixelMask == 0x2u && replay28.request.context.pixel->quadSampleMask == std::array<std::uint16_t, 4>{0xffffu, 0xffffu, 0xffffu, 0xffffu} && replay28.request.context.pixel->reversedBlend == 0u && decodeBase64(serializer.Serialize(replay28.request)) == maskedBytes, "a version 28 request read quad sample masks or a reversed-order blend code, or misread the fields after them");
|
||||
auto version27 = maskedBytes;
|
||||
version27.pop_back();
|
||||
version27.erase(version27.begin() + static_cast<std::ptrdiff_t>(maskOffset), version27.begin() + static_cast<std::ptrdiff_t>(maskOffset + 1u + sampleMaskBytes + 4u));
|
||||
version27[4] = 27u;
|
||||
const auto replay27 = serializer.Deserialize(encodeBase64(version27));
|
||||
require(replay27.request.context.pixel.has_value() && replay27.request.context.pixel->quadPixelMask == 0xfu && replay27.request.context.pixel->reversedBlend == 0u && serializer.Serialize(replay27.request) == encodedPixel, "a version 27 request read a quad pixel mask or reversed-order blend code, or misread the fields after it");
|
||||
auto legacyPixel = plainPixel;
|
||||
legacyPixel.pop_back();
|
||||
legacyPixel.erase(legacyPixel.begin() + static_cast<std::ptrdiff_t>(allBitsOffset), legacyPixel.begin() + static_cast<std::ptrdiff_t>(allBitsOffset + 2u + sampleMaskBytes + 4u));
|
||||
legacyPixel[4] = 26u;
|
||||
const auto replay26 = serializer.Deserialize(encodeBase64(legacyPixel));
|
||||
|
||||
@@ -261,6 +261,7 @@ private:
|
||||
append(key, value.registrationWordLimit);
|
||||
append(key, value.vertexSubgroups);
|
||||
append(key, value.float16DenormPreserve);
|
||||
append(key, value.spannedWaves);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -778,7 +778,7 @@ std::string RequestSerializer::Serialize(const RecompileRequest& request) const
|
||||
std::string buffer;
|
||||
Writer writer(buffer);
|
||||
writer.WriteU32(0x41505335u);
|
||||
writer.WriteU32(31u);
|
||||
writer.WriteU32(32u);
|
||||
writeShaderBinary(writer, request.shader);
|
||||
writeGuestContext(writer, request.context);
|
||||
writeSpirvTarget(writer, request.target);
|
||||
@@ -803,6 +803,7 @@ std::string RequestSerializer::Serialize(const RecompileRequest& request) const
|
||||
writer.WriteBool(request.target.vertexSubgroups);
|
||||
writer.WriteBool(request.target.float16DenormPreserve);
|
||||
writer.WriteU32Span(request.context.linkedCallFallbacks);
|
||||
writer.WriteBool(request.target.spannedWaves);
|
||||
return base64Encode(buffer);
|
||||
}
|
||||
|
||||
@@ -811,7 +812,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 > 31u) throw std::runtime_error("unsupported recompile request serialization version");
|
||||
if (version < 1u || version > 32u) 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);
|
||||
@@ -847,6 +848,7 @@ DeserializedRequest RequestSerializer::Deserialize(std::string_view text) const
|
||||
result.linkedCallFallbacks = reader.ReadU32Vector();
|
||||
result.request.context.linkedCallFallbacks = result.linkedCallFallbacks;
|
||||
}
|
||||
if (version >= 32u) result.request.target.spannedWaves = reader.ReadBool();
|
||||
if (version >= 21u && reader.Remaining() != 0u) throw std::runtime_error("RequestSerializer: unexpected trailing data");
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -524,16 +524,15 @@ CompiledVariant emitVariant(const RecompileRequest& request, IrProgram& program)
|
||||
targetOptions.registrationModules = registration;
|
||||
targetOptions.fragmentDescriptorSet = request.target.registrationModules;
|
||||
targetOptions.float16DenormPreserve = request.target.float16DenormPreserve;
|
||||
static const bool spannedWaves = std::getenv("APS5_SPANNED_WAVE") != nullptr;
|
||||
targetOptions.spannedWaves = spannedWaves;
|
||||
targetOptions.spannedWaves = request.target.spannedWaves;
|
||||
|
||||
constexpr SpirvEmitter spirvEmitter;
|
||||
CompiledShaderArtifact result;
|
||||
result.variantId = nextVariantId();
|
||||
SpirvEmitInfo emitted;
|
||||
try {
|
||||
result.spirv = spirvEmitter.Emit(program, inputInfo, bindings, targetOptions);
|
||||
} catch (const std::exception& error) {
|
||||
if (!targetOptions.spannedWaves) throw;
|
||||
result.spirv = spirvEmitter.Emit(program, inputInfo, bindings, targetOptions, &emitted);
|
||||
} catch (const SpannedWaveUnsupported& error) {
|
||||
std::fprintf(stderr, "[spanned-wave] 0x%llx falls back to two lanes per invocation: %s\n", static_cast<unsigned long long>(request.shader.codeAddress), error.what());
|
||||
targetOptions.spannedWaves = false;
|
||||
result.spirv = spirvEmitter.Emit(program, inputInfo, bindings, targetOptions);
|
||||
@@ -558,6 +557,7 @@ CompiledVariant emitVariant(const RecompileRequest& request, IrProgram& program)
|
||||
result.imageMetadataDword = bindings.layout.ImageMetadataDword();
|
||||
result.runtimeImageCount = bindings.layout.runtimeImageCount;
|
||||
result.hostSubgroupSize = HostSubgroupSize(request);
|
||||
result.fullSubgroups = emitted.fullSubgroups;
|
||||
result.vertexOffsetSgpr = program.Info().vertexOffsetSgpr;
|
||||
result.instanceOffsetSgpr = program.Info().instanceOffsetSgpr;
|
||||
result.vertexOffsetShared = program.Info().vertexOffsetShared;
|
||||
@@ -874,6 +874,7 @@ RecompileResult materializeResult(const CompiledVariant& variant, const Recompil
|
||||
.vertexOffsetConflict = artifact.vertexOffsetConflict,
|
||||
.instanceOffsetConflict = artifact.instanceOffsetConflict,
|
||||
.hostSubgroupSize = artifact.hostSubgroupSize,
|
||||
.fullSubgroups = artifact.fullSubgroups,
|
||||
.parameterExports = artifact.parameterExports,
|
||||
.fragmentParameters = artifact.fragmentParameters,
|
||||
.barycentricEmulation = artifact.barycentricEmulation,
|
||||
|
||||
@@ -267,6 +267,7 @@ struct SpirvTarget {
|
||||
std::uint32_t registrationWordLimit = 200000u;
|
||||
bool vertexSubgroups = true;
|
||||
bool float16DenormPreserve = false;
|
||||
bool spannedWaves = false;
|
||||
};
|
||||
|
||||
struct BindingLayout {
|
||||
@@ -603,6 +604,7 @@ struct CompiledShaderArtifact {
|
||||
bool vertexOffsetConflict = false;
|
||||
bool instanceOffsetConflict = false;
|
||||
std::uint32_t hostSubgroupSize = 0;
|
||||
bool fullSubgroups = false;
|
||||
std::vector<std::uint32_t> parameterExports;
|
||||
std::vector<FragmentParameter> fragmentParameters;
|
||||
BarycentricEmulation barycentricEmulation;
|
||||
@@ -657,7 +659,7 @@ struct RecompileResult : CompiledShaderArtifact, ShaderInvocation {
|
||||
};
|
||||
mix(specialization.size());
|
||||
for (const auto& constant : specialization) mix(constant.id | (static_cast<std::uint64_t>(constant.value) << 32u));
|
||||
mix(hostSubgroupSize | (static_cast<std::uint64_t>(workgroupMemoryDwords) << 32u));
|
||||
mix(hostSubgroupSize | (fullSubgroups ? 0x10000u : 0u) | (static_cast<std::uint64_t>(workgroupMemoryDwords) << 32u));
|
||||
mix(bindings.size());
|
||||
for (const auto& binding : bindings) mix(binding.descriptorSet | (static_cast<std::uint64_t>(binding.binding) << 16u) | (static_cast<std::uint64_t>(binding.kind) << 40u) | (static_cast<std::uint64_t>(binding.count) << 48u));
|
||||
if (identity.Empty()) identity.low = 1;
|
||||
|
||||
@@ -84,9 +84,9 @@ std::filesystem::path ShaderCacheDirectory() {
|
||||
namespace ShaderRecompiler::ShaderDiskCache {
|
||||
|
||||
#if defined(__linux__) && defined(__x86_64__) && defined(__GLIBCXX__)
|
||||
static_assert(sizeof(CompiledShaderArtifact) == 232, "CompiledShaderArtifact changed: update the artifact encoder");
|
||||
static_assert(sizeof(CompiledShaderArtifact) == 240, "CompiledShaderArtifact changed: update the artifact encoder");
|
||||
static_assert(sizeof(ShaderInvocation) == 200, "ShaderInvocation changed: update the invocation encoder");
|
||||
static_assert(sizeof(RecompileResult) == 464, "RecompileResult changed: update EncodeResult and DecodeResult");
|
||||
static_assert(sizeof(RecompileResult) == 472, "RecompileResult changed: update EncodeResult and DecodeResult");
|
||||
static_assert(sizeof(DescriptorBinding) == 592, "DescriptorBinding changed: update the binding encoder");
|
||||
static_assert(sizeof(VertexAttribute) == 32, "VertexAttribute changed: update the attribute encoder");
|
||||
static_assert(sizeof(VertexInput) == 16, "VertexInput changed: update the vertex input encoder");
|
||||
@@ -486,6 +486,7 @@ void encodeArtifact(Writer& writer, const CompiledShaderArtifact& result) {
|
||||
writer.Value(result.vertexOffsetConflict);
|
||||
writer.Value(result.instanceOffsetConflict);
|
||||
writer.Value(result.hostSubgroupSize);
|
||||
writer.Value(result.fullSubgroups);
|
||||
writer.Values(std::span<const std::uint32_t>(result.parameterExports));
|
||||
writer.List(result.fragmentParameters, [](Writer& out, const FragmentParameter& parameter) {
|
||||
out.Value(parameter.location);
|
||||
@@ -532,6 +533,7 @@ void decodeArtifact(Reader& reader, CompiledShaderArtifact& result) {
|
||||
reader.Value(result.vertexOffsetConflict);
|
||||
reader.Value(result.instanceOffsetConflict);
|
||||
reader.Value(result.hostSubgroupSize);
|
||||
reader.Value(result.fullSubgroups);
|
||||
reader.Values(result.parameterExports);
|
||||
reader.List(result.fragmentParameters, 10, [](Reader& in, FragmentParameter& parameter) {
|
||||
in.Value(parameter.location);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "Optimization/BindingAllocator.hpp"
|
||||
#include "Optimization/ShaderStageInputInfo.hpp"
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
@@ -27,10 +28,18 @@ struct SpirvTargetOptions {
|
||||
bool spannedWaves = false;
|
||||
};
|
||||
|
||||
struct SpirvEmitInfo {
|
||||
bool fullSubgroups = false;
|
||||
};
|
||||
|
||||
struct SpannedWaveUnsupported : std::runtime_error {
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
class SpirvEmitter {
|
||||
public:
|
||||
[[nodiscard]] std::vector<std::uint32_t> Emit(const IrProgram& program, const CompiledBindingLayout& bindings, const SpirvTargetOptions& target) const;
|
||||
[[nodiscard]] std::vector<std::uint32_t> Emit(const IrProgram& program, const ShaderStageInputInfo& inputInfo, const CompiledBindingLayout& bindings, const SpirvTargetOptions& target) const;
|
||||
[[nodiscard]] std::vector<std::uint32_t> Emit(const IrProgram& program, const ShaderStageInputInfo& inputInfo, const CompiledBindingLayout& bindings, const SpirvTargetOptions& target, SpirvEmitInfo* info = nullptr) const;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -240,7 +240,7 @@ std::vector<std::uint32_t> SpirvEmitter::Emit(const IrProgram& program, const Co
|
||||
return Emit(program, ShaderStageInputInfo {}, bindings, target);
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> SpirvEmitter::Emit(const IrProgram& program, const ShaderStageInputInfo& inputInfo, const CompiledBindingLayout& bindings, const SpirvTargetOptions& target) const {
|
||||
std::vector<std::uint32_t> SpirvEmitter::Emit(const IrProgram& program, const ShaderStageInputInfo& inputInfo, const CompiledBindingLayout& bindings, const SpirvTargetOptions& target, SpirvEmitInfo* info) const {
|
||||
if (program.Resources().stage != IrShaderStage::Compute && program.Resources().stage != IrShaderStage::Vertex && program.Resources().stage != IrShaderStage::Pixel && program.Resources().stage != IrShaderStage::Mesh && program.Resources().stage != IrShaderStage::Local && program.Resources().stage != IrShaderStage::TessellationControl && program.Resources().stage != IrShaderStage::TessellationEvaluation) {
|
||||
FailProgram(program, "binary SPIR-V emitter received an unsupported shader stage");
|
||||
}
|
||||
@@ -293,6 +293,7 @@ std::vector<std::uint32_t> SpirvEmitter::Emit(const IrProgram& program, const Sh
|
||||
EmitModuleHeader(state, bindings);
|
||||
EmitProgram(state);
|
||||
state.module.EmitEntryPoint(ExecutionModelForStage(state.program.Resources().stage), state.mainFunc, "main", state.interfaceVariables);
|
||||
if (info != nullptr) info->fullSubgroups = state.spannedWave;
|
||||
return state.module.Finalize();
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace ShaderRecompiler
|
||||
|
||||
void SpannedShuffle(SpirvEmitterState& state, std::uint32_t type, std::uint32_t result, std::uint32_t value, std::uint32_t lane) {
|
||||
const bool boolean = type == TypeBool(state);
|
||||
if (!boolean && type != TypeU32(state) && type != TypeF32(state)) FailEmit("a spanned wave shuffles only 32-bit and boolean values");
|
||||
if (!boolean && type != TypeU32(state) && type != TypeF32(state)) throw SpannedWaveUnsupported("a spanned wave shuffles only 32-bit and boolean values");
|
||||
std::uint32_t word = value;
|
||||
if (boolean) word = Select(state, TypeU32(state), value, ConstantU32(state, 1u), ConstantU32(state, 0u));
|
||||
else if (type != TypeU32(state)) word = Unary(state, spv::OpBitcast, TypeU32(state), value);
|
||||
|
||||
@@ -206,11 +206,11 @@ 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] = 31u;
|
||||
upgraded[4] = 32u;
|
||||
if (fixture.pixel) upgraded.insert(upgraded.begin() + 258u, {0u, 0u, 0u});
|
||||
if (fixture.pixel) InsertPixelDefaultsAfterFrontFace(upgraded);
|
||||
if (fixture.tessellation) upgraded.insert(upgraded.end() - 35u, 5u, 0u);
|
||||
upgraded.insert(upgraded.end(), sizeof(std::uint64_t), 0u);
|
||||
upgraded.insert(upgraded.end(), sizeof(std::uint64_t) + 1u, 0u);
|
||||
Require(serializer.Serialize(request) == EncodeBase64(upgraded));
|
||||
auto truncated = bytes;
|
||||
truncated.pop_back();
|
||||
@@ -245,11 +245,11 @@ int main() {
|
||||
layerRejected = std::string_view(error.what()) == "RequestSerializer: truncated data";
|
||||
}
|
||||
Require(layerRejected);
|
||||
layered[4] = 31u;
|
||||
layered[4] = 32u;
|
||||
layered.insert(layered.begin() + 259u, {0u, 0u});
|
||||
InsertPixelDefaultsAfterFrontFace(layered);
|
||||
if (fixture.tessellation) layered.insert(layered.end() - 35u, 5u, 0u);
|
||||
layered.insert(layered.end(), sizeof(std::uint64_t), 0u);
|
||||
layered.insert(layered.end(), sizeof(std::uint64_t) + 1u, 0u);
|
||||
Require(serializer.Serialize(layerReplay.request) == EncodeBase64(layered));
|
||||
}
|
||||
}
|
||||
@@ -270,10 +270,10 @@ 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] = 31u;
|
||||
bytes[4] = 32u;
|
||||
bytes.insert(bytes.begin() + 259u, {0u, 0u});
|
||||
InsertPixelDefaultsAfterFrontFace(bytes);
|
||||
bytes.insert(bytes.end(), sizeof(std::uint64_t), 0u);
|
||||
bytes.insert(bytes.end(), sizeof(std::uint64_t) + 1u, 0u);
|
||||
Require(serializer.Serialize(replay.request) == EncodeBase64(bytes));
|
||||
|
||||
auto truncated23 = DecodeBase64(encoded23);
|
||||
@@ -394,10 +394,10 @@ 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] = 31u;
|
||||
upgraded[4] = 32u;
|
||||
if (fixture.pixel) upgraded.insert(upgraded.begin() + 259u, {0u, 0u});
|
||||
if (fixture.pixel) InsertPixelDefaultsAfterFrontFace(upgraded);
|
||||
upgraded.insert(upgraded.end(), sizeof(std::uint64_t), 0u);
|
||||
upgraded.insert(upgraded.end(), sizeof(std::uint64_t) + 1u, 0u);
|
||||
Require(serializer.Serialize(request) == EncodeBase64(upgraded));
|
||||
for (const bool dither : {false, true}) {
|
||||
if (!fixture.pixel && dither) continue;
|
||||
@@ -407,12 +407,12 @@ 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] = 31u;
|
||||
main25[4] = 32u;
|
||||
if (fixture.pixel) {
|
||||
main25.insert(main25.begin() + 260u, 0u);
|
||||
InsertPixelDefaultsAfterFrontFace(main25);
|
||||
}
|
||||
main25.insert(main25.end(), sizeof(std::uint64_t), 0u);
|
||||
main25.insert(main25.end(), sizeof(std::uint64_t) + 1u, 0u);
|
||||
Require(serializer.Serialize(replay25.request) == EncodeBase64(main25));
|
||||
}
|
||||
}
|
||||
@@ -426,9 +426,9 @@ int main() {
|
||||
const auto replay = serializer.Deserialize(encoded);
|
||||
Require(replay.request.target.float16DenormPreserve == preserve);
|
||||
auto bytes = DecodeBase64(encoded);
|
||||
Require(bytes.size() >= 8u && bytes[4] == 31u && bytes[5] == 0u && bytes[6] == 0u && bytes[7] == 0u);
|
||||
Require(bytes.size() >= 8u && bytes[4] == 32u && bytes[5] == 0u && bytes[6] == 0u && bytes[7] == 0u);
|
||||
auto future = bytes;
|
||||
future[4] = 32u;
|
||||
future[4] = 33u;
|
||||
bool futureRejected = false;
|
||||
try {
|
||||
static_cast<void>(serializer.Deserialize(EncodeBase64(future)));
|
||||
@@ -446,5 +446,10 @@ int main() {
|
||||
Require(rejected);
|
||||
}
|
||||
|
||||
for (const bool spanned : {false, true}) {
|
||||
request.target.spannedWaves = spanned;
|
||||
Require(serializer.Deserialize(serializer.Serialize(request)).request.target.spannedWaves == spanned);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user