mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-18 23:07:07 -05:00
Merge pull request #7178 from AlxcBTW/pr/depth-clear-2
perf(agc): replace the plane copies around a depth plane's UAV clear by a depth clear
This commit is contained in:
5 files changed
+794
-6
No files matched your search
@@ -508,6 +508,19 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
|
||||
bool writersNoted = false;
|
||||
const bool noteWrites = writeEvidenceEnabled() || traceCapSync();
|
||||
|
||||
std::optional<Graphics::DepthPlaneClear> depthPlaneClear;
|
||||
if (indirectArguments == 0 && !partialLastGroup) {
|
||||
if (const auto clear = Graphics::MatchStorageImageClear(std::span(snapshot.code).subspan(codeOffset), userData, compute, groups)) {
|
||||
const auto clearAddress = Graphics::StorageImageClearAddress(*clear);
|
||||
if (clearAddress != 0 && Graphics::DepthSurfaceAt(clearAddress)) {
|
||||
GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Dispatch);
|
||||
std::lock_guard clearLock(GuestMemory::GpuMutex());
|
||||
recordLabelsForPacket(localDevice.get(), submission.queue);
|
||||
depthPlaneClear.emplace(*clear);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
|
||||
std::shared_ptr<PreparedDispatch> prepared;
|
||||
|
||||
@@ -49,6 +49,49 @@ float HostDepthBiasConstant(const Context& context, VkFormat format, float const
|
||||
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);
|
||||
|
||||
struct StorageImageClear {
|
||||
std::array<std::uint32_t, 8> image{};
|
||||
std::array<std::uint32_t, 4> color{};
|
||||
std::uint32_t width = 0;
|
||||
std::uint32_t height = 0;
|
||||
};
|
||||
std::optional<StorageImageClear> MatchStorageImageClear(std::span<const std::uint32_t> code, std::span<const std::uint32_t> userData, const ShaderRecompiler::ShaderComputeStageInfo& compute, const std::array<std::uint32_t, 3>& groups);
|
||||
std::uint64_t StorageImageClearAddress(const StorageImageClear& clear);
|
||||
struct DepthPlaneOverwrite {
|
||||
std::array<std::uint32_t, 8> image{};
|
||||
GuestTextureResource view{};
|
||||
std::uint32_t bits = 0;
|
||||
std::uint32_t width = 0;
|
||||
std::uint32_t height = 0;
|
||||
std::uint32_t raw = 0;
|
||||
std::uint64_t colorAddress = 0;
|
||||
std::uint32_t colorBits = 0;
|
||||
float depth = 0.0f;
|
||||
};
|
||||
class DepthPlaneClear {
|
||||
public:
|
||||
explicit DepthPlaneClear(const StorageImageClear& clear);
|
||||
~DepthPlaneClear();
|
||||
DepthPlaneClear(const DepthPlaneClear&) = delete;
|
||||
DepthPlaneClear& operator=(const DepthPlaneClear&) = delete;
|
||||
bool Active() const { return installed; }
|
||||
|
||||
private:
|
||||
DepthPlaneOverwrite value;
|
||||
DepthPlaneOverwrite* previous = nullptr;
|
||||
bool installed = false;
|
||||
};
|
||||
struct DepthClearStatistics {
|
||||
std::uint64_t matched = 0;
|
||||
std::uint64_t taken = 0;
|
||||
std::uint64_t copiesSkipped = 0;
|
||||
std::uint64_t clears = 0;
|
||||
std::uint64_t bytesSkipped = 0;
|
||||
std::array<std::uint64_t, 8> declined{};
|
||||
};
|
||||
DepthClearStatistics DepthClearCounts();
|
||||
void SetDepthPlaneClearForTest(bool enabled);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <bit>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <shared_mutex>
|
||||
#include <cstdio>
|
||||
@@ -119,6 +120,104 @@ DepthSamplingState prepareDepthSampling(const Context& context, const DepthTarge
|
||||
return state;
|
||||
}
|
||||
|
||||
std::atomic<bool> depthPlaneClearEnabled{true};
|
||||
|
||||
enum DepthClearDecline : std::size_t { DeclineLayers, DeclineFormat, DeclineValue, DeclineColor, DeclinePending, DeclineCoverage, DeclineSize, DeclineCount };
|
||||
static_assert(DeclineCount <= 8, "DepthClearStatistics::declined holds eight reasons");
|
||||
|
||||
struct DepthClearCounters {
|
||||
std::atomic<std::uint64_t> matched{0};
|
||||
std::atomic<std::uint64_t> taken{0};
|
||||
std::atomic<std::uint64_t> copiesSkipped{0};
|
||||
std::atomic<std::uint64_t> clears{0};
|
||||
std::atomic<std::uint64_t> bytesSkipped{0};
|
||||
std::array<std::atomic<std::uint64_t>, 8> declined{};
|
||||
};
|
||||
|
||||
DepthClearCounters& depthClearCounters() {
|
||||
static DepthClearCounters counters;
|
||||
return counters;
|
||||
}
|
||||
|
||||
thread_local DepthPlaneOverwrite* currentOverwrite = nullptr;
|
||||
|
||||
std::optional<DepthPlaneOverwrite> decodeOverwrite(const StorageImageClear& clear) {
|
||||
auto& counters = depthClearCounters();
|
||||
++counters.matched;
|
||||
const auto decline = [&](DepthClearDecline why) -> std::optional<DepthPlaneOverwrite> {
|
||||
++counters.declined[why];
|
||||
return std::nullopt;
|
||||
};
|
||||
DepthPlaneOverwrite overwrite;
|
||||
overwrite.image = clear.image;
|
||||
try {
|
||||
overwrite.view = DecodeTextureResource(clear.image);
|
||||
} catch (...) {
|
||||
return decline(DeclineFormat);
|
||||
}
|
||||
const auto& view = overwrite.view;
|
||||
const bool oneLayer = view.dimension == TextureDimension::k2D || (view.dimension == TextureDimension::k2DArray && view.baseArray == 0 && view.depthOrLastArray == 0);
|
||||
if (!oneLayer || view.baseLevel != 0) return decline(DeclineLayers);
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
try {
|
||||
format = ResolveTextureFormat(view.format);
|
||||
} catch (...) {
|
||||
return decline(DeclineFormat);
|
||||
}
|
||||
enum class Kind { Unorm16, Uint16, Float32, Uint32 };
|
||||
Kind kind;
|
||||
switch (format) {
|
||||
case VK_FORMAT_R16_UNORM: kind = Kind::Unorm16; break;
|
||||
case VK_FORMAT_R16_UINT: kind = Kind::Uint16; break;
|
||||
case VK_FORMAT_R32_SFLOAT: kind = Kind::Float32; break;
|
||||
case VK_FORMAT_R32_UINT:
|
||||
case VK_FORMAT_R32_SINT: kind = Kind::Uint32; break;
|
||||
default: return decline(DeclineFormat);
|
||||
}
|
||||
const std::uint64_t base = clear.color[0] | (static_cast<std::uint64_t>(clear.color[1] & 0xffffu) << 32u);
|
||||
const std::uint64_t stride = (clear.color[1] >> 16u) & 0x3fffu;
|
||||
const std::uint64_t extent = stride == 0 ? clear.color[2] : static_cast<std::uint64_t>(clear.color[2]) * stride;
|
||||
if (base == 0 || (base & 3u) != 0u || extent < 16u || !GuestMemory::Accessible(reinterpret_cast<const void*>(base), 16)) return decline(DeclineColor);
|
||||
if (const auto* recorder = Recorder::Active(); recorder != nullptr && recorder->PendingWriteOverlaps(base, 16)) return decline(DeclinePending);
|
||||
const std::pair<std::uint64_t, std::uint64_t> colorRange{base, base + 16u};
|
||||
if (StorageTexture::AnyPendingOverlaps(std::span(&colorRange, 1))) return decline(DeclinePending);
|
||||
std::uint32_t c0 = 0;
|
||||
try {
|
||||
GuestMemory::Read(base, std::as_writable_bytes(std::span(&c0, 1)), 4);
|
||||
} catch (...) {
|
||||
return decline(DeclineColor);
|
||||
}
|
||||
switch (kind) {
|
||||
case Kind::Unorm16:
|
||||
if (c0 == 0u || c0 == 0x80000000u) overwrite.raw = 0u;
|
||||
else if (c0 == 0x3f800000u) overwrite.raw = 0xffffu;
|
||||
else return decline(DeclineValue);
|
||||
overwrite.bits = 16u;
|
||||
overwrite.depth = static_cast<float>(overwrite.raw) / 65535.0f;
|
||||
break;
|
||||
case Kind::Uint16:
|
||||
if (c0 != 0u && c0 != 0xffffu) return decline(DeclineValue);
|
||||
overwrite.raw = c0;
|
||||
overwrite.bits = 16u;
|
||||
overwrite.depth = static_cast<float>(overwrite.raw) / 65535.0f;
|
||||
break;
|
||||
case Kind::Float32:
|
||||
case Kind::Uint32: {
|
||||
const auto depth = std::bit_cast<float>(c0);
|
||||
if (c0 != 0u && !(std::isnormal(depth) && depth > 0.0f && depth <= 1.0f)) return decline(DeclineValue);
|
||||
overwrite.raw = c0;
|
||||
overwrite.bits = 32u;
|
||||
overwrite.depth = depth;
|
||||
break;
|
||||
}
|
||||
}
|
||||
overwrite.colorAddress = base;
|
||||
overwrite.colorBits = c0;
|
||||
overwrite.width = clear.width;
|
||||
overwrite.height = clear.height;
|
||||
return overwrite;
|
||||
}
|
||||
|
||||
class DepthSurface {
|
||||
public:
|
||||
DepthSurface(const Context& context, const DepthTarget& target, DepthSamplingState sampling = {}) : context(context), target(target), hostFormat(HostDepthFormat(context, target.format)), sampling(std::move(sampling)) {
|
||||
@@ -249,6 +348,14 @@ public:
|
||||
}
|
||||
|
||||
void RecordClear(VkImageAspectFlags aspects) {
|
||||
const float depth = depthFill.value_or(clearDepth);
|
||||
const std::uint8_t stencil = stencilFill.value_or(clearStencil);
|
||||
depthFill.reset();
|
||||
stencilFill.reset();
|
||||
RecordClearValue(aspects, depth, stencil);
|
||||
}
|
||||
|
||||
void RecordClearValue(VkImageAspectFlags aspects, float depth, std::uint8_t stencil) {
|
||||
written = ++depthWrites;
|
||||
auto* recorder = RecorderFor(context);
|
||||
std::unique_ptr<CommandBatch> batch;
|
||||
@@ -257,9 +364,7 @@ public:
|
||||
const VkImageSubresourceRange range{aspects, 0, 1, 0, 1};
|
||||
constexpr VkAccessFlags access = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, access, VK_ACCESS_TRANSFER_WRITE_BIT);
|
||||
const VkClearDepthStencilValue clear{depthFill.value_or(clearDepth), stencilFill.value_or(clearStencil)};
|
||||
depthFill.reset();
|
||||
stencilFill.reset();
|
||||
const VkClearDepthStencilValue clear{depth, stencil};
|
||||
context.Function<PFN_vkCmdClearDepthStencilImage>("vkCmdClearDepthStencilImage")(commands, image, VK_IMAGE_LAYOUT_GENERAL, &clear, 1, &range);
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, access);
|
||||
if (batch) batch->SubmitAndWait();
|
||||
@@ -421,6 +526,7 @@ public:
|
||||
throw std::runtime_error(text);
|
||||
}
|
||||
RequireStorable(resource);
|
||||
auto* overwrite = stencil ? nullptr : PlaneOverwritten(words);
|
||||
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->Descriptor().dimension != resource.dimension) {
|
||||
@@ -428,15 +534,67 @@ public:
|
||||
if (plane.storage != nullptr) plane.replaced.push_back(plane.storage);
|
||||
plane.storage = StorageTexture::Create(context, *context.detiler, resource, 0);
|
||||
}
|
||||
copyPlane(plane, plane.storage, stencil, true);
|
||||
if (overwrite != nullptr) {
|
||||
++depthClearCounters().copiesSkipped;
|
||||
} else {
|
||||
copyPlane(plane, plane.storage, stencil, true);
|
||||
}
|
||||
return plane.storage;
|
||||
}
|
||||
|
||||
bool OverwritesWholePlane(const DepthPlaneOverwrite& overwrite) const {
|
||||
const bool d16 = target.format == VK_FORMAT_D16_UNORM || target.format == VK_FORMAT_D16_UNORM_S8_UINT;
|
||||
return overwrite.bits == (d16 ? 16u : 32u) && overwrite.width >= target.extent.width && overwrite.height >= target.extent.height;
|
||||
}
|
||||
|
||||
DepthPlaneOverwrite* PlaneOverwritten(std::span<const std::uint32_t> words) const {
|
||||
auto* overwrite = currentOverwrite;
|
||||
if (overwrite == nullptr || words.size() < overwrite->image.size() || !std::equal(overwrite->image.begin(), overwrite->image.end(), words.begin())) return nullptr;
|
||||
const bool d16 = target.format == VK_FORMAT_D16_UNORM || target.format == VK_FORMAT_D16_UNORM_S8_UINT;
|
||||
auto& declined = depthClearCounters().declined;
|
||||
if (overwrite->bits != (d16 ? 16u : 32u)) {
|
||||
++declined[DeclineSize];
|
||||
return nullptr;
|
||||
}
|
||||
if (overwrite->width < target.extent.width || overwrite->height < target.extent.height) {
|
||||
++declined[DeclineCoverage];
|
||||
return nullptr;
|
||||
}
|
||||
return overwrite;
|
||||
}
|
||||
|
||||
bool StoreOverwrite(const StorageTexture& mirror) {
|
||||
auto* overwrite = currentOverwrite;
|
||||
if (overwrite == nullptr || !OverwritesWholePlane(*overwrite)) return false;
|
||||
const auto& described = mirror.Descriptor();
|
||||
const auto& view = overwrite->view;
|
||||
if (described.baseAddress != view.baseAddress || described.format != view.format || described.tileMode != view.tileMode || described.dimension != view.dimension) return false;
|
||||
std::uint32_t current = 0;
|
||||
try {
|
||||
GuestMemory::Read(overwrite->colorAddress, std::as_writable_bytes(std::span(¤t, 1)), 4);
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
if (current != overwrite->colorBits) return false;
|
||||
auto* recorder = RecorderFor(context);
|
||||
Require(recorder != nullptr, "depth surface storage clears need the recorder of the surface's device");
|
||||
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 * (d16 ? 2u : 4u);
|
||||
recorder->Commands();
|
||||
const auto timing = recorder->BeginGpuTiming(Recorder::CommandClass::Copy);
|
||||
RecordClearValue(VK_IMAGE_ASPECT_DEPTH_BIT, overwrite->depth, clearStencil);
|
||||
recorder->EndGpuTiming(timing, bytes);
|
||||
auto& counters = depthClearCounters();
|
||||
++counters.clears;
|
||||
counters.bytesSkipped += 2u * bytes;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StoreBack(const StorageTexture& candidate) {
|
||||
for (const bool stencil : {false, true}) {
|
||||
auto& plane = stencil ? stencilPlane : depthPlane;
|
||||
if (plane.storage.get() == &candidate) {
|
||||
copyPlane(plane, plane.storage, stencil, false);
|
||||
if (stencil || !StoreOverwrite(candidate)) copyPlane(plane, plane.storage, stencil, false);
|
||||
written = ++depthWrites;
|
||||
return true;
|
||||
}
|
||||
@@ -1291,6 +1449,64 @@ void NoteMemoryValueFill(const MemoryValueFill& fill) {
|
||||
NoteDepthMetadataFill(fill.address, fill.bytes, value);
|
||||
}
|
||||
|
||||
std::optional<StorageImageClear> MatchStorageImageClear(std::span<const std::uint32_t> code, std::span<const std::uint32_t> userData, const ShaderRecompiler::ShaderComputeStageInfo& compute, const std::array<std::uint32_t, 3>& groups) {
|
||||
static constexpr std::array<std::uint32_t, 15> kernel{0xd7460004u, 0x0401060cu, 0xf4280204u, 0xfa000000u, 0xd7460005u, 0x0405060du, 0x7e0c020eu, 0xbf8cc07fu, 0x7e000208u, 0x7e020209u, 0x7e04020au, 0x7e06020bu, 0xf0200f28u, 0x00000004u, 0xbf810000u};
|
||||
if (code.size() < kernel.size() || code[0] != kernel[0] || !std::equal(kernel.begin(), kernel.end(), code.begin())) return std::nullopt;
|
||||
if (userData.size() != 12u) return std::nullopt;
|
||||
if (compute.numThreads != std::array<std::uint32_t, 3>{8u, 8u, 1u}) return std::nullopt;
|
||||
if (!compute.groupIdEnable[0] || !compute.groupIdEnable[1] || !compute.groupIdEnable[2]) return std::nullopt;
|
||||
if (compute.threadIdComponentCount < 2u) return std::nullopt;
|
||||
std::array<std::uint64_t, 3> covered{};
|
||||
for (std::size_t axis = 0; axis < 3u; ++axis) covered[axis] = compute.partialThreads[axis] != 0u ? compute.partialThreads[axis] : static_cast<std::uint64_t>(groups[axis]) * compute.numThreads[axis];
|
||||
if (covered[0] == 0u || covered[1] == 0u || covered[2] != 1u) return std::nullopt;
|
||||
StorageImageClear clear;
|
||||
std::copy_n(userData.begin(), clear.image.size(), clear.image.begin());
|
||||
std::copy_n(userData.begin() + static_cast<std::ptrdiff_t>(clear.image.size()), clear.color.size(), clear.color.begin());
|
||||
clear.width = static_cast<std::uint32_t>(std::min<std::uint64_t>(covered[0], std::numeric_limits<std::uint32_t>::max()));
|
||||
clear.height = static_cast<std::uint32_t>(std::min<std::uint64_t>(covered[1], std::numeric_limits<std::uint32_t>::max()));
|
||||
return clear;
|
||||
}
|
||||
|
||||
std::uint64_t StorageImageClearAddress(const StorageImageClear& clear) {
|
||||
try {
|
||||
return DecodeTextureResource(clear.image).baseAddress;
|
||||
} catch (...) {
|
||||
return 0u;
|
||||
}
|
||||
}
|
||||
|
||||
DepthPlaneClear::DepthPlaneClear(const StorageImageClear& clear) {
|
||||
if (!depthPlaneClearEnabled.load(std::memory_order_relaxed)) return;
|
||||
const auto decoded = decodeOverwrite(clear);
|
||||
if (decoded) {
|
||||
value = *decoded;
|
||||
previous = currentOverwrite;
|
||||
currentOverwrite = &value;
|
||||
installed = true;
|
||||
++depthClearCounters().taken;
|
||||
}
|
||||
}
|
||||
|
||||
DepthPlaneClear::~DepthPlaneClear() {
|
||||
if (installed) currentOverwrite = previous;
|
||||
}
|
||||
|
||||
DepthClearStatistics DepthClearCounts() {
|
||||
const auto& counters = depthClearCounters();
|
||||
DepthClearStatistics statistics;
|
||||
statistics.matched = counters.matched.load();
|
||||
statistics.taken = counters.taken.load();
|
||||
statistics.copiesSkipped = counters.copiesSkipped.load();
|
||||
statistics.clears = counters.clears.load();
|
||||
statistics.bytesSkipped = counters.bytesSkipped.load();
|
||||
for (std::size_t reason = 0; reason < statistics.declined.size(); ++reason) statistics.declined[reason] = counters.declined[reason].load();
|
||||
return statistics;
|
||||
}
|
||||
|
||||
void SetDepthPlaneClearForTest(bool enabled) {
|
||||
depthPlaneClearEnabled.store(enabled, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
std::size_t HtileDepthClearBytes_nid_no_patch(std::span<const std::uint32_t> code, std::span<const std::uint32_t> userData, const ShaderRecompiler::ShaderComputeStageInfo& compute, std::span<const std::uint32_t> packet, std::uint64_t indirectArguments) {
|
||||
if (userData.size() != 6u || packet.size() < 5u || indirectArguments != 0u || compute.PartialGroups() || !compute.groupIdEnable[0] || compute.threadIdComponentCount == 0u || compute.threadIdComponentCount > 3u) return 0u;
|
||||
if (packet[1] == 0u || packet[1] > 0x400000u || packet[2] != 1u || packet[3] != 1u || (packet[4] & 0x23u) != 1u) return 0u;
|
||||
|
||||
@@ -0,0 +1,516 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
|
||||
#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp"
|
||||
#include "prx/libc/include/GuestAllocations.hpp"
|
||||
#include "prx/libc/include/Shutdown.hpp"
|
||||
#include "DepthFastClearHarness.hpp"
|
||||
#include "VulkanTestDevice.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 <deque>
|
||||
#include <initializer_list>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace AgcDriver {
|
||||
|
||||
struct VulkanDeviceTestAccess {
|
||||
static Graphics::Context ContextOf(const VulkanDevice& device) { return device.graphicsContext(); }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
using DepthFastClearHarness::Compile;
|
||||
using DepthFastClearHarness::Covered;
|
||||
using DepthFastClearHarness::Draw;
|
||||
using DepthFastClearHarness::Height;
|
||||
using DepthFastClearHarness::Require;
|
||||
using DepthFastClearHarness::Shaders;
|
||||
using DepthFastClearHarness::Surface;
|
||||
using DepthFastClearHarness::Width;
|
||||
|
||||
namespace Graphics = AgcDriver::Graphics;
|
||||
|
||||
constexpr std::uint32_t Format16UNorm = 7;
|
||||
constexpr std::uint32_t Format16Uint = 11;
|
||||
constexpr std::uint32_t Format32Float = 22;
|
||||
constexpr std::uint32_t Format32Uint = 20;
|
||||
constexpr std::uint32_t Type2DArray = 13;
|
||||
constexpr std::uint32_t TileLinear = 0;
|
||||
constexpr std::uint32_t TileZ64KBX = 0x18;
|
||||
|
||||
constexpr std::array<std::uint32_t, 15> ClearKernel{0xd7460004u, 0x0401060cu, 0xf4280204u, 0xfa000000u, 0xd7460005u, 0x0405060du, 0x7e0c020eu, 0xbf8cc07fu, 0x7e000208u, 0x7e020209u, 0x7e04020au, 0x7e06020bu, 0xf0200f28u, 0x00000004u, 0xbf810000u};
|
||||
|
||||
class GuestBlock {
|
||||
public:
|
||||
GuestBlock() {
|
||||
constexpr std::size_t bytes = 65536;
|
||||
#ifdef _WIN32
|
||||
block = static_cast<std::uint8_t*>(VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
|
||||
#else
|
||||
block = static_cast<std::uint8_t*>(std::aligned_alloc(bytes, bytes));
|
||||
#endif
|
||||
Require(block != nullptr, "depth clear: cannot allocate a guest block");
|
||||
std::memset(block, 0, bytes);
|
||||
GuestAllocations::Mutation().Add(block, bytes, true, true, true);
|
||||
}
|
||||
|
||||
GuestBlock(const GuestBlock&) = delete;
|
||||
GuestBlock& operator=(const GuestBlock&) = delete;
|
||||
|
||||
std::uint8_t* Data() const { return block; }
|
||||
|
||||
std::uint64_t Address() const { return static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(block)); }
|
||||
|
||||
private:
|
||||
std::uint8_t* block = nullptr;
|
||||
};
|
||||
|
||||
GuestBlock& NewBlock() {
|
||||
static auto* blocks = new std::deque<GuestBlock>();
|
||||
return blocks->emplace_back();
|
||||
}
|
||||
|
||||
std::uint32_t Low(std::uint64_t value) { return static_cast<std::uint32_t>(value); }
|
||||
|
||||
std::uint32_t High(std::uint64_t value) { return static_cast<std::uint32_t>(value >> 32u); }
|
||||
|
||||
std::vector<std::uint32_t> Pm4Packet(std::uint32_t opcode, std::initializer_list<std::uint32_t> body) {
|
||||
std::vector<std::uint32_t> words{0xc0000000u | (static_cast<std::uint32_t>(body.size() - 1) << 16u) | (opcode << 8u)};
|
||||
words.insert(words.end(), body);
|
||||
return words;
|
||||
}
|
||||
|
||||
void Submit(std::vector<std::vector<std::uint32_t>> packets) {
|
||||
std::vector<std::uint32_t> words;
|
||||
for (const auto& packet : packets) words.insert(words.end(), packet.begin(), packet.end());
|
||||
Packet dcb{words.data(), static_cast<std::uint32_t>(words.size()), 0, {}};
|
||||
Require(sceAgcDriverSubmitDcb(&dcb) == 0, "depth clear: DCB submission failed");
|
||||
AgcDriverWaitIdle_nid_postfix();
|
||||
}
|
||||
|
||||
std::array<std::uint32_t, 8> TextureDescriptor(std::uint64_t address, std::uint32_t format, std::uint32_t tile = TileLinear, std::uint32_t type = Type2DArray) {
|
||||
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),
|
||||
0u, 0u, 0u, 0u,
|
||||
};
|
||||
}
|
||||
|
||||
std::array<std::uint32_t, 4> BufferDescriptor(std::uint64_t address, std::uint32_t bytes) {
|
||||
return {Low(address), High(address) & 0xffffu, bytes, 0x31016facu};
|
||||
}
|
||||
|
||||
void DispatchClear(const GuestBlock& code, const GuestBlock& plane, const GuestBlock& color, std::uint32_t format, std::uint32_t groupsX, std::uint32_t groupsY, std::uint32_t tile = TileLinear) {
|
||||
const auto kernel = code.Address();
|
||||
const auto texture = TextureDescriptor(plane.Address(), format, tile);
|
||||
const auto buffer = BufferDescriptor(color.Address(), 16u);
|
||||
std::vector<std::vector<std::uint32_t>> packets{
|
||||
Pm4Packet(0x76, {0x20c, Low(kernel >> 8u)}),
|
||||
Pm4Packet(0x76, {0x20d, High(kernel >> 8u) & 0xffu}),
|
||||
Pm4Packet(0x76, {0x207, 8}),
|
||||
Pm4Packet(0x76, {0x208, 8}),
|
||||
Pm4Packet(0x76, {0x209, 1}),
|
||||
Pm4Packet(0x76, {0x213, (12u << 1u) | 0x80u | 0x100u | 0x200u | (1u << 11u)}),
|
||||
};
|
||||
for (std::uint32_t word = 0; word < 8u; ++word) packets.push_back(Pm4Packet(0x76, {0x240u + word, texture[word]}));
|
||||
for (std::uint32_t word = 0; word < 4u; ++word) packets.push_back(Pm4Packet(0x76, {0x248u + word, buffer[word]}));
|
||||
packets.push_back(Pm4Packet(0x15, {groupsX, groupsY, 1, 0x41}));
|
||||
Submit(std::move(packets));
|
||||
}
|
||||
|
||||
void RunClear(AgcDriver::VulkanDevice& device, const GuestBlock& code, const GuestBlock& plane, const GuestBlock& color, std::uint32_t format, std::uint32_t groupsX, std::uint32_t groupsY, std::uint32_t tile = TileLinear) {
|
||||
std::vector<std::uint32_t> userData(12);
|
||||
const auto texture = TextureDescriptor(plane.Address(), format, tile);
|
||||
const auto buffer = BufferDescriptor(color.Address(), 16u);
|
||||
std::copy(texture.begin(), texture.end(), userData.begin());
|
||||
std::copy(buffer.begin(), buffer.end(), userData.begin() + 8);
|
||||
const std::span<const std::uint32_t> kernel(reinterpret_cast<const std::uint32_t*>(code.Data()), ClearKernel.size());
|
||||
const ShaderRecompiler::ShaderComputeStageInfo compute{{8, 8, 1}, 0u, {true, true, true}, false, 2u, {0, 0, 0}};
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 2> memory{{
|
||||
{static_cast<std::uintptr_t>(code.Address()), std::as_bytes(kernel)},
|
||||
{static_cast<std::uintptr_t>(color.Address()), std::span<const std::byte>(reinterpret_cast<const std::byte*>(color.Data()), 16)},
|
||||
}};
|
||||
ShaderRecompiler::RecompileRequest request{
|
||||
{ShaderRecompiler::ShaderStage::Compute, static_cast<std::uintptr_t>(code.Address()), kernel, 0, {}},
|
||||
{32, 0, userData, compute, std::nullopt, std::nullopt, memory},
|
||||
device.Target(),
|
||||
{0, 0, 0, 128}
|
||||
};
|
||||
request.useCache = false;
|
||||
const auto program = ShaderRecompiler::Recompile(request);
|
||||
std::optional<Graphics::DepthPlaneClear> scope;
|
||||
if (const auto clear = Graphics::MatchStorageImageClear(kernel, userData, compute, {groupsX, groupsY, 1u})) scope.emplace(*clear);
|
||||
device.Dispatch(program, groupsX, groupsY, 1, {}, static_cast<std::uintptr_t>(code.Address()));
|
||||
scope.reset();
|
||||
device.WaitIdle();
|
||||
}
|
||||
|
||||
void SetColor(GuestBlock& color, float value) {
|
||||
const std::array<float, 4> channels{value, value, value, value};
|
||||
std::memcpy(color.Data(), channels.data(), sizeof(channels));
|
||||
AgcDriver::GuestMemory::MarkWritten(color.Address(), sizeof(channels));
|
||||
}
|
||||
|
||||
void SetRaw(GuestBlock& color, std::uint32_t raw) {
|
||||
const std::array<std::uint32_t, 4> channels{raw, 0u, 0u, 0u};
|
||||
std::memcpy(color.Data(), channels.data(), sizeof(channels));
|
||||
AgcDriver::GuestMemory::MarkWritten(color.Address(), sizeof(channels));
|
||||
}
|
||||
|
||||
Graphics::DepthClearStatistics Difference(const Graphics::DepthClearStatistics& now, const Graphics::DepthClearStatistics& before) {
|
||||
Graphics::DepthClearStatistics difference;
|
||||
difference.matched = now.matched - before.matched;
|
||||
difference.taken = now.taken - before.taken;
|
||||
difference.copiesSkipped = now.copiesSkipped - before.copiesSkipped;
|
||||
difference.clears = now.clears - before.clears;
|
||||
difference.bytesSkipped = now.bytesSkipped - before.bytesSkipped;
|
||||
for (std::size_t reason = 0; reason < difference.declined.size(); ++reason) difference.declined[reason] = now.declined[reason] - before.declined[reason];
|
||||
return difference;
|
||||
}
|
||||
|
||||
std::uint64_t Declined(const Graphics::DepthClearStatistics& statistics) {
|
||||
std::uint64_t total = 0;
|
||||
for (const auto count : statistics.declined) total += count;
|
||||
return total;
|
||||
}
|
||||
|
||||
void MatcherCases() {
|
||||
std::vector<std::uint32_t> userData(12);
|
||||
for (std::uint32_t word = 0; word < userData.size(); ++word) userData[word] = 0x1000u + word;
|
||||
ShaderRecompiler::ShaderComputeStageInfo compute{{8, 8, 1}, 0u, {true, true, true}, false, 2u, {0, 0, 0}};
|
||||
const std::array<std::uint32_t, 3> groups{8, 4, 1};
|
||||
std::vector<std::uint32_t> code(ClearKernel.begin(), ClearKernel.end());
|
||||
code.insert(code.end(), {0u, 0u, 0u, 0u});
|
||||
|
||||
const auto clear = Graphics::MatchStorageImageClear(code, userData, compute, groups);
|
||||
Require(clear.has_value(), "the clear kernel was not recognized");
|
||||
Require(clear->width == 64u && clear->height == 32u, "the clear covers " + std::to_string(clear->width) + " x " + std::to_string(clear->height) + " texels, expected 64 x 32");
|
||||
for (std::uint32_t word = 0; word < 8u; ++word) Require(clear->image[word] == 0x1000u + word, "the clear's T# is not user data 0-7");
|
||||
for (std::uint32_t word = 0; word < 4u; ++word) Require(clear->color[word] == 0x1008u + word, "the clear's colour V# is not user data 8-11");
|
||||
|
||||
for (std::size_t word = 0; word < ClearKernel.size(); ++word) {
|
||||
auto changed = code;
|
||||
changed[word] ^= 0x00000100u;
|
||||
Require(!Graphics::MatchStorageImageClear(changed, userData, compute, groups), "a kernel with word " + std::to_string(word) + " changed was recognized");
|
||||
}
|
||||
Require(!Graphics::MatchStorageImageClear(std::span(code).first(ClearKernel.size() - 1), userData, compute, groups), "a truncated kernel was recognized");
|
||||
Require(Graphics::MatchStorageImageClear(std::span(code).first(ClearKernel.size()), userData, compute, groups).has_value(), "the kernel alone, without trailing words, was not recognized");
|
||||
|
||||
auto shortUserData = userData;
|
||||
shortUserData.pop_back();
|
||||
Require(!Graphics::MatchStorageImageClear(code, shortUserData, compute, groups), "eleven user registers were accepted (s12 would be a user register, not the group id)");
|
||||
auto longUserData = userData;
|
||||
longUserData.push_back(0u);
|
||||
Require(!Graphics::MatchStorageImageClear(code, longUserData, compute, groups), "thirteen user registers were accepted (s12 would be a user register, not the group id)");
|
||||
|
||||
auto wide = compute;
|
||||
wide.numThreads = {8, 8, 2};
|
||||
Require(!Graphics::MatchStorageImageClear(code, userData, wide, groups), "8 x 8 x 2 threads were accepted");
|
||||
wide.numThreads = {16, 4, 1};
|
||||
Require(!Graphics::MatchStorageImageClear(code, userData, wide, groups), "16 x 4 threads were accepted");
|
||||
for (std::size_t axis = 0; axis < 3u; ++axis) {
|
||||
auto missing = compute;
|
||||
missing.groupIdEnable[axis] = false;
|
||||
Require(!Graphics::MatchStorageImageClear(code, userData, missing, groups), "a dispatch without the group id of axis " + std::to_string(axis) + " was accepted");
|
||||
}
|
||||
auto oneComponent = compute;
|
||||
oneComponent.threadIdComponentCount = 1u;
|
||||
Require(!Graphics::MatchStorageImageClear(code, userData, oneComponent, groups), "a dispatch with only the x thread id was accepted");
|
||||
|
||||
Require(!Graphics::MatchStorageImageClear(code, userData, compute, {8, 4, 2}), "two layers of groups were accepted");
|
||||
Require(!Graphics::MatchStorageImageClear(code, userData, compute, {0, 4, 1}), "zero groups along x were accepted");
|
||||
Require(!Graphics::MatchStorageImageClear(code, userData, compute, {8, 0, 1}), "zero groups along y were accepted");
|
||||
|
||||
auto partial = compute;
|
||||
partial.partialThreads = {60, 30, 1};
|
||||
const auto cut = Graphics::MatchStorageImageClear(code, userData, partial, groups);
|
||||
Require(cut.has_value() && cut->width == 60u && cut->height == 30u, "a dispatch of 60 x 30 threads does not cover 60 x 30 texels");
|
||||
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> DepthImageTexels(AgcDriver::VulkanDevice& device, const GuestBlock& plane, std::uint32_t format, std::uint32_t texelBytes) {
|
||||
const auto context = AgcDriver::VulkanDeviceTestAccess::ContextOf(device);
|
||||
const auto words = TextureDescriptor(plane.Address(), format);
|
||||
const auto resource = Graphics::DecodeTextureResource(words);
|
||||
std::shared_ptr<Graphics::StorageTexture> mirror;
|
||||
{
|
||||
std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex());
|
||||
mirror = Graphics::DepthSurfaceStorage(context, words, resource, 0);
|
||||
}
|
||||
Require(mirror != nullptr, "the plane has no mirror");
|
||||
mirror->MarkDirty();
|
||||
const std::size_t pitch = std::max<std::size_t>(Width, 256u / texelBytes);
|
||||
Graphics::StorageTexture::FlushPending(plane.Address(), pitch * Height * texelBytes, nullptr, "test");
|
||||
device.WaitIdle();
|
||||
std::vector<std::uint32_t> texels(static_cast<std::size_t>(Width) * Height);
|
||||
for (std::size_t y = 0; y < Height; ++y) {
|
||||
for (std::size_t x = 0; x < Width; ++x) {
|
||||
const auto at = (y * pitch + x) * texelBytes;
|
||||
if (texelBytes == 2u) {
|
||||
std::uint16_t value = 0;
|
||||
std::memcpy(&value, plane.Data() + at, sizeof(value));
|
||||
texels[y * Width + x] = value;
|
||||
} else {
|
||||
std::memcpy(&texels[y * Width + x], plane.Data() + at, sizeof(std::uint32_t));
|
||||
}
|
||||
}
|
||||
}
|
||||
return texels;
|
||||
}
|
||||
|
||||
void RequireDepthTexels(AgcDriver::VulkanDevice& device, const GuestBlock& plane, std::uint32_t format, std::uint32_t texelBytes, std::uint32_t expected, const std::string& label) {
|
||||
const auto texels = DepthImageTexels(device, plane, format, texelBytes);
|
||||
for (std::size_t texel = 0; texel < texels.size(); ++texel) Require(texels[texel] == expected, label + "depth image texel " + std::to_string(texel) + " holds " + std::to_string(texels[texel]) + ", expected " + std::to_string(expected));
|
||||
}
|
||||
|
||||
Graphics::DepthClearStatistics ClearSequence(AgcDriver::VulkanDevice& device, const Shaders& shaders, int mode) {
|
||||
Graphics::SetDepthPlaneClearForTest(mode != 0);
|
||||
const auto before = Graphics::DepthClearCounts();
|
||||
const std::string label = "mode " + std::to_string(mode) + ": ";
|
||||
auto& code = NewBlock();
|
||||
std::memcpy(code.Data(), ClearKernel.data(), sizeof(ClearKernel));
|
||||
auto& plane = NewBlock();
|
||||
auto& color = NewBlock();
|
||||
const Surface surface{plane.Address(), 0, 0, false, VK_FORMAT_D32_SFLOAT};
|
||||
const auto drawLess = [&] { return Draw(device, shaders, surface, {.depthCompare = VK_COMPARE_OP_LESS}); };
|
||||
const auto drawGreater = [&] { return Draw(device, shaders, surface, {.depthCompare = VK_COMPARE_OP_GREATER}); };
|
||||
const auto drawEqual = [&] { return Draw(device, shaders, surface, {.depthCompare = VK_COMPARE_OP_EQUAL}); };
|
||||
|
||||
Require(Draw(device, shaders, surface, {.depthWrite = true, .depthCompare = VK_COMPARE_OP_ALWAYS}) == Covered, label + "the producer did not cover the plane");
|
||||
Require(drawLess() == 0u, label + "the plane does not hold the 0.5 the producer wrote");
|
||||
Require(drawEqual() == Covered, label + "the plane does not hold exactly 0.5");
|
||||
|
||||
SetColor(color, 1.0f);
|
||||
RunClear(device, code, plane, color, Format32Float, 8, 4);
|
||||
Require(drawLess() == Covered, label + "after a clear to 1.0 the depth test 0.5 < 1.0 did not pass everywhere");
|
||||
Require(drawGreater() == 0u, label + "after a clear to 1.0 a texel passed 0.5 > depth");
|
||||
|
||||
SetColor(color, 0.25f);
|
||||
RunClear(device, code, plane, color, Format32Float, 8, 4);
|
||||
Require(drawLess() == 0u, label + "after a clear to 0.25 a texel passed 0.5 < depth");
|
||||
Require(drawGreater() == Covered, label + "after a clear to 0.25 the depth test 0.5 > 0.25 did not pass everywhere");
|
||||
|
||||
SetColor(color, 0.5f);
|
||||
RunClear(device, code, plane, color, Format32Float, 8, 4);
|
||||
Require(drawEqual() == Covered, label + "after a clear to 0.5 the plane does not hold exactly 0.5");
|
||||
|
||||
SetColor(color, 0.75f);
|
||||
RunClear(device, code, plane, color, Format32Float, 4, 4);
|
||||
Require(drawGreater() == 0u, label + "after a clear of the left half to 0.75 a texel passed 0.5 > depth");
|
||||
Require(drawEqual() == Covered / 2u, label + "after a clear of the left half the right half does not keep 0.5");
|
||||
Require(drawLess() == Covered / 2u, label + "after a clear of the left half the left half does not hold 0.75");
|
||||
|
||||
SetColor(color, 2.0f);
|
||||
RunClear(device, code, plane, color, Format32Float, 8, 4);
|
||||
Require(drawEqual() == 0u, label + "after a clear to 2.0 a texel still holds exactly 0.5");
|
||||
|
||||
SetColor(color, 0.125f);
|
||||
RunClear(device, code, plane, color, Format32Float, 8, 4);
|
||||
Require(drawGreater() == Covered, label + "after the last full clear the depth test 0.5 > 0.125 did not pass everywhere");
|
||||
Require(drawLess() == 0u, label + "after the last full clear a texel passed 0.5 < depth");
|
||||
|
||||
Graphics::StorageTexture::FlushPending(plane.Address(), Width * Height * sizeof(float), nullptr, "test");
|
||||
device.WaitIdle();
|
||||
for (std::uint32_t texel = 0; texel < Width * Height; ++texel) {
|
||||
float value = 0.0f;
|
||||
std::memcpy(&value, plane.Data() + texel * sizeof(float), sizeof(value));
|
||||
Require(value == 0.125f, label + "guest depth texel " + std::to_string(texel) + " is " + std::to_string(value) + " after the flush, expected 0.125");
|
||||
}
|
||||
RequireDepthTexels(device, plane, Format32Float, 4, std::bit_cast<std::uint32_t>(0.125f), label);
|
||||
return Difference(Graphics::DepthClearCounts(), before);
|
||||
}
|
||||
|
||||
void IntegerViewCase(AgcDriver::VulkanDevice& device, const Shaders& shaders, int mode) {
|
||||
Graphics::SetDepthPlaneClearForTest(mode != 0);
|
||||
const auto before = Graphics::DepthClearCounts();
|
||||
const std::string label = "integer view, mode " + std::to_string(mode) + ": ";
|
||||
auto& code = NewBlock();
|
||||
std::memcpy(code.Data(), ClearKernel.data(), sizeof(ClearKernel));
|
||||
auto& plane = NewBlock();
|
||||
auto& color = NewBlock();
|
||||
const Surface surface{plane.Address(), 0, 0, false, VK_FORMAT_D32_SFLOAT};
|
||||
Require(Draw(device, shaders, surface, {.depthWrite = true, .depthCompare = VK_COMPARE_OP_ALWAYS}) == Covered, label + "the producer did not cover the plane");
|
||||
const std::array<std::uint32_t, 4> bits{std::bit_cast<std::uint32_t>(0.25f), 0u, 0u, 0u};
|
||||
std::memcpy(color.Data(), bits.data(), sizeof(bits));
|
||||
AgcDriver::GuestMemory::MarkWritten(color.Address(), sizeof(bits));
|
||||
RunClear(device, code, plane, color, Format32Uint, 8, 4, TileZ64KBX);
|
||||
Require(Draw(device, shaders, surface, {.depthCompare = VK_COMPARE_OP_GREATER}) == Covered, label + "after a clear to the bits of 0.25 the depth test 0.5 > 0.25 did not pass everywhere");
|
||||
Require(Draw(device, shaders, surface, {.depthCompare = VK_COMPARE_OP_LESS}) == 0u, label + "after a clear to the bits of 0.25 a texel passed 0.5 < depth");
|
||||
const auto difference = Difference(Graphics::DepthClearCounts(), before);
|
||||
if (mode == 0) Require(difference.matched == 0u && difference.clears == 0u, label + "the lever counted with the switch off");
|
||||
else Require(difference.clears == 1u, label + "the clear was not taken (" + std::to_string(difference.clears) + " clears, " + std::to_string(Declined(difference)) + " declined)");
|
||||
}
|
||||
|
||||
void DriverMatchCase(AgcDriver::VulkanDevice& device, const Shaders& shaders, int mode) {
|
||||
Graphics::SetDepthPlaneClearForTest(mode != 0);
|
||||
const auto before = Graphics::DepthClearCounts();
|
||||
const std::string label = "driver submit, mode " + std::to_string(mode) + ": ";
|
||||
auto& code = NewBlock();
|
||||
std::memcpy(code.Data(), ClearKernel.data(), sizeof(ClearKernel));
|
||||
auto& plane = NewBlock();
|
||||
auto& color = NewBlock();
|
||||
const Surface surface{plane.Address(), 0, 0, false, VK_FORMAT_D32_SFLOAT};
|
||||
Require(Draw(device, shaders, surface, {.depthWrite = true, .depthCompare = VK_COMPARE_OP_ALWAYS}) == Covered, label + "the producer did not cover the plane");
|
||||
SetColor(color, 1.0f);
|
||||
DispatchClear(code, plane, color, Format32Float, 8, 4);
|
||||
const auto difference = Difference(Graphics::DepthClearCounts(), before);
|
||||
if (mode == 0) Require(difference.matched == 0u && difference.taken == 0u, label + "the driver's dispatch reached the lever with the switch off");
|
||||
else Require(difference.matched == 1u && difference.taken == 1u, label + "the driver's dispatch matched " + std::to_string(difference.matched) + " and took " + std::to_string(difference.taken) + " clears (declined " + std::to_string(Declined(difference)) + "), expected 1 and 1");
|
||||
}
|
||||
|
||||
void SixteenBitCase(AgcDriver::VulkanDevice& device, const Shaders& shaders, int mode) {
|
||||
Graphics::SetDepthPlaneClearForTest(mode != 0);
|
||||
const auto before = Graphics::DepthClearCounts();
|
||||
const std::string label = "16-bit plane, mode " + std::to_string(mode) + ": ";
|
||||
auto& code = NewBlock();
|
||||
std::memcpy(code.Data(), ClearKernel.data(), sizeof(ClearKernel));
|
||||
auto& plane = NewBlock();
|
||||
auto& color = NewBlock();
|
||||
const Surface surface{plane.Address(), 0, 0, false, VK_FORMAT_D16_UNORM};
|
||||
const auto drawLess = [&] { return Draw(device, shaders, surface, {.depthCompare = VK_COMPARE_OP_LESS}); };
|
||||
const auto drawGreater = [&] { return Draw(device, shaders, surface, {.depthCompare = VK_COMPARE_OP_GREATER}); };
|
||||
Require(Draw(device, shaders, surface, {.depthWrite = true, .depthCompare = VK_COMPARE_OP_ALWAYS}) == Covered, label + "the producer did not cover the plane");
|
||||
|
||||
SetColor(color, 1.0f);
|
||||
try {
|
||||
RunClear(device, code, plane, color, Format16UNorm, 8, 4);
|
||||
} catch (const std::exception& error) {
|
||||
if (std::string(error.what()).find("unsupported format") == std::string::npos) throw;
|
||||
std::printf("16-bit plane skipped: %s\n", error.what());
|
||||
return;
|
||||
}
|
||||
Require(drawLess() == Covered, label + "after a clear to 1.0 the depth test 0.5 < 1.0 did not pass everywhere");
|
||||
Require(drawGreater() == 0u, label + "after a clear to 1.0 a texel passed 0.5 > depth");
|
||||
RequireDepthTexels(device, plane, Format16UNorm, 2, 0xffffu, label + "clear to 1.0: ");
|
||||
|
||||
SetColor(color, 0.0f);
|
||||
RunClear(device, code, plane, color, Format16UNorm, 8, 4);
|
||||
Require(drawGreater() == Covered, label + "after a clear to 0.0 the depth test 0.5 > 0.0 did not pass everywhere");
|
||||
Require(drawLess() == 0u, label + "after a clear to 0.0 a texel passed 0.5 < depth");
|
||||
RequireDepthTexels(device, plane, Format16UNorm, 2, 0u, label + "clear to 0.0: ");
|
||||
|
||||
SetColor(color, 0.5f);
|
||||
RunClear(device, code, plane, color, Format16UNorm, 8, 4);
|
||||
|
||||
auto& rawPlane = NewBlock();
|
||||
const Surface rawSurface{rawPlane.Address(), 0, 0, false, VK_FORMAT_D16_UNORM};
|
||||
const auto rawLess = [&] { return Draw(device, shaders, rawSurface, {.depthCompare = VK_COMPARE_OP_LESS}); };
|
||||
const auto rawGreater = [&] { return Draw(device, shaders, rawSurface, {.depthCompare = VK_COMPARE_OP_GREATER}); };
|
||||
Require(Draw(device, shaders, rawSurface, {.depthWrite = true, .depthCompare = VK_COMPARE_OP_ALWAYS}) == Covered, label + "the producer of the uint plane did not cover it");
|
||||
SetRaw(color, 0x4000u);
|
||||
RunClear(device, code, rawPlane, color, Format16Uint, 8, 4, TileZ64KBX);
|
||||
Require(rawGreater() == Covered, label + "after a clear to the raw value 0x4000 the depth test 0.5 > 0.25 did not pass everywhere");
|
||||
Require(rawLess() == 0u, label + "after a clear to the raw value 0x4000 a texel passed 0.5 < depth");
|
||||
|
||||
SetRaw(color, 0xffffu);
|
||||
RunClear(device, code, rawPlane, color, Format16Uint, 8, 4, TileZ64KBX);
|
||||
Require(rawLess() == Covered, label + "after a clear to the raw value 0xffff the depth test 0.5 < 1.0 did not pass everywhere");
|
||||
Require(rawGreater() == 0u, label + "after a clear to the raw value 0xffff a texel passed 0.5 > depth");
|
||||
|
||||
SetRaw(color, 0u);
|
||||
RunClear(device, code, rawPlane, color, Format16Uint, 8, 4, TileZ64KBX);
|
||||
Require(rawGreater() == Covered, label + "after a clear to the raw value 0 the depth test 0.5 > 0.0 did not pass everywhere");
|
||||
Require(rawLess() == 0u, label + "after a clear to the raw value 0 a texel passed 0.5 < depth");
|
||||
|
||||
const auto difference = Difference(Graphics::DepthClearCounts(), before);
|
||||
if (mode == 0) {
|
||||
Require(difference.matched == 0u && difference.clears == 0u, label + "the lever counted with the switch off");
|
||||
return;
|
||||
}
|
||||
Require(difference.clears == 4u, label + "the lever took " + std::to_string(difference.clears) + " clears, expected 4 (unorm 1.0 and 0.0, raw 0xffff and 0)");
|
||||
Require(difference.declined[2] >= 2u, label + "the unorm clear to 0.5 and the raw clear to 0x4000 were not declined for their value (" + std::to_string(difference.declined[2]) + ")");
|
||||
Require(difference.bytesSkipped == difference.clears * 2u * Width * Height * sizeof(std::uint16_t), label + "the copies left out add up to " + std::to_string(difference.bytesSkipped) + " bytes");
|
||||
}
|
||||
|
||||
void StencilCase(AgcDriver::VulkanDevice& device, const Shaders& shaders, int mode) {
|
||||
Graphics::SetDepthPlaneClearForTest(mode != 0);
|
||||
const auto before = Graphics::DepthClearCounts();
|
||||
const std::string label = "depth and stencil, mode " + std::to_string(mode) + ": ";
|
||||
auto& code = NewBlock();
|
||||
std::memcpy(code.Data(), ClearKernel.data(), sizeof(ClearKernel));
|
||||
auto& plane = NewBlock();
|
||||
auto& stencilPlane = NewBlock();
|
||||
auto& color = NewBlock();
|
||||
const Surface surface{plane.Address(), stencilPlane.Address(), 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 VkStencilOpState equalFive{VK_STENCIL_OP_KEEP, VK_STENCIL_OP_KEEP, VK_STENCIL_OP_KEEP, VK_COMPARE_OP_EQUAL, 0xffu, 0u, 5u};
|
||||
Require(Draw(device, shaders, surface, {.depthWrite = true, .depthCompare = VK_COMPARE_OP_ALWAYS, .stencilTest = true, .stencil = writeFive}) == Covered, label + "the depth and stencil producer did not cover the surface");
|
||||
SetColor(color, 1.0f);
|
||||
RunClear(device, code, plane, color, Format32Float, 8, 4);
|
||||
Require(Draw(device, shaders, surface, {.depthCompare = VK_COMPARE_OP_LESS}) == Covered, label + "after a clear to 1.0 the depth test 0.5 < 1.0 did not pass everywhere");
|
||||
Require(Draw(device, shaders, surface, {.depthTest = false, .stencilTest = true, .stencil = equalFive}) == Covered, label + "the depth clear changed the stencil values");
|
||||
const auto difference = Difference(Graphics::DepthClearCounts(), before);
|
||||
if (mode == 0) Require(difference.matched == 0u && difference.clears == 0u, label + "the lever counted with the switch off");
|
||||
else Require(difference.clears == 1u, label + "the clear was not taken (" + std::to_string(difference.clears) + " clears, " + std::to_string(Declined(difference)) + " declined)");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
int failures = 0;
|
||||
const auto run = [&](const char* name, auto&& body) {
|
||||
try {
|
||||
body();
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << name << ": " << error.what() << '\n';
|
||||
++failures;
|
||||
}
|
||||
};
|
||||
try {
|
||||
run("matcher", [] { MatcherCases(); });
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return failures != 0 ? 1 : VulkanTestSkipped;
|
||||
const auto shaders = Compile(*device);
|
||||
|
||||
run("switch off", [&] {
|
||||
const auto off = ClearSequence(*device, shaders, 0);
|
||||
Require(off.matched == 0u && off.taken == 0u && off.clears == 0u && off.copiesSkipped == 0u, "the lever counted with the switch off");
|
||||
});
|
||||
|
||||
run("lever", [&] {
|
||||
const auto on = ClearSequence(*device, shaders, 1);
|
||||
Require(on.matched >= 6u, "the lever matched " + std::to_string(on.matched) + " clear dispatches, expected 6");
|
||||
Require(on.clears == 4u, "the lever took " + std::to_string(on.clears) + " clears, expected 4 (1.0, 0.25, 0.5, 0.125)");
|
||||
Require(on.copiesSkipped >= on.clears, "fewer copy-ins were left out than clears taken");
|
||||
Require(on.bytesSkipped == on.clears * 2u * Width * Height * sizeof(float), "the copies left out add up to " + std::to_string(on.bytesSkipped) + " bytes");
|
||||
Require(on.declined[2] >= 1u, "the clear to 2.0 was not declined for its value (" + std::to_string(on.declined[2]) + ")");
|
||||
Require(on.declined[5] >= 1u, "the clear of half the plane was not declined for its coverage (" + std::to_string(on.declined[5]) + ")");
|
||||
});
|
||||
|
||||
Graphics::SetDepthPlaneClearForTest(true);
|
||||
|
||||
for (const int mode : {0, 1}) run(mode == 0 ? "integer view, switch off" : "integer view, lever", [&] { IntegerViewCase(*device, shaders, mode); });
|
||||
for (const int mode : {0, 1}) run(mode == 0 ? "16-bit plane, switch off" : "16-bit plane, lever", [&] { SixteenBitCase(*device, shaders, mode); });
|
||||
for (const int mode : {0, 1}) run(mode == 0 ? "depth and stencil, switch off" : "depth and stencil, lever", [&] { StencilCase(*device, shaders, mode); });
|
||||
for (const int mode : {0, 1}) run(mode == 0 ? "driver submit, switch off" : "driver submit, lever", [&] { DriverMatchCase(*device, shaders, mode); });
|
||||
Graphics::SetDepthPlaneClearForTest(true);
|
||||
LibcRunShutdown_nid_postfix();
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
try { LibcRunShutdown_nid_postfix(); } catch (...) {}
|
||||
return 1;
|
||||
}
|
||||
if (failures != 0) return 1;
|
||||
std::puts("depth clear tests passed");
|
||||
return 0;
|
||||
}
|
||||
@@ -54,7 +54,7 @@
|
||||
- [Stencil reference export replay](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) on devices without `VK_EXT_shader_stencil_export` supports at most sixteen values proved from constant, select and phi exports, with an export proved on every kept path. It records instances, then their triangles, then the reference classes of each triangle. One pipeline filters fragments at shader termination against a dynamic push value before late stencil and depth tests. The [Vulkan stencil-reference replacement rule](https://docs.vulkan.org/refpages/latest/refpages/source/FragStencilRefEXT.html) discards bits above the attachment's stencil width; supported stencil attachments have eight bits. Numeric subgroup observations throw except bounded own-lane mask plumbing and group control with kept-lane and derivative-helper coverage proved. WQM inputs must exclude helper contributions, since [non-quad subgroup operations may treat helpers as inactive](https://docs.vulkan.org/glslext/latest/glslext/khr/GL_KHR_shader_subgroup.html). Readonly guarded SRT reads may report internal host faults; these draws wait and check faults before publishing results, and loops throw. Indirect draws, restart, strips, fans, tessellation, mesh, multisampling, early tests, ordered pixel execution, guest shader memory writes, other fault-producing memory access, clocks, primitive ID inputs, attachment feedback and pipeline face/channel splits throw. An exhausted stage push range throws. Equivalence to console ordering has not been measured on console hardware.
|
||||
|
||||
- [32-bit depth-bits reads of guest memory](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver): a sampled 32_UINT or 32_SINT T# with a depth swizzle mode that no live depth surface matches reads the guest bytes through an R32_SFLOAT image, as the recompiler's depth-bits read expects, and the shader casts the fetched value back to the stored dword. On RX 6700 XT (gfx1031, AMD Windows driver 32.0.21045.5002) the read keeps float denormals, the sign bit alone and quiet and signaling NaN payloads bit for bit. A host that canonicalizes NaNs or flushes denormals on a 32-bit float fetch would change those dwords, and other drivers are not measured. A live depth surface whose extent or base level differs from the T# is not used; the read takes the guest bytes, as the 32-bit float and 16-bit views already do.
|
||||
- [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, or a one-layer 2D array view of layer 0, 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 2D array view of more than one slice of the depth plane, or of a later slice, is a storage image over the slices from 0 to its last (`LayeredDepthStorage`); only the slices from its base slice to its last are copied: a slice that has a depth surface at the plane's address plus a whole depth slice per layer (`DepthSliceBytes`, as for the depth plane copies below) is copied from that surface before every use and back to it after the work, every bound slice each time, through a staging buffer; the other slices hold guest memory like any storage image. These images are kept per address, extent, slice count, base slice, format and tile mode until the device goes away, outside the storage cache's budget. Such a view needs a layer pitch equal to that slice size, and another view of the same memory (the 2D view of its first slice, a view from another base slice, or a sampled copy) is a separate image, copied in before and out after each use, so views that are written before a flush and share a slice do not merge: the later copy-out wins, so their results are not merged. A matching view still throws when it is a cube, a 2D array of more than one layer of the stencil plane or from a later slice, or another 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, address or dimension) 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.
|
||||
- [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, or a one-layer 2D array view of layer 0, 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 dispatch of the 15-word 8x8x1 image clear kernel ([`MatchStorageImageClear`](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp): one colour read from the V# in `user[8..11]` stored to every texel of the T# in `user[0..7]`) that covers the whole plane and stores 0 or 1 through a 16-bit unorm view, 0 or 0xffff through a 16-bit uint view, or 0 or a normal float in (0, 1] through a 32-bit float or integer view leaves out the copy-in and replaces the copy-out by a clear of the depth image to that value; the dispatch still writes the storage image. The colour is read again when the copy-out is replaced, and a changed value keeps the copy-out. Indirect dispatches, partial last groups, dispatches that queue on the draw pipeline, other values and formats, a plane the kernel covers only partly and stencil planes keep both copies. 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 2D array view of more than one slice of the depth plane, or of a later slice, is a storage image over the slices from 0 to its last (`LayeredDepthStorage`); only the slices from its base slice to its last are copied: a slice that has a depth surface at the plane's address plus a whole depth slice per layer (`DepthSliceBytes`, as for the depth plane copies below) is copied from that surface before every use and back to it after the work, every bound slice each time, through a staging buffer; the other slices hold guest memory like any storage image. These images are kept per address, extent, slice count, base slice, format and tile mode until the device goes away, outside the storage cache's budget. Such a view needs a layer pitch equal to that slice size, and another view of the same memory (the 2D view of its first slice, a view from another base slice, or a sampled copy) is a separate image, copied in before and out after each use, so views that are written before a flush and share a slice do not merge: the later copy-out wins, so their results are not merged. A matching view still throws when it is a cube, a 2D array of more than one layer of the stencil plane or from a later slice, or another 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, address or dimension) 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.
|
||||
- [Collapsed color array views](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) preserve the selected slice in the XOR address equation when the view is represented as a shifted 2D surface. Native Vulkan coverage checks R_X draws, array reads, publication and re-upload for slices 1-3; Lost Soul Aside PPSA26358 validates the RGBA16F cube mip chain on Windows/NVIDIA. Other devices and console comparisons remain unverified. Full-array views retain their layer addressing.
|
||||
- A [depth surface](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver) is retired when a color target or a written storage image it does not serve overlaps it, or when it is sampled through a view it cannot serve and guest memory over it was written since it was last bound; lookups then skip it, so sampling reads the memory like any texture. Bound again, it keeps its own depth instead of the bytes written over it. Without write watching, a view it cannot serve still throws. A retired surface also gives up its depth and stencil planes, so another surface sharing them no longer copies from it.
|
||||
- [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. A 2D-array T# whose `BASE_ARRAY` is past slice 0 reads slices `BASE_ARRAY` to `LAST_ARRAY` of the copy, which holds slices 0 to `LAST_ARRAY`, and a 2D binding of it reads slice `BASE_ARRAY`; that follows the colour 2D-array semantics and the titles that hit it (PPSA01556, PPSA03912), and was not measured on hardware for depth surfaces. Planes with mips, a cube or a 2D T# with a base slice other than 0, a `BASE_ARRAY` past `LAST_ARRAY`, 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).
|
||||
|
||||
Reference in new issue
Block a user