perf(agc): sample comparison-read R32F/R16 surfaces from a GPU depth copy of their storage image
This commit is contained in:
parent
6c9cb3a5db
commit
d024709cff
|
|
@ -61,6 +61,12 @@ public:
|
|||
// 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();
|
||||
static bool CanCopyFrom(const StorageTexture& source, const GuestTextureResource& descriptor);
|
||||
~Texture();
|
||||
Texture(const Texture&) = delete;
|
||||
|
|
@ -108,6 +114,11 @@ private:
|
|||
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);
|
||||
|
|
|
|||
|
|
@ -410,6 +410,19 @@ bool ClearedViewEnabled() {
|
|||
return !disabled;
|
||||
}
|
||||
|
||||
bool DepthCopyEnabled() {
|
||||
static const bool disabled = std::getenv("APS5_NO_DEPTH_COPY") != nullptr;
|
||||
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
|
||||
|
|
@ -537,8 +550,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 && DepthCopyEnabled() && 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())) {
|
||||
|
|
@ -562,12 +577,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
|
||||
|
|
@ -602,6 +618,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);
|
||||
|
|
@ -618,6 +639,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()});
|
||||
|
|
@ -650,7 +672,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 {
|
||||
|
|
@ -2313,6 +2340,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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -457,6 +457,153 @@ 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);
|
||||
} 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();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Texture::CanCopyFrom(const StorageTexture& source, const GuestTextureResource& descriptor) {
|
||||
const auto& from = source.Descriptor();
|
||||
if (IsBlockCompressed(descriptor.format) || IsBlockCompressed(from.format)) return false;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,188 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp"
|
||||
#include "prx/libc/include/GuestAllocations.hpp"
|
||||
#include "Recompiler.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#endif
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using AgcDriver::Graphics::Require;
|
||||
using ShaderRecompiler::ShaderStage;
|
||||
|
||||
constexpr std::uint32_t Threads = 32;
|
||||
constexpr std::uint32_t Side = 512;
|
||||
constexpr std::uint32_t Format32Float = 22;
|
||||
constexpr std::uint32_t Type2D = 9;
|
||||
constexpr std::uint32_t LessEqual = 3;
|
||||
constexpr std::uint32_t ClampEdge = 2;
|
||||
constexpr std::size_t BlockBytes = 2u << 20u;
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 9> StoreHalfCode{
|
||||
0x7e280d00, 0x102828ff, 0x3d000000, 0x7e3c0300, 0x7e3e0280, 0xf0201108, 0x0001141e, 0xbf810000, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 9> StoreQuarterCode{
|
||||
0x7e280d00, 0x102828ff, 0x3c800000, 0x7e3c0300, 0x7e3e0280, 0xf0201108, 0x0001141e, 0xbf810000, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 13> SampleCode{
|
||||
0x1614008c, 0xe03c1000, 0x8000010a, 0xbf8c3f70, 0xf0bc0108, 0x00820401, 0xbf8c3f70,
|
||||
0x34160082, 0xe0701000, 0x8001040b, 0xbf810000, 0xbf810000, 0xbf810000,
|
||||
};
|
||||
|
||||
alignas(256) std::array<float, Threads * 3> Input{};
|
||||
alignas(256) std::array<float, Threads> Output{};
|
||||
|
||||
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(BlockBytes, BlockBytes));
|
||||
#endif
|
||||
Require(block != nullptr, "depth compare copy: 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) {
|
||||
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) | (Format32Float << 20u) | (((Side - 1u) & 3u) << 30u),
|
||||
((Side - 1u) >> 2u) | ((Side - 1u) << 14u),
|
||||
0xfacu | (Type2D << 28u),
|
||||
0u, 0u, 0u, 0u,
|
||||
};
|
||||
}
|
||||
|
||||
std::array<std::uint32_t, 4> SamplerDescriptor() {
|
||||
return {ClampEdge | (ClampEdge << 3u) | (ClampEdge << 6u) | (LessEqual << 12u), 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{
|
||||
{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);
|
||||
}
|
||||
|
||||
void Store(AgcDriver::VulkanDevice& device, std::uint8_t* texels, std::span<const std::uint32_t> code) {
|
||||
std::vector<std::uint32_t> userData(16, 0u);
|
||||
const auto texture = TextureDescriptor(texels);
|
||||
std::copy(texture.begin(), texture.end(), userData.begin() + 4);
|
||||
const auto result = Compile(device, code, userData);
|
||||
device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
|
||||
device.WaitIdle();
|
||||
}
|
||||
|
||||
void Sample(AgcDriver::VulkanDevice& device, std::uint8_t* texels, float scale, const std::string& what) {
|
||||
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
|
||||
const float value = static_cast<float>(tid) * scale;
|
||||
Input[tid * 3u + 0u] = (tid & 1u) != 0u ? value - scale / 4.0f : value + scale / 4.0f;
|
||||
Input[tid * 3u + 1u] = (static_cast<float>(tid) + 0.5f) / static_cast<float>(Side);
|
||||
Input[tid * 3u + 2u] = 0.5f / static_cast<float>(Side);
|
||||
}
|
||||
Output.fill(-1.0f);
|
||||
std::vector<std::uint32_t> userData(24, 0u);
|
||||
const auto input = BufferDescriptor(Input.data(), static_cast<std::uint32_t>(sizeof(Input)));
|
||||
const auto output = BufferDescriptor(Output.data(), static_cast<std::uint32_t>(sizeof(Output)));
|
||||
const auto texture = TextureDescriptor(texels);
|
||||
const auto sampler = SamplerDescriptor();
|
||||
std::copy(input.begin(), input.end(), userData.begin());
|
||||
std::copy(output.begin(), output.end(), userData.begin() + 4);
|
||||
std::copy(texture.begin(), texture.end(), userData.begin() + 8);
|
||||
std::copy(sampler.begin(), sampler.end(), userData.begin() + 16);
|
||||
const auto result = Compile(device, SampleCode, userData);
|
||||
device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(SampleCode.data()));
|
||||
device.WaitIdle();
|
||||
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
|
||||
const float expected = (tid & 1u) != 0u ? 1.0f : 0.0f;
|
||||
Require(Output[tid] == expected, what + ": thread " + std::to_string(tid) + " compared to " + std::to_string(Output[tid]) + ", expected " + std::to_string(expected));
|
||||
}
|
||||
}
|
||||
|
||||
float Texel(const std::uint8_t* texels, std::uint32_t x) {
|
||||
float value = 0.0f;
|
||||
std::memcpy(&value, texels + static_cast<std::size_t>(x) * sizeof(float), sizeof(float));
|
||||
return value;
|
||||
}
|
||||
|
||||
void RequireGuestTexels(const std::uint8_t* texels, float scale, const std::string& what) {
|
||||
for (std::uint32_t x = 0; x < Threads; ++x) {
|
||||
const float expected = static_cast<float>(x) * scale;
|
||||
Require(Texel(texels, x) == expected, what + ": texel " + std::to_string(x) + " is " + std::to_string(Texel(texels, x)) + ", expected " + std::to_string(expected));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
GuestBlock block;
|
||||
auto* texels = block.Data();
|
||||
const auto surface = AgcDriver::Graphics::DescribeSurface(AgcDriver::Graphics::DecodeTextureResource(TextureDescriptor(texels)));
|
||||
Require(surface.guestBytes >= (1u << 20u) && surface.guestBytes <= BlockBytes, "depth compare copy: the surface does not fit the guest block");
|
||||
Store(*device, texels, StoreHalfCode);
|
||||
Sample(*device, texels, 1.0f / 32.0f, "comparison sample of pending results");
|
||||
Store(*device, texels, StoreQuarterCode);
|
||||
Sample(*device, texels, 1.0f / 64.0f, "comparison sample after a second store");
|
||||
AgcDriver::Graphics::StorageTexture::FlushPending(reinterpret_cast<std::uintptr_t>(texels), surface.guestBytes, nullptr, "test");
|
||||
device->WaitIdle();
|
||||
RequireGuestTexels(texels, 1.0f / 64.0f, "flushed results");
|
||||
std::puts("depth compare copy tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -519,3 +519,4 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [Null vertex buffer V#](../../core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp) (libSceAgcDriver) - a V# whose four dwords are all zero is bound to a zeroed vertex buffer of the attribute's size with stride 0, so every component of its attribute reads 0. This follows the V# fields in the [AMD RDNA2 ISA](https://gpuopen.com/download/rdna2-shader-instruction-set-architecture.pdf) (`NUM_RECORDS` 0, `DST_SEL` 0 selects 0), not measured on hardware.
|
||||
- [_fstatfs](../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) fills the FreeBSD 11 `struct statfs` of the PS5 payload SDK with the host volume that holds the file: block size, transfer size, total, free and available blocks, maximum name length and the read-only, `nosuid` and `noexec` flags (on Windows only read-only, and `f_files` and `f_ffree` are 0, as NTFS has no fixed number of files). The filesystem type number and name, mount paths, owner, filesystem id and I/O counters are left 0 or empty, since they would describe a host mount, not a console one. Sockets and pipes return `EINVAL`, as FreeBSD's `getvnode` does; a Windows descriptor that is neither a file nor a pipe throws. Stremio Plus (PPSA74126) reads the free space before a download and only logs the other fields
|
||||
- [_WStoul](../../core/libs/prx/libc/src/Strings.cpp), [_Iswctype](../../core/libs/prx/libc/src/RuneLocale.cpp) and [swscanf_s](../../core/libs/prx/libc/src/ScanningWide.cpp) (libc) - `_WStoul` is `wcstoul` and throws for a base other than 0 or 2 to 36. `_Iswctype` takes the class numbers of Dinkumware's [xwcc.h](https://github.com/superna9999/ti-cgt6x/blob/cgt6x_7_3_4/include/xwcc.h) (1 `alnum` to 11 `xdigit`, 12 `blank`) and throws for any other value, including the FreeBSD masks `wctype` returns; the numbering is not confirmed on the console's `libc.prx`. `swscanf_s` follows C11 7.29.2.2 and K.3.9.1.5, but reads the size argument as `unsigned int` like `sscanf_s` where K.3.9.1.5 specifies `rsize_t`; the console's width is unverified. A runtime-constraint violation (null buffer, format or receiving pointer) only returns `EOF`, and a too small array gets a null first element as in `sscanf_s`. A literal mismatch is a matching failure and a field that is only a prefix, such as `0x` or `1e`, does not convert, as C11 requires; FreeBSD's `vfwscanf` reports an input failure for the first and converts the second. Integers out of range are truncated from `strtoll`/`strtoull`. It throws for `%p`, `%S`, `%C`, unknown length modifiers, scanset ranges, `%n` with suppression or width, and narrow `%s`, `%c` or `%[` input that the C-locale `wcrtomb` cannot convert
|
||||
- [Comparison-sampled R32 float and R16 unorm textures](../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver) whose surface a storage image holds are sampled from a D32_SFLOAT or D16_UNORM image that the GPU refills from the storage image whenever its version moves, because Vulkan compares only depth formats. Only surfaces of at least 1 MiB (`APS5_DEPTH_COPY_MIN_KIB`) that are not 3D take the copy; smaller ones stay CPU snapshots compared against guest memory. With copies of small surfaces, one title hit an intermittent boot-time crash that was not explained. `APS5_NO_DEPTH_COPY=1` turns the copies off.
|
||||
|
|
|
|||
Loading…
Reference in New Issue