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:
Simon 2026-10-12 13:43:10 +02:00 committed by Dean Galvin
parent 37dd483084
commit 27e1122774
4 changed files with 343 additions and 7 deletions

View File

@ -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);
}

View File

@ -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; });

View File

@ -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),

View File

@ -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;
}
}