Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,17 @@ class Texture {
// 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);
Texture(const Context& context, VkImage depthImage, VkFormat depthFormat, VkImageAspectFlags aspect, VkComponentMapping components);
struct DepthCopyTag {};
Texture(const Context& context, const std::shared_ptr<StorageTexture>& source, const GuestTextureResource& descriptor, VkComponentMapping components, DepthCopyTag);
static VkFormat DepthCopyFormat(VkFormat colorFormat);
static bool CanDepthCopyFrom(const StorageTexture& source, const GuestTextureResource& descriptor);
bool IsDepthCopy() const { return depthCopy; }
bool RefreshDepthCopy();
struct DepthCopyCounts {
std::uint64_t textures;
std::uint64_t refreshes;
};
static DepthCopyCounts TakeDepthCopyCounts();
static bool CanCopyFrom(const StorageTexture& source, const GuestTextureResource& descriptor);
~Texture();
Texture(const Texture&) = delete;
Expand Down Expand Up @@ -108,6 +119,11 @@ class Texture {
std::shared_ptr<ResidentColor> source;
std::shared_ptr<StorageTexture> storageSource;
std::unique_ptr<CommandBatch> upload;
bool depthCopy = false;
bool copiedOnce = false;
std::uint64_t copiedVersion = 0;
VkFormat depthFormat = VK_FORMAT_UNDEFINED;
std::uint32_t copyWidth = 0, copyHeight = 0, copyLevels = 0, copyLayers = 0, copyElementBytes = 0;
};

VkFormat AttachmentProxyFormat(const Context& context, VkFormat format);
Expand Down
33 changes: 29 additions & 4 deletions core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,14 @@ bool ClearedViewEnabled() {
return !disabled;
}

std::uint64_t DepthCopyMinBytes() {
static const std::uint64_t bytes = [] {
const char* text = std::getenv("APS5_DEPTH_COPY_MIN_KIB");
return (text != nullptr ? std::strtoull(text, nullptr, 10) : 1024ull) << 10u;
}();
return bytes;
}

// Surfaces whose storage image could not be made (no writable committed pages, say): remembered
// so the sampled lookup does not throw and fall back on every use. A failure is keyed by the surface
// (address and size: the heap reuses addresses) and holds only while the guest mappings are what
Expand Down Expand Up @@ -537,8 +545,10 @@ std::shared_ptr<Texture> cachedTexture(const Context& context, std::span<const s
// A storage image whose results for this surface (or for a mip chain containing it) are still on
// the GPU supplies the texture by a view of it; anything else needs those results in guest
// memory first.
const bool depthCopy = depthCompare && guestBytes >= DepthCopyMinBytes() && Texture::DepthCopyFormat(ResolveTextureFormat(resource.format)) != VK_FORMAT_UNDEFINED && resource.dimension != TextureDimension::k3D;
auto source = StorageTexture::FindPending(address, guestBytes);
if (source != nullptr && (depthCompare || !Texture::CanCopyFrom(*source, resource) || MetadataMoved(*source, resource))) source.reset();
if (source != nullptr && (depthCompare ? !depthCopy || !Texture::CanDepthCopyFrom(*source, resource) : !Texture::CanCopyFrom(*source, resource))) source.reset();
if (source != nullptr && MetadataMoved(*source, resource)) source.reset();
std::optional<DccKeys> keys;
bool clearThroughKeys = false;
if (source != nullptr && resource.dccAddress != 0 && IsDccClear(source->FilledKeys())) {
Expand All @@ -562,12 +572,13 @@ std::shared_ptr<Texture> cachedTexture(const Context& context, std::span<const s
// import, recorded behind the producer, so no bytes are read or compared on the CPU and nothing
// waits for the producer. A fast-cleared surface (keys) is viewed only through an image whose own
// descriptor carries the DCC address (below); it stays a snapshot otherwise, its texels not read.
if (!depthCompare && source == nullptr && !clearThroughKeys && SampledFromStorageEligible(context, resource, guestBytes)) {
if ((!depthCompare || depthCopy) && source == nullptr && !clearThroughKeys && SampledFromStorageEligible(context, resource, guestBytes)) {

keys = scanKeys();
if (*keys == DccKeys::Uncompressed) {
source = sampledStorageSource(context, resource, guestBytes);
} else if (ClearedViewEnabled() && StorageClearAvailable(context, resource.format, *keys)) {
if (source != nullptr && depthCompare && !Texture::CanDepthCopyFrom(*source, resource)) source.reset();
} else if (!depthCompare && ClearedViewEnabled() && StorageClearAvailable(context, resource.format, *keys)) {
// A fast-cleared surface is viewed as well, through its image cleared on the GPU (the
// lookup's Refresh, see StorageTexture::upload), when the image's own descriptor names
// the same DCC metadata so the refresh sees the keys. A snapshot of the clear texels
Expand Down Expand Up @@ -602,6 +613,11 @@ std::shared_ptr<Texture> cachedTexture(const Context& context, std::span<const s
}
if (!keys.has_value()) keys = scanKeys();
if (disabled) {
if (source != nullptr && depthCompare) {
auto texture = std::make_shared<Texture>(context, source, resource, components, Texture::DepthCopyTag{});
texture->RefreshDepthCopy();
return texture;
}
if (source != nullptr) return std::make_shared<Texture>(context, source, resource, components);
std::vector<std::byte> snapshot(bytes);
ReadTextureSurface(resource, *keys, snapshot);
Expand All @@ -618,6 +634,7 @@ std::shared_ptr<Texture> cachedTexture(const Context& context, std::span<const s
// see (keys, and no pending results to prefer) ends the view: a snapshot holds the clear.
if ((source == nullptr || source == it->source) && (source != nullptr || *keys == DccKeys::Uncompressed) && StorageImageServesKeys(*it->source, resource.dccAddress)) {
if (!GuestMemory::UnchangedSince(address, bytes, it->source->Generation())) it->source->Refresh();
if (it->texture->IsDepthCopy()) it->texture->RefreshDepthCopy();
it->keys = *keys;
touchTexture(cache, it);
logLookup({it->texture.get(), resource, guestBytes, *keys, 0, it->source.get()});
Expand Down Expand Up @@ -650,7 +667,12 @@ std::shared_ptr<Texture> cachedTexture(const Context& context, std::span<const s
if (source != nullptr) {
entry.source = source;
entry.sourceVersion = source->Version();
entry.texture = std::make_shared<Texture>(context, source, resource, components);
if (depthCompare) {
entry.texture = std::make_shared<Texture>(context, source, resource, components, Texture::DepthCopyTag{});
entry.texture->RefreshDepthCopy();
} else {
entry.texture = std::make_shared<Texture>(context, source, resource, components);
}
entry.accounted = source->Cached() ? 0u : heldBytes(*source);
counters.fromStorage.fetch_add(1, std::memory_order_relaxed);
} else {
Expand Down Expand Up @@ -2313,6 +2335,9 @@ bool ShaderResources::Revalidate(std::span<const CompiledShader> shaders, ProofR
}
}
pendingSerialSeen = EpochRevalidate() && StorageTexture::PendingSerial() == serialBefore ? serialBefore : 0;
for (const auto& texture : textures) {
if (texture != nullptr && texture->IsDepthCopy()) texture->RefreshDepthCopy();
}
return finish(fast, true);
}

Expand Down
154 changes: 154 additions & 0 deletions core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ std::atomic<std::uint64_t> pretestSkipped{0}, unregisteredDropped{0}, keyFlipKep
// Images stored by a FlushPending after a hook skip of theirs (AccessKeptByCpu), of which by the
// hook for the read site of the last skip; images evictStale dropped (the [hooksync] and [storage] lines).
std::atomic<std::uint64_t> flushedAfterSkip{0}, flushedAfterSkipSameSite{0}, staleEvicted{0};
std::atomic<std::uint64_t> depthCopyTextures{0}, depthCopyRefreshes{0};
std::atomic<std::uint64_t> refreshesProved{0};

struct StorageTraffic {
Expand Down Expand Up @@ -457,6 +458,159 @@ Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTex
}
}

VkFormat Texture::DepthCopyFormat(VkFormat colorFormat) {
switch (colorFormat) {
case VK_FORMAT_R32_SFLOAT: return VK_FORMAT_D32_SFLOAT;
case VK_FORMAT_R16_UNORM: return VK_FORMAT_D16_UNORM;
default: return VK_FORMAT_UNDEFINED;
}
}

bool Texture::CanDepthCopyFrom(const StorageTexture& source, const GuestTextureResource& descriptor) {
if (!CanCopyFrom(source, descriptor) || descriptor.dimension == TextureDimension::k3D) return false;
if (DepthCopyFormat(ResolveTextureFormat(descriptor.format)) == VK_FORMAT_UNDEFINED) return false;
if (ResolveTextureFormat(source.Descriptor().format) != ResolveTextureFormat(descriptor.format)) return false;
const auto geometry = DescribeSurface(descriptor);
return geometry.imageDepth == 1 && source.ImageDepth() == 1 && source.ImageLayers() >= geometry.imageLayers && source.Descriptor().mipCount >= descriptor.mipCount;
}

Texture::Texture(const Context& context, const std::shared_ptr<StorageTexture>& source, const GuestTextureResource& descriptor, VkComponentMapping components, DepthCopyTag) : context(context), storageSource(source), depthCopy(true) {
try {
Require(source != nullptr && CanDepthCopyFrom(*source, descriptor), "storage image cannot serve a depth copy of the sampled texture");
const auto colorFormat = ResolveTextureFormat(descriptor.format);
depthFormat = DepthCopyFormat(colorFormat);
const auto geometry = DescribeSurface(descriptor);
copyWidth = descriptor.width;
copyHeight = descriptor.height;
copyLevels = descriptor.mipCount;
copyLayers = geometry.imageLayers;
copyElementBytes = BytesPerElement(descriptor.format);
APS5_LOG_OUT("Texture address=0x%llx %ux%u mips=%u layers=%u depth copy of storage image (vk=%d -> %d)", static_cast<unsigned long long>(descriptor.baseAddress), descriptor.width, descriptor.height, descriptor.mipCount, copyLayers, static_cast<int>(colorFormat), static_cast<int>(depthFormat));

VkImageCreateInfo imageInfo{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
imageInfo.flags = descriptor.dimension == TextureDimension::kCube ? VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT : 0u;
imageInfo.imageType = ImageTypeFor(descriptor.dimension);
imageInfo.format = depthFormat;
imageInfo.extent = {descriptor.width, descriptor.height, 1u};
imageInfo.mipLevels = copyLevels;
imageInfo.arrayLayers = copyLayers;
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
Check(context.Function<PFN_vkCreateImage>("vkCreateImage")(context.device, &imageInfo, nullptr, &image), "vkCreateImage depth copy");
owned = std::make_shared<OwnedImage>(context, image, VK_NULL_HANDLE);
VkMemoryRequirements requirements{};
context.Function<PFN_vkGetImageMemoryRequirements>("vkGetImageMemoryRequirements")(context.device, image, &requirements);
VkMemoryAllocateInfo allocation{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
allocation.allocationSize = requirements.size;
allocationBytes = requirements.size;
allocation.memoryTypeIndex = context.MemoryType(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
Check(context.Function<PFN_vkAllocateMemory>("vkAllocateMemory")(context.device, &allocation, nullptr, &owned->memory), "vkAllocateMemory depth copy");
Check(context.Function<PFN_vkBindImageMemory>("vkBindImageMemory")(context.device, image, owned->memory, 0), "vkBindImageMemory depth copy");

const auto viewLevelCount = std::min(descriptor.lastLevel, descriptor.mipCount - 1u) - descriptor.baseLevel + 1u;
const auto viewLayerCount = geometry.imageLayers - descriptor.baseArray;
if (descriptor.dimension == TextureDimension::kCube) {
Require(viewLayerCount % 6u == 0, "guest cube texture view does not contain a multiple of 6 array slices");
}
VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
viewInfo.image = image;
viewInfo.viewType = ViewTypeFor(descriptor.dimension, viewLayerCount);
viewInfo.format = depthFormat;
viewInfo.components = VkComponentMapping{};
viewInfo.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT, descriptor.baseLevel, viewLevelCount, descriptor.baseArray, viewLayerCount};
VkImageViewMinLodCreateInfoEXT minLod{VK_STRUCTURE_TYPE_IMAGE_VIEW_MIN_LOD_CREATE_INFO_EXT};
ChainMinLod(context, descriptor, viewInfo, minLod);
Check(context.Function<PFN_vkCreateImageView>("vkCreateImageView")(context.device, &viewInfo, nullptr, &view), "vkCreateImageView depth copy");
createFirstLayerView(descriptor, viewInfo);
static_cast<void>(components);
depthCopyTextures.fetch_add(1, std::memory_order_relaxed);
} catch (...) {
release();
throw;
}
}

bool Texture::RefreshDepthCopy() {
if (!depthCopy || storageSource == nullptr) return false;
if (copiedOnce && storageSource->Version() == copiedVersion) return false;
std::uint64_t linearBytes = 0;
for (std::uint32_t level = 0; level < copyLevels; ++level) linearBytes += static_cast<std::uint64_t>(std::max(copyWidth >> level, 1u)) * std::max(copyHeight >> level, 1u) * copyElementBytes * copyLayers;
auto linear = std::make_shared<DeviceBuffer>(context, static_cast<std::size_t>(linearBytes), VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT);
std::vector<VkBufferImageCopy> fromSource, toDepth;
std::uint64_t offset = 0;
for (std::uint32_t level = 0; level < copyLevels; ++level) {
const VkExtent3D extent{std::max(copyWidth >> level, 1u), std::max(copyHeight >> level, 1u), 1u};
VkBufferImageCopy region{};
region.bufferOffset = offset;
region.bufferRowLength = 0;
region.bufferImageHeight = 0;
region.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, level, 0, copyLayers};
region.imageOffset = {0, 0, 0};
region.imageExtent = extent;
fromSource.push_back(region);
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
toDepth.push_back(region);
offset += static_cast<std::uint64_t>(extent.width) * extent.height * copyElementBytes * copyLayers;
}
auto* recorder = Recorder::Active();
std::unique_ptr<CommandBatch> batch;
VkCommandBuffer commands = VK_NULL_HANDLE;
if (recorder != nullptr) {
commands = recorder->Commands();
recorder->Keep(linear);
recorder->Keep(owned);
recorder->Keep(storageSource);
} else {
batch = std::make_unique<CommandBatch>(context);
commands = batch->Handle();
}
const auto barrier = context.Function<PFN_vkCmdPipelineBarrier>("vkCmdPipelineBarrier");
const VkMemoryBarrier produced{VK_STRUCTURE_TYPE_MEMORY_BARRIER, nullptr, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT};
VkImageMemoryBarrier toTransferDst{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
toTransferDst.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
toTransferDst.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
toTransferDst.oldLayout = copiedOnce ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED;
toTransferDst.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
toTransferDst.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
toTransferDst.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
toTransferDst.image = image;
toTransferDst.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, copyLevels, 0, copyLayers};
barrier(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &produced, 0, nullptr, 1, &toTransferDst);
context.Function<PFN_vkCmdCopyImageToBuffer>("vkCmdCopyImageToBuffer")(commands, storageSource->Image(), VK_IMAGE_LAYOUT_GENERAL, linear->Handle(), static_cast<std::uint32_t>(fromSource.size()), fromSource.data());
VkBufferMemoryBarrier linearReady{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER};
linearReady.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
linearReady.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
linearReady.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
linearReady.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
linearReady.buffer = linear->Handle();
linearReady.offset = 0;
linearReady.size = VK_WHOLE_SIZE;
barrier(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1, &linearReady, 0, nullptr);
context.Function<PFN_vkCmdCopyBufferToImage>("vkCmdCopyBufferToImage")(commands, linear->Handle(), image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, static_cast<std::uint32_t>(toDepth.size()), toDepth.data());
VkImageMemoryBarrier toShaderRead{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
toShaderRead.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
toShaderRead.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
toShaderRead.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
toShaderRead.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
toShaderRead.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
toShaderRead.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
toShaderRead.image = image;
toShaderRead.subresourceRange = toTransferDst.subresourceRange;
barrier(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1, &toShaderRead);
if (batch) batch->SubmitAndWait();
copiedOnce = true;
copiedVersion = storageSource->Version();
depthCopyRefreshes.fetch_add(1, std::memory_order_relaxed);
return true;
}

Texture::DepthCopyCounts Texture::TakeDepthCopyCounts() {
return {depthCopyTextures.exchange(0, std::memory_order_relaxed), depthCopyRefreshes.exchange(0, std::memory_order_relaxed)};
}

bool Texture::CanCopyFrom(const StorageTexture& source, const GuestTextureResource& descriptor) {
const auto& from = source.Descriptor();
if (IsBlockCompressed(descriptor.format) || IsBlockCompressed(from.format)) return false;
Expand Down
Loading
Loading