Compare commits

...

16 Commits

Author SHA1 Message Date
crevant 8b095caefe perf(files): move directory-entry names instead of copying them (#758) 2026-09-22 12:26:06 +04:00
nmzik 8dc9b9d532 Persist save allocations for search and mount capacity queries 2026-09-22 11:38:50 +04:00
nmzik e40438f95e Decode ATRAC9 streams after complete RIFF headers
Co-authored-by: Techx3 <56268269+Techx3@users.noreply.github.com>
Co-authored-by: Ali Almohaya <17254073+Almo7aya@users.noreply.github.com>
2026-09-22 11:38:50 +04:00
nmzik a6a56c8a44 Remove obsolete partial indirect draw warnings 2026-09-22 11:38:50 +04:00
nmzik 4b074cf9e9 Avoid heap allocation when compacting materialized shader images 2026-09-22 11:38:50 +04:00
nmzik 7c9e18e73b Ignore inactive metadata address bits in sampled depth descriptors
Co-authored-by: Ali Almohaya <17254073+Almo7aya@users.noreply.github.com>
2026-09-22 11:38:50 +04:00
nmzik d2df2cad74 Resolve draw-uniform image descriptors through scalar buffer chains 2026-09-22 11:38:50 +04:00
nmzik b2a80a3a96 Resolve GPU-selected raw buffer descriptors through shared RDNA2 addressing
Co-authored-by: Bipin <87369440+bipinkrish@users.noreply.github.com>
2026-09-22 11:38:50 +04:00
nmzik cfb8f7b246 Decode unsigned byte sources for SDWA half-float conversion 2026-09-22 11:11:36 +04:00
nmzik d792dfa885 Reject AvPlayer pause and seek before playback 2026-09-22 11:11:36 +04:00
nmzik 12ff571609 Preserve AvPlayer sync mode before source creation 2026-09-22 11:11:36 +04:00
nmzik d3c182f385 Centralize attachment sample validation before acquisition 2026-09-22 11:11:36 +04:00
nmzik bf4dcdce52 Accept resolved FMASK metadata with native MSAA 2026-09-22 11:11:36 +04:00
nmzik 476204cd4b Initialize native NGG vertex launch state and wave size 2026-09-22 11:11:36 +04:00
nmzik f9e56ef8a0 Scope native barriers to shaders with shared execution 2026-09-22 11:11:36 +04:00
nmzik 61761b65f2 Honor PS5 title output-resolution detection permission 2026-09-22 11:11:36 +04:00
27 changed files with 677 additions and 238 deletions
+2 -7
View File
@@ -453,13 +453,8 @@ std::vector<File::DirEntry> File::GetDirEntries(const std::filesystem::path& pat
std::vector<File::DirEntry> ret;
ret.reserve(files.size());
for (const auto& f: files) {
File::DirEntry r {};
r.name = f.name;
r.is_file = f.is_file;
ret.push_back(std::move(r));
for (auto& f: files) {
ret.emplace_back(std::move(f.name), f.is_file);
}
return ret;
-43
View File
@@ -912,17 +912,6 @@ void CommandProcessor::DrawIndirect(uint32_t data_offset, uint32_t draw_initiato
if (!indexed) {
DrawIndirectArgs args {};
std::memcpy(&args, args_addr, sizeof(args));
if (args.instance_count != 1u || args.start_vertex_location != 0u ||
args.start_instance_location != 0u) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1) < 64) {
LOGF("\t warning: partial DrawIndirect args: vertex_count=%" PRIu32
", instance_count=%" PRIu32 ", start_vertex=%" PRIu32
", start_instance=%" PRIu32 "\n",
args.vertex_count_per_instance, args.instance_count,
args.start_vertex_location, args.start_instance_location);
}
}
m_num_instances = args.instance_count;
DrawIndexAuto({.vertex_count = args.vertex_count_per_instance,
.instance_count = args.instance_count,
@@ -934,16 +923,6 @@ void CommandProcessor::DrawIndirect(uint32_t data_offset, uint32_t draw_initiato
DrawIndexedIndirectArgs args {};
std::memcpy(&args, args_addr, sizeof(args));
if (args.base_vertex_location != 0u || args.start_instance_location != 0u) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1) < 64) {
LOGF("\t warning: partial DrawIndexIndirect args: index_count=%" PRIu32
", instance_count=%" PRIu32 ", start_index=%" PRIu32 ", base_vertex=%" PRIu32
", start_instance=%" PRIu32 "\n",
args.index_count_per_instance, args.instance_count, args.start_index_location,
args.base_vertex_location, args.start_instance_location);
}
}
uint64_t index_size = 0;
switch (m_index_type_and_size) {
@@ -1005,17 +984,6 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
if (!indexed) {
auto* args = reinterpret_cast<const DrawIndirectArgs*>(args_addr);
if (args->instance_count != 1u || args->start_vertex_location != 0u ||
args->start_instance_location != 0u) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1) < 64) {
LOGF("\t warning: partial DrawIndirectMulti args[%u]: vertex_count=%" PRIu32
", instance_count=%" PRIu32 ", start_vertex=%" PRIu32
", start_instance=%" PRIu32 "\n",
i, args->vertex_count_per_instance, args->instance_count,
args->start_vertex_location, args->start_instance_location);
}
}
m_num_instances = args->instance_count;
DrawIndexAuto({.vertex_count = args->vertex_count_per_instance,
.instance_count = args->instance_count,
@@ -1026,17 +994,6 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
}
auto* args = reinterpret_cast<const DrawIndexedIndirectArgs*>(args_addr);
if (args->base_vertex_location != 0u || args->start_instance_location != 0u) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1) < 64) {
LOGF("\t warning: partial DrawIndexIndirectMulti args[%u]: index_count=%" PRIu32
", instance_count=%" PRIu32 ", start_index=%" PRIu32 ", base_vertex=%" PRIu32
", start_instance=%" PRIu32 "\n",
i, args->index_count_per_instance, args->instance_count,
args->start_index_location, args->base_vertex_location,
args->start_instance_location);
}
}
uint64_t index_size = 0;
switch (m_index_type_and_size) {
+2 -3
View File
@@ -209,17 +209,16 @@ static void RtCheck(const HW::RenderTarget& rt) {
}
if (rt.info.fmask_compression_enable) {
EXIT_NOT_IMPLEMENTED(rt.attrib.num_samples == 0 && rt.attrib.num_fragments == 0);
// Native MSAA stores expanded samples, independent of FMASK metadata compression.
static bool logged = false;
if (!logged) {
LOGF("RenderTarget: using native Vulkan MSAA without guest FMASK metadata, "
LOGF("RenderTarget: using expanded native Vulkan MSAA samples, "
"fmask=0x%016" PRIx64 "\n",
rt.fmask.addr);
logged = true;
}
}
EXIT_NOT_IMPLEMENTED(rt.info.fmask_data_compression_disable != false);
if (rt.info.cmask_fast_clear_enable || rt.info.dcc_compression_enable) {
static bool logged = false;
if (!logged) {
@@ -184,17 +184,18 @@ bool IsSupportedDepthTextureEncoding(const ShaderTextureResource& descriptor, bo
}
const bool full = common && descriptor.fields[4] == field4_expected &&
descriptor.fields[5] == field5_expected;
if (!full || (descriptor.fields[6] == 0 && descriptor.fields[7] != 0) ||
if (!full ||
(descriptor.MsaaDepth() && !IsMultisampledTexture(descriptor.Type()))) {
return false;
}
if (descriptor.fields[6] == 0) {
const auto metadata_control = descriptor.fields[6] & 0x00ffffffu;
if (metadata_control == 0) {
return true;
}
constexpr uint32_t htile_control = 0x00280000u;
const uint32_t expected_control = htile_control | (descriptor.MsaaDepth() ? (1u << 10u) : 0u);
const auto metadata_addr = descriptor.MetaAddr() << 8u;
return (descriptor.fields[6] & 0x00ffffffu) == expected_control && metadata_addr != 0 &&
return metadata_control == expected_control && metadata_addr != 0 &&
metadata_addr < TRACKER_ADDRESS_SIZE && (metadata_addr & 0x7fffu) == 0 &&
descriptor.TileMode() == Prospero::TileMode::kDepth;
}
@@ -337,6 +337,7 @@ struct PipelineCache::ProgramCache {
if constexpr (std::is_same_v<InputInfo, ShaderVertexInputInfo>) {
options.user_data_base = 8;
options.wave_size = input_info.wave_size;
if (stage == ShaderType::Mesh || stage == ShaderType::TessellationControl) {
options.user_data_base = 0;
options.wave_size = stage == ShaderType::Mesh ? input_info.mesh.wave_size : 64u;
+3 -20
View File
@@ -461,7 +461,6 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
state.height = std::numeric_limits<uint32_t>::max();
state.num_layers = std::numeric_limits<uint32_t>::max();
state.num_color_attachments = 0;
uint32_t attachment_samples = 0;
for (uint32_t i = 0; i < color_count; i++) {
auto& target = colors[i];
EXIT_IF(!target.image_id);
@@ -482,12 +481,6 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
target.desc.view_info.base_level, target.desc.view_info.base_layer,
target.desc.view_info.layer_count);
EXIT_IF(image.backing.samples != target.desc.info.samples || image_view == nullptr);
if (attachment_samples == 0) {
attachment_samples = target.desc.info.samples;
} else if (attachment_samples != target.desc.info.samples) {
EXIT("mixed color attachment sample counts are unsupported: %u and %u\n",
attachment_samples, target.desc.info.samples);
}
const auto& view = target.desc.view_info;
const auto layout = image.binding.is_bound ? vk::ImageLayout::eGeneral
: vk::ImageLayout::eColorAttachmentOptimal;
@@ -536,12 +529,6 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
image.info.data.address, depth.desc.view_info.base_layer,
depth.desc.view_info.layer_count);
EXIT_IF(image_view == nullptr || image.backing.samples != depth.desc.info.samples);
if (attachment_samples == 0) {
attachment_samples = depth.desc.info.samples;
} else if (attachment_samples != depth.desc.info.samples) {
EXIT("mixed color/depth sample counts are unsupported: %u and %u\n", attachment_samples,
depth.desc.info.samples);
}
const bool feedback = depth.depth_write_enable && pixel &&
std::ranges::any_of(pixel->images, [&](const TextureBinding& binding) {
if (binding.image_id != depth.image_id ||
@@ -592,9 +579,6 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
const auto& limits = buffer.GetGraphics().GetPhysicalDeviceProperties().limits;
state.width = limits.maxFramebufferWidth;
state.height = limits.maxFramebufferHeight;
} else if (attachment_samples == 0 ||
vulkan_sample_count(attachment_samples) == vk::SampleCountFlagBits {}) {
EXIT("render state has no valid attachments\n");
}
if (state.num_layers == std::numeric_limits<uint32_t>::max()) {
state.num_layers = 1;
@@ -1109,10 +1093,6 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
vertex_bindings = AcquireVertexBuffers(buffer, state.vertex_info[0]);
index_binding = PrepareIndexBuffer(buffer, index_source);
}
const auto rendering =
AcquireRenderTargets(buffer, state.color_info, state.color_count, state.depth_info,
bindings.pixel);
if (draw.IsIndexed()) {
LogDrawPhase(draw.Name(), "CreatePipeline");
}
@@ -1120,6 +1100,9 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
std::span {state.color_info, state.color_count}, state.depth_info, vertex_stages, buffer,
state.ps_active ? &state.ps_input_info : nullptr, topology, primitive_restart_enable,
state.programs);
const auto rendering =
AcquireRenderTargets(buffer, state.color_info, state.color_count, state.depth_info,
bindings.pixel);
// Resource preparation above may synchronously finish and restart the scheduler. From this
// point onward, every operation targets the current command buffer and cannot touch guest
+6 -1
View File
@@ -20,6 +20,7 @@
#include "kernel/pthread.h"
#include "libs/errno.h"
#include "libs/libs.h"
#include "loader/systemContent.h"
#include <algorithm>
#include <array>
@@ -1654,8 +1655,12 @@ KYTY_SYSV_ABI int VideoOutGetOutputStatus(int handle, VideoOutOutputStatus* stat
return VIDEO_OUT_ERROR_INVALID_HANDLE;
}
int32_t attribute3 = 0;
Loader::SystemContentParamSfoGetInt("ATTRIBUTE3", &attribute3);
ctx->mutex.Lock();
status->resolution = (ctx->width >= 3840 || ctx->height >= 2160 ? 2u : 1u);
// Primary output reports 4K unless param.json Video-out Info enables resolution detection.
status->resolution =
((attribute3 & 4) != 0 && ctx->width < 3840 && ctx->height < 2160 ? 1u : 2u);
status->dynamicRange = 1;
status->refreshRate =
(ctx->output_mode == VIDEO_OUT_OUTPUT_MODE_119_88HZ || Config::GetVblankFrequency() >= 119
@@ -132,12 +132,23 @@ void ValidateNativeProgram(const IR::Program& program) {
program.memory_info[index].planning_only;
});
};
const auto indirect_buffer_handle = [&](const IR::Inst& handle) {
return program.info.uses_dma && handle.NumArgs() == 4u && !handle.Uses().empty() &&
std::ranges::all_of(handle.Uses(), [&](const IR::Use& use) {
if (IR::BufferAccessOf(use.user->GetOpcode()) != IR::BufferAccess::Read) {
return false;
}
const auto index = use.user->Flags<IR::MemoryFlags>().index;
return index < program.memory_info.size() &&
program.memory_info[index].kind == IR::ResourceKind::IndirectBuffer;
});
};
for (const auto* block: program.blocks) {
for (const auto& inst: *block) {
const auto dense = inst.Flags<uint32_t>();
switch (inst.GetOpcode()) {
case IR::ValueOpcode::GetBufferResource:
if (planning_only_handle(inst)) {
if (planning_only_handle(inst) || indirect_buffer_handle(inst)) {
break;
}
if (dense >= program.info.buffers.size()) {
@@ -212,6 +223,9 @@ Emitter::SpirvRequirements Emitter::AnalyzeProgramRequirements(const IR::Program
Fail(program, "buffer operation has invalid memory metadata");
}
const auto& memory = program.memory_info[memory_index];
if (memory.kind == IR::ResourceKind::IndirectBuffer) {
requirements.subgroup_local_invocation_id = true;
}
if (memory.kind == IR::ResourceKind::Buffer) {
if (memory.resource >= program.info.buffers.size()) {
Fail(program, "buffer operation has invalid resource metadata");
@@ -559,6 +559,10 @@ void EmitVoid(ValueEmitContext&) {}
void EmitBarrier(EmitterState& state) {
const auto tessellation = state.program.stage == ShaderType::TessellationControl;
if (!tessellation && ShaderWorkgroupInput(state.program.stage, state.input_info) == nullptr) {
// Independent graphics invocations have no native workgroup left to synchronize.
return;
}
const auto memory_scope = tessellation ? spv::ScopeInvocation : spv::ScopeWorkgroup;
const auto semantics = tessellation ? spv::MemorySemanticsMaskNone
: spv::MemorySemanticsAcquireReleaseMask |
@@ -27,61 +27,66 @@ uint32_t EmitDsMaskedLaneRead(EmitterState& state, uint32_t source, uint32_t tar
return Select(state, TypeU32(state), source_active, shuffled, ConstantU32(state, 0));
}
uint32_t BufferByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
uint32_t index, uint32_t offset, uint32_t soffset) {
auto& state = ctx.state;
const uint32_t packed = StorageBufferPackedStride(state, mem);
const uint32_t stride = packed & 0x3fffu;
const bool swizzle = stride != 0u && ((packed >> 14u) & 1u) != 0u;
if (((packed >> 20u) & 1u) != 0u) {
const auto lane = Binary(state, spv::OpBitwiseAnd, TypeU32(state),
EmitSubgroupLocalInvocationId(state), ConstantU32(state, 63));
index = Binary(state, spv::OpIAdd, TypeU32(state), index, lane);
}
if (mem.offset != 0u) {
offset = Binary(state, spv::OpIAdd, TypeU32(state), offset, ConstantU32(state, mem.offset));
}
struct BufferAddress {
uint32_t offset;
uint32_t byte;
};
uint32_t address = 0;
if (!swizzle) {
if (stride == 0u) {
address = offset;
} else {
const auto indexed = stride == 1u ? index
: Binary(state, spv::OpIMul, TypeU32(state), index,
ConstantU32(state, stride));
address = Binary(state, spv::OpIAdd, TypeU32(state), indexed, offset);
}
} else {
const uint32_t stride_enum = (packed >> 16u) & 3u;
const uint32_t index_stride = 8u << stride_enum;
const auto index_msb = Binary(state, spv::OpShiftRightLogical, TypeU32(state), index,
ConstantU32(state, stride_enum + 3u));
const auto index_lsb = Binary(state, spv::OpBitwiseAnd, TypeU32(state), index,
ConstantU32(state, index_stride - 1u));
const auto offset_msb =
BufferAddress CalculateBufferAddress(EmitterState& state, uint32_t index, uint32_t offset,
uint32_t soffset, uint32_t immediate, uint32_t stride,
uint32_t swizzle, uint32_t index_stride) {
if (immediate != 0u) {
offset = Binary(state, spv::OpIAdd, TypeU32(state), offset, ConstantU32(state, immediate));
}
const auto indexed = Binary(state, spv::OpIMul, TypeU32(state), index, stride);
auto address = Binary(state, spv::OpIAdd, TypeU32(state), indexed, offset);
if (swizzle != 0u) {
const auto index_shift =
Binary(state, spv::OpIAdd, TypeU32(state), index_stride, ConstantU32(state, 3));
const auto indices = Binary(state, spv::OpShiftLeftLogical, TypeU32(state),
ConstantU32(state, 1), index_shift);
const auto index_msb =
Binary(state, spv::OpShiftRightLogical, TypeU32(state), index, index_shift);
const auto index_lsb =
Binary(state, spv::OpBitwiseAnd, TypeU32(state), index,
Binary(state, spv::OpISub, TypeU32(state), indices, ConstantU32(state, 1)));
const auto offset_msb =
Binary(state, spv::OpBitwiseAnd, TypeU32(state), offset, ConstantU32(state, ~3u));
const auto offset_lsb =
Binary(state, spv::OpBitwiseAnd, TypeU32(state), offset, ConstantU32(state, 3u));
const auto indexed_msb = stride == 1u ? index_msb
: Binary(state, spv::OpIMul, TypeU32(state),
index_msb, ConstantU32(state, stride));
const auto msb = Binary(state, spv::OpIMul, TypeU32(state),
Binary(state, spv::OpIAdd, TypeU32(state), indexed_msb, offset_msb),
ConstantU32(state, index_stride));
const auto indexed_msb = Binary(state, spv::OpIMul, TypeU32(state), index_msb, stride);
const auto msb =
Binary(state, spv::OpIMul, TypeU32(state),
Binary(state, spv::OpIAdd, TypeU32(state), indexed_msb, offset_msb), indices);
const auto lsb = Binary(state, spv::OpIAdd, TypeU32(state),
Binary(state, spv::OpShiftLeftLogical, TypeU32(state), index_lsb,
ConstantU32(state, 2u)),
offset_lsb);
address = Binary(state, spv::OpIAdd, TypeU32(state), msb, lsb);
address = Select(state, TypeU32(state), swizzle,
Binary(state, spv::OpIAdd, TypeU32(state), msb, lsb), address);
}
return {offset, Binary(state, spv::OpIAdd, TypeU32(state), address, soffset)};
}
const auto soffset_value = inst.Arg(3).Resolve();
if (soffset_value.IsImmediate() && soffset_value.GetType() == IR::Type::U32 &&
soffset_value.U32() == 0u) {
return address;
uint32_t BufferLane(EmitterState& state) {
return Binary(state, spv::OpBitwiseAnd, TypeU32(state), EmitSubgroupLocalInvocationId(state),
ConstantU32(state, 63));
}
uint32_t BufferByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
auto& state = ctx.state;
const auto packed = StorageBufferPackedStride(state, mem);
const auto stride = packed & 0x3fffu;
auto index = ctx.Arg(inst, 1);
if ((packed & (1u << 20u)) != 0u) {
index = Binary(state, spv::OpIAdd, TypeU32(state), index, BufferLane(state));
}
return Binary(state, spv::OpIAdd, TypeU32(state), address, soffset);
const bool swizzle = stride != 0u && (packed & (1u << 14u)) != 0u;
return CalculateBufferAddress(state, index, ctx.Arg(inst, 2), ctx.Arg(inst, 3), mem.offset,
ConstantU32(state, stride),
swizzle ? ConstantBool(state, true) : 0u,
ConstantU32(state, (packed >> 16u) & 3u))
.byte;
}
uint32_t AddU64Low(EmitterState& state, uint32_t low, uint32_t high, uint32_t add_low,
@@ -196,11 +201,8 @@ uint32_t LoadBdaDword(ValueEmitContext& ctx, uint32_t address) {
});
}
uint32_t LoadBda(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
uint32_t bits) {
auto& state = ctx.state;
const auto address = GuestAddress(ctx, inst, mem);
const auto active = ctx.Arg(inst, inst.NumArgs() - 1);
uint32_t LoadBda(ValueEmitContext& ctx, uint32_t address, uint32_t active, uint32_t bits) {
auto& state = ctx.state;
return EmitValueOrZeroIfCondition(state, active, [&]() {
const auto aligned = Binary(state, spv::OpBitwiseAnd, TypeScalarU64(state), address,
ConstantDeviceAddress(state, ~uint64_t {3}));
@@ -236,8 +238,7 @@ uint32_t LoadBda(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryIn
uint32_t ByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
if (mem.kind == IR::ResourceKind::Buffer) {
return BufferByteAddress(ctx, inst, mem, ctx.Arg(inst, 1), ctx.Arg(inst, 2),
ctx.Arg(inst, 3));
return BufferByteAddress(ctx, inst, mem);
}
if (mem.kind == IR::ResourceKind::Lds || mem.kind == IR::ResourceKind::Gds) {
if (mem.offset == 0u) {
@@ -734,12 +735,86 @@ void FormattedStore(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Memor
});
}
uint32_t LoadIndirectBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t components) {
auto& state = ctx.state;
const auto& handle = *inst.Arg(0).ResolveInstruction();
const auto word1 = ctx.Arg(handle, 1);
const auto records = ctx.Arg(handle, 2);
const auto word3 = ctx.Arg(handle, 3);
const auto field = [&](uint32_t word, uint32_t first, uint32_t count) {
return EmitBitFieldUExtract(state, word, ConstantU32(state, first),
ConstantU32(state, count));
};
const auto nonzero = [&](uint32_t value) {
return Binary(state, spv::OpINotEqual, TypeBool(state), value, ConstantU32(state, 0));
};
const auto has_dword = [&](uint32_t offset, uint32_t size) {
return AndCondition(
state,
Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), size, ConstantU32(state, 4)),
Binary(state, spv::OpULessThanEqual, TypeBool(state), offset,
Binary(state, spv::OpISub, TypeU32(state), size, ConstantU32(state, 4))));
};
const auto stride = field(word1, 16, 14);
const auto swizzle = AndCondition(state, nonzero(stride), nonzero(field(word1, 31, 1)));
const auto index_stride = field(word3, 21, 2);
const auto add_tid = nonzero(field(word3, 23, 1));
const auto index =
Binary(state, spv::OpIAdd, TypeU32(state), ctx.Arg(inst, 1),
Select(state, TypeU32(state), add_tid, BufferLane(state), ConstantU32(state, 0)));
const auto soffset = ctx.Arg(inst, 3);
const auto base = DeviceAddressFromWords(state, ctx.Arg(handle, 0), field(word1, 0, 16));
const auto valid_format = nonzero(field(word3, 12, 7));
const auto mode = field(word3, 28, 2);
const auto index_in_bounds = Binary(state, spv::OpULessThan, TypeBool(state), index, records);
const auto scalar_in_bounds =
Binary(state, spv::OpULessThanEqual, TypeBool(state), soffset, records);
const auto raw_records = Binary(state, spv::OpISub, TypeU32(state), records, soffset);
const auto raw_index_in_bounds =
Binary(state, spv::OpULessThan, TypeBool(state), index, raw_records);
std::array<uint32_t, 4> values {};
for (uint32_t component = 0; component < components; component++) {
const auto address = CalculateBufferAddress(state, index, ctx.Arg(inst, 2), soffset,
ctx.Memory(inst).offset + component * 4u,
stride, swizzle, index_stride);
// RDNA2 raw DWORD vectors check each component, unlike formatted accesses.
const auto structured_bounds =
AndCondition(state, index_in_bounds,
Binary(state, spv::OpULessThan, TypeBool(state), address.offset, stride));
// OOB_SELECT=3 reduces NUM_RECORDS by SOFFSET before the offset/index check.
const auto raw_bounds = AndCondition(
state, scalar_in_bounds,
Select(state, TypeBool(state), swizzle,
AndCondition(state, raw_index_in_bounds, has_dword(address.offset, stride)),
has_dword(address.offset, raw_records)));
auto in_bounds =
Select(state, TypeBool(state),
Binary(state, spv::OpIEqual, TypeBool(state), mode, ConstantU32(state, 0)),
structured_bounds, index_in_bounds);
in_bounds = Select(
state, TypeBool(state),
Binary(state, spv::OpULessThan, TypeBool(state), mode, ConstantU32(state, 2)),
in_bounds,
Select(state, TypeBool(state),
Binary(state, spv::OpIEqual, TypeBool(state), mode, ConstantU32(state, 2)),
nonzero(records), raw_bounds));
const auto guest =
Binary(state, spv::OpIAdd, TypeScalarU64(state), base,
Unary(state, spv::OpUConvert, TypeScalarU64(state), address.byte));
values[component] = LoadBda(ctx, guest, AndCondition(state, valid_format, in_bounds), 32u);
}
return ConstructU32Composite(state, components, values);
}
uint32_t LoadWideBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t components) {
auto& state = ctx.state;
return EmitValueOrDefaultIfCondition(
state, ctx.Arg(inst, inst.NumArgs() - 1), TypeU32Composite(state, components),
ConstantU32CompositeZero(state, components), [&]() {
const auto mem = ctx.Memory(inst);
if (mem.kind == IR::ResourceKind::IndirectBuffer) {
return LoadIndirectBuffer(ctx, inst, components);
}
const auto resource = PrepareMemoryResourceAccess(state, mem);
const auto info = Format::GetFormatInfo(
mem.formatted ? BufferFormat(ctx, mem) : Prospero::BufferFormat::kInvalid);
@@ -1088,7 +1163,8 @@ void EmitLoadMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
value = LoadWideShared(ctx, inst, shared_components);
else if (address_info.access == IR::AddressAccess::Read &&
mem.kind != IR::ResourceKind::Scratch)
value = LoadBda(ctx, inst, mem, address_info.data_bits);
value = LoadBda(ctx, GuestAddress(ctx, inst, mem), ctx.Arg(inst, inst.NumArgs() - 1),
address_info.data_bits);
else if (op == IR::ValueOpcode::LoadBufferU32 && mem.formatted)
value = FormattedLoad(ctx, inst, mem);
else if (inst.GetType() == IR::Type::U8)
@@ -1,4 +1,4 @@
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h"
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
namespace {
@@ -127,10 +127,7 @@ void EmitMeshEntryPoint(EmitterState& state) {
state.builder.AddFunction(spv::OpFunctionCall, TypeVoid(state), state.builder.AllocateId(),
state.mesh_guest_func);
// All guest waves finish before the uniform Vulkan allocation and output stores.
state.builder.AddFunction(spv::OpControlBarrier, ConstantU32(state, spv::ScopeWorkgroup),
ConstantU32(state, spv::ScopeWorkgroup),
ConstantU32(state, spv::MemorySemanticsAcquireReleaseMask |
spv::MemorySemanticsWorkgroupMemoryMask));
EmitBarrier(state);
const auto vertices = MeshLoad(state, state.mesh_allocation, spv::StorageClassWorkgroup,
TypeU32(state), ConstantU32(state, 0));
const auto primitives = MeshLoad(state, state.mesh_allocation, spv::StorageClassWorkgroup,
@@ -527,8 +527,8 @@ constexpr Vop1SdwaRule VOP1_SDWA_RULES[] = {
{Opcode::V_CVT_F32_F16, SdwaSelWords() | SdwaSelFull(), 0, 0, true},
{Opcode::V_CVT_F16_F32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), SdwaSelWords(),
SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), true},
{Opcode::V_CVT_F16_U16, SdwaSelWords() | SdwaSelFull(), SdwaSelWords(),
SdwaSelWords() | SdwaSelFull(), false},
{Opcode::V_CVT_F16_U16, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), SdwaSelWords(),
SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), false},
{Opcode::V_CVT_U16_F16, SdwaSelWords() | SdwaSelFull(), SdwaSelWords(),
SdwaSelWords() | SdwaSelFull(), true},
{Opcode::V_CVT_F16_I16, SdwaSelWords() | SdwaSelFull(), SdwaSelWords(),
@@ -618,13 +618,14 @@ bool SupportsVop1Clamp(Opcode opcode) {
}
bool ValidateVop1Sdwa(Instruction& inst, uint32_t opcode, uint32_t modifier) {
const auto dst_sel = (modifier >> 8u) & 0x7u;
const auto dst_u = (modifier >> 11u) & 0x3u;
const auto clamp = (modifier >> 13u) & 0x1u;
const auto omod = (modifier >> 14u) & 0x3u;
const auto src0_sel = (modifier >> 16u) & 0x7u;
const auto src0_neg = (modifier >> 20u) & 0x1u;
const auto src0_abs = (modifier >> 21u) & 0x1u;
const auto dst_sel = (modifier >> 8u) & 0x7u;
const auto dst_u = (modifier >> 11u) & 0x3u;
const auto clamp = (modifier >> 13u) & 0x1u;
const auto omod = (modifier >> 14u) & 0x3u;
const auto src0_sel = (modifier >> 16u) & 0x7u;
const auto src0_sext = (modifier >> 19u) & 0x1u;
const auto src0_neg = (modifier >> 20u) & 0x1u;
const auto src0_abs = (modifier >> 21u) & 0x1u;
if (src0_sel > 6u || dst_sel > 6u) {
SetUnsupported(inst, Family::VOP1, opcode, "VOP1 SDWA selector is invalid");
@@ -640,7 +641,8 @@ bool ValidateVop1Sdwa(Instruction& inst, uint32_t opcode, uint32_t modifier) {
"VOP1 SDWA destination selector is not supported");
return false;
}
if (!IsVop1SdwaSourceSupported(inst.opcode, src0_sel, src0_neg != 0u, src0_abs != 0u)) {
if (!IsVop1SdwaSourceSupported(inst.opcode, src0_sel, src0_neg != 0u, src0_abs != 0u) ||
(inst.opcode == Opcode::V_CVT_F16_U16 && src0_sel <= 3u && src0_sext != 0u)) {
SetUnsupported(inst, Family::VOP1, opcode, "VOP1 SDWA source selector is not supported");
return false;
}
@@ -1225,6 +1225,12 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
builtin(IR::StageInputKind::PackedAncillary));
}
} else if (options.stage == ShaderType::Vertex) {
// Vulkan owns primitive assembly; each vertex subgroup is one NGG wave.
// Keep its full lane extent: mbcnt(-1) uses lane ordinals, not active counts.
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(2),
IR::U32(IR::Value(options.wave_size << 12u)));
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(3),
IR::U32(IR::Value((1u << 28u) | options.wave_size)));
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(5),
builtin(IR::StageInputKind::VertexIndex));
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(8),
+10 -3
View File
@@ -447,13 +447,20 @@ void ValidateProgram(const Program& program, bool require_ssa) {
ValueOpcodeName(inst.GetOpcode())));
}
const auto& memory = program.memory_info[memory_index];
if (memory.kind != ResourceKind::Buffer &&
memory.kind != ResourceKind::ScalarBuffer) {
const bool vector_buffer = memory.kind == ResourceKind::Buffer ||
memory.kind == ResourceKind::IndirectBuffer;
if (!vector_buffer && memory.kind != ResourceKind::ScalarBuffer) {
return Fail(fmt::format("{} has a non-buffer resource kind",
ValueOpcodeName(inst.GetOpcode())));
}
if (memory.kind == ResourceKind::IndirectBuffer &&
(memory.formatted || memory.typed ||
(inst.GetOpcode() != ValueOpcode::LoadBufferU32x2 &&
inst.GetOpcode() != ValueOpcode::LoadBufferU32x4))) {
return Fail("indirect buffer requires a raw DWORD x2/x4 load");
}
if (buffer_components > 1u &&
(memory.kind != ResourceKind::Buffer || memory.data_bits != 32u ||
(!vector_buffer || memory.data_bits != 32u ||
memory.data_dwords != buffer_components || memory.component_index != 0u)) {
return Fail(fmt::format("{} has inconsistent native-wide metadata",
ValueOpcodeName(inst.GetOpcode())));
@@ -27,6 +27,7 @@ enum class ResourceKind {
ScalarBuffer,
ScalarAddress,
Buffer,
IndirectBuffer,
Flat,
Global,
Scratch,
@@ -357,16 +357,23 @@ struct SamplerPlan {
};
struct ImageRemap {
explicit ImageRemap(const ResourceSpecialization& specialization) {
for (const auto& image: specialization.images) {
indices.push_back(image.fmask ? UINT32_MAX : count++);
explicit ImageRemap(const ResourceSpecialization& specialization)
: source_count(static_cast<uint32_t>(specialization.images.size())) {
EXIT_IF(specialization.images.size() > indices.size());
for (uint32_t index = 0; index < source_count; index++) {
indices[index] = specialization.images[index].fmask ? UINT32_MAX : count++;
}
}
uint32_t operator[](uint32_t index) const {
EXIT_IF(index >= source_count);
return indices[index];
}
template <typename T>
void Apply(std::vector<T>& images) const {
EXIT_IF(images.size() != indices.size());
for (uint32_t index = 0; index < indices.size(); index++) {
EXIT_IF(images.size() != source_count);
for (uint32_t index = 0; index < source_count; index++) {
if (indices[index] != UINT32_MAX && indices[index] != index) {
images[indices[index]] = std::move(images[index]);
}
@@ -374,8 +381,10 @@ struct ImageRemap {
images.resize(count);
}
std::vector<uint32_t> indices;
uint32_t count = 0;
private:
std::array<uint32_t, ShaderInfo::MaxImages> indices;
uint32_t source_count;
uint32_t count = 0;
};
template <typename Images>
@@ -1075,29 +1084,29 @@ void ApplyResourceSpecialization(Program& program, const ResourceSpecialization&
for (auto* block: program.blocks) {
for (auto& inst: *block) {
if (inst.GetOpcode() == ValueOpcode::GetImageResource) {
inst.SetFlags(image_remap.indices.at(inst.Flags<uint32_t>()));
inst.SetFlags(image_remap[inst.Flags<uint32_t>()]);
}
}
}
for (auto& memory: memory_info) {
if (memory.kind == ResourceKind::Image && !memory.planning_only) {
memory.resource = image_remap.indices.at(memory.resource);
memory.resource = image_remap[memory.resource];
}
}
for (auto& buffer: buffers) {
if (buffer.image_alias != BufferResource::NoImageAlias) {
buffer.image_alias = image_remap.indices.at(buffer.image_alias);
buffer.image_alias = image_remap[buffer.image_alias];
}
}
for (auto& pair: sampled_pairs) {
pair.image = image_remap.indices.at(pair.image);
pair.image = image_remap[pair.image];
}
for (auto& image: images) {
if (image.indirect_root != ImageResource::NoIndirectImage) {
image.indirect_root = image_remap.indices.at(image.indirect_root);
image.indirect_root = image_remap[image.indirect_root];
}
for (auto& resource: image.indirect_resources) {
resource = image_remap.indices.at(resource);
resource = image_remap[resource];
}
}
image_remap.Apply(images);
@@ -526,7 +526,7 @@ private:
}
}
void GetHandle(Value value, ValueOpcode expected, uint32_t width, uint32_t pc, Inst*& handle,
bool GetHandle(Value value, ValueOpcode expected, uint32_t width, uint32_t pc, Inst*& handle,
uint32_t& source, bool sampler = false, bool sample_adjust = false) {
handle = value.Resolve().TryInstruction();
if (handle == nullptr || handle->GetOpcode() != expected) {
@@ -535,21 +535,17 @@ private:
DescriptorSource descriptor;
MakeSource(*handle, width, sampler, sample_adjust, descriptor, pc);
uint32_t bad_dword = 0;
if (expected == ValueOpcode::GetImageResource) {
for (; bad_dword < descriptor.dword_count; bad_dword++) {
const auto* value = descriptor.dwords[bad_dword].Resolve().TryInstruction();
if (value != nullptr && value->GetOpcode() == ValueOpcode::ReadConstBuffer) {
Fail(pc, fmt::format("{} dword {} is not a valid runtime value",
ValueOpcodeName(expected), bad_dword));
}
}
bad_dword = 0;
}
if (!ValidateSource(descriptor, bad_dword)) {
if (expected == ValueOpcode::GetBufferResource &&
std::all_of(descriptor.dwords.begin(), descriptor.dwords.begin() + width,
[](Value word) { return word.Resolve().GetType() == Type::U32; })) {
return false;
}
Fail(pc, fmt::format("{} dword {} is not a valid runtime value",
ValueOpcodeName(expected), bad_dword));
}
source = InternSource(descriptor);
return true;
}
void ValidateAddressHandle(Value value, uint32_t pc) const {
@@ -720,7 +716,18 @@ private:
uint32_t resource = 0;
if (buffer != BufferAccess::None) {
GetHandle(inst.Arg(0), ValueOpcode::GetBufferResource, 4, flags.pc, handle, source);
if (!GetHandle(inst.Arg(0), ValueOpcode::GetBufferResource, 4, flags.pc, handle,
source)) {
if (memory.kind != ResourceKind::Buffer || memory.formatted || memory.typed ||
(op != ValueOpcode::LoadBufferU32x2 && op != ValueOpcode::LoadBufferU32x4)) {
Fail(flags.pc,
"buffer descriptor is not a valid runtime value; GPU-selected access "
"requires a raw DWORD x2/x4 load");
}
m_program.memory_info[flags.index].kind = ResourceKind::IndirectBuffer;
m_info.uses_dma = true;
return;
}
resource = AddBuffer(source, memory, op, flags.pc);
if (resource == UINT32_MAX) {
Fail(flags.pc, "buffer resource limit exceeded");
+2
View File
@@ -651,6 +651,7 @@ void BuildStageStaticKey(const ShaderVertexInputInfo& info, std::vector<uint32_t
key.push_back(static_cast<uint32_t>(info.fetch_attrib_reg));
key.push_back(static_cast<uint32_t>(info.fetch_buffer_reg));
key.push_back(info.resources_num);
key.push_back(info.wave_size);
key.push_back(info.scratch_size_dwords);
key.push_back(info.pa_cl_vs_out_cntl);
key.push_back(static_cast<uint32_t>(info.clip_space.enabled));
@@ -764,6 +765,7 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context&
regs.gs_regs.rsrc2.user_sgpr, sh, data, info)) {
EXIT("failed to prepare vertex shader program\n");
}
info.wave_size = (context.GetShaderStages() & 0x00400000u) != 0 ? 32u : 64u;
return params;
}
// NGG user SGPRs start at s8; a separately compiled GS back half also receives
+1
View File
@@ -134,6 +134,7 @@ struct ShaderVertexInputInfo {
int fetch_attrib_reg = 0;
int fetch_buffer_reg = 0;
int buffers_num = 0;
uint32_t wave_size = 64;
uint32_t scratch_size_dwords = 0;
uint32_t pa_cl_vs_out_cntl = 0;
ShaderClipSpaceTransform clip_space;
+8 -6
View File
@@ -464,6 +464,14 @@ private:
const auto* chunk = input + offset;
const auto chunk_size = static_cast<size_t>(AjmReadLe32(chunk + 4));
const auto payload = offset + 8;
// The data chunk describes the whole stream; this job may contain only its prefix.
if (AjmFourCcEquals(chunk, 'd', 'a', 't', 'a')) {
*data_offset = payload;
result->input_consumed = payload;
result->format = GetFormat();
result->total_decoded_samples = m_total_decoded_samples;
return true;
}
if (payload > input_size || chunk_size > input_size - payload) {
result->result = AJM_RESULT_PARTIAL_INPUT;
return false;
@@ -496,12 +504,6 @@ private:
LOGF("AJM ATRAC9 gapless: total=%" PRIu32 ", skip=%" PRIu16 "\n",
gapless_decode.total_samples, gapless_decode.skip_samples);
}
} else if (AjmFourCcEquals(chunk, 'd', 'a', 't', 'a')) {
*data_offset = payload;
result->input_consumed = payload;
result->format = GetFormat();
result->total_decoded_samples = m_total_decoded_samples;
return true;
}
const auto padded_size = chunk_size + (chunk_size & 1u);
+20 -6
View File
@@ -852,11 +852,15 @@ public:
emit_event(stop_event, AVPLAYER_EVENT_STATE_STOP);
return 0;
}
void Pause() {
int Pause() {
std::scoped_lock lifecycle_lock(lifecycle_mutex);
std::lock_guard lock(mutex);
if (state != State::Playing) {
return AVPLAYER_ERROR_OPERATION_FAILED;
}
paused = true;
pause_time = std::chrono::steady_clock::now();
return 0;
}
void Resume() {
std::scoped_lock lifecycle_lock(lifecycle_mutex);
@@ -890,6 +894,9 @@ public:
bool was_paused = false;
{
std::lock_guard lock(mutex);
if (state != State::Playing) {
return AVPLAYER_ERROR_OPERATION_FAILED;
}
was_paused = paused;
}
return StartImpl(ms, was_paused, true);
@@ -1682,6 +1689,7 @@ struct AvPlayerInternal {
AvPlayerPostInitData post_init {};
bool auto_start = false;
int32_t video_buffers = 2;
uint32_t sync_mode = 0;
uint32_t start_bandwidth = 0;
uint32_t minimum_bandwidth = 0;
uint32_t maximum_bandwidth = 0;
@@ -1730,6 +1738,7 @@ static int add_source(AvPlayerInternal* h, const std::string& filename, AvPlayer
if (auto rc = s->Init(filename, type); rc < 0) {
return rc;
}
s->SetSync(h->sync_mode);
h->source = std::move(s);
emit_event(h->event, AVPLAYER_EVENT_STATE_READY);
if (h->auto_start) {
@@ -1915,9 +1924,11 @@ int KYTY_SYSV_ABI AvPlayerPause(AvPlayerInternal* h) {
if (h == nullptr || h->source == nullptr) {
return AVPLAYER_ERROR_INVALID_PARAMS;
}
h->source->Pause();
emit_event(h->event, AVPLAYER_EVENT_STATE_PAUSE);
return 0;
auto rc = h->source->Pause();
if (rc == 0) {
emit_event(h->event, AVPLAYER_EVENT_STATE_PAUSE);
}
return rc;
}
int KYTY_SYSV_ABI AvPlayerResume(AvPlayerInternal* h) {
PRINT_NAME();
@@ -1951,13 +1962,16 @@ int KYTY_SYSV_ABI AvPlayerSetTrickSpeed(AvPlayerInternal* h, int32_t trick_speed
}
int KYTY_SYSV_ABI AvPlayerSetAvSyncMode(AvPlayerInternal* h, uint32_t sync_mode) {
PRINT_NAME();
if (h == nullptr || h->source == nullptr) {
if (h == nullptr) {
return AVPLAYER_ERROR_INVALID_PARAMS;
}
if (sync_mode > 1) {
return AVPLAYER_ERROR_NOT_SUPPORTED;
}
h->source->SetSync(sync_mode);
h->sync_mode = sync_mode;
if (h->source != nullptr) {
h->source->SetSync(sync_mode);
}
return 0;
}
int KYTY_SYSV_ABI AvPlayerSetAvailableBandwidth(AvPlayerInternal* h, uint32_t start_bandwidth,
+59 -16
View File
@@ -27,6 +27,7 @@ namespace SaveData {
// TODO(): specify dir at launcher
static constexpr char SAVE_DATA_DIR[] = "_SaveData";
static constexpr uint64_t SAVE_DATA_BLOCKS_MIN = 48;
static constexpr uint64_t SAVE_DATA_BLOCKS_MAX = 16384;
struct SceSaveDataDirName {
@@ -305,9 +306,9 @@ static bool valid_memory_range(const SaveDataMemoryData& data, size_t size) {
data.buf_size <= size - static_cast<size_t>(data.offset);
}
// Keep memory.dat as raw guest bytes. Replacing a flushed temporary file protects the
// Replacing a flushed temporary file protects the
// previous save if writing fails or the emulator exits partway through an update.
static int write_memory_file(const std::filesystem::path& path, const void* data, uint32_t size) {
static int write_save_file(const std::filesystem::path& path, const void* data, uint32_t size) {
const auto temporary = std::filesystem::path(path.string() + ".tmp");
Common::File file;
if (!file.Create(temporary)) {
@@ -329,6 +330,21 @@ static int write_memory_file(const std::filesystem::path& path, const void* data
return SAVE_DATA_ERROR_INTERNAL;
}
static int read_save_blocks(const std::filesystem::path& directory, uint64_t* blocks) {
Common::File file(directory / "sce_sys" / "blocks.bin", Common::File::Mode::Read);
if (file.IsInvalid() || file.Size() != sizeof(*blocks)) {
return SAVE_DATA_ERROR_BROKEN;
}
uint64_t value = 0;
uint32_t read = 0;
file.Read(&value, sizeof(value), &read);
if (read != sizeof(value) || value < SAVE_DATA_BLOCKS_MIN || value > SAVE_DATA_BLOCKS_MAX) {
return SAVE_DATA_ERROR_BROKEN;
}
*blocks = value;
return OK;
}
static int save_memory(const std::filesystem::path& directory, const SaveDataMemory& memory) {
std::error_code error;
std::filesystem::create_directories(directory, error);
@@ -336,10 +352,10 @@ static int save_memory(const std::filesystem::path& directory, const SaveDataMem
return SAVE_DATA_ERROR_INTERNAL;
}
// Display metadata is a separate host sidecar, leaving memory.dat as raw guest bytes.
int result = write_memory_file(directory / "param.bin", &memory.param, sizeof(memory.param));
int result = write_save_file(directory / "param.bin", &memory.param, sizeof(memory.param));
if (result == OK) {
result =
write_memory_file(directory / "memory.dat", memory.data.data(), memory.data.size());
write_save_file(directory / "memory.dat", memory.data.data(), memory.data.size());
}
return result;
}
@@ -561,9 +577,14 @@ int KYTY_SYSV_ABI SaveDataDirNameSearch(const SaveDataDirNameSearchCond* cond,
result->params[i] = {};
}
if (result->infos != nullptr) {
result->infos[i] = {};
result->infos[i].blocks = SAVE_DATA_BLOCKS_MAX;
result->infos[i].free_blocks = SAVE_DATA_BLOCKS_MAX;
auto& info = result->infos[i];
info = {};
const int status = read_save_blocks(root / dir_list[i], &info.blocks);
if (status != OK) {
return status;
}
// Host-directory saves report their allocation as free space.
info.free_blocks = info.blocks;
}
}
@@ -621,10 +642,22 @@ int KYTY_SYSV_ABI SaveDataMount3(const SaveDataMount3* mount, SaveDataMountResul
bool created = false;
if ((create || create2) && !Common::File::IsDirectoryExisting(mount_dir)) {
Common::File::CreateDirectories(mount_dir);
if (mount->blocks < SAVE_DATA_BLOCKS_MIN || mount->blocks > SAVE_DATA_BLOCKS_MAX) {
return SAVE_DATA_ERROR_PARAMETER;
}
const auto metadata = std::filesystem::path(mount_dir) / "sce_sys";
std::error_code error;
std::filesystem::create_directories(metadata, error);
if (error) {
return SAVE_DATA_ERROR_INTERNAL;
}
const int status = write_save_file(metadata / "blocks.bin", &mount->blocks,
sizeof(mount->blocks));
if (status != OK) {
std::filesystem::remove_all(mount_dir, error);
return status;
}
created = true;
EXIT_NOT_IMPLEMENTED((!Common::File::IsDirectoryExisting(mount_dir)));
}
return mount_save_data(slot, dir_name, mount_dir, created ? 1u : 0u, mount_result);
@@ -1101,13 +1134,23 @@ int KYTY_SYSV_ABI SaveDataGetMountInfo(const SaveDataMountPoint* mount_point,
SaveDataMountInfo* info) {
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(mount_point == nullptr);
EXIT_NOT_IMPLEMENTED(info == nullptr);
if (mount_point == nullptr || info == nullptr ||
std::memchr(mount_point->data, 0, sizeof(mount_point->data)) == nullptr) {
return SAVE_DATA_ERROR_PARAMETER;
}
Common::LockGuard lock(g_mount_mutex);
if (g_mount_slots.Find(mount_point->data) == SaveDataMountSlots::FULL) {
return SAVE_DATA_ERROR_NOT_MOUNTED;
}
uint64_t blocks = 0;
const int status = read_save_blocks(
LibKernel::FileSystem::GetRealFilename(mount_point->data), &blocks);
if (status != OK) {
return status;
}
*info = {};
info->blocks = SAVE_DATA_BLOCKS_MAX;
info->free_blocks = SAVE_DATA_BLOCKS_MAX;
info->blocks = blocks;
info->free_blocks = blocks;
return OK;
}
+1
View File
@@ -164,6 +164,7 @@ void Psf::OpenJson(const std::filesystem::path& file_name) {
add_string_param("contentId", "CONTENT_ID");
add_string_param("contentVersion", "APP_VER");
add_string_param("appVersion", "APP_VER");
add_int_param("attribute3", "ATTRIBUTE3");
add_int_param("userDefinedParam1", "USER_DEFINED_PARAM_1");
add_int_param("userDefinedParam2", "USER_DEFINED_PARAM_2");
add_int_param("userDefinedParam3", "USER_DEFINED_PARAM_3");
+82 -1
View File
@@ -213,8 +213,11 @@ MakeIndirectImageFixture(bool malformed, uint32_t material_immediate = 0,
fixture->AddMemory(shared_buffer, 0x10d8));
fixture->Emit(ValueOpcode::ReferenceU32, {load});
}
const auto invocation = fixture->Emit(
ValueOpcode::GetBuiltin,
{Value(static_cast<uint32_t>(StageInputKind::GlobalInvocationId)), Value(0u)});
const auto selector = fixture->Emit(ValueOpcode::ReadFirstLane,
{fixture->UserData(8), Value(true)});
{invocation, Value(true)});
const auto record =
fixture->Emit(ValueOpcode::IMul32, {selector, Value(224u)});
const auto member = fixture->Emit(ValueOpcode::IAdd32, {record, Value(4u)});
@@ -457,6 +460,83 @@ void TestInvariantIndirectImageMaterialization() {
"wrapped scalar immediate entered the invariant image proof");
}
void TestUniformScalarBufferImage() {
Fixture fixture(ShaderType::Pixel);
std::array<Value, 4> material_words;
std::array<Value, 4> heap_words;
for (uint32_t dword = 0; dword < 4; dword++) {
material_words[dword] = fixture.UserData(dword);
heap_words[dword] = fixture.UserData(dword + 4u);
}
const auto material = fixture.Buffer(material_words);
const auto heap = fixture.Buffer(heap_words);
const auto selector = fixture.Emit(ValueOpcode::ShiftLeftLogical32,
{fixture.UserData(8), Value(2u)});
MemoryInfo scalar;
scalar.kind = ResourceKind::ScalarBuffer;
scalar.offset = 0x40u;
const auto key = fixture.Emit(ValueOpcode::ReadConstBuffer,
{material, selector}, fixture.AddMemory(scalar, 0x100));
const auto record = fixture.Emit(ValueOpcode::IMul32, {key, Value(48u)});
std::array<Value, 8> image_words;
for (uint32_t dword = 0; dword < image_words.size(); dword++) {
scalar.offset = 0x10u + dword * sizeof(uint32_t);
image_words[dword] = fixture.Emit(ValueOpcode::ReadConstBuffer,
{heap, record}, fixture.AddMemory(scalar, 0x200));
}
const auto image = fixture.Image(image_words);
const auto sampler = fixture.Sampler({Value(0u), Value(0u), Value(0u), Value(0u)});
MemoryInfo sample;
sample.kind = ResourceKind::Image;
sample.image_dimension = Decoder::ImageDimension::Dim2D;
fixture.Emit(ValueOpcode::ImageSampleRaw,
{image, sampler, fixture.ImageAddress()}, fixture.AddMemory(sample, 0x228));
fixture.PlanAndTrack();
const auto plan = ExtractResourcePlan(fixture.program);
std::array<uint32_t, 9> user_data{0x1000u, 0u, 0x100u, 0u,
0x2000u, 0u, 0x100u, 0u, 0u};
LinearTestMemory memory;
memory.words[0x44u / 4u] = 1u;
std::array<uint32_t, 8> descriptor{};
descriptor[0] = 0x20u;
descriptor[1] = static_cast<uint32_t>(
Libs::Graphics::Prospero::BufferFormat::k32_32_32_32Float)
<< 20u;
descriptor[2] = 3u | (3u << 14u);
descriptor[3] = Libs::Graphics::DstSel(4, 5, 6, 7) |
(static_cast<uint32_t>(Libs::Graphics::Prospero::ImageType::kColor2D)
<< 28u);
for (uint32_t index = 0; index < 2; index++) {
std::copy(descriptor.begin(), descriptor.end(),
memory.words.begin() + (0x1010u + index * 48u) / 4u);
memory.words[(0x1010u + index * 48u) / 4u] += index;
}
SrtRuntime runtime{.user_data = user_data,
.read_memory = ReadLinearTestMemory,
.userdata = &memory,
.read_specialization_memory = ReadLinearTestMemory};
ResourceSnapshot snapshot;
ResourceSpecialization specialization;
const auto materializes = [&](uint32_t address) {
return MaterializeResources(plan, runtime, snapshot, specialization) &&
snapshot.images.size() == 1 && snapshot.images[0].dwords[0] == address;
};
Check(materializes(0x20u), "nested draw-uniform image descriptor did not materialize");
user_data[8] = 1u;
Check(materializes(0x21u), "changed draw selector reused the previous image descriptor");
memory.words[0x44u / 4u] = 0u;
memory.words[0x1010u / 4u] = 0x30u;
Check(materializes(0x30u), "changed scalar table memory reused the previous image descriptor");
const auto prior_snapshot = snapshot;
const auto prior_specialization = specialization;
memory.fail_address = 0x1044u;
Check(!MaterializeResources(plan, runtime, snapshot, specialization) &&
SameResourceSnapshot(snapshot, prior_snapshot) &&
specialization == prior_specialization,
"unavailable nested image table partially changed the resource snapshot");
}
void TestComputeBufferFill() {
struct Options {
bool scalar = false;
@@ -2013,6 +2093,7 @@ int main() {
Run("FMASK load specialization", TestFmaskLoadSpecialization);
Run("dynamic storage mips", TestDynamicStorageMipTracking);
Run("invariant indirect images", TestInvariantIndirectImageMaterialization);
Run("draw-uniform scalar image", TestUniformScalarBufferImage);
Run("SRT runtime", TestSrtFlatteningAndRuntimeMemoization);
Run("dynamic SRT", TestDynamicSrtReadRemainsExplicit);
Run("phi validation", TestPhiValidation);
+73 -2
View File
@@ -17,6 +17,7 @@
namespace {
std::string g_test_title = "PPSA21564";
std::filesystem::path g_mounted_directory;
std::map<std::string, std::filesystem::path> g_test_mounts;
} // namespace
namespace Loader {
@@ -33,10 +34,14 @@ Common::Time GetTime() {
} // namespace Loader
namespace Libs::LibKernel::FileSystem {
void Mount(const std::filesystem::path& directory, const std::string&) {
void Mount(const std::filesystem::path& directory, const std::string& point) {
g_mounted_directory = directory;
g_test_mounts[point] = directory;
}
void Umount(const std::string& point) { g_test_mounts.erase(point); }
std::filesystem::path GetRealFilename(const std::string& point) {
return g_test_mounts.at(point);
}
void Umount(const std::string&) {}
} // namespace Libs::LibKernel::FileSystem
namespace {
@@ -337,6 +342,7 @@ void TestClassicSavePaths() {
mount.user_id = 1;
mount.dir_name = &name;
mount.mount_mode = text == "save.1" ? 1 : 4;
mount.blocks = 48;
SaveDataMountResult result {};
CHECK(SaveDataMount3(&mount, &result) == OK);
CHECK(g_mounted_directory == fs::path("_SaveData") / "CLASSIC" / text);
@@ -385,6 +391,70 @@ void TestClassicSavePaths() {
CHECK(Search(1) == names);
}
void TestSaveAllocations() {
Reset("CAPACITY");
std::array<SceSaveDataDirName, 2> names {DirName("Options"), DirName("Player")};
const std::array<uint64_t, 2> allocations {48, 96};
for (const auto blocks : {0u, 47u, 16385u}) {
struct SaveDataMount3 invalid {};
invalid.user_id = 1;
invalid.dir_name = &names[0];
invalid.mount_mode = 4;
invalid.blocks = blocks;
SaveDataMountResult result {};
CHECK(SaveDataMount3(&invalid, &result) == SAVE_DATA_ERROR_PARAMETER);
CHECK(!fs::exists("_SaveData/CAPACITY/Options"));
}
for (size_t i = 0; i < names.size(); i++) {
struct SaveDataMount3 mount {};
mount.user_id = 1;
mount.dir_name = &names[i];
mount.mount_mode = 4;
mount.blocks = allocations[i];
SaveDataMountResult result {};
CHECK(SaveDataMount3(&mount, &result) == OK);
CHECK(result.mount_status == 1);
std::ofstream(g_mounted_directory / "USR-DATA") << "saved payload";
CHECK(SaveDataUmount2(0, &result.mount_point) == OK);
SaveDataMountInfo info {};
CHECK(SaveDataGetMountInfo(&result.mount_point, &info) == SAVE_DATA_ERROR_NOT_MOUNTED);
}
Reset("CAPACITY");
std::array<SceSaveDataDirName, 2> found {};
std::array<SaveDataSearchInfo, 2> infos {};
SaveDataDirNameSearchCond cond {};
cond.user_id = 1;
SaveDataDirNameSearchResult search {};
search.dir_names = found.data();
search.dir_names_num = found.size();
search.infos = infos.data();
CHECK(SaveDataDirNameSearch(&cond, &search) == OK && search.set_num == 2);
for (size_t i = 0; i < names.size(); i++) {
CHECK(std::string(found[i].data) == names[i].data);
CHECK(infos[i].blocks == allocations[i] && infos[i].free_blocks == allocations[i]);
struct SaveDataMount3 mount {};
mount.user_id = 1;
mount.dir_name = &names[i];
mount.mount_mode = 34;
mount.blocks = 16384;
SaveDataMountResult result {};
CHECK(SaveDataMount3(&mount, &result) == OK && result.mount_status == 0);
std::ofstream(g_mounted_directory / "USR-DATA", std::ios::app) << "more data";
SaveDataMountInfo info {};
CHECK(SaveDataGetMountInfo(&result.mount_point, &info) == OK);
CHECK(info.blocks == allocations[i] && info.free_blocks == allocations[i]);
CHECK(SaveDataUmount2(0, &result.mount_point) == OK);
}
CHECK(infos[0].blocks + infos[1].blocks == 144);
const fs::path metadata = "_SaveData/CAPACITY/Options/sce_sys/blocks.bin";
fs::resize_file(metadata, 7);
CHECK(SaveDataDirNameSearch(&cond, &search) == SAVE_DATA_ERROR_BROKEN);
CHECK(fs::file_size(metadata) == 7);
CHECK(fs::remove(metadata));
CHECK(SaveDataDirNameSearch(&cond, &search) == SAVE_DATA_ERROR_BROKEN);
CHECK(!fs::exists(metadata));
}
void RunChild(const fs::path& executable, const char* mode) {
#ifdef _WIN32
CHECK(_spawnl(_P_WAIT, executable.string().c_str(), executable.string().c_str(), mode,
@@ -425,6 +495,7 @@ int main(int argc, char** argv) {
TestIsolationAndSync();
TestFailedWritePreservesSave();
TestClassicSavePaths();
TestSaveAllocations();
CHECK(SaveDataTerminate() == OK);
fs::current_path(previous);
fs::remove_all(temp);
+118
View File
@@ -19624,6 +19624,32 @@ TestCase VectorMinMaxF16Ops() {
O::S_ENDPGM}};
}
TestCase VectorCvtF16U16ByteSource() {
using O = ShaderOpcode;
constexpr std::array inputs = {0xff123456u, 0x80123456u, 0x00123456u};
std::vector<u32> code;
AppendVMovLiteral(&code, 16, 0xa5a55a5au);
for (u32 i = 0; i < inputs.size(); i++) {
AppendVMovU32(&code, 30, i * 4u);
AppendBufferLoadDword(&code, 18, 30);
// Convert unsigned byte3 to FP16 word0 while preserving word1 of v16.
code.insert(code.end(), {0x7e20a0f9u, 0x00031412u});
AppendStoreVgpr(&code, 16, i);
}
AppendEnd(&code);
TestCase test;
test.name = "VectorCvtF16U16ByteSource";
test.code = std::move(code);
test.initial.assign(inputs.begin(), inputs.end());
test.expected = {0xa5a55bf8u, 0xa5a55800u, 0xa5a50000u};
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_CVT_F16_U16,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.required_spirv = {"OpBitFieldUExtract", "OpConvertUToF"};
return test;
}
TestCase VectorCvtU16F16Sdwa() {
using O = ShaderOpcode;
@@ -22502,6 +22528,75 @@ TestCase BufferLoadDwordx4ZeroesOnlyOutOfBoundsTail() {
return test;
}
TestCase BufferLoadsGpuSelectedDescriptors() {
using O = ShaderOpcode;
constexpr uint64_t GuestBase = 0x0000000110000000ull;
struct DescriptorCase {
u32 stride, records, mode, soffset;
bool swizzle, bound;
std::array<u32, 6> expected;
};
const DescriptorCase cases[] = {
{24, 2, 0, 0, false, true, {7, 8, 9, 10, 11, 12}},
{12, 2, 0, 0, false, true, {4, 5, 6, 0, 0, 0}},
{12, 2, 1, 0, false, true, {4, 5, 6, 7, 8, 9}},
{12, 1, 2, 0, false, true, {4, 5, 6, 7, 8, 9}},
{12, 0, 2, 0, false, true, {}},
// OOB3 checks offset + soffset, independently of index * stride.
{12, 20, 3, 4, false, true, {5, 6, 7, 8, 0, 0}},
{12, 2, 0, 0, false, false, {}},
// RDNA2 swizzling uses four-byte elements; soffset is added afterward.
{12, 2, 0, 4, true, true, {3, 11, 19, 0, 0, 0}},
{12, 8, 3, 4, true, true, {3, 11, 19, 0, 0, 0}},
{12, 4, 3, 4, true, true, {}},
};
TestCase test;
test.name = "BufferLoadsGpuSelectedDescriptors";
test.initial.resize(2048);
for (u32 i = 0; i < std::size(cases); ++i) {
const auto &input = cases[i];
const u32 data_offset = 4096 + i * 128;
// 120-byte table entries with a descriptor at byte 8, as in PPSA04677.
const std::array<u32, 4> descriptor{
static_cast<u32>(GuestBase + data_offset),
(input.stride << 16u) | (input.swizzle ? 1u << 31u : 0u) | 1u,
input.records, (input.bound ? 0x5204u : 0x204u) | (input.mode << 28u)};
std::copy(descriptor.begin(), descriptor.end(), test.initial.begin() + 130 + i * 30);
for (u32 word = 0; word < 32; ++word) {
test.initial[data_offset / 4 + word] = i * 100 + word + 1;
}
// Reverse the table order through a GPU load and readfirstlane, not host constants.
const u32 selected = static_cast<u32>(std::size(cases)) - 1 - i;
test.initial[64 + i] = selected;
AppendVMovU32(&test.code, 30, (64 + i) * 4);
AppendBufferLoadDword(&test.code, 0, 30);
test.code.push_back(EncodeVop1(0x02, 20, Vgpr(0)));
test.code.push_back(EncodeSop2(0x26, 20, 20, 255)); // s_mul_i32 s20, s20, 120
test.code.push_back(120);
test.code.push_back(EncodeSmem0(0x0a, 8, 0));
test.code.push_back(EncodeSmem1(520, 20));
AppendSMovLiteral(&test.code, 22, cases[selected].soffset);
AppendVMovU32(&test.code, 21, 1);
test.code.push_back(EncodeMubuf0(0x0e, 0, true, false));
test.code.push_back(EncodeMubuf1(0, 2, 21, 22));
test.code.push_back(EncodeMubuf0(0x0d, 16, true, false));
test.code.push_back(EncodeMubuf1(4, 2, 21, 22));
for (u32 component = 0; component < 6; ++component) {
AppendStoreVgpr(&test.code, component, i * 6 + component);
const u32 expected = cases[selected].expected[component];
test.expected.push_back(expected == 0 ? 0 : selected * 100 + expected);
}
}
AppendEnd(&test.code);
test.bda_mappings = {{GuestBase, 0}};
test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::BUFFER_LOAD_DWORD,
O::V_READFIRSTLANE_B32, O::S_MUL_I32, O::S_BUFFER_LOAD_DWORDX4,
O::BUFFER_LOAD_DWORDX4, O::BUFFER_LOAD_DWORDX2,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.required_spirv = {"OpConvertUToPtr", "PhysicalStorageBuffer"};
return test;
}
TestCase BufferStoreDwordx4DropsOnlyOutOfBoundsTail() {
using O = ShaderOpcode;
@@ -27458,6 +27553,7 @@ std::vector<TestCase> MakeCases() {
AddCase(CvtPkrtzF16F32SdwaAndOutputModifiers);
AddCase(PackedMinMaxF16NanAndSignedZeroEdges);
AddCase(VectorMinMaxF16Ops);
AddCase(VectorCvtF16U16ByteSource);
AddCase(VectorCvtU16F16Sdwa);
AddCase(NativeAndSdwa16BitDestinationWrites);
AddCase(VectorMinMaxMed3F16Ops);
@@ -27544,6 +27640,7 @@ std::vector<TestCase> MakeCases() {
AddCase(BufferLoadDwordx3SnapshotsOverlappingAddress);
AddCase(BufferLoadDwordx4SnapshotsOverlappingAddress);
AddCase(BufferLoadDwordx4ZeroesOnlyOutOfBoundsTail);
AddCase(BufferLoadsGpuSelectedDescriptors);
AddCase(BufferStoreDwordx4DropsOnlyOutOfBoundsTail);
AddCase(BufferLoadFormatXyzwRejectsPartialRecord);
AddCase(BufferStoreFormatXyzwDropsPartialRecord);
@@ -29102,6 +29199,17 @@ void CheckDepthTextureEncoding() {
IsSupportedDepthTextureEncoding(compressed_descriptor),
"valid compressed depth descriptor required a prior depth target");
const ShaderTextureResource disabled_metadata{{
0x20018100u, 0xc1600000u, 0x021bc1dfu, 0x91800204u,
0x00000000u, 0x00700000u, 0x00000000u, 0x0020037fu,
}};
auto retained_metadata_address = compressed_descriptor;
retained_metadata_address.fields[6] &= 0xff000000u;
Require("DepthTextureEncoding", "disabled metadata with retained address",
IsSupportedDepthTextureEncoding(disabled_metadata) &&
IsSupportedDepthTextureEncoding(retained_metadata_address),
"uncompressed depth rejected inactive metadata address bits");
const ShaderTextureResource first_use_depth{{
0x0225fc00u, 0x01600000u, 0x00000000u, 0x91800924u,
0x00000000u, 0x00700000u, 0x80280000u, 0x000225fdu,
@@ -32270,6 +32378,16 @@ int main(int argc, char **argv) {
return 0;
}
#endif
if (argc == 2 && std::strcmp(argv[1], "--indirect-buffer-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, BufferLoadsGpuSelectedDescriptors());
RunCase(&vulkan, BufferLoadDwordx4SnapshotsOverlappingAddress());
RunCase(&vulkan, BufferLoadDwordx4ZeroesOnlyOutOfBoundsTail());
RunCase(&vulkan, BufferLoadDwordIdxenUsesDescriptorStride());
RunCase(&vulkan, BufferStoreFormatXAddTidUsesLaneIndex());
RunCase(&vulkan, FlatVirtualAddressRebasesGuestAllocation());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--wave64-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, Wave32VccMasksPreserveOtherHalf());
+76 -34
View File
@@ -1679,45 +1679,14 @@ void TestNewShaderRecompilerSMovB32() {
CheckSpirvBinaryValidates(result.spirv);
}
void TestNewShaderRecompilerSoppMarkers() {
void TestShaderStageBarriers() {
const uint32_t shader[] = {
EncodeSopp(0x00, 3), // s_nop 3
EncodeSopp(0x0c, 0), // s_waitcnt 0
EncodeSopp(0x10, 0x0f), // s_sendmsg 15
EncodeSopp(0x16, 0x2a), // s_ttracedata 42
EncodeSopp(0x20, 1), // s_inst_prefetch 1
EncodeSopp(0x0a, 0), // s_barrier
EncodeSopp(0x01, 0), // s_endpgm
};
auto options = MakeCompileOptions(ShaderType::Compute);
options.dump_ir = true;
auto result = RecompileForTest(shader, options);
Check((result.decoded_dump.find("s_nop 0x00000003") != std::string::npos),
"new decoder did not decode SOPP s_nop");
Check((result.decoded_dump.find("s_waitcnt 0x00000000") != std::string::npos),
"new decoder did not decode SOPP s_waitcnt");
Check((result.decoded_dump.find("s_sendmsg 0x0000000f") != std::string::npos),
"new decoder did not decode SOPP s_sendmsg");
Check((result.decoded_dump.find("s_ttracedata 0x0000002a") != std::string::npos),
"new decoder did not decode SOPP s_ttracedata");
Check((result.decoded_dump.find("s_inst_prefetch 0x00000001") != std::string::npos),
"new decoder did not decode SOPP s_inst_prefetch");
Check((result.decoded_dump.find("s_barrier") != std::string::npos),
"new decoder did not decode SOPP s_barrier");
Check((result.ir_dump.find("ControlNop null, 0x00000003") != std::string::npos),
"SOPP s_nop did not lower to an IR marker");
Check((result.ir_dump.find("Waitcnt null, 0x00000000") != std::string::npos),
"SOPP s_waitcnt did not lower to an IR marker");
Check((result.ir_dump.find("Sendmsg null, 0x0000000f") != std::string::npos),
"SOPP s_sendmsg did not lower to an IR marker");
Check((result.ir_dump.find("TtraceData null, 0x0000002a") != std::string::npos),
"SOPP s_ttracedata did not lower to an IR marker");
Check((result.ir_dump.find("InstPrefetch null, 0x00000001") != std::string::npos),
"SOPP s_inst_prefetch did not lower to an IR marker");
Check((result.ir_dump.find("Barrier null") != std::string::npos),
"SOPP s_barrier did not lower to an IR marker");
const auto result =
RecompileForTest(shader, MakeCompileOptions(ShaderType::Compute));
Check(SpirvContainsOpcode(result.spirv, 224),
"SPIR-V binary does not contain OpControlBarrier");
Check(
@@ -1725,6 +1694,70 @@ void TestNewShaderRecompilerSoppMarkers() {
result.spirv.end(),
"SPIR-V barrier does not use workgroup acquire-release memory semantics");
CheckSpirvBinaryValidates(result.spirv);
const auto vertex_result =
RecompileForTest(shader, MakeCompileOptions(ShaderType::Vertex));
CheckSpirvBinaryValidates(vertex_result.spirv);
Check(!SpirvContainsOpcode(vertex_result.spirv, 224),
"independent vertex invocations retained a workgroup barrier");
}
void TestNggVertexEntryState() {
using namespace ShaderRecompiler;
// PPSA03309 shader 5a39eb2021a5d2c1: retain its NGG prologue and replace
// the resource-dependent vertex body with a position export.
const uint32_t shader[] = {
0xbfa00003u, 0x93ebff03u, 0x00040018u, 0xbefe03c1u,
0x9380ff02u, 0x00090016u, 0x9381ff02u, 0x0009000cu,
0xbf8a0000u, 0xbf076b80u, 0xbf850003u, 0x8f6a8c00u,
0x887c6a01u, 0xbf900009u, 0xd7650001u, 0x000100c1u,
0xd7460001u, 0x04050a6bu, 0x7da80200u, 0xbf880002u,
0xf8000941u, 0x00000000u, 0xbf8cff0fu, 0xbefe03c1u,
0x7da80201u, EncodeSopp(0x08, 2),
EncodeExp0(0x0c, 0xf), EncodeExp1(5, 5, 5, 5), EncodeSopp(0x01),
};
HW::VertexShaderInfo regs{};
regs.es_regs.data_addr = reinterpret_cast<uint64_t>(shader);
ShaderUserData user_data{};
ShaderMappedData mapped{};
mapped.user_data = &user_data;
mapped.code_size_bytes = sizeof(shader);
ShaderMapUserData(regs.es_regs.data_addr, mapped);
std::vector<uint32_t> previous_key;
for (const uint32_t wave_size : {32u, 64u}) {
HW::Context context;
context.SetShaderStages(wave_size == 32u ? 0x00400000u : 0u);
ShaderVertexInputInfo input{};
const auto params = PrepareProgram(regs, context, HW::UserConfig{}, input);
Check(input.wave_size == wave_size,
"vertex preparation lost the native NGG wave size");
const auto key = MakeStageStaticKey(input);
Check(previous_key != key, "NGG wave sizes shared a shader cache key");
previous_key = key;
auto options = MakeCompileOptions(ShaderType::Vertex);
options.user_data_base = 8;
options.user_data = params.user_data;
options.input_info.vertex = &input;
options.wave_size = input.wave_size;
const auto result = RecompileForTest(params.code, options);
bool live_vertex_range = false;
for (const auto *block : result.program.blocks) {
for (const auto &inst : *block) {
if (inst.GetOpcode() == IR::ValueOpcode::UGreaterThan32) {
const auto count = inst.Arg(0).Resolve();
live_vertex_range |= count.IsImmediate() && count.U32() == wave_size;
}
}
}
Check(live_vertex_range && std::ranges::any_of(result.program.info.outputs,
[](const auto &output) {
return output.kind == IR::StageOutputKind::Position;
}),
"NGG launch counts suppressed the vertex export");
Check(result.program.wave_size == wave_size,
"NGG wave size was lost during translation");
CheckSpirvBinaryValidates(result.spirv);
}
}
void TestNewShaderRecompilerSopkWaitcntMarkers() {
@@ -3433,6 +3466,13 @@ void TestNewShaderRecompilerCapturedVop1SdwaByteConvert() {
Check(SpirvContainsOpcode(result.spirv, 112),
"captured SDWA byte conversion did not emit OpConvertUToF");
CheckSpirvBinaryValidates(result.spirv);
const uint32_t byte_sext[] = {0x7e20a0f9u, 0x000b1412u};
ShaderRecompiler::Decoder::Instruction decoded;
ShaderRecompiler::Decoder::DecodeInstruction(byte_sext, 0, decoded);
Check(decoded.word_count == 2u &&
decoded.opcode == ShaderRecompiler::Decoder::Opcode::UNSUPPORTED,
"V_CVT_F16_U16 accepted unimplemented SDWA byte sign extension");
}
void TestNewShaderRecompilerVop1SdwaNotDestination() {
@@ -13443,6 +13483,8 @@ int main() {
TestNewShaderRecompilerSpirvSizeBaselines();
TestDemandDrivenSpirvDeclarations();
TestNewShaderRecompilerSMovB32();
TestShaderStageBarriers();
TestNggVertexEntryState();
TestNewShaderRecompilerClipDisabledPosition();
TestNewShaderRecompilerAuxPositionExports();
TestNewShaderRecompilerNativeWideScalarMemoryIr();