Merge pull request #4500 from ee6aba54/feat/agc-depth-surface-storage
feat(libSceAgcDriver): storage image access to depth surfaces
This commit is contained in:
commit
0d54077b9a
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -153,6 +153,56 @@ 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.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 (resource.dimension != TextureDimension::k2D) {
|
||||
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>(resource.baseAddress), resource.width, resource.height, resource.format, mip, static_cast<int>(resource.dimension), static_cast<int>(resource.tileMode));
|
||||
throw std::runtime_error(text);
|
||||
}
|
||||
Require(target.samples == 1, "storage image access to a multisampled depth surface is not implemented");
|
||||
if (!stencil && hostFormat != target.format) {
|
||||
char text[224];
|
||||
std::snprintf(text, sizeof(text), "AGC graphics: storage image access to the depth plane of depth surface 0x%llx (vk format %d) stored as vk format %d is not implemented", static_cast<unsigned long long>(target.address), static_cast<int>(target.format), static_cast<int>(hostFormat));
|
||||
throw std::runtime_error(text);
|
||||
}
|
||||
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) {
|
||||
std::erase_if(plane.replaced, [](const auto& old) { return old.expired(); });
|
||||
if (plane.storage != nullptr) plane.replaced.push_back(plane.storage);
|
||||
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) {
|
||||
copyPlane(plane, stencil, false);
|
||||
written = ++depthWrites;
|
||||
return true;
|
||||
}
|
||||
if (std::none_of(plane.replaced.begin(), plane.replaced.end(), [&](const auto& old) { return old.lock().get() == &candidate; })) continue;
|
||||
char text[288];
|
||||
std::snprintf(text, sizeof(text), "AGC graphics: storage image writes to the %s plane of depth surface 0x%llx through a view that another view of the plane (guest format %u, tile mode %d) replaced are not implemented", stencil ? "stencil" : "depth", static_cast<unsigned long long>(target.address), plane.storage->Descriptor().format, static_cast<int>(plane.storage->Descriptor().tileMode));
|
||||
throw std::runtime_error(text);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const Context context;
|
||||
const DepthTarget target;
|
||||
const VkFormat hostFormat;
|
||||
|
|
@ -161,8 +211,41 @@ public:
|
|||
VkDeviceMemory memory = VK_NULL_HANDLE;
|
||||
|
||||
private:
|
||||
struct PlaneStorage {
|
||||
std::shared_ptr<StorageTexture> storage;
|
||||
std::vector<std::weak_ptr<StorageTexture>> replaced;
|
||||
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 +681,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),
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include <map>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace {
|
||||
|
|
@ -192,6 +193,14 @@ void TestD16StencilFallback(const Context& context) {
|
|||
};
|
||||
Require(refused({d16s8, FormatR16Unorm, 256, 256, TileDepth64KB, Type2DArray, 1}), "two slices of a D16 surface stored as D24 must be refused, as the plane copy would read the wider image as 16-bit texels");
|
||||
Require(refused({d16s8, FormatR16Uint, 256, 256, TileStandard64KB}), "an R16_UINT view of a D16 surface stored as D24 must be refused");
|
||||
std::string storageError;
|
||||
try {
|
||||
const auto words = tsharp({d16s8, FormatR16Unorm, 256, 256});
|
||||
DepthSurfaceStorage(without, words, DecodeTextureResource(words), 0);
|
||||
} catch (const std::runtime_error& error) {
|
||||
storageError = error.what();
|
||||
}
|
||||
Require(storageError.find("depth plane") != std::string::npos && storageError.find("stored as") != std::string::npos, "storage access to the depth plane of a D16 surface stored as D24 must throw");
|
||||
ClearDepthSurfaces(without.device);
|
||||
|
||||
static int onlyTag = 0;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,314 @@
|
|||
#include "DepthFastClearHarness.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
|
||||
#include "prx/libc/include/GuestAllocations.hpp"
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#endif
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using AgcDriver::Graphics::Require;
|
||||
using DepthFastClearHarness::Covered;
|
||||
using DepthFastClearHarness::Draw;
|
||||
using DepthFastClearHarness::Height;
|
||||
using DepthFastClearHarness::Pixels;
|
||||
using DepthFastClearHarness::Shaders;
|
||||
using DepthFastClearHarness::Surface;
|
||||
using DepthFastClearHarness::Width;
|
||||
|
||||
constexpr std::uint32_t Threads = 32;
|
||||
constexpr std::uint32_t Format8Uint = 5;
|
||||
constexpr std::uint32_t Format32Float = 22;
|
||||
constexpr std::uint32_t Type2D = 9;
|
||||
constexpr std::uint32_t Type2DArray = 13;
|
||||
constexpr std::uint32_t TileLinear = 0;
|
||||
constexpr std::uint32_t TileStandard256B = 1;
|
||||
constexpr std::size_t BlockBytes = 1u << 20u;
|
||||
constexpr std::size_t StencilOffset = 1u << 16u;
|
||||
constexpr std::size_t OutputOffset = 2u << 16u;
|
||||
constexpr std::size_t MultisampledOffset = 4u << 16u;
|
||||
constexpr std::size_t MultisampledColorOffset = 5u << 16u;
|
||||
constexpr std::size_t SampledOffset = 8u << 16u;
|
||||
constexpr std::size_t LaterDepthOffset = 10u << 16u;
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 14> ScaleDepthCode{
|
||||
0x7e3c0300u, 0x7e3e0280u, 0x7e140280u, 0xf0001108u, 0x00010a1eu, 0xbf8c3f70u, 0x34060082u,
|
||||
0xe0701000u, 0x80000a03u, 0x1014140cu, 0xf0201108u, 0x00010a1eu, 0xbf810000u, 0xbf810000u,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 14> AddStencilCode{
|
||||
0x7e3c0300u, 0x7e3e0280u, 0x7e140280u, 0xf0001108u, 0x00010a1eu, 0xbf8c3f70u, 0x34060082u,
|
||||
0xe0701000u, 0x80000a03u, 0x4a141484u, 0xf0201108u, 0x00010a1eu, 0xbf810000u, 0xbf810000u,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 9> StoreCode{
|
||||
0x7e280d00u, 0x102828ffu, 0x3c800000u, 0x7e3c0300u, 0x7e3e0280u, 0xf0201108u, 0x0001141eu, 0xbf810000u, 0xbf810000u,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 10> StoreLayerCode{
|
||||
0x7e280d00u, 0x1028280eu, 0x7e3c0300u, 0x7e3e0280u, 0x7e40020cu, 0x7e42020du, 0xf0241128u, 0x0001141eu, 0xbf810000u, 0xbf810000u,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 10> StoreTwoViewsCode{
|
||||
0x7e280d00u, 0x102828ffu, 0x3c800000u, 0x7e3c0300u, 0x7e3e0280u, 0xf0201108u, 0x0001141eu, 0xf0201108u, 0x0003141eu, 0xbf810000u,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 11> LoadLayerCode{
|
||||
0x7e020280u, 0x7e04020cu, 0xf0001128u, 0x00010300u, 0xbf8c3f70u, 0x34080082u, 0xe0701000u, 0x80000304u, 0xbf810000u, 0xbf810000u, 0xbf810000u,
|
||||
};
|
||||
|
||||
class GuestBlock {
|
||||
public:
|
||||
GuestBlock() {
|
||||
#ifdef _WIN32
|
||||
block = static_cast<std::uint8_t*>(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
|
||||
#else
|
||||
block = static_cast<std::uint8_t*>(std::aligned_alloc(65536, BlockBytes));
|
||||
#endif
|
||||
Require(block != nullptr, "depth storage dispatch: cannot allocate the guest block");
|
||||
std::memset(block, 0, BlockBytes);
|
||||
GuestAllocations::Mutation().Add(block, BlockBytes, true, true, true);
|
||||
}
|
||||
|
||||
~GuestBlock() {
|
||||
GuestAllocations::Mutation().Remove(block);
|
||||
#ifdef _WIN32
|
||||
VirtualFree(block, 0, MEM_RELEASE);
|
||||
#else
|
||||
std::free(block);
|
||||
#endif
|
||||
}
|
||||
|
||||
GuestBlock(const GuestBlock&) = delete;
|
||||
GuestBlock& operator=(const GuestBlock&) = delete;
|
||||
|
||||
std::uint8_t* Data() { return block; }
|
||||
|
||||
private:
|
||||
std::uint8_t* block = nullptr;
|
||||
};
|
||||
|
||||
std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t bytes) {
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(data);
|
||||
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu), bytes, 0x31016facu};
|
||||
}
|
||||
|
||||
std::array<std::uint32_t, 8> TextureDescriptor(const void* data, std::uint32_t format, std::uint32_t type = Type2D, std::uint32_t tile = TileLinear, std::uint32_t lastArray = 0) {
|
||||
const auto address = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(data));
|
||||
return {
|
||||
static_cast<std::uint32_t>(address >> 8u),
|
||||
static_cast<std::uint32_t>((address >> 40u) & 0xffu) | (format << 20u) | (((Width - 1u) & 3u) << 30u),
|
||||
((Width - 1u) >> 2u) | ((Height - 1u) << 14u),
|
||||
0xfacu | (tile << 20u) | (type << 28u),
|
||||
lastArray, 0u, 0u, 0u,
|
||||
};
|
||||
}
|
||||
|
||||
ShaderRecompiler::RecompileResult Compile(AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> code, std::span<const std::uint32_t> userData) {
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
|
||||
const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1};
|
||||
ShaderRecompiler::RecompileRequest request{
|
||||
{ShaderRecompiler::ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(code.data()), code, 0, {}},
|
||||
{32, 0, userData, compute, std::nullopt, std::nullopt, memory},
|
||||
device.Target(),
|
||||
{0, 0, 0, 128}
|
||||
};
|
||||
request.useCache = false;
|
||||
return ShaderRecompiler::Recompile(request);
|
||||
}
|
||||
|
||||
ShaderRecompiler::RecompileResult PlaneProgram(AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> code, std::span<std::uint32_t> output, const std::uint8_t* plane, std::uint32_t format, float factor) {
|
||||
std::vector<std::uint32_t> userData(16, 0u);
|
||||
const auto buffer = BufferDescriptor(output.data(), static_cast<std::uint32_t>(output.size_bytes()));
|
||||
const auto texture = TextureDescriptor(plane, format);
|
||||
std::copy(buffer.begin(), buffer.end(), userData.begin());
|
||||
std::copy(texture.begin(), texture.end(), userData.begin() + 4);
|
||||
userData[12] = std::bit_cast<std::uint32_t>(factor);
|
||||
return Compile(device, code, userData);
|
||||
}
|
||||
|
||||
void RunPlane(AgcDriver::VulkanDevice& device, const ShaderRecompiler::RecompileResult& program, std::span<const std::uint32_t> code, std::span<std::uint32_t> output, std::uint32_t expected, const std::string& what) {
|
||||
std::fill(output.begin(), output.end(), 0xdeadbeefu);
|
||||
device.Dispatch(program, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
|
||||
device.WaitIdle();
|
||||
for (std::uint32_t lane = 0; lane < Threads; ++lane) {
|
||||
Require(output[lane] == expected, what + ": the dispatch read " + std::to_string(output[lane]) + " at texel " + std::to_string(lane) + ", expected " + std::to_string(expected));
|
||||
}
|
||||
}
|
||||
|
||||
void RequireRow(std::uint32_t covered, const std::string& what) {
|
||||
Require(covered == Threads, what + ": " + std::to_string(covered) + " pixels passed, expected the " + std::to_string(Threads) + " the dispatch stored");
|
||||
for (std::uint32_t x = 0; x < Threads; ++x) {
|
||||
Require(std::to_integer<std::uint8_t>(Pixels[x * 4u]) == 255u, what + ": pixel " + std::to_string(x) + " of the first row did not pass");
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TAction>
|
||||
void ExpectRefusal(TAction action, const char* expected, const std::string& what) {
|
||||
try {
|
||||
action();
|
||||
} catch (const std::exception& error) {
|
||||
Require(std::string(error.what()).find(expected) != std::string::npos, what + " threw another error: " + error.what());
|
||||
return;
|
||||
}
|
||||
throw std::runtime_error(what + " was accepted");
|
||||
}
|
||||
|
||||
VkStencilOpState StencilEqual(std::uint32_t reference) {
|
||||
return {VK_STENCIL_OP_KEEP, VK_STENCIL_OP_KEEP, VK_STENCIL_OP_KEEP, VK_COMPARE_OP_EQUAL, 0xffu, 0u, reference};
|
||||
}
|
||||
|
||||
void StoreBackCase(AgcDriver::VulkanDevice& device, const Shaders& shaders, std::uint8_t* block) {
|
||||
auto* depth = block;
|
||||
auto* stencil = block + StencilOffset;
|
||||
const std::span<std::uint32_t> output(reinterpret_cast<std::uint32_t*>(block + OutputOffset), Threads);
|
||||
const Surface surface{reinterpret_cast<std::uintptr_t>(depth), reinterpret_cast<std::uintptr_t>(stencil), 0, false, VK_FORMAT_D32_SFLOAT_S8_UINT};
|
||||
const VkStencilOpState writeFive{VK_STENCIL_OP_KEEP, VK_STENCIL_OP_REPLACE, VK_STENCIL_OP_KEEP, VK_COMPARE_OP_ALWAYS, 0xffu, 0xffu, 5u};
|
||||
const auto bits = [](float value) { return std::bit_cast<std::uint32_t>(value); };
|
||||
Require(Draw(device, shaders, surface, {.depthWrite = true, .depthCompare = VK_COMPARE_OP_ALWAYS, .stencilTest = true, .stencil = writeFive}) == Covered, "the depth and stencil producer did not cover the surface");
|
||||
|
||||
const auto raise = PlaneProgram(device, ScaleDepthCode, output, depth, Format32Float, 1.5f);
|
||||
const auto lower = PlaneProgram(device, ScaleDepthCode, output, depth, Format32Float, 0.5f);
|
||||
RunPlane(device, raise, ScaleDepthCode, output, bits(0.5f), "first depth dispatch");
|
||||
RequireRow(Draw(device, shaders, surface, {.depthCompare = VK_COMPARE_OP_LESS}), "depth test against the first dispatch's depth");
|
||||
RunPlane(device, lower, ScaleDepthCode, output, bits(0.75f), "second depth dispatch");
|
||||
RequireRow(Draw(device, shaders, surface, {.depthCompare = VK_COMPARE_OP_GREATER}), "depth test against the second dispatch's depth");
|
||||
Require(Draw(device, shaders, surface, {.depthCompare = VK_COMPARE_OP_LESS}) == 0, "the first dispatch's depth outlived the second dispatch");
|
||||
RunPlane(device, raise, ScaleDepthCode, output, bits(0.375f), "third depth dispatch, its program's second");
|
||||
RequireRow(Draw(device, shaders, surface, {.depthCompare = VK_COMPARE_OP_LESS}), "depth test against the third dispatch's depth");
|
||||
|
||||
const auto add = PlaneProgram(device, AddStencilCode, output, stencil, Format8Uint, 0.0f);
|
||||
RunPlane(device, add, AddStencilCode, output, 5u, "first stencil dispatch");
|
||||
RequireRow(Draw(device, shaders, surface, {.depthTest = false, .stencilTest = true, .stencil = StencilEqual(9u)}), "stencil test against the first dispatch's stencil");
|
||||
RunPlane(device, add, AddStencilCode, output, 9u, "second stencil dispatch");
|
||||
RequireRow(Draw(device, shaders, surface, {.depthTest = false, .stencilTest = true, .stencil = StencilEqual(13u)}), "stencil test against the second dispatch's stencil");
|
||||
Require(Draw(device, shaders, surface, {.depthTest = false, .stencilTest = true, .stencil = StencilEqual(5u)}) == Covered - Threads, "a stencil dispatch changed stencil values it did not store");
|
||||
RequireRow(Draw(device, shaders, surface, {.depthCompare = VK_COMPARE_OP_LESS}), "depth test after the stencil dispatches");
|
||||
|
||||
AgcDriver::Graphics::StorageTexture::FlushPending(reinterpret_cast<std::uintptr_t>(depth), Width * Height * sizeof(float), nullptr, "test");
|
||||
device.WaitIdle();
|
||||
for (std::uint32_t x = 0; x < Width; ++x) {
|
||||
float texel = 0.0f;
|
||||
std::memcpy(&texel, depth + x * sizeof(float), sizeof(texel));
|
||||
Require(texel == (x < Threads ? 0.5625f : 0.5f), "guest depth texel " + std::to_string(x) + " is " + std::to_string(texel) + " after the flush");
|
||||
}
|
||||
AgcDriver::Graphics::StorageTexture::FlushPending(reinterpret_cast<std::uintptr_t>(stencil), Width * Height, nullptr, "test");
|
||||
device.WaitIdle();
|
||||
for (std::uint32_t x = 0; x < Width; ++x) {
|
||||
Require(stencil[x] == (x < Threads ? 13u : 5u), "guest stencil texel " + std::to_string(x) + " is " + std::to_string(stencil[x]) + " after the flush");
|
||||
}
|
||||
}
|
||||
|
||||
void ArrayViewCase(AgcDriver::VulkanDevice& device, std::uint8_t* block) {
|
||||
std::vector<std::uint32_t> userData(16, 0u);
|
||||
const auto texture = TextureDescriptor(block, Format32Float, Type2DArray);
|
||||
std::copy(texture.begin(), texture.end(), userData.begin() + 4);
|
||||
userData[14] = std::bit_cast<std::uint32_t>(1.0f / 64.0f);
|
||||
const auto program = Compile(device, StoreLayerCode, userData);
|
||||
ExpectRefusal([&] { device.Dispatch(program, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(StoreLayerCode.data())); }, "storage image access to depth/stencil surface", "a one-layer 2D array storage view of the depth plane");
|
||||
}
|
||||
|
||||
void MultisampledCase(AgcDriver::VulkanDevice& device, const Shaders& compiled, std::uint8_t* block) {
|
||||
auto* depth = block + MultisampledOffset;
|
||||
const std::array<AgcDriver::Graphics::CompiledShader, 2> shaders{{
|
||||
{ShaderRecompiler::ShaderStage::Vertex, &compiled.vertex, 0},
|
||||
{ShaderRecompiler::ShaderStage::Fragment, &compiled.pixel, PixelPushOffset(compiled.vertexPush, device.Target())}
|
||||
}};
|
||||
AgcDriver::Graphics::State state{};
|
||||
state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, 32u, 32u, std::nullopt, std::nullopt};
|
||||
state.color = {reinterpret_cast<std::uintptr_t>(block + MultisampledColorOffset), {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, Width * Height * 16u, 0xe4u};
|
||||
state.color.samples = 4;
|
||||
state.colors = {state.color};
|
||||
state.hasColorTarget = true;
|
||||
state.depth = AgcDriver::Graphics::DepthTarget{reinterpret_cast<std::uintptr_t>(depth), 0, {Width, Height}, VK_FORMAT_D32_SFLOAT, 1.0f, 0, 0, false, 4};
|
||||
state.depthTest = true;
|
||||
state.depthWrite = true;
|
||||
state.depthCompare = VK_COMPARE_OP_ALWAYS;
|
||||
state.samples = 4;
|
||||
state.renderExtent = {Width, Height};
|
||||
state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
state.viewport = {0, static_cast<float>(Height), static_cast<float>(Width), -static_cast<float>(Height), 0, 1};
|
||||
state.negativeOneToOne = false;
|
||||
state.scissor = {{0, 0}, {Width, Height}};
|
||||
state.cullMode = VK_CULL_MODE_NONE;
|
||||
state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
|
||||
state.blend.colorWriteMask = 15;
|
||||
state.blends = {state.blend};
|
||||
state.blendConstants = {};
|
||||
const AgcDriver::Pm4::DrawParameters draw{0, static_cast<std::uint32_t>(DepthFastClearHarness::Triangles().size()), 0, 1, 0, false};
|
||||
device.Draw(state, draw, shaders);
|
||||
device.WaitIdle();
|
||||
std::vector<std::uint32_t> userData(16, 0u);
|
||||
const auto texture = TextureDescriptor(depth, Format32Float);
|
||||
std::copy(texture.begin(), texture.end(), userData.begin() + 4);
|
||||
const auto program = Compile(device, StoreCode, userData);
|
||||
ExpectRefusal([&] { device.Dispatch(program, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(StoreCode.data())); }, "storage image access to a multisampled depth surface", "a storage view of a multisampled depth plane");
|
||||
}
|
||||
|
||||
void SampledCopyCase(AgcDriver::VulkanDevice& device, const Shaders& shaders, std::uint8_t* block) {
|
||||
auto* depth = block + SampledOffset;
|
||||
const std::span<std::uint32_t> output(reinterpret_cast<std::uint32_t*>(block + OutputOffset), Threads);
|
||||
const Surface surface{reinterpret_cast<std::uintptr_t>(depth), 0, 0, false, VK_FORMAT_D32_SFLOAT};
|
||||
const Surface later{reinterpret_cast<std::uintptr_t>(block + LaterDepthOffset), 0, 0, false, VK_FORMAT_D32_SFLOAT};
|
||||
Require(Draw(device, shaders, surface, {.depthWrite = true, .depthCompare = VK_COMPARE_OP_ALWAYS}) == Covered, "the sampled depth producer did not cover the surface");
|
||||
Require(Draw(device, shaders, later, {.depthWrite = true, .depthCompare = VK_COMPARE_OP_ALWAYS}) == Covered, "the later depth producer did not cover its surface");
|
||||
|
||||
std::vector<std::uint32_t> userData(16, 0u);
|
||||
const auto buffer = BufferDescriptor(output.data(), static_cast<std::uint32_t>(output.size_bytes()));
|
||||
const auto layers = TextureDescriptor(depth, Format32Float, Type2DArray, TileLinear, 1u);
|
||||
std::copy(buffer.begin(), buffer.end(), userData.begin());
|
||||
std::copy(layers.begin(), layers.end(), userData.begin() + 4);
|
||||
const auto load = Compile(device, LoadLayerCode, userData);
|
||||
const auto raise = PlaneProgram(device, ScaleDepthCode, output, depth, Format32Float, 1.5f);
|
||||
RunPlane(device, load, LoadLayerCode, output, std::bit_cast<std::uint32_t>(0.5f), "first layer of the depth plane copy");
|
||||
RunPlane(device, load, LoadLayerCode, output, std::bit_cast<std::uint32_t>(0.5f), "first layer of the unchanged depth plane copy");
|
||||
RunPlane(device, raise, ScaleDepthCode, output, std::bit_cast<std::uint32_t>(0.5f), "depth dispatch between two plane copies");
|
||||
RunPlane(device, load, LoadLayerCode, output, std::bit_cast<std::uint32_t>(0.75f), "first layer of the depth plane copy after the dispatch stored to the plane");
|
||||
}
|
||||
|
||||
void TwoViewsCase(AgcDriver::VulkanDevice& device, std::uint8_t* block) {
|
||||
std::vector<std::uint32_t> userData(20, 0u);
|
||||
const auto first = TextureDescriptor(block, Format32Float, Type2D, TileLinear);
|
||||
const auto second = TextureDescriptor(block, Format32Float, Type2D, TileStandard256B);
|
||||
std::copy(first.begin(), first.end(), userData.begin() + 4);
|
||||
std::copy(second.begin(), second.end(), userData.begin() + 12);
|
||||
const auto program = Compile(device, StoreTwoViewsCode, userData);
|
||||
ExpectRefusal([&] { device.Dispatch(program, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(StoreTwoViewsCode.data())); }, "through a view that another view of the plane", "a dispatch storing to two views of one depth plane");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
GuestBlock block;
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
const auto shaders = DepthFastClearHarness::Compile(*device);
|
||||
StoreBackCase(*device, shaders, block.Data());
|
||||
ArrayViewCase(*device, block.Data());
|
||||
MultisampledCase(*device, shaders, block.Data());
|
||||
TwoViewsCase(*device, block.Data());
|
||||
SampledCopyCase(*device, shaders, block.Data());
|
||||
std::puts("depth storage dispatch tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -26,8 +26,8 @@
|
|||
|
||||
### Functional
|
||||
|
||||
- [Reused depth storage](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp): a storage view with another extent or a format that cannot hold the cached depth plane uses guest memory. Depth rendering is not written back to those guest bytes; this admission rule does not prove allocation reuse or preserve a depth writer through an incompatible view. Matching depth/stencil storage access still throws.
|
||||
- [Depth plane copies](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver): a depth plane sampled as a 2D array or a cube, or a D16 plane read as `R16_UINT`, is copied into an image of its own (`DepthPlaneCopy`), kept per base address, extent, format and slice count; a slice comes from the live depth surface when one is bound at the slice's address and from guest memory (detiled) otherwise. A draw or dispatch that binds the plane copies again only the slices that changed and records nothing when none did: a surface slice when its image or write serial differs from the one copied (the serial is bumped when the surface is created, when a draw binds it as its depth target and when a pending fast clear is applied), a guest-memory slice when its bytes were written since they were last read (`GuestMemory::UnchangedSince`), a snapshot write overlaps them, or its address, size or tile mode changed. The surface of the latest depth draw is copied at every use until another depth target is bound, since that draw may still be recorded after the copy. A slice's address is the base plus a whole 64 KiB-block depth slice per layer (`DepthSliceBytes`), and the cube face order is the array order; both are inferred from the titles' use, not measured on hardware. Planes with mips, a base slice other than 0, a cube with other than 1 or 6 slices, and other views of the planes still throw, and so do these copies of a 16-bit plane with stencil that the device stores as D24 or D32 (16-bit depth with stencil, below).
|
||||
- [Reused depth storage](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp): a storage view with another extent or a format that cannot hold the cached depth plane uses guest memory. Depth rendering is not written back to those guest bytes; this admission rule does not prove allocation reuse or preserve a depth writer through an incompatible view. A 2D storage view of level 0 with the plane's extent and format (32-bit float or the 32-bit depth-bits layout for the depth plane, 16-bit unorm for D16, 8-bit uint for the stencil plane) is a storage image that is [filled](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) from the depth image before every use and, when the shader writes it, copied back after the work: a whole-plane copy through a staging buffer each way. A written view is also left pending like any other storage image, so the plane's content, drawn depth included, reaches guest memory on the next flush of that range. A matching view still throws when it has another dimension (2D array, cube) or level, when the surface is multisampled, when it names the depth plane of a 16-bit depth with stencil surface the device stores in a wider format, and when it was written after another view of the same plane (another format, tile mode or address) replaced it, as two such views bound in one dispatch are. Stored depth values outside 0..1 are not clamped, and Vulkan leaves that copy undefined without `VK_EXT_depth_range_unrestricted`. `agc_driver_depth_storage_dispatch` pins these paths; none is measured on the console.
|
||||
- [Depth plane copies](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver): a depth plane sampled as a 2D array or a cube, or a D16 plane read as `R16_UINT`, is copied into an image of its own (`DepthPlaneCopy`), kept per base address, extent, format and slice count; a slice comes from the live depth surface when one is bound at the slice's address and from guest memory (detiled) otherwise. A draw or dispatch that binds the plane copies again only the slices that changed and records nothing when none did: a surface slice when its image or write serial differs from the one copied (the serial is bumped when the surface is created, when a draw binds it as its depth target, when a pending fast clear is applied and when a written storage view of the plane is copied back), a guest-memory slice when its bytes were written since they were last read (`GuestMemory::UnchangedSince`), a snapshot write overlaps them, or its address, size or tile mode changed. The surface of the latest depth draw is copied at every use until another depth target is bound, since that draw may still be recorded after the copy. A slice's address is the base plus a whole 64 KiB-block depth slice per layer (`DepthSliceBytes`), and the cube face order is the array order; both are inferred from the titles' use, not measured on hardware. Planes with mips, a base slice other than 0, a cube with other than 1 or 6 slices, and other views of the planes still throw, and so do these copies of a 16-bit plane with stencil that the device stores as D24 or D32 (16-bit depth with stencil, below).
|
||||
- [SW_64KB_D_X color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ColorTargetLayout.cpp) (libSceAgcDriver) use the existing texture block/mip geometry and equations from [AMD addrlib GFX10 non-RB+](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/imported/addrlib/src/gfx10/gfx10addrlib.cpp) for 16 pipes and 256-byte interleave. The address-reference tests use legal pipe/bank XOR bits for that configuration. Raw column bits outside the source configuration's pipe/bank mask 0xcf00 throw; their console meaning is unresolved. Console address equations and render results have not been measured.
|
||||
- [Per-face depth bias](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver): Vulkan has one depth bias per draw, so a draw that rasterizes both faces with different front and back bias (PA_SU_SC_MODE_CNTL, PA_SU_POLY_OFFSET_FRONT/BACK_*) is recorded twice, front faces with the back faces culled and then back faces with the front faces culled. The vertex stages run twice, so a vertex shader that writes memory writes twice, and all front faces land before all back faces instead of in primitive order, which differs only for blending or stencil updates that depend on the order.
|
||||
- [Partial rect-list primitives](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver): a rect-list draw whose vertex or index count is not a multiple of 3 draws its complete rectangles and leaves out the 1 or 2 vertices after them, because rect lists are drawn as Vulkan patch lists of 3 control points, which generate [⌊vertexCount/3⌋ patches](https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#drawing-patch-lists). What the console draws for the incomplete rectangle is not measured; Palworld (PPSA23126) draws 4 vertices × 9 instances as a rect list when its character creation opens.
|
||||
|
|
|
|||
Loading…
Reference in New Issue