feat(libSceAgcDriver): storage image access to depth surfaces
Compute shaders bind the depth and stencil planes of a depth surface as storage images, to read the depth or to write it before later draws test against it. The driver kept depth on the GPU and refused such bindings. A depth surface now owns one storage image per plane (R32_SFLOAT or R16_UNORM for depth, R8_UINT for stencil). Binding copies the plane into it through a staging buffer; a written element is copied back into the depth image during write-back, so the next draw sees the shader's depth. A view whose extent differs from the surface's means the memory was reused for another texture and takes the ordinary texture path; this applies to sampled views of a depth surface too. PPSA15304 copies depth and stencil through storage images every frame.
This commit is contained in:
parent
37dd483084
commit
27e1122774
|
|
@ -12,6 +12,7 @@
|
|||
namespace AgcDriver::Graphics {
|
||||
|
||||
class Texture;
|
||||
class StorageTexture;
|
||||
|
||||
VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target);
|
||||
bool SampleLocationsCompatibleDepth(const Context& context, VkFormat format, std::uint32_t samples);
|
||||
|
|
@ -36,6 +37,8 @@ bool HtileFillCovers(std::uint64_t htile, VkExtent2D extent, std::uint64_t addre
|
|||
std::shared_ptr<Texture> DepthSurfaceTexture(const Context& context, std::span<const std::uint32_t> words, const GuestTextureResource& resource, VkComponentMapping components);
|
||||
VkFormat HostDepthFormat(const Context& context, VkFormat format);
|
||||
float HostDepthBiasConstant(const Context& context, VkFormat format, float constant);
|
||||
std::shared_ptr<StorageTexture> DepthSurfaceStorage(const Context& context, std::span<const std::uint32_t> words, const GuestTextureResource& resource, std::uint32_t mip);
|
||||
bool DepthSurfaceStoreBack(const Context& context, const StorageTexture& storage);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "RdnaDecoder/include/RdnaDecoder/RdnaDescriptorFormat.hpp"
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <map>
|
||||
|
|
@ -153,6 +154,35 @@ public:
|
|||
float clearDepth = 0.0f;
|
||||
std::uint8_t clearStencil = 0;
|
||||
|
||||
std::shared_ptr<StorageTexture> Storage(std::span<const std::uint32_t> words, const GuestTextureResource& resource, std::uint32_t mip) {
|
||||
if (resource.dimension != TextureDimension::k2D || resource.width != target.extent.width || resource.height != target.extent.height) return nullptr;
|
||||
const bool stencil = target.stencilAddress != 0 && resource.baseAddress == target.stencilAddress;
|
||||
const bool d16 = target.format == VK_FORMAT_D16_UNORM || target.format == VK_FORMAT_D16_UNORM_S8_UINT;
|
||||
const auto expected = stencil ? VK_FORMAT_R8_UINT : d16 ? VK_FORMAT_R16_UNORM : VK_FORMAT_R32_SFLOAT;
|
||||
const bool depthBits = !stencil && words.size() >= 4 && ShaderRecompiler::DepthBitsTextureWidth(words[1], words[3]) == (d16 ? 16u : 32u);
|
||||
if (ResolveTextureFormat(resource.format) != expected && !depthBits) return nullptr;
|
||||
if (mip != 0 || resource.baseLevel != 0) {
|
||||
char text[224];
|
||||
std::snprintf(text, sizeof(text), "AGC graphics: storage image access to level %u of the %s plane of depth surface 0x%llx is not implemented", mip, stencil ? "stencil" : "depth", static_cast<unsigned long long>(target.address));
|
||||
throw std::runtime_error(text);
|
||||
}
|
||||
ApplyFastClear();
|
||||
auto& plane = stencil ? stencilPlane : depthPlane;
|
||||
if (plane.storage == nullptr || plane.storage->Descriptor().format != resource.format || plane.storage->Descriptor().tileMode != resource.tileMode || plane.storage->Descriptor().baseAddress != resource.baseAddress) plane.storage = StorageTexture::Create(context, *context.detiler, resource, 0);
|
||||
copyPlane(plane, stencil, true);
|
||||
return plane.storage;
|
||||
}
|
||||
|
||||
bool StoreBack(const StorageTexture& candidate) {
|
||||
for (const bool stencil : {false, true}) {
|
||||
auto& plane = stencil ? stencilPlane : depthPlane;
|
||||
if (plane.storage.get() != &candidate) continue;
|
||||
copyPlane(plane, stencil, false);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const Context context;
|
||||
const DepthTarget target;
|
||||
const VkFormat hostFormat;
|
||||
|
|
@ -161,8 +191,40 @@ public:
|
|||
VkDeviceMemory memory = VK_NULL_HANDLE;
|
||||
|
||||
private:
|
||||
struct PlaneStorage {
|
||||
std::shared_ptr<StorageTexture> storage;
|
||||
std::unique_ptr<DeviceBuffer> staging;
|
||||
};
|
||||
std::map<std::array<std::uint32_t, 12>, std::shared_ptr<Texture>> textures;
|
||||
PlaneStorage depthPlane;
|
||||
PlaneStorage stencilPlane;
|
||||
|
||||
void copyPlane(PlaneStorage& plane, bool stencil, bool toStorage) {
|
||||
auto* recorder = Recorder::Active();
|
||||
Require(recorder != nullptr, "depth surface storage copies need an active recorder");
|
||||
const bool d16 = target.format == VK_FORMAT_D16_UNORM || target.format == VK_FORMAT_D16_UNORM_S8_UINT;
|
||||
const auto bytes = static_cast<std::size_t>(target.extent.width) * target.extent.height * (stencil ? 1u : d16 ? 2u : 4u);
|
||||
if (plane.staging == nullptr) plane.staging = std::make_unique<DeviceBuffer>(context, bytes, VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT);
|
||||
const auto commands = recorder->Commands();
|
||||
recorder->Keep(plane.storage);
|
||||
const auto timing = recorder->BeginGpuTiming(Recorder::CommandClass::Copy);
|
||||
VkBufferImageCopy planeRegion{0, 0, 0, {stencil ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT, 0, 0, 1}, {0, 0, 0}, {target.extent.width, target.extent.height, 1}};
|
||||
VkBufferImageCopy colorRegion = planeRegion;
|
||||
colorRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
const auto sourceImage = toStorage ? image : plane.storage->Image();
|
||||
const auto destinationImage = toStorage ? plane.storage->Image() : image;
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_MEMORY_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT);
|
||||
context.Function<PFN_vkCmdCopyImageToBuffer>("vkCmdCopyImageToBuffer")(commands, sourceImage, VK_IMAGE_LAYOUT_GENERAL, plane.staging->Handle(), 1, toStorage ? &planeRegion : &colorRegion);
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT);
|
||||
context.Function<PFN_vkCmdCopyBufferToImage>("vkCmdCopyBufferToImage")(commands, plane.staging->Handle(), destinationImage, VK_IMAGE_LAYOUT_GENERAL, 1, toStorage ? &colorRegion : &planeRegion);
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT);
|
||||
Recorder::CountBarriers(Recorder::CommandClass::Copy, 3);
|
||||
recorder->EndGpuTiming(timing, bytes);
|
||||
}
|
||||
|
||||
void release() noexcept {
|
||||
depthPlane = {};
|
||||
stencilPlane = {};
|
||||
textures.clear();
|
||||
if (view) context.Function<PFN_vkDestroyImageView>("vkDestroyImageView")(context.device, view, nullptr);
|
||||
if (image) context.Function<PFN_vkDestroyImage>("vkDestroyImage")(context.device, image, nullptr);
|
||||
|
|
@ -598,6 +660,23 @@ bool DepthSurfaceAt(const Context& context, std::span<const std::uint32_t> words
|
|||
return ResolveTextureFormat(resource.format) == expected || depthBits;
|
||||
}
|
||||
|
||||
std::shared_ptr<StorageTexture> DepthSurfaceStorage(const Context& context, std::span<const std::uint32_t> words, const GuestTextureResource& resource, std::uint32_t mip) {
|
||||
std::lock_guard lock(surfacesMutex());
|
||||
const auto& list = surfaces();
|
||||
const auto found = std::find_if(list.rbegin(), list.rend(), [&](const auto& surface) {
|
||||
return surface->context.device == context.device && (surface->target.address == resource.baseAddress || (surface->target.stencilAddress != 0 && surface->target.stencilAddress == resource.baseAddress));
|
||||
});
|
||||
return found == list.rend() ? nullptr : (*found)->Storage(words, resource, mip);
|
||||
}
|
||||
|
||||
bool DepthSurfaceStoreBack(const Context& context, const StorageTexture& storage) {
|
||||
std::lock_guard lock(surfacesMutex());
|
||||
for (const auto& surface : surfaces()) {
|
||||
if (surface->context.device == context.device && surface->StoreBack(storage)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DepthSurfaceAt(std::uint64_t address) {
|
||||
std::lock_guard lock(surfacesMutex());
|
||||
return std::any_of(surfaces().begin(), surfaces().end(), [&](const auto& surface) { return surface->target.address == address || surface->target.stencilAddress == address; });
|
||||
|
|
|
|||
|
|
@ -837,11 +837,7 @@ GuestTextureResource StorageSurface(const Context& context, const GuestTextureRe
|
|||
// `guestBytes` is the surface size when the caller described the surface already (0: described here).
|
||||
std::shared_ptr<StorageTexture> cachedStorageTexture(const Context& context, std::span<const std::uint32_t> words, const GuestTextureResource& viewed, std::uint32_t mip, std::uint64_t guestBytes) {
|
||||
RequireNoMultisampleTarget(viewed.baseAddress, guestBytes != 0 ? static_cast<std::size_t>(guestBytes) : 1u, "a storage image access");
|
||||
if (DepthSurfaceAt(context, words, viewed)) {
|
||||
char text[256];
|
||||
std::snprintf(text, sizeof(text), "AGC graphics: storage image access to depth/stencil surface 0x%llx is not implemented (storage=%ux%u guest format=%u mip=%u dimension=%d tile=%d)", static_cast<unsigned long long>(viewed.baseAddress), viewed.width, viewed.height, viewed.format, mip, static_cast<int>(viewed.dimension), static_cast<int>(viewed.tileMode));
|
||||
throw std::runtime_error(text);
|
||||
}
|
||||
if (auto depth = DepthSurfaceStorage(context, words, viewed, mip)) return depth;
|
||||
static const bool disabled = std::getenv("APS5_NO_TEXTURE_CACHE") != nullptr;
|
||||
if (disabled) return StorageTexture::Create(context, *context.detiler, viewed, mip);
|
||||
static_cast<void>(words);
|
||||
|
|
@ -3613,7 +3609,9 @@ void ShaderResources::WriteBack() {
|
|||
WriteBackBuffers();
|
||||
if (SkipWriteBack()) return;
|
||||
for (std::size_t index = 0; index < storageTextures.size(); ++index) {
|
||||
if (storageWritten[index]) storageTextures[index]->MarkDirty();
|
||||
if (!storageWritten[index]) continue;
|
||||
storageTextures[index]->MarkDirty();
|
||||
static_cast<void>(DepthSurfaceStoreBack(context, *storageTextures[index]));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3624,7 +3622,9 @@ void ShaderResources::MarkGpuWrites(Recorder& recorder) {
|
|||
recorder.NotePendingReads(guestMemory.InPlaceReads(), guestMemory.HoldsLease() ? Recorder::ReadKind::AddressBased : Recorder::ReadKind::DispatchElement);
|
||||
if (SkipWriteBack()) return;
|
||||
for (std::size_t index = 0; index < storageTextures.size(); ++index) {
|
||||
if (storageWritten[index]) storageTextures[index]->MarkDirty();
|
||||
if (!storageWritten[index]) continue;
|
||||
storageTextures[index]->MarkDirty();
|
||||
static_cast<void>(DepthSurfaceStoreBack(context, *storageTextures[index]));
|
||||
}
|
||||
// Written sub-ranges of buffers the GPU copied out of a host import go back into it by the GPU,
|
||||
// recorded here after the work: those regions then need no CPU write-back (HasCopiedWrites),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,254 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/BdaFeatures.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp"
|
||||
#include "prx/libc/include/GuestAllocations.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#include <SDL_loadso.h>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace AgcDriver::Graphics;
|
||||
|
||||
constexpr std::uint32_t Side = 64;
|
||||
constexpr std::size_t BlockBytes = 65536;
|
||||
alignas(4096) std::array<std::uint8_t, BlockBytes * 2> Storage{};
|
||||
constexpr std::uint32_t Format32Float = 22;
|
||||
constexpr std::uint32_t Format8Uint = 5;
|
||||
|
||||
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");
|
||||
context.physical = devices.front();
|
||||
const auto extensions = function<PFN_vkEnumerateDeviceExtensionProperties>("vkEnumerateDeviceExtensionProperties");
|
||||
Check(extensions(context.physical, nullptr, &count, nullptr), "vkEnumerateDeviceExtensionProperties");
|
||||
std::vector<VkExtensionProperties> available(count);
|
||||
Check(extensions(context.physical, nullptr, &count, available.data()), "vkEnumerateDeviceExtensionProperties");
|
||||
const auto hasExtension = [&](const char* name) {
|
||||
for (const auto& extension : available) {
|
||||
if (std::strcmp(extension.extensionName, name) == 0) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
auto bytes = AgcDriver::QueryBdaByteFeatures(context.physical, function<PFN_vkGetPhysicalDeviceFeatures2>("vkGetPhysicalDeviceFeatures2"), available);
|
||||
auto address = AgcDriver::QueryBdaFeatures(context.physical, function<PFN_vkGetPhysicalDeviceFeatures2>("vkGetPhysicalDeviceFeatures2"), available);
|
||||
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");
|
||||
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;
|
||||
std::vector<const char*> extensionsEnabled{VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME, VK_KHR_8BIT_STORAGE_EXTENSION_NAME};
|
||||
if (hasExtension(VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME)) {
|
||||
extensionsEnabled.push_back(VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME);
|
||||
VkPhysicalDeviceExternalMemoryHostPropertiesEXT hostProperties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_MEMORY_HOST_PROPERTIES_EXT};
|
||||
VkPhysicalDeviceProperties2 properties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &hostProperties};
|
||||
function<PFN_vkGetPhysicalDeviceProperties2>("vkGetPhysicalDeviceProperties2")(context.physical, &properties);
|
||||
context.hostImportAlignment = hostProperties.minImportedHostPointerAlignment;
|
||||
}
|
||||
VkDeviceCreateInfo device{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, &address};
|
||||
device.queueCreateInfoCount = 1;
|
||||
device.pQueueCreateInfos = &queue;
|
||||
device.enabledExtensionCount = static_cast<std::uint32_t>(extensionsEnabled.size());
|
||||
device.ppEnabledExtensionNames = extensionsEnabled.data();
|
||||
device.pEnabledFeatures = &enabled;
|
||||
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);
|
||||
VkPhysicalDeviceProperties properties{};
|
||||
function<PFN_vkGetPhysicalDeviceProperties>("vkGetPhysicalDeviceProperties")(context.physical, &properties);
|
||||
context.limits = properties.limits;
|
||||
context.bufferDeviceAddress = true;
|
||||
context.formatProperties = function<PFN_vkGetPhysicalDeviceFormatProperties>("vkGetPhysicalDeviceFormatProperties");
|
||||
context.imageFormatProperties = function<PFN_vkGetPhysicalDeviceImageFormatProperties>("vkGetPhysicalDeviceImageFormatProperties");
|
||||
if (hasExtension(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME)) context.memoryProperties2 = function<PFN_vkGetPhysicalDeviceMemoryProperties2>("vkGetPhysicalDeviceMemoryProperties2");
|
||||
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(); }
|
||||
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{};
|
||||
};
|
||||
|
||||
|
||||
template<typename TAction>
|
||||
void ExpectFailure(TAction action, const char* expected, const char* message) {
|
||||
try {
|
||||
action();
|
||||
} catch (const std::exception& error) {
|
||||
Require(std::string(error.what()).find(expected) != std::string::npos, error.what());
|
||||
return;
|
||||
}
|
||||
throw std::runtime_error(message);
|
||||
}
|
||||
|
||||
GuestTextureResource PlaneResource(std::uint64_t address, std::uint32_t format, std::uint32_t side = Side) {
|
||||
GuestTextureResource resource{};
|
||||
resource.baseAddress = address;
|
||||
resource.width = side;
|
||||
resource.height = side;
|
||||
resource.mipCount = 1;
|
||||
resource.tileMode = TextureTileMode::kLinear;
|
||||
resource.dimension = TextureDimension::k2D;
|
||||
resource.format = format;
|
||||
resource.dstSelX = 4;
|
||||
resource.dstSelY = 5;
|
||||
resource.dstSelZ = 6;
|
||||
resource.dstSelW = 7;
|
||||
return resource;
|
||||
}
|
||||
|
||||
std::size_t CountFloats(const std::uint8_t* bytes, float value) {
|
||||
std::size_t count = 0;
|
||||
for (std::size_t i = 0; i < Side * Side; ++i) {
|
||||
float texel = 0;
|
||||
std::memcpy(&texel, bytes + i * sizeof(float), sizeof(texel));
|
||||
if (texel == value) ++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void Run(Context context, std::uint8_t* block) {
|
||||
const auto address = reinterpret_cast<std::uint64_t>(block);
|
||||
const auto stencilAddress = address + BlockBytes / 2;
|
||||
const auto submit = [&](Recorder& recorder) {
|
||||
recorder.Submit();
|
||||
Check(context.Function<PFN_vkQueueWaitIdle>("vkQueueWaitIdle")(context.queue), "vkQueueWaitIdle");
|
||||
};
|
||||
Recorder recorder(context);
|
||||
recorder.Activate();
|
||||
const auto readBack = [&](StorageTexture& texture) {
|
||||
texture.MarkDirty();
|
||||
texture.WriteBack();
|
||||
submit(recorder);
|
||||
};
|
||||
const DepthTarget target{address, stencilAddress, {Side, Side}, VK_FORMAT_D32_SFLOAT_S8_UINT, 0.25f, 7};
|
||||
Require(DepthSurfaceView(context, target) != VK_NULL_HANDLE, "depth surface was not created");
|
||||
Require(DepthSurfaceStorage(context, {}, PlaneResource(address, Format32Float, Side / 2), 0) == nullptr, "a storage view of another extent over the depth memory was taken for the depth surface");
|
||||
Require(DepthSurfaceTexture(context, {}, PlaneResource(address, Format32Float, Side / 2), {}) == nullptr, "a sampled view of another extent over the depth memory was taken for the depth surface");
|
||||
Require(DepthSurfaceStorage(context, {}, PlaneResource(address, Format8Uint), 0) == nullptr, "an 8-bit storage view of the depth plane was taken for the depth surface");
|
||||
ExpectFailure([&] { static_cast<void>(DepthSurfaceStorage(context, {}, PlaneResource(address, Format32Float), 1)); }, "not implemented", "a storage view of another level of the depth plane was accepted");
|
||||
const auto depth = DepthSurfaceStorage(context, {}, PlaneResource(address, Format32Float), 0);
|
||||
Require(depth != nullptr, "the depth plane has no storage image");
|
||||
submit(recorder);
|
||||
readBack(*depth);
|
||||
Require(CountFloats(block, 0.25f) == Side * Side, "the storage image did not receive the cleared depth");
|
||||
const std::array<std::uint32_t, 4> pattern{std::bit_cast<std::uint32_t>(0.75f), std::bit_cast<std::uint32_t>(0.75f), std::bit_cast<std::uint32_t>(0.75f), std::bit_cast<std::uint32_t>(0.75f)};
|
||||
const char* refusal = nullptr;
|
||||
Require(depth->FillClear(pattern, StorageTexture::WholeImage, refusal), refusal != nullptr ? refusal : "the storage image could not be filled");
|
||||
Require(DepthSurfaceStoreBack(context, *depth), "the depth plane's storage image was not stored back");
|
||||
submit(recorder);
|
||||
Require(DepthSurfaceStorage(context, {}, PlaneResource(address, Format32Float), 0) == depth, "the depth plane's storage image was not reused");
|
||||
submit(recorder);
|
||||
readBack(*depth);
|
||||
Require(CountFloats(block, 0.75f) == Side * Side, "the stored depth did not come back from the depth surface");
|
||||
const auto stencil = DepthSurfaceStorage(context, {}, PlaneResource(stencilAddress, Format8Uint), 0);
|
||||
Require(stencil != nullptr && stencil != depth, "the stencil plane has no storage image of its own");
|
||||
submit(recorder);
|
||||
readBack(*stencil);
|
||||
const auto stencilBytes = DescribeSurface(PlaneResource(stencilAddress, Format8Uint)).guestBytes;
|
||||
Require(stencilBytes <= BlockBytes / 2 && std::count(block + BlockBytes / 2, block + BlockBytes / 2 + stencilBytes, 7) == static_cast<std::ptrdiff_t>(Side * Side), "the storage image did not receive the cleared stencil");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
auto context = device->GetContext();
|
||||
TextureDetiler detiler(context);
|
||||
context.detiler = &detiler;
|
||||
auto* block = Storage.data() + (BlockBytes - reinterpret_cast<std::uintptr_t>(Storage.data()) % BlockBytes) % BlockBytes;
|
||||
GuestAllocations::Mutation().Add(block, BlockBytes, true, true, true);
|
||||
{
|
||||
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
|
||||
Run(context, block);
|
||||
ClearDepthSurfaces(context.device);
|
||||
}
|
||||
GuestAllocations::Mutation().Remove(block);
|
||||
std::cout << "depth surface storage access tests passed\n";
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue