mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-06 13:30:00 +00:00
chore: sync current main for PR 9802
This commit is contained in:
commit
f3da59fe87
22 files changed
+1929
-112
No files matched your search
@@ -337,6 +337,7 @@ set(agcDriverSources
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/ShaderResources.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/ShaderAddressResources.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/GuestBufferMemory.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/ResidentMirror.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/BdaResources.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/BufferAddress.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/ShaderValidation.cpp
|
||||
@@ -2560,12 +2561,13 @@ if(BUILD_TESTING)
|
||||
add_test(NAME agc_heap_mirror_unwatch_no_space_cache COMMAND agc_driver_graphics_tests --heap-mirror-unwatch)
|
||||
add_test(NAME agc_sparse_heap_mirror_unwatch COMMAND agc_driver_graphics_tests --sparse-heap-mirror-unwatch)
|
||||
add_test(NAME agc_sparse_heap_mirror_unwatch_no_space_cache COMMAND agc_driver_graphics_tests --sparse-heap-mirror-unwatch)
|
||||
add_test(NAME agc_sparse_heap_mirror_budget COMMAND agc_driver_graphics_tests --sparse-heap-mirror-budget)
|
||||
add_test(NAME agc_heap_mirror_run_unwatch COMMAND agc_driver_graphics_tests --heap-mirror-run-unwatch)
|
||||
add_test(NAME agc_heap_mirror_run_unwatch_no_space_cache COMMAND agc_driver_graphics_tests --heap-mirror-run-unwatch)
|
||||
set_tests_properties(agc_heap_mirror_unwatch_no_space_cache agc_sparse_heap_mirror_unwatch_no_space_cache agc_heap_mirror_run_unwatch_no_space_cache PROPERTIES ENVIRONMENT "APS5_NO_ADDRESS_SPACE_CACHE=1")
|
||||
set_tests_properties(agc_heap_mirror_unwatch agc_heap_mirror_unwatch_no_space_cache agc_sparse_heap_mirror_unwatch agc_sparse_heap_mirror_unwatch_no_space_cache agc_heap_mirror_run_unwatch agc_heap_mirror_run_unwatch_no_space_cache PROPERTIES TIMEOUT 30)
|
||||
set_tests_properties(agc_heap_mirror_unwatch agc_heap_mirror_unwatch_no_space_cache agc_sparse_heap_mirror_unwatch agc_sparse_heap_mirror_unwatch_no_space_cache agc_heap_mirror_run_unwatch agc_heap_mirror_run_unwatch_no_space_cache agc_sparse_heap_mirror_budget PROPERTIES TIMEOUT 30)
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_heap_mirror_unwatch agc_heap_mirror_unwatch_no_space_cache agc_sparse_heap_mirror_unwatch agc_sparse_heap_mirror_unwatch_no_space_cache agc_heap_mirror_run_unwatch agc_heap_mirror_run_unwatch_no_space_cache PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
set_tests_properties(agc_heap_mirror_unwatch agc_heap_mirror_unwatch_no_space_cache agc_sparse_heap_mirror_unwatch agc_sparse_heap_mirror_unwatch_no_space_cache agc_heap_mirror_run_unwatch agc_heap_mirror_run_unwatch_no_space_cache agc_sparse_heap_mirror_budget PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
else()
|
||||
add_test(NAME agc_heap_mirror_unwatched_import COMMAND agc_driver_graphics_tests --heap-mirror-unwatched-import)
|
||||
set_tests_properties(agc_heap_mirror_unwatched_import PROPERTIES TIMEOUT 30)
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
@@ -35,6 +36,18 @@ struct RecordedDispatch;
|
||||
// The module, layout and pipeline a compute dispatch binds (VulkanDevice::createComputePipeline).
|
||||
struct ComputePipelineObjects;
|
||||
|
||||
struct DeviceQueuePlan {
|
||||
std::uint32_t mainCount = 1;
|
||||
bool settle = false;
|
||||
std::uint32_t settleFamily = 0;
|
||||
std::uint32_t settleIndex = 0;
|
||||
bool sparseBinding = false;
|
||||
std::uint32_t sparseFamily = VK_QUEUE_FAMILY_IGNORED;
|
||||
std::uint32_t sparseIndex = 0;
|
||||
};
|
||||
|
||||
DeviceQueuePlan PlanDeviceQueues(std::span<const VkQueueFamilyProperties> families, std::uint32_t family, bool settles, bool sparseBinding);
|
||||
|
||||
class VulkanDevice {
|
||||
public:
|
||||
explicit VulkanDevice(const PresentationWindow* window = nullptr);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Pm4.hpp"
|
||||
#include "Optimization/ResourceProgram.hpp"
|
||||
#include "prx/libc/include/HostThreadLocal.hpp"
|
||||
#include <atomic>
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
@@ -20,6 +21,7 @@
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace AgcDriver::DriverDetail {
|
||||
|
||||
@@ -191,6 +193,17 @@ DrawFinisher::Pending heldWrites(const DrawTail& tail) {
|
||||
return held;
|
||||
}
|
||||
|
||||
bool EmptyIndirectSkip() {
|
||||
static const bool enabled = std::getenv("APS5_NO_EMPTY_INDIRECT_SKIP") == nullptr;
|
||||
return enabled;
|
||||
}
|
||||
|
||||
struct CpuIndirectProgramsStorage {};
|
||||
|
||||
std::unordered_map<std::uint64_t, bool>& CpuIndirectPrograms() {
|
||||
return HostThreadLocal<std::unordered_map<std::uint64_t, bool>, CpuIndirectProgramsStorage>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DrawVerdict Driver::draw(QueueState& queue, std::span<const std::uint32_t> packet, const Submission& submission, std::string& rejected) {
|
||||
@@ -386,6 +399,25 @@ DrawVerdict Driver::draw(QueueState& queue, std::span<const std::uint32_t> packe
|
||||
programs = decode->programs;
|
||||
for (const auto& patch : userPatches) programs[patch.program].userData[patch.index] = patch.value;
|
||||
if (const auto skipped = SkippedShader(); skipped != 0 && std::any_of(programs.begin(), programs.end(), [skipped](const DrawProgram& program) { return program.binary.codeAddress == skipped; })) return drawn();
|
||||
const bool emptyIndirectSkip = EmptyIndirectSkip();
|
||||
auto& cpuIndirectPrograms = CpuIndirectPrograms();
|
||||
const auto recordDraws = [&](const Pm4::DrawArguments& arguments) { return arguments.count != 0 && arguments.instances != 0 && (!drawParameters.indexed || arguments.firstVertexOrIndex < drawParameters.indexCount); };
|
||||
if (emptyIndirectSkip && drawParameters.indirect && !programs.empty()) {
|
||||
const auto known = cpuIndirectPrograms.find(programs.front().binary.codeAddress);
|
||||
if (known != cpuIndirectPrograms.end() && known->second) {
|
||||
const auto& indirect = *drawParameters.indirect;
|
||||
recordQueuedLabelsBeforeRead(submission.queue);
|
||||
const auto readStart = std::chrono::steady_clock::now();
|
||||
const auto count = std::min(indirect.countIndirect ? Pm4::ReadDrawCount(indirect) : indirect.count, indirect.count);
|
||||
bool any = false;
|
||||
for (std::uint32_t record = 0; record < count && !any; ++record) any = recordDraws(Pm4::ReadDrawArguments(indirect, record));
|
||||
if (!any) {
|
||||
Graphics::CountEmptyIndirectDraw(std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - readStart).count());
|
||||
timing.Mark("empty_indirect_skipped");
|
||||
return drawn();
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto setMeshIndexBuffer = [&](const Pm4::DrawParameters& parameters) {
|
||||
if (!graphics.stages.mesh) return;
|
||||
auto& words = programs.front().userData;
|
||||
@@ -756,6 +788,7 @@ DrawVerdict Driver::finishDraw(DrawTail& tail, PerformanceTimer& timing) {
|
||||
const auto& result = *programResults[i];
|
||||
if (i == 0 && drawParameters.indirect) {
|
||||
indirectCpu = ClassifyIndirectDraw(result, graphics, programs.front(), localDevice, drawParameters, traceIndirect);
|
||||
if (EmptyIndirectSkip()) CpuIndirectPrograms()[programs.front().binary.codeAddress] = indirectCpu.has_value();
|
||||
} else if (i == 0) {
|
||||
fold(result, drawParameters);
|
||||
}
|
||||
|
||||
@@ -81,6 +81,44 @@
|
||||
#include <utility>
|
||||
|
||||
namespace AgcDriver {
|
||||
|
||||
DeviceQueuePlan PlanDeviceQueues(std::span<const VkQueueFamilyProperties> families, std::uint32_t family, bool settles, bool sparseBinding) {
|
||||
DeviceQueuePlan plan;
|
||||
const auto count = static_cast<std::uint32_t>(families.size());
|
||||
if (settles) {
|
||||
if (family < count && families[family].queueCount >= 2) {
|
||||
plan.mainCount = 2;
|
||||
plan.settle = true;
|
||||
plan.settleFamily = family;
|
||||
plan.settleIndex = 1;
|
||||
} else {
|
||||
const auto copies = [&](std::uint32_t index) { return index != family && families[index].queueCount != 0 && (families[index].queueFlags & (VK_QUEUE_TRANSFER_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_GRAPHICS_BIT)) != 0; };
|
||||
for (std::uint32_t i = 0; i < count && !plan.settle; ++i) {
|
||||
if (copies(i) && (families[i].queueFlags & (VK_QUEUE_COMPUTE_BIT | VK_QUEUE_GRAPHICS_BIT)) == 0) plan.settle = true, plan.settleFamily = i;
|
||||
}
|
||||
for (std::uint32_t i = 0; i < count && !plan.settle; ++i) {
|
||||
if (copies(i)) plan.settle = true, plan.settleFamily = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!sparseBinding) return plan;
|
||||
const auto taken = [&](std::uint32_t index) { return plan.settle && index == plan.settleFamily ? plan.settleIndex + 1 : 0u; };
|
||||
const auto binds = [&](std::uint32_t index) { return index != family && families[index].queueCount > taken(index) && (families[index].queueFlags & VK_QUEUE_SPARSE_BINDING_BIT) != 0; };
|
||||
for (std::uint32_t i = 0; i < count && plan.sparseFamily == VK_QUEUE_FAMILY_IGNORED; ++i) {
|
||||
if (binds(i) && families[i].queueFlags == VK_QUEUE_SPARSE_BINDING_BIT) plan.sparseFamily = i;
|
||||
}
|
||||
for (std::uint32_t i = 0; i < count && plan.sparseFamily == VK_QUEUE_FAMILY_IGNORED; ++i) {
|
||||
if (binds(i)) plan.sparseFamily = i;
|
||||
}
|
||||
if (plan.sparseFamily != VK_QUEUE_FAMILY_IGNORED) {
|
||||
plan.sparseIndex = taken(plan.sparseFamily);
|
||||
plan.sparseBinding = true;
|
||||
} else {
|
||||
plan.sparseBinding = family < count && (families[family].queueFlags & VK_QUEUE_SPARSE_BINDING_BIT) != 0;
|
||||
}
|
||||
return plan;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
void check(VkResult result, const char* operation) {
|
||||
@@ -378,8 +416,11 @@ struct VulkanDevice::State {
|
||||
bool drawInputsInPlace = true;
|
||||
bool dmaBufImport = false;
|
||||
bool sparseBinding = false;
|
||||
bool sparseResidencyBuffer = false;
|
||||
bool sparseResidencyAliased = false;
|
||||
std::uint32_t queueFamily = 0;
|
||||
std::uint32_t sparseFamily = VK_QUEUE_FAMILY_IGNORED;
|
||||
std::uint32_t sparseIndex = 0;
|
||||
VkQueue sparseQueue = VK_NULL_HANDLE;
|
||||
std::mutex sparseQueueMutex;
|
||||
VkQueue settleQueue = VK_NULL_HANDLE;
|
||||
@@ -1614,35 +1655,18 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
|
||||
queueInfo.queueCount = 1;
|
||||
queueInfo.pQueuePriorities = &priority;
|
||||
const float priorities[2]{1.0f, 1.0f};
|
||||
bool settleQueue = false;
|
||||
std::uint32_t settleFamily = family;
|
||||
std::uint32_t settleIndex = 0;
|
||||
std::vector<VkQueueFamilyProperties> queues;
|
||||
#ifdef _WIN32
|
||||
const bool settles = true;
|
||||
#else
|
||||
const bool settles = state->dmaBufImport;
|
||||
#endif
|
||||
if (settles) {
|
||||
std::vector<VkQueueFamilyProperties> queues;
|
||||
{
|
||||
std::uint32_t families = 0;
|
||||
auto getFamilies = state->InstanceFunction<PFN_vkGetPhysicalDeviceQueueFamilyProperties>("vkGetPhysicalDeviceQueueFamilyProperties");
|
||||
getFamilies(selected, &families, nullptr);
|
||||
queues.resize(families);
|
||||
getFamilies(selected, &families, queues.data());
|
||||
if (family < families && queues[family].queueCount >= 2) {
|
||||
queueInfo.queueCount = 2;
|
||||
queueInfo.pQueuePriorities = priorities;
|
||||
settleQueue = true;
|
||||
settleIndex = 1;
|
||||
} else {
|
||||
const auto copies = [&](std::uint32_t index) { return index != family && queues[index].queueCount != 0 && (queues[index].queueFlags & (VK_QUEUE_TRANSFER_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_GRAPHICS_BIT)) != 0; };
|
||||
for (std::uint32_t i = 0; i < families && !settleQueue; ++i) {
|
||||
if (copies(i) && (queues[i].queueFlags & (VK_QUEUE_COMPUTE_BIT | VK_QUEUE_GRAPHICS_BIT)) == 0) settleFamily = i, settleQueue = true;
|
||||
}
|
||||
for (std::uint32_t i = 0; i < families && !settleQueue; ++i) {
|
||||
if (copies(i)) settleFamily = i, settleQueue = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
VkDeviceCreateInfo deviceInfo{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO};
|
||||
deviceInfo.queueCreateInfoCount = 1;
|
||||
@@ -1677,50 +1701,38 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
|
||||
// Guest shaders routinely read past descriptor ranges; robust access turns that into zeros
|
||||
// instead of a GPU fault that loses the device.
|
||||
enabled.robustBufferAccess = available.robustBufferAccess;
|
||||
const auto plan = PlanDeviceQueues(queues, family, settles, available.sparseBinding == VK_TRUE);
|
||||
const bool settleQueue = plan.settle;
|
||||
const auto settleFamily = plan.settleFamily;
|
||||
const auto settleIndex = plan.settleIndex;
|
||||
VkDeviceQueueCreateInfo queueInfos[3]{queueInfo, {VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO}, {VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO}};
|
||||
if (available.sparseBinding && state->dmaBufImport) {
|
||||
std::uint32_t families = 0;
|
||||
auto getFamilies = state->InstanceFunction<PFN_vkGetPhysicalDeviceQueueFamilyProperties>("vkGetPhysicalDeviceQueueFamilyProperties");
|
||||
getFamilies(selected, &families, nullptr);
|
||||
std::vector<VkQueueFamilyProperties> queues(families);
|
||||
getFamilies(selected, &families, queues.data());
|
||||
const auto binds = [&](std::uint32_t index) { return index != family && queues[index].queueCount != 0 && (queues[index].queueFlags & VK_QUEUE_SPARSE_BINDING_BIT) != 0; };
|
||||
std::uint32_t sparseFamily = families;
|
||||
for (std::uint32_t i = 0; i < families && sparseFamily == families; ++i) {
|
||||
if (binds(i) && queues[i].queueFlags == VK_QUEUE_SPARSE_BINDING_BIT) sparseFamily = i;
|
||||
queueInfos[0].queueCount = plan.mainCount;
|
||||
queueInfos[0].pQueuePriorities = plan.mainCount == 2 ? priorities : &priority;
|
||||
deviceInfo.queueCreateInfoCount = 1;
|
||||
const auto addQueue = [&](std::uint32_t queueFamily, std::uint32_t index) {
|
||||
for (std::uint32_t info = 1; info < deviceInfo.queueCreateInfoCount; ++info) {
|
||||
if (queueInfos[info].queueFamilyIndex != queueFamily) continue;
|
||||
queueInfos[info].queueCount = std::max(queueInfos[info].queueCount, index + 1);
|
||||
queueInfos[info].pQueuePriorities = queueInfos[info].queueCount == 2 ? priorities : &priority;
|
||||
return;
|
||||
}
|
||||
for (std::uint32_t i = 0; i < families && sparseFamily == families; ++i) {
|
||||
if (binds(i)) sparseFamily = i;
|
||||
}
|
||||
if (sparseFamily != families) {
|
||||
queueInfos[1].queueFamilyIndex = sparseFamily;
|
||||
queueInfos[1].queueCount = 1;
|
||||
queueInfos[1].pQueuePriorities = &priority;
|
||||
deviceInfo.queueCreateInfoCount = 2;
|
||||
state->sparseFamily = sparseFamily;
|
||||
state->sparseBinding = true;
|
||||
} else {
|
||||
state->sparseBinding = family < families && (queues[family].queueFlags & VK_QUEUE_SPARSE_BINDING_BIT) != 0;
|
||||
}
|
||||
enabled.sparseBinding = state->sparseBinding ? VK_TRUE : VK_FALSE;
|
||||
}
|
||||
auto& added = queueInfos[deviceInfo.queueCreateInfoCount++];
|
||||
added = {VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO};
|
||||
added.queueFamilyIndex = queueFamily;
|
||||
added.queueCount = index + 1;
|
||||
added.pQueuePriorities = added.queueCount == 2 ? priorities : &priority;
|
||||
};
|
||||
if (plan.settle && plan.settleFamily != family) addQueue(plan.settleFamily, plan.settleIndex);
|
||||
if (plan.sparseFamily != VK_QUEUE_FAMILY_IGNORED) addQueue(plan.sparseFamily, plan.sparseIndex);
|
||||
deviceInfo.pQueueCreateInfos = queueInfos;
|
||||
if (settleQueue && settleFamily != family) {
|
||||
if (deviceInfo.queueCreateInfoCount == 2 && queueInfos[1].queueFamilyIndex == settleFamily) {
|
||||
settleQueue = queues[settleFamily].queueCount >= 2;
|
||||
if (settleQueue) {
|
||||
queueInfos[1].queueCount = 2;
|
||||
queueInfos[1].pQueuePriorities = priorities;
|
||||
settleIndex = 1;
|
||||
}
|
||||
} else {
|
||||
auto& settleInfo = queueInfos[deviceInfo.queueCreateInfoCount++];
|
||||
settleInfo = {VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO};
|
||||
settleInfo.queueFamilyIndex = settleFamily;
|
||||
settleInfo.queueCount = 1;
|
||||
settleInfo.pQueuePriorities = &priority;
|
||||
}
|
||||
}
|
||||
state->sparseFamily = plan.sparseFamily;
|
||||
state->sparseIndex = plan.sparseIndex;
|
||||
state->sparseBinding = plan.sparseBinding;
|
||||
enabled.sparseBinding = state->sparseBinding ? VK_TRUE : VK_FALSE;
|
||||
enabled.sparseResidencyBuffer = state->sparseBinding ? available.sparseResidencyBuffer : VK_FALSE;
|
||||
enabled.sparseResidencyAliased = state->sparseBinding ? available.sparseResidencyAliased : VK_FALSE;
|
||||
state->sparseResidencyBuffer = enabled.sparseResidencyBuffer == VK_TRUE;
|
||||
state->sparseResidencyAliased = enabled.sparseResidencyAliased == VK_TRUE;
|
||||
// Indirect draw records with a non-zero first instance, and several records per call.
|
||||
enabled.drawIndirectFirstInstance = available.drawIndirectFirstInstance;
|
||||
enabled.multiDrawIndirect = available.multiDrawIndirect;
|
||||
@@ -2042,7 +2054,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
|
||||
state->settleFamily = settleFamily;
|
||||
state->DeviceFunction<PFN_vkGetDeviceQueue>("vkGetDeviceQueue")(state->device, settleFamily, settleIndex, &state->settleQueue);
|
||||
}
|
||||
if (state->sparseFamily != VK_QUEUE_FAMILY_IGNORED) state->DeviceFunction<PFN_vkGetDeviceQueue>("vkGetDeviceQueue")(state->device, state->sparseFamily, 0, &state->sparseQueue);
|
||||
if (state->sparseFamily != VK_QUEUE_FAMILY_IGNORED) state->DeviceFunction<PFN_vkGetDeviceQueue>("vkGetDeviceQueue")(state->device, state->sparseFamily, state->sparseIndex, &state->sparseQueue);
|
||||
if (checkpoints) state->checkpointMarker = state->DeviceFunction<PFN_vkCmdSetCheckpointNV>("vkCmdSetCheckpointNV");
|
||||
if (bufferMarkers) state->CreateMarkerBuffer();
|
||||
if (deviceFault || checkpoints || bufferMarkers) {
|
||||
@@ -3849,6 +3861,8 @@ Graphics::Context VulkanDevice::buildContext() const {
|
||||
context.individuallyFreeDescriptorSets = state->individuallyFreeDescriptorSets;
|
||||
context.dmaBufImport = state->dmaBufImport;
|
||||
context.sparseBinding = state->sparseBinding;
|
||||
context.sparseResidencyBuffer = state->sparseResidencyBuffer;
|
||||
context.sparseResidencyAliased = state->sparseResidencyAliased;
|
||||
context.queueFamily = state->queueFamily;
|
||||
context.sparseFamily = state->sparseFamily;
|
||||
context.sparseQueue = state->sparseQueue;
|
||||
|
||||
@@ -182,6 +182,8 @@ struct Context {
|
||||
bool drawInputsInPlace = true;
|
||||
bool dmaBufImport = false;
|
||||
bool sparseBinding = false;
|
||||
bool sparseResidencyBuffer = false;
|
||||
bool sparseResidencyAliased = false;
|
||||
std::uint32_t queueFamily = 0;
|
||||
std::uint32_t sparseFamily = VK_QUEUE_FAMILY_IGNORED;
|
||||
VkQueue sparseQueue = VK_NULL_HANDLE;
|
||||
|
||||
@@ -94,6 +94,8 @@ bool DrawRecipes();
|
||||
enum class IndirectDrawPath : std::uint8_t { Gpu, NotFolded, FetchUnknown, NonVertexPath, IndxOffset, DrawIndex, VertexRange, FeatureGap, PendingImage, PendingLabelOrCopy, NotImported, Disabled, IndexRange, Count };
|
||||
void CountIndirectDraw(IndirectDrawPath path, double readMs, bool rewritten = false);
|
||||
std::array<std::uint64_t, static_cast<std::size_t>(IndirectDrawPath::Count)> IndirectDrawCounts();
|
||||
void CountEmptyIndirectDraw(double readMs);
|
||||
std::uint64_t EmptyIndirectDrawCount();
|
||||
|
||||
struct DrawPassCounts {
|
||||
std::uint64_t draws = 0;
|
||||
|
||||
@@ -248,6 +248,11 @@ struct MirrorStats {
|
||||
};
|
||||
MirrorStats MirrorCounters();
|
||||
std::uint64_t SnapshotMismatches();
|
||||
struct HeapMirrorHold {
|
||||
std::uint64_t mirrors = 0;
|
||||
std::uint64_t bytes = 0;
|
||||
};
|
||||
HeapMirrorHold HeapMirrorsWithin(std::uint64_t begin, std::uint64_t end);
|
||||
void ClearSnapshotCopies(VkDevice device);
|
||||
std::vector<std::uint64_t> AddressTableKey(std::span<const std::pair<std::uint64_t, std::uint64_t>> writes, std::span<const ShaderRecompiler::BdaAbi::Range> own);
|
||||
void ClearImageMirrors(VkDevice device);
|
||||
@@ -492,6 +497,7 @@ private:
|
||||
std::vector<std::pair<std::uint64_t, std::uint64_t>> writes;
|
||||
std::vector<std::pair<std::uint64_t, std::uint64_t>> readsOverBase;
|
||||
std::vector<std::pair<std::uint64_t, std::vector<std::byte>>> heapReferences;
|
||||
std::vector<std::pair<std::uint64_t, std::shared_ptr<Buffer>>> heapReadbacks;
|
||||
// UploadPrepare ran (regions are frozen); `uploaded` once UploadFinish ran.
|
||||
bool prepared = false;
|
||||
bool uploaded = false;
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_RESIDENTMIRROR_HPP
|
||||
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_RESIDENTMIRROR_HPP
|
||||
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Context.hpp"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <span>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace AgcDriver::Graphics {
|
||||
|
||||
class Buffer;
|
||||
class ResidentMemory;
|
||||
|
||||
using GuestRuns = std::vector<std::pair<std::uint64_t, std::uint64_t>>;
|
||||
|
||||
GuestRuns GuestNonzeroBlocks(const GuestRuns& candidates);
|
||||
bool GuestBytesZero(std::uint64_t begin, std::uint64_t end);
|
||||
void SetResidentAddressSpaceLimit(std::uint64_t bytes);
|
||||
|
||||
class ResidentBuffer : public std::enable_shared_from_this<ResidentBuffer> {
|
||||
public:
|
||||
static constexpr std::uint64_t BlockBytes = 65536;
|
||||
|
||||
static std::shared_ptr<ResidentBuffer> Create(const Context& context, std::uint64_t base, std::uint64_t bytes, const GuestRuns& backed);
|
||||
~ResidentBuffer();
|
||||
ResidentBuffer(const ResidentBuffer&) = delete;
|
||||
ResidentBuffer& operator=(const ResidentBuffer&) = delete;
|
||||
|
||||
VkBuffer Handle() const { return buffer; }
|
||||
VkDeviceAddress DeviceAddress() const { return address + lead; }
|
||||
std::uint64_t Origin() const { return origin; }
|
||||
std::uint64_t BoundBytes() const { return bound * BlockBytes; }
|
||||
std::uint32_t MemoryType() const { return type; }
|
||||
std::byte* Block(std::uint64_t guest) const;
|
||||
std::uint64_t UnboundBytes(const GuestRuns& runs) const;
|
||||
void Bind(const GuestRuns& runs);
|
||||
void MarkChanged(std::uint64_t begin, std::uint64_t end);
|
||||
void Read(std::uint64_t guest, std::span<std::byte> out) const;
|
||||
void Store(std::uint64_t guest, std::span<const std::byte> bytes);
|
||||
std::shared_ptr<Buffer> RecordReadback(VkCommandBuffer commands, std::uint64_t guest, std::uint64_t bytes) const;
|
||||
void ReadDevice(std::uint64_t guest, std::span<std::byte> out);
|
||||
static void FlushChanged(const Context& context);
|
||||
|
||||
private:
|
||||
struct Slot {
|
||||
void* slab = nullptr;
|
||||
std::uint32_t index = 0;
|
||||
std::byte* host = nullptr;
|
||||
};
|
||||
ResidentBuffer(const Context& context, std::uint64_t base, std::uint64_t bytes, const GuestRuns& backed);
|
||||
std::size_t blockIndex(std::uint64_t guest) const;
|
||||
void submit(const std::vector<VkSparseMemoryBind>& binds);
|
||||
void flush();
|
||||
std::shared_ptr<Buffer> readbackBatch(std::uint64_t guest, std::uint64_t bytes);
|
||||
void release() noexcept;
|
||||
|
||||
Context context;
|
||||
std::shared_ptr<ResidentMemory> memory;
|
||||
VkBuffer buffer = VK_NULL_HANDLE;
|
||||
VkDeviceAddress address = 0;
|
||||
std::uint32_t type = 0;
|
||||
std::uint64_t origin = 0;
|
||||
std::uint64_t lead = 0;
|
||||
std::uint64_t size = 0;
|
||||
std::uint64_t bound = 0;
|
||||
std::vector<Slot> slots;
|
||||
std::mutex changedMutex;
|
||||
GuestRuns changed;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -468,6 +468,7 @@ struct DrawProfile {
|
||||
// with a constant, and the time of the CPU record reads (their syncs).
|
||||
std::array<std::uint64_t, IndirectPathCount> indirect{};
|
||||
std::uint64_t indirectRewritten = 0;
|
||||
std::uint64_t indirectEmpty = 0;
|
||||
double indirectReadUs = 0;
|
||||
// Draw packets that drew nothing, by DrawSkip, and their time.
|
||||
std::array<std::uint64_t, static_cast<std::size_t>(DrawSkip::Count)> skips{};
|
||||
@@ -565,12 +566,12 @@ void reportDraw(const std::array<double, PhaseCount>& us, const ShaderResources:
|
||||
if (room()) n += std::snprintf(line + n, sizeof(line) - static_cast<std::size_t>(n), "; hook waits inside draws %.1f ms (max %.1f; the draws' own syncs, %.1f ms, not counted)", profile.hookWaitUs / 1000.0, profile.maxHookWaitUs / 1000.0, profile.ownSyncUs / 1000.0);
|
||||
std::uint64_t indirectCpu = 0;
|
||||
for (std::size_t i = 1; i < IndirectPathCount; ++i) indirectCpu += profile.indirect[i];
|
||||
if ((profile.indirect[0] != 0 || indirectCpu != 0) && room()) {
|
||||
if ((profile.indirect[0] != 0 || indirectCpu != 0 || profile.indirectEmpty != 0) && room()) {
|
||||
n += std::snprintf(line + n, sizeof(line) - static_cast<std::size_t>(n), "; indirect: gpu-side %llu (%llu rewritten), cpu-side %llu (", static_cast<unsigned long long>(profile.indirect[0]), static_cast<unsigned long long>(profile.indirectRewritten), static_cast<unsigned long long>(indirectCpu));
|
||||
for (std::size_t i = 1; i < IndirectPathCount && room(); ++i) {
|
||||
if (profile.indirect[i] != 0) n += std::snprintf(line + n, sizeof(line) - static_cast<std::size_t>(n), " %s %llu", IndirectDrawPathNames[i], static_cast<unsigned long long>(profile.indirect[i]));
|
||||
}
|
||||
if (room()) n += std::snprintf(line + n, sizeof(line) - static_cast<std::size_t>(n), "), argument reads %.1f ms", profile.indirectReadUs / 1000.0);
|
||||
if (room()) n += std::snprintf(line + n, sizeof(line) - static_cast<std::size_t>(n), "), empty skipped before their stages %llu, argument reads %.1f ms", static_cast<unsigned long long>(profile.indirectEmpty), profile.indirectReadUs / 1000.0);
|
||||
}
|
||||
if (room()) n += std::snprintf(line + n, sizeof(line) - static_cast<std::size_t>(n), "; skipped packets:");
|
||||
for (std::size_t i = 0; i < profile.skips.size() && room(); ++i) {
|
||||
@@ -901,6 +902,21 @@ std::array<std::uint64_t, IndirectPathCount> IndirectDrawCounts() {
|
||||
return stats.indirect;
|
||||
}
|
||||
|
||||
void CountEmptyIndirectDraw(double readMs) {
|
||||
static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr;
|
||||
if (!profile) return;
|
||||
auto& stats = Profile();
|
||||
std::lock_guard lock(stats.mutex);
|
||||
++stats.indirectEmpty;
|
||||
stats.indirectReadUs += readMs * 1000.0;
|
||||
}
|
||||
|
||||
std::uint64_t EmptyIndirectDrawCount() {
|
||||
auto& stats = Profile();
|
||||
std::lock_guard lock(stats.mutex);
|
||||
return stats.indirectEmpty;
|
||||
}
|
||||
|
||||
std::array<DrawPassCounts, 2> DrawPassTotals() {
|
||||
auto& stats = Profile();
|
||||
std::lock_guard lock(stats.mutex);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "prx/libSceAgcDriver/Graphics/include/BdaResources.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/ResidentMirror.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/UnitShadow.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
@@ -74,6 +75,9 @@ struct ImageMirror {
|
||||
std::vector<std::uint64_t> generations;
|
||||
std::weak_ptr<const GuestAllocations::Range> range;
|
||||
std::shared_ptr<Buffer> buffer;
|
||||
std::shared_ptr<ResidentBuffer> resident;
|
||||
std::uint64_t accounted = 0;
|
||||
bool listed = false;
|
||||
// Writable ranges only: the guest bytes the mirror holds, compared on refresh and at write-back.
|
||||
std::vector<std::byte> shadow;
|
||||
// Identity for the life of this mirror (see ImageMirrorSerial); the top bit keeps mirror serials
|
||||
@@ -1655,7 +1659,11 @@ const HostImport* importJoined(const Context& context, HostImports& state, std::
|
||||
auto run = joinedRun(lease, begin, end);
|
||||
if (run.ranges.empty()) return nullptr;
|
||||
if (const auto failed = state.failedJoins.find(run.base); failed != state.failedJoins.end() && sameRun(failed->second, run)) return nullptr;
|
||||
return importRunAt(context, state, std::move(run), true, begin, lease, end, true, ImportRequester::Join);
|
||||
const auto existing = state.imports.find(run.base);
|
||||
const bool existed = existing != state.imports.end() && sameRun(existing->second, run);
|
||||
const auto* entry = importRunAt(context, state, std::move(run), true, begin, lease, end, true, ImportRequester::Join);
|
||||
if (entry != nullptr && !existed) ++state.epoch;
|
||||
return entry;
|
||||
}
|
||||
|
||||
const HostImport* importRunAt(const Context& context, HostImports& state, ImportRun run, bool joined, std::uint64_t address, const GuestAllocations::Lease& lease, std::uint64_t end, bool evict, ImportRequester requester) {
|
||||
@@ -2122,25 +2130,104 @@ std::uint64_t heapMirrorBudget() {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
std::string heapMirrorError(const GuestAllocations::Range& range, std::uint64_t held, std::string_view reason) {
|
||||
std::string heapMirrorError(std::uint64_t address, std::uint64_t bytes, std::uint64_t held, std::string_view reason) {
|
||||
constexpr std::string_view prefix = "AGC graphics: ";
|
||||
if (reason.starts_with(prefix)) reason.remove_prefix(prefix.size());
|
||||
char where[64];
|
||||
std::snprintf(where, sizeof(where), "heap mirror of 0x%llx+0x%llx: ", static_cast<unsigned long long>(range.address), static_cast<unsigned long long>(range.bytes));
|
||||
std::snprintf(where, sizeof(where), "heap mirror of 0x%llx+0x%llx: ", static_cast<unsigned long long>(address), static_cast<unsigned long long>(bytes));
|
||||
char holding[96];
|
||||
std::snprintf(holding, sizeof(holding), "; heap mirrors hold %llu MiB of %llu MiB (APS5_HEAP_MIRROR_MIB)", static_cast<unsigned long long>(held >> 20u), static_cast<unsigned long long>(heapMirrorBudget() >> 20u));
|
||||
return std::string(prefix) + where + std::string(reason) + holding;
|
||||
}
|
||||
|
||||
std::uint64_t heapMirrorsBeside(const GuestAllocations::Range& range) {
|
||||
auto& state = Mirrors();
|
||||
std::lock_guard lock(state.mutex);
|
||||
std::uint64_t held = state.heapBytes;
|
||||
if (const auto found = state.entries.find(range.address); found != state.entries.end() && found->second->heap) held -= found->second->bytes;
|
||||
return held;
|
||||
void sweepMirrors();
|
||||
|
||||
void unlistMirror(ImageMirrors& state, ImageMirror& mirror) {
|
||||
if (!mirror.listed) return;
|
||||
state.heapBytes -= mirror.accounted;
|
||||
mirror.listed = false;
|
||||
}
|
||||
|
||||
void sweepMirrors();
|
||||
std::uint64_t blockBytes(const GuestRuns& runs) {
|
||||
constexpr std::uint64_t block = ResidentBuffer::BlockBytes;
|
||||
std::uint64_t blocks = 0;
|
||||
std::uint64_t last = 0;
|
||||
bool any = false;
|
||||
for (const auto& [first, end] : runs) {
|
||||
if (first >= end) continue;
|
||||
auto index = first / block;
|
||||
if (any && index == last) ++index;
|
||||
if (index <= (end - 1) / block) blocks += (end - 1) / block - index + 1;
|
||||
last = (end - 1) / block;
|
||||
any = true;
|
||||
}
|
||||
return blocks * block;
|
||||
}
|
||||
|
||||
void reserveHeapBytes(std::uint64_t address, std::uint64_t bytes, std::uint64_t need) {
|
||||
auto& state = Mirrors();
|
||||
for (int attempt = 0;; ++attempt) {
|
||||
{
|
||||
std::lock_guard lock(state.mutex);
|
||||
std::uint64_t held = state.heapBytes;
|
||||
if (const auto found = state.entries.find(address); found != state.entries.end() && found->second->heap && found->second->listed) held -= found->second->accounted;
|
||||
if (held + need <= heapMirrorBudget()) {
|
||||
state.heapBytes += need;
|
||||
return;
|
||||
}
|
||||
if (attempt != 0) throw std::runtime_error(heapMirrorError(address, bytes, held, "past the budget"));
|
||||
}
|
||||
sweepMirrors();
|
||||
}
|
||||
}
|
||||
|
||||
void growResident(ImageMirror& mirror, const GuestRuns& runs) {
|
||||
if (mirror.resident == nullptr) return;
|
||||
const auto need = mirror.resident->UnboundBytes(runs);
|
||||
if (need == 0) return;
|
||||
auto& state = Mirrors();
|
||||
bool counted = false;
|
||||
for (int attempt = 0; !counted; ++attempt) {
|
||||
{
|
||||
std::lock_guard lock(state.mutex);
|
||||
if (!mirror.listed) break;
|
||||
if (state.heapBytes + need <= heapMirrorBudget()) {
|
||||
state.heapBytes += need;
|
||||
mirror.accounted += need;
|
||||
counted = true;
|
||||
break;
|
||||
}
|
||||
if (attempt != 0) throw std::runtime_error(heapMirrorError(mirror.base, mirror.reserved, state.heapBytes, "past the budget"));
|
||||
}
|
||||
sweepMirrors();
|
||||
}
|
||||
try {
|
||||
mirror.resident->Bind(runs);
|
||||
} catch (const std::runtime_error& error) {
|
||||
std::uint64_t held = 0;
|
||||
{
|
||||
std::lock_guard lock(state.mutex);
|
||||
if (counted && mirror.listed) {
|
||||
state.heapBytes -= need;
|
||||
mirror.accounted -= need;
|
||||
}
|
||||
held = state.heapBytes;
|
||||
}
|
||||
throw std::runtime_error(heapMirrorError(mirror.base, mirror.reserved, held, error.what()));
|
||||
}
|
||||
}
|
||||
|
||||
std::uint64_t mirrorStart(const ImageMirror& mirror) {
|
||||
return mirror.resident != nullptr ? mirror.resident->Origin() : mirror.base;
|
||||
}
|
||||
|
||||
VkBuffer mirrorHandle(const ImageMirror& mirror) {
|
||||
return mirror.resident != nullptr ? mirror.resident->Handle() : mirror.buffer->Handle();
|
||||
}
|
||||
|
||||
VkDeviceAddress mirrorAddress(const ImageMirror& mirror) {
|
||||
return mirror.resident != nullptr ? mirror.resident->DeviceAddress() : mirror.buffer->DeviceAddress();
|
||||
}
|
||||
|
||||
bool sameRange(const std::weak_ptr<const GuestAllocations::Range>& mirrored, const std::shared_ptr<const GuestAllocations::Range>& range) {
|
||||
return !mirrored.owner_before(range) && !range.owner_before(mirrored);
|
||||
@@ -2162,6 +2249,16 @@ constexpr std::uint8_t SparseZero = 2;
|
||||
bool compareBlock(const RefreshBlock& block) {
|
||||
const auto offset = static_cast<std::size_t>(block.address - block.mirror->base);
|
||||
const auto* guest = reinterpret_cast<const std::byte*>(block.address);
|
||||
if (block.mirror->resident != nullptr) {
|
||||
auto* target = block.mirror->resident->Block(block.address);
|
||||
if (block.fill == SparseZero) {
|
||||
if (target != nullptr) std::memset(target, 0, block.length);
|
||||
return true;
|
||||
}
|
||||
if (target != nullptr) std::memcpy(target, guest, block.length);
|
||||
if (block.fill == 0) block.mirror->generations[static_cast<std::size_t>(block.address / 65536 - block.mirror->base / 65536)] = block.generation;
|
||||
return target != nullptr;
|
||||
}
|
||||
if (block.fill != 0) {
|
||||
auto* target = block.mirror->buffer->Bytes().data() + offset;
|
||||
if (block.fill == SparseZero) std::memset(target, 0, block.length);
|
||||
@@ -2313,9 +2410,33 @@ void appendBlocks(std::vector<RefreshBlock>& blocks, ImageMirror& mirror, std::u
|
||||
}
|
||||
|
||||
// Compares the gathered blocks (of one or many mirrors) and counts them.
|
||||
void bindHeapBlocks(std::span<const RefreshBlock> blocks) {
|
||||
std::vector<std::pair<ImageMirror*, GuestRuns>> wanted;
|
||||
for (const auto& block : blocks) {
|
||||
if (block.mirror->resident == nullptr || block.fill == SparseZero) continue;
|
||||
if (block.mirror->resident->Block(block.address) == nullptr && GuestBytesZero(block.address, block.address + block.length)) continue;
|
||||
if (wanted.empty() || wanted.back().first != block.mirror) {
|
||||
const auto found = std::find_if(wanted.begin(), wanted.end(), [&](const auto& entry) { return entry.first == block.mirror; });
|
||||
if (found == wanted.end()) wanted.emplace_back(block.mirror, GuestRuns{});
|
||||
else std::rotate(found, std::next(found), wanted.end());
|
||||
}
|
||||
auto& runs = wanted.back().second;
|
||||
if (!runs.empty() && runs.back().second == block.address) runs.back().second = block.address + block.length;
|
||||
else runs.emplace_back(block.address, block.address + block.length);
|
||||
}
|
||||
for (auto& [mirror, runs] : wanted) {
|
||||
std::sort(runs.begin(), runs.end());
|
||||
growResident(*mirror, runs);
|
||||
}
|
||||
}
|
||||
|
||||
void compareBlocks(std::span<const RefreshBlock> blocks) {
|
||||
if (blocks.empty()) return;
|
||||
bindHeapBlocks(blocks);
|
||||
const auto copied = RefreshPool::Get().Run(blocks);
|
||||
for (const auto& block : blocks) {
|
||||
if (block.mirror->resident != nullptr && block.mirror->resident->Block(block.address) != nullptr) block.mirror->resident->MarkChanged(block.address, block.address + block.length);
|
||||
}
|
||||
auto& state = Mirrors();
|
||||
state.blocksCompared += blocks.size();
|
||||
state.blocksCopied += copied;
|
||||
@@ -2542,6 +2663,7 @@ std::shared_ptr<ImageMirror> acquireMirror(const Context& context, const std::sh
|
||||
{
|
||||
std::lock_guard lock(state.mutex);
|
||||
if (state.device != context.device) {
|
||||
for (auto& [base, entry] : state.entries) entry->listed = false;
|
||||
state.entries.clear();
|
||||
state.failed.clear();
|
||||
state.heapBytes = 0;
|
||||
@@ -2574,6 +2696,7 @@ std::shared_ptr<ImageMirror> acquireMirror(const Context& context, const std::sh
|
||||
mirror->writable = range->writable;
|
||||
mirror->heap = heap;
|
||||
mirror->range = range;
|
||||
GuestRuns backed;
|
||||
if (heap) {
|
||||
std::uint64_t generation = 0;
|
||||
const auto serial = GuestMemory::UnwatchSerial();
|
||||
@@ -2591,7 +2714,7 @@ std::shared_ptr<ImageMirror> acquireMirror(const Context& context, const std::sh
|
||||
std::lock_guard lock(state.mutex);
|
||||
++state.heapUnwatched;
|
||||
if (const auto found = state.entries.find(range->address); found != state.entries.end() && found->second->heap) {
|
||||
state.heapBytes -= found->second->bytes;
|
||||
unlistMirror(state, *found->second);
|
||||
stale = std::move(found->second);
|
||||
state.entries.erase(found);
|
||||
++state.version;
|
||||
@@ -2599,15 +2722,64 @@ std::shared_ptr<ImageMirror> acquireMirror(const Context& context, const std::sh
|
||||
return nullptr;
|
||||
}
|
||||
mirror->watchedSerial.store(serial, std::memory_order_relaxed);
|
||||
if (heapMirrorsBeside(*range) + mirror->bytes > heapMirrorBudget()) {
|
||||
sweepMirrors();
|
||||
if (const auto held = heapMirrorsBeside(*range); held + mirror->bytes > heapMirrorBudget()) throw std::runtime_error(heapMirrorError(*range, held, "past the budget"));
|
||||
if (context.sparseBinding && context.sparseResidencyBuffer && context.sparseResidencyAliased) backed = GuestNonzeroBlocks(sparse ? clipPieces(pieces, mirror->base, mirror->base + mirror->bytes) : GuestRuns{{mirror->base, mirror->base + mirror->bytes}});
|
||||
auto need = blockBytes(backed);
|
||||
reserveHeapBytes(range->address, range->bytes, need);
|
||||
try {
|
||||
mirror->resident = ResidentBuffer::Create(context, mirror->base, mirror->bytes, backed);
|
||||
} catch (const std::runtime_error& error) {
|
||||
std::uint64_t held = 0;
|
||||
{
|
||||
std::lock_guard lock(state.mutex);
|
||||
state.heapBytes -= need;
|
||||
held = state.heapBytes;
|
||||
}
|
||||
throw std::runtime_error(heapMirrorError(range->address, range->bytes, held, error.what()));
|
||||
}
|
||||
if (mirror->resident == nullptr) {
|
||||
{
|
||||
std::lock_guard lock(state.mutex);
|
||||
state.heapBytes -= need;
|
||||
}
|
||||
need = mirror->bytes;
|
||||
reserveHeapBytes(range->address, range->bytes, need);
|
||||
}
|
||||
mirror->accounted = need;
|
||||
}
|
||||
if (mirror->resident != nullptr) {
|
||||
{
|
||||
const GuestMemory::ReadSiteScope site(GuestMemory::ReadSite::MirrorRefresh);
|
||||
for (const auto& [first, last] : backed) {
|
||||
for (auto at = first; at < last;) {
|
||||
const auto next = std::min(last, (at / ResidentBuffer::BlockBytes + 1) * ResidentBuffer::BlockBytes);
|
||||
GuestMemory::Read(at, std::span<std::byte>(mirror->resident->Block(at), static_cast<std::size_t>(next - at)));
|
||||
at = next;
|
||||
}
|
||||
mirror->resident->MarkChanged(first / ResidentBuffer::BlockBytes * ResidentBuffer::BlockBytes, (last + ResidentBuffer::BlockBytes - 1) / ResidentBuffer::BlockBytes * ResidentBuffer::BlockBytes);
|
||||
}
|
||||
}
|
||||
mirror->committed = std::move(pieces);
|
||||
std::lock_guard lock(state.mutex);
|
||||
++state.rebuilds;
|
||||
mirror->serial = (1ull << 63u) | ++state.serials;
|
||||
if (const auto found = state.entries.find(range->address); found != state.entries.end() && found->second->heap) unlistMirror(state, *found->second);
|
||||
mirror->listed = true;
|
||||
state.entries[range->address] = mirror;
|
||||
++state.version;
|
||||
return mirror;
|
||||
}
|
||||
try {
|
||||
mirror->buffer = std::make_shared<Buffer>(context, static_cast<std::size_t>(mirror->bytes), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
|
||||
} catch (const std::runtime_error& error) {
|
||||
if (heap) throw std::runtime_error(heapMirrorError(*range, heapMirrorsBeside(*range), error.what()));
|
||||
if (heap) {
|
||||
std::uint64_t held = 0;
|
||||
{
|
||||
std::lock_guard lock(state.mutex);
|
||||
state.heapBytes -= mirror->accounted;
|
||||
held = state.heapBytes;
|
||||
}
|
||||
throw std::runtime_error(heapMirrorError(range->address, range->bytes, held, error.what()));
|
||||
}
|
||||
std::fprintf(stderr, "[gpu] image mirror of 0x%llx+0x%llx failed: %s; falling back to copies\n", static_cast<unsigned long long>(range->address), static_cast<unsigned long long>(range->bytes), error.what());
|
||||
std::lock_guard lock(state.mutex);
|
||||
state.failed.insert(range->address);
|
||||
@@ -2634,8 +2806,8 @@ std::shared_ptr<ImageMirror> acquireMirror(const Context& context, const std::sh
|
||||
++state.rebuilds;
|
||||
mirror->serial = (1ull << 63u) | ++state.serials;
|
||||
// A replaced mirror lives on in the regions of recorded work that bound it.
|
||||
if (const auto found = state.entries.find(range->address); found != state.entries.end() && found->second->heap) state.heapBytes -= found->second->bytes;
|
||||
if (heap) state.heapBytes += mirror->bytes;
|
||||
if (const auto found = state.entries.find(range->address); found != state.entries.end() && found->second->heap) unlistMirror(state, *found->second);
|
||||
mirror->listed = heap;
|
||||
state.entries[range->address] = mirror;
|
||||
++state.version;
|
||||
return mirror;
|
||||
@@ -2671,7 +2843,7 @@ void sweepMirrors() {
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
if (it->second->heap) state.heapBytes -= it->second->bytes;
|
||||
if (it->second->heap) unlistMirror(state, *it->second);
|
||||
it = state.entries.erase(it);
|
||||
++state.version;
|
||||
}
|
||||
@@ -2688,9 +2860,9 @@ void releaseImportedMirrors(const Context& context, const std::vector<std::uint6
|
||||
for (const auto base : imported) {
|
||||
const auto found = state.entries.find(base);
|
||||
if (found == state.entries.end() || !found->second->heap) continue;
|
||||
state.heapBytes -= found->second->bytes;
|
||||
state.heapImportedBytes += found->second->accounted;
|
||||
unlistMirror(state, *found->second);
|
||||
++state.heapImported;
|
||||
state.heapImportedBytes += found->second->bytes;
|
||||
released.push_back(std::move(found->second));
|
||||
state.entries.erase(found);
|
||||
}
|
||||
@@ -3013,6 +3185,18 @@ std::uint64_t SnapshotMismatches() {
|
||||
return Snapshots().mismatched.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
HeapMirrorHold HeapMirrorsWithin(std::uint64_t begin, std::uint64_t end) {
|
||||
auto& state = Mirrors();
|
||||
std::lock_guard lock(state.mutex);
|
||||
HeapMirrorHold hold;
|
||||
for (auto it = state.entries.lower_bound(begin); it != state.entries.end() && it->first < end; ++it) {
|
||||
if (!it->second->heap || it->second->range.expired()) continue;
|
||||
++hold.mirrors;
|
||||
if (it->second->listed) hold.bytes += it->second->accounted;
|
||||
}
|
||||
return hold;
|
||||
}
|
||||
|
||||
std::vector<std::uint64_t> AddressTableKey(std::span<const std::pair<std::uint64_t, std::uint64_t>> writes, std::span<const ShaderRecompiler::BdaAbi::Range> own) {
|
||||
std::vector<std::uint64_t> key;
|
||||
key.reserve(writes.size() * 2u + 1u + own.size() * 4u);
|
||||
@@ -3041,6 +3225,7 @@ void ClearImageMirrors(VkDevice device) {
|
||||
std::lock_guard lock(state.mutex);
|
||||
if (state.device != device) return;
|
||||
entries.swap(state.entries);
|
||||
for (auto& [base, entry] : entries) entry->listed = false;
|
||||
state.failed.clear();
|
||||
state.heapBytes = 0;
|
||||
state.device = VK_NULL_HANDLE;
|
||||
@@ -3983,7 +4168,7 @@ bool GuestBufferMemory::bindSparseMirror(Region& region, const GuestAllocations:
|
||||
refreshHeapMirrors(whole, blocks);
|
||||
}
|
||||
compareBlocks(blocks);
|
||||
if (mirror == nullptr || !mirror->heap || region.begin < mirror->base || (region.begin - mirror->base) % context.limits.minStorageBufferOffsetAlignment != 0) return false;
|
||||
if (mirror == nullptr || !mirror->heap || region.begin < mirror->base || (region.begin - mirrorStart(*mirror)) % context.limits.minStorageBufferOffsetAlignment != 0) return false;
|
||||
region.mirror = std::move(mirror);
|
||||
if (!servesView(region, std::max(region.lastView, region.begin))) {
|
||||
region.mirror = nullptr;
|
||||
@@ -4689,6 +4874,7 @@ void GuestBufferMemory::UploadFinish(bool addressable) {
|
||||
if (heapsEnd > heapsBegin) StorageTexture::PublishShadowsOnly(heapsBegin, static_cast<std::size_t>(heapsEnd - heapsBegin), PublishScope::Whole, "address-based heaps");
|
||||
if (!gpuCopies.empty()) recordGpuCopies(gpuCopies, addressable);
|
||||
takeHeapReferences();
|
||||
ResidentBuffer::FlushChanged(context);
|
||||
if (!profile) return;
|
||||
const auto loopUs = microsecondsSince(loopStart);
|
||||
readUs.fetch_add(refreshUs, std::memory_order_relaxed);
|
||||
@@ -4923,16 +5109,36 @@ void GuestBufferMemory::RecordCopyBacks(Recorder& recorder) {
|
||||
VkCommandBuffer commands = VK_NULL_HANDLE;
|
||||
auto timing = Recorder::NoTiming;
|
||||
std::uint64_t copiedBytes = 0;
|
||||
if (!writes.empty()) {
|
||||
auto sorted = writes;
|
||||
std::sort(sorted.begin(), sorted.end());
|
||||
for (const auto& range : sorted) {
|
||||
if (!merged.empty() && range.first < merged.back().second) merged.back().second = std::max(merged.back().second, range.second);
|
||||
else merged.push_back(range);
|
||||
}
|
||||
}
|
||||
for (const auto& [begin, end] : merged) {
|
||||
const auto* found = owner(begin, end);
|
||||
if (found == nullptr || found->mirror == nullptr || !found->mirror->heap || !found->mirror->writable || found->mirror->resident == nullptr) continue;
|
||||
const auto& region = *found;
|
||||
{
|
||||
const auto from = begin;
|
||||
const auto to = std::min(end, servedEnd(region));
|
||||
if (from >= to || std::any_of(heapReadbacks.begin(), heapReadbacks.end(), [&](const auto& entry) { return entry.first == from; })) continue;
|
||||
if (!recording) {
|
||||
commands = recorder.Commands();
|
||||
timing = recorder.BeginGpuTiming(StagingCopyBackKey);
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT);
|
||||
Recorder::CountBarriers(Recorder::CommandClass::StagingOut);
|
||||
recording = true;
|
||||
}
|
||||
heapReadbacks.emplace_back(from, region.mirror->resident->RecordReadback(commands, from, to - from));
|
||||
recorder.Keep(heapReadbacks.back().second);
|
||||
copiedBytes += to - from;
|
||||
}
|
||||
}
|
||||
for (auto& region : regions) {
|
||||
if (!region.gpuCopy || region.copiedBack) continue;
|
||||
if (merged.empty() && !writes.empty()) {
|
||||
auto sorted = writes;
|
||||
std::sort(sorted.begin(), sorted.end());
|
||||
for (const auto& range : sorted) {
|
||||
if (!merged.empty() && range.first < merged.back().second) merged.back().second = std::max(merged.back().second, range.second);
|
||||
else merged.push_back(range);
|
||||
}
|
||||
}
|
||||
for (const auto& [begin, end] : merged) {
|
||||
const auto from = std::max(begin, region.begin);
|
||||
const auto to = std::min(end, region.end);
|
||||
@@ -4989,7 +5195,7 @@ VkDescriptorBufferInfo GuestBufferMemory::Descriptor(std::uint64_t address, std:
|
||||
return std::string(text);
|
||||
};
|
||||
if (!(address >= region.begin && address + bytes <= region.end && (region.buffer != nullptr || region.direct != nullptr || region.mirror != nullptr))) Require(false, describe("exceeds"));
|
||||
const auto base = region.direct != nullptr ? region.direct->base - region.direct->lead : region.mirror != nullptr ? region.mirror->base : region.begin;
|
||||
const auto base = region.direct != nullptr ? region.direct->base - region.direct->lead : region.mirror != nullptr ? mirrorStart(*region.mirror) : region.begin;
|
||||
const auto offset = address - base;
|
||||
Require(context.limits.minStorageBufferOffsetAlignment != 0, "no storage buffer offset alignment");
|
||||
Require(base % 4 == 0, "a guest buffer view in a GPU owner that does not start at a DWORD boundary is not implemented");
|
||||
@@ -5003,7 +5209,7 @@ VkDescriptorBufferInfo GuestBufferMemory::Descriptor(std::uint64_t address, std:
|
||||
}
|
||||
if (address - adjustment + range > end) Require(false, describe("rounded up to its view range exceeds"));
|
||||
Require(range <= context.limits.maxStorageBufferRange, "guest buffer view exceeds descriptor range limit");
|
||||
const auto handle = region.direct != nullptr ? region.direct->buffer : region.mirror != nullptr ? region.mirror->buffer->Handle() : region.buffer->Handle();
|
||||
const auto handle = region.direct != nullptr ? region.direct->buffer : region.mirror != nullptr ? mirrorHandle(*region.mirror) : region.buffer->Handle();
|
||||
return {handle, offset - adjustment, range};
|
||||
}
|
||||
|
||||
@@ -5016,7 +5222,7 @@ std::uint64_t GuestBufferMemory::ViewBytes(std::uint64_t bytes, std::uint32_t ad
|
||||
|
||||
ShaderRecompiler::BdaAbi::Range GuestBufferMemory::addressRange(const Region& region) {
|
||||
Require(region.buffer != nullptr || region.direct != nullptr || region.mirror != nullptr, "incomplete guest GPU upload");
|
||||
const auto address = region.direct != nullptr ? region.direct->address + (region.begin - region.direct->base) : region.mirror != nullptr ? region.mirror->buffer->DeviceAddress() + (region.begin - region.mirror->base) : region.buffer->DeviceAddress();
|
||||
const auto address = region.direct != nullptr ? region.direct->address + (region.begin - region.direct->base) : region.mirror != nullptr ? mirrorAddress(*region.mirror) + (region.begin - region.mirror->base) : region.buffer->DeviceAddress();
|
||||
Require(region.end - region.begin <= std::numeric_limits<std::uint64_t>::max() - address, "GPU address range overflow");
|
||||
const auto permissions = ShaderRecompiler::BdaAbi::Read | (region.direct != nullptr && region.writable ? ShaderRecompiler::BdaAbi::Write : 0u);
|
||||
return {region.begin, region.direct != nullptr ? inPlaceEnd(region) : servedEnd(region), address, permissions, 0};
|
||||
@@ -5279,15 +5485,19 @@ std::vector<std::pair<std::uint64_t, std::uint64_t>> mergeWrites(std::vector<std
|
||||
|
||||
void GuestBufferMemory::takeHeapReferences() {
|
||||
heapReferences.clear();
|
||||
heapReadbacks.clear();
|
||||
for (const auto& [begin, end] : mergeWrites(writes)) {
|
||||
const auto* found = owner(begin, end);
|
||||
if (found == nullptr || found->mirror == nullptr || !found->mirror->heap || !found->mirror->writable) continue;
|
||||
Require(end <= found->end, "written range exceeds its heap mirror");
|
||||
const auto& mirror = *found->mirror;
|
||||
auto& mirror = *found->mirror;
|
||||
const auto served = std::min(end, servedEnd(*found));
|
||||
if (begin >= served) continue;
|
||||
const auto bytes = mirror.buffer->Bytes().subspan(static_cast<std::size_t>(begin - mirror.base), static_cast<std::size_t>(served - begin));
|
||||
heapReferences.emplace_back(begin, std::vector<std::byte>(bytes.begin(), bytes.end()));
|
||||
growResident(mirror, {{begin, served}});
|
||||
std::vector<std::byte> bytes(static_cast<std::size_t>(served - begin));
|
||||
if (mirror.resident != nullptr) mirror.resident->Read(begin, bytes);
|
||||
else std::memcpy(bytes.data(), mirror.buffer->Bytes().data() + (begin - mirror.base), bytes.size());
|
||||
heapReferences.emplace_back(begin, std::move(bytes));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5317,14 +5527,28 @@ void GuestBufferMemory::WriteBack() {
|
||||
const auto served = std::min(end, servedEnd(region));
|
||||
if (mirror.heap && begin >= served) continue;
|
||||
const auto length = static_cast<std::size_t>((mirror.heap ? served : end) - begin);
|
||||
const auto current = mirror.buffer->Bytes().subspan(offset, length);
|
||||
if (mirror.heap) {
|
||||
const auto reference = std::find_if(heapReferences.begin(), heapReferences.end(), [&](const auto& entry) { return entry.first == begin; });
|
||||
Require(reference != heapReferences.end() && reference->second.size() == length, "heap mirror write-back has no reference");
|
||||
if (mirror.resident == nullptr) {
|
||||
const auto current = mirror.buffer->Bytes().subspan(offset, length);
|
||||
if (mirror.sparse) storeSparse(mirror, begin, current, reference->second);
|
||||
else storeChanged(begin, current, reference->second);
|
||||
continue;
|
||||
}
|
||||
std::vector<std::byte> current(length);
|
||||
if (const auto readback = std::find_if(heapReadbacks.begin(), heapReadbacks.end(), [&](const auto& entry) { return entry.first == begin; }); readback != heapReadbacks.end()) {
|
||||
Require(readback->second->Bytes().size() == length, "heap mirror readback does not match its write-back");
|
||||
std::memcpy(current.data(), readback->second->Bytes().data(), length);
|
||||
} else {
|
||||
mirror.resident->ReadDevice(begin, current);
|
||||
}
|
||||
mirror.resident->Store(begin, current);
|
||||
if (mirror.sparse) storeSparse(mirror, begin, current, reference->second);
|
||||
else storeChanged(begin, current, reference->second);
|
||||
continue;
|
||||
}
|
||||
const auto current = mirror.buffer->Bytes().subspan(offset, length);
|
||||
GuestMemory::WriteChanged(begin, current, std::span<const std::byte>(mirror.shadow).subspan(offset, length));
|
||||
std::memcpy(mirror.shadow.data() + offset, current.data(), length);
|
||||
continue;
|
||||
@@ -5375,6 +5599,7 @@ void GuestBufferMemory::WriteBack() {
|
||||
lease.clear();
|
||||
space.reset();
|
||||
heapReferences.clear();
|
||||
heapReadbacks.clear();
|
||||
}
|
||||
|
||||
std::optional<std::vector<std::pair<std::uint64_t, std::uint64_t>>> GuestBufferMemory::DirectRegions() const {
|
||||
@@ -5471,6 +5696,7 @@ std::vector<std::pair<std::uint64_t, std::uint64_t>> GuestBufferMemory::DeviceRe
|
||||
}
|
||||
|
||||
void GuestBufferMemory::RecordStagingCopies(Recorder& recorder) {
|
||||
ResidentBuffer::FlushChanged(context);
|
||||
if (!uploaded || committed) return;
|
||||
static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr;
|
||||
std::vector<Region*> copies;
|
||||
|
||||
@@ -0,0 +1,659 @@
|
||||
#include "prx/libSceAgcDriver/Graphics/include/ResidentMirror.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/BufferPool.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Resources.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libc/include/GuestArena.hpp"
|
||||
#ifndef _WIN32
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cerrno>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
namespace AgcDriver::Graphics {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::uint64_t BlockSize = ResidentBuffer::BlockBytes;
|
||||
constexpr std::uint64_t SlabBytes = std::uint64_t{16} << 20u;
|
||||
constexpr std::uint32_t SlabBlocks = static_cast<std::uint32_t>(SlabBytes / BlockSize);
|
||||
constexpr std::uint64_t ZeroBytes = SlabBytes;
|
||||
constexpr VkAccessFlags TransferredAccess = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_UNIFORM_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_INDIRECT_COMMAND_READ_BIT;
|
||||
|
||||
[[noreturn]] void fail(const char* step, VkResult result) {
|
||||
char text[160];
|
||||
std::snprintf(text, sizeof(text), "%s failed (%d)", step, static_cast<int>(result));
|
||||
throw std::runtime_error(text);
|
||||
}
|
||||
|
||||
struct TransientBuffer {
|
||||
TransientBuffer(const Context& context, VkBuffer buffer) : context(context), buffer(buffer) {}
|
||||
~TransientBuffer() { context.Function<PFN_vkDestroyBuffer>("vkDestroyBuffer")(context.device, buffer, nullptr); }
|
||||
TransientBuffer(const TransientBuffer&) = delete;
|
||||
TransientBuffer& operator=(const TransientBuffer&) = delete;
|
||||
Context context;
|
||||
VkBuffer buffer;
|
||||
};
|
||||
|
||||
template<typename Work>
|
||||
void recordTransfer(const Context& context, Work&& work) {
|
||||
GuestMemory::AssertGpuLockHeld("heap mirror transfer");
|
||||
std::vector<std::shared_ptr<void>> kept;
|
||||
if (auto* recorder = Recorder::Active(); recorder != nullptr) {
|
||||
const auto commands = recorder->Commands();
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT);
|
||||
work(commands, kept);
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, TransferredAccess);
|
||||
for (auto& object : kept) recorder->Keep(std::move(object));
|
||||
recorder->MarkCovered(TransferredAccess);
|
||||
return;
|
||||
}
|
||||
CommandBatch batch(context);
|
||||
RecordMemoryBarrier(context, batch.Handle(), VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT);
|
||||
work(batch.Handle(), kept);
|
||||
RecordMemoryBarrier(context, batch.Handle(), VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, TransferredAccess);
|
||||
batch.SubmitAndWait();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ResidentMemory {
|
||||
public:
|
||||
struct Slab {
|
||||
VkDeviceMemory memory = VK_NULL_HANDLE;
|
||||
std::unique_ptr<std::byte[]> host;
|
||||
std::vector<std::uint32_t> free;
|
||||
};
|
||||
|
||||
ResidentMemory(const Context& context, std::uint32_t type) : context(context), type(type) {
|
||||
zero = allocate(ZeroBytes, "vkAllocateMemory of the heap mirrors' zero block");
|
||||
}
|
||||
|
||||
~ResidentMemory() {
|
||||
const auto freeMemory = context.Function<PFN_vkFreeMemory>("vkFreeMemory");
|
||||
for (auto& slab : slabs) freeMemory(context.device, slab->memory, nullptr);
|
||||
freeMemory(context.device, zero, nullptr);
|
||||
}
|
||||
|
||||
ResidentMemory(const ResidentMemory&) = delete;
|
||||
ResidentMemory& operator=(const ResidentMemory&) = delete;
|
||||
|
||||
VkDeviceMemory Zero() const { return zero; }
|
||||
bool Zeroed() const { return zeroed.load(std::memory_order_acquire); }
|
||||
|
||||
void FillZero(VkCommandBuffer commands, std::vector<std::shared_ptr<void>>& kept) {
|
||||
if (Zeroed()) return;
|
||||
VkBufferCreateInfo info{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
|
||||
info.size = ZeroBytes;
|
||||
info.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
||||
VkBuffer buffer = VK_NULL_HANDLE;
|
||||
if (const auto result = context.Function<PFN_vkCreateBuffer>("vkCreateBuffer")(context.device, &info, nullptr, &buffer); result != VK_SUCCESS) fail("vkCreateBuffer of the heap mirrors' zero fill", result);
|
||||
auto transient = std::make_shared<TransientBuffer>(context, buffer);
|
||||
VkMemoryRequirements requirements{};
|
||||
context.Function<PFN_vkGetBufferMemoryRequirements>("vkGetBufferMemoryRequirements")(context.device, buffer, &requirements);
|
||||
if ((requirements.memoryTypeBits & (1u << type)) == 0 || requirements.size > ZeroBytes) throw std::runtime_error("the heap mirrors' zero block memory cannot back a transfer buffer to fill it");
|
||||
if (const auto result = context.Function<PFN_vkBindBufferMemory>("vkBindBufferMemory")(context.device, buffer, zero, 0); result != VK_SUCCESS) fail("vkBindBufferMemory of the heap mirrors' zero fill", result);
|
||||
context.Function<PFN_vkCmdFillBuffer>("vkCmdFillBuffer")(commands, buffer, 0, VK_WHOLE_SIZE, 0);
|
||||
kept.push_back(std::move(transient));
|
||||
zeroed.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
std::tuple<Slab*, std::uint32_t> Take() {
|
||||
std::lock_guard lock(mutex);
|
||||
for (auto& slab : slabs) {
|
||||
if (slab->free.empty()) continue;
|
||||
const auto index = slab->free.back();
|
||||
slab->free.pop_back();
|
||||
return {slab.get(), index};
|
||||
}
|
||||
auto slab = std::make_unique<Slab>();
|
||||
slab->memory = allocate(SlabBytes, "vkAllocateMemory of a heap mirror slab");
|
||||
slab->host = std::unique_ptr<std::byte[]>(new std::byte[SlabBytes]);
|
||||
for (std::uint32_t index = SlabBlocks; index-- > 1;) slab->free.push_back(index);
|
||||
slabs.push_back(std::move(slab));
|
||||
return {slabs.back().get(), 0u};
|
||||
}
|
||||
|
||||
void Put(Slab* slab, std::uint32_t index) noexcept {
|
||||
std::lock_guard lock(mutex);
|
||||
slab->free.push_back(index);
|
||||
if (slab->free.size() != SlabBlocks) return;
|
||||
const auto found = std::find_if(slabs.begin(), slabs.end(), [&](const auto& held) { return held.get() == slab; });
|
||||
context.Function<PFN_vkFreeMemory>("vkFreeMemory")(context.device, slab->memory, nullptr);
|
||||
slabs.erase(found);
|
||||
}
|
||||
|
||||
private:
|
||||
VkDeviceMemory allocate(std::uint64_t bytes, const char* operation) {
|
||||
const VkMemoryAllocateFlagsInfo flags{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO, nullptr, VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT, 0};
|
||||
VkMemoryAllocateInfo info{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, context.bufferDeviceAddress ? &flags : nullptr};
|
||||
info.allocationSize = bytes;
|
||||
info.memoryTypeIndex = type;
|
||||
if (context.bufferPool != nullptr) return context.bufferPool->AllocateMemory(info, operation);
|
||||
VkDeviceMemory memory = VK_NULL_HANDLE;
|
||||
if (const auto result = context.Function<PFN_vkAllocateMemory>("vkAllocateMemory")(context.device, &info, nullptr, &memory); result != VK_SUCCESS) fail(operation, result);
|
||||
return memory;
|
||||
}
|
||||
|
||||
Context context;
|
||||
std::uint32_t type;
|
||||
VkDeviceMemory zero = VK_NULL_HANDLE;
|
||||
std::atomic<bool> zeroed{false};
|
||||
std::mutex mutex;
|
||||
std::vector<std::unique_ptr<Slab>> slabs;
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
struct ResidentDevices {
|
||||
std::mutex mutex;
|
||||
std::map<std::tuple<VkDevice, std::uint32_t>, std::weak_ptr<ResidentMemory>> memories;
|
||||
std::map<VkDevice, std::uint64_t> addressSpace;
|
||||
std::uint64_t limit = 0;
|
||||
std::mutex pendingMutex;
|
||||
std::vector<std::weak_ptr<ResidentBuffer>> pending;
|
||||
};
|
||||
|
||||
ResidentDevices& Devices() {
|
||||
static ResidentDevices devices;
|
||||
return devices;
|
||||
}
|
||||
|
||||
std::shared_ptr<ResidentMemory> residentMemory(const Context& context, std::uint32_t type) {
|
||||
auto& devices = Devices();
|
||||
std::lock_guard lock(devices.mutex);
|
||||
auto& slot = devices.memories[{context.device, type}];
|
||||
if (auto held = slot.lock()) return held;
|
||||
auto made = std::make_shared<ResidentMemory>(context, type);
|
||||
slot = made;
|
||||
return made;
|
||||
}
|
||||
|
||||
struct Candidate {
|
||||
std::uint64_t begin = 0;
|
||||
std::uint64_t end = 0;
|
||||
bool readable = true;
|
||||
};
|
||||
|
||||
bool allZero(std::uint64_t begin, std::uint64_t end) {
|
||||
const auto* at = reinterpret_cast<const unsigned char*>(begin);
|
||||
const auto* stop = reinterpret_cast<const unsigned char*>(end);
|
||||
while (at < stop && reinterpret_cast<std::uintptr_t>(at) % sizeof(std::uint64_t) != 0) {
|
||||
if (*at++ != 0) return false;
|
||||
}
|
||||
while (stop - at >= static_cast<std::ptrdiff_t>(sizeof(std::uint64_t))) {
|
||||
std::uint64_t word = 0;
|
||||
std::memcpy(&word, at, sizeof(word));
|
||||
if (word != 0) return false;
|
||||
at += sizeof(word);
|
||||
}
|
||||
while (at < stop) {
|
||||
if (*at++ != 0) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
struct Mapping {
|
||||
std::uint64_t begin = 0;
|
||||
std::uint64_t end = 0;
|
||||
bool readable = false;
|
||||
bool privateAnonymous = false;
|
||||
};
|
||||
|
||||
std::vector<Mapping> mappings(std::uint64_t begin, std::uint64_t end) {
|
||||
std::ifstream maps("/proc/self/maps");
|
||||
Require(static_cast<bool>(maps), "cannot read /proc/self/maps for a heap mirror's pages");
|
||||
std::vector<Mapping> found;
|
||||
std::string line;
|
||||
while (std::getline(maps, line)) {
|
||||
std::istringstream fields(line);
|
||||
std::string span, permissions, offset, device;
|
||||
unsigned long long inode = 0;
|
||||
fields >> span >> permissions >> offset >> device >> inode;
|
||||
const auto dash = span.find('-');
|
||||
Require(dash != std::string::npos && permissions.size() == 4, "unexpected /proc/self/maps line: " + line);
|
||||
const auto first = std::stoull(span.substr(0, dash), nullptr, 16);
|
||||
const auto last = std::stoull(span.substr(dash + 1), nullptr, 16);
|
||||
if (last <= begin || first >= end) continue;
|
||||
found.push_back({std::max<std::uint64_t>(first, begin), std::min<std::uint64_t>(last, end), permissions[0] == 'r', inode == 0 && permissions[3] == 'p'});
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
void fileData(std::vector<Candidate>& maybe, const Mapping& mapping, int file, std::uint64_t offset) {
|
||||
const auto end = offset + (mapping.end - mapping.begin);
|
||||
auto at = static_cast<off_t>(offset);
|
||||
while (static_cast<std::uint64_t>(at) < end) {
|
||||
const auto data = lseek(file, at, SEEK_DATA);
|
||||
if (data < 0) {
|
||||
Require(errno == ENXIO, "SEEK_DATA on a heap mirror's shared backing failed");
|
||||
return;
|
||||
}
|
||||
if (static_cast<std::uint64_t>(data) >= end) return;
|
||||
const auto hole = lseek(file, data, SEEK_HOLE);
|
||||
Require(hole >= 0, "SEEK_HOLE on a heap mirror's shared backing failed");
|
||||
const auto stop = std::min<std::uint64_t>(static_cast<std::uint64_t>(hole), end);
|
||||
maybe.push_back({mapping.begin + (static_cast<std::uint64_t>(data) - offset), mapping.begin + (stop - offset), mapping.readable});
|
||||
at = static_cast<off_t>(stop);
|
||||
}
|
||||
}
|
||||
|
||||
void populatedPages(std::vector<Candidate>& maybe, const Mapping& mapping) {
|
||||
static const auto page = static_cast<std::uint64_t>(sysconf(_SC_PAGESIZE));
|
||||
const int file = open("/proc/self/pagemap", O_RDONLY | O_CLOEXEC);
|
||||
Require(file >= 0, "cannot open /proc/self/pagemap for a heap mirror's pages");
|
||||
constexpr std::size_t Entries = 4096;
|
||||
std::array<std::uint64_t, Entries> entries{};
|
||||
const auto first = mapping.begin / page;
|
||||
const auto last = (mapping.end + page - 1) / page;
|
||||
for (auto at = first; at < last;) {
|
||||
const auto count = static_cast<std::size_t>(std::min<std::uint64_t>(Entries, last - at));
|
||||
const auto read = pread(file, entries.data(), count * sizeof(std::uint64_t), static_cast<off_t>(at * sizeof(std::uint64_t)));
|
||||
if (read != static_cast<ssize_t>(count * sizeof(std::uint64_t))) {
|
||||
close(file);
|
||||
throw std::runtime_error("reading /proc/self/pagemap for a heap mirror's pages failed");
|
||||
}
|
||||
for (std::size_t index = 0; index < count; ++index) {
|
||||
constexpr std::uint64_t present = std::uint64_t{1} << 63u;
|
||||
constexpr std::uint64_t swapped = std::uint64_t{1} << 62u;
|
||||
if ((entries[index] & (present | swapped)) == 0) continue;
|
||||
const auto address = (at + index) * page;
|
||||
const auto from = std::max(address, mapping.begin);
|
||||
const auto to = std::min(address + page, mapping.end);
|
||||
if (!maybe.empty() && maybe.back().end == from && maybe.back().readable == mapping.readable) maybe.back().end = to;
|
||||
else maybe.push_back({from, to, mapping.readable});
|
||||
}
|
||||
at += count;
|
||||
}
|
||||
close(file);
|
||||
}
|
||||
#endif
|
||||
|
||||
std::vector<Candidate> possiblyNonzero(std::uint64_t begin, std::uint64_t end) {
|
||||
std::vector<Candidate> maybe;
|
||||
#ifdef _WIN32
|
||||
maybe.push_back({begin, end, true});
|
||||
#else
|
||||
for (const auto& mapping : mappings(begin, end)) {
|
||||
int file = -1;
|
||||
std::uint64_t offset = 0;
|
||||
if (GuestArena::GuestArenaSharedBacking_nid_postfix(static_cast<std::uintptr_t>(mapping.begin), static_cast<std::size_t>(mapping.end - mapping.begin), &file, &offset)) {
|
||||
try {
|
||||
fileData(maybe, mapping, file, offset);
|
||||
} catch (...) {
|
||||
close(file);
|
||||
throw;
|
||||
}
|
||||
close(file);
|
||||
} else if (mapping.privateAnonymous) {
|
||||
populatedPages(maybe, mapping);
|
||||
} else {
|
||||
maybe.push_back({mapping.begin, mapping.end, mapping.readable});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return maybe;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool GuestBytesZero(std::uint64_t begin, std::uint64_t end) {
|
||||
return allZero(begin, end);
|
||||
}
|
||||
|
||||
GuestRuns GuestNonzeroBlocks(const GuestRuns& candidates) {
|
||||
GuestRuns runs;
|
||||
for (const auto& [first, last] : candidates) {
|
||||
if (first >= last) continue;
|
||||
const auto maybe = possiblyNonzero(first, last);
|
||||
std::size_t at = 0;
|
||||
for (auto block = first / BlockSize * BlockSize; block < last; block += BlockSize) {
|
||||
const auto from = std::max(first, block);
|
||||
const auto to = std::min(last, block + BlockSize);
|
||||
while (at < maybe.size() && maybe[at].end <= from) ++at;
|
||||
bool nonzero = false;
|
||||
for (auto next = at; !nonzero && next < maybe.size() && maybe[next].begin < to; ++next) {
|
||||
const auto low = std::max(from, maybe[next].begin);
|
||||
const auto high = std::min(to, maybe[next].end);
|
||||
if (low < high) nonzero = !maybe[next].readable || !allZero(low, high);
|
||||
}
|
||||
if (!nonzero) continue;
|
||||
if (!runs.empty() && runs.back().second == from) runs.back().second = to;
|
||||
else runs.emplace_back(from, to);
|
||||
}
|
||||
}
|
||||
return runs;
|
||||
}
|
||||
|
||||
void SetResidentAddressSpaceLimit(std::uint64_t bytes) {
|
||||
auto& devices = Devices();
|
||||
std::lock_guard lock(devices.mutex);
|
||||
devices.limit = bytes;
|
||||
}
|
||||
|
||||
std::shared_ptr<ResidentBuffer> ResidentBuffer::Create(const Context& context, std::uint64_t base, std::uint64_t bytes, const GuestRuns& backed) {
|
||||
if (!context.sparseBinding || !context.sparseResidencyBuffer || !context.sparseResidencyAliased) return nullptr;
|
||||
const auto first = base / BlockSize * BlockSize;
|
||||
const auto size = (base - first + bytes + BlockSize - 1) / BlockSize * BlockSize;
|
||||
{
|
||||
auto& devices = Devices();
|
||||
std::lock_guard lock(devices.mutex);
|
||||
const auto limit = devices.limit != 0 ? std::min(devices.limit, context.limits.sparseAddressSpaceSize) : context.limits.sparseAddressSpaceSize;
|
||||
auto& used = devices.addressSpace[context.device];
|
||||
if (size > limit || used > limit - size) return nullptr;
|
||||
used += size;
|
||||
}
|
||||
return std::shared_ptr<ResidentBuffer>(new ResidentBuffer(context, base, bytes, backed));
|
||||
}
|
||||
|
||||
ResidentBuffer::ResidentBuffer(const Context& context, std::uint64_t base, std::uint64_t bytes, const GuestRuns& backed) : context(context) {
|
||||
origin = base / BlockSize * BlockSize;
|
||||
lead = base - origin;
|
||||
size = (lead + bytes + BlockSize - 1) / BlockSize * BlockSize;
|
||||
try {
|
||||
VkBufferCreateInfo info{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
|
||||
info.flags = VK_BUFFER_CREATE_SPARSE_BINDING_BIT | VK_BUFFER_CREATE_SPARSE_ALIASED_BIT;
|
||||
info.size = size;
|
||||
info.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
||||
const std::array<std::uint32_t, 2> families{context.queueFamily, context.sparseFamily};
|
||||
if (context.sparseQueue != VK_NULL_HANDLE && context.sparseFamily != context.queueFamily) {
|
||||
info.sharingMode = VK_SHARING_MODE_CONCURRENT;
|
||||
info.queueFamilyIndexCount = static_cast<std::uint32_t>(families.size());
|
||||
info.pQueueFamilyIndices = families.data();
|
||||
}
|
||||
if (const auto result = context.Function<PFN_vkCreateBuffer>("vkCreateBuffer")(context.device, &info, nullptr, &buffer); result != VK_SUCCESS) fail("vkCreateBuffer of a resident heap mirror", result);
|
||||
VkMemoryRequirements requirements{};
|
||||
context.Function<PFN_vkGetBufferMemoryRequirements>("vkGetBufferMemoryRequirements")(context.device, buffer, &requirements);
|
||||
if (requirements.alignment == 0 || requirements.alignment > BlockSize || BlockSize % requirements.alignment != 0) {
|
||||
char text[160];
|
||||
std::snprintf(text, sizeof(text), "the device's sparse buffer block of %llu bytes does not divide a heap mirror's 64 KiB block", static_cast<unsigned long long>(requirements.alignment));
|
||||
throw std::runtime_error(text);
|
||||
}
|
||||
type = context.DeviceMemoryType(requirements.memoryTypeBits, SlabBytes, "a resident heap mirror slab");
|
||||
memory = residentMemory(context, type);
|
||||
slots.resize(static_cast<std::size_t>(size / BlockSize));
|
||||
std::vector<bool> wanted(slots.size(), false);
|
||||
for (const auto& [first, last] : backed) {
|
||||
if (first >= last) continue;
|
||||
for (auto index = blockIndex(first); index <= blockIndex(last - 1); ++index) wanted[index] = true;
|
||||
}
|
||||
std::vector<VkSparseMemoryBind> binds;
|
||||
for (std::size_t index = 0; index < slots.size();) {
|
||||
if (wanted[index]) {
|
||||
auto [slab, at] = memory->Take();
|
||||
slots[index] = {slab, at, slab->host.get() + at * BlockSize};
|
||||
std::memset(slots[index].host, 0, static_cast<std::size_t>(BlockSize));
|
||||
binds.push_back({index * BlockSize, BlockSize, slab->memory, at * BlockSize, 0});
|
||||
++bound;
|
||||
++index;
|
||||
continue;
|
||||
}
|
||||
auto last = index;
|
||||
while (last < slots.size() && !wanted[last] && (last - index + 1) * BlockSize <= ZeroBytes) ++last;
|
||||
binds.push_back({index * BlockSize, (last - index) * BlockSize, memory->Zero(), 0, 0});
|
||||
index = last;
|
||||
}
|
||||
submit(binds);
|
||||
const VkBufferDeviceAddressInfo addressInfo{VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, nullptr, buffer};
|
||||
address = context.Function<PFN_vkGetBufferDeviceAddressKHR>("vkGetBufferDeviceAddressKHR")(context.device, &addressInfo);
|
||||
if (address == 0) fail("vkGetBufferDeviceAddressKHR of a resident heap mirror", VK_ERROR_UNKNOWN);
|
||||
} catch (...) {
|
||||
release();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
ResidentBuffer::~ResidentBuffer() {
|
||||
release();
|
||||
}
|
||||
|
||||
void ResidentBuffer::release() noexcept {
|
||||
if (buffer != VK_NULL_HANDLE) context.Function<PFN_vkDestroyBuffer>("vkDestroyBuffer")(context.device, buffer, nullptr);
|
||||
buffer = VK_NULL_HANDLE;
|
||||
for (auto& slot : slots) {
|
||||
if (slot.host != nullptr) memory->Put(static_cast<ResidentMemory::Slab*>(slot.slab), slot.index);
|
||||
slot = {};
|
||||
}
|
||||
auto& devices = Devices();
|
||||
std::lock_guard lock(devices.mutex);
|
||||
if (const auto found = devices.addressSpace.find(context.device); found != devices.addressSpace.end()) found->second -= std::min(found->second, size);
|
||||
size = 0;
|
||||
}
|
||||
|
||||
std::size_t ResidentBuffer::blockIndex(std::uint64_t guest) const {
|
||||
Require(guest >= origin && guest - origin < size, "guest address outside its resident heap mirror");
|
||||
return static_cast<std::size_t>((guest - origin) / BlockSize);
|
||||
}
|
||||
|
||||
std::byte* ResidentBuffer::Block(std::uint64_t guest) const {
|
||||
const auto& slot = slots[blockIndex(guest)];
|
||||
return slot.host == nullptr ? nullptr : slot.host + (guest - origin) % BlockSize;
|
||||
}
|
||||
|
||||
std::uint64_t ResidentBuffer::UnboundBytes(const GuestRuns& runs) const {
|
||||
std::vector<std::size_t> seen;
|
||||
for (const auto& [first, last] : runs) {
|
||||
if (first >= last) continue;
|
||||
for (auto index = blockIndex(first); index <= blockIndex(last - 1); ++index) {
|
||||
if (slots[index].host == nullptr) seen.push_back(index);
|
||||
}
|
||||
}
|
||||
std::sort(seen.begin(), seen.end());
|
||||
return static_cast<std::uint64_t>(std::unique(seen.begin(), seen.end()) - seen.begin()) * BlockSize;
|
||||
}
|
||||
|
||||
void ResidentBuffer::Bind(const GuestRuns& runs) {
|
||||
std::vector<VkSparseMemoryBind> binds;
|
||||
std::vector<std::size_t> made;
|
||||
for (const auto& [first, last] : runs) {
|
||||
if (first >= last) continue;
|
||||
for (auto index = blockIndex(first); index <= blockIndex(last - 1); ++index) {
|
||||
if (slots[index].host != nullptr) continue;
|
||||
auto [slab, at] = memory->Take();
|
||||
slots[index] = {slab, at, slab->host.get() + at * BlockSize};
|
||||
std::memset(slots[index].host, 0, static_cast<std::size_t>(BlockSize));
|
||||
binds.push_back({index * BlockSize, BlockSize, slab->memory, at * BlockSize, 0});
|
||||
made.push_back(index);
|
||||
}
|
||||
}
|
||||
if (binds.empty()) return;
|
||||
if (auto* recorder = Recorder::Active(); recorder != nullptr) recorder->Sync();
|
||||
try {
|
||||
submit(binds);
|
||||
} catch (...) {
|
||||
for (const auto index : made) {
|
||||
memory->Put(static_cast<ResidentMemory::Slab*>(slots[index].slab), slots[index].index);
|
||||
slots[index] = {};
|
||||
}
|
||||
throw;
|
||||
}
|
||||
bound += made.size();
|
||||
for (const auto index : made) MarkChanged(origin + index * BlockSize, origin + (index + 1) * BlockSize);
|
||||
}
|
||||
|
||||
void ResidentBuffer::MarkChanged(std::uint64_t begin, std::uint64_t end) {
|
||||
if (begin >= end) return;
|
||||
bool first = false;
|
||||
{
|
||||
std::lock_guard lock(changedMutex);
|
||||
first = changed.empty();
|
||||
if (!changed.empty() && changed.back().second == begin) changed.back().second = end;
|
||||
else changed.emplace_back(begin, end);
|
||||
}
|
||||
if (!first) return;
|
||||
auto& devices = Devices();
|
||||
std::lock_guard lock(devices.pendingMutex);
|
||||
devices.pending.push_back(weak_from_this());
|
||||
}
|
||||
|
||||
void ResidentBuffer::Read(std::uint64_t guest, std::span<std::byte> out) const {
|
||||
std::size_t done = 0;
|
||||
while (done < out.size()) {
|
||||
const auto at = guest + done;
|
||||
const auto within = static_cast<std::size_t>((at - origin) % BlockSize);
|
||||
const auto length = std::min<std::size_t>(out.size() - done, static_cast<std::size_t>(BlockSize) - within);
|
||||
if (const auto* source = Block(at); source != nullptr) std::memcpy(out.data() + done, source, length);
|
||||
else std::memset(out.data() + done, 0, length);
|
||||
done += length;
|
||||
}
|
||||
}
|
||||
|
||||
void ResidentBuffer::Store(std::uint64_t guest, std::span<const std::byte> bytes) {
|
||||
std::size_t done = 0;
|
||||
while (done < bytes.size()) {
|
||||
const auto at = guest + done;
|
||||
const auto within = static_cast<std::size_t>((at - origin) % BlockSize);
|
||||
const auto length = std::min<std::size_t>(bytes.size() - done, static_cast<std::size_t>(BlockSize) - within);
|
||||
if (auto* target = Block(at); target != nullptr) std::memcpy(target, bytes.data() + done, length);
|
||||
else Require(GuestBytesZero(reinterpret_cast<std::uintptr_t>(bytes.data() + done), reinterpret_cast<std::uintptr_t>(bytes.data() + done + length)), "the GPU wrote a heap mirror block that holds no memory");
|
||||
done += length;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Buffer> ResidentBuffer::RecordReadback(VkCommandBuffer commands, std::uint64_t guest, std::uint64_t bytes) const {
|
||||
Require(guest >= origin && bytes <= size && guest - origin <= size - bytes, "heap mirror readback outside its buffer");
|
||||
auto staging = std::make_shared<Buffer>(context, static_cast<std::size_t>(bytes), VK_BUFFER_USAGE_TRANSFER_DST_BIT);
|
||||
CopyBuffer(context, commands, buffer, guest - origin, staging->Handle(), 0, bytes);
|
||||
return staging;
|
||||
}
|
||||
|
||||
void ResidentBuffer::ReadDevice(std::uint64_t guest, std::span<std::byte> out) {
|
||||
GuestMemory::AssertGpuLockHeld("heap mirror device read");
|
||||
Require(!Recorder::InCompletion(), "heap mirror device read inside a GPU completion: its write-back needs a recorded readback");
|
||||
auto staging = readbackBatch(guest, out.size());
|
||||
std::memcpy(out.data(), staging->Bytes().data(), out.size());
|
||||
}
|
||||
|
||||
std::shared_ptr<Buffer> ResidentBuffer::readbackBatch(std::uint64_t guest, std::uint64_t bytes) {
|
||||
CommandBatch batch(context);
|
||||
RecordMemoryBarrier(context, batch.Handle(), VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT);
|
||||
auto staging = RecordReadback(batch.Handle(), guest, bytes);
|
||||
RecordMemoryBarrier(context, batch.Handle(), VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT);
|
||||
batch.SubmitAndWait();
|
||||
return staging;
|
||||
}
|
||||
|
||||
void ResidentBuffer::flush() {
|
||||
GuestRuns runs;
|
||||
{
|
||||
std::lock_guard lock(changedMutex);
|
||||
runs.swap(changed);
|
||||
}
|
||||
if (runs.empty()) return;
|
||||
try {
|
||||
std::sort(runs.begin(), runs.end());
|
||||
std::vector<VkBufferCopy> regions;
|
||||
std::uint64_t total = 0;
|
||||
std::uint64_t cursor = 0;
|
||||
for (const auto& [first, last] : runs) {
|
||||
for (auto at = std::max(first, cursor); at < last;) {
|
||||
const auto next = std::min(last, (at / BlockSize + 1) * BlockSize);
|
||||
if (slots[blockIndex(at)].host != nullptr) {
|
||||
if (!regions.empty() && regions.back().dstOffset + regions.back().size == at - origin) regions.back().size += next - at;
|
||||
else regions.push_back({total, at - origin, next - at});
|
||||
total += next - at;
|
||||
}
|
||||
at = next;
|
||||
}
|
||||
cursor = std::max(cursor, last);
|
||||
}
|
||||
if (regions.empty()) return;
|
||||
auto staging = std::make_shared<Buffer>(context, static_cast<std::size_t>(total), VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
|
||||
for (const auto& region : regions) {
|
||||
for (VkDeviceSize done = 0; done < region.size;) {
|
||||
const auto at = origin + region.dstOffset + done;
|
||||
const auto length = std::min<VkDeviceSize>(region.size - done, BlockSize - (at - origin) % BlockSize);
|
||||
std::memcpy(staging->Bytes().data() + region.srcOffset + done, Block(at), static_cast<std::size_t>(length));
|
||||
done += length;
|
||||
}
|
||||
}
|
||||
recordTransfer(context, [&](VkCommandBuffer commands, std::vector<std::shared_ptr<void>>& kept) {
|
||||
context.Function<PFN_vkCmdCopyBuffer>("vkCmdCopyBuffer")(commands, staging->Handle(), buffer, static_cast<std::uint32_t>(regions.size()), regions.data());
|
||||
kept.push_back(staging);
|
||||
kept.push_back(shared_from_this());
|
||||
});
|
||||
} catch (...) {
|
||||
{
|
||||
std::lock_guard lock(changedMutex);
|
||||
changed.insert(changed.begin(), runs.begin(), runs.end());
|
||||
}
|
||||
auto& devices = Devices();
|
||||
std::lock_guard lock(devices.pendingMutex);
|
||||
devices.pending.push_back(weak_from_this());
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void ResidentBuffer::FlushChanged(const Context& context) {
|
||||
auto& devices = Devices();
|
||||
std::vector<std::shared_ptr<ResidentMemory>> unzeroed;
|
||||
{
|
||||
std::lock_guard lock(devices.mutex);
|
||||
for (const auto& [key, held] : devices.memories) {
|
||||
if (std::get<0>(key) != context.device) continue;
|
||||
if (auto memory = held.lock(); memory != nullptr && !memory->Zeroed()) unzeroed.push_back(std::move(memory));
|
||||
}
|
||||
}
|
||||
for (auto& memory : unzeroed) recordTransfer(context, [&](VkCommandBuffer commands, std::vector<std::shared_ptr<void>>& kept) { memory->FillZero(commands, kept); });
|
||||
std::vector<std::weak_ptr<ResidentBuffer>> waiting;
|
||||
{
|
||||
std::lock_guard lock(devices.pendingMutex);
|
||||
waiting.swap(devices.pending);
|
||||
}
|
||||
for (std::size_t index = 0; index < waiting.size(); ++index) {
|
||||
const auto held = waiting[index].lock();
|
||||
if (held == nullptr) continue;
|
||||
if (held->context.device != context.device) {
|
||||
std::lock_guard lock(devices.pendingMutex);
|
||||
devices.pending.push_back(waiting[index]);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
held->flush();
|
||||
} catch (...) {
|
||||
std::lock_guard lock(devices.pendingMutex);
|
||||
devices.pending.insert(devices.pending.end(), waiting.begin() + static_cast<std::ptrdiff_t>(index) + 1, waiting.end());
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ResidentBuffer::submit(const std::vector<VkSparseMemoryBind>& binds) {
|
||||
const bool ownQueue = context.sparseQueue != VK_NULL_HANDLE && context.sparseQueueMutex != nullptr;
|
||||
const VkSparseBufferMemoryBindInfo bufferBinds{buffer, static_cast<std::uint32_t>(binds.size()), binds.data()};
|
||||
VkBindSparseInfo bind{VK_STRUCTURE_TYPE_BIND_SPARSE_INFO};
|
||||
bind.bufferBindCount = 1;
|
||||
bind.pBufferBinds = &bufferBinds;
|
||||
const VkFenceCreateInfo fenceInfo{VK_STRUCTURE_TYPE_FENCE_CREATE_INFO};
|
||||
VkFence fence = VK_NULL_HANDLE;
|
||||
if (const auto result = context.Function<PFN_vkCreateFence>("vkCreateFence")(context.device, &fenceInfo, nullptr, &fence); result != VK_SUCCESS) fail("vkCreateFence of a resident heap mirror bind", result);
|
||||
VkResult submitted = VK_SUCCESS;
|
||||
VkResult waited = VK_SUCCESS;
|
||||
{
|
||||
std::unique_lock<std::mutex> queueLock;
|
||||
std::unique_lock<GuestMemory::GpuMutexType> gpuLock;
|
||||
if (ownQueue) queueLock = std::unique_lock(*context.sparseQueueMutex);
|
||||
else gpuLock = std::unique_lock(GuestMemory::GpuMutex());
|
||||
submitted = context.Function<PFN_vkQueueBindSparse>("vkQueueBindSparse")(ownQueue ? context.sparseQueue : context.queue, 1, &bind, fence);
|
||||
if (submitted == VK_SUCCESS) waited = context.Function<PFN_vkWaitForFences>("vkWaitForFences")(context.device, 1, &fence, VK_TRUE, UINT64_MAX);
|
||||
}
|
||||
context.Function<PFN_vkDestroyFence>("vkDestroyFence")(context.device, fence, nullptr);
|
||||
if (submitted != VK_SUCCESS) fail("vkQueueBindSparse of a resident heap mirror", submitted);
|
||||
if (waited != VK_SUCCESS) fail("vkWaitForFences of a resident heap mirror bind", waited);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3131,6 +3131,63 @@ void RunSparseHeapMirrorUnwatchTests(const Context& context, const BdaTestAccess
|
||||
std::cout << "an address-based build keeps and refreshes a sparse heap's mirror once its range has left the write watch\n";
|
||||
}
|
||||
|
||||
void RunSparseHeapMirrorBudgetTests(const Context& context, const BdaTestAccess& access) {
|
||||
constexpr std::size_t block = 65536;
|
||||
constexpr std::size_t reserved = 128 * block;
|
||||
constexpr std::size_t committed = 80 * block;
|
||||
#ifdef _WIN32
|
||||
if (!GuestArena::GuestArenaAvailable_nid_postfix() || !GuestArena::GuestArenaWriteWatched_nid_postfix()) {
|
||||
std::cout << "guest arena unavailable or not write-watched: sparse heaps past the heap mirror budget not tested\n";
|
||||
return;
|
||||
}
|
||||
auto* guest = static_cast<std::byte*>(GuestArena::GuestArenaAllocate_nid_postfix(reserved, block));
|
||||
Require(guest != nullptr, "cannot allocate the sparse heap in the guest arena");
|
||||
GuestArena::GuestArenaCommit_nid_postfix(guest, committed, PAGE_READWRITE, block);
|
||||
#else
|
||||
if (!GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix()) {
|
||||
std::cout << "write watch unavailable: sparse heaps past the heap mirror budget not tested\n";
|
||||
return;
|
||||
}
|
||||
auto* guest = reserveSparse(reserved, committed);
|
||||
GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(guest, reserved);
|
||||
#endif
|
||||
std::memset(guest, 0x5a, committed);
|
||||
const auto base = reinterpret_cast<std::uintptr_t>(guest);
|
||||
const auto registry = [&](bool add) {
|
||||
auto* mutation = GuestAllocations::GuestAllocationsBegin_nid_postfix();
|
||||
if (add) GuestAllocations::GuestAllocationsAdd_nid_postfix(mutation, guest, reserved, true, true, true);
|
||||
else GuestAllocations::GuestAllocationsRemove_nid_postfix(mutation, guest);
|
||||
GuestAllocations::GuestAllocationsEnd_nid_postfix(mutation);
|
||||
};
|
||||
registry(true);
|
||||
const auto before = MirrorCounters();
|
||||
{
|
||||
GuestBufferMemory memory(context);
|
||||
memory.AddWritable(base, reserved);
|
||||
memory.Upload(false);
|
||||
std::uint32_t adjustment = 0;
|
||||
const auto view = memory.Descriptor(base, reserved, adjustment);
|
||||
const auto bytes = access.bytes(view.buffer);
|
||||
Require(adjustment == 0 && view.range >= committed && bytes[view.offset + committed - 1] == std::byte{0x5a}, "a sparse heap past the heap mirror budget was not copied up to its last committed byte");
|
||||
bytes[view.offset + 8] = std::byte{0x77};
|
||||
memory.WriteBack();
|
||||
}
|
||||
Require(guest[8] == std::byte{0x77}, "a store to a sparse heap copied past the heap mirror budget was not written back");
|
||||
const auto after = MirrorCounters();
|
||||
Require(after.sparseBinds == before.sparseBinds && after.heapMirrors == before.heapMirrors && after.heapBytes == before.heapBytes, "a sparse heap past the heap mirror budget was mirrored or left heap mirror bytes held");
|
||||
registry(false);
|
||||
ClearImageMirrors(context.device);
|
||||
ClearSnapshotCopies(context.device);
|
||||
#ifdef _WIN32
|
||||
GuestArena::GuestArenaReset_nid_postfix(guest, reserved);
|
||||
GuestArena::GuestArenaRelease_nid_postfix(guest, reserved);
|
||||
#else
|
||||
GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(guest, reserved);
|
||||
releaseSparse(guest, reserved);
|
||||
#endif
|
||||
std::cout << "a sparse heap whose mirror would pass the heap mirror budget is copied\n";
|
||||
}
|
||||
|
||||
void RunHeapMirrorRunUnwatchTests(const Context& context, const BdaTestAccess& access) {
|
||||
constexpr std::size_t block = 65536;
|
||||
constexpr std::size_t heapBytes = 32 * block;
|
||||
|
||||
@@ -19,6 +19,7 @@ void RunBdaResourceTests(const AgcDriver::Graphics::Context& context, const BdaT
|
||||
bool RunSparseBufferHoleTests(const AgcDriver::Graphics::Context& context, const BdaTestAccess& access);
|
||||
void RunHeapMirrorUnwatchTests(const AgcDriver::Graphics::Context& context, const BdaTestAccess& access, bool throughImport);
|
||||
void RunSparseHeapMirrorUnwatchTests(const AgcDriver::Graphics::Context& context, const BdaTestAccess& access);
|
||||
void RunSparseHeapMirrorBudgetTests(const AgcDriver::Graphics::Context& context, const BdaTestAccess& access);
|
||||
void RunHeapMirrorRunUnwatchTests(const AgcDriver::Graphics::Context& context, const BdaTestAccess& access);
|
||||
void RunGuestAllocationTests();
|
||||
void RunGuestLeaseWaitTests();
|
||||
|
||||
@@ -11239,6 +11239,23 @@ int main(int argc, char** argv) {
|
||||
Require(mock.live == 0, "the sparse heap mirror unwatch test leaked Vulkan objects");
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--sparse-heap-mirror-budget") {
|
||||
{
|
||||
mock = MockVulkan{};
|
||||
auto context = mockContext();
|
||||
context.bufferDeviceAddress = true;
|
||||
context.limits.maxStorageBufferRange = 1u << 27;
|
||||
RunSparseHeapMirrorBudgetTests(context, {
|
||||
[](VkBuffer buffer) -> std::span<std::byte> { return mockBufferMemory(buffer); },
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{}
|
||||
});
|
||||
}
|
||||
Require(mock.live == 0, "the sparse heap mirror budget test leaked Vulkan objects");
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--heap-mirror-run-unwatch") {
|
||||
{
|
||||
mock = MockVulkan{};
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using AgcDriver::Graphics::Require;
|
||||
|
||||
constexpr VkQueueFlags Graphics = VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT;
|
||||
constexpr VkQueueFlags Sparse = VK_QUEUE_SPARSE_BINDING_BIT;
|
||||
constexpr std::uint32_t Own = VK_QUEUE_FAMILY_IGNORED;
|
||||
|
||||
struct Case {
|
||||
const char* name;
|
||||
std::vector<VkQueueFamilyProperties> families;
|
||||
bool settles;
|
||||
std::uint32_t mainCount;
|
||||
bool settle;
|
||||
std::uint32_t settleFamily;
|
||||
std::uint32_t settleIndex;
|
||||
bool sparseBinding;
|
||||
std::uint32_t sparseFamily;
|
||||
std::uint32_t sparseIndex;
|
||||
};
|
||||
|
||||
VkQueueFamilyProperties family(VkQueueFlags flags, std::uint32_t queues) {
|
||||
VkQueueFamilyProperties properties{};
|
||||
properties.queueFlags = flags;
|
||||
properties.queueCount = queues;
|
||||
return properties;
|
||||
}
|
||||
|
||||
std::string describe(const AgcDriver::DeviceQueuePlan& plan) {
|
||||
return "main " + std::to_string(plan.mainCount) + " queues, settle " + (plan.settle ? std::to_string(plan.settleFamily) + ":" + std::to_string(plan.settleIndex) : std::string("none")) + ", sparse " + (!plan.sparseBinding ? std::string("none") : plan.sparseFamily == Own ? std::string("on the main queue") : std::to_string(plan.sparseFamily) + ":" + std::to_string(plan.sparseIndex));
|
||||
}
|
||||
|
||||
void check(const Case& test) {
|
||||
const auto plan = AgcDriver::PlanDeviceQueues(test.families, 0, test.settles, true);
|
||||
const auto dense = AgcDriver::PlanDeviceQueues(test.families, 0, test.settles, false);
|
||||
Require(plan.mainCount == dense.mainCount && plan.settle == dense.settle && plan.settleFamily == dense.settleFamily && plan.settleIndex == dense.settleIndex, std::string("device queue plan, ") + test.name + ": sparse binding changed the queues: " + describe(dense) + " without it, " + describe(plan) + " with it");
|
||||
Require(!dense.sparseBinding && dense.sparseFamily == Own, std::string("device queue plan, ") + test.name + ": a device without sparse binding got a sparse queue: " + describe(dense));
|
||||
const bool expected = plan.mainCount == test.mainCount && plan.settle == test.settle && (!test.settle || (plan.settleFamily == test.settleFamily && plan.settleIndex == test.settleIndex)) && plan.sparseBinding == test.sparseBinding && plan.sparseFamily == test.sparseFamily && (test.sparseFamily == Own || plan.sparseIndex == test.sparseIndex);
|
||||
Require(expected, std::string("device queue plan, ") + test.name + ": got " + describe(plan));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
const std::vector<Case> cases{
|
||||
{"one family with one queue", {family(Graphics | Sparse, 1)}, true, 1, false, 0, 0, true, Own, 0},
|
||||
{"one family with one queue, no settles", {family(Graphics | Sparse, 1)}, false, 1, false, 0, 0, true, Own, 0},
|
||||
{"settle family with one queue that also binds", {family(Graphics | Sparse, 1), family(VK_QUEUE_TRANSFER_BIT | Sparse, 1)}, true, 1, true, 1, 0, true, Own, 0},
|
||||
{"settle family with two queues that also binds", {family(Graphics, 1), family(VK_QUEUE_TRANSFER_BIT | Sparse, 2)}, true, 1, true, 1, 0, true, 1, 1},
|
||||
{"settle on the main family", {family(Graphics | Sparse, 2), family(VK_QUEUE_TRANSFER_BIT | Sparse, 1)}, true, 2, true, 0, 1, true, 1, 0},
|
||||
{"sparse-only family preferred", {family(Graphics | Sparse, 16), family(VK_QUEUE_TRANSFER_BIT | Sparse, 2), family(Sparse, 1)}, true, 2, true, 0, 1, true, 2, 0},
|
||||
{"no settles, a second family binds", {family(Graphics | Sparse, 1), family(VK_QUEUE_TRANSFER_BIT | Sparse, 1)}, false, 1, false, 0, 0, true, 1, 0},
|
||||
{"no queue binds", {family(Graphics, 1), family(VK_QUEUE_TRANSFER_BIT, 1)}, true, 1, true, 1, 0, false, Own, 0},
|
||||
{"only the settle queue binds", {family(Graphics, 1), family(VK_QUEUE_TRANSFER_BIT | Sparse, 1)}, true, 1, true, 1, 0, false, Own, 0},
|
||||
};
|
||||
for (const auto& test : cases) check(test);
|
||||
std::puts("device queue plan tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
|
||||
#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <exception>
|
||||
#include <initializer_list>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using AgcDriver::Graphics::Require;
|
||||
|
||||
constexpr std::byte Kept{0x40};
|
||||
constexpr std::uint32_t BaseVertexLocation = 0x8c;
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 20> VertexCode{
|
||||
0x7e000d05u, 0x080200f4u, 0x10020300u, 0x100202f4u, 0x060202f3u, 0x060400f3u, 0x10040500u, 0x060404f3u,
|
||||
0x7e060208u, 0x7e0802f2u, 0x7e0a02ffu, 0x44800000u, 0xf80000d1u, 0x00000005u, 0xf80008cfu, 0x04030201u, 0xbf810000u, 0xbf810000u, 0xbf810000u, 0xbf810000u,
|
||||
};
|
||||
alignas(256) constexpr std::array<std::uint32_t, 8> PixelCode{0x7e000200u, 0x7e020280u, 0xf800180fu, 0x00010100u, 0xbf810000u, 0xbf810000u, 0xbf810000u, 0xbf810000u};
|
||||
alignas(256) std::array<std::byte, 1024> FirstMemory{};
|
||||
alignas(256) std::array<std::byte, 1024> NoInstancesMemory{};
|
||||
alignas(256) std::array<std::byte, 1024> NoVerticesMemory{};
|
||||
alignas(256) std::array<std::byte, 1024> LastMemory{};
|
||||
alignas(256) std::array<std::uint32_t, 16> Records{3, 1, 0, 0, 3, 0, 0, 0, 0, 1, 0, 0, 3, 1, 0, 0};
|
||||
|
||||
struct Header {
|
||||
Shader shader{};
|
||||
ShaderUserData userData{};
|
||||
};
|
||||
|
||||
alignas(8) Header VertexHeader{};
|
||||
alignas(8) Header PixelHeader{};
|
||||
|
||||
void Registered(Header& header, const std::uint32_t* code, std::size_t bytes, std::uint8_t type) {
|
||||
header.shader.file_header = 0x34333231u;
|
||||
header.shader.version = 0x18u;
|
||||
header.shader.header_size = sizeof(Header);
|
||||
header.shader.shader_size = static_cast<std::uint32_t>(bytes);
|
||||
header.shader.code = code;
|
||||
header.shader.type = type;
|
||||
header.shader.user_data = &header.userData;
|
||||
AgcDriverRegisterShader_nid_postfix(&header.shader);
|
||||
}
|
||||
|
||||
void Append(std::vector<std::uint32_t>& commands, std::uint32_t opcode, std::initializer_list<std::uint32_t> payload) {
|
||||
commands.push_back(0xc0000000u | (static_cast<std::uint32_t>(payload.size() - 1) << 16u) | (opcode << 8u));
|
||||
commands.insert(commands.end(), payload);
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> DrawFour() {
|
||||
const auto vertex = reinterpret_cast<std::uintptr_t>(VertexCode.data());
|
||||
const auto pixel = reinterpret_cast<std::uintptr_t>(PixelCode.data());
|
||||
const auto records = reinterpret_cast<std::uintptr_t>(Records.data());
|
||||
const std::vector<std::pair<std::uint32_t, std::uint32_t>> context{
|
||||
{0x2d5, 0x2000},
|
||||
{0x1b6, 0}, {0x207, 0x01200000}, {0x200, 0}, {0x203, 0x800},
|
||||
{0x2dc, 0xaa00}, {0x2f8, 0}, {0x292, 2}, {0x293, 0},
|
||||
{0x80, 0}, {0x8d, 0}, {0x83, 0xffff}, {0x8c, 0xa},
|
||||
{0x2f9, 0x2d}, {0x313, 0x6000}, {0x30e, 0xffffffff}, {0x30f, 0xffffffff},
|
||||
{0x206, 0x43f}, {0x204, 0x80000}, {0x205, 0x240},
|
||||
{0x8e, 0xf}, {0x8f, 0xf}, {0x202, 0xcc0010},
|
||||
{0x1c4, 0}, {0x1c5, 9}, {0x1c3, 4}, {0x31c, 0x28028},
|
||||
{0x31b, 0}, {0x31d, 0}, {0x3b0, (63u << 14u) | 3u},
|
||||
{0x3b8, 0x9000000}, {0x1e0, 0},
|
||||
{0xc, 0}, {0xd, 0x40040},
|
||||
{0x81, 0x80000000}, {0x82, 0x40040},
|
||||
{0x90, 0x80000000}, {0x91, 0x40040},
|
||||
{0x94, 0x80000000}, {0x95, 0x40040},
|
||||
{0x10f, std::bit_cast<std::uint32_t>(32.0f)}, {0x110, std::bit_cast<std::uint32_t>(32.0f)},
|
||||
{0x111, std::bit_cast<std::uint32_t>(-2.0f)}, {0x112, std::bit_cast<std::uint32_t>(2.0f)},
|
||||
{0x113, std::bit_cast<std::uint32_t>(1.0f)}, {0x114, 0},
|
||||
{0xb4, 0}, {0xb5, std::bit_cast<std::uint32_t>(1.0f)},
|
||||
{0x2fe, 0}, {0x302, 0}, {0x306, 0}, {0x30a, 0},
|
||||
};
|
||||
const std::vector<std::pair<std::uint32_t, std::uint32_t>> shader{
|
||||
{0xc8, static_cast<std::uint32_t>(vertex >> 8u)}, {0xc9, static_cast<std::uint32_t>(vertex >> 40u)}, {0x8b, 1u << 1u},
|
||||
{0x008, static_cast<std::uint32_t>(pixel >> 8u)}, {0x009, static_cast<std::uint32_t>(pixel >> 40u)}, {0x00b, 1u << 1u}, {0x00c, std::bit_cast<std::uint32_t>(1.0f)},
|
||||
};
|
||||
std::vector<std::uint32_t> commands;
|
||||
Append(commands, 0x79, {0x242, 4});
|
||||
Append(commands, 0x79, {0x24a, 0});
|
||||
for (const auto& [offset, value] : context) Append(commands, 0x69, {offset, value});
|
||||
for (const auto& [offset, value] : shader) Append(commands, 0x76, {offset, value});
|
||||
Append(commands, 0x11, {1, static_cast<std::uint32_t>(records), static_cast<std::uint32_t>(records >> 32u)});
|
||||
const auto draw = [&](std::array<std::byte, 1024>& memory, std::uint32_t recordOffset) {
|
||||
const auto color = reinterpret_cast<std::uintptr_t>(memory.data());
|
||||
Append(commands, 0x69, {0x318, static_cast<std::uint32_t>(color >> 8u)});
|
||||
Append(commands, 0x69, {0x390, static_cast<std::uint32_t>(color >> 40u)});
|
||||
Append(commands, 0x76, {BaseVertexLocation, 0});
|
||||
Append(commands, 0x24, {recordOffset, BaseVertexLocation, 0, 2});
|
||||
};
|
||||
draw(FirstMemory, 0);
|
||||
draw(NoInstancesMemory, 16);
|
||||
draw(NoVerticesMemory, 32);
|
||||
draw(LastMemory, 48);
|
||||
return commands;
|
||||
}
|
||||
|
||||
std::size_t Covered(const std::array<std::byte, 1024>& memory) {
|
||||
return static_cast<std::size_t>(std::count_if(memory.begin(), memory.end(), [](std::byte value) { return value != Kept; }));
|
||||
}
|
||||
|
||||
void EmptyIndirectDrawsSkipTheirStages() {
|
||||
FirstMemory.fill(Kept);
|
||||
NoInstancesMemory.fill(Kept);
|
||||
NoVerticesMemory.fill(Kept);
|
||||
LastMemory.fill(Kept);
|
||||
Registered(VertexHeader, VertexCode.data(), sizeof(VertexCode), 2);
|
||||
Registered(PixelHeader, PixelCode.data(), sizeof(PixelCode), 1);
|
||||
auto commands = DrawFour();
|
||||
Packet packet{commands.data(), static_cast<std::uint32_t>(commands.size()), 0, {}};
|
||||
Require(sceAgcDriverSubmitDcb(&packet) == 0, "the draw submission failed");
|
||||
AgcDriverWaitIdle_nid_postfix();
|
||||
const auto first = Covered(FirstMemory);
|
||||
Require(first != 0, "the first indirect draw, whose record draws 3 vertices, covered nothing");
|
||||
Require(Covered(NoInstancesMemory) == 0, "the indirect draw whose record has no instances covered " + std::to_string(Covered(NoInstancesMemory)) + " bytes, expected none");
|
||||
Require(Covered(NoVerticesMemory) == 0, "the indirect draw whose record has no vertices covered " + std::to_string(Covered(NoVerticesMemory)) + " bytes, expected none");
|
||||
for (std::size_t index = 0; index < FirstMemory.size(); ++index) {
|
||||
Require(LastMemory[index] == FirstMemory[index], "byte " + std::to_string(index) + " of the indirect draw after the empty ones is " + std::to_string(static_cast<unsigned>(LastMemory[index])) + ", the first indirect draw wrote " + std::to_string(static_cast<unsigned>(FirstMemory[index])));
|
||||
}
|
||||
const auto empty = AgcDriver::Graphics::EmptyIndirectDrawCount();
|
||||
Require(empty == 2, std::to_string(empty) + " of the 2 indirect draws whose records draw nothing were skipped before their stages were built");
|
||||
const auto counts = AgcDriver::Graphics::IndirectDrawCounts();
|
||||
std::uint64_t cpu = 0;
|
||||
for (std::size_t path = 1; path < counts.size(); ++path) cpu += counts[path];
|
||||
Require(counts[0] == 0 && cpu == 2, "the drawing indirect draws took the GPU-side path " + std::to_string(counts[0]) + " times and the CPU path " + std::to_string(cpu) + " times, expected the CPU path twice");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
#ifdef _WIN32
|
||||
_putenv_s("APS5_PROFILE_DRAW", "1");
|
||||
#else
|
||||
setenv("APS5_PROFILE_DRAW", "1", 1);
|
||||
#endif
|
||||
{
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
}
|
||||
EmptyIndirectDrawsSkipTheirStages();
|
||||
AgcDriverShutdown_nid_postfix();
|
||||
std::puts("CPU-path indirect draws whose records draw nothing were skipped before their stages were built");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
AgcDriverShutdown_nid_postfix();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/ResidentMirror.hpp"
|
||||
#include "prx/libc/include/GuestAllocations.hpp"
|
||||
#include "prx/libc/include/GuestArena.hpp"
|
||||
#include "prx/libc/include/GuestWriteWatch.hpp"
|
||||
#include "Recompiler.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using AgcDriver::Graphics::Require;
|
||||
using ShaderRecompiler::ShaderStage;
|
||||
|
||||
constexpr std::uint32_t Threads = 64;
|
||||
constexpr std::uint64_t Block = 65536;
|
||||
constexpr std::uint64_t HeapBytes = 8u << 20u;
|
||||
constexpr std::uint64_t JoinedBytes = 16u << 20u;
|
||||
constexpr std::uint64_t ThreadStride = 16384;
|
||||
constexpr std::uint64_t StoreBlock = 3 * Block;
|
||||
constexpr std::uint32_t LateThread = 5;
|
||||
constexpr std::uint64_t ZeroedBlock = 2 * Block;
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 10> Code{
|
||||
0x3404008e, 0xdc388000, 0x04000002, 0x34060082, 0xbf8c3f70, 0xe0701000, 0x80010403, 0xe0701000, 0x80020303, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 7> CopyCode{0x34060082, 0xe0301000, 0x80000403, 0xbf8c3f70, 0xe0701000, 0x80010403, 0xbf810000};
|
||||
|
||||
class OutputBlock {
|
||||
public:
|
||||
OutputBlock() {
|
||||
#ifdef _WIN32
|
||||
block = static_cast<std::uint32_t*>(VirtualAlloc(nullptr, Block, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
|
||||
#else
|
||||
block = static_cast<std::uint32_t*>(std::aligned_alloc(Block, Block));
|
||||
#endif
|
||||
Require(block != nullptr, "resident heap mirror: cannot allocate the output block");
|
||||
GuestAllocations::Mutation().Add(block, Block, true, true, true);
|
||||
}
|
||||
|
||||
~OutputBlock() {
|
||||
GuestAllocations::Mutation().Remove(block);
|
||||
#ifdef _WIN32
|
||||
VirtualFree(block, 0, MEM_RELEASE);
|
||||
#else
|
||||
std::free(block);
|
||||
#endif
|
||||
}
|
||||
|
||||
OutputBlock(const OutputBlock&) = delete;
|
||||
OutputBlock& operator=(const OutputBlock&) = delete;
|
||||
|
||||
std::uint32_t* Data() const { return block; }
|
||||
|
||||
private:
|
||||
std::uint32_t* block = nullptr;
|
||||
};
|
||||
|
||||
class GuestHeap {
|
||||
public:
|
||||
explicit GuestHeap(bool writable, std::uint64_t ranges = 1, std::uint64_t rangeBytes = HeapBytes, void* at = nullptr) : ranges(ranges), rangeBytes(rangeBytes) {
|
||||
const auto bytes = rangeBytes * ranges;
|
||||
#ifdef _WIN32
|
||||
raw = at == nullptr ? GuestArena::GuestArenaAllocate_nid_postfix(bytes, Block)
|
||||
: GuestArena::GuestArenaAllocateAtOrAbove_nid_postfix(reinterpret_cast<std::uintptr_t>(at), bytes, Block);
|
||||
Require(raw != nullptr && (at == nullptr || raw == at), "resident heap mirror: cannot allocate the guest heap");
|
||||
GuestArena::GuestArenaCommit_nid_postfix(raw, bytes, PAGE_READWRITE, bytes);
|
||||
base = static_cast<std::uint8_t*>(raw);
|
||||
#else
|
||||
raw = mmap(at, bytes + Block, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE | (at != nullptr ? MAP_FIXED_NOREPLACE : 0), -1, 0);
|
||||
Require(raw != MAP_FAILED && (at == nullptr || raw == at), "resident heap mirror: cannot map the guest heap");
|
||||
base = reinterpret_cast<std::uint8_t*>((reinterpret_cast<std::uintptr_t>(raw) + Block - 1) / Block * Block);
|
||||
#endif
|
||||
GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(base, bytes);
|
||||
GuestAllocations::Mutation mutation;
|
||||
for (std::uint64_t range = 0; range < ranges; ++range) mutation.Add(base + range * rangeBytes, rangeBytes, true, writable, true);
|
||||
}
|
||||
|
||||
~GuestHeap() {
|
||||
{
|
||||
GuestAllocations::Mutation mutation;
|
||||
for (std::uint64_t range = 0; range < ranges; ++range) mutation.Remove(base + range * rangeBytes);
|
||||
}
|
||||
GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(base, rangeBytes * ranges);
|
||||
#ifdef _WIN32
|
||||
GuestArena::GuestArenaReset_nid_postfix(raw, rangeBytes * ranges);
|
||||
GuestArena::GuestArenaRelease_nid_postfix(raw, rangeBytes * ranges);
|
||||
#else
|
||||
munmap(raw, rangeBytes * ranges + Block);
|
||||
#endif
|
||||
}
|
||||
|
||||
GuestHeap(const GuestHeap&) = delete;
|
||||
GuestHeap& operator=(const GuestHeap&) = delete;
|
||||
|
||||
std::uint8_t* Data() const { return base; }
|
||||
void* Raw() const { return raw; }
|
||||
std::uint64_t Address() const { return reinterpret_cast<std::uintptr_t>(base); }
|
||||
|
||||
void Store(std::uint64_t offset, std::uint32_t value) const { std::memcpy(base + offset, &value, sizeof(value)); }
|
||||
|
||||
std::uint32_t Load(std::uint64_t offset) const {
|
||||
std::uint32_t value = 0;
|
||||
std::memcpy(&value, base + offset, sizeof(value));
|
||||
return value;
|
||||
}
|
||||
|
||||
private:
|
||||
std::uint64_t ranges = 1;
|
||||
std::uint64_t rangeBytes = HeapBytes;
|
||||
void* raw = nullptr;
|
||||
std::uint8_t* base = nullptr;
|
||||
};
|
||||
|
||||
struct ImportBudget {
|
||||
explicit ImportBudget(std::uint64_t bytes) { AgcDriver::Graphics::SetHostImportBudget(bytes); }
|
||||
~ImportBudget() { AgcDriver::Graphics::SetHostImportBudget(0); }
|
||||
ImportBudget(const ImportBudget&) = delete;
|
||||
ImportBudget& operator=(const ImportBudget&) = delete;
|
||||
};
|
||||
|
||||
std::array<std::uint32_t, 4> BufferDescriptor(std::uint64_t address, std::uint32_t bytes) {
|
||||
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu), bytes, 0x31016facu};
|
||||
}
|
||||
|
||||
std::string Hex(std::uint64_t value) {
|
||||
char text[24];
|
||||
std::snprintf(text, sizeof(text), "0x%llx", static_cast<unsigned long long>(value));
|
||||
return text;
|
||||
}
|
||||
|
||||
std::uint32_t Pattern(std::uint32_t thread) {
|
||||
return 0x5a000000u + thread * 0x00010203u + 1u;
|
||||
}
|
||||
|
||||
void Run(AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> code, const std::vector<std::uint32_t>& userData) {
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 1> regions{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
|
||||
const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1};
|
||||
ShaderRecompiler::RecompileRequest request{
|
||||
{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(code.data()), code, 0, {}},
|
||||
{32, 0, userData, compute, std::nullopt, std::nullopt, regions},
|
||||
device.Target(),
|
||||
{0, 0, 0, 128}
|
||||
};
|
||||
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 Dispatch(AgcDriver::VulkanDevice& device, std::uint64_t read, std::uint64_t written, const OutputBlock& output) {
|
||||
std::fill(output.Data(), output.Data() + Threads, 0xdeadbeefu);
|
||||
std::vector<std::uint32_t> userData(12, 0u);
|
||||
userData[0] = static_cast<std::uint32_t>(read);
|
||||
userData[1] = static_cast<std::uint32_t>(read >> 32u);
|
||||
const auto out = BufferDescriptor(reinterpret_cast<std::uintptr_t>(output.Data()), static_cast<std::uint32_t>(Threads * sizeof(std::uint32_t)));
|
||||
const auto store = BufferDescriptor(written + StoreBlock, static_cast<std::uint32_t>(Threads * sizeof(std::uint32_t)));
|
||||
std::copy(out.begin(), out.end(), userData.begin() + 4);
|
||||
std::copy(store.begin(), store.end(), userData.begin() + 8);
|
||||
Run(device, Code, userData);
|
||||
}
|
||||
|
||||
void Dispatch(AgcDriver::VulkanDevice& device, const GuestHeap& read, const GuestHeap& written, const OutputBlock& output) {
|
||||
Dispatch(device, read.Address(), written.Address(), output);
|
||||
}
|
||||
|
||||
void Copy(AgcDriver::VulkanDevice& device, std::uint64_t source, const OutputBlock& output) {
|
||||
std::fill(output.Data(), output.Data() + Threads, 0xdeadbeefu);
|
||||
std::vector<std::uint32_t> userData(8, 0u);
|
||||
const auto in = BufferDescriptor(source, static_cast<std::uint32_t>(Threads * sizeof(std::uint32_t)));
|
||||
const auto out = BufferDescriptor(reinterpret_cast<std::uintptr_t>(output.Data()), static_cast<std::uint32_t>(Threads * sizeof(std::uint32_t)));
|
||||
std::copy(in.begin(), in.end(), userData.begin());
|
||||
std::copy(out.begin(), out.end(), userData.begin() + 4);
|
||||
Run(device, CopyCode, userData);
|
||||
}
|
||||
|
||||
void CheckReads(const OutputBlock& output, const std::string& step, bool late) {
|
||||
for (std::uint32_t thread = 0; thread < Threads; ++thread) {
|
||||
const auto expected = thread * ThreadStride < Block ? Pattern(thread) : late && thread == LateThread ? Pattern(thread) : 0u;
|
||||
Require(output.Data()[thread] == expected, "resident heap mirror: " + step + ": thread " + std::to_string(thread) + " read " + Hex(output.Data()[thread]) + " at heap offset " + Hex(thread * ThreadStride) + ", expected " + Hex(expected));
|
||||
}
|
||||
}
|
||||
|
||||
void CheckStores(const GuestHeap& written, const std::string& step) {
|
||||
for (std::uint32_t thread = 0; thread < Threads; ++thread) {
|
||||
const auto value = written.Load(StoreBlock + thread * sizeof(std::uint32_t));
|
||||
Require(value == thread * 4u, "resident heap mirror: " + step + ": the GPU store of thread " + std::to_string(thread) + " into a never-written heap block reached the guest as " + Hex(value) + ", expected " + Hex(thread * 4u));
|
||||
}
|
||||
}
|
||||
|
||||
void CheckHeld(std::uint64_t before, std::uint64_t written, const std::string& step) {
|
||||
const auto held = AgcDriver::Graphics::MirrorCounters().heapBytes - before;
|
||||
Require(held == written, "resident heap mirror: " + step + ": the heap mirrors of two " + std::to_string(HeapBytes >> 10u) + " KiB heaps hold " + std::to_string(held >> 10u) + " KiB of device memory; the guest and the GPU wrote " + std::to_string(written >> 10u) + " KiB of them");
|
||||
}
|
||||
|
||||
void ResidentTests(AgcDriver::VulkanDevice& device) {
|
||||
GuestHeap read(false);
|
||||
GuestHeap written(true);
|
||||
const OutputBlock output;
|
||||
for (std::uint32_t thread = 0; thread * ThreadStride < Block; ++thread) read.Store(thread * ThreadStride, Pattern(thread));
|
||||
written.Store(16, 0x77u);
|
||||
const ImportBudget budget(AgcDriver::Graphics::HostImportLiveBytes() + Block);
|
||||
const auto before = AgcDriver::Graphics::MirrorCounters();
|
||||
Dispatch(device, read, written, output);
|
||||
const auto made = AgcDriver::Graphics::MirrorCounters();
|
||||
Require(made.heapMirrors == before.heapMirrors + 2, "resident heap mirror: the two heaps were not heap mirrored (" + std::to_string(made.heapMirrors - before.heapMirrors) + " mirrors made)");
|
||||
CheckHeld(before.heapBytes, 3 * Block, "first dispatch");
|
||||
CheckReads(output, "first dispatch", false);
|
||||
CheckStores(written, "first dispatch");
|
||||
Require(written.Load(16) == 0x77u, "resident heap mirror: the written heap's guest bytes changed");
|
||||
read.Store(LateThread * ThreadStride, Pattern(LateThread));
|
||||
std::memset(read.Data() + ZeroedBlock, 0, Block);
|
||||
Dispatch(device, read, written, output);
|
||||
CheckHeld(before.heapBytes, 4 * Block, "after a guest write into a never-written block and zeros written into another");
|
||||
CheckReads(output, "after a guest write into a never-written block", true);
|
||||
const auto steady = AgcDriver::Graphics::MirrorCounters();
|
||||
Dispatch(device, read, written, output);
|
||||
const auto again = AgcDriver::Graphics::MirrorCounters();
|
||||
Require(again.rebuilds == steady.rebuilds && again.heapBytes == steady.heapBytes, "resident heap mirror: an unchanged dispatch rebuilt or grew the heap mirrors");
|
||||
CheckReads(output, "unchanged dispatch", true);
|
||||
CheckStores(written, "unchanged dispatch");
|
||||
}
|
||||
|
||||
struct AddressSpaceLimit {
|
||||
explicit AddressSpaceLimit(std::uint64_t bytes) { AgcDriver::Graphics::SetResidentAddressSpaceLimit(bytes); }
|
||||
~AddressSpaceLimit() { AgcDriver::Graphics::SetResidentAddressSpaceLimit(0); }
|
||||
AddressSpaceLimit(const AddressSpaceLimit&) = delete;
|
||||
AddressSpaceLimit& operator=(const AddressSpaceLimit&) = delete;
|
||||
};
|
||||
|
||||
void CopiedTests(AgcDriver::VulkanDevice& device) {
|
||||
const AddressSpaceLimit limit(1);
|
||||
GuestHeap read(false);
|
||||
GuestHeap written(true);
|
||||
const OutputBlock output;
|
||||
for (std::uint32_t thread = 0; thread * ThreadStride < Block; ++thread) read.Store(thread * ThreadStride, Pattern(thread));
|
||||
written.Store(16, 0x77u);
|
||||
const ImportBudget budget(AgcDriver::Graphics::HostImportLiveBytes() + Block);
|
||||
const auto before = AgcDriver::Graphics::MirrorCounters();
|
||||
Dispatch(device, read, written, output);
|
||||
const auto made = AgcDriver::Graphics::MirrorCounters();
|
||||
Require(made.heapMirrors == before.heapMirrors + 2, "copied heap mirror: the two heaps were not heap mirrored without sparse address space (" + std::to_string(made.heapMirrors - before.heapMirrors) + " mirrors made)");
|
||||
Require(made.heapBytes - before.heapBytes == 2 * HeapBytes, "copied heap mirror: two " + std::to_string(HeapBytes >> 10u) + " KiB heaps without sparse address space hold " + std::to_string((made.heapBytes - before.heapBytes) >> 10u) + " KiB, expected whole copies");
|
||||
CheckReads(output, "copied, first dispatch", false);
|
||||
CheckStores(written, "copied, first dispatch");
|
||||
Require(written.Load(16) == 0x77u, "copied heap mirror: the written heap's guest bytes changed");
|
||||
read.Store(LateThread * ThreadStride, Pattern(LateThread));
|
||||
Dispatch(device, read, written, output);
|
||||
CheckReads(output, "copied, after a guest write", true);
|
||||
CheckStores(written, "copied, after a guest write");
|
||||
}
|
||||
|
||||
void ReusedAddressTests(AgcDriver::VulkanDevice& device) {
|
||||
GuestHeap written(true);
|
||||
const OutputBlock output;
|
||||
const ImportBudget budget(AgcDriver::Graphics::HostImportLiveBytes() + Block);
|
||||
void* at = nullptr;
|
||||
std::uint64_t address = 0;
|
||||
{
|
||||
GuestHeap first(false);
|
||||
for (std::uint32_t thread = 0; thread * ThreadStride < Block; ++thread) first.Store(thread * ThreadStride, Pattern(thread));
|
||||
Dispatch(device, first, written, output);
|
||||
CheckReads(output, "the first heap", false);
|
||||
at = first.Raw();
|
||||
address = first.Address();
|
||||
}
|
||||
GuestHeap second(false, 1, HeapBytes, at);
|
||||
Require(second.Address() == address, "resident heap mirror: the second heap did not take the first heap's address");
|
||||
for (std::uint32_t thread = 0; thread * ThreadStride < Block; ++thread) second.Store(thread * ThreadStride, ~Pattern(thread));
|
||||
Dispatch(device, second, written, output);
|
||||
for (std::uint32_t thread = 0; thread < Threads; ++thread) {
|
||||
const auto expected = thread * ThreadStride < Block ? ~Pattern(thread) : 0u;
|
||||
Require(output.Data()[thread] == expected, "resident heap mirror: a heap mapped where a mirrored heap was freed: thread " + std::to_string(thread) + " read " + Hex(output.Data()[thread]) + ", expected " + Hex(expected));
|
||||
}
|
||||
const auto held = AgcDriver::Graphics::HeapMirrorsWithin(address, address + HeapBytes);
|
||||
Require(held.mirrors == 1 && held.bytes == Block, "resident heap mirror: a heap mapped where a mirrored heap was freed holds " + std::to_string(held.mirrors) + " live heap mirrors (" + std::to_string(held.bytes >> 10u) + " KiB), expected 1 (64 KiB)");
|
||||
}
|
||||
|
||||
void JoinedImportTests(AgcDriver::VulkanDevice& device) {
|
||||
GuestHeap pair(true, 2, JoinedBytes);
|
||||
const OutputBlock output;
|
||||
const auto begin = pair.Address();
|
||||
const auto end = begin + 2 * JoinedBytes;
|
||||
for (std::uint32_t thread = 0; thread * ThreadStride < Block; ++thread) pair.Store(thread * ThreadStride, Pattern(thread));
|
||||
pair.Store(JoinedBytes - sizeof(std::uint32_t), 0x41u);
|
||||
pair.Store(JoinedBytes, 0x42u);
|
||||
const auto before = AgcDriver::Graphics::HeapMirrorsWithin(begin, end);
|
||||
{
|
||||
const ImportBudget budget(AgcDriver::Graphics::HostImportLiveBytes() + Block);
|
||||
Dispatch(device, begin, begin + JoinedBytes, output);
|
||||
CheckReads(output, "two adjacent heaps", false);
|
||||
}
|
||||
const auto mirrored = AgcDriver::Graphics::HeapMirrorsWithin(begin, end);
|
||||
Require(before.mirrors == 0 && mirrored.mirrors == 2 && mirrored.bytes == 4 * Block, "resident heap mirror: two adjacent heaps hold " + std::to_string(before.mirrors) + " heap mirrors before the first dispatch and " + std::to_string(mirrored.mirrors) + " (" + std::to_string(mirrored.bytes >> 10u) + " KiB) after it, expected 0 and 2 (256 KiB); " + std::to_string(AgcDriver::Graphics::HostImportLiveBytes() >> 10u) + " KiB host imports live");
|
||||
const auto live = AgcDriver::Graphics::HostImportLiveBytes();
|
||||
Copy(device, begin + JoinedBytes - Threads * sizeof(std::uint32_t) / 2, output);
|
||||
Require(output.Data()[Threads / 2 - 1] == 0x41u && output.Data()[Threads / 2] == 0x42u, "resident heap mirror: a view across two adjacent heaps read " + Hex(output.Data()[Threads / 2 - 1]) + " " + Hex(output.Data()[Threads / 2]) + ", expected 0x41 0x42");
|
||||
const auto joined = AgcDriver::Graphics::HostImportLiveBytes() - live;
|
||||
Require(joined >= 2 * JoinedBytes, "resident heap mirror: a view across two adjacent heaps made no joined host import (" + std::to_string(joined >> 10u) + " KiB imported)");
|
||||
Dispatch(device, begin, begin + JoinedBytes, output);
|
||||
CheckReads(output, "after the joined import", false);
|
||||
const auto released = AgcDriver::Graphics::HeapMirrorsWithin(begin, end);
|
||||
Require(released.mirrors == 0 && released.bytes == 0, "resident heap mirror: a joined host import over two heap-mirrored heaps left " + std::to_string(released.mirrors) + " of their " + std::to_string(mirrored.mirrors) + " heap mirrors and " + std::to_string(released.bytes >> 10u) + " of " + std::to_string(mirrored.bytes >> 10u) + " KiB held");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
#ifdef _WIN32
|
||||
const bool watched = GuestArena::GuestArenaWriteWatched_nid_postfix();
|
||||
#else
|
||||
const bool watched = GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix();
|
||||
#endif
|
||||
if (!watched) {
|
||||
std::puts("skipped, no guest write watch: heap mirrors need one");
|
||||
return VulkanTestSkipped;
|
||||
}
|
||||
ResidentTests(*device);
|
||||
ReusedAddressTests(*device);
|
||||
JoinedImportTests(*device);
|
||||
CopiedTests(*device);
|
||||
std::puts("resident heap mirror tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp)
|
||||
target_include_directories(libSceLibreSsl PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(libSceLibreSsl PRIVATE mbedcrypto)
|
||||
configure_windows_unwind(libSceLibreSsl)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
@@ -13,4 +14,15 @@ if(BUILD_TESTING)
|
||||
set_tests_properties(guest_libressl_digest PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceLibreSsl>")
|
||||
endif()
|
||||
|
||||
add_test_executable(guest_libressl_des_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslDes.cpp)
|
||||
target_include_directories(guest_libressl_des_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_libressl_des_tests PRIVATE libSceLibreSsl libc)
|
||||
configure_windows_unwind(guest_libressl_des_tests)
|
||||
add_test(NAME guest_libressl_des COMMAND guest_libressl_des_tests)
|
||||
set_tests_properties(guest_libressl_des PROPERTIES TIMEOUT 20)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_libressl_des PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceLibreSsl>")
|
||||
endif()
|
||||
endif()
|
||||
@@ -1,7 +1,17 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include "mbedtls/des.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::size_t DesScheduleBytes = 128;
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
@@ -301,19 +311,28 @@ int APS5_VABI CRYPTO_set_mem_functions_nid_postfix(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI DES_ecb_encrypt_nid_postfix(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
void APS5_VABI DES_ecb_encrypt_nid_postfix(const unsigned char* input, unsigned char* output, const unsigned char* schedule, int enc) {
|
||||
if (input == nullptr || output == nullptr || schedule == nullptr) APS5_INVALID_ARG_EX;
|
||||
mbedtls_des_context context;
|
||||
mbedtls_des_init(&context);
|
||||
unsigned char block[MBEDTLS_DES_KEY_SIZE];
|
||||
std::memcpy(block, input, sizeof(block));
|
||||
int status = enc ? mbedtls_des_setkey_enc(&context, schedule) : mbedtls_des_setkey_dec(&context, schedule);
|
||||
if (status == 0) status = mbedtls_des_crypt_ecb(&context, block, output);
|
||||
mbedtls_des_free(&context);
|
||||
if (status != 0) throw std::runtime_error("DES_ecb_encrypt: mbedtls failed");
|
||||
}
|
||||
|
||||
int APS5_VABI DES_set_key_nid_postfix(const unsigned char* key, unsigned char* schedule) {
|
||||
if (key == nullptr || schedule == nullptr) APS5_INVALID_ARG_EX;
|
||||
std::memset(schedule, 0, DesScheduleBytes);
|
||||
std::memcpy(schedule, key, MBEDTLS_DES_KEY_SIZE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI DES_set_key_nid_postfix(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI DES_set_odd_parity_nid_postfix(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
void APS5_VABI DES_set_odd_parity_nid_postfix(unsigned char* key) {
|
||||
if (key == nullptr) APS5_INVALID_ARG_EX;
|
||||
mbedtls_des_key_set_parity(key);
|
||||
}
|
||||
|
||||
int APS5_VABI DTLSv1_client_method_nid_postfix(void) {
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <stdexcept>
|
||||
|
||||
using DesBlock = std::array<unsigned char, 8>;
|
||||
using DesSchedule = std::array<unsigned char, 128>;
|
||||
|
||||
extern "C" {
|
||||
void APS5_VABI DES_ecb_encrypt_nid_postfix(const unsigned char*, unsigned char*, const unsigned char*, int);
|
||||
int APS5_VABI DES_set_key_nid_postfix(const unsigned char*, unsigned char*);
|
||||
void APS5_VABI DES_set_odd_parity_nid_postfix(unsigned char*);
|
||||
}
|
||||
|
||||
static void Require(bool value) { if (!value) std::abort(); }
|
||||
|
||||
template<typename TAction>
|
||||
static void RequireThrows(TAction action) {
|
||||
try { action(); } catch (const std::invalid_argument&) { return; }
|
||||
std::abort();
|
||||
}
|
||||
|
||||
static DesBlock Block(std::uint64_t value) {
|
||||
DesBlock block{};
|
||||
for (int index = 0; index < 8; ++index) block[index] = static_cast<unsigned char>(value >> (56 - 8 * index));
|
||||
return block;
|
||||
}
|
||||
|
||||
static void CheckVector(std::uint64_t key, std::uint64_t plain, std::uint64_t cipher) {
|
||||
const auto keyBlock = Block(key);
|
||||
DesSchedule schedule{};
|
||||
schedule.fill(0xaa);
|
||||
Require(DES_set_key_nid_postfix(keyBlock.data(), schedule.data()) == 0);
|
||||
const auto plainBlock = Block(plain);
|
||||
DesBlock output{};
|
||||
DES_ecb_encrypt_nid_postfix(plainBlock.data(), output.data(), schedule.data(), 1);
|
||||
Require(output == Block(cipher));
|
||||
DES_ecb_encrypt_nid_postfix(output.data(), output.data(), schedule.data(), 0);
|
||||
Require(output == plainBlock);
|
||||
DesBlock again{};
|
||||
DES_ecb_encrypt_nid_postfix(plainBlock.data(), again.data(), schedule.data(), 7);
|
||||
Require(again == Block(cipher));
|
||||
}
|
||||
|
||||
static void CheckParity() {
|
||||
DesBlock key{0x00, 0x01, 0x02, 0x03, 0x12, 0x13, 0xfe, 0xff};
|
||||
DES_set_odd_parity_nid_postfix(key.data());
|
||||
Require(key == (DesBlock{0x01, 0x01, 0x02, 0x02, 0x13, 0x13, 0xfe, 0xfe}));
|
||||
for (int value = 0; value < 256; ++value) {
|
||||
DesBlock single{};
|
||||
single.fill(static_cast<unsigned char>(value));
|
||||
DES_set_odd_parity_nid_postfix(single.data());
|
||||
Require((single[0] & 0xfe) == (value & 0xfe) && std::popcount(single[0]) % 2 == 1);
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
CheckVector(0x133457799bbcdff1, 0x0123456789abcdef, 0x85e813540f0ab405);
|
||||
CheckVector(0x0e329232ea6d0d73, 0x8787878787878787, 0x0000000000000000);
|
||||
CheckVector(0x0101010101010101, 0x8000000000000000, 0x95f8a5e5dd31d900);
|
||||
CheckParity();
|
||||
DesSchedule schedule{};
|
||||
const auto weak = Block(0x0101010101010101);
|
||||
Require(DES_set_key_nid_postfix(weak.data(), schedule.data()) == 0);
|
||||
const auto badParity = Block(0x0000000000000000);
|
||||
Require(DES_set_key_nid_postfix(badParity.data(), schedule.data()) == 0);
|
||||
RequireThrows([&] { DES_set_key_nid_postfix(nullptr, schedule.data()); });
|
||||
RequireThrows([&] { DES_ecb_encrypt_nid_postfix(weak.data(), schedule.data(), nullptr, 1); });
|
||||
RequireThrows([] { DES_set_odd_parity_nid_postfix(nullptr); });
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user