Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class VulkanDevice {
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; }
Expand Down Expand Up @@ -329,6 +330,7 @@ class VulkanDevice {
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
24 changes: 23 additions & 1 deletion core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -1747,17 +1750,23 @@ 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();
subgroupSizeFeatures.pNext = byteFeatures.pNext;
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) {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -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;
Expand All @@ -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);
Expand Down
10 changes: 9 additions & 1 deletion core/libs/prx/libSceAgcDriver/tests/ComputePipelineCapture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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}}; });
Expand Down
4 changes: 3 additions & 1 deletion core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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}}};
Expand Down Expand Up @@ -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; });
Expand Down
10 changes: 8 additions & 2 deletions core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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");
Expand All @@ -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);
Expand Down Expand Up @@ -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));
Expand Down
Loading
Loading