test(agc): reproduce stale bytes in sparse buffer holes
This commit is contained in:
parent
7a84b22267
commit
8404d62be9
|
|
@ -1050,6 +1050,12 @@ if(BUILD_TESTING)
|
|||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
endforeach()
|
||||
add_test(NAME agc_sparse_buffer_holes COMMAND agc_driver_graphics_tests --sparse-buffer-holes)
|
||||
set_tests_properties(agc_sparse_buffer_holes PROPERTIES TIMEOUT 30)
|
||||
set_tests_properties(agc_driver_sparse_buffer_holes PROPERTIES TIMEOUT 30)
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_sparse_buffer_holes PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test(NAME agc_driver_emulated_color_compare_srgb_decode COMMAND agc_driver_emulated_color_compare_tests)
|
||||
set_tests_properties(agc_driver_emulated_color_compare_srgb_decode PROPERTIES
|
||||
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_SRGB_SHADER_DECODE=1"
|
||||
|
|
|
|||
|
|
@ -746,6 +746,69 @@ void sparseHeapTests(const Context& context, const BdaTestAccess& access) {
|
|||
releaseSparse(block, reserved);
|
||||
}
|
||||
|
||||
bool RunSparseBufferHoleTests(const Context& context, const BdaTestAccess& access) {
|
||||
constexpr std::size_t unit = 65536;
|
||||
constexpr std::size_t copied = 3 * unit;
|
||||
bool passed = true;
|
||||
for (const bool writable : {false, true}) {
|
||||
for (const std::string shape : {"interior", "shortened", "committed"}) {
|
||||
const auto name = std::string(writable ? "writable " : "readable ") + shape;
|
||||
try {
|
||||
const bool whole = shape == "committed";
|
||||
const auto reserved = shape == "shortened" ? 4 * unit : copied;
|
||||
struct Reservation {
|
||||
std::size_t bytes;
|
||||
std::byte* data;
|
||||
~Reservation() { releaseSparse(data, bytes); }
|
||||
} reservation{reserved, reserveSparse(reserved, whole ? reserved : unit)};
|
||||
if (!whole) {
|
||||
#ifdef _WIN32
|
||||
Require(VirtualAlloc(reservation.data + 2 * unit, unit, MEM_COMMIT, PAGE_READWRITE) != nullptr, "cannot commit the sparse heap tail");
|
||||
#else
|
||||
Require(mprotect(reservation.data + 2 * unit, unit, PROT_READ | PROT_WRITE) == 0, "cannot commit the sparse heap tail");
|
||||
#endif
|
||||
}
|
||||
std::memset(reservation.data, 0x31, unit);
|
||||
if (whole) std::memset(reservation.data + unit, 0x3c, unit);
|
||||
std::memset(reservation.data + 2 * unit, 0x72, unit);
|
||||
VkBuffer seeded = VK_NULL_HANDLE;
|
||||
{
|
||||
Buffer seed(context, copied, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
|
||||
std::fill(seed.Bytes().begin(), seed.Bytes().end(), std::byte{0xa5});
|
||||
seeded = seed.Handle();
|
||||
}
|
||||
const auto base = reinterpret_cast<std::uintptr_t>(reservation.data);
|
||||
GuestBufferMemory memory(context);
|
||||
if (writable) memory.AddWritable(base, reserved);
|
||||
else memory.AddReadable(base, reserved);
|
||||
memory.Upload(false);
|
||||
std::uint32_t adjustment = 0;
|
||||
const auto view = memory.Descriptor(base, reserved, adjustment);
|
||||
Require(view.buffer == seeded, "the sparse upload did not reuse the poisoned buffer");
|
||||
Require(adjustment == 0 && view.offset == 0 && view.range == copied && memory.CopiedBytes() == copied, "the sparse upload has the wrong allocated span");
|
||||
auto bytes = access.bytes(view.buffer).first(copied);
|
||||
const auto equals = [](std::span<const std::byte> part, std::byte value) {
|
||||
return std::all_of(part.begin(), part.end(), [value](std::byte byte) { return byte == value; });
|
||||
};
|
||||
Require(equals(bytes.first(unit), std::byte{0x31}) && equals(bytes.subspan(2 * unit, unit), std::byte{0x72}), "committed GPU bytes changed during upload");
|
||||
const bool holeZero = equals(bytes.subspan(unit, unit), whole ? std::byte{0x3c} : std::byte{});
|
||||
bytes[8] = std::byte{0x19};
|
||||
bytes[unit + 8] = std::byte{0xd4};
|
||||
bytes[2 * unit + 8] = std::byte{0x27};
|
||||
memory.WriteBack();
|
||||
Require(reservation.data[8] == (writable ? std::byte{0x19} : std::byte{0x31}) && reservation.data[2 * unit + 8] == (writable ? std::byte{0x27} : std::byte{0x72}), "sparse write-back did not preserve its writable ranges");
|
||||
if (whole) Require(reservation.data[unit + 8] == (writable ? std::byte{0xd4} : std::byte{0x3c}), "committed middle write-back changed permissions");
|
||||
Require(holeZero, "uncommitted GPU bytes retain prior buffer contents");
|
||||
std::cout << "sparse buffer holes: " << name << " passed\n";
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << "sparse buffer holes: " << name << ": " << error.what() << '\n';
|
||||
passed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return passed;
|
||||
}
|
||||
|
||||
void hostWriteWatchExclusionTests() {
|
||||
#ifdef _WIN32
|
||||
if (AgcDriver::GuestMemory::WriteWatched()) {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ struct BdaTestAccess {
|
|||
};
|
||||
|
||||
void RunBdaResourceTests(const AgcDriver::Graphics::Context& context, const BdaTestAccess& access);
|
||||
bool RunSparseBufferHoleTests(const AgcDriver::Graphics::Context& context, const BdaTestAccess& access);
|
||||
void RunGuestAllocationTests();
|
||||
void RunGuestLeaseWaitTests();
|
||||
void RunUnmappedGapTests();
|
||||
|
|
|
|||
|
|
@ -4536,6 +4536,19 @@ int main(int argc, char** argv) {
|
|||
setenv("APS5_HEAP_MIRROR_MIB", "4", 1);
|
||||
#endif
|
||||
try {
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--sparse-buffer-holes") {
|
||||
bool passed = false;
|
||||
{
|
||||
auto context = mockContext();
|
||||
context.limits.maxStorageBufferRange = 1u << 27;
|
||||
passed = RunSparseBufferHoleTests(context, {
|
||||
[](VkBuffer buffer) -> std::span<std::byte> { return mockBufferMemory(buffer); }, {}, {}, {}, {}
|
||||
});
|
||||
}
|
||||
Require(mock.live == 0, "sparse buffer hole tests leaked Vulkan objects");
|
||||
std::cout << "sparse buffer hole tests " << (passed ? "passed" : "failed") << '\n';
|
||||
return passed ? 0 : 1;
|
||||
}
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--descriptor-layout-flags") {
|
||||
descriptorLayoutFlagTests();
|
||||
std::cout << "Descriptor layout native and captured binding flags passed\n";
|
||||
|
|
|
|||
|
|
@ -0,0 +1,396 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Resources.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#include <SDL_loadso.h>
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <initializer_list>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace AgcDriver::Graphics;
|
||||
|
||||
constexpr std::size_t PageBytes = 65536;
|
||||
constexpr std::size_t BufferBytes = 3 * PageBytes;
|
||||
constexpr std::size_t ReservedBytes = 4 * PageBytes;
|
||||
constexpr std::uint32_t WorkgroupSize = 64;
|
||||
constexpr std::array<std::uint32_t, 3> Patterns{0x11223344u, 0x8899aabbu, 0x55667788u};
|
||||
|
||||
class Device {
|
||||
public:
|
||||
Device() {
|
||||
#ifdef _WIN32
|
||||
library = SDL_LoadObject("vulkan-1.dll");
|
||||
#else
|
||||
library = SDL_LoadObject("libvulkan.so.1");
|
||||
#endif
|
||||
Require(library != nullptr, "cannot load Vulkan");
|
||||
try {
|
||||
instanceProc = reinterpret_cast<PFN_vkGetInstanceProcAddr>(SDL_LoadFunction(library, "vkGetInstanceProcAddr"));
|
||||
Require(instanceProc != nullptr, "missing Vulkan instance resolver");
|
||||
VkApplicationInfo application{VK_STRUCTURE_TYPE_APPLICATION_INFO};
|
||||
application.apiVersion = VK_API_VERSION_1_1;
|
||||
VkInstanceCreateInfo info{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO};
|
||||
info.pApplicationInfo = &application;
|
||||
Check(function<PFN_vkCreateInstance>("vkCreateInstance")(&info, nullptr, &instance), "vkCreateInstance");
|
||||
std::uint32_t count = 0;
|
||||
const auto enumerate = function<PFN_vkEnumeratePhysicalDevices>("vkEnumeratePhysicalDevices");
|
||||
Check(enumerate(instance, &count, nullptr), "vkEnumeratePhysicalDevices");
|
||||
Require(count != 0, "no Vulkan device");
|
||||
std::vector<VkPhysicalDevice> devices(count);
|
||||
Check(enumerate(instance, &count, devices.data()), "vkEnumeratePhysicalDevices");
|
||||
const auto properties = function<PFN_vkGetPhysicalDeviceProperties>("vkGetPhysicalDeviceProperties");
|
||||
int rank = -1;
|
||||
for (const auto candidate : devices) {
|
||||
VkPhysicalDeviceProperties described{};
|
||||
properties(candidate, &described);
|
||||
if (described.apiVersion < VK_API_VERSION_1_1) continue;
|
||||
const int candidateRank = described.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU ? 2 : described.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU ? 1 : 0;
|
||||
if (candidateRank <= rank) continue;
|
||||
context.physical = candidate;
|
||||
rank = candidateRank;
|
||||
}
|
||||
Require(context.physical != VK_NULL_HANDLE, "no Vulkan 1.1 device");
|
||||
VkPhysicalDeviceSubgroupProperties subgroup{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES};
|
||||
VkPhysicalDeviceProperties2 selected{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &subgroup};
|
||||
function<PFN_vkGetPhysicalDeviceProperties2>("vkGetPhysicalDeviceProperties2")(context.physical, &selected);
|
||||
context.limits = selected.properties.limits;
|
||||
const auto queues = function<PFN_vkGetPhysicalDeviceQueueFamilyProperties>("vkGetPhysicalDeviceQueueFamilyProperties");
|
||||
queues(context.physical, &count, nullptr);
|
||||
std::vector<VkQueueFamilyProperties> families(count);
|
||||
queues(context.physical, &count, families.data());
|
||||
std::uint32_t family = 0;
|
||||
while (family < count && (families[family].queueFlags & VK_QUEUE_COMPUTE_BIT) == 0) ++family;
|
||||
Require(family < count, "no Vulkan compute queue");
|
||||
std::printf("Selected GPU name=%s vendor=0x%x device=0x%x subgroup=%u\n", selected.properties.deviceName, selected.properties.vendorID, selected.properties.deviceID, subgroup.subgroupSize);
|
||||
std::printf("sparse buffer holes GPU: api=%u.%u.%u; queueFamily=%u\n", VK_API_VERSION_MAJOR(selected.properties.apiVersion), VK_API_VERSION_MINOR(selected.properties.apiVersion), VK_API_VERSION_PATCH(selected.properties.apiVersion), family);
|
||||
const float priority = 1;
|
||||
VkDeviceQueueCreateInfo queue{VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO};
|
||||
queue.queueFamilyIndex = family;
|
||||
queue.queueCount = 1;
|
||||
queue.pQueuePriorities = &priority;
|
||||
VkDeviceCreateInfo device{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO};
|
||||
device.queueCreateInfoCount = 1;
|
||||
device.pQueueCreateInfos = &queue;
|
||||
Check(function<PFN_vkCreateDevice>("vkCreateDevice")(context.physical, &device, nullptr, &context.device), "vkCreateDevice");
|
||||
context.deviceProc = function<PFN_vkGetDeviceProcAddr>("vkGetDeviceProcAddr");
|
||||
function<PFN_vkGetPhysicalDeviceMemoryProperties>("vkGetPhysicalDeviceMemoryProperties")(context.physical, &context.memory);
|
||||
context.Function<PFN_vkGetDeviceQueue>("vkGetDeviceQueue")(context.device, family, 0, &context.queue);
|
||||
VkCommandPoolCreateInfo pool{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO};
|
||||
pool.queueFamilyIndex = family;
|
||||
Check(context.Function<PFN_vkCreateCommandPool>("vkCreateCommandPool")(context.device, &pool, nullptr, &context.pool), "vkCreateCommandPool");
|
||||
} catch (...) {
|
||||
release();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
~Device() { release(); }
|
||||
Device(const Device&) = delete;
|
||||
Device& operator=(const Device&) = delete;
|
||||
const Context& GetContext() const { return context; }
|
||||
|
||||
private:
|
||||
template<typename TFunction>
|
||||
TFunction function(const char* name) const {
|
||||
const auto result = reinterpret_cast<TFunction>(instanceProc(instance, name));
|
||||
Require(result != nullptr, name);
|
||||
return result;
|
||||
}
|
||||
|
||||
void release() noexcept {
|
||||
if (context.pool != VK_NULL_HANDLE) context.Function<PFN_vkDestroyCommandPool>("vkDestroyCommandPool")(context.device, context.pool, nullptr);
|
||||
context.bufferPool.reset();
|
||||
if (context.device != VK_NULL_HANDLE) function<PFN_vkDestroyDevice>("vkDestroyDevice")(context.device, nullptr);
|
||||
if (instance != VK_NULL_HANDLE) function<PFN_vkDestroyInstance>("vkDestroyInstance")(instance, nullptr);
|
||||
if (library != nullptr) SDL_UnloadObject(library);
|
||||
}
|
||||
|
||||
void* library = nullptr;
|
||||
PFN_vkGetInstanceProcAddr instanceProc = nullptr;
|
||||
VkInstance instance = VK_NULL_HANDLE;
|
||||
Context context{};
|
||||
};
|
||||
|
||||
class GuestPages {
|
||||
public:
|
||||
explicit GuestPages(bool committed) {
|
||||
#ifdef _WIN32
|
||||
memory = static_cast<std::byte*>(VirtualAlloc(nullptr, ReservedBytes, MEM_RESERVE, PAGE_NOACCESS));
|
||||
#else
|
||||
const auto block = mmap(nullptr, ReservedBytes, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
if (block != MAP_FAILED) memory = static_cast<std::byte*>(block);
|
||||
#endif
|
||||
Require(memory != nullptr, "cannot reserve sparse guest pages");
|
||||
try {
|
||||
for (std::size_t page = 0; page < Patterns.size(); ++page) {
|
||||
if (page == 1 && !committed) continue;
|
||||
auto* begin = memory + page * PageBytes;
|
||||
#ifdef _WIN32
|
||||
Require(VirtualAlloc(begin, PageBytes, MEM_COMMIT, PAGE_READWRITE) == begin, "cannot commit sparse guest page");
|
||||
#else
|
||||
Require(mprotect(begin, PageBytes, PROT_READ | PROT_WRITE) == 0, "cannot commit sparse guest page");
|
||||
#endif
|
||||
for (std::size_t offset = 0; offset < PageBytes; offset += sizeof(std::uint32_t)) std::memcpy(begin + offset, &Patterns[page], sizeof(std::uint32_t));
|
||||
}
|
||||
} catch (...) {
|
||||
release();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
~GuestPages() { release(); }
|
||||
GuestPages(const GuestPages&) = delete;
|
||||
GuestPages& operator=(const GuestPages&) = delete;
|
||||
std::uint64_t Address() const { return reinterpret_cast<std::uint64_t>(memory); }
|
||||
|
||||
private:
|
||||
void release() noexcept {
|
||||
#ifdef _WIN32
|
||||
if (memory != nullptr) VirtualFree(memory, 0, MEM_RELEASE);
|
||||
#else
|
||||
if (memory != nullptr) munmap(memory, ReservedBytes);
|
||||
#endif
|
||||
memory = nullptr;
|
||||
}
|
||||
|
||||
std::byte* memory = nullptr;
|
||||
};
|
||||
|
||||
void Emit(std::vector<std::uint32_t>& words, spv::Op opcode, std::initializer_list<std::uint32_t> operands) {
|
||||
words.push_back(static_cast<std::uint32_t>((operands.size() + 1u) << 16u) | opcode);
|
||||
words.insert(words.end(), operands);
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> CopyModule() {
|
||||
enum : std::uint32_t { Void = 1, FunctionType, Uint, Uint3, Words, Block, BlockPointer, Source, Destination, UintPointer, InputPointer, Invocation, Zero, Main, Label, InvocationValue, Index, SourcePointer, Value, DestinationPointer, Bound };
|
||||
std::vector<std::uint32_t> words{spv::MagicNumber, 0x00010300u, 0u, Bound, 0u};
|
||||
Emit(words, spv::OpCapability, {spv::CapabilityShader});
|
||||
Emit(words, spv::OpMemoryModel, {spv::AddressingModelLogical, spv::MemoryModelGLSL450});
|
||||
Emit(words, spv::OpEntryPoint, {spv::ExecutionModelGLCompute, Main, 0x6e69616du, 0u, Invocation});
|
||||
Emit(words, spv::OpExecutionMode, {Main, spv::ExecutionModeLocalSize, WorkgroupSize, 1, 1});
|
||||
Emit(words, spv::OpDecorate, {Invocation, spv::DecorationBuiltIn, spv::BuiltInGlobalInvocationId});
|
||||
Emit(words, spv::OpDecorate, {Words, spv::DecorationArrayStride, 4});
|
||||
Emit(words, spv::OpDecorate, {Block, spv::DecorationBlock});
|
||||
Emit(words, spv::OpMemberDecorate, {Block, 0, spv::DecorationOffset, 0});
|
||||
Emit(words, spv::OpDecorate, {Source, spv::DecorationDescriptorSet, 0});
|
||||
Emit(words, spv::OpDecorate, {Source, spv::DecorationBinding, 0});
|
||||
Emit(words, spv::OpDecorate, {Destination, spv::DecorationDescriptorSet, 0});
|
||||
Emit(words, spv::OpDecorate, {Destination, spv::DecorationBinding, 1});
|
||||
Emit(words, spv::OpTypeVoid, {Void});
|
||||
Emit(words, spv::OpTypeFunction, {FunctionType, Void});
|
||||
Emit(words, spv::OpTypeInt, {Uint, 32, 0});
|
||||
Emit(words, spv::OpTypeVector, {Uint3, Uint, 3});
|
||||
Emit(words, spv::OpTypeRuntimeArray, {Words, Uint});
|
||||
Emit(words, spv::OpTypeStruct, {Block, Words});
|
||||
Emit(words, spv::OpTypePointer, {BlockPointer, spv::StorageClassStorageBuffer, Block});
|
||||
Emit(words, spv::OpVariable, {BlockPointer, Source, spv::StorageClassStorageBuffer});
|
||||
Emit(words, spv::OpVariable, {BlockPointer, Destination, spv::StorageClassStorageBuffer});
|
||||
Emit(words, spv::OpTypePointer, {UintPointer, spv::StorageClassStorageBuffer, Uint});
|
||||
Emit(words, spv::OpTypePointer, {InputPointer, spv::StorageClassInput, Uint3});
|
||||
Emit(words, spv::OpVariable, {InputPointer, Invocation, spv::StorageClassInput});
|
||||
Emit(words, spv::OpConstant, {Uint, Zero, 0});
|
||||
Emit(words, spv::OpFunction, {Void, Main, 0, FunctionType});
|
||||
Emit(words, spv::OpLabel, {Label});
|
||||
Emit(words, spv::OpLoad, {Uint3, InvocationValue, Invocation});
|
||||
Emit(words, spv::OpCompositeExtract, {Uint, Index, InvocationValue, 0});
|
||||
Emit(words, spv::OpAccessChain, {UintPointer, SourcePointer, Source, Zero, Index});
|
||||
Emit(words, spv::OpLoad, {Uint, Value, SourcePointer});
|
||||
Emit(words, spv::OpAccessChain, {UintPointer, DestinationPointer, Destination, Zero, Index});
|
||||
Emit(words, spv::OpStore, {DestinationPointer, Value});
|
||||
Emit(words, spv::OpReturn, {});
|
||||
Emit(words, spv::OpFunctionEnd, {});
|
||||
return words;
|
||||
}
|
||||
|
||||
class CopyPipeline {
|
||||
public:
|
||||
explicit CopyPipeline(const Context& context) : context(context) {
|
||||
try {
|
||||
const std::array<VkDescriptorSetLayoutBinding, 2> bindings{{{0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, nullptr}, {1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, nullptr}}};
|
||||
VkDescriptorSetLayoutCreateInfo setLayoutInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO};
|
||||
setLayoutInfo.bindingCount = static_cast<std::uint32_t>(bindings.size());
|
||||
setLayoutInfo.pBindings = bindings.data();
|
||||
Check(context.Function<PFN_vkCreateDescriptorSetLayout>("vkCreateDescriptorSetLayout")(context.device, &setLayoutInfo, nullptr, &setLayout), "vkCreateDescriptorSetLayout");
|
||||
const VkDescriptorPoolSize poolSize{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 2};
|
||||
VkDescriptorPoolCreateInfo poolInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO};
|
||||
poolInfo.maxSets = 1;
|
||||
poolInfo.poolSizeCount = 1;
|
||||
poolInfo.pPoolSizes = &poolSize;
|
||||
Check(context.Function<PFN_vkCreateDescriptorPool>("vkCreateDescriptorPool")(context.device, &poolInfo, nullptr, &descriptorPool), "vkCreateDescriptorPool");
|
||||
VkDescriptorSetAllocateInfo allocateInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO};
|
||||
allocateInfo.descriptorPool = descriptorPool;
|
||||
allocateInfo.descriptorSetCount = 1;
|
||||
allocateInfo.pSetLayouts = &setLayout;
|
||||
Check(context.Function<PFN_vkAllocateDescriptorSets>("vkAllocateDescriptorSets")(context.device, &allocateInfo, &set), "vkAllocateDescriptorSets");
|
||||
const auto module = CopyModule();
|
||||
VkShaderModuleCreateInfo moduleInfo{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
|
||||
moduleInfo.codeSize = module.size() * sizeof(std::uint32_t);
|
||||
moduleInfo.pCode = module.data();
|
||||
Check(context.Function<PFN_vkCreateShaderModule>("vkCreateShaderModule")(context.device, &moduleInfo, nullptr, &shader), "vkCreateShaderModule");
|
||||
VkPipelineLayoutCreateInfo layoutInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO};
|
||||
layoutInfo.setLayoutCount = 1;
|
||||
layoutInfo.pSetLayouts = &setLayout;
|
||||
Check(context.Function<PFN_vkCreatePipelineLayout>("vkCreatePipelineLayout")(context.device, &layoutInfo, nullptr, &pipelineLayout), "vkCreatePipelineLayout");
|
||||
VkComputePipelineCreateInfo pipelineInfo{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO};
|
||||
pipelineInfo.stage = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO};
|
||||
pipelineInfo.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT;
|
||||
pipelineInfo.stage.module = shader;
|
||||
pipelineInfo.stage.pName = "main";
|
||||
pipelineInfo.layout = pipelineLayout;
|
||||
Check(context.Function<PFN_vkCreateComputePipelines>("vkCreateComputePipelines")(context.device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &pipeline), "vkCreateComputePipelines");
|
||||
} catch (...) {
|
||||
release();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
~CopyPipeline() { release(); }
|
||||
CopyPipeline(const CopyPipeline&) = delete;
|
||||
CopyPipeline& operator=(const CopyPipeline&) = delete;
|
||||
|
||||
void Copy(const VkDescriptorBufferInfo& source, Buffer& destination) const {
|
||||
Require(source.range == BufferBytes && destination.Bytes().size() == BufferBytes, "sparse buffer holes GPU copy has an unexpected extent");
|
||||
const std::array<VkDescriptorBufferInfo, 2> infos{{source, {destination.Handle(), 0, BufferBytes}}};
|
||||
std::array<VkWriteDescriptorSet, 2> writes{};
|
||||
for (std::uint32_t binding = 0; binding < writes.size(); ++binding) {
|
||||
writes[binding] = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
|
||||
writes[binding].dstSet = set;
|
||||
writes[binding].dstBinding = binding;
|
||||
writes[binding].descriptorCount = 1;
|
||||
writes[binding].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
||||
writes[binding].pBufferInfo = &infos[binding];
|
||||
}
|
||||
context.Function<PFN_vkUpdateDescriptorSets>("vkUpdateDescriptorSets")(context.device, static_cast<std::uint32_t>(writes.size()), writes.data(), 0, nullptr);
|
||||
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
|
||||
CommandBatch batch(context);
|
||||
RecordMemoryBarrier(context, batch.Handle(), VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT);
|
||||
context.Function<PFN_vkCmdBindPipeline>("vkCmdBindPipeline")(batch.Handle(), VK_PIPELINE_BIND_POINT_COMPUTE, pipeline);
|
||||
context.Function<PFN_vkCmdBindDescriptorSets>("vkCmdBindDescriptorSets")(batch.Handle(), VK_PIPELINE_BIND_POINT_COMPUTE, pipelineLayout, 0, 1, &set, 0, nullptr);
|
||||
context.Function<PFN_vkCmdDispatch>("vkCmdDispatch")(batch.Handle(), static_cast<std::uint32_t>(BufferBytes / sizeof(std::uint32_t) / WorkgroupSize), 1, 1);
|
||||
RecordMemoryBarrier(context, batch.Handle(), VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT);
|
||||
batch.SubmitAndWait();
|
||||
destination.Invalidate();
|
||||
}
|
||||
|
||||
private:
|
||||
void release() noexcept {
|
||||
if (pipeline != VK_NULL_HANDLE) context.Function<PFN_vkDestroyPipeline>("vkDestroyPipeline")(context.device, pipeline, nullptr);
|
||||
if (pipelineLayout != VK_NULL_HANDLE) context.Function<PFN_vkDestroyPipelineLayout>("vkDestroyPipelineLayout")(context.device, pipelineLayout, nullptr);
|
||||
if (shader != VK_NULL_HANDLE) context.Function<PFN_vkDestroyShaderModule>("vkDestroyShaderModule")(context.device, shader, nullptr);
|
||||
if (descriptorPool != VK_NULL_HANDLE) context.Function<PFN_vkDestroyDescriptorPool>("vkDestroyDescriptorPool")(context.device, descriptorPool, nullptr);
|
||||
if (setLayout != VK_NULL_HANDLE) context.Function<PFN_vkDestroyDescriptorSetLayout>("vkDestroyDescriptorSetLayout")(context.device, setLayout, nullptr);
|
||||
}
|
||||
|
||||
const Context& context;
|
||||
VkDescriptorSetLayout setLayout = VK_NULL_HANDLE;
|
||||
VkDescriptorPool descriptorPool = VK_NULL_HANDLE;
|
||||
VkDescriptorSet set = VK_NULL_HANDLE;
|
||||
VkShaderModule shader = VK_NULL_HANDLE;
|
||||
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
||||
VkPipeline pipeline = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
void Run(const Context& context, const CopyPipeline& pipeline, bool writable, const std::string& shape) {
|
||||
const auto label = std::string("sparse buffer holes GPU: ") + (writable ? "writable " : "readable ") + shape;
|
||||
const bool committed = shape == "committed";
|
||||
const auto requested = shape == "shortened" ? ReservedBytes : BufferBytes;
|
||||
GuestPages pages(committed);
|
||||
const auto ranges = AgcDriver::GuestMemory::DescribeCommitted(pages.Address(), requested);
|
||||
Require(ranges.whole == committed, label + ": unexpected guest commitment");
|
||||
if (!committed) {
|
||||
Require(ranges.ranges == std::vector<std::pair<std::uint64_t, std::uint64_t>>{{pages.Address(), pages.Address() + PageBytes}, {pages.Address() + 2 * PageBytes, pages.Address() + BufferBytes}}, label + ": guest hole is not inaccessible");
|
||||
}
|
||||
Buffer readback(context, BufferBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT);
|
||||
std::memset(readback.Bytes().data(), 0xcc, BufferBytes);
|
||||
VkBuffer primed = VK_NULL_HANDLE;
|
||||
{
|
||||
Buffer earlier(context, BufferBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
|
||||
primed = earlier.Handle();
|
||||
std::memset(earlier.Bytes().data(), 0xa5, BufferBytes);
|
||||
}
|
||||
GuestBufferMemory memory(context);
|
||||
if (writable) memory.AddWritable(pages.Address(), requested);
|
||||
else memory.AddReadable(pages.Address(), requested);
|
||||
{
|
||||
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
|
||||
memory.Upload(false);
|
||||
}
|
||||
std::uint32_t adjustment = 0;
|
||||
const auto descriptor = memory.Descriptor(pages.Address(), requested, adjustment);
|
||||
Require(descriptor.buffer == primed && descriptor.offset == 0 && descriptor.range == BufferBytes && adjustment == 0, label + ": the upload did not reuse the primed buffer at the expected extent");
|
||||
std::cout << label << " reused primed buffer; requested=" << requested << "; allocated=" << descriptor.range << '\n';
|
||||
pipeline.Copy(descriptor, readback);
|
||||
const auto bytes = readback.Bytes();
|
||||
std::size_t mismatches = 0;
|
||||
std::size_t staleWords = 0;
|
||||
for (std::size_t offset = 0; offset < BufferBytes; offset += sizeof(std::uint32_t)) {
|
||||
std::uint32_t actual = 0;
|
||||
std::memcpy(&actual, bytes.data() + offset, sizeof(actual));
|
||||
const auto page = offset / PageBytes;
|
||||
if (page == 1 && !committed) {
|
||||
mismatches += actual != 0;
|
||||
staleWords += actual == 0xa5a5a5a5u;
|
||||
} else {
|
||||
Require(actual == Patterns[page], label + ": GPU readback changed backed guest bytes");
|
||||
}
|
||||
}
|
||||
std::cout << label << " readback checked; words=" << BufferBytes / sizeof(std::uint32_t) << "; nonzeroHoleWords=" << mismatches << "; staleHoleWords=" << staleWords << '\n';
|
||||
Require(mismatches == 0, label + ": GPU readback exposed stale pooled bytes in guest holes");
|
||||
std::cout << label << " passed\n";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
std::unique_ptr<Device> device;
|
||||
try {
|
||||
device = std::make_unique<Device>();
|
||||
} catch (const std::exception& error) {
|
||||
if (std::getenv("ANYPS5_REQUIRE_VULKAN") != nullptr) throw;
|
||||
std::printf("skipped, no usable Vulkan device: %s\n", error.what());
|
||||
return VulkanTestSkipped;
|
||||
}
|
||||
const auto& context = device->GetContext();
|
||||
Require(context.limits.maxStorageBufferRange >= BufferBytes && context.limits.maxComputeWorkGroupSize[0] >= WorkgroupSize && context.limits.maxComputeWorkGroupInvocations >= WorkgroupSize && context.limits.maxComputeWorkGroupCount[0] >= BufferBytes / sizeof(std::uint32_t) / WorkgroupSize, "Vulkan limits cannot support the bounded sparse buffer readback");
|
||||
CopyPipeline pipeline(context);
|
||||
bool passed = true;
|
||||
for (const bool writable : {false, true}) {
|
||||
for (const auto* shape : {"interior", "shortened", "committed"}) {
|
||||
try {
|
||||
Run(context, pipeline, writable, shape);
|
||||
} catch (const std::exception& error) {
|
||||
if (std::string(error.what()).find(": GPU readback exposed stale pooled bytes in guest holes") == std::string::npos) throw;
|
||||
std::cerr << error.what() << '\n';
|
||||
passed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!passed) return 1;
|
||||
std::puts("Sparse buffer hole GPU readback tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue