test(agc): cover depth storage access through dispatches
Draw depth and stencil, read and rewrite each plane in compute dispatches, and check the stored values with depth-tested and stencil-tested draws, a following dispatch and the flushed guest bytes. The array view, multisampled and two-view refusals are checked through dispatches as well.
This commit is contained in:
parent
889e5d436a
commit
738ae9df06
|
|
@ -0,0 +1,286 @@
|
|||
#include "DepthFastClearHarness.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
|
||||
#include "prx/libc/include/GuestAllocations.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 <iostream>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using AgcDriver::Graphics::Require;
|
||||
using DepthFastClearHarness::Covered;
|
||||
using DepthFastClearHarness::Draw;
|
||||
using DepthFastClearHarness::Height;
|
||||
using DepthFastClearHarness::Pixels;
|
||||
using DepthFastClearHarness::Shaders;
|
||||
using DepthFastClearHarness::Surface;
|
||||
using DepthFastClearHarness::Width;
|
||||
|
||||
constexpr std::uint32_t Threads = 32;
|
||||
constexpr std::uint32_t Format8Uint = 5;
|
||||
constexpr std::uint32_t Format32Float = 22;
|
||||
constexpr std::uint32_t Type2D = 9;
|
||||
constexpr std::uint32_t Type2DArray = 13;
|
||||
constexpr std::uint32_t TileLinear = 0;
|
||||
constexpr std::uint32_t TileStandard256B = 1;
|
||||
constexpr std::size_t BlockBytes = 1u << 20u;
|
||||
constexpr std::size_t StencilOffset = 1u << 16u;
|
||||
constexpr std::size_t OutputOffset = 2u << 16u;
|
||||
constexpr std::size_t MultisampledOffset = 4u << 16u;
|
||||
constexpr std::size_t MultisampledColorOffset = 5u << 16u;
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 14> ScaleDepthCode{
|
||||
0x7e3c0300u, 0x7e3e0280u, 0x7e140280u, 0xf0001108u, 0x00010a1eu, 0xbf8c3f70u, 0x34060082u,
|
||||
0xe0701000u, 0x80000a03u, 0x1014140cu, 0xf0201108u, 0x00010a1eu, 0xbf810000u, 0xbf810000u,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 14> AddStencilCode{
|
||||
0x7e3c0300u, 0x7e3e0280u, 0x7e140280u, 0xf0001108u, 0x00010a1eu, 0xbf8c3f70u, 0x34060082u,
|
||||
0xe0701000u, 0x80000a03u, 0x4a141484u, 0xf0201108u, 0x00010a1eu, 0xbf810000u, 0xbf810000u,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 9> StoreCode{
|
||||
0x7e280d00u, 0x102828ffu, 0x3c800000u, 0x7e3c0300u, 0x7e3e0280u, 0xf0201108u, 0x0001141eu, 0xbf810000u, 0xbf810000u,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 10> StoreLayerCode{
|
||||
0x7e280d00u, 0x1028280eu, 0x7e3c0300u, 0x7e3e0280u, 0x7e40020cu, 0x7e42020du, 0xf0241128u, 0x0001141eu, 0xbf810000u, 0xbf810000u,
|
||||
};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 10> StoreTwoViewsCode{
|
||||
0x7e280d00u, 0x102828ffu, 0x3c800000u, 0x7e3c0300u, 0x7e3e0280u, 0xf0201108u, 0x0001141eu, 0xf0201108u, 0x0003141eu, 0xbf810000u,
|
||||
};
|
||||
|
||||
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(65536, BlockBytes));
|
||||
#endif
|
||||
Require(block != nullptr, "depth storage dispatch: 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, std::uint32_t format, std::uint32_t type = Type2D, std::uint32_t tile = TileLinear) {
|
||||
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) | (format << 20u) | (((Width - 1u) & 3u) << 30u),
|
||||
((Width - 1u) >> 2u) | ((Height - 1u) << 14u),
|
||||
0xfacu | (tile << 20u) | (type << 28u),
|
||||
0u, 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{
|
||||
{ShaderRecompiler::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);
|
||||
}
|
||||
|
||||
ShaderRecompiler::RecompileResult PlaneProgram(AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> code, std::span<std::uint32_t> output, const std::uint8_t* plane, std::uint32_t format, float factor) {
|
||||
std::vector<std::uint32_t> userData(16, 0u);
|
||||
const auto buffer = BufferDescriptor(output.data(), static_cast<std::uint32_t>(output.size_bytes()));
|
||||
const auto texture = TextureDescriptor(plane, format);
|
||||
std::copy(buffer.begin(), buffer.end(), userData.begin());
|
||||
std::copy(texture.begin(), texture.end(), userData.begin() + 4);
|
||||
userData[12] = std::bit_cast<std::uint32_t>(factor);
|
||||
return Compile(device, code, userData);
|
||||
}
|
||||
|
||||
void RunPlane(AgcDriver::VulkanDevice& device, const ShaderRecompiler::RecompileResult& program, std::span<const std::uint32_t> code, std::span<std::uint32_t> output, std::uint32_t expected, const std::string& what) {
|
||||
std::fill(output.begin(), output.end(), 0xdeadbeefu);
|
||||
device.Dispatch(program, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
|
||||
device.WaitIdle();
|
||||
for (std::uint32_t lane = 0; lane < Threads; ++lane) {
|
||||
Require(output[lane] == expected, what + ": the dispatch read " + std::to_string(output[lane]) + " at texel " + std::to_string(lane) + ", expected " + std::to_string(expected));
|
||||
}
|
||||
}
|
||||
|
||||
void RequireRow(std::uint32_t covered, const std::string& what) {
|
||||
Require(covered == Threads, what + ": " + std::to_string(covered) + " pixels passed, expected the " + std::to_string(Threads) + " the dispatch stored");
|
||||
for (std::uint32_t x = 0; x < Threads; ++x) {
|
||||
Require(std::to_integer<std::uint8_t>(Pixels[x * 4u]) == 255u, what + ": pixel " + std::to_string(x) + " of the first row did not pass");
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TAction>
|
||||
void ExpectRefusal(TAction action, const char* expected, const std::string& what) {
|
||||
try {
|
||||
action();
|
||||
} catch (const std::exception& error) {
|
||||
Require(std::string(error.what()).find(expected) != std::string::npos, what + " threw another error: " + error.what());
|
||||
return;
|
||||
}
|
||||
throw std::runtime_error(what + " was accepted");
|
||||
}
|
||||
|
||||
VkStencilOpState StencilEqual(std::uint32_t reference) {
|
||||
return {VK_STENCIL_OP_KEEP, VK_STENCIL_OP_KEEP, VK_STENCIL_OP_KEEP, VK_COMPARE_OP_EQUAL, 0xffu, 0u, reference};
|
||||
}
|
||||
|
||||
void StoreBackCase(AgcDriver::VulkanDevice& device, const Shaders& shaders, std::uint8_t* block) {
|
||||
auto* depth = block;
|
||||
auto* stencil = block + StencilOffset;
|
||||
const std::span<std::uint32_t> output(reinterpret_cast<std::uint32_t*>(block + OutputOffset), Threads);
|
||||
const Surface surface{reinterpret_cast<std::uintptr_t>(depth), reinterpret_cast<std::uintptr_t>(stencil), 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 auto bits = [](float value) { return std::bit_cast<std::uint32_t>(value); };
|
||||
Require(Draw(device, shaders, surface, {.depthWrite = true, .depthCompare = VK_COMPARE_OP_ALWAYS, .stencilTest = true, .stencil = writeFive}) == Covered, "the depth and stencil producer did not cover the surface");
|
||||
|
||||
const auto raise = PlaneProgram(device, ScaleDepthCode, output, depth, Format32Float, 1.5f);
|
||||
const auto lower = PlaneProgram(device, ScaleDepthCode, output, depth, Format32Float, 0.5f);
|
||||
RunPlane(device, raise, ScaleDepthCode, output, bits(0.5f), "first depth dispatch");
|
||||
RequireRow(Draw(device, shaders, surface, {.depthCompare = VK_COMPARE_OP_LESS}), "depth test against the first dispatch's depth");
|
||||
RunPlane(device, lower, ScaleDepthCode, output, bits(0.75f), "second depth dispatch");
|
||||
RequireRow(Draw(device, shaders, surface, {.depthCompare = VK_COMPARE_OP_GREATER}), "depth test against the second dispatch's depth");
|
||||
Require(Draw(device, shaders, surface, {.depthCompare = VK_COMPARE_OP_LESS}) == 0, "the first dispatch's depth outlived the second dispatch");
|
||||
RunPlane(device, raise, ScaleDepthCode, output, bits(0.375f), "third depth dispatch, its program's second");
|
||||
RequireRow(Draw(device, shaders, surface, {.depthCompare = VK_COMPARE_OP_LESS}), "depth test against the third dispatch's depth");
|
||||
|
||||
const auto add = PlaneProgram(device, AddStencilCode, output, stencil, Format8Uint, 0.0f);
|
||||
RunPlane(device, add, AddStencilCode, output, 5u, "first stencil dispatch");
|
||||
RequireRow(Draw(device, shaders, surface, {.depthTest = false, .stencilTest = true, .stencil = StencilEqual(9u)}), "stencil test against the first dispatch's stencil");
|
||||
RunPlane(device, add, AddStencilCode, output, 9u, "second stencil dispatch");
|
||||
RequireRow(Draw(device, shaders, surface, {.depthTest = false, .stencilTest = true, .stencil = StencilEqual(13u)}), "stencil test against the second dispatch's stencil");
|
||||
Require(Draw(device, shaders, surface, {.depthTest = false, .stencilTest = true, .stencil = StencilEqual(5u)}) == Covered - Threads, "a stencil dispatch changed stencil values it did not store");
|
||||
RequireRow(Draw(device, shaders, surface, {.depthCompare = VK_COMPARE_OP_LESS}), "depth test after the stencil dispatches");
|
||||
|
||||
AgcDriver::Graphics::StorageTexture::FlushPending(reinterpret_cast<std::uintptr_t>(depth), Width * Height * sizeof(float), nullptr, "test");
|
||||
device.WaitIdle();
|
||||
for (std::uint32_t x = 0; x < Width; ++x) {
|
||||
float texel = 0.0f;
|
||||
std::memcpy(&texel, depth + x * sizeof(float), sizeof(texel));
|
||||
Require(texel == (x < Threads ? 0.5625f : 0.5f), "guest depth texel " + std::to_string(x) + " is " + std::to_string(texel) + " after the flush");
|
||||
}
|
||||
AgcDriver::Graphics::StorageTexture::FlushPending(reinterpret_cast<std::uintptr_t>(stencil), Width * Height, nullptr, "test");
|
||||
device.WaitIdle();
|
||||
for (std::uint32_t x = 0; x < Width; ++x) {
|
||||
Require(stencil[x] == (x < Threads ? 13u : 5u), "guest stencil texel " + std::to_string(x) + " is " + std::to_string(stencil[x]) + " after the flush");
|
||||
}
|
||||
}
|
||||
|
||||
void ArrayViewCase(AgcDriver::VulkanDevice& device, std::uint8_t* block) {
|
||||
std::vector<std::uint32_t> userData(16, 0u);
|
||||
const auto texture = TextureDescriptor(block, Format32Float, Type2DArray);
|
||||
std::copy(texture.begin(), texture.end(), userData.begin() + 4);
|
||||
userData[14] = std::bit_cast<std::uint32_t>(1.0f / 64.0f);
|
||||
const auto program = Compile(device, StoreLayerCode, userData);
|
||||
ExpectRefusal([&] { device.Dispatch(program, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(StoreLayerCode.data())); }, "storage image access to depth/stencil surface", "a one-layer 2D array storage view of the depth plane");
|
||||
}
|
||||
|
||||
void MultisampledCase(AgcDriver::VulkanDevice& device, const Shaders& compiled, std::uint8_t* block) {
|
||||
auto* depth = block + MultisampledOffset;
|
||||
const std::array<AgcDriver::Graphics::CompiledShader, 2> shaders{{
|
||||
{ShaderRecompiler::ShaderStage::Vertex, &compiled.vertex, 0},
|
||||
{ShaderRecompiler::ShaderStage::Fragment, &compiled.pixel, PixelPushOffset(compiled.vertexPush, device.Target())}
|
||||
}};
|
||||
AgcDriver::Graphics::State state{};
|
||||
state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, 32u, 32u, std::nullopt, std::nullopt};
|
||||
state.color = {reinterpret_cast<std::uintptr_t>(block + MultisampledColorOffset), {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, Width * Height * 16u, 0xe4u};
|
||||
state.color.samples = 4;
|
||||
state.colors = {state.color};
|
||||
state.hasColorTarget = true;
|
||||
state.depth = AgcDriver::Graphics::DepthTarget{reinterpret_cast<std::uintptr_t>(depth), 0, {Width, Height}, VK_FORMAT_D32_SFLOAT, 1.0f, 0, 0, false, 4};
|
||||
state.depthTest = true;
|
||||
state.depthWrite = true;
|
||||
state.depthCompare = VK_COMPARE_OP_ALWAYS;
|
||||
state.samples = 4;
|
||||
state.renderExtent = {Width, Height};
|
||||
state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
state.viewport = {0, static_cast<float>(Height), static_cast<float>(Width), -static_cast<float>(Height), 0, 1};
|
||||
state.negativeOneToOne = false;
|
||||
state.scissor = {{0, 0}, {Width, Height}};
|
||||
state.cullMode = VK_CULL_MODE_NONE;
|
||||
state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
|
||||
state.blend.colorWriteMask = 15;
|
||||
state.blends = {state.blend};
|
||||
state.blendConstants = {};
|
||||
const AgcDriver::Pm4::DrawParameters draw{0, static_cast<std::uint32_t>(DepthFastClearHarness::Triangles().size()), 0, 1, 0, false};
|
||||
device.Draw(state, draw, shaders);
|
||||
device.WaitIdle();
|
||||
std::vector<std::uint32_t> userData(16, 0u);
|
||||
const auto texture = TextureDescriptor(depth, Format32Float);
|
||||
std::copy(texture.begin(), texture.end(), userData.begin() + 4);
|
||||
const auto program = Compile(device, StoreCode, userData);
|
||||
ExpectRefusal([&] { device.Dispatch(program, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(StoreCode.data())); }, "storage image access to a multisampled depth surface", "a storage view of a multisampled depth plane");
|
||||
}
|
||||
|
||||
void TwoViewsCase(AgcDriver::VulkanDevice& device, std::uint8_t* block) {
|
||||
std::vector<std::uint32_t> userData(20, 0u);
|
||||
const auto first = TextureDescriptor(block, Format32Float, Type2D, TileLinear);
|
||||
const auto second = TextureDescriptor(block, Format32Float, Type2D, TileStandard256B);
|
||||
std::copy(first.begin(), first.end(), userData.begin() + 4);
|
||||
std::copy(second.begin(), second.end(), userData.begin() + 12);
|
||||
const auto program = Compile(device, StoreTwoViewsCode, userData);
|
||||
ExpectRefusal([&] { device.Dispatch(program, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(StoreTwoViewsCode.data())); }, "through a view that another view of the plane", "a dispatch storing to two views of one depth plane");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
GuestBlock block;
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
const auto shaders = DepthFastClearHarness::Compile(*device);
|
||||
StoreBackCase(*device, shaders, block.Data());
|
||||
ArrayViewCase(*device, block.Data());
|
||||
MultisampledCase(*device, shaders, block.Data());
|
||||
TwoViewsCase(*device, block.Data());
|
||||
std::puts("depth storage dispatch tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue