mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-07 13:39:55 +00:00
Merge pull request #10000 from EagleBearerYS/feat/shader-sampler-limits
feat(shader): raise the sampler and sampled-pair limits past the 32-bit sampler masks
This commit is contained in:
17 files changed
+461
-64
No files matched your search
@@ -68,10 +68,10 @@ private:
|
||||
std::uint64_t misses = 0;
|
||||
};
|
||||
|
||||
void RequireFilterMinmax(const Context& context, VkFormat format, std::uint32_t samplerMask, std::span<const std::shared_ptr<Sampler>> samplers);
|
||||
bool ForcesDegammaOn(std::uint32_t guestFormat, std::uint32_t samplerMask, std::span<const std::shared_ptr<Sampler>> samplers);
|
||||
void RequireBorderSwizzle(std::uint32_t bcSwizzle, std::uint32_t samplerMask, std::span<const std::shared_ptr<Sampler>> samplers);
|
||||
void RequireNonSeamlessCube(bool cube, std::uint32_t samplerMask, std::span<const std::shared_ptr<Sampler>> samplers);
|
||||
void RequireFilterMinmax(const Context& context, VkFormat format, std::uint64_t samplerMask, std::span<const std::shared_ptr<Sampler>> samplers);
|
||||
bool ForcesDegammaOn(std::uint32_t guestFormat, std::uint64_t samplerMask, std::span<const std::shared_ptr<Sampler>> samplers);
|
||||
void RequireBorderSwizzle(std::uint32_t bcSwizzle, std::uint64_t samplerMask, std::span<const std::shared_ptr<Sampler>> samplers);
|
||||
void RequireNonSeamlessCube(bool cube, std::uint64_t samplerMask, std::span<const std::shared_ptr<Sampler>> samplers);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Sampler.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp"
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
|
||||
@@ -102,10 +103,10 @@ namespace AgcDriver::Graphics {
|
||||
return sampler;
|
||||
}
|
||||
|
||||
void RequireFilterMinmax(const Context& context, VkFormat format, std::uint32_t samplerMask, std::span<const std::shared_ptr<Sampler>> samplers) {
|
||||
void RequireFilterMinmax(const Context& context, VkFormat format, std::uint64_t samplerMask, std::span<const std::shared_ptr<Sampler>> samplers) {
|
||||
bool filtered = false;
|
||||
for (std::uint32_t element = 0; element < 32u; ++element) {
|
||||
if (((samplerMask >> element) & 1u) == 0u) continue;
|
||||
for (auto mask = samplerMask; mask != 0u; mask &= mask - 1u) {
|
||||
const auto element = static_cast<std::uint32_t>(std::countr_zero(mask));
|
||||
if (element >= samplers.size()) Require(false, "a sampled texture is paired with sampler element " + std::to_string(element) + ", which its shader does not bind");
|
||||
filtered = filtered || samplers[element]->RequiresFilterMinmax();
|
||||
}
|
||||
@@ -123,29 +124,31 @@ namespace AgcDriver::Graphics {
|
||||
return requiresNonSeamlessCube;
|
||||
}
|
||||
|
||||
bool ForcesDegammaOn(std::uint32_t guestFormat, std::uint32_t samplerMask, std::span<const std::shared_ptr<Sampler>> samplers) {
|
||||
bool ForcesDegammaOn(std::uint32_t guestFormat, std::uint64_t samplerMask, std::span<const std::shared_ptr<Sampler>> samplers) {
|
||||
if (IsSrgbTextureFormat(guestFormat) || IgnoresForcedDegamma(guestFormat)) return false;
|
||||
for (std::uint32_t element = 0; element < 32u && element < samplers.size(); ++element) {
|
||||
if (((samplerMask >> element) & 1u) != 0u && samplers[element]->ForcesDegamma()) return true;
|
||||
for (auto mask = samplerMask; mask != 0u; mask &= mask - 1u) {
|
||||
const auto element = static_cast<std::uint32_t>(std::countr_zero(mask));
|
||||
if (element < samplers.size() && samplers[element]->ForcesDegamma()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void RequireBorderSwizzle(std::uint32_t bcSwizzle, std::uint32_t samplerMask, std::span<const std::shared_ptr<Sampler>> samplers) {
|
||||
void RequireBorderSwizzle(std::uint32_t bcSwizzle, std::uint64_t samplerMask, std::span<const std::shared_ptr<Sampler>> samplers) {
|
||||
constexpr std::uint32_t BcSwizzleXyzw = 0u;
|
||||
constexpr std::uint32_t BcSwizzleZyxw = 4u;
|
||||
if (bcSwizzle == BcSwizzleXyzw || bcSwizzle == BcSwizzleZyxw) return;
|
||||
for (std::uint32_t element = 0; element < 32u; ++element) {
|
||||
if (((samplerMask >> element) & 1u) == 0u) continue;
|
||||
for (auto mask = samplerMask; mask != 0u; mask &= mask - 1u) {
|
||||
const auto element = static_cast<std::uint32_t>(std::countr_zero(mask));
|
||||
if (element >= samplers.size()) Require(false, "a sampled texture is paired with sampler element " + std::to_string(element) + ", which its shader does not bind");
|
||||
if (samplers[element]->ReadsOpaqueBlackBorder()) Require(false, "a sampled texture whose BC swizzle " + std::to_string(bcSwizzle) + " moves the alpha channel is sampled through an opaque black border, which is not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
void RequireNonSeamlessCube(bool cube, std::uint32_t samplerMask, std::span<const std::shared_ptr<Sampler>> samplers) {
|
||||
void RequireNonSeamlessCube(bool cube, std::uint64_t samplerMask, std::span<const std::shared_ptr<Sampler>> samplers) {
|
||||
if (!cube) return;
|
||||
for (std::uint32_t element = 0; element < 32u && element < samplers.size(); ++element) {
|
||||
if (((samplerMask >> element) & 1u) != 0u && samplers[element]->RequiresNonSeamlessCube()) Require(false, "a cube texture is sampled through a sampler that disables seamless cube filtering, which needs VK_EXT_non_seamless_cube_map");
|
||||
for (auto mask = samplerMask; mask != 0u; mask &= mask - 1u) {
|
||||
const auto element = static_cast<std::uint32_t>(std::countr_zero(mask));
|
||||
if (element < samplers.size() && samplers[element]->RequiresNonSeamlessCube()) Require(false, "a cube texture is sampled through a sampler that disables seamless cube filtering, which needs VK_EXT_non_seamless_cube_map");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <bit>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <array>
|
||||
@@ -1987,7 +1988,7 @@ struct ReusedTable {
|
||||
std::vector<ReusedTableElement> elements;
|
||||
std::vector<GuestMemory::UnchangedQuery> queries;
|
||||
std::vector<std::pair<std::uint64_t, std::uint64_t>> ranges;
|
||||
std::vector<std::uint32_t> imageSamplers;
|
||||
std::vector<std::uint64_t> imageSamplers;
|
||||
std::vector<std::uint32_t> samplers;
|
||||
std::vector<std::string> bound;
|
||||
bool replayable = false;
|
||||
@@ -2856,7 +2857,10 @@ void VisitContentKey(const CompiledShader& shader, bool dataWords, bool movableB
|
||||
// must not serve a build with another (the variant implies it, this makes it explicit).
|
||||
packBits(binding.bufferWritten);
|
||||
visit(static_cast<std::uint32_t>(binding.imageSamplers.size()));
|
||||
for (const auto sampler : binding.imageSamplers) visit(sampler);
|
||||
for (const auto sampler : binding.imageSamplers) {
|
||||
visit(static_cast<std::uint32_t>(sampler));
|
||||
visit(static_cast<std::uint32_t>(sampler >> 32u));
|
||||
}
|
||||
visit(static_cast<std::uint32_t>(binding.imageShapes.size()));
|
||||
for (const auto shape : binding.imageShapes) visit(static_cast<std::uint32_t>(shape));
|
||||
}
|
||||
@@ -2877,7 +2881,7 @@ void ShaderResources::AppendContentKey(const CompiledShader& shader, bool dataWo
|
||||
};
|
||||
for (const auto& binding : shader.program->bindings) {
|
||||
addWords(20);
|
||||
addWords(binding.imageSamplers.size());
|
||||
addWords(binding.imageSamplers.size() * 2u);
|
||||
addWords(binding.imageShapes.size());
|
||||
if ((dataWords && !movableBuffers) || !DataRole(binding.role)) addWords(binding.guestDescriptor.size());
|
||||
for (const auto* bits : {&binding.imageWritten, &binding.samplerDepthCompare, &binding.imageDepthCompare, &binding.imageAtomic, &binding.samplerUnnormalized, &binding.samplerIntegerBorder, &binding.samplerCoordinatesIgnored, &binding.imageUnnormalized, &binding.imageUnnormalizedBaseLevel, &binding.bufferWritten}) addWords(bits->size() / 32u + (bits->size() % 32u != 0 ? 1u : 0u));
|
||||
@@ -5143,8 +5147,9 @@ void ShaderResources::resolveImageBinding(const ShaderRecompiler::DescriptorBind
|
||||
if (!shaderSamplers.empty()) {
|
||||
const bool singleLevel = DescriptorSingleLevel(words);
|
||||
const auto firstSampler = static_cast<std::size_t>(shaderSamplers.data() - samplers.data());
|
||||
for (std::uint32_t sampler = 0; sampler < shaderSamplers.size() && sampler < 32u; ++sampler) {
|
||||
if (((binding.imageSamplers[element] >> sampler) & 1u) == 0) continue;
|
||||
for (auto mask = binding.imageSamplers[element]; mask != 0u; mask &= mask - 1u) {
|
||||
const auto sampler = static_cast<std::uint32_t>(std::countr_zero(mask));
|
||||
if (sampler >= shaderSamplers.size()) break;
|
||||
auto& source = samplerSources.at(firstSampler + sampler);
|
||||
(singleLevel ? source.singleLevelImage : source.mipmappedImage) = true;
|
||||
}
|
||||
@@ -5398,8 +5403,9 @@ void ShaderResources::resolveImageTables() {
|
||||
RequireNonSeamlessCube(resource.dimension == TextureDimension::kCube, binding.imageSamplers.at(element), stageSamplers);
|
||||
const auto numeric = ShaderRecompiler::DepthBitsTextureWidth(words[1], words[3]) != 0u ? ShaderRecompiler::IrTextureNumericClass::Float : ShaderRecompiler::SampledTextureNumericClass(static_cast<ShaderRecompiler::IrBufferFormat>(resource.format));
|
||||
const bool integerImage = numeric == ShaderRecompiler::IrTextureNumericClass::Uint || numeric == ShaderRecompiler::IrTextureNumericClass::Sint;
|
||||
for (std::uint32_t sampler = 0; sampler < stageSamplers.size() && sampler < 32u; ++sampler) {
|
||||
if (((binding.imageSamplers.at(element) >> sampler) & 1u) == 0) continue;
|
||||
for (auto mask = binding.imageSamplers.at(element); mask != 0u; mask &= mask - 1u) {
|
||||
const auto sampler = static_cast<std::uint32_t>(std::countr_zero(mask));
|
||||
if (sampler >= stageSamplers.size()) break;
|
||||
const auto& source = samplerSources.at(table.firstSampler + sampler);
|
||||
const auto descriptor = DecodeSamplerResource(source.words, source.unnormalized, true, source.integerBorder);
|
||||
const bool border = descriptor.addressModeU == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER || descriptor.addressModeV == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER || descriptor.addressModeW == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
|
||||
|
||||
@@ -248,7 +248,7 @@ DescriptorBinding sampleBinding(std::uint32_t seed) {
|
||||
binding.samplerCoordinatesIgnored = {false, seed % 2 == 0, true};
|
||||
binding.imageUnnormalized = {true, false, seed % 2 == 0};
|
||||
binding.imageUnnormalizedBaseLevel = {false, true, seed % 3 == 0};
|
||||
binding.imageSamplers = {0x5u, 0u, 0x80000000u};
|
||||
binding.imageSamplers = {0x5u, 0u, 0x8000000080000000u};
|
||||
binding.bufferRead = {true, false, false, true, true, false};
|
||||
binding.bufferExtent = {32, 0, 16, 4096, 8, 0};
|
||||
return binding;
|
||||
|
||||
@@ -3176,6 +3176,11 @@ TableCompiled CompileTable(ShaderRecompiler::RecompileRequest request) {
|
||||
return out;
|
||||
}
|
||||
|
||||
std::shared_ptr<const ShaderRecompiler::ResourceCapture> CaptureTable(const ShaderRecompiler::RecompileRequest& request) {
|
||||
AgcDriver::ShaderMemory memory({});
|
||||
return memory.Capture(request);
|
||||
}
|
||||
|
||||
std::array<std::uint32_t, 8> TableSelected(const ShaderRecompiler::ImageTableSnapshot& tables, std::uint32_t table, const char* message) {
|
||||
namespace Abi = ShaderRecompiler::ImageTableAbi;
|
||||
const auto& column = tables.tables.at(table);
|
||||
@@ -4648,24 +4653,26 @@ void verifyImageTableUnkeyedAccess() {
|
||||
struct TableManyGuest {
|
||||
std::array<std::array<std::uint32_t, 8>, 128> heap{};
|
||||
std::array<std::uint32_t, 4> output{};
|
||||
std::array<std::uint32_t, 128> srt{};
|
||||
std::array<std::uint32_t, 160> srt{};
|
||||
};
|
||||
|
||||
constexpr std::uint32_t TableManyDirectWord = 96u;
|
||||
constexpr std::uint32_t TableManyExtraSamplerWord = 112u;
|
||||
constexpr std::uint32_t TableManyMoreSamplerWord = 116u;
|
||||
constexpr std::array<std::uint32_t, 8> TableManySamplerSgprs{8u, 12u, 64u, 68u, 72u, 76u, 80u, 84u};
|
||||
|
||||
std::vector<std::uint32_t> TableManyKeyCode(std::uint32_t first, std::uint32_t second, bool twoSamplers, std::uint32_t direct = 0u, bool extraSampler = false) {
|
||||
std::vector<std::uint32_t> TableManyKeyCode(std::uint32_t first, std::uint32_t second, std::uint32_t samplers, std::uint32_t direct = 0u, bool extraSampler = false) {
|
||||
std::vector<std::uint32_t> code{0xf4080100u, 0xfa000000u, 0xf4080200u, 0xfa000010u, 0xf4080300u, 0xfa000020u, 0xf4080700u, 0xfa000030u, 0xf4080a00u, 0xfa000040u, 0x7e100280u};
|
||||
for (std::uint32_t image = 0; image < direct; image++) code.insert(code.end(), {0xf40c0000u | ((44u + image * 8u) << 6u), 0xfa000000u | ((TableManyDirectWord + image * 8u) * 4u)});
|
||||
if (extraSampler) code.insert(code.end(), {0xf4080f00u, 0xfa000000u | (TableManyExtraSamplerWord * 4u)});
|
||||
for (std::uint32_t sampler = 2u; sampler < samplers; sampler++) code.insert(code.end(), {0xf4080000u | (TableManySamplerSgprs[sampler] << 6u), 0xfa000000u | ((TableManyMoreSamplerWord + (sampler - 2u) * 4u) * 4u)});
|
||||
for (std::uint32_t read = 0; read < first + second; read++) {
|
||||
code.insert(code.end(), {0xf4000400u, 0xfa000050u + read * 4u, 0x8f108510u, read < first ? 0xf42c0502u : 0xf42c0514u, 0x20000000u, 0xf09c0f08u, 0x00450400u, 0x06100908u});
|
||||
if (twoSamplers) code.insert(code.end(), {0xf09c0f08u, 0x00650400u, 0x06100908u});
|
||||
code.insert(code.end(), {0xf4000400u, 0xfa000050u + read * 4u, 0x8f108510u, read < first ? 0xf42c0502u : 0xf42c0514u, 0x20000000u});
|
||||
for (std::uint32_t sampler = 0; sampler < samplers; sampler++) code.insert(code.end(), {0xf09c0f08u, ((TableManySamplerSgprs[sampler] / 4u) << 21u) | 0x00050400u, 0x06100908u});
|
||||
}
|
||||
for (std::uint32_t image = 0; image < direct; image++) {
|
||||
const auto resource = ((44u + image * 8u) / 4u) << 16u;
|
||||
code.insert(code.end(), {0xf09c0f08u, 0x00400400u | resource, 0x06100908u});
|
||||
if (twoSamplers) code.insert(code.end(), {0xf09c0f08u, 0x00600400u | resource, 0x06100908u});
|
||||
for (std::uint32_t sampler = 0; sampler < samplers; sampler++) code.insert(code.end(), {0xf09c0f08u, ((TableManySamplerSgprs[sampler] / 4u) << 21u) | 0x00000400u | resource, 0x06100908u});
|
||||
if (extraSampler && image + 1u == direct) code.insert(code.end(), {0xf09c0f08u, 0x01e00400u | resource, 0x06100908u});
|
||||
}
|
||||
code.insert(code.end(), {0xe0700000u, 0x80070800u, 0xbf810000u});
|
||||
@@ -4692,6 +4699,8 @@ void verifySelectedRecordsWithinLimits(bool pairs, bool direct) {
|
||||
std::copy(direct.begin(), direct.end(), guest.srt.begin() + TableManyDirectWord + image * 8u);
|
||||
}
|
||||
guest.srt[TableManyExtraSamplerWord] = 0x91u;
|
||||
std::fill(guest.srt.begin() + TableManyMoreSamplerWord, guest.srt.end(), 0u);
|
||||
for (std::uint32_t sampler = 2u; sampler < TableManySamplerSgprs.size(); sampler++) guest.srt[TableManyMoreSamplerWord + (sampler - 2u) * 4u] = 0x92u;
|
||||
const auto address = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(guest.srt.data()));
|
||||
const std::array<std::uint32_t, 2> userData{static_cast<std::uint32_t>(address), static_cast<std::uint32_t>(address >> 32u)};
|
||||
const auto columnOf = [](const IrResourcePlan& plan, const ImageResource& image) { return *plan.descriptorSources.at(image.source).tableColumn; };
|
||||
@@ -4699,49 +4708,50 @@ void verifySelectedRecordsWithinLimits(bool pairs, bool direct) {
|
||||
return static_cast<std::uint32_t>(std::count_if(plan.info.images.begin(), plan.info.images.end(), [&](const ImageResource& image) { return image.table != NoTable && columnOf(plan, image).selected; }));
|
||||
};
|
||||
const std::string name = pairs ? "selected records within the pair limit" : "selected records within the image limit";
|
||||
const std::uint32_t limit = pairs ? ShaderInfo::MaxSampledPairs / 2u : ShaderInfo::MaxImages;
|
||||
const std::uint32_t samplers = pairs ? static_cast<std::uint32_t>(TableManySamplerSgprs.size()) : 1u;
|
||||
const std::uint32_t limit = pairs ? ShaderInfo::MaxSampledPairs / samplers : ShaderInfo::MaxImages;
|
||||
const auto outside = Abi::PoisonCode(static_cast<std::uint32_t>(Abi::PoisonReason::OutsideDomain));
|
||||
if (direct) {
|
||||
const auto directImages = [&](const IrResourcePlan& plan) {
|
||||
return static_cast<std::uint32_t>(std::count_if(plan.info.images.begin(), plan.info.images.end(), [](const ImageResource& image) { return image.table == NoTable; }));
|
||||
};
|
||||
const auto reads = limit - 1u;
|
||||
const auto fittingDirectCode = TableManyKeyCode(reads, 0u, pairs, 1u);
|
||||
const auto fittingDirectCode = TableManyKeyCode(reads, 0u, samplers, 1u);
|
||||
const auto fittingDirect = GetResourcePlan(TableRequest(fittingDirectCode, userData));
|
||||
const auto describe = [&](const IrResourcePlan& plan) {
|
||||
return std::to_string(plan.info.images.size()) + " images (" + std::to_string(selectedTables(plan)) + " selected tables, " + std::to_string(directImages(plan)) + " direct), " + std::to_string(plan.info.sampledPairs.size()) + " pairs";
|
||||
};
|
||||
require(fittingDirect->info.images.size() == limit && selectedTables(*fittingDirect) == reads && directImages(*fittingDirect) == 1u && fittingDirect->info.sampledPairs.size() == (pairs ? 2u : 1u) * limit, (name + ": selected reads and a direct image that fit lost the per-key split").c_str());
|
||||
std::cout << name << ": " << reads << " selected reads and 1 direct image" << (pairs ? ", each with 2 samplers" : "") << ": " << describe(*fittingDirect) << '\n';
|
||||
const auto pastDirectCode = TableManyKeyCode(reads, 0u, pairs, pairs ? 1u : 2u, pairs);
|
||||
require(fittingDirect->info.images.size() == limit && selectedTables(*fittingDirect) == reads && directImages(*fittingDirect) == 1u && fittingDirect->info.sampledPairs.size() == samplers * limit, (name + ": selected reads and a direct image that fit lost the per-key split").c_str());
|
||||
std::cout << name << ": " << reads << " selected reads and 1 direct image" << (pairs ? ", each with " + std::to_string(samplers) + " samplers" : std::string()) << ": " << describe(*fittingDirect) << '\n';
|
||||
const auto pastDirectCode = TableManyKeyCode(reads, 0u, samplers, pairs ? 1u : 2u, pairs);
|
||||
const auto pastDirectRequest = TableRequest(pastDirectCode, userData);
|
||||
const auto pastDirect = GetResourcePlan(pastDirectRequest);
|
||||
const auto mergedDirect = std::find_if(pastDirect->info.images.begin(), pastDirect->info.images.end(), [](const ImageResource& image) { return image.table != NoTable; });
|
||||
require(pastDirect->info.images.size() == (pairs ? 2u : 3u) && selectedTables(*pastDirect) == 0u && directImages(*pastDirect) == (pairs ? 1u : 2u) && pastDirect->info.sampledPairs.size() == (pairs ? 5u : 3u) && mergedDirect != pastDirect->info.images.end(), (name + ": selected reads that the direct " + (pairs ? "sampler" : "images") + " push past the limit were not merged into one table").c_str());
|
||||
require(pastDirect->info.images.size() == (pairs ? 2u : 3u) && selectedTables(*pastDirect) == 0u && directImages(*pastDirect) == (pairs ? 1u : 2u) && pastDirect->info.sampledPairs.size() == (pairs ? 2u * samplers + 1u : 3u) && mergedDirect != pastDirect->info.images.end(), (name + ": selected reads that the direct " + (pairs ? "sampler" : "images") + " push past the limit were not merged into one table").c_str());
|
||||
const auto mergedColumn = columnOf(*pastDirect, *mergedDirect);
|
||||
require(!mergedColumn.selected && mergedColumn.selection == 0u && !mergedColumn.unkeyed && mergedColumn.keyDomains.size() == reads, (name + ": the table merged beside the direct " + (pairs ? "sampler" : "images") + " is not the union of the keys").c_str());
|
||||
const auto compiledDirect = CompileTable(pastDirectRequest);
|
||||
const auto& directTables = compiledDirect.capture->snapshot.tables;
|
||||
const auto capturedDirect = pairs ? CaptureTable(pastDirectRequest) : CompileTable(pastDirectRequest).capture;
|
||||
const auto& directTables = capturedDirect->snapshot.tables;
|
||||
require(directTables.tables.size() == 1u, (name + ": the snapshot beside the direct " + (pairs ? "sampler" : "images") + " does not hold one table").c_str());
|
||||
const auto& directSnapshot = directTables.tables[mergedDirect->table];
|
||||
for (const auto key : {2u, 2u + reads / 2u, 1u + reads}) {
|
||||
require(key < directSnapshot.codes.size() && (directSnapshot.codes[key] & Abi::PoisonFlag) == 0u && directSnapshot.words.at(directSnapshot.codes[key]).dwords == heap[key], (name + ": a key beside the direct " + (pairs ? "sampler" : "images") + " does not bind its record").c_str());
|
||||
}
|
||||
require(directSnapshot.codes.at(1) == outside && directSnapshot.codes.at(reads + 2u) == outside, (name + ": a key no read selects was bound beside the direct " + (pairs ? "sampler" : "images")).c_str());
|
||||
std::cout << name << ": " << reads << " selected reads and " << (pairs ? "1 direct image with a third sampler" : "2 direct images") << ": " << describe(*pastDirect) << ", the reads merged into one table with " << mergedColumn.keyDomains.size() << " key domains\n";
|
||||
std::cout << name << ": " << reads << " selected reads and " << (pairs ? "1 direct image with one more sampler" : "2 direct images") << ": " << describe(*pastDirect) << ", the reads merged into one table with " << mergedColumn.keyDomains.size() << " key domains\n";
|
||||
return;
|
||||
}
|
||||
const auto fitting = TableManyKeyCode(limit, 0u, pairs);
|
||||
const auto fitting = TableManyKeyCode(limit, 0u, samplers);
|
||||
const auto atLimit = GetResourcePlan(TableRequest(fitting, userData));
|
||||
require(atLimit->info.images.size() == limit && selectedTables(*atLimit) == limit && atLimit->info.sampledPairs.size() == (pairs ? 2u : 1u) * limit, (name + ": reads that fit lost the per-key split").c_str());
|
||||
const auto past = TableManyKeyCode(limit + 1u, 0u, pairs);
|
||||
require(atLimit->info.images.size() == limit && selectedTables(*atLimit) == limit && atLimit->info.sampledPairs.size() == samplers * limit, (name + ": reads that fit lost the per-key split").c_str());
|
||||
const auto past = TableManyKeyCode(limit + 1u, 0u, samplers);
|
||||
const auto request = TableRequest(past, userData);
|
||||
const auto plan = GetResourcePlan(request);
|
||||
require(plan->info.images.size() == 1u && plan->info.images[0].table == 0u && plan->info.sampledPairs.size() == (pairs ? 2u : 1u), (name + ": reads past the limit were not merged into one table").c_str());
|
||||
require(plan->info.images.size() == 1u && plan->info.images[0].table == 0u && plan->info.sampledPairs.size() == samplers, (name + ": reads past the limit were not merged into one table").c_str());
|
||||
const auto merged = columnOf(*plan, plan->info.images[0]);
|
||||
require(!merged.selected && merged.selection == 0u && !merged.unkeyed && merged.keyDomains.size() == limit + 1u, (name + ": the merged table is not the union of the keys").c_str());
|
||||
const auto compiled = CompileTable(request);
|
||||
const auto& tables = compiled.capture->snapshot.tables;
|
||||
const auto captured = pairs ? CaptureTable(request) : CompileTable(request).capture;
|
||||
const auto& tables = captured->snapshot.tables;
|
||||
require(tables.tables.size() == 1u, (name + ": the snapshot does not hold one table").c_str());
|
||||
const auto& snapshot = tables.tables[0];
|
||||
for (const auto key : {2u, 2u + limit / 2u, 2u + limit}) {
|
||||
@@ -4749,12 +4759,151 @@ void verifySelectedRecordsWithinLimits(bool pairs, bool direct) {
|
||||
}
|
||||
require(snapshot.codes.at(1) == outside && snapshot.codes.at(limit + 3u) == outside, (name + ": a key no read selects was bound").c_str());
|
||||
if (pairs) return;
|
||||
const auto split = TableManyKeyCode(40u, 30u, false);
|
||||
const auto split = TableManyKeyCode(40u, 30u, 1u);
|
||||
const auto twoHeaps = GetResourcePlan(TableRequest(split, userData));
|
||||
const auto unselected = std::find_if(twoHeaps->info.images.begin(), twoHeaps->info.images.end(), [&](const ImageResource& image) { return !columnOf(*twoHeaps, image).selected; });
|
||||
require(twoHeaps->info.images.size() == 31u && selectedTables(*twoHeaps) == 30u && unselected != twoHeaps->info.images.end() && columnOf(*twoHeaps, *unselected).keyDomains.size() == 40u, (name + ": the larger heap was not merged first, or the smaller one lost its split").c_str());
|
||||
}
|
||||
|
||||
struct SamplerLimitGuest {
|
||||
std::array<std::uint32_t, 1024> record{};
|
||||
std::array<std::uint32_t, 4> output{};
|
||||
std::array<std::uint32_t, 256> srt{};
|
||||
};
|
||||
|
||||
constexpr std::uint32_t SamplerLimitTableSamplerByte = 2048u;
|
||||
constexpr std::uint32_t SamplerLimitDirectSamplerWord = 8u;
|
||||
constexpr std::uint32_t SamplerLimitDirectImageWord = 160u;
|
||||
|
||||
std::vector<std::uint32_t> SamplerLimitCode(std::uint32_t tableSamplers, bool directImages, const std::vector<std::pair<std::uint32_t, std::uint32_t>>& pairs) {
|
||||
std::vector<std::uint32_t> code{0xf4080100u, 0xfa000000u, 0xf4080700u, 0xfa000010u, 0x7e200500u, 0x8f108c10u, 0x7e100280u};
|
||||
for (const auto& [image, sampler] : pairs) {
|
||||
if (directImages) code.insert(code.end(), {0xf40c0500u, 0xfa000000u | ((SamplerLimitDirectImageWord + image * 8u) * 4u)});
|
||||
else code.insert(code.end(), {0xf42c0502u, 0x20000000u | (image * 32u)});
|
||||
if (sampler < tableSamplers) code.insert(code.end(), {0xf4280202u, 0x20000000u | (SamplerLimitTableSamplerByte + sampler * 16u)});
|
||||
else code.insert(code.end(), {0xf4080200u, 0xfa000000u | ((SamplerLimitDirectSamplerWord + (sampler - tableSamplers) * 4u) * 4u)});
|
||||
code.insert(code.end(), {0xf09c0f08u, 0x00450400u, 0x06100908u});
|
||||
}
|
||||
code.insert(code.end(), {0xe0700000u, 0x80070800u, 0xbf810000u});
|
||||
return code;
|
||||
}
|
||||
|
||||
std::uint32_t SamplerLimitImageOf(const std::uint32_t* words) {
|
||||
return (words[2] >> 14u) - 1u;
|
||||
}
|
||||
|
||||
void verifySamplerLimits() {
|
||||
using namespace ShaderRecompiler;
|
||||
static TableTexture textures[4];
|
||||
alignas(4096) static SamplerLimitGuest guest;
|
||||
constexpr std::uint32_t Images = 51u;
|
||||
constexpr std::uint32_t TableSamplers = 60u;
|
||||
guest.record = {};
|
||||
for (std::uint32_t image = 0; image < Images; image++) {
|
||||
const auto words = TableT(textures[image % 4u].bytes.data(), TableFormat8888UNorm, TableType2D, image + 1u);
|
||||
std::copy(words.begin(), words.end(), guest.record.begin() + image * 8u);
|
||||
}
|
||||
for (std::uint32_t sampler = 0; sampler < 64u; sampler++) {
|
||||
const std::array<std::uint32_t, 4> words{0x92u, (sampler + 1u) << 12u, 0u, 0u};
|
||||
std::copy(words.begin(), words.end(), guest.record.begin() + SamplerLimitTableSamplerByte / 4u + sampler * 4u);
|
||||
}
|
||||
guest.srt = {};
|
||||
const auto heapV = TableV(guest.record.data(), 4096u, 1u);
|
||||
const auto outputV = TableV(guest.output.data(), 0u, 16u);
|
||||
std::copy(heapV.begin(), heapV.end(), guest.srt.begin());
|
||||
std::copy(outputV.begin(), outputV.end(), guest.srt.begin() + 4);
|
||||
for (std::uint32_t sampler = 0; sampler < 36u; sampler++) {
|
||||
const std::array<std::uint32_t, 4> words{0x92u, (sampler + 101u) << 12u, 0u, 0u};
|
||||
std::copy(words.begin(), words.end(), guest.srt.begin() + SamplerLimitDirectSamplerWord + sampler * 4u);
|
||||
}
|
||||
for (std::uint32_t image = 0; image < 3u; image++) {
|
||||
const auto words = TableT(textures[image].bytes.data(), image == 1u ? TableFormat32UInt : TableFormat8888UNorm, TableType2D, image + 1u);
|
||||
std::copy(words.begin(), words.end(), guest.srt.begin() + SamplerLimitDirectImageWord + image * 8u);
|
||||
}
|
||||
const auto address = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(guest.srt.data()));
|
||||
const std::array<std::uint32_t, 2> userData{static_cast<std::uint32_t>(address), static_cast<std::uint32_t>(address >> 32u)};
|
||||
|
||||
const auto directOf = [](std::uint32_t image) { return image < 19u ? 0u : image < 36u ? 1u : 2u; };
|
||||
const auto riftPairs = [&](std::uint32_t tableSamplers) {
|
||||
std::vector<std::pair<std::uint32_t, std::uint32_t>> pairs;
|
||||
for (std::uint32_t image = 0; image < Images; image++) pairs.emplace_back(image, image);
|
||||
for (std::uint32_t sampler = Images; sampler < tableSamplers; sampler++) pairs.emplace_back(sampler - Images, sampler);
|
||||
for (std::uint32_t image = 0; image < Images; image++) pairs.emplace_back(image, tableSamplers + directOf(image));
|
||||
pairs.emplace_back(0u, tableSamplers + 2u);
|
||||
return pairs;
|
||||
};
|
||||
const auto riftCode = SamplerLimitCode(TableSamplers, false, riftPairs(TableSamplers));
|
||||
const auto riftRequest = TableRequest(riftCode, userData);
|
||||
const auto rift = GetResourcePlan(riftRequest);
|
||||
const auto& samplers = rift->info.samplers;
|
||||
const auto tableCount = static_cast<std::uint32_t>(std::count_if(samplers.begin(), samplers.end(), [](const SamplerResource& sampler) { return sampler.table != NoTable; }));
|
||||
require(samplers.size() == TableSamplers + 3u && tableCount == TableSamplers && rift->info.images.size() == Images && rift->info.sampledPairs.size() == 112u, ("sampler limits: the program planned " + std::to_string(samplers.size()) + " samplers (" + std::to_string(tableCount) + " through the table), " + std::to_string(rift->info.images.size()) + " images and " + std::to_string(rift->info.sampledPairs.size()) + " pairs, expected 63 (60), 51 and 112").c_str());
|
||||
std::vector<std::uint32_t> directResources;
|
||||
for (std::uint32_t resource = 0; resource < samplers.size(); resource++) {
|
||||
if (samplers[resource].table == NoTable) directResources.push_back(resource);
|
||||
}
|
||||
require(directResources.size() == 3u && directResources.front() >= 32u, "sampler limits: the direct samplers were not planned after the table samplers, at resource indices past 31");
|
||||
const auto compiled = CompileTable(riftRequest);
|
||||
const auto* guestSamplers = TableBinding(*compiled.result, DescriptorRole::GuestSamplers);
|
||||
require(guestSamplers != nullptr && guestSamplers->guestDescriptor.size() == 6u * 4u, "sampler limits: the three direct samplers are not bound as six sampler elements");
|
||||
for (std::uint32_t element = 0; element < 6u; element++) {
|
||||
require(guestSamplers->guestDescriptor[element * 4u + 1u] == (element / 2u + 101u) << 12u, "sampler limits: a direct sampler element does not hold its S#");
|
||||
}
|
||||
const auto* table = TableBinding(*compiled.result, DescriptorRole::ImageTable);
|
||||
require(table != nullptr && table->count == Images && table->imageSamplers.size() == Images, "sampler limits: the image table does not bind the 51 images with their sampler masks");
|
||||
for (std::uint32_t element = 0; element < Images; element++) {
|
||||
const auto image = SamplerLimitImageOf(table->guestDescriptor.data() + element * 8u);
|
||||
require(image < Images, "sampler limits: an image table element does not hold one of the images");
|
||||
auto expected = std::uint64_t{3} << (directOf(image) * 2u);
|
||||
if (image == 0u) expected |= std::uint64_t{3} << 4u;
|
||||
require(table->imageSamplers[element] == expected, ("sampler limits: image " + std::to_string(image) + " is paired with sampler elements " + std::to_string(table->imageSamplers[element]) + ", expected " + std::to_string(expected)).c_str());
|
||||
}
|
||||
std::cout << "sampler limits: 60 table samplers and 3 direct samplers (resources " << directResources[0] << " to " << directResources[2] << "), 51 images and 112 pairs planned; each image table element pairs with its direct sampler elements\n";
|
||||
|
||||
constexpr std::uint32_t Direct = 20u;
|
||||
std::vector<std::pair<std::uint32_t, std::uint32_t>> directPairs;
|
||||
for (std::uint32_t sampler = 0; sampler < Direct; sampler++) {
|
||||
directPairs.emplace_back(0u, sampler);
|
||||
directPairs.emplace_back(1u, sampler);
|
||||
if (sampler >= 16u) directPairs.emplace_back(2u, sampler);
|
||||
}
|
||||
const auto directCode = SamplerLimitCode(0u, true, directPairs);
|
||||
const auto direct = CompileTable(TableRequest(directCode, userData));
|
||||
const auto* directSamplers = TableBinding(*direct.result, DescriptorRole::GuestSamplers);
|
||||
require(directSamplers != nullptr && directSamplers->guestDescriptor.size() == Direct * 2u * 4u, "sampler limits: 20 direct samplers, each paired with a filtered and an integer image, are not bound as 40 sampler elements");
|
||||
const auto all = (std::uint64_t{1} << (Direct * 2u)) - 1u;
|
||||
std::uint32_t pairedImages = 0u;
|
||||
for (const auto& binding : direct.result->bindings) {
|
||||
if (binding.kind != DescriptorKind::SampledImage || binding.role != DescriptorRole::GuestImages) continue;
|
||||
for (std::uint32_t element = 0; element < binding.count; element++) {
|
||||
const auto image = SamplerLimitImageOf(binding.guestDescriptor.data() + element * 8u);
|
||||
const auto expected = image == 2u ? all & ~((std::uint64_t{1} << 32u) - 1u) : all;
|
||||
require(image < 3u && binding.imageSamplers.at(element) == expected, ("sampler limits: direct image " + std::to_string(image) + " is paired with sampler elements " + std::to_string(binding.imageSamplers.at(element)) + ", expected " + std::to_string(expected)).c_str());
|
||||
pairedImages++;
|
||||
}
|
||||
}
|
||||
require(pairedImages == 3u, "sampler limits: the three direct images are not bound");
|
||||
std::cout << "sampler limits: 20 direct samplers bound as 40 sampler elements; the image sampled only through samplers 16 to 19 pairs with elements 32 to 39\n";
|
||||
|
||||
constexpr std::uint32_t TableDirect = 18u;
|
||||
std::vector<std::pair<std::uint32_t, std::uint32_t>> tablePairs;
|
||||
for (std::uint32_t sampler = 0; sampler < TableDirect; sampler++) tablePairs.emplace_back(sampler % 2u, sampler);
|
||||
const auto tableDirectCode = SamplerLimitCode(0u, false, tablePairs);
|
||||
const auto tableDirect = CompileTable(TableRequest(tableDirectCode, userData));
|
||||
const auto* tableDirectSamplers = TableBinding(*tableDirect.result, DescriptorRole::GuestSamplers);
|
||||
require(tableDirectSamplers != nullptr && tableDirectSamplers->guestDescriptor.size() == TableDirect * 2u * 4u, "sampler limits: 18 direct samplers paired with image table images are not bound as 36 sampler elements");
|
||||
const auto* tableImages = TableBinding(*tableDirect.result, DescriptorRole::ImageTable);
|
||||
require(tableImages != nullptr && tableImages->count == 2u && tableImages->imageSamplers.size() == 2u, "sampler limits: the image table does not bind the two images beside 18 direct samplers");
|
||||
for (std::uint32_t element = 0; element < 2u; element++) {
|
||||
const auto image = SamplerLimitImageOf(tableImages->guestDescriptor.data() + element * 8u);
|
||||
std::uint64_t expected = 0u;
|
||||
for (std::uint32_t sampler = image; sampler < TableDirect; sampler += 2u) expected |= std::uint64_t{3} << (sampler * 2u);
|
||||
require(image < 2u && tableImages->imageSamplers[element] == expected, ("sampler limits: image table image " + std::to_string(image) + " is paired with sampler elements " + std::to_string(tableImages->imageSamplers[element]) + ", expected " + std::to_string(expected)).c_str());
|
||||
}
|
||||
std::cout << "sampler limits: 18 direct samplers beside an image table bound as 36 sampler elements; each table image pairs with its samplers' elements up to 35\n";
|
||||
|
||||
}
|
||||
|
||||
void verifyImageTableSrtKey() {
|
||||
using namespace ShaderRecompiler;
|
||||
namespace Abi = ImageTableAbi;
|
||||
@@ -10955,6 +11104,11 @@ int main(int argc, char** argv) {
|
||||
std::cout << "Direct samplers count toward the sampled pair limit beside selected image-table records\n";
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--sampler-limits") {
|
||||
verifySamplerLimits();
|
||||
std::cout << "Sampler limits passed\n";
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--srt-read-patching") {
|
||||
verifyUniformVectorComponents();
|
||||
verifyWaterfallRuntimeValues();
|
||||
|
||||
@@ -21,7 +21,8 @@ if(BUILD_TESTING)
|
||||
add_test(NAME agc_shader_selected_records_pair_limit COMMAND agc_shader_memory_tests --selected-records-pair-limit)
|
||||
add_test(NAME agc_shader_selected_records_direct_image_limit COMMAND agc_shader_memory_tests --selected-records-direct-image-limit)
|
||||
add_test(NAME agc_shader_selected_records_direct_pair_limit COMMAND agc_shader_memory_tests --selected-records-direct-pair-limit)
|
||||
set_tests_properties(agc_shader_selected_records_image_limit agc_shader_selected_records_pair_limit agc_shader_selected_records_direct_image_limit agc_shader_selected_records_direct_pair_limit PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
|
||||
add_test(NAME agc_shader_sampler_limits COMMAND agc_shader_memory_tests --sampler-limits)
|
||||
set_tests_properties(agc_shader_selected_records_image_limit agc_shader_selected_records_pair_limit agc_shader_selected_records_direct_image_limit agc_shader_selected_records_direct_pair_limit agc_shader_sampler_limits PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
|
||||
add_test(NAME agc_shader_srt_read_patching COMMAND agc_shader_memory_tests --srt-read-patching)
|
||||
set_tests_properties(agc_shader_srt_read_patching PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
|
||||
add_test(NAME agc_shader_descriptor_phis COMMAND agc_shader_memory_tests --descriptor-phis)
|
||||
@@ -69,7 +70,7 @@ if(BUILD_TESTING)
|
||||
add_test(NAME agc_shader_entry_lane_proof_budget COMMAND agc_shader_memory_tests --entry-lane-proof-budget)
|
||||
set_tests_properties(agc_shader_entry_lane_proof_budget PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_shader_bindless agc_shader_selected_records_image_limit agc_shader_selected_records_pair_limit agc_shader_selected_records_direct_image_limit agc_shader_selected_records_direct_pair_limit agc_shader_srt_read_patching agc_shader_type_zero_image_materialization agc_shader_frontend_pair agc_shader_frontend_pair_off agc_shader_layout_pair agc_shader_layout_pair_off agc_shader_result_memo_budget agc_shader_result_memo_budget_threads agc_shader_result_memo_budget_masked agc_shader_entry_lane_proof_budget PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
set_tests_properties(agc_shader_bindless agc_shader_selected_records_image_limit agc_shader_selected_records_pair_limit agc_shader_selected_records_direct_image_limit agc_shader_selected_records_direct_pair_limit agc_shader_srt_read_patching agc_shader_type_zero_image_materialization agc_shader_frontend_pair agc_shader_frontend_pair_off agc_shader_layout_pair agc_shader_layout_pair_off agc_shader_result_memo_budget agc_shader_result_memo_budget_threads agc_shader_result_memo_budget_masked agc_shader_entry_lane_proof_budget agc_shader_sampler_limits PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
agc_add_default_test(agc_shader_memory_tests)
|
||||
endif()
|
||||
|
||||
@@ -236,7 +236,7 @@ void Gather(AgcDriver::VulkanDevice& device, const Sampler& sampler, const char*
|
||||
}
|
||||
}
|
||||
|
||||
std::uint32_t PairedSamplers(const ShaderRecompiler::RecompileResult& result, std::uint32_t format) {
|
||||
std::uint64_t PairedSamplers(const ShaderRecompiler::RecompileResult& result, std::uint32_t format) {
|
||||
for (const auto& binding : result.bindings) {
|
||||
if (binding.kind != ShaderRecompiler::DescriptorKind::SampledImage) continue;
|
||||
for (std::uint32_t element = 0; element < binding.count; ++element) {
|
||||
|
||||
@@ -0,0 +1,229 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
|
||||
#include "Recompiler.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using AgcDriver::Graphics::Require;
|
||||
using ShaderRecompiler::ShaderStage;
|
||||
|
||||
constexpr std::uint32_t Width = 64;
|
||||
constexpr std::uint32_t Height = 4;
|
||||
constexpr std::uint32_t Lanes = 64;
|
||||
constexpr std::uint32_t Samplers = 17;
|
||||
constexpr std::uint32_t Last = Samplers - 1u;
|
||||
constexpr std::uint32_t SamplerWord = 32;
|
||||
constexpr std::uint32_t Format32UInt = 20;
|
||||
constexpr std::uint32_t Format32Float = 22;
|
||||
constexpr std::uint32_t Format8888UNorm = 56;
|
||||
constexpr std::uint32_t Format8888Srgb = 130;
|
||||
constexpr std::uint32_t Type2D = 9;
|
||||
constexpr std::uint32_t ForceDegamma = 1u << 20u;
|
||||
constexpr std::uint32_t Elements = Last * 2u + 1u;
|
||||
constexpr std::uint64_t FloatElements = (std::uint64_t{1} << Elements) - 1u;
|
||||
constexpr std::uint64_t IntegerElements = (std::uint64_t{1} << (Last * 2u)) - 1u;
|
||||
constexpr std::array<std::uint32_t, 4> PointClamp{0x92u, (4u * 256u) << 12u, 0u, 0u};
|
||||
constexpr std::array<std::uint32_t, 4> OpaqueBlackBorder{0x1b6u, (4u * 256u) << 12u, 0u, 1u << 30u};
|
||||
constexpr std::array<std::uint32_t, 4> OpaqueWhiteBorder{0x1b6u, (4u * 256u) << 12u, 0u, 2u << 30u};
|
||||
|
||||
alignas(256) std::array<float, Width * Height> FloatTexels{};
|
||||
alignas(256) std::array<std::uint32_t, Width * Height> ColorTexels{};
|
||||
alignas(256) std::array<std::uint32_t, Width * Height> UintTexels{};
|
||||
alignas(256) std::array<float, Lanes * 16u> Output{};
|
||||
alignas(256) std::array<std::uint32_t, 128> Srt{};
|
||||
|
||||
std::vector<std::uint32_t> SamplerCode(bool lastOutside) {
|
||||
std::vector<std::uint32_t> code{0xf4080700u, 0xfa000020u, 0xf4080900u, 0xfa000040u, 0xf4080a00u, 0xfa000050u};
|
||||
code.insert(code.end(), {0x360200bfu, 0x7e020d01u, 0x060202f0u, 0x100202ffu, 0x3c800000u, 0x7e0402ffu, 0x3e000000u, 0x061402f4u, 0x7e160302u, 0x7e220280u});
|
||||
for (std::uint32_t sampler = 0; sampler < Samplers; ++sampler) {
|
||||
code.insert(code.end(), {0xf4080800u, 0xfa000000u | ((SamplerWord + sampler * 4u) * 4u)});
|
||||
if (sampler == Last) {
|
||||
code.insert(code.end(), {0xf09c8f08u, lastOutside ? 0x01090c0au : 0x01090c01u});
|
||||
continue;
|
||||
}
|
||||
code.insert(code.end(), {0xf09c8f08u, 0x01090401u, sampler == 0u ? 0x7e100304u : 0x06100908u, 0xf09c8f08u, 0x010a0401u, 0x4a222304u});
|
||||
}
|
||||
code.insert(code.end(), {0x7e240308u, 0x7e120202u, 0x34201286u, 0x4a202100u, 0x34202084u, 0xe0781000u, 0x80070c10u, 0xe0741800u, 0x80071110u, 0xbf810000u});
|
||||
return code;
|
||||
}
|
||||
|
||||
const std::vector<std::uint32_t> InsideCode = SamplerCode(false);
|
||||
const std::vector<std::uint32_t> OutsideCode = SamplerCode(true);
|
||||
|
||||
std::array<std::uint32_t, 4> Buffer(const void* base, std::uint32_t bytes) {
|
||||
const auto address = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(base));
|
||||
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu), bytes, 0x31016facu};
|
||||
}
|
||||
|
||||
std::array<std::uint32_t, 4> Image(const void* texels, std::uint32_t format, std::uint32_t bcSwizzle = 0u) {
|
||||
const auto address = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(texels));
|
||||
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) | (1u << 31u), 0xfacu | (bcSwizzle << 25u) | (Type2D << 28u)};
|
||||
}
|
||||
|
||||
struct Outcome {
|
||||
std::vector<float> lanes;
|
||||
std::vector<std::uint32_t> integerSums;
|
||||
std::vector<float> floatSums;
|
||||
std::vector<std::array<std::uint32_t, 4>> samplers;
|
||||
std::vector<std::uint64_t> imageSamplers;
|
||||
};
|
||||
|
||||
Outcome Run(AgcDriver::VulkanDevice& device, const std::vector<std::uint32_t>& code, const std::array<std::uint32_t, 4>& image, const std::array<std::uint32_t, 4>& last) {
|
||||
Srt = {};
|
||||
const auto output = Buffer(Output.data(), static_cast<std::uint32_t>(sizeof(Output)));
|
||||
std::copy(output.begin(), output.end(), Srt.begin() + 8);
|
||||
const auto integerImage = Image(UintTexels.data(), Format32UInt);
|
||||
std::copy(image.begin(), image.end(), Srt.begin() + 16);
|
||||
std::copy(integerImage.begin(), integerImage.end(), Srt.begin() + 20);
|
||||
for (std::uint32_t sampler = 0; sampler < Samplers; ++sampler) {
|
||||
const auto& words = sampler == Last ? last : PointClamp;
|
||||
std::copy(words.begin(), words.end(), Srt.begin() + SamplerWord + sampler * 4u);
|
||||
}
|
||||
const auto rootAddress = reinterpret_cast<std::uintptr_t>(Srt.data());
|
||||
const std::array<std::uint32_t, 2> userData{static_cast<std::uint32_t>(rootAddress), static_cast<std::uint32_t>(rootAddress >> 32u)};
|
||||
const std::span<const std::uint32_t> span(code);
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 2> memory{{
|
||||
{reinterpret_cast<std::uintptr_t>(span.data()), std::as_bytes(span)},
|
||||
{reinterpret_cast<std::uintptr_t>(Srt.data()), std::as_bytes(std::span(Srt))},
|
||||
}};
|
||||
const ShaderRecompiler::ShaderComputeStageInfo compute{{Lanes, 1, 1}, 0u, {true, false, false}, false, 1};
|
||||
ShaderRecompiler::RecompileRequest request{
|
||||
{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(span.data()), span, 0, {}},
|
||||
{64, 0, userData, compute, std::nullopt, std::nullopt, memory},
|
||||
device.Target(),
|
||||
{0, 0, 0, 128}
|
||||
};
|
||||
request.useCache = false;
|
||||
const auto result = ShaderRecompiler::Recompile(request);
|
||||
Outcome outcome;
|
||||
for (const auto& binding : result.bindings) {
|
||||
if (binding.role == ShaderRecompiler::DescriptorRole::GuestSamplers) {
|
||||
for (std::size_t word = 0; word + 4u <= binding.guestDescriptor.size(); word += 4u) outcome.samplers.push_back({binding.guestDescriptor[word], binding.guestDescriptor[word + 1u], binding.guestDescriptor[word + 2u], binding.guestDescriptor[word + 3u]});
|
||||
}
|
||||
if (binding.kind == ShaderRecompiler::DescriptorKind::SampledImage) outcome.imageSamplers.insert(outcome.imageSamplers.end(), binding.imageSamplers.begin(), binding.imageSamplers.end());
|
||||
}
|
||||
Output.fill(-1.0f);
|
||||
device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(span.data()));
|
||||
device.WaitIdle();
|
||||
outcome.lanes.assign(Output.begin(), Output.begin() + Lanes * 4u);
|
||||
for (std::uint32_t lane = 0; lane < Lanes; ++lane) {
|
||||
outcome.integerSums.push_back(std::bit_cast<std::uint32_t>(Output[512u + lane * 4u]));
|
||||
outcome.floatSums.push_back(Output[513u + lane * 4u]);
|
||||
}
|
||||
return outcome;
|
||||
}
|
||||
|
||||
std::string Refusal(AgcDriver::VulkanDevice& device, const std::vector<std::uint32_t>& code, const std::array<std::uint32_t, 4>& image, const std::array<std::uint32_t, 4>& last) {
|
||||
try {
|
||||
static_cast<void>(Run(device, code, image, last));
|
||||
} catch (const std::exception& error) {
|
||||
return error.what();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
float Component(const Outcome& outcome, std::uint32_t lane, std::uint32_t component) {
|
||||
return outcome.lanes.at(lane * 4u + component);
|
||||
}
|
||||
|
||||
float FirstSamples(float texel) {
|
||||
float sum = texel;
|
||||
for (std::uint32_t sampler = 1; sampler < Last; ++sampler) sum += texel;
|
||||
return sum;
|
||||
}
|
||||
|
||||
void Fill() {
|
||||
for (std::uint32_t y = 0; y < Height; ++y) {
|
||||
for (std::uint32_t x = 0; x < Width; ++x) {
|
||||
FloatTexels[y * Width + x] = static_cast<float>(x) + 100.0f * static_cast<float>(y) + 0.25f;
|
||||
ColorTexels[y * Width + x] = 0x1000u + x * 3u + 0x100u * y;
|
||||
UintTexels[y * Width + x] = 0x2000u + x * 5u + 0x100u * y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CheckPairing(AgcDriver::VulkanDevice& device) {
|
||||
const auto outcome = Run(device, InsideCode, Image(FloatTexels.data(), Format32Float), OpaqueWhiteBorder);
|
||||
Require(outcome.samplers.size() == Elements, "many direct samplers: the sampler binding holds " + std::to_string(outcome.samplers.size()) + " elements, expected " + std::to_string(Elements));
|
||||
const auto& words = outcome.samplers[Elements - 1u];
|
||||
Require(words == OpaqueWhiteBorder, "many direct samplers: sampler element " + std::to_string(Elements - 1u) + " does not hold the last S#");
|
||||
std::vector<std::uint64_t> expected{FloatElements, IntegerElements};
|
||||
auto paired = outcome.imageSamplers;
|
||||
std::sort(paired.begin(), paired.end());
|
||||
std::sort(expected.begin(), expected.end());
|
||||
Require(paired == expected, "many direct samplers: the float image is not paired with sampler elements 0 to " + std::to_string(Elements - 1u) + " and the integer image with 0 to " + std::to_string(Elements - 2u));
|
||||
for (std::uint32_t lane = 0; lane < Lanes; ++lane) Require(outcome.integerSums[lane] == Last * UintTexels[lane % Width], "many direct samplers: lane " + std::to_string(lane) + " summed " + std::to_string(outcome.integerSums[lane]) + " from the integer image through the 16 point copies, expected " + std::to_string(Last * UintTexels[lane % Width]));
|
||||
std::printf("many direct samplers: %u samplers bound as %zu sampler elements; the float image is paired with elements 0 to %u, the last sampler's at element %u, and the integer image with 0 to %u through the point copies\n", Samplers, outcome.samplers.size(), Elements - 1u, Elements - 1u, Elements - 2u);
|
||||
}
|
||||
|
||||
void CheckBorder(AgcDriver::VulkanDevice& device) {
|
||||
const auto image = Image(FloatTexels.data(), Format32Float);
|
||||
const auto border = Run(device, OutsideCode, image, OpaqueWhiteBorder);
|
||||
const auto clamped = Run(device, OutsideCode, image, PointClamp);
|
||||
const auto edge = FloatTexels[Width - 1u];
|
||||
for (std::uint32_t lane = 0; lane < Lanes; ++lane) {
|
||||
const auto expected = FirstSamples(FloatTexels[lane % Width]);
|
||||
Require(std::bit_cast<std::uint32_t>(border.floatSums[lane]) == std::bit_cast<std::uint32_t>(expected), "many direct samplers: lane " + std::to_string(lane) + " summed " + std::to_string(border.floatSums[lane]) + " through samplers 0 to 15, expected " + std::to_string(expected));
|
||||
Require(Component(border, lane, 0) == 1.0f && Component(border, lane, 3) == 1.0f, "many direct samplers: lane " + std::to_string(lane) + " read " + std::to_string(Component(border, lane, 0)) + ", alpha " + std::to_string(Component(border, lane, 3)) + " outside the image through sampler 16, expected its opaque white border");
|
||||
Require(Component(clamped, lane, 0) == edge, "many direct samplers: the clamped control of lane " + std::to_string(lane) + " read " + std::to_string(Component(clamped, lane, 0)) + ", expected the edge texel " + std::to_string(edge));
|
||||
}
|
||||
std::puts("many direct samplers: sampler 16, at sampler element 32, reads its opaque white border outside the image; a clamping S# there reads the edge texel");
|
||||
}
|
||||
|
||||
void CheckDegamma(AgcDriver::VulkanDevice& device) {
|
||||
const auto image = Image(ColorTexels.data(), Format8888UNorm);
|
||||
auto forced = PointClamp;
|
||||
forced[0] |= ForceDegamma;
|
||||
const auto last = Run(device, InsideCode, image, forced);
|
||||
const auto srgb = Run(device, InsideCode, Image(ColorTexels.data(), Format8888Srgb), PointClamp);
|
||||
const auto plain = Run(device, InsideCode, image, PointClamp);
|
||||
for (std::uint32_t lane = 0; lane < Lanes; ++lane) {
|
||||
for (std::uint32_t component = 0; component < 4u; ++component) {
|
||||
Require(std::bit_cast<std::uint32_t>(Component(last, lane, component)) == std::bit_cast<std::uint32_t>(Component(srgb, lane, component)), "many direct samplers: lane " + std::to_string(lane) + " component " + std::to_string(component) + " sampled " + std::to_string(Component(last, lane, component)) + " with FORCE_DEGAMMA on sampler 16, " + std::to_string(Component(srgb, lane, component)) + " from the same texels as 8_8_8_8_SRGB");
|
||||
}
|
||||
Require(std::bit_cast<std::uint32_t>(last.floatSums[lane]) == std::bit_cast<std::uint32_t>(srgb.floatSums[lane]), "many direct samplers: lane " + std::to_string(lane) + " summed " + std::to_string(last.floatSums[lane]) + " with FORCE_DEGAMMA on sampler 16, " + std::to_string(srgb.floatSums[lane]) + " from the same texels as 8_8_8_8_SRGB");
|
||||
Require(Component(last, lane, 1) < Component(plain, lane, 1), "many direct samplers: lane " + std::to_string(lane) + " green " + std::to_string(Component(last, lane, 1)) + " with FORCE_DEGAMMA on sampler 16 is not decoded below " + std::to_string(Component(plain, lane, 1)));
|
||||
}
|
||||
std::puts("many direct samplers: FORCE_DEGAMMA on sampler 16, at sampler element 32, decodes the paired 8_8_8_8_UNORM image as 8_8_8_8_SRGB");
|
||||
}
|
||||
|
||||
void CheckBorderSwizzle(AgcDriver::VulkanDevice& device) {
|
||||
const auto alphaImage = Image(FloatTexels.data(), Format32Float, 1u);
|
||||
const auto control = Refusal(device, InsideCode, alphaImage, PointClamp);
|
||||
Require(control.empty(), "many direct samplers: an alpha-moving BC swizzle without an opaque black border was refused: " + control);
|
||||
std::puts("many direct samplers: an alpha-moving BC swizzle paired with a point-clamp S# on sampler 16 is planned");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
const auto limits = device->graphicsContext().limits;
|
||||
if (limits.maxPerStageDescriptorSamplers < Elements || limits.maxDescriptorSetSamplers < Elements) {
|
||||
std::printf("skipped, the device binds fewer than %u samplers per stage or set\n", Elements);
|
||||
return VulkanTestSkipped;
|
||||
}
|
||||
Fill();
|
||||
CheckPairing(*device);
|
||||
CheckBorder(*device);
|
||||
CheckDegamma(*device);
|
||||
CheckBorderSwizzle(*device);
|
||||
std::puts("many direct samplers tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -13,8 +13,8 @@ struct ShaderInfo {
|
||||
std::uint32_t scratchDwords = 0;
|
||||
std::uint32_t sharedMemoryBytes = 0;
|
||||
static constexpr std::uint32_t MaxImages = 64;
|
||||
static constexpr std::uint32_t MaxSamplers = 32;
|
||||
static constexpr std::uint32_t MaxSampledPairs = 64;
|
||||
static constexpr std::uint32_t MaxSamplers = 64;
|
||||
static constexpr std::uint32_t MaxSampledPairs = 256;
|
||||
|
||||
std::vector<BufferResource> buffers;
|
||||
std::vector<ImageResource> images;
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ struct PreparedDescriptorBinding {
|
||||
std::vector<bool> identityImageSwizzle;
|
||||
std::vector<std::uint32_t> samplerFilterElements;
|
||||
std::vector<bool> inactive;
|
||||
std::vector<std::uint32_t> tableSamplerMasks;
|
||||
std::vector<std::uint64_t> tableSamplerMasks;
|
||||
std::size_t writtenBuffers = 0;
|
||||
std::size_t readOnlyBuffers = 0;
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@ struct ImageTableElement {
|
||||
RdnaImageDimension dimension = RdnaImageDimension::Unknown;
|
||||
bool depthCompare = false;
|
||||
DescriptorBindingKind kind = DescriptorBindingKind::Buffers;
|
||||
std::uint32_t samplers = 0;
|
||||
std::uint64_t samplers = 0;
|
||||
bool atomic = false;
|
||||
bool atomic64 = false;
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
@@ -613,12 +614,12 @@ DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& l
|
||||
plan.runtimeState.assign(layout.runtimeStateDwords, 0u);
|
||||
for (std::uint32_t target = 0; target < exportMappings.size(); ++target) plan.runtimeState.at(RuntimeAbi::StateExportMappings + target / 4u) |= static_cast<std::uint32_t>(exportMappings[target]) << (target % 4u * 8u);
|
||||
}
|
||||
std::vector<std::uint32_t> samplerMasks(info.samplers.size());
|
||||
std::vector<std::uint64_t> samplerMasks(info.samplers.size());
|
||||
for (const auto& binding : plan.bindings) {
|
||||
if (binding.descriptor.role != DescriptorRole::GuestSamplers) continue;
|
||||
if (binding.resources.size() > ShaderInfo::MaxSamplers) fail("prepared sampler mask capacity exceeded");
|
||||
if (binding.resources.size() > std::numeric_limits<std::uint64_t>::digits) fail("prepared sampler mask capacity exceeded");
|
||||
for (std::uint32_t element = 0; element < binding.resources.size(); ++element) {
|
||||
if (binding.inactive.empty() || !binding.inactive[element]) samplerMasks.at(binding.resources[element]) |= 1u << element;
|
||||
if (binding.inactive.empty() || !binding.inactive[element]) samplerMasks.at(binding.resources[element]) |= std::uint64_t{1} << element;
|
||||
}
|
||||
}
|
||||
for (auto& binding : plan.bindings) {
|
||||
@@ -713,9 +714,9 @@ void DescriptorBindingBuilder::Populate(BindingAllocationResult& allocation, con
|
||||
physical.imageWritten.push_back(ImageBindingResourceClass(element.kind) == ImageResourceClass::Storage);
|
||||
physical.imageAtomic.push_back(element.atomic);
|
||||
physical.imageAtomic64.push_back(element.atomic64);
|
||||
std::uint32_t samplers = 0;
|
||||
std::uint64_t samplers = 0;
|
||||
for (std::uint32_t sampler = 0; sampler < entry.tableSamplerMasks.size(); ++sampler) {
|
||||
if ((element.samplers & (1u << sampler)) != 0u) samplers |= entry.tableSamplerMasks[sampler];
|
||||
if (((element.samplers >> sampler) & 1u) != 0u) samplers |= entry.tableSamplerMasks[sampler];
|
||||
}
|
||||
physical.imageSamplers.push_back(samplers);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@
|
||||
|
||||
namespace ShaderRecompiler {
|
||||
|
||||
static_assert(ShaderInfo::MaxSamplers <= std::numeric_limits<std::uint64_t>::digits);
|
||||
|
||||
namespace {
|
||||
|
||||
using ImageTableAbi::PoisonReason;
|
||||
@@ -1888,9 +1890,9 @@ ResolvedImageTables ResourceMaterializer::ResolveImageTables(const ShaderInfo& i
|
||||
};
|
||||
HashScratchIndex elementIndex{threadScratch<HashSlot, ElementIndexTag>()};
|
||||
HashScratchIndex samplerIndex{threadScratch<HashSlot, SamplerIndexTag>()};
|
||||
std::vector<std::uint32_t> directSamplers(info.images.size(), 0u);
|
||||
std::vector<std::uint64_t> directSamplers(info.images.size(), 0u);
|
||||
for (const auto& pair : info.sampledPairs) {
|
||||
if (info.samplers.at(pair.sampler).table == NoTable) directSamplers.at(pair.image) |= 1u << pair.sampler;
|
||||
if (info.samplers.at(pair.sampler).table == NoTable) directSamplers.at(pair.image) |= std::uint64_t{1} << pair.sampler;
|
||||
}
|
||||
const auto reducingSampler = reducingSamplers(info, snapshot);
|
||||
const auto imageRecord = [&](std::uint32_t resource, const DescriptorValue& words) {
|
||||
|
||||
@@ -562,7 +562,7 @@ struct DescriptorBinding {
|
||||
std::vector<bool> samplerCoordinatesIgnored;
|
||||
std::vector<bool> imageUnnormalized;
|
||||
std::vector<bool> imageUnnormalizedBaseLevel;
|
||||
std::vector<std::uint32_t> imageSamplers;
|
||||
std::vector<std::uint64_t> imageSamplers;
|
||||
std::vector<bool> bufferRead;
|
||||
std::vector<std::uint32_t> bufferExtent;
|
||||
};
|
||||
|
||||
@@ -477,7 +477,7 @@ void encodeBinding(Writer& writer, const DescriptorBinding& binding) {
|
||||
writer.Flags(binding.imageUnnormalizedBaseLevel);
|
||||
writer.Flags(binding.samplerIntegerBorder);
|
||||
writer.Flags(binding.samplerCoordinatesIgnored);
|
||||
writer.Values(std::span<const std::uint32_t>(binding.imageSamplers));
|
||||
writer.Values(std::span<const std::uint64_t>(binding.imageSamplers));
|
||||
writer.Flags(binding.bufferRead);
|
||||
writer.Values(std::span<const std::uint32_t>(binding.bufferExtent));
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
namespace ShaderRecompiler::ShaderDiskCache {
|
||||
|
||||
inline constexpr std::uint32_t FormatVersion = 51;
|
||||
inline constexpr std::uint32_t FormatVersion = 52;
|
||||
|
||||
enum class LoadStatus {
|
||||
Loaded,
|
||||
|
||||
@@ -59,7 +59,8 @@
|
||||
- An [image table](../../../core/shader/recompiler/ImageTableAbi.hpp) entry that cannot be bound exactly reads zeros, and an active lane that selects it raises a BDA fault (reason 9) naming the record's address, words and reason; the dispatch's write-back reports the first one.
|
||||
- An [image table](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) snapshot holds at most 65536 keys per table, 65536 distinct images and 256 S# entries per shader; a key past a longer table's snapshot, or an entry over the budget, faults when selected. A selected column (a T# the SRT walker evaluates) holds its one record wherever it lies.
|
||||
- [Image tables](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) read by `s_load` through a 64-bit pointer hold the keys the key's known bits allow, at most 65536; a key past them, or an entry at a wrapping, unaligned or unmapped address, faults when selected.
|
||||
- An [image table](../../../core/shader/recompiler/Optimization/src/ResourceTracker.cpp) whose key is neither a V#-relative scalar read nor a value the SRT walker evaluates (for example a shifted or masked runtime-offset V# read, a per-lane value made uniform by `v_readfirstlane`, or a loop-carried phi) binds every record of the snapshot domain instead of the selected ones. So does a table read at `(key << n) & mask` with such a key: a key domain would select records by the unmasked key, so that key is not narrowed. A key the walker evaluates selects its one record instead, unless the shader may write the memory the key is read from at run time (the same buffer, or any memory when the read or a write goes through a pointer or a GPU-only descriptor); then the column binds the whole domain too. A write to the key's memory through another V# over the same bytes is not detected. A shader whose selected records would pass 64 images or 64 sampled image/sampler pairs binds, for the heaps with the most such reads, the union of their keys in one table instead.
|
||||
- An [image table](../../../core/shader/recompiler/Optimization/src/ResourceTracker.cpp) whose key is neither a V#-relative scalar read nor a value the SRT walker evaluates (for example a shifted or masked runtime-offset V# read, a per-lane value made uniform by `v_readfirstlane`, or a loop-carried phi) binds every record of the snapshot domain instead of the selected ones. So does a table read at `(key << n) & mask` with such a key: a key domain would select records by the unmasked key, so that key is not narrowed. A key the walker evaluates selects its one record instead, unless the shader may write the memory the key is read from at run time (the same buffer, or any memory when the read or a write goes through a pointer or a GPU-only descriptor); then the column binds the whole domain too. A write to the key's memory through another V# over the same bytes is not detected. A shader whose selected records would pass 64 images or 256 sampled image/sampler pairs binds, for the heaps with the most such reads, the union of their keys in one table instead.
|
||||
- A shader plans at most 64 [samplers](../../../core/shader/recompiler/Optimization/src/ResourceTracker.cpp) (`ShaderInfo::MaxSamplers`, the size of the runtime ABI's `ShaderData` sampler metadata, which [`BindingAllocator`](../../../core/shader/recompiler/Optimization/src/BindingAllocator.cpp) holds every shader to), at most 32 of them direct (each takes two elements of the 64-element sampler heap), and at most 256 image/sampler pairs (`ShaderInfo::MaxSampledPairs`); past them it refuses with `sampler resource limit exceeded`, `shader sampler pairs exceed runtime heap capacity` or `sampled image/sampler pair limit exceeded`. The image-to-sampler masks are 64-bit, one bit per sampler heap element or per sampler, so more samplers need a larger `ShaderData` and wider masks. Ratchet & Clank: Rift Apart (PPSA01474) plans up to 63 samplers and 112 pairs in one shader.
|
||||
- [Image table pointer indexing](../../../core/shader/recompiler/Optimization/src/ResourceTracker.cpp) propagates 32-bit value bounds through `ReadFirstLane` and `SelectU32` so waterfall key masks keep their bit domain. The SRT walker validates runtime select conditions whose active lane mask implies the condition through `LogicalAnd` conjunctions, assuming unselected inactive lanes need not be uniform across the full wave. The same assumption lets the two `s_load`s of one T# record match when one offset is added before `v_readfirstlane` and the other after (`ReadFirstLane(Select(mask, (key << 5) + 16, old), mask)` against `ReadFirstLane(key, mask) << 5`): with no active lane the hardware reads lane 0 of the inactive arm, and the table still binds the whole record.
|
||||
|
||||
- [Constant-stride image-table offsets](../../../core/shader/recompiler/Optimization/src/ResourceTracker.cpp) scaled before `v_readfirstlane` are recognized through selects only when the active mask implies their condition. Index expressions, including image-dimension queries, remain in GPU IR; variable scales are not constant-stride columns. Runtime SRT evaluation supports unsigned greater-than-or-equal comparisons, uniform selected components of constructed U32x4 values, and known active-mask conjuncts in nested selects. CPU regressions cover unrelated masks, disjunctions, varying/out-of-range components and variable shifts. Live GPU behavior of these additions remains unmeasured.
|
||||
|
||||
Reference in new issue
Block a user