mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-06 13:30:00 +00:00
test(agc): pin the unset-format T# rule to image types and sampled reads
This commit is contained in:
1 parent
0dd1814129
commit
e1eda1a2ae
3 files changed
+256
-2
No files matched your search
@@ -3,11 +3,13 @@
|
||||
#include "BdaTests.hpp"
|
||||
#include "GraphicsTests.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Memory/DrawWriteRanges.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Resources.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Pipeline.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/PipelineJournal.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/TextureCache.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/VertexInput.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/BufferPool.hpp"
|
||||
@@ -6982,6 +6984,161 @@ void resourceTests() {
|
||||
}
|
||||
}
|
||||
|
||||
void unsetFormatStorageImageTests() {
|
||||
using ShaderRecompiler::DescriptorBinding;
|
||||
using ShaderRecompiler::DescriptorImageShape;
|
||||
using ShaderRecompiler::DescriptorKind;
|
||||
using ShaderRecompiler::DescriptorRole;
|
||||
constexpr std::uint32_t Format8888UNorm = 56;
|
||||
constexpr std::uint32_t Type2D = 9;
|
||||
alignas(256) std::array<std::byte, 4096> guest{};
|
||||
const auto base = reinterpret_cast<std::uint64_t>(guest.data());
|
||||
const auto words = [](std::uint32_t format, std::uint64_t baseAddress) {
|
||||
return std::vector<std::uint32_t>{
|
||||
static_cast<std::uint32_t>(baseAddress >> 8u),
|
||||
static_cast<std::uint32_t>((baseAddress >> 40u) & 0xffu) | ((format & 0x1ffu) << 20u) | (3u << 30u),
|
||||
3u | (15u << 14u),
|
||||
0xfacu | (Type2D << 28u),
|
||||
0u,
|
||||
0u,
|
||||
0u,
|
||||
0u,
|
||||
};
|
||||
};
|
||||
const auto storage = [&](std::uint32_t format, std::uint64_t baseAddress) {
|
||||
DescriptorBinding binding{};
|
||||
binding.kind = DescriptorKind::StorageImage;
|
||||
binding.role = DescriptorRole::GuestImages;
|
||||
binding.binding = ShaderRecompiler::RuntimeAbi::FirstStorageImageBinding;
|
||||
binding.count = 1;
|
||||
binding.imageWritten = {true};
|
||||
binding.guestDescriptor = words(format, baseAddress);
|
||||
return binding;
|
||||
};
|
||||
for (const std::uint32_t format : {0u, 0x1ffu}) {
|
||||
mock = MockVulkan{};
|
||||
auto context = mockContext();
|
||||
context.device = makeHandle<VkDevice>();
|
||||
context.limits.maxPerStageDescriptorStorageImages = 8;
|
||||
context.limits.maxDescriptorSetStorageImages = 8;
|
||||
ShaderRecompiler::RecompileResult vertex;
|
||||
ShaderRecompiler::RecompileResult fragment;
|
||||
vertex.bindings.push_back(storage(format, base));
|
||||
const auto color = AgcDriver::Graphics::DecodeState(makeState()).color;
|
||||
{
|
||||
AgcDriver::Graphics::TextureDetiler detiler(context);
|
||||
context.detiler = &detiler;
|
||||
expectFailure([&] { AgcDriver::Graphics::ShaderResources resources(context, vertex, fragment, color, 0, 0); }, "unsupported guest texture format");
|
||||
}
|
||||
Require(mock.live == 0, "a rejected unset-format storage image leaked Vulkan objects");
|
||||
}
|
||||
{
|
||||
mock = MockVulkan{};
|
||||
auto context = mockContext();
|
||||
context.device = makeHandle<VkDevice>();
|
||||
context.limits.maxPerStageDescriptorSampledImages = 8;
|
||||
context.limits.maxDescriptorSetSampledImages = 8;
|
||||
ShaderRecompiler::RecompileResult vertex;
|
||||
ShaderRecompiler::RecompileResult fragment;
|
||||
auto sampled = storage(0u, base);
|
||||
sampled.kind = DescriptorKind::SampledImage;
|
||||
sampled.binding = ShaderRecompiler::RuntimeAbi::FirstImageBinding;
|
||||
sampled.imageWritten.clear();
|
||||
sampled.imageShape = DescriptorImageShape::Image2D;
|
||||
sampled.imageSamplers = {0};
|
||||
vertex.bindings.push_back(sampled);
|
||||
const auto color = AgcDriver::Graphics::DecodeState(makeState()).color;
|
||||
{
|
||||
AgcDriver::Graphics::TextureDetiler detiler(context);
|
||||
context.detiler = &detiler;
|
||||
AgcDriver::Graphics::TextureCache textureCache(context);
|
||||
context.textureCache = &textureCache;
|
||||
AgcDriver::Graphics::ShaderResources resources(context, vertex, fragment, color, 0, 0);
|
||||
}
|
||||
Require(mock.live == 0, "a sampled unset-format image leaked Vulkan objects");
|
||||
}
|
||||
{
|
||||
mock = MockVulkan{};
|
||||
auto context = mockContext();
|
||||
context.device = makeHandle<VkDevice>();
|
||||
context.limits.maxPerStageDescriptorSampledImages = 8;
|
||||
context.limits.maxDescriptorSetSampledImages = 8;
|
||||
ShaderRecompiler::RecompileResult vertex;
|
||||
ShaderRecompiler::RecompileResult fragment;
|
||||
auto sampled = storage(0u, base);
|
||||
sampled.kind = DescriptorKind::SampledImage;
|
||||
sampled.binding = ShaderRecompiler::RuntimeAbi::FirstImageBinding;
|
||||
sampled.imageWritten.clear();
|
||||
sampled.imageShape = DescriptorImageShape::Image1DArray;
|
||||
sampled.imageSamplers = {0};
|
||||
sampled.guestDescriptor[3] = (sampled.guestDescriptor[3] & ~(0xfu << 28u)) | (12u << 28u);
|
||||
vertex.bindings.push_back(sampled);
|
||||
const auto color = AgcDriver::Graphics::DecodeState(makeState()).color;
|
||||
{
|
||||
AgcDriver::Graphics::TextureDetiler detiler(context);
|
||||
context.detiler = &detiler;
|
||||
AgcDriver::Graphics::TextureCache textureCache(context);
|
||||
context.textureCache = &textureCache;
|
||||
AgcDriver::Graphics::ShaderResources resources(context, vertex, fragment, color, 0, 0);
|
||||
}
|
||||
Require(mock.live == 0, "a sampled TYPE 12 image with an unset format leaked Vulkan objects");
|
||||
}
|
||||
for (const std::uint32_t format : {0u, 0x1ffu}) {
|
||||
mock = MockVulkan{};
|
||||
auto context = mockContext();
|
||||
context.device = makeHandle<VkDevice>();
|
||||
context.limits.maxPerStageDescriptorSampledImages = 8;
|
||||
context.limits.maxDescriptorSetSampledImages = 8;
|
||||
ShaderRecompiler::RecompileResult vertex;
|
||||
ShaderRecompiler::RecompileResult fragment;
|
||||
auto sampled = storage(format, base);
|
||||
sampled.kind = DescriptorKind::SampledImage;
|
||||
sampled.binding = ShaderRecompiler::RuntimeAbi::FirstImageBinding;
|
||||
sampled.imageWritten.clear();
|
||||
sampled.imageShape = DescriptorImageShape::Image2D;
|
||||
sampled.imageSamplers = {0};
|
||||
sampled.guestDescriptor[3] = (sampled.guestDescriptor[3] & ~(0xfu << 28u)) | (2u << 28u);
|
||||
vertex.bindings.push_back(sampled);
|
||||
const auto color = AgcDriver::Graphics::DecodeState(makeState()).color;
|
||||
{
|
||||
AgcDriver::Graphics::TextureDetiler detiler(context);
|
||||
context.detiler = &detiler;
|
||||
AgcDriver::Graphics::TextureCache textureCache(context);
|
||||
context.textureCache = &textureCache;
|
||||
expectFailure([&] { AgcDriver::Graphics::ShaderResources resources(context, vertex, fragment, color, 0, 0); }, "unsupported image type 2");
|
||||
}
|
||||
Require(mock.live == 0, "a rejected reserved-type sampled image leaked Vulkan objects");
|
||||
}
|
||||
{
|
||||
mock = MockVulkan{};
|
||||
auto context = mockContext();
|
||||
context.device = makeHandle<VkDevice>();
|
||||
context.limits.maxPerStageDescriptorStorageImages = 8;
|
||||
context.limits.maxDescriptorSetStorageImages = 8;
|
||||
ShaderRecompiler::RecompileResult vertex;
|
||||
ShaderRecompiler::RecompileResult fragment;
|
||||
vertex.bindings.push_back(storage(0u, 0));
|
||||
const auto color = AgcDriver::Graphics::DecodeState(makeState()).color;
|
||||
{
|
||||
AgcDriver::Graphics::TextureDetiler detiler(context);
|
||||
context.detiler = &detiler;
|
||||
AgcDriver::Graphics::ShaderResources resources(context, vertex, fragment, color, 0, 0);
|
||||
}
|
||||
Require(mock.live == 0, "a null storage image with an unset format leaked Vulkan objects");
|
||||
}
|
||||
{
|
||||
ShaderRecompiler::RecompileResult compiled;
|
||||
compiled.bindings.push_back(storage(Format8888UNorm, base));
|
||||
bool bounded = false;
|
||||
const auto ranges = AgcDriver::DriverDetail::DispatchWriteRanges(compiled, bounded);
|
||||
Require(ranges.size() == 1 && ranges[0].first == base && ranges[0].second == base + 4096u, "a written storage image did not contribute its whole surface to the dispatch write ranges");
|
||||
compiled.bindings.front().guestDescriptor = words(0u, base);
|
||||
expectFailure([&] { static_cast<void>(AgcDriver::DriverDetail::DispatchWriteRanges(compiled, bounded)); }, "unsupported guest texture format");
|
||||
compiled.bindings.front().guestDescriptor = words(0u, 0u);
|
||||
Require(AgcDriver::DriverDetail::DispatchWriteRanges(compiled, bounded).empty(), "a null written storage image contributed dispatch write ranges");
|
||||
}
|
||||
}
|
||||
|
||||
void descriptorCacheTests() {
|
||||
mock = MockVulkan{};
|
||||
auto context = mockContext();
|
||||
@@ -13321,6 +13478,7 @@ int main(int argc, char** argv) {
|
||||
descriptorPoolTests();
|
||||
pushConstantTests();
|
||||
resourceTests();
|
||||
unsetFormatStorageImageTests();
|
||||
oversizedDescriptorTests();
|
||||
viewAtRangeLimitTests();
|
||||
guestBufferViewBytesTests();
|
||||
|
||||
@@ -158,6 +158,23 @@ void RunGuestTextureResourceTests() {
|
||||
const std::array<std::uint32_t, 8> reportedStorage{0x04efd000u, 0x00040011u, 0x00017a00u, 0x00005204u, 0x0af00000u, 0u, 0x00000612u, 0x00fff000u};
|
||||
Require(IsNullTextureResource(reportedStorage), "the type-0 words of a written storage slot were not classified as null");
|
||||
reject([&] { static_cast<void>(DecodeTextureResource(reportedStorage)); }, "guest texture descriptor");
|
||||
Fields unsetFormat = base;
|
||||
unsetFormat.format = 0u;
|
||||
unsetFormat.typeRaw = 12u;
|
||||
Require(!IsNullTextureResource(pack(unsetFormat)), "a zero-format texture was classified as null for every consumer");
|
||||
Require(IsNullSampledTextureResource(pack(unsetFormat)), "a zero-format image-type texture was not classified as null for a sampled consumer");
|
||||
reject([] { ResolveTextureFormat(0u); }, "unsupported guest texture format");
|
||||
unsetFormat.format = 0x1ffu;
|
||||
Require(!IsNullTextureResource(pack(unsetFormat)), "an all-ones-format texture was classified as null for every consumer");
|
||||
Require(IsNullSampledTextureResource(pack(unsetFormat)), "an all-ones-format image-type texture was not classified as null for a sampled consumer");
|
||||
reject([] { ResolveTextureFormat(0x1ffu); }, "unsupported guest texture format");
|
||||
unsetFormat.typeRaw = 2u;
|
||||
unsetFormat.format = 0u;
|
||||
Require(!IsNullSampledTextureResource(pack(unsetFormat)), "a reserved type-2 texture with a zero format was classified as null for a sampled consumer");
|
||||
unsetFormat.format = 0x1ffu;
|
||||
Require(!IsNullSampledTextureResource(pack(unsetFormat)), "a reserved type-2 texture with an all-ones format was classified as null for a sampled consumer");
|
||||
unsetFormat.typeRaw = 15u;
|
||||
Require(!IsNullSampledTextureResource(pack(unsetFormat)), "a type-15 texture with an unset format was classified as null for a sampled consumer");
|
||||
auto result = DecodeTextureResource(pack(base));
|
||||
Require(result.baseAddress == (base.base40 << 8u), "decoded base address changed");
|
||||
Require(result.width == 16 && result.height == 16, "decoded width or height changed");
|
||||
|
||||
@@ -2104,7 +2104,7 @@ void verifyTypeZeroImageMaterialization() {
|
||||
|
||||
{
|
||||
auto tornT = oldT;
|
||||
tornT[1] &= ~(0x1ffu << 20u);
|
||||
tornT[3] = (tornT[3] & ~(0xfu << 28u)) | (2u << 28u);
|
||||
srt.fill(0u);
|
||||
std::copy(tornT.begin(), tornT.end(), srt.begin());
|
||||
std::copy(outputV.begin(), outputV.end(), srt.begin() + 16u);
|
||||
@@ -2145,7 +2145,7 @@ void verifyTypeZeroImageMaterialization() {
|
||||
|
||||
{
|
||||
auto tornT = oldT;
|
||||
tornT[1] &= ~(0x1ffu << 20u);
|
||||
tornT[3] = (tornT[3] & ~(0xfu << 28u)) | (2u << 28u);
|
||||
srt.fill(0u);
|
||||
std::copy(tornT.begin(), tornT.end(), srt.begin());
|
||||
std::copy(outputV.begin(), outputV.end(), srt.begin() + 16u);
|
||||
@@ -2366,6 +2366,68 @@ void verifyTypeZeroImageMaterialization() {
|
||||
}
|
||||
}
|
||||
|
||||
void verifyUnsetFormatImageMaterialization() {
|
||||
using namespace ShaderRecompiler;
|
||||
constexpr std::array<std::uint32_t, 8> measuredImage{0xffffffffu, 0xffffffffu, 0x00000019u, 0xc0017901u, 0x00000342u, 0xc8000000u, 0xc0064900u, 0x0000062fu};
|
||||
const auto materialize = [](const std::array<std::uint32_t, 8>& words, const ImageResource& image) {
|
||||
IrResourcePlan plan;
|
||||
plan.srtPlanComplete = true;
|
||||
plan.resourceTrackingComplete = true;
|
||||
plan.userDataCount = 0u;
|
||||
plan.materializationSources = {0u};
|
||||
std::vector<std::unique_ptr<IrValue>> values;
|
||||
DescriptorSource source;
|
||||
source.dwordCount = static_cast<std::uint32_t>(words.size());
|
||||
for (std::uint32_t i = 0u; i < words.size(); ++i) {
|
||||
auto value = std::make_unique<IrValue>(IrOpcode::Void, IrType::U32, i);
|
||||
value->SetImmediateU32(words[i]);
|
||||
source.dwords[i] = value.get();
|
||||
values.push_back(std::move(value));
|
||||
}
|
||||
plan.descriptorSources.push_back(source);
|
||||
plan.info.images.push_back(image);
|
||||
plan.info.runtimeImageModes.push_back(ResourceMaterializer::RuntimeImageModes(image));
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceMaterializer{}.Materialize(plan, SrtRuntime{}, snapshot);
|
||||
return snapshot;
|
||||
};
|
||||
ImageResource sampled;
|
||||
sampled.source = 0u;
|
||||
sampled.resourceClass = ImageResourceClass::Sampled;
|
||||
sampled.dimension = RdnaImageDimension::Dim1DArray;
|
||||
sampled.read = true;
|
||||
for (const std::uint32_t format : {0u, 0x1ffu}) {
|
||||
auto words = measuredImage;
|
||||
words[1] = (words[1] & ~(0x1ffu << 20u)) | (format << 20u);
|
||||
const auto snapshot = materialize(words, sampled);
|
||||
require(snapshot.images.size() == 1u && snapshot.images[0].dwordCount == 8u && snapshot.images[0].dwords == words,
|
||||
"an unset-format measured image-type T# was rejected or altered during materialization");
|
||||
for (const bool atomic : {false, true}) {
|
||||
auto storage = sampled;
|
||||
storage.resourceClass = ImageResourceClass::Storage;
|
||||
storage.read = false;
|
||||
storage.written = !atomic;
|
||||
storage.atomic = atomic;
|
||||
bool rejected = false;
|
||||
try {
|
||||
static_cast<void>(materialize(words, storage));
|
||||
} catch (const std::runtime_error& error) {
|
||||
rejected = std::string_view(error.what()).find("image descriptor") != std::string_view::npos;
|
||||
}
|
||||
require(rejected, atomic ? "an unset-format atomic image descriptor was accepted" : "an unset-format writable image descriptor was accepted");
|
||||
}
|
||||
}
|
||||
const auto expectInvalidType = [&](std::uint32_t type, const char* message) {
|
||||
auto words = measuredImage;
|
||||
words[1] &= ~(0x1ffu << 20u);
|
||||
words[3] = (words[3] & ~(0xfu << 28u)) | (type << 28u);
|
||||
expectFailure([&] { static_cast<void>(materialize(words, sampled)); }, "runtime image descriptor is invalid", message);
|
||||
};
|
||||
expectInvalidType(2u, "a reserved TYPE 2 T# with an unset format was accepted as a null image");
|
||||
expectInvalidType(7u, "a reserved TYPE 7 T# with an unset format was accepted as a null image");
|
||||
expectInvalidType(15u, "an unimplemented TYPE 15 T# with an unset format was accepted as a null image");
|
||||
}
|
||||
|
||||
void verifyOutOfRangeConstantReads() {
|
||||
std::vector<std::uint32_t> code{0xf40c0200u, 0xfa000000u, 0xf4080500u, 0xfa000020u, 0xf4080600u, 0xfa000040u, 0xf4000400u, 0xfa000050u, 0xf4080a00u, 0xfa000080u, 0xbf8cc07fu, 0xf42c0814u, 0xfa000000u, 0xbf8cc07fu, 0xbf068010u, 0xbf850008u};
|
||||
for (std::uint32_t dword = 0; dword < 8u; dword++) {
|
||||
@@ -2773,6 +2835,22 @@ void verifyGuardedDescriptorSources() {
|
||||
AgcDriver::ShaderMemory memory({});
|
||||
expectFailure([&] { static_cast<void>(memory.Capture(request)); }, "runtime image descriptor is invalid", "guarded sources: an invalid T# behind a taken branch was accepted");
|
||||
}
|
||||
auto unsetFormat = valid;
|
||||
unsetFormat[1] = valid[1] & ~(0x1ffu << 20u);
|
||||
fill(unsetFormat, 1u);
|
||||
{
|
||||
AgcDriver::ShaderMemory memory({});
|
||||
const auto capture = memory.Capture(request);
|
||||
require(capture->snapshot.images.size() == 1u && capture->snapshot.images[0].dwords == unsetFormat, "guarded sources: a T# with an unset format behind a taken branch was rejected or altered");
|
||||
}
|
||||
auto filledFormat = valid;
|
||||
filledFormat[1] = valid[1] | (0x1ffu << 20u);
|
||||
fill(filledFormat, 1u);
|
||||
{
|
||||
AgcDriver::ShaderMemory memory({});
|
||||
const auto capture = memory.Capture(request);
|
||||
require(capture->snapshot.images.size() == 1u && capture->snapshot.images[0].dwords == filledFormat, "guarded sources: a T# with an all-ones format behind a taken branch was rejected or altered");
|
||||
}
|
||||
auto typeOne = valid;
|
||||
typeOne[3] = 0xfacu | (1u << 28u);
|
||||
fill(typeOne, 1u);
|
||||
@@ -10894,6 +10972,7 @@ int main(int argc, char** argv) {
|
||||
verifyLayoutPair();
|
||||
verifySignedSrtComparison();
|
||||
verifyTypeZeroImageMaterialization();
|
||||
verifyUnsetFormatImageMaterialization();
|
||||
verifyOutOfRangeConstantReads();
|
||||
verifySignedMinimum();
|
||||
verifyMayEvaluate();
|
||||
|
||||
Reference in new issue
Block a user