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
8 changes: 8 additions & 0 deletions core/libs/prx/libSceAgcDriver/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1782,6 +1782,14 @@ foreach(agcExecutionTestName IN LISTS ANYPS5_AGC_EXECUTION_TESTS)
message(FATAL_ERROR "ANYPS5_AGC_EXECUTION_TESTS names ${agcExecutionTestName}, which has no source in tests/execution")
endif()
endforeach()
if(BUILD_TESTING)
add_test(NAME agc_driver_indirect_thread_dimensions_cpu COMMAND agc_driver_indirect_thread_dimensions_tests)
set_tests_properties(agc_driver_indirect_thread_dimensions_cpu PROPERTIES ENVIRONMENT "APS5_NO_GPU_THREAD_DIMENSIONS=1" SKIP_RETURN_CODE 77 TIMEOUT 60)
if(WIN32)
set_tests_properties(agc_driver_indirect_thread_dimensions_cpu PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
if(ANYPS5_ENABLE_SPIRV_TOOLS)
target_compile_definitions(agc_driver_stencil_null_feedback_tests PRIVATE ANYPS5_ENABLE_SPIRV_TOOLS=1)
target_link_libraries(agc_driver_stencil_null_feedback_tests PRIVATE SPIRV-Tools-static)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp"
#include "prx/libc/include/GuestAllocations.hpp"
#include <array>
#include <chrono>
#include <functional>
#include <memory>
Expand Down Expand Up @@ -272,7 +273,7 @@ class VulkanDevice {
int cpuReason;
double argumentReadMs;
};
IndirectOutcome DispatchIndirect(const ShaderRecompiler::RecompileResult& shader, std::uint64_t arguments, std::span<const Graphics::GuestMemorySnapshot> snapshots = {}, std::uint64_t programAddress = 0, std::shared_ptr<PreparedDispatch> prepared = nullptr, std::shared_ptr<const Recipe>* recipe = nullptr);
IndirectOutcome DispatchIndirect(const ShaderRecompiler::RecompileResult& shader, std::uint64_t arguments, std::span<const Graphics::GuestMemorySnapshot> snapshots = {}, std::uint64_t programAddress = 0, std::shared_ptr<PreparedDispatch> prepared = nullptr, std::shared_ptr<const Recipe>* recipe = nullptr, const std::array<std::uint32_t, 3>& threadGroup = {});
// A value-equal dispatch-cache hit with a recipe (design_cpu_final M4), in two steps.
// PrepareRecipe, WITHOUT GuestMemory::GpuMutex: the pre-checks (recipes on, the recipe's
// device is this one, its template and pipeline objects still alive), the template's
Expand All @@ -294,7 +295,7 @@ class VulkanDevice {
// with the recipe's, else the process aborts. `refreshByWords`: the driver's data-only hit
// (`shader` carries live flat-SRT words the recipe's hash does not name), so the template's
// data refresh is decided by the per-word compare instead of the hash.
RecipeOutcome DispatchRecipe(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::uint64_t arguments, std::uint64_t programAddress, const std::shared_ptr<RecipeHit>& hit, IndirectOutcome& outcome, const std::shared_ptr<PreparedDispatch>& verify = nullptr, bool refreshByWords = false);
RecipeOutcome DispatchRecipe(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::uint64_t arguments, std::uint64_t programAddress, const std::shared_ptr<RecipeHit>& hit, IndirectOutcome& outcome, const std::shared_ptr<PreparedDispatch>& verify = nullptr, bool refreshByWords = false, const std::array<std::uint32_t, 3>& threadGroup = {});
// Whether dispatch-cache hits use recipes (APS5_NO_DISPATCH_RECIPE unset) and whether every
// hit is verified (APS5_VERIFY_RECIPE=1).
static bool DispatchRecipes();
Expand Down Expand Up @@ -347,7 +348,7 @@ class VulkanDevice {
ShaderRecompiler::SpirvTarget buildTarget() const;
Graphics::Context buildContext() const;
// Body of Dispatch and DispatchIndirect: `arguments` 0 dispatches x, y, z groups.
IndirectOutcome dispatch(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::uint64_t arguments, std::span<const Graphics::GuestMemorySnapshot> snapshots, std::uint64_t programAddress, std::shared_ptr<PreparedDispatch> prepared, std::shared_ptr<const Recipe>* recipe);
IndirectOutcome dispatch(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::uint64_t arguments, std::span<const Graphics::GuestMemorySnapshot> snapshots, std::uint64_t programAddress, std::shared_ptr<PreparedDispatch> prepared, std::shared_ptr<const Recipe>* recipe, const std::array<std::uint32_t, 3>& threadGroup = {});
// The stage-A pre-sync over `surfaces` (see PrepareDispatch): the serial waited for, 0 none.
std::uint64_t presync(std::span<const Graphics::PresyncSurface> surfaces);
std::shared_ptr<ComputePipelineObjects> computePipeline(const Graphics::Context& context, const ShaderRecompiler::RecompileResult& shader, VkDescriptorSetLayout setLayout, std::span<const std::uint32_t> layoutKey, VkShaderStageFlags pushStages, std::uint64_t programAddress);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,16 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
}
}
if (partialLastGroup) compute.partialThreads = partialGroups;
std::array<std::uint32_t, 3> threadGroup{};
if (indirectArguments != 0 && (packet[4] & 0x20u) != 0) {
for (std::uint32_t axis = 0; axis < 3; ++axis) threadGroup[axis] = std::max(readRegister(queue.shader, 0x207 + axis) & 0xffffu, 1u);
compute.partialThreads = {~0u, ~0u, ~0u};
}
ShaderRecompiler::RecompileRequest request{
{ShaderRecompiler::ShaderStage::Compute, address, std::span(snapshot.code).subspan(codeOffset), snapshot.headerAddress, snapshot.header},
{(packet[4] & 0x8000u) != 0 ? 32u : 64u, 0, userData, compute, std::nullopt, std::nullopt, memory, DispatchFloatMode(snapshot, queue.shader)},
localDevice->ComputeTarget((packet[4] & 0x8000u) != 0 ? 32u : 64u),
{0, 0, 0, 128}
{0, 0, 0, threadGroup != std::array<std::uint32_t, 3>{} ? 0u : 128u}
};
static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr;
static double captureMs = 0, keyMs = 0, recompileMs = 0, deviceMs = 0;
Expand Down Expand Up @@ -560,7 +565,7 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
try {
if (recipeHit != nullptr) {
VulkanDevice::IndirectOutcome outcome{0, 0};
const auto result = localDevice->DispatchRecipe(compiled, groups[0], groups[1], groups[2], indirectArguments, address, recipeHit, outcome, VulkanDevice::VerifyRecipes() ? prepared : nullptr, dataHit);
const auto result = localDevice->DispatchRecipe(compiled, groups[0], groups[1], groups[2], indirectArguments, address, recipeHit, outcome, VulkanDevice::VerifyRecipes() ? prepared : nullptr, dataHit, threadGroup);
if (result == RecipeOutcome::Rebuild) {

recipeHit = nullptr;
Expand All @@ -569,7 +574,7 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
}
if (indirectArguments != 0) countIndirect(outcome.cpuReason, outcome.argumentReadMs);
} else if (indirectArguments != 0) {
const auto outcome = localDevice->DispatchIndirect(compiled, indirectArguments, snapshots, address, std::move(prepared), attachTo != nullptr ? &builtRecipe : nullptr);
const auto outcome = localDevice->DispatchIndirect(compiled, indirectArguments, snapshots, address, std::move(prepared), attachTo != nullptr ? &builtRecipe : nullptr, threadGroup);
countIndirect(outcome.cpuReason, outcome.argumentReadMs);
} else {
localDevice->Dispatch(compiled, groups[0], groups[1], groups[2], snapshots, address, std::move(prepared), attachTo != nullptr ? &builtRecipe : nullptr);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ void Driver::countIndirect(int path, double readMs) {
int Driver::indirectArgumentsPath(std::span<const std::uint32_t> packet, std::uint64_t arguments) {
static const bool gpuIndirect = std::getenv("APS5_NO_GPU_INDIRECT") == nullptr;
if (!gpuIndirect) return IndirectDisabled;
if ((packet.back() & 0x20u) != 0) return IndirectThreadDimensions;
static const bool gpuThreadDimensions = std::getenv("APS5_NO_GPU_THREAD_DIMENSIONS") == nullptr;
if ((packet.back() & 0x20u) != 0 && !gpuThreadDimensions) return IndirectThreadDimensions;
if (arguments % 4 != 0) return IndirectMisaligned;
return IndirectGpu;
}
Expand Down
48 changes: 42 additions & 6 deletions core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4276,6 +4276,8 @@ struct RecordedDispatch {
std::shared_ptr<Graphics::ShaderResources::DrawBindings> bindings{};
std::uint64_t cpuArguments = 0;
int cpuReason = 0;
std::array<std::uint32_t, 3> threadGroup{};
std::array<std::uint32_t, 3> threadLimit{};
};

VkDevice VulkanDevice::Device() const {
Expand Down Expand Up @@ -4532,15 +4534,16 @@ void VulkanDevice::Dispatch(const ShaderRecompiler::RecompileResult& shader, std
static_cast<void>(dispatch(shader, x, y, z, 0, snapshots, programAddress, std::move(prepared), recipe));
}

VulkanDevice::IndirectOutcome VulkanDevice::DispatchIndirect(const ShaderRecompiler::RecompileResult& shader, std::uint64_t arguments, std::span<const Graphics::GuestMemorySnapshot> snapshots, std::uint64_t programAddress, std::shared_ptr<PreparedDispatch> prepared, std::shared_ptr<const Recipe>* recipe) {
return dispatch(shader, 0, 0, 0, arguments, snapshots, programAddress, std::move(prepared), recipe);
VulkanDevice::IndirectOutcome VulkanDevice::DispatchIndirect(const ShaderRecompiler::RecompileResult& shader, std::uint64_t arguments, std::span<const Graphics::GuestMemorySnapshot> snapshots, std::uint64_t programAddress, std::shared_ptr<PreparedDispatch> prepared, std::shared_ptr<const Recipe>* recipe, const std::array<std::uint32_t, 3>& threadGroup) {
return dispatch(shader, 0, 0, 0, arguments, snapshots, programAddress, std::move(prepared), recipe, threadGroup);
}

void VulkanDevice::decideIndirect(RecordedDispatch& record, IndirectOutcome& outcome, char* groupsText) {
auto& recorder = *state->recorder;
auto& timer = *record.timer;
const auto& context = *record.context;
const auto arguments = record.arguments;
const bool threadDimensions = record.threadGroup != std::array<std::uint32_t, 3>{};
// Decided here, after the resource build (which may retire imports and wait for recorded
// work) and before the dispatch itself is recorded, so the import cannot be dropped before
// the record. The build already recorded the copy-ins of its misaligned and staged regions
Expand All @@ -4567,7 +4570,8 @@ void VulkanDevice::decideIndirect(RecordedDispatch& record, IndirectOutcome& out
outcome.cpuReason = 8;
} else if (labelPending() || state->CopiedWriterOverlaps(arguments, 12)) {
outcome.cpuReason = 2;
} else if ((record.argumentImport = Graphics::HostImportFor(context, arguments, 12)) == nullptr) {
} else if ((record.argumentImport = Graphics::HostImportFor(context, arguments, 12)) == nullptr || (threadDimensions && record.argumentImport->address == 0)) {
record.argumentImport = nullptr;
// Valid until the next refreshImports, which only runs under GuestMemory::GpuMutex (held
// here, by every Dispatch caller) and Keeps a retired VkBuffer while the recorder is busy.
outcome.cpuReason = 3;
Expand All @@ -4584,6 +4588,10 @@ void VulkanDevice::decideIndirect(RecordedDispatch& record, IndirectOutcome& out
Graphics::FinishLeaseWork();
std::array<std::uint32_t, 3> groups{};
GuestMemory::Read(arguments, std::as_writable_bytes(std::span(groups)), 4);
if (threadDimensions) {
record.threadLimit = groups;
for (std::size_t axis = 0; axis < groups.size(); ++axis) groups[axis] = groups[axis] / record.threadGroup[axis] + (groups[axis] % record.threadGroup[axis] != 0 ? 1u : 0u);
}
outcome.argumentReadMs = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - readStart).count();
record.x = groups[0];
record.y = groups[1];
Expand Down Expand Up @@ -4660,6 +4668,24 @@ void VulkanDevice::recordDispatch(RecordedDispatch& record) {
}
recordStep(PhaseRecordDataRefresh);
}
std::shared_ptr<Graphics::Buffer> threadArguments;
if (record.threadGroup != std::array<std::uint32_t, 3>{}) {
const auto data = resources.shaderDataBuffer(*record.shader);
const auto limitDword = record.shader->program->imageMetadataDword;
Graphics::Require(data != VK_NULL_HANDLE && limitDword >= 3, "Vulkan dispatch: a thread-dimension dispatch has no ShaderData thread limit");
const VkDeviceSize limitByte = static_cast<VkDeviceSize>(limitDword - 3) * sizeof(std::uint32_t);
Graphics::RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT);
if (argumentImport != nullptr) {
threadArguments = std::make_shared<Graphics::Buffer>(context, 24, VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
Graphics::Require(recorder.RecordDispatchArguments(commands, argumentImport->address + (arguments - argumentImport->base), threadArguments->DeviceAddress(), &record.threadGroup), "Vulkan dispatch: thread-dimension indirect arguments need the argument kernel");
const VkBufferCopy limit{12, limitByte, 12};
context.Function<PFN_vkCmdCopyBuffer>("vkCmdCopyBuffer")(commands, threadArguments->Handle(), data, 1, &limit);
} else {
context.Resolved(&Graphics::DeviceFunctions::cmdUpdateBuffer, "vkCmdUpdateBuffer")(commands, data, limitByte, sizeof(record.threadLimit), record.threadLimit.data());
}
resources.forgetShaderDataWords(*record.shader);
covered = 0;
}
using CommandClass = Graphics::Recorder::CommandClass;
if (Graphics::Recorder::BarrierValidate()) {
if (argumentImport != nullptr) {
Expand Down Expand Up @@ -4693,7 +4719,11 @@ void VulkanDevice::recordDispatch(RecordedDispatch& record) {
}
VkBuffer indirectBuffer = argumentImport != nullptr ? argumentImport->buffer : VK_NULL_HANDLE;
VkDeviceSize indirectOffset = argumentImport != nullptr ? Graphics::HostImportOffset(*argumentImport, arguments) : 0;
if (argumentImport != nullptr && argumentImport->address != 0) {
if (threadArguments != nullptr) {
indirectBuffer = threadArguments->Handle();
indirectOffset = 0;
recorder.Keep(std::move(threadArguments));
} else if (argumentImport != nullptr && argumentImport->address != 0) {
auto checked = std::make_shared<Graphics::Buffer>(context, 12, VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
if (recorder.RecordDispatchArguments(commands, argumentImport->address + (arguments - argumentImport->base), checked->DeviceAddress())) {
indirectBuffer = checked->Handle();
Expand Down Expand Up @@ -4911,7 +4941,7 @@ std::shared_ptr<ComputePipelineObjects> VulkanDevice::computePipeline(const Grap
return finish(std::move(objects), nullptr);
}

VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::uint64_t arguments, std::span<const Graphics::GuestMemorySnapshot> snapshots, std::uint64_t programAddress, std::shared_ptr<PreparedDispatch> prepared, std::shared_ptr<const Recipe>* recipeOut) {
VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::uint64_t arguments, std::span<const Graphics::GuestMemorySnapshot> snapshots, std::uint64_t programAddress, std::shared_ptr<PreparedDispatch> prepared, std::shared_ptr<const Recipe>* recipeOut, const std::array<std::uint32_t, 3>& threadGroup) {
startJournalReplay();
state->recorder->BoundKeptBytes();
ShaderRecompiler::RuntimeAbi::RequireVersion(shader.runtimeAbiVersion);
Expand Down Expand Up @@ -5049,6 +5079,10 @@ VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::Rec
Graphics::ShaderResources::MoveProof proof;
auto moved = cached->MovedDispatchBuffers(shaders[0], state->recorder.get(), DispatchMoveBytes(), proof);
moves.checked.fetch_add(1, std::memory_order_relaxed);
if (moved.has_value() && threadGroup != std::array<std::uint32_t, 3>{}) {
const auto data = cached->shaderDataAllocation(shaders[0]);
if (data.has_value() && std::ranges::any_of(*moved, [&](const Graphics::ShaderResources::MovedBuffer& entry) { return entry.allocation == *data; })) moved.reset();
}
if (moved.has_value() && !moved->empty()) {
overlay = cached->PrepareDrawBindings(*state->recorder, *moved, true);
if (overlay != nullptr && overlay->allocation.set == VK_NULL_HANDLE) overlay.reset();
Expand Down Expand Up @@ -5151,6 +5185,7 @@ VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::Rec
RecordedDispatch record{&context, &shaders[0], resources, objects, pushStages, &pushBytes, x, y, z, arguments, nullptr, programAddress, fromCache && TemplateDataRefresh() ? RecordedDispatch::DataRefresh::Words : RecordedDispatch::DataRefresh::None, 0, revalidateMs, &timer};
record.resourceKeyMatched = fromCache;
record.bindings = overlay;
record.threadGroup = threadGroup;
if (arguments != 0) decideIndirect(record, outcome, groupsText);
// The indirect hold total below still covers the whole call of a CPU-resolved one.
const bool indirect = record.arguments != 0 || outcome.cpuReason != 0;
Expand Down Expand Up @@ -5225,7 +5260,7 @@ VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::Rec
return outcome;
}

RecipeOutcome VulkanDevice::DispatchRecipe(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::uint64_t arguments, std::uint64_t programAddress, const std::shared_ptr<RecipeHit>& hit, IndirectOutcome& outcome, const std::shared_ptr<PreparedDispatch>& verify, bool refreshByWords) {
RecipeOutcome VulkanDevice::DispatchRecipe(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::uint64_t arguments, std::uint64_t programAddress, const std::shared_ptr<RecipeHit>& hit, IndirectOutcome& outcome, const std::shared_ptr<PreparedDispatch>& verify, bool refreshByWords, const std::array<std::uint32_t, 3>& threadGroup) {
state->recorder->BoundKeptBytes();
ShaderRecompiler::RuntimeAbi::RequireVersion(shader.runtimeAbiVersion);
PerformanceTimer timing("Vulkan.DispatchRecipe");
Expand Down Expand Up @@ -5321,6 +5356,7 @@ RecipeOutcome VulkanDevice::DispatchRecipe(const ShaderRecompiler::RecompileResu
if (dataRefresh == RecordedDispatch::DataRefresh::Words) counters.refreshByWords.fetch_add(1, std::memory_order_relaxed);
RecordedDispatch record{&context, &shaders[0], hit->resources, hit->objects, recipe.pushes ? VkShaderStageFlags{VK_SHADER_STAGE_COMPUTE_BIT} : VkShaderStageFlags{0}, &recipe.pushBytes, x, y, z, arguments, nullptr, programAddress, dataRefresh, recipe.dataWordsHash, 0, &timer};
record.resourceKeyMatched = verify != nullptr;
record.threadGroup = threadGroup;
if (arguments != 0) decideIndirect(record, outcome, groupsText);
const bool indirect = record.arguments != 0 || outcome.cpuReason != 0;
const auto recordStart = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
Expand Down
Loading
Loading