feat(agc-driver): sample depth and stencil surfaces from their GPU images

A depth target lives only in a GPU image (its guest surface is never
written), so a texture over its depth or stencil plane now samples that
image through a view of the plane's aspect: R32_SFLOAT for D32, R16_UNORM
for D16, R8_UINT for stencil, one level and slice at the surface's extent.
Other shapes, and storage access to a depth surface, throw. Depth images
get the sampled usage, and the barriers after a render pass and before a
dispatch include depth attachment writes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
STL1te 2026-09-29 11:19:03 +03:00
parent b7eaabbc1e
commit 30c0cf1dfb
8 changed files with 80 additions and 11 deletions

View File

@ -2871,7 +2871,7 @@ void VulkanDevice::recordDispatch(RecordedDispatch& record) {
Graphics::Recorder::CountMerged(CommandClass::DispatchLeading);
} else {
const auto timing = recorder.BeginGpuTiming(CommandClass::DispatchLeading);
Graphics::RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT);
Graphics::RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT);
Graphics::Recorder::CountBarriers(CommandClass::DispatchLeading);
recorder.EndGpuTiming(timing);
++d.preBarriersRecorded;

View File

@ -1,15 +1,20 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_DEPTHSURFACE_HPP
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_DEPTHSURFACE_HPP
#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp"
#include "prx/libSceAgcDriver/Graphics/include/State.hpp"
#include <cstdint>
#include <memory>
#include <span>
namespace AgcDriver::Graphics {
class Texture;
// The attachment view of the device's image for a depth/stencil target, made on first use. The
// image lives on the GPU only, always in the general layout: its guest surface (tiled, HTILE
// compressed) is never read or written, so a new image starts at the target's clear values, and
// texture reads of the surface are refused (DepthSurfaceAt). Images stay until the device goes
// shaders sample the image itself (DepthSurfaceTexture). Images stay until the device goes
// (ClearDepthSurfaces), so their views are stable for cached framebuffers.
// ponytail: never evicted, one image per distinct surface; evict (and keep recorded ones alive) if
// titles churn through many.
@ -17,6 +22,10 @@ VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target);
void ClearDepthSurfaces(VkDevice device);
// Whether the depth or stencil plane of a surface drawn with starts at `address`.
bool DepthSurfaceAt(std::uint64_t address);
// The texture sampling the depth or stencil plane that starts at the descriptor's base address,
// or null when no surface drawn with on this device has a plane there. The plane is read in place,
// so the descriptor must describe it (its format, extent, one level and slice); anything else throws.
std::shared_ptr<Texture> DepthSurfaceTexture(const Context& context, std::span<const std::uint32_t> words, const GuestTextureResource& resource, VkComponentMapping components);
}

View File

@ -57,6 +57,9 @@ public:
// compute pass writing it and the next pass sampling it share one image and copy nothing.
// CanCopyFrom says whether the two descriptors address the same surface compatibly.
Texture(const Context& context, const std::shared_ptr<StorageTexture>& source, const GuestTextureResource& descriptor, VkComponentMapping components);
// A view of one plane (aspect) of a depth surface's image, which stays in the general layout;
// the surface (DepthSurface.hpp) owns the image and its views.
Texture(const Context& context, VkImage depthImage, VkFormat depthFormat, VkImageAspectFlags aspect, VkComponentMapping components);
static bool CanCopyFrom(const StorageTexture& source, const GuestTextureResource& descriptor);
~Texture();
Texture(const Texture&) = delete;

View File

@ -1,9 +1,15 @@
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Resources.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
#include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp"
#include <algorithm>
#include <array>
#include <cstdio>
#include <map>
#include <memory>
#include <mutex>
#include <stdexcept>
#include <vector>
namespace AgcDriver::Graphics {
@ -16,6 +22,7 @@ public:
VkFormatProperties properties{};
context.formatProperties(context.physical, target.format, &properties);
Require((properties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0, "depth/stencil format " + std::to_string(target.format) + " cannot be an attachment on this device");
Require((properties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) != 0, "depth/stencil format " + std::to_string(target.format) + " cannot be sampled on this device");
Require(target.extent.width <= context.limits.maxFramebufferWidth && target.extent.height <= context.limits.maxFramebufferHeight, "depth target exceeds framebuffer limits");
const VkImageAspectFlags aspects = VK_IMAGE_ASPECT_DEPTH_BIT | (target.stencilAddress != 0 ? VK_IMAGE_ASPECT_STENCIL_BIT : 0u);
try {
@ -27,7 +34,7 @@ public:
info.arrayLayers = 1;
info.samples = VK_SAMPLE_COUNT_1_BIT;
info.tiling = VK_IMAGE_TILING_OPTIMAL;
info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
Check(context.Function<PFN_vkCreateImage>("vkCreateImage")(context.device, &info, nullptr, &image), "vkCreateImage depth");
@ -74,6 +81,31 @@ public:
DepthSurface(const DepthSurface&) = delete;
DepthSurface& operator=(const DepthSurface&) = delete;
// The texture sampling the plane at the descriptor's base address, one per descriptor.
std::shared_ptr<Texture> Sampled(std::span<const std::uint32_t> words, const GuestTextureResource& resource, VkComponentMapping components) {
std::array<std::uint32_t, 12> key{};
std::copy_n(words.begin(), std::min<std::size_t>(words.size(), 8), key.begin());
key[8] = components.r;
key[9] = components.g;
key[10] = components.b;
key[11] = components.a;
if (const auto found = textures.find(key); found != textures.end()) return found->second;
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 auto format = ResolveTextureFormat(resource.format);
if (format != expected || resource.dimension != TextureDimension::k2D || resource.width != target.extent.width || resource.height != target.extent.height || resource.baseLevel != 0 || resource.lastLevel != 0 || resource.baseArray != 0) {
char text[448];
std::snprintf(text, sizeof(text), "AGC graphics: sampling the %s plane of depth surface 0x%llx (%ux%u, vk format %d) as a %ux%u texture of guest format %u (vk %d), tile mode %u, dimension %d, levels %u-%u, slice %u is not implemented (T# %08x %08x %08x %08x %08x %08x %08x %08x)",
stencil ? "stencil" : "depth", static_cast<unsigned long long>(target.address), target.extent.width, target.extent.height, static_cast<int>(target.format), resource.width, resource.height, resource.format, static_cast<int>(format),
static_cast<unsigned>(resource.tileMode), static_cast<int>(resource.dimension), resource.baseLevel, resource.lastLevel, resource.baseArray, key[0], key[1], key[2], key[3], key[4], key[5], key[6], key[7]);
throw std::runtime_error(text);
}
auto texture = std::make_shared<Texture>(context, image, target.format, stencil ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT, components);
textures.emplace(key, texture);
return texture;
}
const Context context;
const DepthTarget target;
VkImage image = VK_NULL_HANDLE;
@ -81,7 +113,10 @@ public:
VkDeviceMemory memory = VK_NULL_HANDLE;
private:
std::map<std::array<std::uint32_t, 12>, std::shared_ptr<Texture>> textures;
void release() noexcept {
textures.clear();
if (view) context.Function<PFN_vkDestroyImageView>("vkDestroyImageView")(context.device, view, nullptr);
if (image) context.Function<PFN_vkDestroyImage>("vkDestroyImage")(context.device, image, nullptr);
if (memory) context.Function<PFN_vkFreeMemory>("vkFreeMemory")(context.device, memory, nullptr);
@ -122,6 +157,16 @@ void ClearDepthSurfaces(VkDevice device) {
std::erase_if(surfaces(), [&](const auto& surface) { return surface->context.device == device; });
}
std::shared_ptr<Texture> DepthSurfaceTexture(const Context& context, std::span<const std::uint32_t> words, const GuestTextureResource& resource, VkComponentMapping components) {
std::lock_guard lock(surfacesMutex());
const auto& list = surfaces();
// The newest surface made at the address.
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)->Sampled(words, resource, components);
}
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

@ -1,7 +1,6 @@
#include <cstdio>
#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Context.hpp"
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
#include <algorithm>
#include <stdexcept>
#include <string>
@ -47,12 +46,6 @@ GuestTextureResource DecodeTextureResource(std::span<const std::uint32_t> words)
const auto base40 = (static_cast<std::uint64_t>(words[0]) | (static_cast<std::uint64_t>(words[1]) << 32u)) & 0xffffffffffull;
const auto baseAddress = base40 << 8u;
Require(baseAddress != 0, "guest texture descriptor has a null base address");
// A depth surface's contents live on the GPU only; its guest memory is stale.
if (DepthSurfaceAt(baseAddress)) {
char text[96];
std::snprintf(text, sizeof(text), "AGC graphics: sampling depth/stencil surface 0x%llx is not implemented", static_cast<unsigned long long>(baseAddress));
throw std::runtime_error(text);
}
const auto minLod = (words[1] >> 8u) & 0xfffu;
const auto format = (words[1] >> 20u) & 0x1ffu;

View File

@ -1304,7 +1304,7 @@ void Recorder::endOpenRenderPass() {
context.Resolved(&DeviceFunctions::cmdEndRenderPass, "vkCmdEndRenderPass")(open->commands);
// The pass's attachment and shader writes are visible to everything recorded after it (the
// host sees them at the batch's fence).
recordBarrier(open->commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT);
recordBarrier(open->commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT);
CountBarriers(CommandClass::Draw);
EndGpuTiming(pass.timing);
open->coveredAccess = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;

View File

@ -1,4 +1,5 @@
#include "prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp"
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
#include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp"
#include <algorithm>
@ -262,6 +263,8 @@ std::shared_ptr<StorageTexture> sampledStorageSource(const Context& context, con
// `guestBytes` is the surface size when the caller described the surface already (0: described here).
std::shared_ptr<Texture> cachedTexture(const Context& context, std::span<const std::uint32_t> words, const GuestTextureResource& resource, VkComponentMapping components, std::uint64_t guestBytes = 0) {
CaptureTrace::Log("sampled-lookup address=%llx width=%u height=%u dcc=%llx", static_cast<unsigned long long>(resource.baseAddress), resource.width, resource.height, static_cast<unsigned long long>(resource.dccAddress));
// A depth surface's contents live in its GPU image; its guest memory is stale.
if (auto depth = DepthSurfaceTexture(context, words, resource, components)) return depth;
static const bool disabled = std::getenv("APS5_NO_TEXTURE_CACHE") != nullptr;
const bool profile = LookupOutcomes::Profiled();
const auto start = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
@ -474,6 +477,11 @@ std::array<std::uint32_t, 8> SurfaceKey(const Context& context, const GuestTextu
// `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& resource, std::uint32_t mip, std::uint64_t guestBytes) {
if (DepthSurfaceAt(resource.baseAddress)) {
char text[112];
std::snprintf(text, sizeof(text), "AGC graphics: storage image access to depth/stencil surface 0x%llx is not implemented", static_cast<unsigned long long>(resource.baseAddress));
throw std::runtime_error(text);
}
static const bool disabled = std::getenv("APS5_NO_TEXTURE_CACHE") != nullptr;
if (disabled) return std::make_shared<StorageTexture>(context, *context.detiler, resource, mip);
static_cast<void>(words);

View File

@ -457,6 +457,17 @@ Texture::Texture(const Context& context, const std::shared_ptr<StorageTexture>&
}
}
Texture::Texture(const Context& context, VkImage depthImage, VkFormat depthFormat, VkImageAspectFlags aspect, VkComponentMapping components) : context(context) {
layout = VK_IMAGE_LAYOUT_GENERAL;
VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
viewInfo.image = depthImage;
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
viewInfo.format = depthFormat;
viewInfo.components = components;
viewInfo.subresourceRange = {aspect, 0, 1, 0, 1};
Check(context.Function<PFN_vkCreateImageView>("vkCreateImageView")(context.device, &viewInfo, nullptr, &view), "vkCreateImageView depth plane");
}
Texture::~Texture() {
release();
}