Compare commits

...

8 Commits

Author SHA1 Message Date
nmzik 099de8dea6 graphics: publish each PM4 WRITE_DATA word exactly once 2026-10-18 16:17:51 +02:00
opcod3 f4bdb52922 agc: restore verified fused shader size name 2026-10-18 16:12:28 +02:00
nmzik 22ef16a044 shader: implement PS5 vector data cache invalidation 2026-10-18 15:57:14 +02:00
nmzik 37df7f3278 Show pipeline cache sizes in MB and remove obsolete texture folder cleanup 2026-10-18 03:57:45 +02:00
nmzik 33208a545a shader: remove artificial sampled image and sampler pair limit 2026-10-18 03:20:38 +02:00
Santiago Paquete 4059179e9d graphics: write proven compute buffer fills to guest memory (#1628)
Avoid readbacks for compute metadata fills

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-10-18 05:42:14 +02:00
nmzik fe3857a171 graphics: materialize CMASK clears for multisampled targets (PPSA13794) 2026-10-18 02:37:56 +02:00
nmzik c3e7ec8285 graphics: fix Jusant regression from d8c4a9af3 2026-10-18 01:03:37 +02:00
28 changed files with 299 additions and 126 deletions
-38
View File
@@ -89,42 +89,6 @@ static void MountSandboxDirs() {
MountOrCreateDir("_TempData/" + title_id, "/temp");
}
static bool ClearDirectoryContents(const std::filesystem::path& dir) {
bool ok = true;
for (const auto& entry: Common::File::GetDirEntries(dir)) {
if (entry.name == "." || entry.name == "..") {
continue;
}
auto path = dir / entry.name;
if (entry.is_file) {
Common::File::RemoveReadonly(path);
ok = Common::File::DeleteFile(path) && ok;
} else {
ok = ClearDirectoryContents(path) && ok;
ok = Common::File::DeleteDirectory(path) && ok;
}
}
return ok;
}
static void ClearDebugTextureFolder() {
const std::string debug_texture_folder = "_Textures";
if (!Common::File::IsDirectoryExisting(debug_texture_folder)) {
Common::File::CreateDirectories(debug_texture_folder);
return;
}
if (!ClearDirectoryContents(debug_texture_folder)) {
LOGF_COLOR(Log::Color::BrightYellow, "TextureDump: failed to completely clear %s\n",
debug_texture_folder.c_str());
}
}
static void Init(const Config::ConfigOptions& cfg, const std::filesystem::path& param_json,
Common::Subsystems& subsystems) {
EXIT_IF(!Common::Thread::IsMainThread());
@@ -198,8 +162,6 @@ void Run(const RunOptions& options) {
Common::Subsystems subsystems(true);
Init(options.config, param_json, subsystems);
ClearDebugTextureFolder();
PrintSystemInfo();
std::string title_id;
Loader::SystemContentParamSfoGetString("TITLE_ID", &title_id);
+5 -6
View File
@@ -343,12 +343,11 @@ void CommandProcessor::WriteData(uint32_t* dst, const uint32_t* src, uint32_t dw
return;
}
if (write_one_address) {
for (uint32_t i = 0; i < dw_num; i++) {
dst[0] = src[i];
}
} else {
memcpy(dst, src, static_cast<size_t>(dw_num) * sizeof(uint32_t));
// Completion labels can be reused immediately; memcpy may write a destination twice.
auto* output = static_cast<volatile uint32_t*>(dst);
for (uint32_t i = 0; i < dw_num; i++) {
*output = src[i];
output += !write_one_address;
}
}
+10 -6
View File
@@ -716,6 +716,13 @@ ImageId TextureCache::ResolveDepthOverlap(const ImageInfo& requested, BindingTyp
"TextureCache: unsupported unequal-sample depth overlap copy (%u -> %u)\n",
cached.backing.samples, replacement.backing.samples);
}
if (info.IsDepth() && info.metadata.kind == ImageMetadataKind::Htile) {
// Copied depth supersedes pending clears from the previous native image.
const auto metadata = m_surface_metas.find(info.metadata.range.address);
if (metadata != m_surface_metas.end()) {
metadata->second.clear_mask.reset();
}
}
FreeImage(cached_id);
return replacement_id;
}
@@ -1033,7 +1040,7 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc,
std::scoped_lock lock {m_lock};
auto& image = m_slot_images[id];
image.info.metadata = desc.info.metadata;
// Native color metadata must not retain a reused HTile/CMask/FMask clear flag.
// Native color metadata must not retain a reused HTile clear flag.
m_surface_metas.erase(range.address);
if (range.size == 0 || desc.info.resources.levels != 1) {
return;
@@ -1411,11 +1418,8 @@ vk::ImageView TextureCache::FindDepthTarget(ImageId id, const ImageDesc& desc) {
image.info.metadata = desc.info.metadata;
if (desc.info.HasMetadata()) {
const auto [metadata, inserted] = m_surface_metas.try_emplace(desc.info.metadata.range.address);
if (inserted) {
metadata->second.type = MetaDataInfo::Type::HTile;
if (image.info.htile_initial_clear) {
metadata->second.clear_mask.set();
}
if (inserted && image.info.htile_initial_clear) {
metadata->second.clear_mask.set();
}
}
RefreshImage(id);
-3
View File
@@ -81,9 +81,6 @@ public:
private:
struct MetaDataInfo {
enum class Type : uint8_t { CMask, FMask, HTile };
Type type;
// PS5 DB_DEPTH_VIEW encodes 13-bit base and last array-slice indices.
std::bitset<8192> clear_mask;
};
@@ -220,8 +220,7 @@ void RenderExecutor::ResolveRenderColorTarget(CommandBuffer& buffer, RenderColor
}
const bool has_dcc = rt.info.dcc_compression_enable && rt.dcc_addr.addr != 0;
const bool has_cmask = !rt.info.dcc_compression_enable && rt.info.cmask_fast_clear_enable &&
rt.cmask.addr != 0 && samples == 1 &&
!rt.info.fmask_compression_enable &&
rt.cmask.addr != 0 &&
!rt.attrib3.write_vrs_rate_hint_to_cmask;
if (has_dcc || has_cmask) {
TileSizeAlign metadata_size {};
@@ -233,7 +232,7 @@ void RenderExecutor::ResolveRenderColorTarget(CommandBuffer& buffer, RenderColor
} else {
(void)TileGetCmaskSize(width, height, layers, levels, metadata_size);
}
// DCC owns the clear when both planes are enabled; single-sample CMASK stays expanded.
// DCC owns the clear when both planes are enabled.
desc.info.metadata.kind = has_dcc ? ImageMetadataKind::Dcc : ImageMetadataKind::Cmask;
desc.info.metadata.range = {has_dcc ? rt.dcc_addr.addr : rt.cmask.addr, metadata_size.size};
desc.info.metadata.clear_word =
-10
View File
@@ -294,16 +294,6 @@ static void RtCheck(const HW::RenderTarget& rt) {
EXIT_NOT_IMPLEMENTED(rt.dcc.overwrite_combiner_disable != false);
// EXIT_NOT_IMPLEMENTED(rt.dcc.data_write_on_dcc_clear_to_reg != false);
EXIT_NOT_IMPLEMENTED(rt.dcc.dcc_clear_key_enable != false);
if (rt.cmask.addr != 0x0000000000000000 || rt.fmask.addr != 0x0000000000000000 ||
rt.dcc_addr.addr != 0x0000000000000000) {
static bool logged = false;
if (!logged) {
LOGF("RenderTarget: temporary: ignoring PS5 metadata addresses cmask=0x%016" PRIx64
" fmask=0x%016" PRIx64 " dcc=0x%016" PRIx64 "\n",
rt.cmask.addr, rt.fmask.addr, rt.dcc_addr.addr);
logged = true;
}
}
}
}
@@ -580,8 +580,8 @@ void PipelineCache::InitializeDriverCache() {
return;
}
if (!initial_data.empty()) {
PipelineCacheLog("Vulkan pipeline cache: loaded {} bytes from {}", initial_data.size(),
path);
PipelineCacheLog("Vulkan pipeline cache: loaded {:.2f} MB from {}",
static_cast<double>(initial_data.size()) / 1'000'000.0, path);
} else {
PipelineCacheLog("Vulkan pipeline cache: initialized empty");
}
@@ -621,8 +621,8 @@ void PipelineCache::Save() {
}
if (result != vk::Result::eSuccess || size == 0 ||
size > std::numeric_limits<uint32_t>::max()) {
PipelineCacheLog("Vulkan pipeline cache: save failed ({}, {} bytes)",
vk::to_string(result), size);
PipelineCacheLog("Vulkan pipeline cache: save failed ({}, {:.2f} MB)",
vk::to_string(result), static_cast<double>(size) / 1'000'000.0);
return;
}
payload.resize(size);
@@ -654,7 +654,8 @@ void PipelineCache::Save() {
Common::PathToString(m_driver_cache_path));
return;
}
PipelineCacheLog("Vulkan pipeline cache: saved {} bytes to {}", payload.size(),
PipelineCacheLog("Vulkan pipeline cache: saved {:.2f} MB to {}",
static_cast<double>(payload.size()) / 1'000'000.0,
Common::PathToString(m_driver_cache_path));
m_driver_cache_dirty = false;
}
+3 -3
View File
@@ -203,9 +203,9 @@ private:
void ResetBindings();
void UpdateComputeMetaClears(const ShaderComputeInputInfo& input,
const CommandBuffer& buffer);
[[nodiscard]] bool TryConsumeComputeImageClear(const ShaderComputeInputInfo& input,
CommandBuffer& command, uint32_t group_x,
uint32_t group_y, uint32_t group_z, uint32_t mode);
[[nodiscard]] bool TryConsumeComputeFill(const ShaderComputeInputInfo& input,
CommandBuffer& command, uint32_t group_x,
uint32_t group_y, uint32_t group_z, uint32_t mode);
RenderContext& m_context;
GraphicsBindings m_graphics_bindings;
@@ -128,9 +128,9 @@ bool ResolveComputeBufferFill(const ShaderComputeInputInfo& input, uint32_t grou
return true;
}
bool RenderExecutor::TryConsumeComputeImageClear(const ShaderComputeInputInfo& input,
CommandBuffer& command, uint32_t group_x,
uint32_t group_y, uint32_t group_z, uint32_t mode) {
bool RenderExecutor::TryConsumeComputeFill(const ShaderComputeInputInfo& input,
CommandBuffer& command, uint32_t group_x,
uint32_t group_y, uint32_t group_z, uint32_t mode) {
const auto& program = *input.stage.program;
const auto& resources = *input.stage.resources;
const auto& fill = resources.uniform_fill;
@@ -186,14 +186,26 @@ bool RenderExecutor::TryConsumeComputeImageClear(const ShaderComputeInputInfo& i
size)) {
return false;
}
if (!cache.ClearImageFromBuffer(command, descriptor.Base48(), size, packed_clear)) {
return false;
const bool image_clear =
cache.ClearImageFromBuffer(command, descriptor.Base48(), size, packed_clear);
if (!image_clear) {
// CPU fills invalidate overlapping cached images. Preserve the native dispatch for
// partial or ambiguous image backings, including pooled metadata aliases.
if ((descriptor.Base48() & 3u) != 0 ||
!m_context.IsMapped(descriptor.Base48(), size) ||
cache.IsRegionRegistered(descriptor.Base48(), size)) {
return false;
}
// Metadata need not have an image backing yet. Keep proven fills in guest memory
// when CPU-owned, so color-clear discovery can read them without a GPU download.
m_context.GetBufferCache().FillBuffer(descriptor.Base48(), size, packed_clear, false);
}
static std::atomic<uint32_t> logged_clears {0};
if (logged_clears.fetch_add(1, std::memory_order_relaxed) < 32) {
LOGF("GraphicsRenderDispatchDirect: compute image clear shader=0x%016" PRIx64
LOGF("GraphicsRenderDispatchDirect: compute %s shader=0x%016" PRIx64
" addr=0x%016" PRIx64 " size=0x%016" PRIx64 " value=0x%08" PRIx32 "\n",
input.stage.program->shader_hash, descriptor.Base48(), size, packed_clear);
image_clear ? "image clear" : "buffer fill", input.stage.program->shader_hash,
descriptor.Base48(), size, packed_clear);
}
return true;
}
@@ -308,8 +320,8 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
if (resources.specialization_reads.empty()) {
// Cached metadata addresses can be reused as ordinary buffers; preserve their guest writes.
UpdateComputeMetaClears(input_info, buffer);
if (TryConsumeComputeImageClear(input_info, buffer, thread_group_x, thread_group_y,
thread_group_z, mode)) {
if (TryConsumeComputeFill(input_info, buffer, thread_group_x, thread_group_y,
thread_group_z, mode)) {
ResetBindings();
return;
}
@@ -655,6 +655,14 @@ void EmitStoreCompletion(EmitterState& state) {
spv::MemorySemanticsImageMemoryMask));
}
void EmitDataCacheInvalidate(EmitterState& state) {
// RDNA2 GL0/GL1 invalidation makes vector memory visible without a workgroup rendezvous.
state.builder.AddFunction(
spv::OpMemoryBarrier, ConstantU32(state, spv::ScopeDevice),
ConstantU32(state, spv::MemorySemanticsAcquireMask | spv::MemorySemanticsUniformMemoryMask |
spv::MemorySemanticsImageMemoryMask));
}
void EmitBarrier(EmitterState& state) {
const auto tessellation = state.program.stage == ShaderType::TessellationControl;
if (!tessellation && ShaderWorkgroupInput(state.program.stage, state.input_info) == nullptr) {
@@ -218,6 +218,7 @@ inline constexpr auto EmitTtraceData = EmitVoid;
inline constexpr auto EmitInstPrefetch = EmitVoid;
void EmitBarrier(EmitterState& state);
void EmitStoreCompletion(EmitterState& state);
void EmitDataCacheInvalidate(EmitterState& state);
void EmitMeshAllocate(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitMeshDrawParameter(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitGetTessellationAttribute(ValueEmitContext& ctx, const IR::Inst& inst);
@@ -307,6 +307,7 @@ void EmitBlock(ValueEmitContext& ctx, const IR::Block* block, EmitInstruction&&
ctx.state.lane_half = half;
if (half == 0 || (inst.GetOpcode() != IR::ValueOpcode::Barrier &&
inst.GetOpcode() != IR::ValueOpcode::StoreCompletion &&
inst.GetOpcode() != IR::ValueOpcode::DataCacheInvalidate &&
inst.GetOpcode() != IR::ValueOpcode::MeshAllocate)) {
emit_instruction(lane, inst);
}
@@ -70,6 +70,8 @@ constexpr MemoryOpcodeInfo MUBUF_OPCODE_LIST[] = {
{0x52u, Opcode::BUFFER_ATOMIC_ADD_X2, 2, 32},
{0x59u, Opcode::BUFFER_ATOMIC_AND_X2, 2, 32},
{0x5au, Opcode::BUFFER_ATOMIC_OR_X2, 2, 32},
{0x71u, Opcode::BUFFER_GL0_INV, 0, 0},
{0x72u, Opcode::BUFFER_GL1_INV, 0, 0},
{0x80u, Opcode::BUFFER_LOAD_FORMAT_D16_X, 1, 16, false, false, true},
{0x83u, Opcode::BUFFER_LOAD_FORMAT_D16_XYZW, 2, 16, false, false, true},
{0x84u, Opcode::BUFFER_STORE_FORMAT_D16_X, 1, 16, false, false, true},
@@ -312,6 +314,9 @@ void DecodeMubuf(uint32_t pc, std::span<const uint32_t> code, uint32_t word_inde
if (inst.opcode == Opcode::UNSUPPORTED) {
SetUnsupported(inst, Family::MUBUF, opcode, "MUBUF opcode is not implemented");
}
if (inst.opcode == Opcode::BUFFER_GL0_INV || inst.opcode == Opcode::BUFFER_GL1_INV) {
return;
}
DecodeVectorGpr(vdata, inst.dst);
if (inst.opcode == Opcode::BUFFER_LOAD_FORMAT_D16_XYZW ||
@@ -566,6 +566,9 @@ std::string InstructionToString(const Instruction& inst) {
OperandToString(inst.src0).c_str()));
case Opcode::S_BARRIER:
return WithUnsupportedReason(inst, fmt::format("0x{:08x}: s_barrier", inst.pc));
case Opcode::BUFFER_GL0_INV:
case Opcode::BUFFER_GL1_INV:
return fmt::format("0x{:08x}: {}", inst.pc, magic_enum::enum_name(inst.opcode));
case Opcode::V_NOP:
return WithUnsupportedReason(inst, fmt::format("0x{:08x}: v_nop", inst.pc));
case Opcode::S_ENDPGM:
@@ -599,6 +599,8 @@ enum class Opcode {
BUFFER_ATOMIC_XOR,
BUFFER_ATOMIC_FMIN,
BUFFER_ATOMIC_FMAX,
BUFFER_GL0_INV,
BUFFER_GL1_INV,
FLAT_LOAD_UBYTE,
FLAT_LOAD_SBYTE,
FLAT_LOAD_USHORT,
@@ -940,6 +940,10 @@ void Translator::DS_PERMUTE(const Decoder::Instruction& inst, bool backward) {
void Translator::EmitMemory(const Decoder::Instruction& inst) {
switch (inst.opcode) {
case Decoder::Opcode::BUFFER_GL0_INV:
case Decoder::Opcode::BUFFER_GL1_INV:
ir.Emit(IR::ValueOpcode::DataCacheInvalidate);
return;
case Decoder::Opcode::IMAGE_BVH_INTERSECT_RAY: {
const auto result = ir.Emit(IR::ValueOpcode::BvhIntersect,
{ConstructU32x4(inst.src1, 4), MakeImageAddress(inst, inst.src0), ir.GetExec()},
@@ -495,7 +495,6 @@ struct ShaderInfo {
static constexpr uint32_t MaxBufferRoots = 64;
static constexpr uint32_t MaxImages = 64;
static constexpr uint32_t MaxSamplers = 32;
static constexpr uint32_t MaxSampledPairs = 64;
std::vector<BufferResource> buffers;
std::vector<ImageResource> images;
@@ -94,6 +94,7 @@ bool HasSideEffects(ValueOpcode opcode) {
case ValueOpcode::SetTessellationAttribute:
case ValueOpcode::MeshAllocate:
case ValueOpcode::StoreCompletion:
case ValueOpcode::DataCacheInvalidate:
case ValueOpcode::Barrier: return true;
default: return false;
}
@@ -350,6 +350,7 @@ VALUE_OPCODE(SetAttribute, Void, U32x4, U1)
VALUE_OPCODE(ControlNop, Void)
VALUE_OPCODE(StoreCompletion, Void)
VALUE_OPCODE(DataCacheInvalidate, Void)
VALUE_OPCODE(Barrier, Void)
VALUE_OPCODE(Sendmsg, Void)
VALUE_OPCODE(TtraceData, Void)
@@ -3823,9 +3823,6 @@ private:
return;
}
}
if (m_info.sampled_pairs.size() >= ShaderInfo::MaxSampledPairs) {
Fail(pc, "sampled image/sampler pair limit exceeded");
}
m_info.sampled_pairs.push_back({image, sampler, pc});
}
@@ -509,6 +509,7 @@ void Visit(Program& program, ShaderStageInputInfo input_info, InputUsage& inputs
case ValueOpcode::ReadConst: info.uses_flattened_srt = true; break;
case ValueOpcode::BitwiseXor32: info.has_bitwise_xor = true; break;
case ValueOpcode::StoreCompletion: info.subgroup_barrier = true; break;
case ValueOpcode::DataCacheInvalidate: info.coherent_buffers = true; break;
case ValueOpcode::BvhIntersect: info.bvh = true; break;
case ValueOpcode::ConditionRef:
info.subgroup_vote |=
@@ -481,6 +481,12 @@
<widget class="QGroupBox" name="rendering_compatibility_group">
<property name="title"><string>Rendering compatibility</string></property>
<layout class="QVBoxLayout" name="renderingCompatibilityLayout">
<item>
<widget class="QLabel" name="label_rendering_compatibility">
<property name="text"><string>These settings may help with rendering bugs at a slight performance cost.</string></property>
<property name="wordWrap"><bool>true</bool></property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBox_sync_raw_image_buffers">
<property name="toolTip"><string>Can fix missing textures and incorrect transparency (Resident Evil 2).</string></property>
+1 -1
View File
@@ -670,7 +670,7 @@ int KYTY_SYSV_ABI AgcCreateShader(Shader** dst, void* header, const volatile voi
return OK;
}
int KYTY_SYSV_ABI AgcUnknownGetFusedShaderSize(SizeAlign* dst, const Shader* front,
int KYTY_SYSV_ABI AgcGetFusedShaderSize(SizeAlign* dst, const Shader* front,
const Shader* back) {
PRINT_NAME();
+1 -1
View File
@@ -40,7 +40,7 @@ int KYTY_SYSV_ABI AgcInit(uint32_t* state, uint32_t ver);
void* KYTY_SYSV_ABI AgcGetRegisterDefaults2(uint32_t ver);
void* KYTY_SYSV_ABI AgcGetRegisterDefaults2Internal(uint32_t ver);
int KYTY_SYSV_ABI AgcCreateShader(Shader** dst, void* header, const volatile void* code);
int KYTY_SYSV_ABI AgcUnknownGetFusedShaderSize(SizeAlign* dst, const Shader* front,
int KYTY_SYSV_ABI AgcGetFusedShaderSize(SizeAlign* dst, const Shader* front,
const Shader* back);
int KYTY_SYSV_ABI AgcFuseShaderHalves_0200(Shader* fused_result, const Shader* front,
const Shader* back, void* scratch_mem);
+1 -1
View File
@@ -18,7 +18,7 @@ LIB_DEFINE(InitAgcDriver_1) {
LIB_FUNC("2JtWUUiYBXs", Gen5::AgcGetRegisterDefaults2);
LIB_FUNC("wRbq6ZjNop4", Gen5::AgcGetRegisterDefaults2Internal);
LIB_FUNC("f3dg2CSgRKY", Gen5::AgcCreateShader);
LIB_FUNC("dolOmWH+huQ", Gen5::AgcUnknownGetFusedShaderSize);
LIB_FUNC("dolOmWH+huQ", Gen5::AgcGetFusedShaderSize);
LIB_FUNC("fd5Bp5tGTgo", Gen5::AgcFuseShaderHalves_0200);
LIB_FUNC("nApJjpKNBl4", Gen5::AgcFuseShaderHalves);
LIB_FUNC("vcmNN+AAXnY", Gen5::AgcSetCxRegIndirectPatchSetAddress);
+33
View File
@@ -4929,6 +4929,38 @@ void TestImagesSamplersAndAliases() {
"buffer/image descriptor alias was not linked");
}
void TestIndependentImageSamplerPairs() {
Fixture fixture(ShaderType::Pixel);
const auto address = fixture.ImageAddress();
MemoryInfo memory;
memory.kind = ResourceKind::Image;
memory.image_dimension = Decoder::ImageDimension::Dim2D;
for (uint32_t use = 0; use < 144; ++use) {
const auto pair = use % 72;
const auto pc = use * 4;
const auto image = fixture.Image(
{Value(pair / 8), Value(0u), Value(0u), Value(0u), Value(0u),
Value(0u), Value(0u), Value(0u)}, pc);
const auto sampler = fixture.Sampler(
{Value(pair % 8), Value(0u), Value(0u), Value(0u)}, pc);
fixture.Emit(ValueOpcode::ImageSampleRaw,
{image, sampler, address, Value(true)},
fixture.AddMemory(memory, pc));
}
fixture.PlanAndTrack();
Check(fixture.program.info.images.size() == 9 &&
fixture.program.info.samplers.size() == 8 &&
fixture.program.info.sampled_pairs.size() == 72,
"distinct image/sampler combinations were limited or duplicated");
for (uint32_t index = 0; index < 72; ++index) {
const auto &pair = fixture.program.info.sampled_pairs[index];
Check(pair.image == index / 8 && pair.sampler == index % 8 &&
pair.first_use_pc == index * 4,
"sampled pair lost its independent resources or first use");
}
}
void TestSampleAdjustSamplerScratch() {
Fixture fixture(ShaderType::Pixel);
const auto active = fixture.Emit(
@@ -8255,6 +8287,7 @@ int main() {
Run("scalar/vector alias", TestScalarAndVectorBufferAlias);
Run("runtime unsigned min", TestRuntimeUnsignedMinDescriptor);
Run("images and samplers", TestImagesSamplersAndAliases);
Run("independent image/sampler pairs", TestIndependentImageSamplerPairs);
Run("SampleAdjust sampler scratch", TestSampleAdjustSamplerScratch);
Run("FMASK load specialization", TestFmaskLoadSpecialization);
Run("gather LOD sampler validation", TestGatherLodSamplerValidation);
+129 -36
View File
@@ -390,7 +390,6 @@ struct TextureCacheTestAccess {
static void RegisterHtileMeta(TextureCache &cache, uint64_t address) {
std::lock_guard lock(cache.m_lock);
auto &metadata = cache.m_surface_metas[address];
metadata.type = TextureCache::MetaDataInfo::Type::HTile;
metadata.clear_mask.reset();
}
@@ -408,10 +407,10 @@ struct RenderExecutorTestAccess {
executor.DrawAuto(0, command, args);
}
static bool TryConsumeComputeImageClear(RenderExecutor &executor,
static bool TryConsumeComputeFill(RenderExecutor &executor,
const ShaderComputeInputInfo &input, CommandBuffer &command,
uint32_t x, uint32_t y, uint32_t z, uint32_t mode) {
return executor.TryConsumeComputeImageClear(input, command, x, y, z, mode);
return executor.TryConsumeComputeFill(input, command, x, y, z, mode);
}
static void UpdateComputeMetaClears(RenderExecutor &executor,
@@ -10487,22 +10486,34 @@ public:
vk::Extent3D extent = {1, 1, 1}, uint32_t layer = 0,
uint32_t mip = 0) {
auto &scheduler = context.GetCommandScheduler();
auto &image = context.GetTextureCache().GetImage(id);
const auto bytes = image.info.bytes_per_block * extent.width *
auto *image = &context.GetTextureCache().GetImage(id);
std::unique_ptr<Libs::Graphics::Image> resolved;
if (image->backing.samples > 1) {
auto info = image->info;
info.data = {};
info.metadata = {};
info.samples = 1;
info.resources = {1, 1};
resolved = std::make_unique<Libs::Graphics::Image>(m_runtime_context, scheduler, info);
resolved->Resolve(*image, {mip, 1, layer, 1}, {});
image = resolved.get();
layer = mip = 0;
}
const auto bytes = image->info.bytes_per_block * extent.width *
extent.height * extent.depth;
auto probe = CreateHostBuffer(name, bytes,
vk::BufferUsageFlagBits::eTransferDst, {});
scheduler.Current().EndRendering();
image.Transit(vk::ImageLayout::eTransferSrcOptimal,
image->Transit(vk::ImageLayout::eTransferSrcOptimal,
vk::AccessFlagBits2::eTransferRead, {},
scheduler.Current().Handle());
vk::BufferImageCopy copy{};
copy.imageSubresource = {image.info.IsDepth() ? vk::ImageAspectFlagBits::eDepth
copy.imageSubresource = {image->info.IsDepth() ? vk::ImageAspectFlagBits::eDepth
: vk::ImageAspectFlagBits::eColor, mip, layer, 1};
copy.imageOffset = offset;
copy.imageExtent = extent;
scheduler.Current().Handle().copyImageToBuffer(
image.backing.image, vk::ImageLayout::eTransferSrcOptimal,
image->backing.image, vk::ImageLayout::eTransferSrcOptimal,
probe.buffer, 1, &copy);
vk::BufferMemoryBarrier barrier{};
barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
@@ -11509,7 +11520,7 @@ public:
clear.float32[0] == 1.0f && clear.float32[1] == 1.0f,
"a repeated DWORD fill was decoded as a two-register clear");
constexpr uintptr_t base = 0x0000000204100000ull;
constexpr uint64_t allocation_size = 0x400000;
constexpr uint64_t allocation_size = 0x1000000;
constexpr uint64_t allocation_alignment = 0x10000;
struct FillCase {
uint32_t fill;
@@ -11521,6 +11532,8 @@ public:
Prospero::ChannelType type = Prospero::ChannelType::kFloat;
uint32_t clear_word = 0;
bool eliminate_fast_clear = false;
uint32_t samples_log2 = 0;
bool goat = false;
};
constexpr std::array cases{
FillCase{0x40404040u, {0, 0x3c000000u}},
@@ -11528,7 +11541,7 @@ public:
FillCase{0x40404040u, {0, 0x3c000000u}, true},
FillCase{.fill = 0, .texel = {0x804020ffu, 0}, .cmask = true,
.layout = Prospero::ChannelLayout::k8_8_8_8,
.type = Prospero::ChannelType::kUNorm},
.type = Prospero::ChannelType::kUNorm, .goat = true},
FillCase{.fill = 0, .texel = {0, 0}, .cmask = true,
.eliminate_fast_clear = true},
FillCase{.fill = 0, .texel = {0, 0x3c000000u}, .cmask = true,
@@ -11552,6 +11565,15 @@ public:
FillCase{.fill = 0x20202020u, .texel = {0x0000ffffu},
.layout = Prospero::ChannelLayout::k16_16,
.type = Prospero::ChannelType::kUNorm, .clear_word = 0x0000ffffu},
FillCase{.fill = 0, .texel = {0x804020ffu, 0}, .cmask = true,
.layout = Prospero::ChannelLayout::k8_8_8_8,
.type = Prospero::ChannelType::kUNorm, .samples_log2 = 2},
FillCase{.fill = 0, .texel = {0x804020ffu, 0}, .cmask = true,
.layout = Prospero::ChannelLayout::k8_8_8_8,
.type = Prospero::ChannelType::kUNorm, .eliminate_fast_clear = true, .samples_log2 = 2},
FillCase{.fill = 0, .texel = {0, 0x3c000000u}, .cmask = true,
.eliminate_fast_clear = true, .samples_log2 = 2},
FillCase{.fill = 0x40404040u, .texel = {0, 0x3c000000u}, .eliminate_fast_clear = true},
};
EnsureRuntimeContext();
// Astro's generic metadata fill, through S_ENDPGM; trailing debug data is omitted.
@@ -11584,11 +11606,18 @@ public:
}
return words;
}();
// Goat Simulator 3's immediate-zero fill (b0a34a6ed90ebace).
static constexpr std::array<uint32_t, 6> goat_fill{
0xd7460000u, 0x04010c04u, 0x7e020280u,
0xe0102000u, 0x80000100u, 0xbf810000u};
for (const auto *shader : {&native_fill, &raw_fill}) {
ShaderMapUserData(reinterpret_cast<uint64_t>(shader->data()),
{.type = Prospero::ShaderBinaryType::kCs,
.code_size_bytes = sizeof(native_fill)});
}
ShaderMapUserData(reinterpret_cast<uint64_t>(goat_fill.data()),
{.type = Prospero::ShaderBinaryType::kCs,
.code_size_bytes = sizeof(goat_fill)});
int64_t direct_offset = -1;
Require(name, "direct allocation",
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
@@ -11610,12 +11639,13 @@ public:
const auto bytes_per_pixel = TextureGetRenderTargetFormat(
layout, type, Prospero::ChannelOrder::kStandard).bytes_per_element;
const uint32_t probe_width = bytes_per_pixel == 2 ? 2 : 1;
const uint32_t selected_layer = fill_case.cmask || fill_case.eliminate_fast_clear ? 1 : 0;
TileSizeAlign dcc_size{};
(void)TileGetDccSize(512, 256, 1, bytes_per_pixel, 1,
Prospero::TileMode::kRenderTarget, dcc_size, 0);
const uint64_t dcc_address = base + (fill_case.eliminate_fast_clear ? 0x200000 :
(void)TileGetDccSize(512, 256, selected_layer + 1, bytes_per_pixel, 1,
Prospero::TileMode::kRenderTarget, dcc_size, fill_case.samples_log2);
const uint64_t dcc_address = base + (fill_case.samples_log2 != 0 ? 0x800000 :
fill_case.eliminate_fast_clear ? 0x200000 :
fill_case.reuse_mips ? 0x160000 : 0x100000);
const uint32_t selected_layer = fill_case.cmask ? 1 : 0;
const uint64_t metadata_size = fill_case.cmask ? 0x2000 : dcc_size.size;
const std::vector<u32> expected(
fill_case.texel.begin(), fill_case.texel.begin() + std::max(bytes_per_pixel / 4u, 1u));
@@ -11629,13 +11659,16 @@ public:
auto &scheduler = context.GetCommandScheduler();
registers.SetColorBase(0, {.addr = base});
registers.SetColorInfo(
0, {.cmask_fast_clear_enable = !fill_case.reuse_unorm,
0, {.fmask_compression_enable = fill_case.samples_log2 != 0,
.cmask_fast_clear_enable = !fill_case.reuse_unorm,
.dcc_compression_enable = !fill_case.cmask,
.format = layout,
.channel_type = type,
.channel_order = Prospero::ChannelOrder::kStandard});
registers.SetColorView(0, {.base_array_slice_index = selected_layer,
.last_array_slice_index = selected_layer});
registers.SetColorAttrib(0, {.num_samples = fill_case.samples_log2,
.num_fragments = fill_case.samples_log2});
registers.SetColorAttrib2(0, {.height = 255, .width = 511});
registers.SetColorAttrib3(0,
{.tile_mode = Prospero::TileMode::kRenderTarget,
@@ -11643,6 +11676,7 @@ public:
.metadata_pipe_aligned = true});
registers.SetColorDccAddr(0, {.addr = dcc_address});
registers.SetColorCmask(0, {.addr = dcc_address});
registers.SetColorFmask(0, {.addr = fill_case.samples_log2 != 0 ? base + 0xc00000 : 0});
registers.SetColorClearWord0(0, {.word0 = fill_case.cmask ? expected[0] : fill_case.clear_word});
registers.SetColorClearWord1(0, {.word1 = fill_case.cmask && expected.size() > 1u ? expected[1] : 0});
registers.SetRenderTargetMask(0x0f);
@@ -11696,27 +11730,39 @@ public:
registers.SetColorAttrib2(0, single_mip);
}
const auto fill_metadata = [&](uint32_t count, bool raw = false) {
const auto *shader = &native_fill;
if (raw) {
shader = &raw_fill;
}
shaders.SetCsShader({.data_addr = reinterpret_cast<uint64_t>(shader->data()),
const bool goat = fill_case.goat && !raw;
const auto *shader = goat ? goat_fill.data()
: raw ? raw_fill.data() : native_fill.data();
shaders.SetCsShader({.data_addr = reinterpret_cast<uint64_t>(shader),
.num_thread_x = 64, .num_thread_y = 1, .num_thread_z = 1,
.wave_size = 64, .user_sgpr = 10, .tgid_x_en = true});
.wave_size = 64, .user_sgpr = goat ? uint8_t{4} : uint8_t{10},
.tgid_x_en = true});
const std::array<uint32_t, 10> user_data{
static_cast<uint32_t>(dcc_address),
static_cast<uint32_t>(dcc_address >> 32u) | (4u << 16u),
count, 0x00014004u, fill_case.fill, 0, 0x200, 0, count, 1};
for (uint32_t i = 0; i < user_data.size(); i++) {
for (uint32_t i = 0; i < (goat ? 4u : user_data.size()); i++) {
shaders.SetCsUserSgpr(i, user_data[i], HW::UserSgprType::Unknown);
}
executor.DispatchDirect(0, scheduler.Current(), (count + 63) / 64, 1, 1, 0x41u, false);
};
const auto metadata_words = static_cast<uint32_t>(metadata_size / 4);
if (fill_case.goat) {
WriteMetadata(context, dcc_address, metadata_size, 0xa5a5a5a5u);
}
fill_metadata(metadata_words);
Require(name, "metadata before descriptor",
!texture_cache.IsMeta(dcc_address),
"the native fill guessed an unbound metadata allocation's type");
if (fill_case.goat) {
std::vector<uint32_t> filled_metadata(metadata_words);
Require(name, "Goat metadata fill stays CPU-owned",
!resources.GetBufferCache().IsRegionGpuModified(dcc_address, metadata_size) &&
LibKernel::Memory::TryReadBacking(dcc_address, filled_metadata.data(), metadata_size) &&
std::ranges::all_of(filled_metadata,
[&](uint32_t word) { return word == fill_case.fill; }),
"Goat's proven metadata fill required a GPU readback or lost its constant value");
}
const auto check_expanded_metadata = [&] {
context.GetBufferCache().ReadMemory(dcc_address, metadata_size, false);
@@ -11724,9 +11770,10 @@ public:
Require(name, "expanded metadata readback",
LibKernel::Memory::TryReadBacking(dcc_address, bytes.data(), bytes.size()),
"native color metadata could not be read back");
const auto selected = bytes.begin() + (fill_case.cmask ? 0x1000 : 0);
const auto selected = bytes.begin() + metadata_size * selected_layer / (selected_layer + 1);
Require(name, "selected metadata slice expansion",
std::all_of(bytes.begin(), selected, [](uint8_t v) { return v == 0; }) &&
std::all_of(bytes.begin(), selected,
[&](uint8_t v) { return v == static_cast<uint8_t>(fill_case.fill); }) &&
std::all_of(selected, bytes.end(), [](uint8_t v) { return v == 0xff; }),
"materialization retained a clear key or changed an unselected slice");
};
@@ -11752,7 +11799,8 @@ public:
color.image_id == preexisting_id &&
texture_cache.FindTexture(color.image_id, sampled) != nullptr,
"FCE failed to retain the sampled allocation");
const std::vector<u32> stale = layout == Prospero::ChannelLayout::k11_11_10
const std::vector<u32> stale = layout == Prospero::ChannelLayout::k8_8_8_8
? std::vector<u32>{0xffff00ffu} : layout == Prospero::ChannelLayout::k11_11_10
? std::vector<u32>{0x780003c0u} : layout == Prospero::ChannelLayout::k32_32
? std::vector<u32>{0x3f800000u, 0u} : std::vector<u32>{0x00003c00u, 0x3c003c00u};
Require(name, "FCE before graphics shader or target binding",
@@ -11769,7 +11817,7 @@ public:
Require(name, "repeated indexed FCE preserves expanded color",
ReadCachedTexel(name, context, color.image_id, {}, {1, 1, 1}, 1) == stale,
"FCE reapplied the consumed clear after rendering");
WriteMetadata(context, dcc_address, metadata_size, 0);
WriteMetadata(context, dcc_address, metadata_size, fill_case.fill);
RenderExecutorTestAccess::DrawIndex(executor, scheduler.Current(),
{.index_count = 3, .instance_count = 1});
(void)texture_cache.FindTexture(color.image_id, sampled);
@@ -11871,6 +11919,10 @@ public:
retains_painted_texel(),
"a partial same-value write reused stale clear bytes after rendering");
fill_metadata(metadata_words, true);
fill_metadata(metadata_words);
Require(name, "already GPU-owned metadata stays ordered",
resources.GetBufferCache().HasGpuDirtyBytes(dcc_address, metadata_size),
"a fill discarded pending native writes to its metadata pages");
bind();
Require(name, "same-value raw native re-clear",
read_texel() == expected,
@@ -11883,6 +11935,11 @@ public:
retains_painted_texel(),
"a nonuniform metadata allocation cleared the whole target");
fill_metadata(metadata_words);
if (fill_case.goat) {
Require(name, "repeated Goat fill stays CPU-owned",
!resources.GetBufferCache().IsRegionGpuModified(dcc_address, metadata_size),
"re-clearing expanded metadata required another GPU readback");
}
bind();
Require(name, "complete fill after partial fill",
read_texel() == expected,
@@ -11948,7 +12005,7 @@ public:
ImageDesc previous_depth{};
previous_depth.type = BindingType::DepthTarget;
previous_depth.info.data = {base + 0x180000, 0x10000};
previous_depth.info.data = {base + 0xa00000, 0x10000};
previous_depth.info.pixel_format = vk::Format::eD32Sfloat;
previous_depth.info.guest_format = Prospero::BufferFormat::k32Float;
previous_depth.info.type = Prospero::ImageType::kColor2D;
@@ -14942,7 +14999,7 @@ public:
texture_cache.ClearMeta(high_slice_target.htile_data_base_addr),
"single high slice was rejected or changed the backing layer layout");
auto high_slice_readback = CreateHostBuffer(
name, 4 * sizeof(uint32_t), vk::BufferUsageFlagBits::eTransferDst, {});
name, 7 * sizeof(uint32_t), vk::BufferUsageFlagBits::eTransferDst, {});
auto high_slice_metadata = CreateHostBuffer(
name, 6 * sizeof(uint32_t), vk::BufferUsageFlagBits::eTransferDst, {});
const auto htile_slice_size = high_slice.desc.info.metadata.range.size / 87;
@@ -15013,20 +15070,53 @@ public:
high_slice_storage.view_info.format = vk::Format::eR32Sfloat;
high_slice_storage.view_info.aspect = vk::ImageAspectFlagBits::eColor;
high_slice_storage.view_info.usage = vk::ImageUsageFlagBits::eStorage;
const auto high_slice_storage_id = texture_cache.FindImage(high_slice_storage);
Require(name, "HTile depth replacement before raw buffer read",
texture_cache.FindImage(high_slice_storage) != high_slice.image_id,
high_slice_storage_id != high_slice.image_id,
"R32 storage binding retained the native depth backing");
resources.GetBufferCache().FillBuffer(
htile_slice_address, htile_slice_size, 0, false);
copy_htile(2);
Config::Load({});
for (uint32_t pass = 0; pass < 3; ++pass) {
if (pass < 2) {
Require(name, "HTile clear across copied depth recreation",
texture_cache.ClearMeta(high_slice_target.htile_data_base_addr),
"copied depth lost HTile registration");
}
RenderDepthInfo copied_depth{};
RenderExecutorTestAccess::ResolveRenderDepthTarget(
executor, scheduler.Current(), copied_depth);
if (pass == 2) {
vk::ClearValue prior_depth{};
prior_depth.depthStencil.depth = 0.625f;
TextureCacheTestAccess::ClearImage(texture_cache, scheduler.Current(),
copied_depth.image_id, {vk::ImageAspectFlagBits::eDepth, 0, 1, 86, 1},
prior_depth);
}
const auto rendering = RenderExecutorTestAccess::AcquireRenderTargets(
executor, scheduler.Current(), &no_color, 0, copied_depth);
Require(name, "copied depth consumes only subsequent HTile clears",
copied_depth.image_id != high_slice_storage_id &&
rendering.depth_stencil_attachment.depth_clear == (pass == 1),
"copied depth was cleared again or ignored a subsequent metadata clear");
scheduler.BeginRendering(rendering);
scheduler.EndRendering();
RenderExecutorTestAccess::ResetBindings(executor);
const vk::BufferImageCopy copy{(4 + pass) * sizeof(uint32_t), 0, 0,
{vk::ImageAspectFlagBits::eDepth, 0, 86, 1}, {}, {1, 1, 1}};
texture_cache.GetImage(copied_depth.image_id).Download(
std::span{&copy, 1}, high_slice_readback.buffer, 0, high_slice_readback.size);
}
scheduler.Current().Handle().pipelineBarrier2(bounds_dependency);
scheduler.Finish();
Require(name, "high HTile slice contents and neighbor",
ReadBuffer(name, high_slice_readback, 4) == std::vector<uint32_t>{
ReadBuffer(name, high_slice_readback, 7) == std::vector<uint32_t>{
std::bit_cast<uint32_t>(0.625f), std::bit_cast<uint32_t>(0.375f),
std::bit_cast<uint32_t>(0.625f), std::bit_cast<uint32_t>(0.625f)},
"layer86 clear missed, repeated, or changed adjacent layer85");
std::bit_cast<uint32_t>(0.625f), std::bit_cast<uint32_t>(0.625f),
std::bit_cast<uint32_t>(0.625f), std::bit_cast<uint32_t>(0.375f),
std::bit_cast<uint32_t>(0.625f)},
"layer86 clear missed, repeated, or discarded copied depth");
Require(name, "GPU-visible opt-in ZMask support",
ReadBuffer(name, high_slice_metadata, 6) == std::vector<uint32_t>{0, 15, 0, 15, 0, 15},
"raw HTile binding ignored the ZMask setting or lost it after depth replacement");
@@ -16133,9 +16223,9 @@ public:
{vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil, 0, 1, 0, 1},
stencil_initial);
Require(name, "stencil fill coverage",
!RenderExecutorTestAccess::TryConsumeComputeImageClear(executor, stencil_compute,
!RenderExecutorTestAccess::TryConsumeComputeFill(executor, stencil_compute,
scheduler.Current(), 0, 1, 1, 0x41u) &&
!RenderExecutorTestAccess::TryConsumeComputeImageClear(executor, stencil_compute,
!RenderExecutorTestAccess::TryConsumeComputeFill(executor, stencil_compute,
scheduler.Current(), 2, 1, 1, 0x41u),
"incomplete or excessive stencil dispatch was consumed");
auto &stencil_source = stencil_snapshot.buffers[0];
@@ -16143,12 +16233,12 @@ public:
stencil_source.dwords[0] = static_cast<uint32_t>(stencil_address);
stencil_source.dwords[1] = static_cast<uint32_t>(stencil_address >> 32) | (4u << 16);
Require(name, "stencil fill source alias",
!RenderExecutorTestAccess::TryConsumeComputeImageClear(executor, stencil_compute,
!RenderExecutorTestAccess::TryConsumeComputeFill(executor, stencil_compute,
scheduler.Current(), 1, 1, 1, 0x41u),
"an aliased scalar load was consumed as a uniform image clear");
stencil_source = clean_source;
Require(name, "stencil IMAGE_STORE clear",
RenderExecutorTestAccess::TryConsumeComputeImageClear(executor, stencil_compute,
RenderExecutorTestAccess::TryConsumeComputeFill(executor, stencil_compute,
scheduler.Current(), 1, 1, 1, 0x41u),
"captured clear attempted an unsupported depth/stencil storage binding");
auto stencil_readback = CreateHostBuffer(name, 8,
@@ -48998,6 +49088,9 @@ int main(int argc, char **argv) {
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--htile-clear-only") == 0) {
CheckNativeMsaaState();
CheckPs5DepthRegisterDecoding();
CheckDepthHtileStencilCompatibility();
VulkanHarness vulkan;
vulkan.CheckUnifiedTextureCacheFlow();
vulkan.CheckRenderExecutorStencilBindingDiscovery();
+54
View File
@@ -1967,6 +1967,59 @@ void TestNggVertexEntryState() {
}
}
void TestNativeDataCacheInvalidation() {
using namespace ShaderRecompiler;
// Captured f5edf70dbb1f6057:0x194 and 0x1a4, each followed by S_WAITCNT.
const std::array cache_ops = {0xe1c40000u, 0u, 0xbf8c0000u,
0xe1c80000u, 0u, 0xbf8c0000u, EncodeSopp(0x01)};
Decoder::Program decoded;
Decoder::DecodeProgram(cache_ops, decoded);
for (const uint32_t index : {0u, 2u}) {
const auto &inst = decoded.instructions[index];
Check(inst.opcode == (index == 0 ? Decoder::Opcode::BUFFER_GL0_INV
: Decoder::Opcode::BUFFER_GL1_INV) &&
inst.word_count == 2 && inst.src_count == 0 && inst.data_dwords == 0 &&
inst.dst.kind == Decoder::OperandKind::Unknown,
"RDNA2 cache invalidation acquired a buffer operand or lost its encoding");
}
auto options = MakeCompileOptions(ShaderType::Compute);
options.dump_ir = true;
ShaderComputeInputInfo compute{};
compute.threads_num[1] = compute.threads_num[2] = 1;
options.input_info.compute = &compute;
for (const uint32_t wave_size : {32u, 64u}) {
compute.threads_num[0] = compute.wave_size = options.wave_size = wave_size;
const auto minimal = RecompileForTest(cache_ops, options);
CheckSpirvBinaryValidates(minimal.spirv);
Check(minimal.program.info.buffers.empty(),
"cache invalidation introduced a phantom buffer binding");
const auto source = DisassembleSpirvBinary(minimal.spirv);
const auto first = source.find("OpMemoryBarrier %uint_1 %uint_2114");
const auto second = source.find("OpMemoryBarrier %uint_1 %uint_2114", first + 1);
Check(first != std::string::npos && second != std::string::npos &&
source.find("OpMemoryBarrier", second + 1) == std::string::npos &&
source.find("OpControlBarrier") == std::string::npos,
"cache invalidation must fence device memory once per wave without convergence");
const std::array reload = {
EncodeMubuf0(0x1c, 0, false), EncodeMubuf1(0, 0, 0),
0xe1c40000u, 0u, 0xbf8c0000u,
EncodeMubuf0(0x0c, 0, false), EncodeMubuf1(1, 0, 0),
EncodeMubuf0(0x1c, 4, false), EncodeMubuf1(1, 0, 0), EncodeSopp(0x01),
};
const auto result = RecompileForTest(reload, options);
CheckSpirvBinaryValidates(result.spirv);
const auto store = result.ir_dump.find("StoreBufferU32");
const auto fence = result.ir_dump.find("DataCacheInvalidate");
const auto load = result.ir_dump.find("LoadBufferU32");
Check(store < fence && fence < load && load != std::string::npos &&
result.program.info.coherent_buffers &&
DisassembleSpirvBinary(result.spirv).find(" Coherent") != std::string::npos,
"plain buffer reload lost its cache fence order or coherent visibility");
}
}
void TestNativeStoreCompletion() {
using namespace ShaderRecompiler;
const std::array register_wait = {EncodeSopk(0x17, 4, static_cast<int16_t>(0x9234)), EncodeSopp(0x01)};
@@ -16523,6 +16576,7 @@ int main() {
TestNewShaderDecoderArchitecture();
TestImageAddressOperands();
TestSopkCompareImmediateExtension();
TestNativeDataCacheInvalidation();
TestNativeStoreCompletion();
TestNewShaderRecompilerCapturedVopcSdwaCmpxClass();
TestVopcCmpxClassF16Decoder();