mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-25 12:26:51 +01:00
shader: preserve scalar buffer offset alignment and carry
This commit is contained in:
@@ -1187,10 +1187,11 @@ void EmitReadConstBuffer(ValueEmitContext& ctx, const IR::Inst& inst) {
|
|||||||
if (mem.planning_only) return;
|
if (mem.planning_only) return;
|
||||||
auto& state = ctx.state;
|
auto& state = ctx.state;
|
||||||
mem.kind = IR::ResourceKind::ScalarBuffer;
|
mem.kind = IR::ResourceKind::ScalarBuffer;
|
||||||
const auto address = Binary(state, spv::OpIAdd, TypeU32(state), ctx.Arg(inst, 1),
|
auto index = Binary(state, spv::OpShiftRightLogical, TypeU32(state), ctx.Arg(inst, 1),
|
||||||
ConstantU32(state, mem.offset));
|
ConstantU32(state, 2));
|
||||||
const auto index =
|
if (mem.offset >= 4u) {
|
||||||
Binary(state, spv::OpShiftRightLogical, TypeU32(state), address, ConstantU32(state, 2));
|
index = Binary(state, spv::OpIAdd, TypeU32(state), index, ConstantU32(state, mem.offset >> 2u));
|
||||||
|
}
|
||||||
const auto access = PrepareMemoryResourceAccess(state, mem);
|
const auto access = PrepareMemoryResourceAccess(state, mem);
|
||||||
const auto element = EmitMemoryElementIndex(state, access, index);
|
const auto element = EmitMemoryElementIndex(state, access, index);
|
||||||
const auto condition = EmitMemoryElementInBounds(state, access, element);
|
const auto condition = EmitMemoryElementInBounds(state, access, element);
|
||||||
|
|||||||
@@ -225,9 +225,9 @@ void MarkCleanFlatSlots(const ResourcePlan& program, const DescriptorSource* sou
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ReadScalarTable(uint64_t base, uint64_t size, uint32_t dynamic_offset,
|
bool ReadScalarTable(uint64_t base, uint64_t size, uint64_t dynamic_offset,
|
||||||
const SrtRuntime& runtime, std::span<uint32_t> words) {
|
const SrtRuntime& runtime, std::span<uint32_t> words) {
|
||||||
const auto offset = static_cast<uint64_t>(dynamic_offset) & ~uint64_t {3};
|
const auto offset = dynamic_offset & ~uint64_t {3};
|
||||||
const auto count = std::min<uint64_t>(words.size(), offset < size ? (size - offset) / 4u : 0u);
|
const auto count = std::min<uint64_t>(words.size(), offset < size ? (size - offset) / 4u : 0u);
|
||||||
std::ranges::fill(words.subspan(count), 0u);
|
std::ranges::fill(words.subspan(count), 0u);
|
||||||
if (count == 0u) {
|
if (count == 0u) {
|
||||||
@@ -306,26 +306,24 @@ bool MaterializeIndirectImage(const ResourcePlan& program,
|
|||||||
material.Stride() != indirect.selector_stride) {
|
material.Stride() != indirect.selector_stride) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Enumerate every wrapped scalar-buffer offset that can pass the descriptor bounds.
|
// The first aligned offset includes the immediate added after shader U32 arithmetic.
|
||||||
const auto step = std::gcd<uint64_t>(indirect.selector_stride, uint64_t {1} << 32u);
|
const auto step = std::max<uint64_t>(4u,
|
||||||
const auto residue = static_cast<uint64_t>(indirect.selector_offset) % step;
|
std::gcd<uint64_t>(indirect.selector_stride, uint64_t {1} << 32u));
|
||||||
const auto limit = std::min<uint64_t>(UINT32_MAX, material.GetSize() + 3u);
|
const uint64_t first = indirect.selector_offset;
|
||||||
const auto probe_count = residue <= limit ? (limit - residue) / step + 1u : 0u;
|
const auto size = material.GetSize();
|
||||||
|
const auto limit = std::min(first + (uint64_t {1} << 32u) - step, size >= 4u ? size - 4u : 0u);
|
||||||
|
const auto probe_count = size >= 4u && first <= limit ? (limit - first) / step + 1u : 0u;
|
||||||
if (probe_count > MaxIndirectImageProbes) {
|
if (probe_count > MaxIndirectImageProbes) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
keys.reserve(static_cast<size_t>(probe_count) + 1u);
|
keys.reserve(static_cast<size_t>(probe_count) + 1u);
|
||||||
keys.push_back(0u);
|
keys.push_back(0u);
|
||||||
for (uint64_t offset = residue; offset <= limit && probe_count != 0u; offset += step) {
|
for (uint64_t probe = 0, offset = first; probe < probe_count; ++probe, offset += step) {
|
||||||
uint32_t key = 0;
|
uint32_t key = 0;
|
||||||
if (!ReadScalarTable(material.Base48(), material.GetSize(),
|
if (!ReadScalarTable(material.Base48(), size, offset, runtime, {&key, 1})) {
|
||||||
static_cast<uint32_t>(offset), runtime, {&key, 1})) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
keys.push_back(key);
|
keys.push_back(key);
|
||||||
if (limit - offset < step) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
std::ranges::sort(keys);
|
std::ranges::sort(keys);
|
||||||
keys.erase(std::unique(keys.begin(), keys.end()), keys.end());
|
keys.erase(std::unique(keys.begin(), keys.end()), keys.end());
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
|
#include <numeric>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <span>
|
#include <span>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -1204,7 +1205,7 @@ private:
|
|||||||
const auto* memory = material_read != nullptr
|
const auto* memory = material_read != nullptr
|
||||||
? ScalarReadMemory(*material_read, material_memory_index) : nullptr;
|
? ScalarReadMemory(*material_read, material_memory_index) : nullptr;
|
||||||
if (table_offset != 0u || memory == nullptr || memory->kind != ResourceKind::ScalarBuffer ||
|
if (table_offset != 0u || memory == nullptr || memory->kind != ResourceKind::ScalarBuffer ||
|
||||||
memory->offset != 0u || !MemoryIndexBelongsTo(material_memory_index, *material_read)) {
|
memory->offset > INT32_MAX || !MemoryIndexBelongsTo(material_memory_index, *material_read)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Value selector;
|
Value selector;
|
||||||
@@ -1212,6 +1213,9 @@ private:
|
|||||||
indirect.selector_offset)) {
|
indirect.selector_offset)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
const auto step = std::gcd<uint64_t>(indirect.selector_stride, uint64_t {1} << 32u);
|
||||||
|
indirect.selector_offset =
|
||||||
|
(static_cast<uint32_t>(indirect.selector_offset % step) & ~3u) + (memory->offset & ~3u);
|
||||||
const auto* shift = plan.reads[0]->Arg(1).Resolve().TryInstruction();
|
const auto* shift = plan.reads[0]->Arg(1).Resolve().TryInstruction();
|
||||||
const std::array<const Inst*, 1> material_users {shift};
|
const std::array<const Inst*, 1> material_users {shift};
|
||||||
if (!UsesOnly(*material_read, material_users) || !UsesOnly(*shift, plan.reads)) {
|
if (!UsesOnly(*material_read, material_users) || !UsesOnly(*shift, plan.reads)) {
|
||||||
|
|||||||
@@ -456,16 +456,15 @@ bool SrtWalker::EvaluateRawRead(const Inst& inst, uint64_t& result) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const auto byte_offset =
|
const auto byte_offset =
|
||||||
static_cast<uint64_t>(immediate) + static_cast<uint32_t>(offset);
|
(static_cast<uint64_t>(immediate) & ~uint64_t {3}) + (static_cast<uint32_t>(offset) & ~3u);
|
||||||
const auto aligned = byte_offset & ~uint64_t {3};
|
|
||||||
const auto stride = (static_cast<uint32_t>(high) >> 16u) & 0x3fffu;
|
const auto stride = (static_cast<uint32_t>(high) >> 16u) & 0x3fffu;
|
||||||
const auto size = stride == 0u
|
const auto size = stride == 0u
|
||||||
? static_cast<uint64_t>(static_cast<uint32_t>(records))
|
? static_cast<uint64_t>(static_cast<uint32_t>(records))
|
||||||
: static_cast<uint64_t>(stride) * static_cast<uint32_t>(records);
|
: static_cast<uint64_t>(stride) * static_cast<uint32_t>(records);
|
||||||
if (aligned > size || size - aligned < sizeof(uint32_t)) {
|
if (byte_offset > size || size - byte_offset < sizeof(uint32_t)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
address = ((base & ~uint64_t {3}) + byte_offset) & ~uint64_t {3};
|
address = (base & ~uint64_t {3}) + byte_offset;
|
||||||
} else {
|
} else {
|
||||||
const auto relative = (immediate & ~int64_t {3}) +
|
const auto relative = (immediate & ~int64_t {3}) +
|
||||||
static_cast<int64_t>(static_cast<uint32_t>(offset) & ~3u);
|
static_cast<int64_t>(static_cast<uint32_t>(offset) & ~3u);
|
||||||
|
|||||||
@@ -206,8 +206,9 @@ bool ReadLinearTestMemory(void *userdata, uint64_t address, std::span<uint32_t>
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Fixture>
|
std::unique_ptr<Fixture>
|
||||||
MakeIndirectImageFixture(bool malformed, uint32_t material_immediate = 0,
|
MakeIndirectImageFixture(bool malformed, uint32_t material_immediate = 4,
|
||||||
bool memory_backed_material = false) {
|
bool memory_backed_material = false, uint32_t member_offset = 0,
|
||||||
|
uint32_t material_stride = 224) {
|
||||||
auto fixture = std::make_unique<Fixture>();
|
auto fixture = std::make_unique<Fixture>();
|
||||||
std::array<Value, 4> material_words;
|
std::array<Value, 4> material_words;
|
||||||
std::array<Value, 4> heap_words;
|
std::array<Value, 4> heap_words;
|
||||||
@@ -249,9 +250,9 @@ MakeIndirectImageFixture(bool malformed, uint32_t material_immediate = 0,
|
|||||||
const auto selector = fixture->Emit(ValueOpcode::ReadFirstLane,
|
const auto selector = fixture->Emit(ValueOpcode::ReadFirstLane,
|
||||||
{invocation, Value(true)});
|
{invocation, Value(true)});
|
||||||
const auto record =
|
const auto record =
|
||||||
fixture->Emit(ValueOpcode::IMul32, {selector, Value(224u)});
|
fixture->Emit(ValueOpcode::IMul32, {selector, Value(material_stride)});
|
||||||
const auto member = fixture->Emit(ValueOpcode::IAdd32, {record, Value(4u)});
|
const auto member = member_offset == 0 ? record :
|
||||||
fixture->Emit(ValueOpcode::ReferenceU32, {record});
|
fixture->Emit(ValueOpcode::IAdd32, {record, Value(member_offset)});
|
||||||
fixture->Emit(ValueOpcode::ReferenceU32, {member});
|
fixture->Emit(ValueOpcode::ReferenceU32, {member});
|
||||||
MemoryInfo material_scalar;
|
MemoryInfo material_scalar;
|
||||||
material_scalar.kind = ResourceKind::ScalarBuffer;
|
material_scalar.kind = ResourceKind::ScalarBuffer;
|
||||||
@@ -497,7 +498,7 @@ void TestInvariantIndirectImageMaterialization() {
|
|||||||
rebound_specialization != collapsed_specialization,
|
rebound_specialization != collapsed_specialization,
|
||||||
"larger indirect candidate topology reused the old specialization");
|
"larger indirect candidate topology reused the old specialization");
|
||||||
|
|
||||||
auto memory_backed = MakeIndirectImageFixture(false, 0u, true);
|
auto memory_backed = MakeIndirectImageFixture(false, 4u, true);
|
||||||
memory_backed->PlanAndTrack();
|
memory_backed->PlanAndTrack();
|
||||||
auto memory_backed_plan = ExtractResourcePlan(memory_backed->program);
|
auto memory_backed_plan = ExtractResourcePlan(memory_backed->program);
|
||||||
EliminateDeadCode(memory_backed->program.blocks);
|
EliminateDeadCode(memory_backed->program.blocks);
|
||||||
@@ -550,13 +551,34 @@ void TestInvariantIndirectImageMaterialization() {
|
|||||||
malformed->program.descriptor_sources.empty(),
|
malformed->program.descriptor_sources.empty(),
|
||||||
"malformed indirect image pattern was partially accepted");
|
"malformed indirect image pattern was partially accepted");
|
||||||
|
|
||||||
auto wrapped_immediate = MakeIndirectImageFixture(false, 4u);
|
auto negative_immediate = MakeIndirectImageFixture(false, 0xfffffffcu);
|
||||||
|
CheckFatal([&] { negative_immediate->PlanAndTrack(); },
|
||||||
CheckFatal([&] { wrapped_immediate->PlanAndTrack(); },
|
|
||||||
"not a valid runtime value",
|
"not a valid runtime value",
|
||||||
"wrapped scalar immediate entered the invariant image proof");
|
"negative scalar immediate entered the indirect image proof");
|
||||||
Check(!wrapped_immediate->program.resource_tracking_complete,
|
|
||||||
"wrapped scalar immediate entered the invariant image proof");
|
for (const auto [immediate, member, first, stride] :
|
||||||
|
{std::array{0u, 4u, 4u, 224u}, std::array{4u, 4u, 8u, 224u},
|
||||||
|
std::array{36u, 0u, 36u, 224u}, std::array{1u, 3u, 0u, 224u},
|
||||||
|
std::array{1u, 3u, 0u, 1u}, std::array{1u, 3u, 0u, 2u}}) {
|
||||||
|
auto split_offset = MakeIndirectImageFixture(false, immediate, false, member, stride);
|
||||||
|
split_offset->PlanAndTrack();
|
||||||
|
const auto split_plan = ExtractResourcePlan(split_offset->program);
|
||||||
|
LinearTestMemory split_memory;
|
||||||
|
std::copy(image_descriptor.begin(), image_descriptor.end(),
|
||||||
|
split_memory.words.begin() + 0x1000u / 4u);
|
||||||
|
split_memory.fail_address = first == 36u ? 0x1004u : UINT64_MAX;
|
||||||
|
split_memory.watched_address = split_memory.base + first;
|
||||||
|
user_data[1] = stride << 16u;
|
||||||
|
user_data[2] = stride < 4u ? 8u / stride : 1u;
|
||||||
|
SrtRuntime split_runtime{.user_data = user_data,
|
||||||
|
.userdata = &split_memory,
|
||||||
|
.read_specialization_memory = ReadLinearTestMemory};
|
||||||
|
Check(MaterializeResources(split_plan, split_runtime, snapshot, specialization) &&
|
||||||
|
snapshot.images.size() == 1 &&
|
||||||
|
snapshot.images[0].dwords == image_descriptor &&
|
||||||
|
split_memory.watched_reads == 1u,
|
||||||
|
"indirect scalar offsets did not align and bound their components independently");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestGuardedDirectImageTable() {
|
void TestGuardedDirectImageTable() {
|
||||||
|
|||||||
@@ -488,35 +488,34 @@ void TestSharedIntegerRuntimeDependencies() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TestConstantBufferBounds() {
|
void TestConstantBufferBounds() {
|
||||||
Fixture fixture;
|
struct Case {
|
||||||
const auto memory = fixture.AddMemory(ResourceKind::ScalarBuffer);
|
uint32_t offset;
|
||||||
const auto buffer =
|
uint32_t immediate;
|
||||||
fixture.Emit(ValueOpcode::GetBufferResource,
|
bool valid;
|
||||||
{Value(0x3000u), Value(0u), Value(16u), Value(0u)});
|
uint32_t expected;
|
||||||
const auto read = fixture.EmitMemory(ValueOpcode::ReadConstBuffer,
|
};
|
||||||
{buffer, Value(12u)}, memory);
|
for (const auto &test : {Case{12u, 0u, true, 0xa5a5a5a5u},
|
||||||
LoadBuffer(fixture, {read, Value(0u), Value(16u), Value(0u)});
|
Case{3u, 1u, true, 0x12345678u},
|
||||||
fixture.Plan();
|
Case{0xfffffffcu, 4u, false, 0u},
|
||||||
|
Case{16u, 0u, false, 0u}}) {
|
||||||
|
Fixture fixture;
|
||||||
|
const auto memory = fixture.AddMemory(ResourceKind::ScalarBuffer, test.immediate);
|
||||||
|
const auto buffer =
|
||||||
|
fixture.Emit(ValueOpcode::GetBufferResource,
|
||||||
|
{Value(0x3000u), Value(0u), Value(16u), Value(0u)});
|
||||||
|
const auto read = fixture.EmitMemory(ValueOpcode::ReadConstBuffer,
|
||||||
|
{buffer, Value(test.offset)}, memory);
|
||||||
|
LoadBuffer(fixture, {read, Value(0u), Value(16u), Value(0u)});
|
||||||
|
fixture.Plan();
|
||||||
|
|
||||||
TestMemory memory_image{{{0x300cu, 0xa5a5a5a5u}}};
|
TestMemory memory_image{{{0x3000u, 0x12345678u}, {0x300cu, 0xa5a5a5a5u}}};
|
||||||
SrtRuntime runtime{.read_memory = ReadMemory, .userdata = &memory_image};
|
SrtRuntime runtime{.read_memory = ReadMemory, .userdata = &memory_image};
|
||||||
std::vector<uint32_t> flat;
|
std::vector<uint32_t> flat;
|
||||||
Check(SrtWalker(fixture.program, runtime).RefreshFlatBuffer(flat) &&
|
Check(SrtWalker(fixture.program, runtime).RefreshFlatBuffer(flat) == test.valid,
|
||||||
flat == std::vector<uint32_t>{0xa5a5a5a5u},
|
"constant-buffer walk misaligned or wrapped its offset components");
|
||||||
"constant-buffer SRT walk failed");
|
Check(test.valid ? flat == std::vector<uint32_t>{test.expected} : memory_image.reads == 0,
|
||||||
|
"constant-buffer walk read the wrong word or accessed an out-of-bounds address");
|
||||||
Fixture overflow;
|
}
|
||||||
const auto overflow_memory = overflow.AddMemory(ResourceKind::ScalarBuffer);
|
|
||||||
const auto overflow_buffer =
|
|
||||||
overflow.Emit(ValueOpcode::GetBufferResource,
|
|
||||||
{Value(0x3000u), Value(0u), Value(16u), Value(0u)});
|
|
||||||
const auto overflow_read =
|
|
||||||
overflow.EmitMemory(ValueOpcode::ReadConstBuffer,
|
|
||||||
{overflow_buffer, Value(16u)}, overflow_memory);
|
|
||||||
LoadBuffer(overflow, {overflow_read, Value(0u), Value(16u), Value(0u)});
|
|
||||||
overflow.Plan();
|
|
||||||
Check(!SrtWalker(overflow.program, runtime).RefreshFlatBuffer(flat),
|
|
||||||
"out-of-bounds constant-buffer walk was accepted");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestReadLaneElimination() {
|
void TestReadLaneElimination() {
|
||||||
|
|||||||
@@ -23772,6 +23772,35 @@ TestCase ScalarMemoryLoadVariants() {
|
|||||||
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TestCase ScalarBufferOffsetAlignmentAndCarry() {
|
||||||
|
using O = ShaderOpcode;
|
||||||
|
std::vector<u32> code;
|
||||||
|
AppendSMovLiteral(&code, 12, 0xfffffffcu);
|
||||||
|
code.push_back(EncodeSop2(0x00, 12, 12, 4));
|
||||||
|
code.push_back(EncodeSmem0(0x08, 20));
|
||||||
|
code.push_back(EncodeSmem1(4, 12));
|
||||||
|
AppendSMovLiteral(&code, 12, 3u);
|
||||||
|
code.push_back(EncodeSop2(0x00, 12, 12, 4));
|
||||||
|
code.push_back(EncodeSmem0(0x08, 21));
|
||||||
|
code.push_back(EncodeSmem1(1, 12));
|
||||||
|
AppendStoreSgpr(&code, 20, 0);
|
||||||
|
AppendStoreSgpr(&code, 21, 1);
|
||||||
|
AppendEnd(&code);
|
||||||
|
|
||||||
|
TestCase test{"ScalarBufferOffsetAlignmentAndCarry", code,
|
||||||
|
{0x11111111u, 0x22222222u}, {0u, 0x11111111u},
|
||||||
|
{O::S_MOV_B32, O::S_ADD_U32, O::S_BUFFER_LOAD_DWORD,
|
||||||
|
O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
||||||
|
test.forbidden_spirv = {"flattened_srt"};
|
||||||
|
test.compute_info.threads_num[0] = 1;
|
||||||
|
test.compute_info.threads_num[1] = 1;
|
||||||
|
test.compute_info.threads_num[2] = 1;
|
||||||
|
test.compute_info.group_id[0] = true;
|
||||||
|
test.compute_info.workgroup_register = 4;
|
||||||
|
test.has_compute_info = true;
|
||||||
|
return test;
|
||||||
|
}
|
||||||
|
|
||||||
TestCase ScalarLoadSignedImmediateOffsetAddsSoffset() {
|
TestCase ScalarLoadSignedImmediateOffsetAddsSoffset() {
|
||||||
using O = ShaderOpcode;
|
using O = ShaderOpcode;
|
||||||
|
|
||||||
@@ -29888,6 +29917,7 @@ std::vector<TestCase> MakeCases() {
|
|||||||
AddCase(BranchVccnzUsesCarryProducedWaveMask);
|
AddCase(BranchVccnzUsesCarryProducedWaveMask);
|
||||||
AddCase(ScalarMemRealtimeCapturedPlaceholder);
|
AddCase(ScalarMemRealtimeCapturedPlaceholder);
|
||||||
AddCase(ScalarMemoryLoadVariants);
|
AddCase(ScalarMemoryLoadVariants);
|
||||||
|
AddCase(ScalarBufferOffsetAlignmentAndCarry);
|
||||||
AddCase(ScalarLoadSignedImmediateOffsetAddsSoffset);
|
AddCase(ScalarLoadSignedImmediateOffsetAddsSoffset);
|
||||||
AddCase(ScalarLoadAlignsComponentsAndMasksAddress);
|
AddCase(ScalarLoadAlignsComponentsAndMasksAddress);
|
||||||
AddCase(ScalarLoadAlignsDynamicBase);
|
AddCase(ScalarLoadAlignsDynamicBase);
|
||||||
|
|||||||
@@ -12822,10 +12822,10 @@ void TestSrtWalkerRealSBufferTranslation() {
|
|||||||
const auto walked = ShaderRecompiler::IR::SrtWalker(ir, runtime).RefreshFlatBuffer(flat);
|
const auto walked = ShaderRecompiler::IR::SrtWalker(ir, runtime).RefreshFlatBuffer(flat);
|
||||||
Check(walked, "SRT walk failed");
|
Check(walked, "SRT walk failed");
|
||||||
Check(flat.size() == 4 &&
|
Check(flat.size() == 4 &&
|
||||||
std::equal(flat.begin(), flat.end(), table.begin() + 1),
|
std::equal(flat.begin(), flat.end(), table.begin()),
|
||||||
"real S_BUFFER_LOAD walk used the wrong final alignment");
|
"real S_BUFFER_LOAD walk did not align offset components independently");
|
||||||
|
|
||||||
user_data[10] = 4 * sizeof(uint32_t);
|
user_data[10] = 3 * sizeof(uint32_t);
|
||||||
const auto bounds_walked = ShaderRecompiler::IR::SrtWalker(ir, runtime).RefreshFlatBuffer(flat);
|
const auto bounds_walked = ShaderRecompiler::IR::SrtWalker(ir, runtime).RefreshFlatBuffer(flat);
|
||||||
Check(!bounds_walked, "real S_BUFFER_LOAD walk ignored descriptor bounds");
|
Check(!bounds_walked, "real S_BUFFER_LOAD walk ignored descriptor bounds");
|
||||||
CheckFlattenedReadSlots(
|
CheckFlattenedReadSlots(
|
||||||
|
|||||||
Reference in New Issue
Block a user