mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-28 13:56:59 +01:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9f88043ebe | |||
| c2173801d4 | |||
| 56d03f16e9 | |||
| 3ecc6b7d1b | |||
| 5bf37f52ad | |||
| 57791645e4 | |||
| d1fe1324c8 | |||
| b485dd24be | |||
| ae25558321 | |||
| ec08ea197b | |||
| f6f1ac8b40 | |||
| b82512be06 | |||
| ba8b0c85b8 | |||
| b6bd9ecae4 | |||
| b95ea4bb5f | |||
| e93b4b0378 | |||
| 0fd41ae206 |
+1
-1
@@ -350,7 +350,7 @@ set(inc_headers
|
||||
${KYTY_THIRD_PARTY_DIR}/SPIRV-Tools/include
|
||||
${KYTY_THIRD_PARTY_DIR}/SPIRV-Headers/include
|
||||
${KYTY_THIRD_PARTY_DIR}/fmt/include
|
||||
${KYTY_THIRD_PARTY_DIR}/xxhash
|
||||
${KYTY_THIRD_PARTY_DIR}/xxHash
|
||||
${KYTY_THIRD_PARTY_DIR}/cpuinfo/include
|
||||
${KYTY_THIRD_PARTY_DIR}/renderdoc
|
||||
${KYTY_THIRD_PARTY_DIR}/stb
|
||||
|
||||
@@ -311,7 +311,6 @@ void RenderExecutor::ResolveRenderDepthTarget(CommandBuffer& buffer, RenderDepth
|
||||
}
|
||||
r.desc = MakeDepthTargetDesc(buffer, z);
|
||||
r.depth_clear_enable = rc.depth_clear_enable;
|
||||
r.depth_load_clear_enable = r.depth_clear_enable;
|
||||
r.depth_clear_value = hw.GetDepthClearValue();
|
||||
r.depth_test_enable = dc.z_enable;
|
||||
r.depth_write_enable = r.depth_test_enable && dc.z_write_enable &&
|
||||
@@ -421,9 +420,9 @@ vk::ImageAspectFlags RenderDepthInfo::AttachmentWriteAspects() const {
|
||||
}
|
||||
|
||||
const auto available = ImageViewOps::DepthAspectMask(format);
|
||||
vk::ImageAspectFlags writes {};
|
||||
vk::ImageAspectFlags writes = load_clear_aspects & available;
|
||||
if ((available & vk::ImageAspectFlagBits::eDepth) &&
|
||||
(depth_load_clear_enable || depth_write_enable)) {
|
||||
depth_write_enable) {
|
||||
writes |= vk::ImageAspectFlagBits::eDepth;
|
||||
}
|
||||
if (!(available & vk::ImageAspectFlagBits::eStencil)) {
|
||||
@@ -461,8 +460,7 @@ vk::ImageAspectFlags RenderDepthInfo::AttachmentWriteAspects() const {
|
||||
(can_pass && depth_pass && state.passOp != vk::StencilOp::eKeep) ||
|
||||
(can_pass && depth_fail && state.depthFailOp != vk::StencilOp::eKeep);
|
||||
};
|
||||
if (stencil_clear_enable ||
|
||||
(stencil_test_enable && (face_writes(stencil_front) || face_writes(stencil_back)))) {
|
||||
if (stencil_test_enable && (face_writes(stencil_front) || face_writes(stencil_back))) {
|
||||
writes |= vk::ImageAspectFlagBits::eStencil;
|
||||
}
|
||||
return writes;
|
||||
|
||||
@@ -22,7 +22,7 @@ struct RenderDepthInfo {
|
||||
// Discovery keeps guest image information but can remap the view into a larger cache image.
|
||||
TextureCache::ImageDesc desc;
|
||||
bool depth_clear_enable = false;
|
||||
bool depth_load_clear_enable = false;
|
||||
vk::ImageAspectFlags load_clear_aspects;
|
||||
float depth_clear_value = 0.0f;
|
||||
bool depth_test_enable = false;
|
||||
// Effective draw writes; discovery applies test, target-write and clear controls.
|
||||
|
||||
@@ -507,14 +507,15 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
|
||||
}
|
||||
const auto image_view = cache.FindDepthTarget(depth.image_id, depth.desc);
|
||||
const auto& metadata = depth.desc.info.metadata;
|
||||
if (metadata.kind == ImageMetadataKind::Htile && depth.depth_clear_enable &&
|
||||
!cache.ClearMeta(metadata.range.address)) {
|
||||
EXIT("failed to acquire HTile metadata for a depth clear\n");
|
||||
}
|
||||
const bool meta_clear =
|
||||
metadata.kind == ImageMetadataKind::Htile &&
|
||||
cache.IsMetaCleared(metadata.range.address, depth.desc.view_info.base_layer);
|
||||
depth.depth_load_clear_enable = depth.depth_clear_enable || meta_clear;
|
||||
depth.load_clear_aspects = {};
|
||||
if (depth.depth_clear_enable || meta_clear)
|
||||
depth.load_clear_aspects |= vk::ImageAspectFlagBits::eDepth;
|
||||
// Apply pending clears to compressed stencil as well as depth.
|
||||
if (depth.stencil_clear_enable || (meta_clear && metadata.stencil_compressed))
|
||||
depth.load_clear_aspects |= vk::ImageAspectFlagBits::eStencil;
|
||||
if (meta_clear &&
|
||||
!cache.TouchMeta(metadata.range.address, depth.desc.view_info.base_layer, false)) {
|
||||
EXIT("failed to consume HTile clear state\n");
|
||||
@@ -567,9 +568,11 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
|
||||
attachment.clear_value[0] = std::bit_cast<uint32_t>(depth.depth_clear_value);
|
||||
attachment.clear_value[1] = depth.stencil_clear_value;
|
||||
attachment.has_depth = static_cast<bool>(aspects & vk::ImageAspectFlagBits::eDepth);
|
||||
attachment.depth_clear = depth.depth_load_clear_enable;
|
||||
attachment.depth_clear =
|
||||
static_cast<bool>(depth.load_clear_aspects & vk::ImageAspectFlagBits::eDepth);
|
||||
attachment.has_stencil = static_cast<bool>(aspects & vk::ImageAspectFlagBits::eStencil);
|
||||
attachment.stencil_clear = depth.stencil_clear_enable;
|
||||
attachment.stencil_clear =
|
||||
static_cast<bool>(depth.load_clear_aspects & vk::ImageAspectFlagBits::eStencil);
|
||||
}
|
||||
if (color_count == 0 && !depth.image_id) {
|
||||
const auto& limits = buffer.GetGraphics().GetPhysicalDeviceProperties().limits;
|
||||
@@ -1060,7 +1063,7 @@ bool RenderExecutor::CopyDepthToColor(CommandBuffer& buffer, const DrawCallInfo&
|
||||
state.ps_input_info.ps_sample_mask_export_enable);
|
||||
vk::ImageAspectFlags feedback;
|
||||
const auto rendering = AcquireRenderTargets(buffer, &color, 1, depth, feedback);
|
||||
if (depth.depth_load_clear_enable) {
|
||||
if (depth.load_clear_aspects) {
|
||||
m_context.GetCommandScheduler().BeginRendering(rendering);
|
||||
}
|
||||
auto& cache = m_context.GetTextureCache();
|
||||
|
||||
@@ -67,6 +67,44 @@ uint32_t EmitExt(EmitterState& state, uint32_t type, uint32_t opcode,
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t EmitF32ToF16RtzBits(EmitterState& state, uint32_t value) {
|
||||
// Native half conversion causes regressions; use compact integer RTZ conversion.
|
||||
const auto bits = EmitBitcastF32ToU32(state, value);
|
||||
const auto abs = EmitAndConstant(state, bits, 0x7fffffffu);
|
||||
const auto sign = EmitAndConstant(
|
||||
state, Binary(state, spv::OpShiftRightLogical, TypeU32(state), bits, ConstantU32(state, 16)),
|
||||
0x8000u);
|
||||
const auto exp = Binary(state, spv::OpShiftRightLogical, TypeU32(state), abs,
|
||||
ConstantU32(state, 23));
|
||||
const auto mant = EmitAndConstant(state, abs, 0x007fffffu);
|
||||
|
||||
// Align the hidden bit to the half subnormal mantissa. Even unused shifts must be < 32.
|
||||
const auto shift = EmitUMin32(
|
||||
state, Binary(state, spv::OpISub, TypeU32(state), ConstantU32(state, 126), exp),
|
||||
ConstantU32(state, 31));
|
||||
const auto sub = Binary(state, spv::OpShiftRightLogical, TypeU32(state),
|
||||
EmitOrU32(state, mant, ConstantU32(state, 0x00800000u)), shift);
|
||||
|
||||
// Drop 13 mantissa bits and rebias the exponent; finite overflow saturates to max half.
|
||||
const auto truncated = Binary(state, spv::OpShiftRightLogical, TypeU32(state), abs,
|
||||
ConstantU32(state, 13));
|
||||
const auto normal = EmitUMin32(
|
||||
state, Binary(state, spv::OpISub, TypeU32(state), truncated, ConstantU32(state, 0x1c000u)),
|
||||
ConstantU32(state, 0x7bffu));
|
||||
const auto finite = Select(state, TypeU32(state),
|
||||
EmitCompareU32Constant(state, spv::OpULessThan, abs, 0x38800000u),
|
||||
sub, normal);
|
||||
const auto nan = EmitOrU32(state, EmitAndConstant(state, truncated, 0x03ffu),
|
||||
ConstantU32(state, 0x7e00u));
|
||||
const auto special = Select(state, TypeU32(state),
|
||||
EmitCompareU32Constant(state, spv::OpIEqual, mant, 0),
|
||||
ConstantU32(state, 0x7c00u), nan);
|
||||
const auto magnitude = Select(
|
||||
state, TypeU32(state),
|
||||
EmitCompareU32Constant(state, spv::OpUGreaterThanEqual, abs, 0x7f800000u), special, finite);
|
||||
return EmitOrU32(state, sign, magnitude);
|
||||
}
|
||||
|
||||
uint32_t EmitF32ToU32(EmitterState& state, uint32_t src, bool signed_value) {
|
||||
const auto trunc = EmitTruncF32Value(state, src);
|
||||
const auto converted_raw = state.builder.AllocateId();
|
||||
@@ -210,23 +248,10 @@ uint32_t EmitCompositeExtractU32x2(EmitterState& state, uint32_t arg0, IR::Value
|
||||
}
|
||||
|
||||
uint32_t EmitPackFloat2x16Rtz(EmitterState& state, uint32_t arg0, uint32_t arg1) {
|
||||
const auto pair = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpCompositeConstruct, TypeF32Vector(state, 2), pair, arg0, arg1);
|
||||
const auto packed = EmitPackHalf2x16(state, pair);
|
||||
const auto back = EmitExt(state, TypeF32Vector(state, 2), GLSLstd450UnpackHalf2x16, {packed});
|
||||
auto result = packed;
|
||||
for (uint32_t i = 0; i < 2; i++) {
|
||||
const auto rounded = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeF32(state), rounded, back, i);
|
||||
const auto above =
|
||||
Binary(state, spv::OpFOrdGreaterThan, TypeBool(state),
|
||||
EmitExt(state, TypeF32(state), GLSLstd450FAbs, {rounded}),
|
||||
EmitExt(state, TypeF32(state), GLSLstd450FAbs, {i == 0 ? arg0 : arg1}));
|
||||
result = Binary(state, spv::OpISub, TypeU32(state), result,
|
||||
Select(state, TypeU32(state), above,
|
||||
ConstantU32(state, i == 0 ? 1u : 0x10000u), ConstantU32(state, 0)));
|
||||
}
|
||||
return result;
|
||||
const auto low = EmitF32ToF16RtzBits(state, arg0);
|
||||
const auto high = Binary(state, spv::OpShiftLeftLogical, TypeU32(state),
|
||||
EmitF32ToF16RtzBits(state, arg1), ConstantU32(state, 16));
|
||||
return EmitOrU32(state, low, high);
|
||||
}
|
||||
|
||||
uint32_t EmitFPSaturate32(EmitterState& state, uint32_t arg0) {
|
||||
|
||||
@@ -326,6 +326,7 @@ inline constexpr auto EmitSharedAtomicSMin32 = EmitAtomic32;
|
||||
inline constexpr auto EmitSharedAtomicUMin32 = EmitAtomic32;
|
||||
inline constexpr auto EmitSharedAtomicSMax32 = EmitAtomic32;
|
||||
inline constexpr auto EmitSharedAtomicUMax64 = EmitSharedAtomic64;
|
||||
inline constexpr auto EmitSharedAtomicCmpSwap32 = EmitAtomic32;
|
||||
inline constexpr auto EmitSharedAtomicCmpSwap64 = EmitSharedAtomic64;
|
||||
inline constexpr auto EmitSharedAtomicUMax32 = EmitAtomic32;
|
||||
inline constexpr auto EmitSharedAtomicAnd32 = EmitAtomic32;
|
||||
|
||||
@@ -420,6 +420,7 @@ void EnsureLdsStorage(EmitterState& state);
|
||||
struct MemoryResourceAccess {
|
||||
IR::ResourceKind kind = IR::ResourceKind::None;
|
||||
uint32_t object_pointer = 0;
|
||||
uint32_t descriptor_index = 0;
|
||||
uint32_t length = 0;
|
||||
uint32_t byte_offset = 0;
|
||||
uint32_t element_bits = 32;
|
||||
|
||||
@@ -1075,6 +1075,63 @@ uint32_t LoadIndirectFormattedX(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t LoadRawBuffer(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
|
||||
const MemoryResourceAccess& resource, uint32_t components) {
|
||||
auto& state = ctx.state;
|
||||
const auto scalar = [&] {
|
||||
std::array<uint32_t, 4> values {};
|
||||
for (uint32_t component = 0; component < components; component++) {
|
||||
values[component] =
|
||||
LoadWordPrepared(ctx, inst, RebaseRawComponent(mem, component), resource);
|
||||
}
|
||||
return ConstructU32Composite(state, components, values);
|
||||
};
|
||||
if (!mem.IsCoherentBufferPairLoad(inst.GetOpcode())) return scalar();
|
||||
|
||||
const auto first = ByteAddress(ctx, inst, mem, resource.byte_offset);
|
||||
const auto second = ByteAddress(ctx, inst, RebaseRawComponent(mem, 1), resource.byte_offset);
|
||||
const auto wide =
|
||||
PrepareStorageBufferResourceAccess(state, mem, state.storage_buffers[3],
|
||||
resource.non_uniform ? resource.descriptor_index : 0u);
|
||||
const auto index =
|
||||
Binary(state, spv::OpShiftRightLogical, TypeU32(state), first, ConstantU32(state, 3));
|
||||
auto atomic =
|
||||
Binary(state, spv::OpIEqual, TypeBool(state),
|
||||
Binary(state, spv::OpBitwiseAnd, TypeU32(state), first, ConstantU32(state, 7)),
|
||||
ConstantU32(state, 0));
|
||||
atomic = AndCondition(state, atomic,
|
||||
Binary(state, spv::OpIEqual, TypeBool(state), second,
|
||||
EmitAddU32(state, first, ConstantU32(state, 4))));
|
||||
// The 64-bit alias requires the entire pair; partial and swizzled reads retain DWORD bounds.
|
||||
atomic = AndCondition(state, atomic, EmitMemoryElementInBounds(state, wide, index));
|
||||
const auto atomic_label = state.builder.AllocateId(), scalar_label = state.builder.AllocateId();
|
||||
const auto merge = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpSelectionMerge, merge, spv::SelectionControlMaskNone);
|
||||
state.builder.AddFunction(spv::OpBranchConditional, atomic, atomic_label, scalar_label);
|
||||
EmitLabel(state, atomic_label);
|
||||
const auto pointer = EmitStorageBufferElementPointer(
|
||||
state, wide, index, TypeStorageBufferElementPointer(state, 64));
|
||||
const auto snapshot = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpAtomicLoad, TypeU64(state), snapshot, pointer,
|
||||
ConstantU32(state, spv::ScopeDevice),
|
||||
ConstantU32(state, spv::MemorySemanticsAcquireMask |
|
||||
spv::MemorySemanticsUniformMemoryMask));
|
||||
const std::array<uint32_t, 4> words {
|
||||
EmitCompositeExtractU64(state, snapshot, IR::Value(0u)),
|
||||
EmitCompositeExtractU64(state, snapshot, IR::Value(1u))};
|
||||
const auto pair = ConstructU32Composite(state, 2, words);
|
||||
state.builder.AddFunction(spv::OpBranch, merge);
|
||||
EmitLabel(state, scalar_label);
|
||||
const auto fallback = scalar();
|
||||
const auto scalar_exit = state.current_label;
|
||||
state.builder.AddFunction(spv::OpBranch, merge);
|
||||
EmitLabel(state, merge);
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpPhi, TypeU32Composite(state, 2), result, pair, atomic_label,
|
||||
fallback, scalar_exit);
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t LoadBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t components) {
|
||||
auto& state = ctx.state;
|
||||
const auto mem = ctx.Memory(inst);
|
||||
@@ -1100,12 +1157,7 @@ uint32_t LoadBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t compon
|
||||
return LoadFormattedPrepared(ctx, inst, selected, resource, info, components);
|
||||
}
|
||||
if (resource.object_pointer == 0u) return ConstantU32CompositeZero(state, components);
|
||||
std::array<uint32_t, 4> values {};
|
||||
for (uint32_t component = 0; component < components; component++) {
|
||||
values[component] =
|
||||
LoadWordPrepared(ctx, inst, RebaseRawComponent(selected, component), resource);
|
||||
}
|
||||
return ConstructU32Composite(state, components, values);
|
||||
return LoadRawBuffer(ctx, inst, selected, resource, components);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -207,10 +207,11 @@ MemoryResourceAccess PrepareStorageBufferResourceAccess(EmitterState& state,
|
||||
const bool non_uniform = array_index != 0u;
|
||||
const auto static_index = state.program.info.buffers.at(mem.resource).descriptor_index;
|
||||
if (!non_uniform) array_index = ConstantU32(state, static_index);
|
||||
MemoryResourceAccess access {
|
||||
.kind = mem.kind,
|
||||
.non_uniform = non_uniform,
|
||||
.memory_access = mem.coherent ? spv::MemoryAccessVolatileMask : spv::MemoryAccessMaskNone};
|
||||
MemoryResourceAccess access {.kind = mem.kind,
|
||||
.descriptor_index = array_index,
|
||||
.non_uniform = non_uniform,
|
||||
.memory_access = mem.coherent ? spv::MemoryAccessVolatileMask
|
||||
: spv::MemoryAccessMaskNone};
|
||||
access.object_pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpAccessChain, buffer.pointer_type, access.object_pointer,
|
||||
buffer.variable, array_index);
|
||||
@@ -504,6 +505,7 @@ uint32_t EncodePackedColorComponent(EmitterState& state, const Format::BufferFor
|
||||
|
||||
spv::Op SpirvAtomicOpcode(IR::ValueOpcode opcode) {
|
||||
switch (opcode) {
|
||||
case IR::ValueOpcode::SharedAtomicCmpSwap32:
|
||||
case IR::ValueOpcode::SharedAtomicCmpSwap64:
|
||||
case IR::ValueOpcode::ImageAtomicCompareSwap32:
|
||||
case IR::ValueOpcode::BufferAtomicCmpSwap32: return spv::OpAtomicCompareExchange;
|
||||
@@ -561,8 +563,12 @@ uint32_t EmitAtomicOperation(ValueEmitContext& ctx, const IR::Inst& inst, uint32
|
||||
const bool wide = inst.GetType() == IR::Type::U64;
|
||||
const auto type = wide ? TypeU64(ctx.state) : TypeU32(ctx.state);
|
||||
if (opcode == spv::OpAtomicCompareExchange) {
|
||||
const auto desired = ctx.Arg(inst, inst.NumArgs() - 3);
|
||||
const auto comparator = ctx.Arg(inst, inst.NumArgs() - 2);
|
||||
auto desired = ctx.Arg(inst, inst.NumArgs() - 3);
|
||||
auto comparator = ctx.Arg(inst, inst.NumArgs() - 2);
|
||||
if (inst.GetOpcode() == IR::ValueOpcode::SharedAtomicCmpSwap32) {
|
||||
// RDNA2 DS compares DATA and stores DATA2, opposite to BUFFER atomics.
|
||||
std::swap(desired, comparator);
|
||||
}
|
||||
ctx.state.builder.AddFunction(
|
||||
spv::OpAtomicCompareExchange, TypeU32(ctx.state), old, pointer,
|
||||
ConstantU32(ctx.state, scope), ConstantU32(ctx.state, spv::MemorySemanticsMaskNone),
|
||||
|
||||
@@ -115,6 +115,7 @@ constexpr MemoryOpcodeInfo DS_OPCODE_LIST[] = {
|
||||
{0x27u, Opcode::DS_MIN_RTN_U32, 1, 32}, {0x28u, Opcode::DS_MAX_RTN_U32, 1, 32},
|
||||
{0x29u, Opcode::DS_AND_RTN_B32, 1, 32}, {0x2au, Opcode::DS_OR_RTN_B32, 1, 32},
|
||||
{0x2bu, Opcode::DS_XOR_RTN_B32, 1, 32}, {0x2du, Opcode::DS_WRXCHG_RTN_B32, 1, 32},
|
||||
{0x30u, Opcode::DS_CMPST_RTN_B32, 1, 32},
|
||||
{0x35u, Opcode::DS_SWIZZLE_B32, 1, 32}, {0x36u, Opcode::DS_READ_B32, 1, 32},
|
||||
{0x37u, Opcode::DS_READ2_B32, 2, 32}, {0x38u, Opcode::DS_READ2ST64_B32, 2, 32},
|
||||
{0x39u, Opcode::DS_READ_I8, 1, 8, true}, {0x3au, Opcode::DS_READ_U8, 1, 8},
|
||||
@@ -201,6 +202,7 @@ bool IsDsAtomicOpcode(Opcode opcode) {
|
||||
case Opcode::DS_MAX_U32:
|
||||
case Opcode::DS_MAX_RTN_U32:
|
||||
case Opcode::DS_MAX_RTN_U64:
|
||||
case Opcode::DS_CMPST_RTN_B32:
|
||||
case Opcode::DS_CMPST_RTN_B64:
|
||||
case Opcode::DS_AND_B32:
|
||||
case Opcode::DS_AND_RTN_B32:
|
||||
@@ -221,6 +223,7 @@ uint32_t DsSourceCount(Opcode opcode) {
|
||||
case Opcode::DS_WRITE2ST64_B32:
|
||||
case Opcode::DS_WRITE2_B64:
|
||||
case Opcode::DS_WRITE2ST64_B64:
|
||||
case Opcode::DS_CMPST_RTN_B32:
|
||||
case Opcode::DS_CMPST_RTN_B64:
|
||||
case Opcode::DS_MSKOR_B32: return 3u;
|
||||
case Opcode::DS_MIN_F32:
|
||||
@@ -455,6 +458,9 @@ void DecodeDs(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
if (inst.opcode == Opcode::DS_SWIZZLE_B32 && inst.offset >= 0xe000u) {
|
||||
SetUnsupported(inst, Family::DS, opcode, "DS swizzle FFT mode is not implemented");
|
||||
}
|
||||
if (inst.gds && inst.opcode == Opcode::DS_CMPST_RTN_B32) {
|
||||
SetUnsupported(inst, Family::DS, opcode, "DS compare-store return B32 GDS variant is not implemented");
|
||||
}
|
||||
if (inst.gds &&
|
||||
(inst.opcode == Opcode::DS_SWIZZLE_B32 || inst.opcode == Opcode::DS_PERMUTE_B32 ||
|
||||
inst.opcode == Opcode::DS_BPERMUTE_B32 ||
|
||||
|
||||
@@ -718,6 +718,7 @@ std::string InstructionToString(const Instruction& inst) {
|
||||
case Opcode::DS_MAX_U32:
|
||||
case Opcode::DS_MAX_RTN_U32:
|
||||
case Opcode::DS_MAX_RTN_U64:
|
||||
case Opcode::DS_CMPST_RTN_B32:
|
||||
case Opcode::DS_CMPST_RTN_B64:
|
||||
case Opcode::DS_AND_B32:
|
||||
case Opcode::DS_AND_RTN_B32:
|
||||
|
||||
@@ -632,6 +632,7 @@ enum class Opcode {
|
||||
DS_MAX_U32,
|
||||
DS_MAX_RTN_U32,
|
||||
DS_MAX_RTN_U64,
|
||||
DS_CMPST_RTN_B32,
|
||||
DS_CMPST_RTN_B64,
|
||||
DS_AND_B32,
|
||||
DS_AND_RTN_B32,
|
||||
|
||||
@@ -27,7 +27,7 @@ void Translator::PackedFloat16(const Decoder::Instruction& inst, IR::ValueOpcode
|
||||
if (quiet_snan) {
|
||||
const auto quiet_snan_lane = [&](const Decoder::Operand& operand, bool high) {
|
||||
const auto bits = ReadU16LaneAsU32(operand, high, false);
|
||||
const auto exponent = ir.BitwiseAnd(bits, IR::U32(IR::Value(0x7c00u)));
|
||||
const auto exponent = ir.BitwiseAnd(bits, IR::U32(IR::Value(0x7e00u)));
|
||||
const auto payload = ir.BitwiseAnd(bits, IR::U32(IR::Value(0x01ffu)));
|
||||
const auto snan = ir.LogicalAnd(ir.IEqual(exponent, IR::U32(IR::Value(0x7c00u))),
|
||||
ir.INotEqual(payload, IR::U32(IR::Value(0u))));
|
||||
|
||||
@@ -1085,6 +1085,8 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicUMax32, true);
|
||||
case Decoder::Opcode::DS_MAX_RTN_U64:
|
||||
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicUMax64, true);
|
||||
case Decoder::Opcode::DS_CMPST_RTN_B32:
|
||||
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicCmpSwap32, true);
|
||||
case Decoder::Opcode::DS_CMPST_RTN_B64:
|
||||
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicCmpSwap64, true);
|
||||
case Decoder::Opcode::DS_AND_B32:
|
||||
|
||||
@@ -233,13 +233,6 @@ IR::Value Translator::ReadOperand(const Decoder::Operand& operand, IR::Type type
|
||||
if (type == IR::Type::F64) {
|
||||
if (operand.kind == Decoder::OperandKind::LiteralConstant) {
|
||||
pair = {IR::U32(IR::Value(0u)), IR::U32(IR::Value(operand.value))};
|
||||
} else if (operand.kind == Decoder::OperandKind::FloatInlineConstant) {
|
||||
const auto bits = operand.value == 0x3e22f983u
|
||||
? 0x3fc45f306dc9c882ull
|
||||
: std::bit_cast<uint64_t>(static_cast<double>(
|
||||
std::bit_cast<float>(operand.value)));
|
||||
pair = {IR::U32(IR::Value(static_cast<uint32_t>(bits))),
|
||||
IR::U32(IR::Value(static_cast<uint32_t>(bits >> 32u)))};
|
||||
}
|
||||
if (operand.absolute) {
|
||||
pair[1] = ir.BitwiseAnd(pair[1], IR::U32(IR::Value(0x7fffffffu)));
|
||||
@@ -488,6 +481,14 @@ std::array<IR::U32, 2> Translator::ReadU32Pair(const Decoder::Operand& operand)
|
||||
if (operand.kind == Decoder::OperandKind::VccLo) {
|
||||
return {ir.GetVccLo(), ir.GetVccHi()};
|
||||
}
|
||||
if (operand.kind == Decoder::OperandKind::FloatInlineConstant) {
|
||||
const auto bits =
|
||||
operand.value == 0x3e22f983u
|
||||
? 0x3fc45f306dc9c882ull
|
||||
: std::bit_cast<uint64_t>(static_cast<double>(std::bit_cast<float>(operand.value)));
|
||||
return {IR::U32(IR::Value(static_cast<uint32_t>(bits))),
|
||||
IR::U32(IR::Value(static_cast<uint32_t>(bits >> 32u)))};
|
||||
}
|
||||
const auto low = ApplyBitSourceModifiers(operand, ReadRawU32(operand));
|
||||
IR::U32 high(IR::Value(0u));
|
||||
if (operand.kind == Decoder::OperandKind::Sgpr || operand.kind == Decoder::OperandKind::Vgpr) {
|
||||
@@ -546,9 +547,11 @@ IR::U32 Translator::Read16LaneBits(const Decoder::Operand& operand, bool high_la
|
||||
auto bits = ReadRawU32(operand);
|
||||
if (operand.kind == Decoder::OperandKind::FloatInlineConstant) {
|
||||
// Inline floats encode the operand's width before SDWA or OP_SEL selects bits.
|
||||
const auto half = IR::F16(ir.Emit(IR::ValueOpcode::ConvertF16F32, {ir.BitCastF32(bits)}));
|
||||
const auto half_bits = IR::U16(ir.Emit(IR::ValueOpcode::BitCastU16F16, {half}));
|
||||
bits = IR::U32(ir.Emit(IR::ValueOpcode::ConvertU32U16, {half_bits}));
|
||||
const auto value = operand.value;
|
||||
bits = IR::U32(IR::Value(value == 0x3e22f983u
|
||||
? 0x3118u
|
||||
: ((value >> 16u) & 0x8000u) |
|
||||
((((value >> 23u) & 0xffu) - 112u) << 10u)));
|
||||
}
|
||||
bits = ApplyBitSourceModifiers(raw_operand, bits);
|
||||
uint32_t offset = (high_lane ? operand.op_sel_hi : operand.op_sel) ? 16u : 0u;
|
||||
|
||||
@@ -82,6 +82,11 @@ struct MemoryInfo {
|
||||
bool coherent = false;
|
||||
bool planning_only = false;
|
||||
|
||||
[[nodiscard]] bool IsCoherentBufferPairLoad(ValueOpcode opcode) const {
|
||||
return kind == ResourceKind::Buffer && opcode == ValueOpcode::LoadBufferU32x2 && coherent &&
|
||||
!typed && !formatted && data_bits == 32u;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool SupportsIndirectBufferLoad(ValueOpcode opcode) const {
|
||||
if (typed) return false;
|
||||
if (data_bits == 8u) return !formatted && opcode == ValueOpcode::LoadBufferU8;
|
||||
|
||||
@@ -178,6 +178,7 @@ SharedAccess SharedAccessOf(ValueOpcode opcode) {
|
||||
case ValueOpcode::SharedAtomicSMax32:
|
||||
case ValueOpcode::SharedAtomicUMax32:
|
||||
case ValueOpcode::SharedAtomicUMax64:
|
||||
case ValueOpcode::SharedAtomicCmpSwap32:
|
||||
case ValueOpcode::SharedAtomicCmpSwap64:
|
||||
case ValueOpcode::SharedAtomicAnd32:
|
||||
case ValueOpcode::SharedAtomicOr32:
|
||||
|
||||
@@ -307,6 +307,7 @@ VALUE_OPCODE(SharedAtomicUMin32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(SharedAtomicSMax32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(SharedAtomicUMax32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(SharedAtomicUMax64, U64, U32, U64, U1)
|
||||
VALUE_OPCODE(SharedAtomicCmpSwap32, U32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(SharedAtomicCmpSwap64, U64, U32, U64, U64, U1)
|
||||
VALUE_OPCODE(SharedAtomicAnd32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(SharedAtomicOr32, U32, U32, U32, U1)
|
||||
|
||||
@@ -51,11 +51,13 @@ public:
|
||||
}
|
||||
|
||||
void QueueUsers(const std::vector<Use>& users) {
|
||||
++generation;
|
||||
affected.clear();
|
||||
for (const auto& use: users) affected.push_back(use.user);
|
||||
}
|
||||
|
||||
void FlushUsers() {
|
||||
++generation;
|
||||
// Pattern folds inspect nested operands even when intermediate operations
|
||||
// stay dynamic. Visit consumers once per change, including cyclic Phi edges.
|
||||
// stay dynamic. Visit their union once per drain, including cyclic Phi edges.
|
||||
for (size_t i = 0; i < affected.size(); ++i) {
|
||||
auto* inst = affected[i];
|
||||
const auto found = locations.find(inst);
|
||||
@@ -64,6 +66,7 @@ public:
|
||||
Queue(inst);
|
||||
for (const auto& use: inst->Uses()) affected.push_back(use.user);
|
||||
}
|
||||
affected.clear();
|
||||
}
|
||||
|
||||
Block::iterator Prepend(Block& block, Block::iterator before, ValueOpcode opcode,
|
||||
@@ -977,27 +980,30 @@ void ConstantPropagationPass(const BlockList& blocks, uint32_t wave_size) {
|
||||
std::vector<Use> users;
|
||||
std::vector<Value> operands;
|
||||
const auto drain = [&] {
|
||||
for (size_t i = 0; i < worklist.pending.size(); ++i) {
|
||||
auto* inst = worklist.pending[i];
|
||||
auto& location = worklist.locations.at(inst);
|
||||
location.queued = false;
|
||||
const auto opcode = inst->GetOpcode();
|
||||
if (opcode == ValueOpcode::Identity || opcode == ValueOpcode::Void) continue;
|
||||
const auto where = location.instruction;
|
||||
users.assign(inst->Uses().begin(), inst->Uses().end());
|
||||
operands.clear();
|
||||
for (size_t arg = 0; arg < inst->NumArgs(); ++arg) operands.push_back(inst->Arg(arg));
|
||||
mask_projection.Fold(*inst, wave_size);
|
||||
FoldInstruction(*inst->Parent(), where, worklist);
|
||||
bool changed = opcode != inst->GetOpcode() || operands.size() != inst->NumArgs();
|
||||
for (size_t arg = 0; !changed && arg < operands.size(); ++arg)
|
||||
changed = operands[arg] != inst->Arg(arg);
|
||||
if (changed) {
|
||||
worklist.QueueUsers(users);
|
||||
worklist.Queue(inst);
|
||||
do {
|
||||
for (size_t i = 0; i < worklist.pending.size(); ++i) {
|
||||
auto* inst = worklist.pending[i];
|
||||
auto& location = worklist.locations.at(inst);
|
||||
location.queued = false;
|
||||
const auto opcode = inst->GetOpcode();
|
||||
if (opcode == ValueOpcode::Identity || opcode == ValueOpcode::Void) continue;
|
||||
const auto where = location.instruction;
|
||||
users.assign(inst->Uses().begin(), inst->Uses().end());
|
||||
operands.clear();
|
||||
for (size_t arg = 0; arg < inst->NumArgs(); ++arg) operands.push_back(inst->Arg(arg));
|
||||
mask_projection.Fold(*inst, wave_size);
|
||||
FoldInstruction(*inst->Parent(), where, worklist);
|
||||
bool changed = opcode != inst->GetOpcode() || operands.size() != inst->NumArgs();
|
||||
for (size_t arg = 0; !changed && arg < operands.size(); ++arg)
|
||||
changed = operands[arg] != inst->Arg(arg);
|
||||
if (changed) {
|
||||
worklist.QueueUsers(users);
|
||||
worklist.Queue(inst);
|
||||
}
|
||||
}
|
||||
}
|
||||
worklist.pending.clear();
|
||||
worklist.pending.clear();
|
||||
worklist.FlushUsers();
|
||||
} while (!worklist.pending.empty());
|
||||
};
|
||||
drain();
|
||||
// Normalize retained PHI/select values only after every supported field read has
|
||||
|
||||
@@ -1409,7 +1409,8 @@ ResourcePlan ExtractResourcePlan(const Program& program) {
|
||||
}
|
||||
if (op != ValueOpcode::LogicalAnd && op != ValueOpcode::LogicalOr &&
|
||||
op != ValueOpcode::LogicalNot && op != ValueOpcode::IEqual32 &&
|
||||
op != ValueOpcode::INotEqual32 && op != ValueOpcode::ULessThanEqual32 &&
|
||||
op != ValueOpcode::INotEqual32 && op != ValueOpcode::ULessThan32 &&
|
||||
op != ValueOpcode::ULessThanEqual32 &&
|
||||
op != ValueOpcode::SelectU1 && op != ValueOpcode::SelectU32) return finish(opaque());
|
||||
auto& node = plan.value_storage.emplace_back(op);
|
||||
for (size_t index = 0; index < inst->NumArgs(); ++index)
|
||||
|
||||
@@ -4047,18 +4047,18 @@ UniformFillPlan RecognizePatternFill(Program& program, const Decoder::Program& d
|
||||
struct Pattern {
|
||||
size_t code_words;
|
||||
uint64_t hash;
|
||||
uint32_t wave_size;
|
||||
uint32_t wave_size_mask;
|
||||
bool bounded;
|
||||
};
|
||||
constexpr std::array patterns {
|
||||
// Bounded 64-lane variant.
|
||||
Pattern {.code_words = 69, .hash = 0x0704b9818e3cba06ull, .wave_size = 64, .bounded = true},
|
||||
Pattern {.code_words = 69, .hash = 0x0704b9818e3cba06ull, .wave_size_mask = 64, .bounded = true},
|
||||
// Dispatch thread dimensions, 64-lane variants.
|
||||
Pattern {.code_words = 42, .hash = 0x92ce57f21310ef02ull, .wave_size = 64, .bounded = false},
|
||||
Pattern {.code_words = 40, .hash = 0x374161027f58bee7ull, .wave_size = 64, .bounded = false},
|
||||
Pattern {.code_words = 40, .hash = 0x7d7172c56c2e296dull, .wave_size = 64, .bounded = false},
|
||||
// Dispatch thread dimensions, 32-lane variant.
|
||||
Pattern {.code_words = 38, .hash = 0xacbda1a1ccdd4198ull, .wave_size = 32, .bounded = false},
|
||||
Pattern {.code_words = 42, .hash = 0x92ce57f21310ef02ull, .wave_size_mask = 64, .bounded = false},
|
||||
Pattern {.code_words = 40, .hash = 0x374161027f58bee7ull, .wave_size_mask = 64, .bounded = false},
|
||||
Pattern {.code_words = 40, .hash = 0x7d7172c56c2e296dull, .wave_size_mask = 64, .bounded = false},
|
||||
// Dispatch thread dimensions, valid in both wave sizes.
|
||||
Pattern {.code_words = 38, .hash = 0xacbda1a1ccdd4198ull, .wave_size_mask = 32 | 64, .bounded = false},
|
||||
};
|
||||
if (program.stage != ShaderType::Compute || input.compute == nullptr ||
|
||||
decoded.instructions.empty() || program.user_data_base != 0 || program.user_data_count < 10 ||
|
||||
@@ -4078,7 +4078,7 @@ UniformFillPlan RecognizePatternFill(Program& program, const Decoder::Program& d
|
||||
const auto pattern = std::ranges::find_if(patterns, [&](const Pattern& candidate) {
|
||||
return candidate.code_words == words && candidate.hash == hash;
|
||||
});
|
||||
if (pattern == patterns.end() || program.wave_size != pattern->wave_size ||
|
||||
if (pattern == patterns.end() || (program.wave_size & pattern->wave_size_mask) == 0 ||
|
||||
cs.dispatch_thread_dimensions != !pattern->bounded) return {};
|
||||
std::array<Value, 10> userdata;
|
||||
for (auto* block: program.blocks) {
|
||||
|
||||
@@ -340,6 +340,7 @@ void Visit(Program& program, ShaderStageInputInfo input_info, InputUsage& inputs
|
||||
}
|
||||
if (kind == ResourceKind::Buffer) {
|
||||
info.coherent_buffers |= memory.coherent;
|
||||
info.buffer_int64_atomics |= memory.IsCoherentBufferPairLoad(op);
|
||||
if (memory.resource >= info.buffers.size()) {
|
||||
return Fail("buffer operation has invalid resource metadata");
|
||||
}
|
||||
|
||||
@@ -1054,7 +1054,13 @@ bool SrtWalker::EvaluateInst(const Inst& inst, uint64_t& result) {
|
||||
}
|
||||
return false;
|
||||
case ValueOpcode::ULessThan32:
|
||||
if (binary()) {
|
||||
// No U32 is below zero, including an unknown loop-carried value.
|
||||
if (!Arg(inst, 1, b)) return false;
|
||||
if (static_cast<uint32_t>(b) == 0u) {
|
||||
result = 0u;
|
||||
return true;
|
||||
}
|
||||
if (Arg(inst, 0, a)) {
|
||||
result = static_cast<uint32_t>(a) < static_cast<uint32_t>(b);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -583,16 +583,16 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context&
|
||||
mesh.input_primitive, sh.m_vgtGsOutPrimType, sh.m_vgtGsMaxVertOut,
|
||||
group.primitive_group_size, group.vertex_group_size, mesh.max_vertices);
|
||||
}
|
||||
mesh.max_primitives = mesh.fast_launch ? mesh.max_vertices :
|
||||
native_ngg ? mesh.InputPrimitiveCount(mesh.max_vertices) :
|
||||
group.primitive_group_size * (sh.m_vgtGsMaxVertOut - 2u);
|
||||
mesh.primitives_per_group = std::min({static_cast<uint32_t>(group.primitive_group_size),
|
||||
mesh.InputPrimitiveCount(group.vertex_group_size),
|
||||
native_ngg ? mesh.max_primitives :
|
||||
native_ngg ? mesh.InputPrimitiveCount(mesh.max_vertices) :
|
||||
mesh.max_vertices / sh.m_vgtGsMaxVertOut});
|
||||
EXIT_IF(mesh.primitives_per_group == 0u);
|
||||
mesh.vertices_per_group = mesh.InputVertexCount(mesh.primitives_per_group);
|
||||
const auto threads = native_ngg && !mesh.fast_launch ? mesh.vertices_per_group : mesh.max_vertices;
|
||||
mesh.max_primitives = mesh.primitives_per_group * (sh.m_geNggSubgrpCntl & 0x1ffu);
|
||||
const auto threads = std::max(native_ngg && !mesh.fast_launch ? mesh.vertices_per_group
|
||||
: mesh.max_vertices,
|
||||
mesh.max_primitives);
|
||||
mesh.threads_num[0] = ((threads + mesh.wave_size - 1u) / mesh.wave_size) * mesh.wave_size;
|
||||
mesh.threads_num[1] = mesh.threads_num[2] = 1u;
|
||||
return params;
|
||||
|
||||
@@ -338,14 +338,20 @@ static bool HasWindowsForbiddenFilenameCharacter(const std::string& relative_pat
|
||||
std::filesystem::path MountPoints::ResolvePath(const std::string& mounted_name) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
// Guests join mount points and names with extra slashes (e.g. "/addcont0//file").
|
||||
std::string normalized = Common::FixFilenameSlash(mounted_name);
|
||||
normalized.erase(std::unique(normalized.begin(), normalized.end(),
|
||||
[](char a, char b) { return a == '/' && b == '/'; }),
|
||||
normalized.end());
|
||||
|
||||
// Match the entire guest path so a mount root works with or without a trailing slash.
|
||||
const auto mounted_path = Common::FixDirectorySlash(mounted_name);
|
||||
const auto mounted_path = Common::FixDirectorySlash(normalized);
|
||||
const auto it = std::find_if(
|
||||
m_mount_pairs.begin(), m_mount_pairs.end(),
|
||||
[&mounted_path](const MountPair& p) { return mounted_path.starts_with(p.point); });
|
||||
if (it != m_mount_pairs.end()) {
|
||||
const auto& p = *it;
|
||||
auto rel_path = Common::RemoveFirst(Common::FixFilenameSlash(mounted_name), p.point.size());
|
||||
auto rel_path = Common::RemoveFirst(normalized, p.point.size());
|
||||
while (rel_path.starts_with('/')) {
|
||||
rel_path = Common::RemoveFirst(rel_path, 1);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,9 @@
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <atomic>
|
||||
#include <cinttypes>
|
||||
#include <iterator>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
@@ -176,6 +179,14 @@ static int GetHostPathStat(const std::filesystem::path& host_path, LibKernel::Fi
|
||||
return OK;
|
||||
}
|
||||
|
||||
// Mounts can appear after a failed lookup (e.g. additional content), so forget negative results.
|
||||
void InvalidateFailedResolves() {
|
||||
std::scoped_lock lock(g_mutex);
|
||||
for (auto it = g_resolved_paths.begin(); it != g_resolved_paths.end();) {
|
||||
it = it->second.result != OK ? g_resolved_paths.erase(it) : std::next(it);
|
||||
}
|
||||
}
|
||||
|
||||
static int ResolveOnePath(const char* guest_path, uint32_t* id, uint64_t* size) {
|
||||
if (guest_path == nullptr || guest_path[0] == '\0') {
|
||||
return LibKernel::KERNEL_ERROR_EINVAL;
|
||||
@@ -225,6 +236,10 @@ static int ResolveOnePath(const char* guest_path, uint32_t* id, uint64_t* size)
|
||||
log_missing = true;
|
||||
}
|
||||
}
|
||||
if (!log_missing && info.result == OK && path.starts_with("/addcont")) {
|
||||
LOGF("\tAPR resolve: %s -> %s (%" PRIu64 " bytes)\n", guest_path,
|
||||
Common::PathToString(info.host_path).c_str(), info.file_size);
|
||||
}
|
||||
if (log_missing) {
|
||||
LOGF("\tAPR resolve missing path: %s -> %s\n", guest_path, Common::PathToString(info.host_path).c_str());
|
||||
}
|
||||
@@ -1181,6 +1196,13 @@ static int ExecuteCommand(const CommandBufferState::Command& entry) {
|
||||
uint64_t bytes_read = 0;
|
||||
auto result = ReadHostFileToGuest(host_path, command.file_offset, command.destination,
|
||||
command.size, &bytes_read);
|
||||
static std::atomic<uint32_t> s_logged_dlc_reads {0};
|
||||
if (host_path.filename() == "re_dlc_000.pak" && s_logged_dlc_reads.fetch_add(1) < 32) {
|
||||
LOGF("\tAPR read dlc: id=0x%08" PRIx32 ", offset=0x%016" PRIx64 ", size=0x%016" PRIx64
|
||||
", read=0x%016" PRIx64 ", result=0x%08" PRIx32 "\n",
|
||||
command.file_id, command.file_offset, command.size, bytes_read,
|
||||
static_cast<uint32_t>(result));
|
||||
}
|
||||
if (result != OK) {
|
||||
LOGF("\tAPR submit read failed: id=0x%08" PRIx32 ", result=0x%08" PRIx32 ", path=%s\n",
|
||||
command.file_id, static_cast<uint32_t>(result), Common::PathToString(host_path).c_str());
|
||||
|
||||
@@ -22,13 +22,16 @@
|
||||
|
||||
namespace Libs {
|
||||
|
||||
namespace AprShared {
|
||||
void InvalidateFailedResolves();
|
||||
}
|
||||
|
||||
LIB_VERSION("AppContent", 1, "AppContentUtil", 1, 1);
|
||||
|
||||
namespace AppContent {
|
||||
|
||||
constexpr uint32_t APP_CONTENT_APPPARAM_SKU_FLAG_FULL = 3;
|
||||
constexpr int APP_CONTENT_ERROR_PARAMETER = -2133262334; /* 0x80D90002 */
|
||||
constexpr int APP_CONTENT_ERROR_BUSY = -2133262333; /* 0x80D90003 */
|
||||
constexpr int APP_CONTENT_ERROR_NOT_MOUNTED = -2133262332; /* 0x80D90004 */
|
||||
constexpr int APP_CONTENT_ERROR_NOT_FOUND = -2133262331; /* 0x80D90005 */
|
||||
constexpr int APP_CONTENT_ERROR_MOUNT_FULL = -2133262330; /* 0x80D90006 */
|
||||
@@ -207,7 +210,10 @@ int KYTY_SYSV_ABI AppContentAddcontMount(uint32_t servic
|
||||
|
||||
for (const auto& m: g_addcont_mounts) {
|
||||
if (m.label == entry->entitlement_label) {
|
||||
return APP_CONTENT_ERROR_BUSY;
|
||||
const auto guest_point = m.point.substr(0, m.point.size() - 1);
|
||||
std::strncpy(mount_point->data, guest_point.c_str(), sizeof(mount_point->data) - 1);
|
||||
LOGF("\t already mounted %s\n", guest_point.c_str());
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,7 +238,15 @@ int KYTY_SYSV_ABI AppContentAddcontMount(uint32_t servic
|
||||
LOGF("\t mounted %s -> %s\n", mount.point.c_str(),
|
||||
Common::PathToString(entry->mount_dir).c_str());
|
||||
|
||||
std::strncpy(mount_point->data, mount.point.c_str(), sizeof(mount_point->data) - 1);
|
||||
AprShared::InvalidateFailedResolves();
|
||||
|
||||
// Titles scan /download0/pdlc for downloaded content; it must exist even when empty.
|
||||
if (const auto pdlc = LibKernel::FileSystem::GetRealFilename("/download0/pdlc"); !pdlc.empty()) {
|
||||
std::filesystem::create_directories(pdlc, ec);
|
||||
}
|
||||
|
||||
const auto guest_point = mount.point.substr(0, mount.point.size() - 1);
|
||||
std::strncpy(mount_point->data, guest_point.c_str(), sizeof(mount_point->data) - 1);
|
||||
g_addcont_mounts.push_back(std::move(mount));
|
||||
|
||||
return OK;
|
||||
@@ -251,8 +265,8 @@ int KYTY_SYSV_ABI AppContentAddcontUnmount(const AppContentMountPoint* mount_poi
|
||||
return APP_CONTENT_ERROR_NOT_MOUNTED;
|
||||
}
|
||||
|
||||
const std::string point(mount_point->data,
|
||||
strnlen(mount_point->data, sizeof(mount_point->data)));
|
||||
const std::string point = Common::FixDirectorySlash(
|
||||
std::string_view(mount_point->data, strnlen(mount_point->data, sizeof(mount_point->data))));
|
||||
|
||||
std::lock_guard lock(g_addcont_mutex);
|
||||
|
||||
@@ -263,6 +277,7 @@ int KYTY_SYSV_ABI AppContentAddcontUnmount(const AppContentMountPoint* mount_poi
|
||||
}
|
||||
LibKernel::FileSystem::Umount(point);
|
||||
g_addcont_mounts.erase(it);
|
||||
AprShared::InvalidateFailedResolves();
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
@@ -2006,6 +2006,11 @@ int KYTY_SYSV_ABI listen(int s, int backlog) {
|
||||
return Network::Net::Listen(s, backlog);
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI shutdown(int s, int how) {
|
||||
PRINT_NAME();
|
||||
return Network::Net::Shutdown(s, how);
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI accept(int s, void* addr, uint32_t* addrlen) {
|
||||
PRINT_NAME();
|
||||
return Network::Net::Accept(s, addr, addrlen);
|
||||
@@ -2242,6 +2247,7 @@ LIB_DEFINE(InitLibKernel_1_Posix) {
|
||||
LIB_FUNC("KuOmgKoqCdY", Posix::bind);
|
||||
LIB_FUNC("XVL8So3QJUk", Posix::connect);
|
||||
LIB_FUNC("pxnCmagrtao", Posix::listen);
|
||||
LIB_FUNC("TUuiYS2kE8s", Posix::shutdown);
|
||||
LIB_FUNC("3e+4Iv7IJ8U", Posix::accept);
|
||||
LIB_FUNC("RenI1lL1WFk", Posix::getsockname);
|
||||
LIB_FUNC("6O8EwYOgH9Y", Posix::getsockopt);
|
||||
|
||||
@@ -221,11 +221,17 @@ int KYTY_SYSV_ABI PlayGoGetChunkId(int handle, uint16_t* out_chunk_id_list,
|
||||
if (out_entries == nullptr) {
|
||||
return PLAYGO_ERROR_BAD_POINTER;
|
||||
}
|
||||
if (number_of_entries != 0 && out_chunk_id_list == nullptr) {
|
||||
return PLAYGO_ERROR_BAD_POINTER;
|
||||
if (out_chunk_id_list != nullptr && number_of_entries == 0) {
|
||||
return PLAYGO_ERROR_BAD_SIZE;
|
||||
}
|
||||
ensure_chunks_loaded();
|
||||
|
||||
// A null output buffer asks for the total chunk count.
|
||||
if (out_chunk_id_list == nullptr) {
|
||||
*out_entries = g_chunks_num;
|
||||
return OK;
|
||||
}
|
||||
|
||||
const uint32_t entries = std::min(number_of_entries, g_chunks_num);
|
||||
for (uint32_t i = 0; i < entries; i++) {
|
||||
out_chunk_id_list[i] = static_cast<uint16_t>(i);
|
||||
|
||||
@@ -258,6 +258,10 @@ static int KYTY_SYSV_ABI SystemServicePowerTick() {
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI SystemServiceDisableMusicPlayer() {
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI SystemServiceReportAbnormalTermination(const void* info) {
|
||||
PRINT_NAME();
|
||||
|
||||
@@ -577,6 +581,7 @@ LIB_DEFINE(InitSystemService_1) {
|
||||
LIB_FUNC("8Lo6Zv94aho", SystemService::SystemServiceDisableNoticeScreenSkipFlagAutoSet);
|
||||
LIB_FUNC("Q3utJvma4Mo", SystemService::SystemServiceSetNoticeScreenSkipFlag);
|
||||
LIB_FUNC("XbbJC3E+L5M", SystemService::SystemServicePowerTick);
|
||||
LIB_FUNC("x1UB9bwDSOw", SystemService::SystemServiceDisableMusicPlayer);
|
||||
LIB_FUNC("3s8cHiCBKBE", SystemService::SystemServiceReportAbnormalTermination);
|
||||
SystemGesture::InitSystemGesture_1(s);
|
||||
}
|
||||
|
||||
+26
-13
@@ -891,6 +891,7 @@ struct P2pEndpoint {
|
||||
uint16_t virtual_port = 0;
|
||||
int nonblocking = 0;
|
||||
int broadcast = 0;
|
||||
int use_crypto = 0;
|
||||
int send_buffer = 9216;
|
||||
int receive_buffer = 40 * 1024;
|
||||
};
|
||||
@@ -1095,6 +1096,7 @@ static int* P2pSocketOption(P2pEndpoint& endpoint, int option) {
|
||||
case 0x1001: return &endpoint.send_buffer;
|
||||
case 0x1002: return &endpoint.receive_buffer;
|
||||
case 0x1200: return &endpoint.nonblocking;
|
||||
case 0x20000: return &endpoint.use_crypto;
|
||||
default: return nullptr;
|
||||
}
|
||||
}
|
||||
@@ -2096,19 +2098,15 @@ int KYTY_SYSV_ABI Shutdown(int s, int how) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
if (::shutdown(socket, how) == SOCKET_ERROR) {
|
||||
if (::shutdown(socket, how) != 0) {
|
||||
return SetHostSocketError();
|
||||
}
|
||||
#if defined(_WIN32)
|
||||
if (how == SD_RECEIVE || how == SD_BOTH) {
|
||||
transport->receive_shutdown.store(true, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
return 0;
|
||||
#else
|
||||
*Posix::GetErrorAddr() = Posix::POSIX_ENOSYS;
|
||||
return -1;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI Getsockname(int s, void* addr, uint32_t* addrlen) {
|
||||
@@ -2463,9 +2461,9 @@ int64_t KYTY_SYSV_ABI Recvfrom(int s, void* buf, uint64_t len, int flags, void*
|
||||
return -1;
|
||||
}
|
||||
|
||||
NativeSocket socket = INVALID_NATIVE_SOCKET;
|
||||
std::shared_ptr<SocketTransport> transport;
|
||||
if (!GetSocketBackend(s, &socket, nullptr, &transport)) {
|
||||
NativeSocket socket = INVALID_NATIVE_SOCKET;
|
||||
SocketSlot state;
|
||||
if (!GetSocketBackend(s, &socket, &state)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -2473,6 +2471,21 @@ int64_t KYTY_SYSV_ABI Recvfrom(int s, void* buf, uint64_t len, int flags, void*
|
||||
if (host_flags < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (state.p2p) {
|
||||
if (!state.p2p->bound || (!state.p2p->nonblocking && (flags & 0x80) == 0)) {
|
||||
return SetGuestSocketError(Posix::POSIX_EOPNOTSUPP);
|
||||
}
|
||||
char pending;
|
||||
if (::recv(socket, &pending, sizeof(pending), MSG_PEEK) < 0) {
|
||||
SetHostSocketError();
|
||||
// Winsock reports a peeked datagram's truncation as EMSGSIZE.
|
||||
if (*Posix::GetErrorAddr() != Posix::POSIX_EMSGSIZE) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
// Pending UDPP2P packets still require framing and optional decryption.
|
||||
return SetGuestSocketError(Posix::POSIX_EOPNOTSUPP);
|
||||
}
|
||||
|
||||
const auto host_len = static_cast<SocketIoLength>(
|
||||
std::min<uint64_t>(len, std::numeric_limits<SocketIoLength>::max()));
|
||||
@@ -2480,12 +2493,12 @@ int64_t KYTY_SYSV_ABI Recvfrom(int s, void* buf, uint64_t len, int flags, void*
|
||||
SocketLength host_addrlen = sizeof(host_addr);
|
||||
int64_t result = 0;
|
||||
#if defined(_WIN32)
|
||||
if (transport->type == SOCK_STREAM) {
|
||||
if (state.transport->type == SOCK_STREAM) {
|
||||
if (addr != nullptr &&
|
||||
::getpeername(socket, reinterpret_cast<sockaddr*>(&host_addr), &host_addrlen) != 0) {
|
||||
return SetHostSocketError();
|
||||
}
|
||||
result = ReceiveStream(*transport, static_cast<char*>(buf), host_len, host_flags,
|
||||
result = ReceiveStream(*state.transport, static_cast<char*>(buf), host_len, host_flags,
|
||||
(flags & 0x80) != 0);
|
||||
if (result < 0) {
|
||||
return result;
|
||||
@@ -2493,7 +2506,7 @@ int64_t KYTY_SYSV_ABI Recvfrom(int s, void* buf, uint64_t len, int flags, void*
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
transport.reset();
|
||||
state.transport.reset();
|
||||
if (addr == nullptr) {
|
||||
result = ::recv(socket, static_cast<char*>(buf), host_len, host_flags);
|
||||
} else {
|
||||
|
||||
@@ -413,7 +413,9 @@ void TestAddContent(const std::filesystem::path& root) {
|
||||
Check(mount(0, nullptr, &point) == static_cast<int>(0x80d90002u) &&
|
||||
list(0, nullptr, 0, nullptr) == static_cast<int>(0x817d0002u),
|
||||
"null add-on parameters return parameter errors");
|
||||
Check(mount(0, &info[3].label, &point) == OK, "mount owned add-on");
|
||||
Check(mount(0, &info[3].label, &point) == OK &&
|
||||
!std::string_view(point.data).ends_with('/'),
|
||||
"mount owned add-on without trailing slash");
|
||||
const std::string mounted_file = std::string(point.data) + "/payload.txt";
|
||||
const int fd = FileSystem::KernelOpen(mounted_file.c_str(), 0, 0);
|
||||
std::array<char, 11> payload {};
|
||||
@@ -422,8 +424,9 @@ void TestAddContent(const std::filesystem::path& root) {
|
||||
FileSystem::KernelClose(fd) == OK,
|
||||
"guest reads files from the selected title's add-on");
|
||||
MountPoint repeated {};
|
||||
Check(mount(0, &info[3].label, &repeated) == static_cast<int>(0x80d90003u),
|
||||
"duplicate mount returns BUSY");
|
||||
Check(mount(0, &info[3].label, &repeated) == OK &&
|
||||
std::strcmp(repeated.data, point.data) == 0,
|
||||
"duplicate mount returns the same mount point");
|
||||
Check(unmount(&point) == OK && FileSystem::GetRealFilename(mounted_file).empty() &&
|
||||
unmount(&point) == static_cast<int>(0x80d90004u),
|
||||
"unmount removes mapping and repeated unmount reports NOT_MOUNTED");
|
||||
@@ -1902,6 +1905,11 @@ void CheckSocketWakeup() {
|
||||
namespace Net = Libs::Network::Net;
|
||||
Loader::SymbolDatabase symbols;
|
||||
Libs::LibNet::InitNet_1_Net(&symbols);
|
||||
Libs::InitLibKernel_1(&symbols);
|
||||
const auto *shutdown_symbol = symbols.Find(
|
||||
{"TUuiYS2kE8s", "Posix", 1, "libkernel", 1, 1, Loader::SymbolType::Func});
|
||||
Check(shutdown_symbol != nullptr, "POSIX shutdown export resolves");
|
||||
const auto shutdown = reinterpret_cast<int (KYTY_SYSV_ABI *)(int, int)>(shutdown_symbol->vaddr);
|
||||
const auto *connect_symbol = symbols.Find(
|
||||
{"OXXX4mUk3uk", "Net", 1, "Net", 1, 1, Loader::SymbolType::Func});
|
||||
const auto *getsockopt_symbol = symbols.Find(
|
||||
@@ -2142,6 +2150,76 @@ void CheckSocketWakeup() {
|
||||
CheckSocketReceiveBuffer(reader, writer);
|
||||
CheckSocketReceiveConcurrency();
|
||||
#endif
|
||||
const auto [shutdown_reader, shutdown_writer] = CreateTcpPair();
|
||||
const auto ready_to_read = [](int fd) {
|
||||
std::array<uint64_t, 16> readable {};
|
||||
readable[fd / 64] = uint64_t {1} << (fd % 64);
|
||||
const std::array<int64_t, 2> timeout {2, 0};
|
||||
return Net::Select(fd + 1, readable.data(), nullptr, nullptr, timeout.data()) == 1;
|
||||
};
|
||||
Check(shutdown(-1, 1) == -1 &&
|
||||
*Libs::Posix::GetErrorAddr() == Libs::Posix::POSIX_EBADF &&
|
||||
shutdown(shutdown_writer, 3) == -1 &&
|
||||
*Libs::Posix::GetErrorAddr() == Libs::Posix::POSIX_EINVAL,
|
||||
"shutdown rejects invalid descriptors and directions");
|
||||
Check(Net::Send(shutdown_writer, "x", 1, 0) == 1 && shutdown(shutdown_writer, 1) == 0 &&
|
||||
ready_to_read(shutdown_reader) &&
|
||||
Net::Recv(shutdown_reader, received.data(), 1, 0) == 1 && received[0] == 'x' &&
|
||||
ready_to_read(shutdown_reader) &&
|
||||
Net::Recv(shutdown_reader, received.data(), 1, 0) == 0 &&
|
||||
Net::Send(shutdown_reader, "y", 1, 0) == 1 &&
|
||||
ready_to_read(shutdown_writer) &&
|
||||
Net::Recv(shutdown_writer, received.data(), 1, 0) == 1 && received[0] == 'y',
|
||||
"write shutdown drains queued bytes, signals EOF, and preserves reverse traffic");
|
||||
Check(Net::SocketClose(shutdown_reader) == 0 && Net::SocketClose(shutdown_writer) == 0,
|
||||
"close half-shutdown sockets");
|
||||
const int encrypted = Net::Socket(2, 6, 0);
|
||||
int crypto = -1;
|
||||
uint32_t crypto_size = sizeof(crypto);
|
||||
Check(encrypted >= 0 &&
|
||||
Net::Getsockopt(encrypted, 0xffff, 0x20000, &crypto, &crypto_size) == 0 &&
|
||||
crypto == 0,
|
||||
"UDPP2P encryption defaults to disabled");
|
||||
for (const int setting : {7, 0}) {
|
||||
Check(Net::Setsockopt(encrypted, 0xffff, 0x20000, &setting, sizeof(setting)) == 0 &&
|
||||
Net::Getsockopt(encrypted, 0xffff, 0x20000, &crypto, &crypto_size) == 0 &&
|
||||
crypto == (setting != 0),
|
||||
"UDPP2P encryption configuration retains normalized enable and disable values");
|
||||
Check(Net::Send(encrypted, "x", 1, 0) == -1 &&
|
||||
*Libs::Posix::GetErrorAddr() == Libs::Posix::POSIX_EOPNOTSUPP,
|
||||
"UDPP2P configuration never exposes unframed plaintext transport");
|
||||
}
|
||||
std::array<uint8_t, 16> p2p_address {16, 2, 0x0e, 0x4a, 127, 0, 0, 1, 0x1e, 0x61};
|
||||
Check(Net::Bind(encrypted, p2p_address.data(), p2p_address.size()) == 0,
|
||||
"bind UDPP2P physical and virtual ports");
|
||||
char untouched = '!';
|
||||
auto source = p2p_address;
|
||||
uint32_t source_size = source.size();
|
||||
Check(Net::Recvfrom(encrypted, &untouched, 1, 0x80, source.data(), &source_size) == -1 &&
|
||||
*Libs::Posix::GetErrorAddr() == Libs::Posix::POSIX_EWOULDBLOCK,
|
||||
"per-call nonblocking UDPP2P receive reports an actually empty queue");
|
||||
Check(Net::Setsockopt(encrypted, 0xffff, 0x1200, &enabled, sizeof(enabled)) == 0 &&
|
||||
Net::Setsockopt(encrypted, 0xffff, 0x20000, &enabled, sizeof(enabled)) == 0 &&
|
||||
Net::Recvfrom(encrypted, &untouched, 1, 0, source.data(), &source_size) == -1 &&
|
||||
*Libs::Posix::GetErrorAddr() == Libs::Posix::POSIX_EWOULDBLOCK &&
|
||||
untouched == '!' && source == p2p_address && source_size == source.size(),
|
||||
"empty encrypted nonblocking receive preserves all caller outputs");
|
||||
const int raw_sender = Net::Socket(2, 2, 0);
|
||||
Check(raw_sender >= 0 &&
|
||||
Net::Sendto(raw_sender, "pending", 7, 0, p2p_address.data(), p2p_address.size()) == 7,
|
||||
"queue a datagram requiring unsupported UDPP2P processing");
|
||||
const auto pending_deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2);
|
||||
while (Net::Recv(encrypted, &untouched, 1, 0) == -1 &&
|
||||
*Libs::Posix::GetErrorAddr() == Libs::Posix::POSIX_EWOULDBLOCK &&
|
||||
std::chrono::steady_clock::now() < pending_deadline) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
Check(*Libs::Posix::GetErrorAddr() == Libs::Posix::POSIX_EOPNOTSUPP &&
|
||||
Net::Recv(encrypted, &untouched, 1, 0) == -1 &&
|
||||
*Libs::Posix::GetErrorAddr() == Libs::Posix::POSIX_EOPNOTSUPP && untouched == '!',
|
||||
"queued packets remain queued and unsupported instead of masquerading as empty");
|
||||
Check(Net::SocketClose(encrypted) == 0 && Net::SocketClose(raw_sender) == 0,
|
||||
"close UDPP2P readiness fixtures");
|
||||
Check(Net::SocketClose(writer) == 0, "close wake writer");
|
||||
*net_errno = Libs::Posix::POSIX_EINVAL;
|
||||
Check(net_recv(reader, received.data(), received.size(), 0) == 0 &&
|
||||
|
||||
@@ -874,7 +874,7 @@ void TestFiniteImageRefreshReusesScalarReads() {
|
||||
"finite image refresh grew reusable resource storage after warmup");
|
||||
}
|
||||
|
||||
void TestEmptyTraversalSkipsUnmappedDescriptor(bool independent_mask) {
|
||||
void TestEmptyTraversalSkipsUnmappedDescriptor(bool independent_mask, bool counted_loop = false) {
|
||||
using namespace Libs::Graphics::ShaderRecompiler::IR;
|
||||
using Libs::Graphics::ShaderRecompiler::CFG::TerminatorKind;
|
||||
Program program;
|
||||
@@ -898,54 +898,87 @@ void TestEmptyTraversalSkipsUnmappedDescriptor(bool independent_mask) {
|
||||
block.AddBranch(&target);
|
||||
if (other) block.AddBranch(other);
|
||||
};
|
||||
const auto lane = emit(entry, ValueOpcode::GetBuiltin,
|
||||
{Value(static_cast<uint32_t>(StageInputKind::LocalInvocationId)), Value(0u)});
|
||||
const auto active = emit(entry, ValueOpcode::INotEqual32, {lane, Value(0u)});
|
||||
const auto initial_mask = independent_mask
|
||||
? emit(entry, ValueOpcode::IEqual32, {lane, Value(0u)}) : active;
|
||||
const auto initial = emit(entry, ValueOpcode::SelectU32,
|
||||
{initial_mask, Value(UINT32_MAX), Value(0u)});
|
||||
Value lane, active, initial;
|
||||
if (!counted_loop) {
|
||||
lane = emit(entry, ValueOpcode::GetBuiltin,
|
||||
{Value(static_cast<uint32_t>(StageInputKind::LocalInvocationId)), Value(0u)});
|
||||
active = emit(entry, ValueOpcode::INotEqual32, {lane, Value(0u)});
|
||||
const auto initial_mask = independent_mask
|
||||
? emit(entry, ValueOpcode::IEqual32, {lane, Value(0u)}) : active;
|
||||
initial = emit(entry, ValueOpcode::SelectU32,
|
||||
{initial_mask, Value(UINT32_MAX), Value(0u)});
|
||||
}
|
||||
program.memory_info.push_back({.kind = ResourceKind::ScalarAddress, .planning_only = true});
|
||||
const auto address = emit(entry, ValueOpcode::GetAddressResource, {Value(0x1000u), Value(0u)});
|
||||
const auto count = emit(entry, ValueOpcode::LoadAddressU32,
|
||||
{address, Value(0u), Value(0u), Value(true)});
|
||||
count.Instruction()->SetFlags(MemoryFlags{.index = 0});
|
||||
const auto rejected = emit(entry, ValueOpcode::ULessThanEqual32, {count, lane});
|
||||
const auto accepted = emit(entry, ValueOpcode::LogicalNot, {rejected});
|
||||
branch(entry, loop, emit(entry, ValueOpcode::LogicalAnd, {active, accepted}), &merge);
|
||||
if (counted_loop) {
|
||||
branch(entry, loop);
|
||||
} else {
|
||||
const auto rejected = emit(entry, ValueOpcode::ULessThanEqual32, {count, lane});
|
||||
const auto accepted = emit(entry, ValueOpcode::LogicalNot, {rejected});
|
||||
branch(entry, loop, emit(entry, ValueOpcode::LogicalAnd, {active, accepted}), &merge);
|
||||
}
|
||||
|
||||
// A nonempty traversal contains a varying loop-carried value. Its cycle must
|
||||
// remain unknown; only the proven empty traversal can select the bypass arm.
|
||||
auto &carried = loop.AppendNewInst(ValueOpcode::Phi);
|
||||
carried.SetFlags(Type::U32);
|
||||
const auto next = emit(body, ValueOpcode::IAdd32, {Value(&carried), Value(1u)});
|
||||
carried.AddPhiOperand(&entry, lane);
|
||||
carried.AddPhiOperand(&entry, counted_loop ? Value(0u) : lane);
|
||||
carried.AddPhiOperand(&body, next);
|
||||
branch(loop, body, emit(loop, ValueOpcode::INotEqual32, {Value(&carried), Value(UINT32_MAX)}), &merge);
|
||||
const auto condition = counted_loop
|
||||
? emit(loop, ValueOpcode::ULessThan32, {Value(&carried), count})
|
||||
: emit(loop, ValueOpcode::INotEqual32, {Value(&carried), Value(UINT32_MAX)});
|
||||
branch(loop, counted_loop ? use : body, condition, &merge);
|
||||
loop.terminator.loop_header = true;
|
||||
loop.terminator.continue_block = &body;
|
||||
loop.terminator.merge_block = &merge;
|
||||
branch(body, loop);
|
||||
auto &selected = merge.AppendNewInst(ValueOpcode::Phi);
|
||||
selected.SetFlags(Type::U32);
|
||||
selected.AddPhiOperand(&entry, initial);
|
||||
selected.AddPhiOperand(&loop, Value(&carried));
|
||||
const auto valid = emit(merge, ValueOpcode::INotEqual32, {Value(&selected), Value(UINT32_MAX)});
|
||||
branch(merge, use, emit(merge, ValueOpcode::LogicalAnd, {active, valid}), &exit);
|
||||
branch(use, exit);
|
||||
if (counted_loop) {
|
||||
branch(merge, exit);
|
||||
branch(use, body);
|
||||
} else {
|
||||
auto &selected = merge.AppendNewInst(ValueOpcode::Phi);
|
||||
selected.SetFlags(Type::U32);
|
||||
selected.AddPhiOperand(&entry, initial);
|
||||
selected.AddPhiOperand(&loop, Value(&carried));
|
||||
const auto valid = emit(merge, ValueOpcode::INotEqual32, {Value(&selected), Value(UINT32_MAX)});
|
||||
branch(merge, use, emit(merge, ValueOpcode::LogicalAnd, {active, valid}), &exit);
|
||||
branch(use, exit);
|
||||
}
|
||||
DescriptorSource source;
|
||||
source.dwords = {Value(0x8000u), Value(1u << 16u), Value(16u), Value(3u << 28u)};
|
||||
const auto descriptor_address = emit(use, ValueOpcode::GetAddressResource,
|
||||
{Value(0x2000u), Value(0u)});
|
||||
for (uint32_t i = 0; i < 4; ++i) {
|
||||
const auto index = static_cast<uint32_t>(program.memory_info.size());
|
||||
program.memory_info.push_back({.kind = ResourceKind::ScalarAddress, .planning_only = true});
|
||||
source.dwords[i] = emit(use, ValueOpcode::LoadAddressU32,
|
||||
{descriptor_address, Value(i * 4u), Value(0u), Value(true)});
|
||||
source.dwords[i].Instruction()->SetFlags(MemoryFlags{.index = index});
|
||||
}
|
||||
source.dword_count = 4;
|
||||
program.descriptor_sources.push_back(source);
|
||||
program.info.buffers.push_back({.source = 0, .read = true});
|
||||
const auto buffer_index = static_cast<uint32_t>(program.memory_info.size());
|
||||
program.memory_info.push_back({.kind = ResourceKind::Buffer, .resource = 0});
|
||||
const auto buffer = emit(use, ValueOpcode::GetBufferResource,
|
||||
{source.dwords[0], source.dwords[1], source.dwords[2], source.dwords[3]});
|
||||
emit(use, ValueOpcode::LoadBufferU32, {buffer, Value(0u), Value(0u), Value(0u), Value(true)})
|
||||
.Instruction()->SetFlags(MemoryFlags{.index = 1});
|
||||
.Instruction()->SetFlags(MemoryFlags{.index = buffer_index});
|
||||
auto plan = ExtractResourcePlan(program);
|
||||
struct Reads { uint32_t count = 0; bool clean = true; } reads;
|
||||
struct Reads { uint32_t count = 0; bool clean = true; uint32_t descriptors = 0; } reads;
|
||||
const SrtRuntime runtime{
|
||||
.read_memory = +[](void *data, uint64_t address, std::span<uint32_t> words) {
|
||||
constexpr std::array<uint32_t, 4> descriptor{0x8000u, 1u << 16u, 16u,
|
||||
(3u << 28u) | 0x4dfacu};
|
||||
if (address < 0x2000u || address >= 0x2010u || address % 4u || words.size() != 1)
|
||||
return false;
|
||||
++static_cast<Reads *>(data)->descriptors;
|
||||
words[0] = descriptor[(address - 0x2000u) / 4u];
|
||||
return true;
|
||||
},
|
||||
.userdata = &reads,
|
||||
.read_specialization_memory = +[](void *data, uint64_t address, std::span<uint32_t> words) {
|
||||
const auto &reads = *static_cast<Reads *>(data);
|
||||
@@ -957,9 +990,11 @@ void TestEmptyTraversalSkipsUnmappedDescriptor(bool independent_mask) {
|
||||
ResourceSpecialization specialization;
|
||||
for (const auto state : {Reads{0u, true}, Reads{4u, true}, Reads{0u, false}, Reads{0u, true}}) {
|
||||
reads = state;
|
||||
uint32_t condition = 99;
|
||||
Check(!SrtWalker(plan, runtime).Evaluate(plan.control_flow[3].condition, condition),
|
||||
"direct merge evaluation used missing or previous runtime reachability");
|
||||
if (!counted_loop) {
|
||||
uint32_t condition = 99;
|
||||
Check(!SrtWalker(plan, runtime).Evaluate(plan.control_flow[3].condition, condition),
|
||||
"direct merge evaluation used missing or previous runtime reachability");
|
||||
}
|
||||
Check(MaterializeResources(plan, runtime, snapshot, specialization),
|
||||
"partial traversal proof rejected a valid resource snapshot");
|
||||
const bool inactive = !independent_mask && reads.clean && reads.count == 0u;
|
||||
@@ -967,6 +1002,8 @@ void TestEmptyTraversalSkipsUnmappedDescriptor(bool independent_mask) {
|
||||
"empty traversal kept its descriptor or an uncertain traversal lost it");
|
||||
Check(specialization.buffers[0].zero_stride_oob == inactive,
|
||||
"inactive traversal did not expose dead vector reads to specialization");
|
||||
Check(reads.descriptors == (inactive ? 0u : 4u),
|
||||
"inactive traversal read its descriptor or an uncertain traversal skipped it");
|
||||
if (inactive) {
|
||||
Check(!snapshot.specialization_reads.empty(), "empty traversal lost its clean scalar read");
|
||||
for (const auto &read : snapshot.specialization_reads)
|
||||
@@ -1331,6 +1368,7 @@ int main() {
|
||||
TestFiniteImageRefreshReusesScalarReads();
|
||||
for (const bool independent_mask : {false, true})
|
||||
TestEmptyTraversalSkipsUnmappedDescriptor(independent_mask);
|
||||
TestEmptyTraversalSkipsUnmappedDescriptor(false, true);
|
||||
TestCyclicControlPredicatesRemainUnknown();
|
||||
TestEmptyScalarBufferSpecialization();
|
||||
TestMixedSamplerVariantsShareRuntimeDescriptor();
|
||||
|
||||
@@ -14672,7 +14672,7 @@ public:
|
||||
phased_rendering.num_color_attachments == 0 &&
|
||||
phased_rendering.depth_stencil_attachment.has_depth &&
|
||||
phased_rendering.depth_stencil_attachment.depth_clear &&
|
||||
phased_depth.depth_load_clear_enable &&
|
||||
static_cast<bool>(phased_depth.load_clear_aspects & vk::ImageAspectFlagBits::eDepth) &&
|
||||
texture_cache.IsMeta(phased_depth.desc.info.metadata.range.address) &&
|
||||
!texture_cache.IsMetaCleared(
|
||||
phased_depth.desc.info.metadata.range.address,
|
||||
@@ -14709,6 +14709,8 @@ public:
|
||||
RenderDepthInfo depth_only{};
|
||||
RenderExecutorTestAccess::ResolveRenderDepthTarget(
|
||||
executor, scheduler.Current(), depth_only);
|
||||
(void)RenderExecutorTestAccess::AcquireRenderTargets(
|
||||
executor, scheduler.Current(), &no_color, 0, depth_only);
|
||||
Require(
|
||||
name, "depth-only target with stale stencil state",
|
||||
depth_only.image_id && depth_only.desc.view_info.format == vk::Format::eD32Sfloat &&
|
||||
@@ -14853,7 +14855,7 @@ public:
|
||||
bounds_depth.image_id == depth_only.image_id &&
|
||||
bounds_depth.depth_bounds_test_enable &&
|
||||
!bounds_depth.depth_write_enable &&
|
||||
bounds_depth.depth_load_clear_enable == (pass == 0) &&
|
||||
static_cast<bool>(bounds_depth.load_clear_aspects & vk::ImageAspectFlagBits::eDepth) == (pass == 0) &&
|
||||
!texture_cache.IsMetaCleared(depth_only_htile_address, 0) &&
|
||||
bounds_feedback == (pass == 0
|
||||
? vk::ImageAspectFlagBits::eDepth
|
||||
@@ -14932,7 +14934,7 @@ public:
|
||||
executor, scheduler.Current(), &no_color, 0, acquired_high_slice);
|
||||
Require(name, "high HTile slice clear consumption",
|
||||
acquired_high_slice.image_id == high_slice.image_id &&
|
||||
acquired_high_slice.depth_load_clear_enable == (pass == 0) &&
|
||||
static_cast<bool>(acquired_high_slice.load_clear_aspects & vk::ImageAspectFlagBits::eDepth) == (pass == 0) &&
|
||||
rendering.depth_stencil_attachment.depth_clear == (pass == 0) &&
|
||||
!texture_cache.IsMetaCleared(high_slice_target.htile_data_base_addr, 86) &&
|
||||
texture_cache.IsMetaCleared(high_slice_target.htile_data_base_addr, 85),
|
||||
@@ -17912,7 +17914,7 @@ public:
|
||||
registers.SetTargetOutputMode(1, 4);
|
||||
stencil_target.z_info.htile_acceleration = true;
|
||||
stencil_target.htile_data_base_addr = depth_address + 0x30000;
|
||||
registers.SetDepthRenderTarget(stencil_target);
|
||||
registers.SetStencilClearValue(5);
|
||||
registers.SetDepthClearValue(1);
|
||||
stencil_control = {};
|
||||
stencil_control.stencil_enable = true;
|
||||
@@ -17922,35 +17924,60 @@ public:
|
||||
registers.SetStencilMask({0x80, 0, 0x80, 1, 0x80, 0, 0x80, 1});
|
||||
user_config.SetPrimitiveType(Prospero::PrimitiveType::kTriList);
|
||||
shaders.SetEsShaderBase(vertex_address);
|
||||
for (uint32_t discard = 0; discard < null_pixels.size(); discard++) {
|
||||
const auto &code = null_pixels[discard];
|
||||
const auto address = reinterpret_cast<uint64_t>(code.data());
|
||||
ShaderMapUserData(address,
|
||||
{.type = Prospero::ShaderBinaryType::kPs,
|
||||
.code_size_bytes = static_cast<uint32_t>(code.size() * sizeof(u32))});
|
||||
shaders.SetPsShaderBase(address);
|
||||
RenderExecutorTestAccess::ResolveRenderDepthTarget(executor, scheduler.Current(), depth);
|
||||
(void)cache.FindDepthTarget(depth.image_id, depth.desc);
|
||||
vk::ClearValue zero{};
|
||||
TextureCacheTestAccess::ClearImage(cache, scheduler.Current(), depth.image_id,
|
||||
{vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil,
|
||||
0, 1, 0, 1}, zero);
|
||||
Require(name, "pending stencil-pass HTile clear",
|
||||
cache.ClearMeta(stencil_target.htile_data_base_addr),
|
||||
"the stencil pass did not retain its HTile metadata");
|
||||
RenderExecutorTestAccess::DrawAuto(
|
||||
executor, scheduler.Current(), {.vertex_count = 3, .instance_count = 1});
|
||||
const auto depth_pixels = ReadCachedTexel(name, context, depth.image_id,
|
||||
{}, {extent, extent, 1});
|
||||
Require(name, "NULL-export full depth clear",
|
||||
std::ranges::all_of(depth_pixels, [](u32 v) { return v == 0x3f800000u; }),
|
||||
"an unexported color target clipped the pending depth clear");
|
||||
const auto stencil = read_stencil();
|
||||
const auto expected = discard == 0 ? 0x80808080u : 0u;
|
||||
Require(name, "NULL-export stencil coverage and discard",
|
||||
std::ranges::all_of(stencil, [=](u32 v) { return v == expected; }),
|
||||
"a stale color target clipped stencil coverage or the NULL-export shader was skipped");
|
||||
// Compressed metadata clears stencil; an uncompressed plane and an explicit
|
||||
// depth-only clear preserve its previous contents. Match Suikoden's stale mask 2.
|
||||
for (uint32_t clear_mode = 0; clear_mode < 3; clear_mode++) {
|
||||
stencil_target.stencil_info.htile_stencil_disabled = clear_mode == 0;
|
||||
registers.SetDepthRenderTarget(stencil_target);
|
||||
registers.SetRenderControl({.depth_clear_enable = clear_mode == 2});
|
||||
for (uint32_t discard = 0; discard < null_pixels.size(); discard++) {
|
||||
const auto &code = null_pixels[discard];
|
||||
const auto address = reinterpret_cast<uint64_t>(code.data());
|
||||
ShaderMapUserData(address,
|
||||
{.type = Prospero::ShaderBinaryType::kPs,
|
||||
.code_size_bytes = static_cast<uint32_t>(code.size() * sizeof(u32))});
|
||||
shaders.SetPsShaderBase(address);
|
||||
RenderExecutorTestAccess::ResolveRenderDepthTarget(executor, scheduler.Current(), depth);
|
||||
(void)cache.FindDepthTarget(depth.image_id, depth.desc);
|
||||
vk::ClearValue zero{};
|
||||
zero.depthStencil.stencil = 2;
|
||||
TextureCacheTestAccess::ClearImage(cache, scheduler.Current(), depth.image_id,
|
||||
{vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil,
|
||||
0, 1, 0, 1}, zero);
|
||||
Require(name, "pending stencil-pass HTile clear",
|
||||
clear_mode == 2
|
||||
? cache.TouchMeta(stencil_target.htile_data_base_addr, 0, false)
|
||||
: cache.ClearMeta(stencil_target.htile_data_base_addr),
|
||||
"the stencil pass did not retain its HTile metadata");
|
||||
RenderExecutorTestAccess::DrawAuto(
|
||||
executor, scheduler.Current(), {.vertex_count = 3, .instance_count = 1});
|
||||
const auto depth_pixels = ReadCachedTexel(name, context, depth.image_id,
|
||||
{}, {extent, extent, 1});
|
||||
Require(name, "NULL-export full depth clear",
|
||||
std::ranges::all_of(depth_pixels, [](u32 v) { return v == 0x3f800000u; }),
|
||||
"an unexported color target clipped the pending depth clear");
|
||||
const auto stencil = read_stencil();
|
||||
const auto cleared = clear_mode == 1 ? 0x05050505u : 0x02020202u;
|
||||
const auto expected = cleared | (discard == 0 ? 0x80808080u : 0u);
|
||||
Require(name, "NULL-export stencil coverage and discard",
|
||||
std::ranges::all_of(stencil, [=](u32 v) { return v == expected; }),
|
||||
"a stale color target clipped stencil coverage or the NULL-export shader was skipped");
|
||||
Require(name, "HTile clear consumed once",
|
||||
!cache.IsMetaCleared(stencil_target.htile_data_base_addr, 0),
|
||||
"the draw retained a pending metadata clear");
|
||||
if (clear_mode == 1 && discard == 1) {
|
||||
zero.depthStencil.stencil = 9;
|
||||
TextureCacheTestAccess::ClearImage(cache, scheduler.Current(), depth.image_id,
|
||||
{vk::ImageAspectFlagBits::eStencil, 0, 1, 0, 1}, zero);
|
||||
RenderExecutorTestAccess::DrawAuto(
|
||||
executor, scheduler.Current(), {.vertex_count = 3, .instance_count = 1});
|
||||
Require(name, "consumed HTile clear preserves later stencil",
|
||||
std::ranges::all_of(read_stencil(), [](u32 v) { return v == 0x09090909u; }),
|
||||
"a consumed metadata clear ran again on the next draw");
|
||||
}
|
||||
}
|
||||
}
|
||||
registers.SetRenderControl({});
|
||||
|
||||
// A depth-only draw retains the previous PS address and write masks, but
|
||||
// SPI_SHADER_COL_FORMAT disables its exports. Compiling this stale PS
|
||||
@@ -21015,6 +21042,7 @@ CoverageClass ClassifyOpcode(ShaderOpcode opcode,
|
||||
case Opcode::DS_ADD_U32:
|
||||
case Opcode::DS_ADD_U64:
|
||||
case Opcode::DS_MAX_RTN_U64:
|
||||
case Opcode::DS_CMPST_RTN_B32:
|
||||
case Opcode::DS_CMPST_RTN_B64:
|
||||
case Opcode::DS_ADD_RTN_U32:
|
||||
case Opcode::DS_SUB_U32:
|
||||
@@ -25191,6 +25219,34 @@ TestCase VectorCarryAndBitCountOps() {
|
||||
O::S_ENDPGM}};
|
||||
}
|
||||
|
||||
TestCase ScalarVector64BitFloatInlineConstants() {
|
||||
using O = ShaderOpcode;
|
||||
std::vector<u32> code;
|
||||
code.push_back(EncodeSop1(0x04, 40, 242));
|
||||
code.push_back(EncodeSop2(0x11, 42, 240, InlineU32(0)));
|
||||
code.push_back(EncodeSop1(0x04, 44, 248));
|
||||
AppendVop3(&code, 0x300, 2, InlineU32(32), 244);
|
||||
code.push_back(EncodeSMovB32(46, InlineU32(0)));
|
||||
AppendSMovLiteral(&code, 47, 0x3ff00000u);
|
||||
code.push_back(EncodeSopc(0x12, 46, 242));
|
||||
code.push_back(EncodeSMovB32(48, 253));
|
||||
AppendStoreSgprPair(&code, 40, 0);
|
||||
AppendStoreSgprPair(&code, 42, 2);
|
||||
AppendStoreVgpr(&code, 2, 4);
|
||||
AppendStoreVgpr(&code, 3, 5);
|
||||
AppendStoreSgprPair(&code, 44, 6);
|
||||
AppendStoreSgpr(&code, 48, 8);
|
||||
AppendEnd(&code);
|
||||
|
||||
TestCase test;
|
||||
test.name = "ScalarVector64BitFloatInlineConstants";
|
||||
test.code = std::move(code);
|
||||
test.expected = {0u, 0x3ff00000u, 0u, 0x3fe00000u, 0x40000000u, 0u, 0x6dc9c882u, 0x3fc45f30u, 1u};
|
||||
test.opcodes = {O::S_MOV_B64, O::S_OR_B64, O::V_LSHRREV_B64, O::S_MOV_B32, O::S_CMP_EQ_U64,
|
||||
O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase VectorMbcntUsesThreadMask() {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
@@ -25821,6 +25877,49 @@ TestCase CvtPkrtzF16F32SubnormalRoundsTowardZero() {
|
||||
O::S_ENDPGM}};
|
||||
}
|
||||
|
||||
TestCase CvtPkrtzF16F32RuntimeEdges() {
|
||||
using O = ShaderOpcode;
|
||||
// Independent expected half encodings, including truncation boundaries and NaN payloads.
|
||||
constexpr std::array<std::array<u32, 2>, 31> inputs{{
|
||||
{0x00000000u, 0x0000u}, {0x80000000u, 0x8000u},
|
||||
{0x00000001u, 0x0000u}, {0x007fffffu, 0x0000u},
|
||||
{0x00800000u, 0x0000u}, {0x32ffffffu, 0x0000u},
|
||||
{0x33000000u, 0x0000u}, {0x337fffffu, 0x0000u},
|
||||
{0x33800000u, 0x0001u}, {0x33c00000u, 0x0001u},
|
||||
{0x34000000u, 0x0002u}, {0x387fc000u, 0x03ffu},
|
||||
{0x387fe000u, 0x03ffu}, {0x387fffffu, 0x03ffu},
|
||||
{0x38800000u, 0x0400u}, {0x38802000u, 0x0401u},
|
||||
{0x3e22f983u, 0x3117u}, {0x3f800000u, 0x3c00u},
|
||||
{0x3f801000u, 0x3c00u}, {0x3f801001u, 0x3c00u},
|
||||
{0x3f802000u, 0x3c01u}, {0x477fe000u, 0x7bffu},
|
||||
{0x477ff000u, 0x7bffu}, {0x477fffffu, 0x7bffu},
|
||||
{0x47800000u, 0x7bffu}, {0x7f7fffffu, 0x7bffu},
|
||||
{0x7f800000u, 0x7c00u}, {0x7f800001u, 0x7e00u},
|
||||
{0x7fa02000u, 0x7f01u}, {0x7fc12000u, 0x7e09u},
|
||||
{0x7fffffffu, 0x7fffu},
|
||||
}};
|
||||
TestCase test;
|
||||
test.name = "CvtPkrtzF16F32RuntimeEdges";
|
||||
for (const auto &input : inputs)
|
||||
test.initial.insert(test.initial.end(), {input[0], input[0] ^ 0x80000000u});
|
||||
test.expected = test.initial;
|
||||
for (u32 i = 0; i < inputs.size(); ++i) {
|
||||
for (u32 reg = 0; reg < 2; ++reg) {
|
||||
AppendVMovU32(&test.code, 30, (i * 2u + reg) * 4u);
|
||||
AppendBufferLoadDword(&test.code, reg, 30);
|
||||
}
|
||||
test.code.push_back(EncodeVop2(0x2f, 2, Vgpr(0), 1));
|
||||
AppendStoreVgpr(&test.code, 2, static_cast<u32>(test.expected.size()));
|
||||
const auto half = inputs[i][1];
|
||||
test.expected.push_back(half | ((half ^ 0x8000u) << 16));
|
||||
}
|
||||
AppendEnd(&test.code);
|
||||
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_CVT_PKRTZ_F16_F32,
|
||||
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
test.forbidden_spirv = {"PackHalf2x16", "UnpackHalf2x16"};
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase CvtPkrtzF16F32SdwaAndOutputModifiers() {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
@@ -25879,13 +25978,17 @@ TestCase PackedMinMaxF16NanAndSignedZeroEdges() {
|
||||
AppendVMovLiteral(&code, 3, 0x80007e00u);
|
||||
AppendVMovLiteral(&code, 4, 0x40007d01u);
|
||||
AppendVMovLiteral(&code, 5, 0x7d014000u);
|
||||
AppendVMovLiteral(&code, 6, 0x40007e01u);
|
||||
AppendVMovLiteral(&code, 7, 0x7e013c00u);
|
||||
|
||||
AppendVop3p(&code, 0x11, 10, Vgpr(0), Vgpr(1), 0, 0x3);
|
||||
AppendVop3p(&code, 0x12, 11, Vgpr(2), Vgpr(3), 0, 0x3);
|
||||
AppendVop3p(&code, 0x11, 12, Vgpr(4), Vgpr(5), 0, 0x3);
|
||||
AppendVop3p(&code, 0x12, 13, Vgpr(4), Vgpr(5), 0, 0x3);
|
||||
AppendVop3p(&code, 0x11, 14, Vgpr(6), Vgpr(7), 0, 0x3);
|
||||
AppendVop3p(&code, 0x12, 15, Vgpr(6), Vgpr(7), 0, 0x3);
|
||||
|
||||
for (u32 i = 0; i < 4; i++) {
|
||||
for (u32 i = 0; i < 6; i++) {
|
||||
AppendStoreVgpr(&code, 10 + i, i);
|
||||
}
|
||||
AppendEnd(&code);
|
||||
@@ -25893,7 +25996,7 @@ TestCase PackedMinMaxF16NanAndSignedZeroEdges() {
|
||||
return {"PackedMinMaxF16NanAndSignedZeroEdges",
|
||||
code,
|
||||
{},
|
||||
{0x80004000u, 0x00004000u, 0x7f017f01u, 0x7f017f01u},
|
||||
{0x80004000u, 0x00004000u, 0x7f017f01u, 0x7f017f01u, 0x40003c00u, 0x40003c00u},
|
||||
{O::V_MOV_B32, O::V_PK_MIN_F16, O::V_PK_MAX_F16,
|
||||
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
||||
}
|
||||
@@ -31053,6 +31156,69 @@ TestCase BufferLoadDwordx2SnapshotsOverlappingAddress() {
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase CoherentBufferLoadDwordx2Bounds(u32 wave, u32 prefix) {
|
||||
using O = ShaderOpcode;
|
||||
TestCase test;
|
||||
test.name = wave == 64 ? (prefix ? "CoherentBufferPairBoundsOffsetWave64"
|
||||
: "CoherentBufferPairBoundsWave64")
|
||||
: (prefix ? "CoherentBufferPairBoundsOffsetWave32"
|
||||
: "CoherentBufferPairBoundsWave32");
|
||||
test.initial = {0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u};
|
||||
test.initial.resize(12);
|
||||
test.expected = test.initial;
|
||||
const u32 addresses[] = {0u, 4u, 8u, 0xfffffffcu};
|
||||
const u32 base = prefix / 4;
|
||||
const u32 values[][2] = {{test.initial[base], test.initial[base + 1]},
|
||||
{test.initial[base + 1], test.initial[base + 2]},
|
||||
{test.initial[base + 2], 0u},
|
||||
{0u, test.initial[base]}};
|
||||
for (u32 i = 0; i < std::size(addresses); ++i) {
|
||||
AppendVMovLiteral(&test.code, 20, addresses[i]);
|
||||
test.code.push_back(EncodeMubuf0(0x0d, 0, false, true, true) | (1u << 15u));
|
||||
test.code.push_back(EncodeMubuf1(2, 0, 20));
|
||||
for (u32 component = 0; component < 2; ++component) {
|
||||
AppendStoreVgpr(&test.code, 2 + component, 4 + i * 2 + component);
|
||||
test.expected[4 + i * 2 + component] = values[i][component];
|
||||
}
|
||||
}
|
||||
AppendEnd(&test.code);
|
||||
test.user_data = MakeStructuredStorageBufferData(0, 12);
|
||||
test.has_user_data = true;
|
||||
test.storage_buffer_offsets = {prefix};
|
||||
test.storage_buffer_ranges = {{0u, 12u + prefix}};
|
||||
test.required_spirv = {"OpAtomicLoad %ulong", "Int64Atomics",
|
||||
"ArrayStride 8"};
|
||||
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORDX2, O::BUFFER_STORE_DWORD,
|
||||
O::S_ENDPGM};
|
||||
test.compute_info.wave_size = wave;
|
||||
test.has_compute_info = true;
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase CoherentBufferLoadDwordx2Swizzled() {
|
||||
using O = ShaderOpcode;
|
||||
TestCase test;
|
||||
test.name = "CoherentBufferLoadDwordx2Swizzled";
|
||||
AppendVMovU32(&test.code, 20, 0);
|
||||
test.code.push_back(EncodeMubuf0(0x0d, 0, true, false, true) | (1u << 15u));
|
||||
test.code.push_back(EncodeMubuf1(2, 0, 20));
|
||||
AppendStoreVgpr(&test.code, 2, 9);
|
||||
AppendStoreVgpr(&test.code, 3, 10);
|
||||
AppendEnd(&test.code);
|
||||
test.initial = {0x11111111u, 0x22222222u, 0, 0, 0, 0,
|
||||
0, 0, 0x33333333u, 0, 0};
|
||||
test.expected = test.initial;
|
||||
test.expected[9] = test.initial[0];
|
||||
test.expected[10] =
|
||||
test.initial[8]; // Four-byte elements span eight-index groups.
|
||||
test.user_data = MakeStructuredStorageBufferData(8, 8);
|
||||
test.user_data[1] |= 1u << 31u;
|
||||
test.has_user_data = true;
|
||||
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORDX2, O::BUFFER_STORE_DWORD,
|
||||
O::S_ENDPGM};
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase BufferLoadDwordx3SnapshotsOverlappingAddress() {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
@@ -34698,6 +34864,60 @@ TestCase DsAtomicReturn64() {
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase DsCompareStoreReturn32() {
|
||||
using O = ShaderOpcode;
|
||||
TestCase test;
|
||||
test.name = "DsCompareStoreReturn32";
|
||||
auto &code = test.code;
|
||||
const auto result = [&](u32 reg, u32 value) {
|
||||
AppendStoreVgpr(&code, reg, static_cast<u32>(test.expected.size()));
|
||||
test.expected.push_back(value);
|
||||
};
|
||||
struct Case { u32 before, compare, replacement; };
|
||||
constexpr std::array cases{Case{0x12345678u, 0x12345678u, 0x9abcdef0u},
|
||||
Case{0x12345678u, 0x9abcdef0u, 0x12345678u},
|
||||
Case{0x80000000u, 0u, 0x7fffffffu},
|
||||
Case{UINT32_MAX, UINT32_MAX, 0u}};
|
||||
AppendVMovU32(&code, 7, 0);
|
||||
for (const auto &item : cases) {
|
||||
AppendVMovLiteral(&code, 2, item.before);
|
||||
code.push_back(EncodeDs0(0x0d, 8));
|
||||
code.push_back(EncodeDs1(0, 2, 7));
|
||||
AppendVMovLiteral(&code, 5, item.compare);
|
||||
AppendVMovLiteral(&code, 19, item.replacement);
|
||||
AppendVMovU32(&code, 4, 8);
|
||||
// CS04b812daaacd532f pc 0x3e8: DATA compares, DATA2 replaces, VDst aliases ADDR.
|
||||
code.insert(code.end(), {0xd8c00000u, 0x04130504u});
|
||||
result(4, item.before);
|
||||
code.push_back(EncodeDs0(0x36, 8));
|
||||
code.push_back(EncodeDs1(8, 0, 7));
|
||||
result(8, item.before == item.compare ? item.replacement : item.before);
|
||||
}
|
||||
AppendVMovU32(&code, 5, 0);
|
||||
AppendVMovU32(&code, 4, 12); // Past the three LDS DWORDs.
|
||||
code.insert(code.end(), {0xd8c00000u, 0x04130504u});
|
||||
result(4, 0);
|
||||
AppendVMovLiteral(&code, 2, 0x12345678u);
|
||||
code.push_back(EncodeDs0(0x0d, 8));
|
||||
code.push_back(EncodeDs1(0, 2, 7));
|
||||
AppendVMovLiteral(&code, 5, 0x12345678u);
|
||||
AppendVMovU32(&code, 4, 8);
|
||||
code.push_back(EncodeSMovB32(126, InlineU32(0)));
|
||||
code.insert(code.end(), {0xd8c00000u, 0x04130504u});
|
||||
code.push_back(EncodeSMovB32(126, InlineU32(1)));
|
||||
result(4, 8); // An inactive lane keeps its destination and leaves memory alone.
|
||||
code.push_back(EncodeDs0(0x36, 8));
|
||||
code.push_back(EncodeDs1(8, 0, 7));
|
||||
result(8, 0x12345678u);
|
||||
AppendEnd(&code);
|
||||
test.compute_info.lds_size_dwords = 3;
|
||||
test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::DS_WRITE_B32, O::DS_CMPST_RTN_B32,
|
||||
O::DS_READ_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
test.required_spirv = {"OpAtomicCompareExchange %uint"};
|
||||
test.forbidden_spirv = {"OpLoopMerge"};
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase DsAtomic64Contention(bool add, u32 wave_size) {
|
||||
using O = ShaderOpcode;
|
||||
TestCase test;
|
||||
@@ -35548,6 +35768,65 @@ TestCase Wave64AppendConsumeHighHalf() {
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase CoherentBufferPairContention(u32 wave_size) {
|
||||
using O = ShaderOpcode;
|
||||
TestCase test;
|
||||
test.name = wave_size == 64 ? "CoherentBufferPairContentionWave64"
|
||||
: "CoherentBufferPairContentionWave32";
|
||||
auto &code = test.code;
|
||||
AppendVMovU32(&code, 20, 0);
|
||||
AppendVMovU32(&code, 6, 0);
|
||||
AppendSMovLiteral(&code, 20, 128);
|
||||
const auto loop = code.size();
|
||||
code.push_back(EncodeVop2(0x25, 2, InlineU32(1), 0));
|
||||
code.push_back(
|
||||
EncodeVop1(0x37, 3, Vgpr(2))); // v_not_b32: correlated high word.
|
||||
AppendBufferStoreOpcode(&code, 0x50, 2, 20);
|
||||
code.push_back(EncodeMubuf0(0x0d, 0, false, true, true) | (1u << 15u));
|
||||
code.push_back(EncodeMubuf1(4, 12, 20));
|
||||
code.push_back(EncodeVop2(0x1d, 4, Vgpr(4), 5));
|
||||
code.push_back(EncodeVop1(0x37, 4, Vgpr(4)));
|
||||
code.push_back(EncodeVop2(0x1c, 6, Vgpr(4), 6));
|
||||
code.push_back(EncodeSop2(0x01, 20, 20, InlineU32(1)));
|
||||
code.push_back(EncodeSopc(0x07, 20, InlineU32(0)));
|
||||
code.push_back(EncodeSopp(0x05, static_cast<u32>(loop - code.size() - 1)));
|
||||
AppendStoreVgprAtLaneDwordOffset(&code, 6, 0, 2);
|
||||
code.push_back(EncodeSopp(0x0a, 0));
|
||||
// Restore a deterministic mailbox after all readers finish.
|
||||
code.push_back(EncodeSop1(0x04, 126, InlineU32(1)));
|
||||
AppendVMovU32(&code, 2, 0);
|
||||
AppendVMovLiteral(&code, 3, 0xffffffffu);
|
||||
AppendBufferStoreOpcode(&code, 0x50, 2, 20);
|
||||
AppendEnd(&code);
|
||||
test.initial.assign(wave_size * 2 + 2, 0);
|
||||
test.initial[1] = 0xffffffffu;
|
||||
test.expected = test.initial;
|
||||
test.required_spirv = {"OpAtomicExchange %ulong", "OpAtomicLoad %ulong",
|
||||
"Int64Atomics", "OpLoopMerge"};
|
||||
test.opcodes = {O::V_MOV_B32,
|
||||
O::S_MOV_B32,
|
||||
O::S_MOV_B64,
|
||||
O::V_ADD_NC_U32,
|
||||
O::V_NOT_B32,
|
||||
O::V_XOR_B32,
|
||||
O::V_OR_B32,
|
||||
O::BUFFER_ATOMIC_SWAP_X2,
|
||||
O::BUFFER_LOAD_DWORDX2,
|
||||
O::S_SUB_U32,
|
||||
O::S_CMP_LG_U32,
|
||||
O::S_CBRANCH_SCC1,
|
||||
O::V_LSHLREV_B32,
|
||||
O::BUFFER_STORE_DWORD,
|
||||
O::S_BARRIER,
|
||||
O::S_ENDPGM};
|
||||
test.compute_info.threads_num[0] = wave_size * 2;
|
||||
test.compute_info.threads_num[1] = test.compute_info.threads_num[2] = 1;
|
||||
test.compute_info.thread_ids_num = 1;
|
||||
test.compute_info.wave_size = wave_size;
|
||||
test.has_compute_info = true;
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase BufferWorkgroupPublication(u32 wave_size, bool dlc_only = false,
|
||||
bool store_completion = false) {
|
||||
using O = ShaderOpcode;
|
||||
@@ -41958,6 +42237,7 @@ std::vector<TestCase> MakeCases() {
|
||||
AddCase(VectorBfeI32SignExtendsField);
|
||||
AddCase(VectorAlignByteUsesTwoBitByteOffset);
|
||||
AddCase(VectorCarryAndBitCountOps);
|
||||
AddCase(ScalarVector64BitFloatInlineConstants);
|
||||
AddCase(VectorMbcntUsesThreadMask);
|
||||
AddCase(VectorAddcWritesPerLaneCarryOut);
|
||||
AddCase(VectorAddcUsesPerLaneCarryIn);
|
||||
@@ -41976,6 +42256,7 @@ std::vector<TestCase> MakeCases() {
|
||||
AddCase(Vop3pInt16Ops);
|
||||
AddCase(CvtPkU8F32PacksSelectedByte);
|
||||
AddCase(CvtPkrtzF16F32SubnormalRoundsTowardZero);
|
||||
AddCase(CvtPkrtzF16F32RuntimeEdges);
|
||||
AddCase(CvtPkrtzF16F32SdwaAndOutputModifiers);
|
||||
AddCase(PackedMinMaxF16NanAndSignedZeroEdges);
|
||||
AddCase(VectorMinMaxF16Ops);
|
||||
@@ -42133,6 +42414,12 @@ std::vector<TestCase> MakeCases() {
|
||||
cases.push_back(BufferSubwordStoresAtHostOffset(2));
|
||||
cases.push_back(BufferSubwordStoresAtHostOffset(1));
|
||||
AddCase(BufferLoadDwordx2SnapshotsOverlappingAddress);
|
||||
for (const u32 wave : {32u, 64u}) {
|
||||
for (const u32 prefix : {0u, 4u})
|
||||
cases.push_back(CoherentBufferLoadDwordx2Bounds(wave, prefix));
|
||||
cases.push_back(CoherentBufferPairContention(wave));
|
||||
}
|
||||
AddCase(CoherentBufferLoadDwordx2Swizzled);
|
||||
AddCase(BufferLoadDwordx3SnapshotsOverlappingAddress);
|
||||
AddCase(BufferLoadDwordx4SnapshotsOverlappingAddress);
|
||||
AddCase(BufferLoadDwordx4ZeroesOnlyOutOfBoundsTail);
|
||||
@@ -42276,6 +42563,7 @@ std::vector<TestCase> MakeCases() {
|
||||
}
|
||||
AddCase(DsAtomicReturnVariants);
|
||||
AddCase(DsAtomicReturn64);
|
||||
AddCase(DsCompareStoreReturn32);
|
||||
for (bool add : {false, true}) {
|
||||
cases.push_back(DsAtomic64Bounds(add));
|
||||
for (u32 wave_size : {32, 64}) {
|
||||
@@ -45486,12 +45774,12 @@ void CheckDepthAttachmentWrites() {
|
||||
|
||||
target.depth_test_enable = false;
|
||||
target.depth_write_enable = false;
|
||||
target.depth_load_clear_enable = true;
|
||||
target.load_clear_aspects = vk::ImageAspectFlagBits::eDepth;
|
||||
Require("DepthAttachmentWrites", "depth clear",
|
||||
target.AttachmentWriteAspects() == vk::ImageAspectFlagBits::eDepth,
|
||||
"depth attachment clear did not claim depth");
|
||||
|
||||
target.depth_load_clear_enable = false;
|
||||
target.load_clear_aspects = {};
|
||||
target.stencil_test_enable = true;
|
||||
target.stencil_front = {vk::StencilOp::eKeep, vk::StencilOp::eKeep,
|
||||
vk::StencilOp::eKeep, vk::CompareOp::eAlways, 0xff, 0xff, 0};
|
||||
@@ -45517,7 +45805,7 @@ void CheckDepthAttachmentWrites() {
|
||||
"masked front write or KEEP-only back face claimed stencil");
|
||||
|
||||
target.stencil_test_enable = false;
|
||||
target.stencil_clear_enable = true;
|
||||
target.load_clear_aspects = vk::ImageAspectFlagBits::eStencil;
|
||||
Require("DepthAttachmentWrites", "stencil clear",
|
||||
target.AttachmentWriteAspects() == vk::ImageAspectFlagBits::eStencil,
|
||||
"stencil attachment clear did not claim stencil");
|
||||
@@ -45591,12 +45879,11 @@ void CheckDepthFeedbackAspects() {
|
||||
feedback(sampled) == vk::ImageAspectFlagBits::eDepth,
|
||||
"overlapping mip and layer boundaries missed depth feedback");
|
||||
target.depth_write_enable = false;
|
||||
target.depth_load_clear_enable = true;
|
||||
target.load_clear_aspects = vk::ImageAspectFlagBits::eDepth;
|
||||
Require("DepthFeedbackAspects", "depth clear only",
|
||||
feedback(sampled) == vk::ImageAspectFlagBits::eDepth,
|
||||
"a sampled depth attachment clear missed feedback");
|
||||
target.depth_load_clear_enable = false;
|
||||
target.stencil_clear_enable = true;
|
||||
target.load_clear_aspects = vk::ImageAspectFlagBits::eStencil;
|
||||
sampled.aspect = vk::ImageAspectFlagBits::eStencil;
|
||||
Require("DepthFeedbackAspects", "stencil clear only",
|
||||
feedback(sampled) == vk::ImageAspectFlagBits::eStencil,
|
||||
@@ -45650,7 +45937,7 @@ void CheckDynamicRenderingState() {
|
||||
depth_attachment_layout(attachment) ==
|
||||
vk::ImageLayout::eDepthStencilReadOnlyOptimal,
|
||||
"fully read-only depth/stencil used a writable layout");
|
||||
attachment.depth_load_clear_enable = true;
|
||||
attachment.load_clear_aspects = vk::ImageAspectFlagBits::eDepth;
|
||||
Require("DynamicRenderingState", "deferred depth clear layout",
|
||||
depth_attachment_layout(attachment) ==
|
||||
vk::ImageLayout::eDepthAttachmentStencilReadOnlyOptimal,
|
||||
@@ -45661,9 +45948,9 @@ void CheckDynamicRenderingState() {
|
||||
depth_attachment_layout(attachment) ==
|
||||
vk::ImageLayout::eDepthAttachmentStencilReadOnlyOptimal,
|
||||
"depth-only writes did not retain read-only stencil");
|
||||
attachment.depth_load_clear_enable = false;
|
||||
attachment.load_clear_aspects = {};
|
||||
attachment.depth_write_enable = false;
|
||||
attachment.stencil_clear_enable = true;
|
||||
attachment.load_clear_aspects = vk::ImageAspectFlagBits::eStencil;
|
||||
Require("DynamicRenderingState", "depth-read stencil-write layout",
|
||||
depth_attachment_layout(attachment) ==
|
||||
vk::ImageLayout::eDepthReadOnlyStencilAttachmentOptimal,
|
||||
@@ -47786,6 +48073,13 @@ int main(int argc, char **argv) {
|
||||
std::setvbuf(stdout, nullptr, _IONBF, 0);
|
||||
EnsureConfigInitialized();
|
||||
CheckLeastRecentlyUsedCacheOrdering();
|
||||
if (argc == 2 && std::strcmp(argv[1], "--cvt-pkrtz-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
RunCase(&vulkan, CvtPkrtzF16F32SubnormalRoundsTowardZero());
|
||||
RunCase(&vulkan, CvtPkrtzF16F32RuntimeEdges());
|
||||
RunCase(&vulkan, CvtPkrtzF16F32SdwaAndOutputModifiers());
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--thread-dimensions-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
CheckComputeThreadDimensions(vulkan);
|
||||
@@ -47800,7 +48094,8 @@ int main(int argc, char **argv) {
|
||||
RunCase(&vulkan, DsWideLdsPartialBounds());
|
||||
RunCase(&vulkan, DsAtomic64Bounds(false));
|
||||
RunCase(&vulkan, DsAtomic64Bounds(true));
|
||||
for (auto test : {DsWideLdsPartialBounds(), DsAtomic64Bounds(false), DsAtomic64Bounds(true), DsAtomicReturn64()}) {
|
||||
for (auto test : {DsWideLdsPartialBounds(), DsAtomic64Bounds(false), DsAtomic64Bounds(true), DsAtomicReturn64(),
|
||||
DsCompareStoreReturn32()}) {
|
||||
test.compute_info.lds_storage = true;
|
||||
std::erase(test.required_spirv, "WorkgroupMemoryExplicitLayoutKHR");
|
||||
test.required_spirv.push_back("StorageBuffer");
|
||||
@@ -48324,6 +48619,12 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--buffer-publication-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
for (const u32 wave : {32u, 64u}) {
|
||||
for (const u32 prefix : {0u, 4u})
|
||||
RunCase(&vulkan, CoherentBufferLoadDwordx2Bounds(wave, prefix));
|
||||
RunCase(&vulkan, CoherentBufferPairContention(wave));
|
||||
}
|
||||
RunCase(&vulkan, CoherentBufferLoadDwordx2Swizzled());
|
||||
RunCase(&vulkan, BufferWorkgroupPublication(32));
|
||||
RunCase(&vulkan, BufferWorkgroupPublication(64));
|
||||
RunCase(&vulkan, BufferWorkgroupPublication(32, true));
|
||||
|
||||
@@ -8119,6 +8119,24 @@ void TestDsReturningAtomic64Decoder() {
|
||||
"captured DS_CMPST_RTN_B64 lost its comparison/replacement pairs or aliases");
|
||||
}
|
||||
|
||||
void TestDsCompareStore32Decoder() {
|
||||
using namespace ShaderRecompiler::Decoder;
|
||||
// CS04b812daaacd532f pc 0x3e8 (AC Valhalla, #281).
|
||||
const uint32_t words[] = {0xd8c00000u, 0x04130504u};
|
||||
Instruction decoded;
|
||||
DecodeInstruction(words, 0u, decoded);
|
||||
Check(decoded.opcode == Opcode::DS_CMPST_RTN_B32 && decoded.word_count == 2u &&
|
||||
decoded.data_dwords == 1u && decoded.src_count == 3u && decoded.src0.reg == 4u &&
|
||||
decoded.src1.reg == 5u && decoded.src2.reg == 19u && decoded.dst.reg == 4u &&
|
||||
decoded.offset == 0u && !decoded.gds,
|
||||
"captured DS_CMPST_RTN_B32 lost its comparison/replacement order or aliases");
|
||||
|
||||
const uint32_t gds_words[] = {words[0] | (1u << 17u), words[1]};
|
||||
DecodeInstruction(gds_words, 0u, decoded);
|
||||
Check(decoded.opcode == Opcode::UNSUPPORTED && decoded.word_count == 2u && decoded.gds,
|
||||
"DS_CMPST_RTN_B32 must reject the unimplemented GDS variant");
|
||||
}
|
||||
|
||||
void TestDsFloatMinMaxDecoder() {
|
||||
using namespace ShaderRecompiler::Decoder;
|
||||
for (bool max_value : {false, true}) {
|
||||
@@ -11090,12 +11108,13 @@ void TestMergedShaderUserDataSnapshot() {
|
||||
ShaderMapUserData(regs.gs_regs.data_addr, mapped);
|
||||
HW::Context context;
|
||||
context.SetShaderStages(0x20);
|
||||
context.SetMaxOutputPerSubgroup(252);
|
||||
context.SetMaxOutputPerSubgroup(192);
|
||||
context.SetGsMaxVertOut(18);
|
||||
context.SetNggSubgrpCntl(16);
|
||||
context.SetGsOutPrimType(2);
|
||||
HW::UserConfig user_config;
|
||||
user_config.SetPrimitiveType(Prospero::PrimitiveType::kTriList);
|
||||
user_config.SetGeControl({14, 14});
|
||||
user_config.SetGeControl({62, 192});
|
||||
ShaderVertexInputInfo first_input{};
|
||||
const auto first = PrepareProgram(regs, context, user_config, first_input);
|
||||
regs.gs_regs.user_data_addr = reinterpret_cast<uint64_t>(second_table.data());
|
||||
@@ -11108,8 +11127,10 @@ void TestMergedShaderUserDataSnapshot() {
|
||||
"merged shader parameters did not snapshot ordinary user SGPRs at s8");
|
||||
Check(first.hash == second.hash &&
|
||||
MakeStageStaticKey(first_input) == MakeStageStaticKey(second_input) &&
|
||||
first_input.mesh.scratch_size_dwords == 7,
|
||||
"merged shader identity or maximum scratch size changed with dynamic user data");
|
||||
first_input.mesh.scratch_size_dwords == 7 &&
|
||||
first_input.mesh.primitives_per_group == 10 &&
|
||||
first_input.mesh.max_primitives == 160 && first_input.mesh.threads_num[0] == 192,
|
||||
"merged shader lost its identity, scratch size or amplified subgroup bounds");
|
||||
CompileOptions options{};
|
||||
options.stage = ShaderType::Mesh;
|
||||
options.user_data_base = 0;
|
||||
@@ -11172,6 +11193,7 @@ void TestMergedShaderUserDataSnapshot() {
|
||||
regs.gs_regs.rsrc2.lds_size = 64;
|
||||
regs.gs_regs.rsrc1.gs_vgpr_component_count = 1;
|
||||
context.SetGsMaxVertOut(0);
|
||||
context.SetNggSubgrpCntl(1);
|
||||
for (const uint32_t wave_size : {32u, 64u}) {
|
||||
context.SetShaderStages(wave_size == 32 ? 0x00400000u : 0u);
|
||||
for (const uint16_t capacity : {64u, 128u, 256u}) {
|
||||
@@ -11197,18 +11219,31 @@ void TestMergedShaderUserDataSnapshot() {
|
||||
context.SetShaderStages(0x20);
|
||||
context.SetMaxOutputPerSubgroup(256);
|
||||
context.SetGsMaxVertOut(8);
|
||||
context.SetNggSubgrpCntl(6);
|
||||
user_config.SetPrimitiveType(Prospero::PrimitiveType::kTriFan);
|
||||
user_config.SetGeControl({32, 32});
|
||||
ShaderVertexInputInfo fan_input{};
|
||||
PrepareProgram(regs, context, user_config, fan_input);
|
||||
Check(fan_input.mesh.primitives_per_group == 30 && fan_input.mesh.vertices_per_group == 32 &&
|
||||
fan_input.mesh.max_vertices == 256 && fan_input.mesh.max_primitives == 192,
|
||||
fan_input.mesh.max_vertices == 256 && fan_input.mesh.max_primitives == 180,
|
||||
"captured triangle-fan GS configuration lost its subgroup assembly limits");
|
||||
context.SetShaderStages(0x00482030u);
|
||||
context.SetShaderStages(0x00402030u);
|
||||
context.SetMaxOutputPerSubgroup(32);
|
||||
context.SetGsMaxVertOut(32);
|
||||
user_config.SetPrimitiveType(Prospero::PrimitiveType::kPointList);
|
||||
user_config.SetGeControl({1, 1});
|
||||
for (const uint32_t amplification : {32u, 48u}) {
|
||||
context.SetNggSubgrpCntl(amplification);
|
||||
ShaderVertexInputInfo input{};
|
||||
PrepareProgram(regs, context, user_config, input);
|
||||
Check(!input.mesh.fast_launch && input.mesh.primitives_per_group == 1 &&
|
||||
input.mesh.vertices_per_group == 1 && input.mesh.max_vertices == 32 &&
|
||||
input.mesh.max_primitives == amplification &&
|
||||
input.mesh.threads_num[0] == (amplification == 32u ? 32u : 64u),
|
||||
"native NGG output primitives were derived from a vertex strip or omitted from subgroup size");
|
||||
}
|
||||
context.SetShaderStages(0x00482030u);
|
||||
context.SetNggSubgrpCntl(32);
|
||||
regs.gs_regs.rsrc1.gs_vgpr_component_count = 0;
|
||||
regs.gs_regs.rsrc2.es_vgpr_component_count = 1;
|
||||
ShaderVertexInputInfo fast_input{};
|
||||
@@ -14490,6 +14525,7 @@ void TestPatternFill() {
|
||||
const PatternCase cases[] = {
|
||||
{bounded_shader, 64, true, 10},
|
||||
{thread_dimension_shader, 32, false, 8},
|
||||
{thread_dimension_shader, 64, false, 8},
|
||||
{thread_dimension_wave64_shader, 64, false, 9},
|
||||
{thread_dimension_wave64_scalar_shader, 64, false, 8},
|
||||
{thread_dimension_wave64_vector_shader, 64, false, 8},
|
||||
@@ -14566,6 +14602,8 @@ void TestPatternFill() {
|
||||
Check(Build(changed).uniform_fill.fill.kind == UniformFillKind::None,
|
||||
"pattern recognition accepted modified executable instructions");
|
||||
for (const uint32_t invalid_input : {0u, 1u, 2u, 3u}) {
|
||||
// Both wave sizes are covered above for this executable.
|
||||
if (invalid_input == 1 && shader.data() == thread_dimension_shader) continue;
|
||||
const auto saved_compute = compute;
|
||||
const auto saved_wave = options.wave_size;
|
||||
if (invalid_input == 0) compute.workgroup_register = 11;
|
||||
@@ -16480,6 +16518,7 @@ int main() {
|
||||
TestScalarAshrI64Decoder();
|
||||
TestImageAtomicWidthDecoder();
|
||||
TestDsReturningAtomic64Decoder();
|
||||
TestDsCompareStore32Decoder();
|
||||
TestDsFloatMinMaxDecoder();
|
||||
TestNewShaderDecoderArchitecture();
|
||||
TestImageAddressOperands();
|
||||
|
||||
Reference in New Issue
Block a user