diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp index d24cec76ac4..3517e33f2c7 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp @@ -170,7 +170,7 @@ class DepthSurface { class DepthPlaneCopy { public: - DepthPlaneCopy(const Context& context, VkExtent2D extent, VkFormat format, std::uint32_t layers) : context(context), extent(extent), format(format), layers(layers) { + DepthPlaneCopy(const Context& context, VkExtent2D extent, VkFormat format, std::uint32_t layers) : context(context), extent(extent), format(format), layers(layers), guestLayers(layers) { this->context.bufferPool.reset(); VkImageCreateInfo info{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; info.flags = layers % 6u == 0 && extent.width == extent.height ? VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT : 0u; @@ -208,11 +208,23 @@ class DepthPlaneCopy { const auto geometry = DescribeSurface(resource); Require(geometry.layers == layers && geometry.sliceLinearBytes == sliceBytes() && !geometry.mips.empty(), "depth plane copy geometry does not match its layers"); std::vector> uploads; + std::vector uploadLayers; for (std::uint32_t layer = 0; layer < layers; ++layer) { - if (slices[layer] != VK_NULL_HANDLE) continue; + auto& held = guestLayers[layer]; + if (slices[layer] != VK_NULL_HANDLE) { + held = {}; + continue; + } + const GuestLayer current{resource.baseAddress + geometry.GuestLayerOffset(layer), geometry.layerBytes, resource.tileMode, geometry.thick, GuestMemory::CollectWrites(resource.baseAddress + geometry.GuestLayerOffset(layer), static_cast(geometry.layerBytes))}; + if (held.generation != 0 && held.address == current.address && held.bytes == current.bytes && held.tileMode == current.tileMode && held.thick == current.thick && !Recorder::SnapshotWriteOverlaps(current.address, static_cast(current.bytes)) && GuestMemory::UnchangedSince(current.address, static_cast(current.bytes), held.generation)) { + held.generation = current.generation; + continue; + } auto upload = std::make_shared(context, static_cast(geometry.layerBytes), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); - GuestMemory::ReadCommitted(resource.baseAddress + geometry.GuestLayerOffset(layer), upload->Bytes().first(static_cast(geometry.layerBytes))); + GuestMemory::ReadCommitted(current.address, upload->Bytes().first(static_cast(geometry.layerBytes))); uploads.push_back(std::move(upload)); + uploadLayers.push_back(layer); + held = current; } if (!uploads.empty()) { Require(context.detiler != nullptr, "depth plane copy requires a texture detiler"); @@ -235,10 +247,9 @@ class DepthPlaneCopy { } if (!uploads.empty()) { RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_HOST_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_HOST_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT); - std::size_t next = 0; - for (std::uint32_t layer = 0; layer < layers; ++layer) { - if (slices[layer] != VK_NULL_HANDLE) continue; - context.detiler->Dispatch(commands, resource.tileMode, format == VK_FORMAT_D32_SFLOAT ? 4u : 2u, uploads[next++]->Handle(), 0, staging->Handle(), geometry.LinearLayerOffset(layer), geometry.mips.front(), false, layer, geometry.thick); + for (std::size_t upload = 0; upload < uploads.size(); ++upload) { + const auto layer = uploadLayers[upload]; + context.detiler->Dispatch(commands, resource.tileMode, format == VK_FORMAT_D32_SFLOAT ? 4u : 2u, uploads[upload]->Handle(), 0, staging->Handle(), geometry.LinearLayerOffset(layer), geometry.mips.front(), false, layer, geometry.thick); } if (recorder != nullptr) { for (auto& upload : uploads) recorder->Keep(upload); @@ -280,10 +291,19 @@ class DepthPlaneCopy { const std::uint32_t layers; private: + struct GuestLayer { + std::uint64_t address = 0; + std::uint64_t bytes = 0; + TextureTileMode tileMode = TextureTileMode::kLinear; + bool thick = false; + std::uint64_t generation = 0; + }; + VkImage image = VK_NULL_HANDLE; VkDeviceMemory memory = VK_NULL_HANDLE; std::unique_ptr staging; std::map, std::shared_ptr> textures; + std::vector guestLayers; VkDeviceSize sliceBytes() const { return static_cast(extent.width) * extent.height * (format == VK_FORMAT_D32_SFLOAT ? 4u : 2u); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneGuestLayers.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneGuestLayers.cpp new file mode 100644 index 00000000000..a03da4ce0d9 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneGuestLayers.cpp @@ -0,0 +1,273 @@ +#include "prx/libSceAgcDriver/Execution/include/BdaFeatures.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include "prx/libc/include/GuestWriteWatch.hpp" +#include +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using namespace AgcDriver::Graphics; + +constexpr std::uint32_t Side = 256; +constexpr std::uint32_t Format32Float = 22; +constexpr std::size_t LayerBytes = std::size_t{Side} * Side * 4u; +constexpr std::size_t BlockBytes = 2u * LayerBytes; +constexpr int Skipped = 77; + +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(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("vkCreateInstance")(&info, nullptr, &instance), "vkCreateInstance"); + std::uint32_t count = 0; + const auto enumerate = function("vkEnumeratePhysicalDevices"); + Check(enumerate(instance, &count, nullptr), "vkEnumeratePhysicalDevices"); + Require(count != 0, "no Vulkan device"); + std::vector devices(count); + Check(enumerate(instance, &count, devices.data()), "vkEnumeratePhysicalDevices"); + context.physical = devices.front(); + const auto extensions = function("vkEnumerateDeviceExtensionProperties"); + Check(extensions(context.physical, nullptr, &count, nullptr), "vkEnumerateDeviceExtensionProperties"); + std::vector available(count); + Check(extensions(context.physical, nullptr, &count, available.data()), "vkEnumerateDeviceExtensionProperties"); + auto bytes = AgcDriver::QueryBdaByteFeatures(context.physical, function("vkGetPhysicalDeviceFeatures2"), available); + auto address = AgcDriver::QueryBdaFeatures(context.physical, function("vkGetPhysicalDeviceFeatures2"), available); + const auto queues = function("vkGetPhysicalDeviceQueueFamilyProperties"); + queues(context.physical, &count, nullptr); + std::vector families(count); + queues(context.physical, &count, families.data()); + constexpr VkQueueFlags needed = VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT; + std::uint32_t family = 0; + while (family < count && (families[family].queueFlags & needed) != needed) ++family; + Require(family < count, "no Vulkan graphics and compute queue"); + const float priority = 1; + VkDeviceQueueCreateInfo queue{VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO}; + queue.queueFamilyIndex = family; + queue.queueCount = 1; + queue.pQueuePriorities = &priority; + VkPhysicalDeviceFeatures enabled{}; + enabled.shaderInt64 = VK_TRUE; + address.pNext = &bytes; + const std::array extensionsEnabled{VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME, VK_KHR_8BIT_STORAGE_EXTENSION_NAME}; + VkDeviceCreateInfo device{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, &address}; + device.queueCreateInfoCount = 1; + device.pQueueCreateInfos = &queue; + device.enabledExtensionCount = static_cast(extensionsEnabled.size()); + device.ppEnabledExtensionNames = extensionsEnabled.data(); + device.pEnabledFeatures = &enabled; + Check(function("vkCreateDevice")(context.physical, &device, nullptr, &context.device), "vkCreateDevice"); + context.deviceProc = function("vkGetDeviceProcAddr"); + function("vkGetPhysicalDeviceMemoryProperties")(context.physical, &context.memory); + VkPhysicalDeviceProperties properties{}; + function("vkGetPhysicalDeviceProperties")(context.physical, &properties); + context.limits = properties.limits; + context.bufferDeviceAddress = true; + context.formatProperties = function("vkGetPhysicalDeviceFormatProperties"); + context.Function("vkGetDeviceQueue")(context.device, family, 0, &context.queue); + VkCommandPoolCreateInfo pool{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO}; + pool.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + pool.queueFamilyIndex = family; + Check(context.Function("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 + TFunction function(const char* name) const { + const auto result = reinterpret_cast(instanceProc(instance, name)); + Require(result != nullptr, name); + return result; + } + + void release() noexcept { + if (context.device != VK_NULL_HANDLE) ClearDepthSurfaces(context.device); + if (context.pool != VK_NULL_HANDLE) context.Function("vkDestroyCommandPool")(context.device, context.pool, nullptr); + context.bufferPool.reset(); + if (context.device != VK_NULL_HANDLE) function("vkDestroyDevice")(context.device, nullptr); + if (instance != VK_NULL_HANDLE) function("vkDestroyInstance")(instance, nullptr); + if (library != nullptr) SDL_UnloadObject(library); + } + + void* library = nullptr; + PFN_vkGetInstanceProcAddr instanceProc = nullptr; + VkInstance instance = VK_NULL_HANDLE; + Context context{}; +}; + +class WatchedBlock { +public: + WatchedBlock() { +#ifdef _WIN32 + block = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(BlockBytes, 65536)); + if (block != nullptr) GuestArena::GuestArenaCommit_nid_postfix(block, BlockBytes, PAGE_READWRITE, BlockBytes); +#else + constexpr std::uintptr_t alignment = 65536; + void* mapped = mmap(nullptr, BlockBytes + alignment, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (mapped != MAP_FAILED) { + const auto begin = reinterpret_cast(mapped); + const auto aligned = (begin + alignment - 1) & ~(alignment - 1); + if (aligned != begin) munmap(mapped, aligned - begin); + if (aligned + BlockBytes != begin + BlockBytes + alignment) munmap(reinterpret_cast(aligned + BlockBytes), begin + alignment - aligned); + block = reinterpret_cast(aligned); + GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(block, BlockBytes); + } +#endif + Require(block != nullptr, "depth plane guest layers: cannot allocate the guest block"); + GuestAllocations::Mutation().Add(block, BlockBytes, true, true, true); + } + + ~WatchedBlock() { + GuestAllocations::Mutation().Remove(block); +#ifdef _WIN32 + GuestArena::GuestArenaReset_nid_postfix(block, BlockBytes); + GuestArena::GuestArenaRelease_nid_postfix(block, BlockBytes); +#else + munmap(block, BlockBytes); + GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(block, BlockBytes); +#endif + } + + WatchedBlock(const WatchedBlock&) = delete; + WatchedBlock& operator=(const WatchedBlock&) = delete; + + std::uint8_t* Data() const { return block; } + std::uint64_t Address() const { return static_cast(reinterpret_cast(block)); } + +private: + std::uint8_t* block = nullptr; +}; + +std::atomic watchedBegin{0}; +std::atomic watchedEnd{0}; +std::atomic watchedReads{0}; + +void CountReads(std::uint64_t address, std::size_t bytes) { + if (address < watchedEnd.load() && address + bytes > watchedBegin.load()) watchedReads.fetch_add(1); +} + +void FillLayer(std::uint8_t* layer, float depth) { + for (std::size_t offset = 0; offset < LayerBytes; offset += sizeof(depth)) std::memcpy(layer + offset, &depth, sizeof(depth)); +} + +void Run(const Context& base, const WatchedBlock& block) { + TextureDetiler detiler(base); + auto context = base; + context.detiler = &detiler; + const auto address = block.Address(); + FillLayer(block.Data(), 1.0f); + FillLayer(block.Data() + LayerBytes, 0.25f); + Require(DepthSliceBytes({Side, Side}, 4u) == LayerBytes, "the depth slice of the test surface is not one layer of the guest block"); + DepthSurfaceView(context, DepthTarget{address, 0, {Side, Side}, VK_FORMAT_D32_SFLOAT, 1.0f, 0, 0}); + GuestTextureResource resource{}; + resource.baseAddress = address; + resource.width = Side; + resource.height = Side; + resource.depthOrLastArray = 1; + resource.mipCount = 1; + resource.tileMode = TextureTileMode::kZ64KBX; + resource.dimension = TextureDimension::k2DArray; + resource.format = Format32Float; + resource.dstSelX = 4; + resource.dstSelY = 5; + resource.dstSelZ = 6; + resource.dstSelW = 7; + const std::array words{}; + const VkComponentMapping components{VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY}; + watchedBegin = address + LayerBytes; + watchedEnd = address + BlockBytes; + AgcDriver::GuestMemory::SetFlushHook(&CountReads); + const auto sample = [&](const char* when) { + watchedReads = 0; + const auto texture = DepthSurfaceTexture(context, words, resource, components); + Require(texture != nullptr, std::string("the 2D array over the depth surface was not sampled through its depth planes ") + when); + return watchedReads.load(); + }; + try { + Require(sample("at its first use") != 0, "the first sampling of the depth planes did not read the layer no depth surface holds"); + Require(sample("again") == 0, "sampling the depth planes again read the guest layer no depth surface holds although nothing wrote it"); + FillLayer(block.Data() + LayerBytes, 0.75f); + Require(sample("after a CPU write") != 0, "sampling the depth planes after a CPU write to the guest layer did not read it again"); + Require(sample("after the re-read") == 0, "sampling the depth planes after the re-read of the written layer read it once more"); + AgcDriver::GuestMemory::MarkWritten(address + LayerBytes, 4096); + Require(sample("after a driver store") != 0, "sampling the depth planes after a driver store over the guest layer did not read it again"); + } catch (...) { + AgcDriver::GuestMemory::SetFlushHook(nullptr); + throw; + } + AgcDriver::GuestMemory::SetFlushHook(nullptr); +} + +} + +int main() { + try { + if (!AgcDriver::GuestMemory::WriteWatched()) { + std::puts("skipped, guest memory has no write watch"); + return Skipped; + } + std::optional device; + try { + device.emplace(); + } 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 Skipped; + } + { + WatchedBlock block; + Require(AgcDriver::GuestMemory::Watched(block.Address(), BlockBytes), "depth plane guest layers: the guest block is not write-watched"); + Run(device->GetContext(), block); + } + device.reset(); + std::puts("depth plane guest layer tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +}