Compare commits

..
Author SHA1 Message Date
nmzikandChai T 37e4861e1d np: separate PSN emulation from host networking
Co-authored-by: Chai T <chaitukca@gmail.com>
2026-10-29 08:30:47 +01:00
nmzik 5e837a9bea shader: share formatted indirect buffer load emission 2026-10-29 05:52:04 +01:00
nmzikandFrenzied a0b99a8c21 ngs2: accept larger custom voice state buffers
Co-authored-by: Frenzied <frenziedsaint@gmail.com>
2026-10-29 05:52:04 +01:00
nmzikandFrenzied 8cfe1c4d34 ngs2: apply custom channel matrices before mastering output
Co-authored-by: Frenzied <frenziedsaint@gmail.com>
2026-10-29 05:52:04 +01:00
Çağatay Güçlü f71da54b9b shader: support fractional-even tessellation spacing 2026-10-29 05:52:04 +01:00
Çağatay Güçlü 63207739bc shader: initialize local vertex count from active lanes 2026-10-29 05:52:04 +01:00
Çağatay Güçlü 143f640706 shader: preserve counterclockwise tessellation output 2026-10-29 05:52:04 +01:00
nmzik 54b937c85f shader: support high products of 24-bit integers 2026-10-28 22:49:21 +01:00
nmzik 22610cb93e shader: support unsigned 16-bit multiply-add 2026-10-28 22:41:29 +01:00
nmzik 0386f33bf3 shader: support four 16-bit integer ternary operations 2026-10-28 22:37:24 +01:00
nmzik 138d5b86c4 shader: support S_QUADMASK_B32 2026-10-28 22:31:16 +01:00
nmzik 963aaeb3b6 gpu: preserve low-alpha 2/10/10/10 render-target storage 2026-10-28 22:29:50 +01:00
Çağatay Güçlü 77458260f1 shader: reflect masked hull control-point addresses 2026-10-28 22:19:09 +01:00
nmzik 41de2fd64e shader: support five VOP3 float conversion forms 2026-10-28 22:19:02 +01:00
Çağatay Güçlü 128c938514 shader: support quad tessellation domains and factor routing 2026-10-28 22:09:39 +01:00
Chai T a8e3c29b2e shader: implement V_ASHRREV_I64 (VOP3 0x301) 2026-10-28 21:51:28 +01:00
36 changed files with 1554 additions and 311 deletions

No files matched your search

+10
View File
@@ -692,6 +692,16 @@ if(BUILD_TESTING)
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --tessellation-only) COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --tessellation-only)
add_test(NAME shader_recompiler_float_min_max add_test(NAME shader_recompiler_float_min_max
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --float-min-max-only) COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --float-min-max-only)
add_test(NAME shader_recompiler_vop3_conversions
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --vop3-conversions-only)
add_test(NAME shader_recompiler_quadmask
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --quadmask-only)
add_test(NAME shader_recompiler_integer16_ternary
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --integer16-ternary-only)
add_test(NAME shader_recompiler_mad_u16
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --mad-u16-only)
add_test(NAME shader_recompiler_mul_hi24
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --mul-hi24-only)
add_test(NAME shader_recompiler_alignbyte add_test(NAME shader_recompiler_alignbyte
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --alignbyte-only) COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --alignbyte-only)
add_test(NAME graphics_pipeline_rasterization add_test(NAME graphics_pipeline_rasterization
+4
View File
@@ -133,6 +133,10 @@ bool IsNetworkConnected() {
return g_config->network_connected; return g_config->network_connected;
} }
bool PsnEmulationEnabled() {
return g_config->psn_emulation_enabled;
}
bool ShaderValidationEnabled() { bool ShaderValidationEnabled() {
return g_config->shader_validation_enabled; return g_config->shader_validation_enabled;
} }
+2
View File
@@ -69,6 +69,7 @@ struct ConfigOptions {
bool vr_enabled = false; bool vr_enabled = false;
bool amd_cpu_enabled = false; bool amd_cpu_enabled = false;
bool network_connected = false; bool network_connected = false;
bool psn_emulation_enabled = false;
uint32_t vblank_frequency = 60; uint32_t vblank_frequency = 60;
uint32_t console_language = DEFAULT_CONSOLE_LANGUAGE; uint32_t console_language = DEFAULT_CONSOLE_LANGUAGE;
bool vulkan_validation_enabled = false; bool vulkan_validation_enabled = false;
@@ -130,6 +131,7 @@ bool StretchEnabled();
bool VrEnabled(); bool VrEnabled();
bool AmdCpuEnabled(); bool AmdCpuEnabled();
bool IsNetworkConnected(); bool IsNetworkConnected();
bool PsnEmulationEnabled();
uint32_t GetVblankFrequency(); uint32_t GetVblankFrequency();
uint32_t GetConsoleLanguage(); uint32_t GetConsoleLanguage();
bool VulkanValidationEnabled(); bool VulkanValidationEnabled();
+1
View File
@@ -46,6 +46,7 @@ constexpr FormatInfo kFormatInfo[] = {
{BufferFormat::k11_11_10UInt, 4, 0, 4, true, true}, {BufferFormat::k11_11_10UInt, 4, 0, 4, true, true},
{BufferFormat::k11_11_10Float, 4, 0, 4, true, false}, {BufferFormat::k11_11_10Float, 4, 0, 4, true, false},
{BufferFormat::k10_11_11Float, 4, 0, 4, true, false}, {BufferFormat::k10_11_11Float, 4, 0, 4, true, false},
{BufferFormat::k2_10_10_10UNorm, 4, 0, 4, true, false},
{BufferFormat::k10_10_10_2UNorm, 4, 0, 4, true, false}, {BufferFormat::k10_10_10_2UNorm, 4, 0, 4, true, false},
{BufferFormat::k10_10_10_2UInt, 4, 0, 4, true, true}, {BufferFormat::k10_10_10_2UInt, 4, 0, 4, true, true},
{BufferFormat::k8_8_8_8UNorm, 4, 0, 4, true, false}, {BufferFormat::k8_8_8_8UNorm, 4, 0, 4, true, false},
@@ -17,7 +17,7 @@
namespace Libs::Graphics { namespace Libs::Graphics {
enum class ColorTransform : uint32_t { None, SwapBgra16, Reverse10_11_11 }; enum class ColorTransform : uint32_t { None, SwapBgra16, Reverse10_11_11, Rotate2_10_10_10 };
enum class VideoOutCompression : uint8_t { enum class VideoOutCompression : uint8_t {
Uncompressed, Uncompressed,
@@ -105,6 +105,8 @@ struct ImageInfo {
switch (guest_format) { switch (guest_format) {
case Prospero::BufferFormat::k10_11_11Float: case Prospero::BufferFormat::k10_11_11Float:
return ColorTransform::Reverse10_11_11; return ColorTransform::Reverse10_11_11;
case Prospero::BufferFormat::k2_10_10_10UNorm:
return ColorTransform::Rotate2_10_10_10;
default: return ColorTransform::None; default: return ColorTransform::None;
} }
} }
@@ -32,8 +32,12 @@ struct HostFormatInfo {
HostFormatInfo ResolveHostFormat(Prospero::BufferFormat guest_format, HostFormatInfo ResolveHostFormat(Prospero::BufferFormat guest_format,
Prospero::ChannelOrder order) { Prospero::ChannelOrder order) {
if (guest_format == Prospero::BufferFormat::k10_11_11Float) { switch (guest_format) {
case Prospero::BufferFormat::k10_11_11Float:
return {vk::Format::eB10G11R11UfloatPack32, Prospero::ColorMappingBgra}; return {vk::Format::eB10G11R11UfloatPack32, Prospero::ColorMappingBgra};
case Prospero::BufferFormat::k2_10_10_10UNorm:
return {vk::Format::eA2R10G10B10UnormPack32, Prospero::ColorMappingAbgr};
default: break;
} }
if (order == Prospero::ChannelOrder::kAlt) { if (order == Prospero::ChannelOrder::kAlt) {
switch (guest_format) { switch (guest_format) {
@@ -104,7 +104,8 @@ TileManager::Tiling TileManager::Prepare(bool tile, uint64_t tiled_capacity,
block.block_depth == 0 || block.block_size == 0 || info.tiling.texel_width == 0 || block.block_depth == 0 || block.block_size == 0 || info.tiling.texel_width == 0 ||
info.tiling.texel_height == 0 || info.tiled_slice_stride == 0); info.tiling.texel_height == 0 || info.tiled_slice_stride == 0);
EXIT_IF((transform == ColorTransform::SwapBgra16 && block.bytes_per_element != 8u) || EXIT_IF((transform == ColorTransform::SwapBgra16 && block.bytes_per_element != 8u) ||
(transform == ColorTransform::Reverse10_11_11 && block.bytes_per_element != 4u)); ((transform == ColorTransform::Reverse10_11_11 ||
transform == ColorTransform::Rotate2_10_10_10) && block.bytes_per_element != 4u));
const auto checked_multiply = [](uint64_t left, uint64_t right, uint64_t& result) { const auto checked_multiply = [](uint64_t left, uint64_t right, uint64_t& result) {
return (left == 0 || right <= UINT64_MAX / left) && (result = left * right, true); return (left == 0 || right <= UINT64_MAX / left) && (result = left * right, true);
}; };
@@ -560,7 +561,8 @@ void TileManager::TransformColor(Result input, Result output, ColorTransform tra
uint32_t element_bytes = 0; uint32_t element_bytes = 0;
switch (transform) { switch (transform) {
case ColorTransform::SwapBgra16: element_bytes = 8; break; case ColorTransform::SwapBgra16: element_bytes = 8; break;
case ColorTransform::Reverse10_11_11: element_bytes = 4; break; case ColorTransform::Reverse10_11_11:
case ColorTransform::Rotate2_10_10_10: element_bytes = 4; break;
default: EXIT("invalid color transform\n"); default: EXIT("invalid color transform\n");
} }
EXIT_IF(input.size == 0 || input.size % element_bytes != 0 || EXIT_IF(input.size == 0 || input.size % element_bytes != 0 ||
@@ -1,4 +1,4 @@
// ColorTransform values match imageInfo.h. Preserve the floating-point bit fields exactly. // ColorTransform values match imageInfo.h. Preserve the component bit fields exactly.
uvec2 transform_color(uvec2 value, uint transform, bool to_host) { uvec2 transform_color(uvec2 value, uint transform, bool to_host) {
switch (transform) { switch (transform) {
case 1u: case 1u:
@@ -15,6 +15,13 @@ uvec2 transform_color(uvec2 value, uint transform, bool to_host) {
} }
break; break;
} }
case 3u:
if (to_host) {
value.x = (value.x >> 2u) | (value.x << 30u);
} else {
value.x = (value.x << 2u) | (value.x >> 30u);
}
break;
} }
return value; return value;
} }
@@ -156,11 +156,14 @@ void ReflectTessellationLayout(const Decoder::Program& program, bool local,
if (rhs.constant == 0u && third.constant == 8u) value = constant(0u); if (rhs.constant == 0u && third.constant == 8u) value = constant(0u);
} }
break; break;
case Opcode::V_AND_B32: case Opcode::V_AND_B32: {
if ((lhs.kind == Kind::PackedControlPoint && rhs == constant(0xffu)) || const auto mask = lhs.kind == Kind::PackedControlPoint ? rhs
(rhs.kind == Kind::PackedControlPoint && lhs == constant(0xffu))) : rhs.kind == Kind::PackedControlPoint ? lhs
value = constant(0u); : Address {};
if (mask == constant(0xffu)) value = constant(0u);
if (mask == constant(0x1f00u)) value = {Kind::Affine, 256u, 0u};
break; break;
}
case Opcode::V_ADD_NC_U32: value = add(lhs, rhs); break; case Opcode::V_ADD_NC_U32: value = add(lhs, rhs); break;
case Opcode::V_MUL_LO_U32: value = multiply(lhs, rhs); break; case Opcode::V_MUL_LO_U32: value = multiply(lhs, rhs); break;
case Opcode::V_MUL_U32_U24: value = multiply(low24(lhs), low24(rhs)); break; case Opcode::V_MUL_U32_U24: value = multiply(low24(lhs), low24(rhs)); break;
@@ -118,7 +118,7 @@ struct EmitterState {
uint32_t bda_pointer_function = 0; uint32_t bda_pointer_function = 0;
uint32_t indirect_format_function = 0; uint32_t indirect_format_function = 0;
uint32_t indirect_format_geometry_function = 0; uint32_t indirect_format_geometry_function = 0;
std::map<std::array<uint32_t, 3>, uint32_t> indirect_buffer_load_functions; std::map<std::array<uint32_t, 4>, uint32_t> indirect_buffer_load_functions;
uint32_t bvh_intersect_function = 0; uint32_t bvh_intersect_function = 0;
uint32_t gds_variable = 0; uint32_t gds_variable = 0;
uint32_t gds_length = 0; uint32_t gds_length = 0;
@@ -540,7 +540,6 @@ uint32_t EmitF32BitsToF16RtzBits(EmitterState& state, uint32_t bits);
void EmitProgram(EmitterState& state); void EmitProgram(EmitterState& state);
void DefineGetBdaPointer(EmitterState& state); void DefineGetBdaPointer(EmitterState& state);
void DefineIndirectBufferFormats(ValueEmitContext& ctx);
void DefineIndirectBufferLoads(ValueEmitContext& ctx); void DefineIndirectBufferLoads(ValueEmitContext& ctx);
void DefineBvhIntersect(EmitterState& state); void DefineBvhIntersect(EmitterState& state);
uint32_t GetBdaPointer(EmitterState& state, uint32_t address); uint32_t GetBdaPointer(EmitterState& state, uint32_t address);
@@ -1026,10 +1026,8 @@ uint32_t LoadIndirectBuffer(ValueEmitContext& ctx, const IndirectBufferAccess& b
return ConstructU32Composite(state, components, values); return ConstructU32Composite(state, components, values);
} }
uint32_t LoadIndirectFormattedX(ValueEmitContext& ctx, const IR::Inst& inst) { uint32_t LoadIndirectFormattedX(ValueEmitContext& ctx, const IndirectBufferAccess& buffer) {
auto& state = ctx.state; auto& state = ctx.state;
return EmitValueOrZeroIfCondition(state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
const auto buffer = PrepareIndirectBuffer(state, IndirectBufferArgs(ctx, inst), true);
const auto geometry = state.builder.AllocateId(); const auto geometry = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFunctionCall, TypeU32Composite(state, 4), geometry, state.builder.AddFunction(spv::OpFunctionCall, TypeU32Composite(state, 4), geometry,
state.indirect_format_geometry_function, buffer.format); state.indirect_format_geometry_function, buffer.format);
@@ -1072,7 +1070,6 @@ uint32_t LoadIndirectFormattedX(ValueEmitContext& ctx, const IR::Inst& inst) {
state.indirect_format_function, buffer.format, buffer.selector, state.indirect_format_function, buffer.format, buffer.selector,
component, raw); component, raw);
return result; return result;
});
} }
uint32_t LoadRawBuffer(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem, uint32_t LoadRawBuffer(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
@@ -1135,15 +1132,12 @@ uint32_t LoadRawBuffer(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Me
uint32_t LoadBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t components) { uint32_t LoadBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t components) {
auto& state = ctx.state; auto& state = ctx.state;
const auto mem = ctx.Memory(inst); const auto mem = ctx.Memory(inst);
if (mem.kind == IR::ResourceKind::IndirectBuffer && mem.formatted) {
return LoadIndirectFormattedX(ctx, inst);
}
return EmitValueOrDefaultIfCondition( return EmitValueOrDefaultIfCondition(
state, ctx.Arg(inst, inst.NumArgs() - 1), TypeU32Composite(state, components), state, ctx.Arg(inst, inst.NumArgs() - 1), TypeU32Composite(state, components),
ConstantU32CompositeZero(state, components), [&]() { ConstantU32CompositeZero(state, components), [&]() {
if (mem.kind == IR::ResourceKind::IndirectBuffer) { if (mem.kind == IR::ResourceKind::IndirectBuffer) {
const auto function = state.indirect_buffer_load_functions.at( const auto function = state.indirect_buffer_load_functions.at(
{mem.data_bits, components, uint32_t(mem.coherent)}); {mem.data_bits, components, uint32_t(mem.coherent), uint32_t(mem.formatted)});
const auto args = IndirectBufferArgs(ctx, inst); const auto args = IndirectBufferArgs(ctx, inst);
const auto result = state.builder.AllocateId(); const auto result = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFunctionCall, TypeU32Composite(state, components), state.builder.AddFunction(spv::OpFunctionCall, TypeU32Composite(state, components),
@@ -1231,62 +1225,8 @@ void StoreWideShared(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t compo
}); });
} }
} // namespace
uint32_t GetBdaPointer(EmitterState& state, uint32_t address) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFunctionCall, TypeU64(state), result,
state.bda_pointer_function, address);
return result;
}
void DefineIndirectBufferLoads(ValueEmitContext& ctx) {
auto& state = ctx.state;
for (const auto* block: state.program.blocks) {
for (const auto& inst: *block) {
const auto components = IR::BufferComponentCount(inst.GetOpcode());
if (components == 0u || inst.GetOpcode() == IR::ValueOpcode::ReadConstBuffer) continue;
const auto& memory = ctx.Memory(inst);
if (memory.kind != IR::ResourceKind::IndirectBuffer || memory.formatted ||
!memory.SupportsIndirectBufferLoad(inst.GetOpcode())) continue;
auto [entry, inserted] = state.indirect_buffer_load_functions.try_emplace(
std::array {memory.data_bits, components, uint32_t(memory.coherent)}, 0u);
if (!inserted) continue;
const auto function = entry->second = state.builder.AllocateId();
const auto type = TypeU32(state);
const auto result_type = TypeU32Composite(state, components);
const auto signature = state.builder.Type(
spv::OpTypeFunction, result_type, type, type, type, type, type, type, type, type);
const auto name = "load_indirect_buffer_" + std::to_string(memory.data_bits) + "x" +
std::to_string(components) + (memory.coherent ? "_coherent" : "");
state.builder.AddName(function, name.c_str());
state.builder.AddFunction(spv::OpFunction, result_type, function,
spv::FunctionControlMaskNone, signature);
std::array<uint32_t, 8> args;
for (auto& arg: args) {
arg = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFunctionParameter, type, arg);
}
EmitLabel(state, state.builder.AllocateId());
const auto result = LoadIndirectBuffer(ctx, PrepareIndirectBuffer(state, args, false),
memory.data_bits, components, memory.coherent);
state.builder.AddFunction(spv::OpReturnValue, result);
state.builder.AddFunction(spv::OpFunctionEnd);
}
}
}
void DefineIndirectBufferFormats(ValueEmitContext& ctx) { void DefineIndirectBufferFormats(ValueEmitContext& ctx) {
auto& state = ctx.state; auto& state = ctx.state;
const auto needed = std::ranges::any_of(state.program.blocks, [&](const IR::Block* block) {
return std::ranges::any_of(*block, [&](const IR::Inst& inst) {
if (inst.GetOpcode() != IR::ValueOpcode::LoadBufferU32) return false;
const auto& memory = ctx.Memory(inst);
return memory.kind == IR::ResourceKind::IndirectBuffer && memory.formatted;
});
});
if (!needed) return;
const auto type = TypeU32(state); const auto type = TypeU32(state);
constexpr auto format_count = constexpr auto format_count =
static_cast<uint32_t>(Prospero::BufferFormat::k32_32_32_32Float) + 1u; static_cast<uint32_t>(Prospero::BufferFormat::k32_32_32_32Float) + 1u;
@@ -1358,6 +1298,57 @@ void DefineIndirectBufferFormats(ValueEmitContext& ctx) {
state.builder.AddFunction(spv::OpFunctionEnd); state.builder.AddFunction(spv::OpFunctionEnd);
} }
} // namespace
uint32_t GetBdaPointer(EmitterState& state, uint32_t address) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFunctionCall, TypeU64(state), result,
state.bda_pointer_function, address);
return result;
}
void DefineIndirectBufferLoads(ValueEmitContext& ctx) {
auto& state = ctx.state;
for (const auto* block: state.program.blocks) {
for (const auto& inst: *block) {
const auto components = IR::BufferComponentCount(inst.GetOpcode());
if (components == 0u || inst.GetOpcode() == IR::ValueOpcode::ReadConstBuffer) continue;
const auto& memory = ctx.Memory(inst);
if (memory.kind != IR::ResourceKind::IndirectBuffer ||
!memory.SupportsIndirectBufferLoad(inst.GetOpcode())) continue;
auto [entry, inserted] = state.indirect_buffer_load_functions.try_emplace(
std::array {memory.data_bits, components, uint32_t(memory.coherent),
uint32_t(memory.formatted)}, 0u);
if (!inserted) continue;
if (memory.formatted && state.indirect_format_function == 0u) {
DefineIndirectBufferFormats(ctx);
}
const auto function = entry->second = state.builder.AllocateId();
const auto type = TypeU32(state);
const auto result_type = TypeU32Composite(state, components);
const auto signature = state.builder.Type(
spv::OpTypeFunction, result_type, type, type, type, type, type, type, type, type);
const auto name = "load_indirect_buffer_" + std::to_string(memory.data_bits) + "x" +
std::to_string(components) + (memory.coherent ? "_coherent" : "") +
(memory.formatted ? "_formatted" : "");
state.builder.AddName(function, name.c_str());
state.builder.AddFunction(spv::OpFunction, result_type, function,
spv::FunctionControlMaskNone, signature);
std::array<uint32_t, 8> args;
for (auto& arg: args) {
arg = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFunctionParameter, type, arg);
}
EmitLabel(state, state.builder.AllocateId());
const auto buffer = PrepareIndirectBuffer(state, args, memory.formatted);
const auto result = memory.formatted ? LoadIndirectFormattedX(ctx, buffer)
: LoadIndirectBuffer(ctx, buffer, memory.data_bits, components, memory.coherent);
state.builder.AddFunction(spv::OpReturnValue, result);
state.builder.AddFunction(spv::OpFunctionEnd);
}
}
}
void DefineGetBdaPointer(EmitterState& state) { void DefineGetBdaPointer(EmitterState& state) {
if (!state.program.info.uses_dma) { if (!state.program.info.uses_dma) {
return; return;
@@ -675,7 +675,6 @@ void EmitProgram(EmitterState& state) {
} }
DefineGetBdaPointer(state); DefineGetBdaPointer(state);
DefineIndirectBufferLoads(ctx); DefineIndirectBufferLoads(ctx);
DefineIndirectBufferFormats(ctx);
DefineBvhIntersect(state); DefineBvhIntersect(state);
if (program.info.uses_swizzle) { if (program.info.uses_swizzle) {
ctx.scratch_u32_variable = state.builder.AllocateId(); ctx.scratch_u32_variable = state.builder.AllocateId();
@@ -30,11 +30,13 @@ uint32_t TessellationPointer(ValueEmitContext& ctx, const IR::Inst& inst) {
if (kind == Attribute::Factor) { if (kind == Attribute::Factor) {
EXIT_NOT_IMPLEMENTED(!inst.Arg(1).IsImmediate()); EXIT_NOT_IMPLEMENTED(!inst.Arg(1).IsImmediate());
const auto index = inst.Arg(1).U32() / 4u; const auto index = inst.Arg(1).U32() / 4u;
const auto outer = index < 3u; const bool quad = tess.domain == 2u;
EXIT_NOT_IMPLEMENTED(index >= 4u); const auto outer_count = quad ? 4u : 3u;
const bool outer = index < outer_count;
EXIT_NOT_IMPLEMENTED(index >= (quad ? 6u : 4u));
state.builder.AddFunction(spv::OpAccessChain, TypePointer(state, storage, TypeF32(state)), state.builder.AddFunction(spv::OpAccessChain, TypePointer(state, storage, TypeF32(state)),
pointer, outer ? variable : state.tess_inner_variable, pointer, outer ? variable : state.tess_inner_variable,
ConstantU32(state, outer ? index : index - 3u)); ConstantU32(state, outer ? index : index - outer_count));
return pointer; return pointer;
} }
auto address = ctx.Arg(inst, 1); auto address = ctx.Arg(inst, 1);
@@ -128,20 +130,25 @@ void DefineTessellationInterfaces(EmitterState& state) {
void DefineTessellationExecutionModes(EmitterState& state) { void DefineTessellationExecutionModes(EmitterState& state) {
const auto& tess = state.input_info.vertex->tess; const auto& tess = state.input_info.vertex->tess;
// CxVgtTfParam: triangles, integer or fractional-odd partitioning, clockwise output. // Triangle or quad domain, integer or fractional partitioning, triangle output.
EXIT_NOT_IMPLEMENTED(tess.domain != 1u || EXIT_NOT_IMPLEMENTED((tess.domain != 1u && tess.domain != 2u) ||
(tess.partitioning != 0u && tess.partitioning != 2u) || (tess.partitioning != 0u && tess.partitioning != 2u && tess.partitioning != 3u) ||
tess.output_topology != 2u); (tess.output_topology != 2u && tess.output_topology != 3u));
state.builder.RequireCapability(spv::CapabilityTessellation); state.builder.RequireCapability(spv::CapabilityTessellation);
if (state.program.stage == ShaderType::TessellationControl) { if (state.program.stage == ShaderType::TessellationControl) {
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeOutputVertices, state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeOutputVertices,
tess.output_control_points); tess.output_control_points);
} else { } else {
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeTriangles); state.builder.AddExecutionMode(state.main_func, tess.domain == 1u
? spv::ExecutionModeTriangles
: spv::ExecutionModeQuads);
state.builder.AddExecutionMode( state.builder.AddExecutionMode(
state.main_func, tess.partitioning == 0u ? spv::ExecutionModeSpacingEqual state.main_func, tess.partitioning == 0u ? spv::ExecutionModeSpacingEqual
: spv::ExecutionModeSpacingFractionalOdd); : tess.partitioning == 2u ? spv::ExecutionModeSpacingFractionalOdd
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeVertexOrderCw); : spv::ExecutionModeSpacingFractionalEven);
state.builder.AddExecutionMode(state.main_func, tess.output_topology == 2u
? spv::ExecutionModeVertexOrderCw
: spv::ExecutionModeVertexOrderCcw);
} }
} }
@@ -70,6 +70,7 @@ constexpr OpcodeMap SOP1_OPCODE_LIST[] = {
{0x21u, Opcode::S_SWAPPC_B64}, {0x21u, Opcode::S_SWAPPC_B64},
{0x24u, Opcode::S_AND_SAVEEXEC_B64}, {0x24u, Opcode::S_AND_SAVEEXEC_B64},
{0x28u, Opcode::S_ORN2_SAVEEXEC_B64}, {0x28u, Opcode::S_ORN2_SAVEEXEC_B64},
{0x2cu, Opcode::S_QUADMASK_B32},
{0x2du, Opcode::S_QUADMASK_B64}, {0x2du, Opcode::S_QUADMASK_B64},
{0x34u, Opcode::S_ABS_I32}, {0x34u, Opcode::S_ABS_I32},
{0x37u, Opcode::S_ANDN1_SAVEEXEC_B64}, {0x37u, Opcode::S_ANDN1_SAVEEXEC_B64},
@@ -517,6 +517,7 @@ std::string InstructionToString(const Instruction& inst) {
case Opcode::S_NOT_B64: case Opcode::S_NOT_B64:
case Opcode::S_WQM_B32: case Opcode::S_WQM_B32:
case Opcode::S_WQM_B64: case Opcode::S_WQM_B64:
case Opcode::S_QUADMASK_B32:
case Opcode::S_QUADMASK_B64: case Opcode::S_QUADMASK_B64:
case Opcode::S_AND_SAVEEXEC_B32: case Opcode::S_AND_SAVEEXEC_B32:
case Opcode::S_ORN2_SAVEEXEC_B32: case Opcode::S_ORN2_SAVEEXEC_B32:
@@ -63,6 +63,7 @@ enum class Opcode {
S_FLBIT_I32, S_FLBIT_I32,
S_FLBIT_I32_I64, S_FLBIT_I32_I64,
S_BITREPLICATE_B64_B32, S_BITREPLICATE_B64_B32,
S_QUADMASK_B32,
S_QUADMASK_B64, S_QUADMASK_B64,
S_GETPC_B64, S_GETPC_B64,
S_SETPC_B64, S_SETPC_B64,
@@ -248,7 +249,9 @@ enum class Opcode {
V_MIN_I32, V_MIN_I32,
V_MAX_I32, V_MAX_I32,
V_MUL_I32_I24, V_MUL_I32_I24,
V_MUL_HI_I32_I24,
V_MUL_U32_U24, V_MUL_U32_U24,
V_MUL_HI_U32_U24,
V_MUL_LO_U32, V_MUL_LO_U32,
V_MUL_HI_U32, V_MUL_HI_U32,
V_MUL_LO_I32, V_MUL_LO_I32,
@@ -257,6 +260,7 @@ enum class Opcode {
V_CVT_PK_U8_F32, V_CVT_PK_U8_F32,
V_MAD_F32, V_MAD_F32,
V_MAD_I16, V_MAD_I16,
V_MAD_U16,
V_MAD_I32_I24, V_MAD_I32_I24,
V_MAD_U32_U24, V_MAD_U32_U24,
V_MAD_U64_U32, V_MAD_U64_U32,
@@ -277,16 +281,20 @@ enum class Opcode {
V_MIN3_I32, V_MIN3_I32,
V_MIN3_U32, V_MIN3_U32,
V_MIN3_F16, V_MIN3_F16,
V_MIN3_I16,
V_MIN3_U16, V_MIN3_U16,
V_MAX3_F32, V_MAX3_F32,
V_MAX3_I32, V_MAX3_I32,
V_MAX3_U32, V_MAX3_U32,
V_MAX3_F16, V_MAX3_F16,
V_MAX3_I16,
V_MAX3_U16,
V_MED3_F32, V_MED3_F32,
V_MED3_I32, V_MED3_I32,
V_MED3_U32, V_MED3_U32,
V_MED3_F16, V_MED3_F16,
V_MED3_I16, V_MED3_I16,
V_MED3_U16,
V_SAD_U32, V_SAD_U32,
V_ADD3_U32, V_ADD3_U32,
V_LSHL_ADD_U32, V_LSHL_ADD_U32,
@@ -354,6 +362,7 @@ enum class Opcode {
V_ASHRREV_I32, V_ASHRREV_I32,
V_LSHLREV_B64, V_LSHLREV_B64,
V_LSHRREV_B64, V_LSHRREV_B64,
V_ASHRREV_I64,
V_LSHLREV_B16, V_LSHLREV_B16,
V_LSHRREV_B16, V_LSHRREV_B16,
V_ASHRREV_I16, V_ASHRREV_I16,
@@ -35,7 +35,9 @@ constexpr Vop2OpcodeInfo VOP2_OPCODE_LIST[] = {
{0x05u, Opcode::V_SUBREV_F32}, {0x05u, Opcode::V_SUBREV_F32},
{0x08u, Opcode::V_MUL_F32, Vop2SdwaProfile::Float32}, {0x08u, Opcode::V_MUL_F32, Vop2SdwaProfile::Float32},
{0x09u, Opcode::V_MUL_I32_I24, Vop2SdwaProfile::IntegerPartialDestination}, {0x09u, Opcode::V_MUL_I32_I24, Vop2SdwaProfile::IntegerPartialDestination},
{0x0au, Opcode::V_MUL_HI_I32_I24, Vop2SdwaProfile::IntegerPartialDestination},
{0x0bu, Opcode::V_MUL_U32_U24, Vop2SdwaProfile::IntegerPartialDestination}, {0x0bu, Opcode::V_MUL_U32_U24, Vop2SdwaProfile::IntegerPartialDestination},
{0x0cu, Opcode::V_MUL_HI_U32_U24, Vop2SdwaProfile::IntegerPartialDestination},
{0x0fu, Opcode::V_MIN_F32}, {0x0fu, Opcode::V_MIN_F32},
{0x10u, Opcode::V_MAX_F32}, {0x10u, Opcode::V_MAX_F32},
{0x11u, Opcode::V_MIN_I32, Vop2SdwaProfile::IntegerFullDestination}, {0x11u, Opcode::V_MIN_I32, Vop2SdwaProfile::IntegerFullDestination},
@@ -157,6 +159,10 @@ constexpr OpcodeMap VOP3_ENCODED_VOP1_OPCODE_LIST[] = {
{0x04u, Opcode::V_CVT_F64_I32}, {0x04u, Opcode::V_CVT_F64_I32},
{0x0fu, Opcode::V_CVT_F32_F64}, {0x0fu, Opcode::V_CVT_F32_F64},
{0x10u, Opcode::V_CVT_F64_F32}, {0x10u, Opcode::V_CVT_F64_F32},
{0x11u, Opcode::V_CVT_F32_UBYTE0},
{0x12u, Opcode::V_CVT_F32_UBYTE1},
{0x13u, Opcode::V_CVT_F32_UBYTE2},
{0x14u, Opcode::V_CVT_F32_UBYTE3},
{0x16u, Opcode::V_CVT_F64_U32}, {0x16u, Opcode::V_CVT_F64_U32},
{0x17u, Opcode::V_TRUNC_F64}, {0x17u, Opcode::V_TRUNC_F64},
{0x18u, Opcode::V_CEIL_F64}, {0x18u, Opcode::V_CEIL_F64},
@@ -167,6 +173,7 @@ constexpr OpcodeMap VOP3_ENCODED_VOP1_OPCODE_LIST[] = {
{0x07u, Opcode::V_CVT_U32_F32}, {0x07u, Opcode::V_CVT_U32_F32},
{0x08u, Opcode::V_CVT_I32_F32}, {0x08u, Opcode::V_CVT_I32_F32},
{0x0au, Opcode::V_CVT_F16_F32}, {0x0au, Opcode::V_CVT_F16_F32},
{0x0bu, Opcode::V_CVT_F32_F16},
{0x0cu, Opcode::V_CVT_RPI_I32_F32}, {0x0cu, Opcode::V_CVT_RPI_I32_F32},
{0x0du, Opcode::V_CVT_FLR_I32_F32}, {0x0du, Opcode::V_CVT_FLR_I32_F32},
{0x0eu, Opcode::V_CVT_OFF_F32_I4}, {0x0eu, Opcode::V_CVT_OFF_F32_I4},
@@ -334,16 +341,20 @@ constexpr OpcodeMap VOP3_OPCODE_LIST[] = {
{0x152u, Opcode::V_MIN3_I32}, {0x152u, Opcode::V_MIN3_I32},
{0x153u, Opcode::V_MIN3_U32}, {0x153u, Opcode::V_MIN3_U32},
{0x351u, Opcode::V_MIN3_F16}, {0x351u, Opcode::V_MIN3_F16},
{0x352u, Opcode::V_MIN3_I16},
{0x353u, Opcode::V_MIN3_U16}, {0x353u, Opcode::V_MIN3_U16},
{0x154u, Opcode::V_MAX3_F32}, {0x154u, Opcode::V_MAX3_F32},
{0x155u, Opcode::V_MAX3_I32}, {0x155u, Opcode::V_MAX3_I32},
{0x156u, Opcode::V_MAX3_U32}, {0x156u, Opcode::V_MAX3_U32},
{0x354u, Opcode::V_MAX3_F16}, {0x354u, Opcode::V_MAX3_F16},
{0x355u, Opcode::V_MAX3_I16},
{0x356u, Opcode::V_MAX3_U16},
{0x157u, Opcode::V_MED3_F32}, {0x157u, Opcode::V_MED3_F32},
{0x158u, Opcode::V_MED3_I32}, {0x158u, Opcode::V_MED3_I32},
{0x159u, Opcode::V_MED3_U32}, {0x159u, Opcode::V_MED3_U32},
{0x357u, Opcode::V_MED3_F16}, {0x357u, Opcode::V_MED3_F16},
{0x358u, Opcode::V_MED3_I16}, {0x358u, Opcode::V_MED3_I16},
{0x359u, Opcode::V_MED3_U16},
{0x15du, Opcode::V_SAD_U32}, {0x15du, Opcode::V_SAD_U32},
{0x15eu, Opcode::V_CVT_PK_U8_F32}, {0x15eu, Opcode::V_CVT_PK_U8_F32},
{0x178u, Opcode::V_XOR3_B32}, {0x178u, Opcode::V_XOR3_B32},
@@ -354,6 +365,7 @@ constexpr OpcodeMap VOP3_OPCODE_LIST[] = {
{0x16cu, Opcode::V_MUL_HI_I32}, {0x16cu, Opcode::V_MUL_HI_I32},
{0x2ffu, Opcode::V_LSHLREV_B64}, {0x2ffu, Opcode::V_LSHLREV_B64},
{0x300u, Opcode::V_LSHRREV_B64}, {0x300u, Opcode::V_LSHRREV_B64},
{0x301u, Opcode::V_ASHRREV_I64},
{0x303u, Opcode::V_ADD_NC_U16}, {0x303u, Opcode::V_ADD_NC_U16},
{0x304u, Opcode::V_SUB_NC_U16}, {0x304u, Opcode::V_SUB_NC_U16},
{0x305u, Opcode::V_MUL_LO_U16}, {0x305u, Opcode::V_MUL_LO_U16},
@@ -370,6 +382,7 @@ constexpr OpcodeMap VOP3_OPCODE_LIST[] = {
{0x311u, Opcode::V_PACK_B32_F16}, {0x311u, Opcode::V_PACK_B32_F16},
{0x314u, Opcode::V_LSHLREV_B16}, {0x314u, Opcode::V_LSHLREV_B16},
{0x319u, Opcode::V_SUBREV_I32}, {0x319u, Opcode::V_SUBREV_I32},
{0x340u, Opcode::V_MAD_U16},
{0x345u, Opcode::V_XAD_U32}, {0x345u, Opcode::V_XAD_U32},
{0x346u, Opcode::V_LSHL_ADD_U32}, {0x346u, Opcode::V_LSHL_ADD_U32},
{0x347u, Opcode::V_ADD_LSHL_U32}, {0x347u, Opcode::V_ADD_LSHL_U32},
@@ -488,8 +501,10 @@ bool IsNativeVop3F16TernaryOpcode(Opcode opcode) {
} }
bool IsNativeVop3B16TernaryOpcode(Opcode opcode) { bool IsNativeVop3B16TernaryOpcode(Opcode opcode) {
return opcode == Opcode::V_MIN3_U16 || opcode == Opcode::V_MED3_I16 || return opcode == Opcode::V_MIN3_I16 || opcode == Opcode::V_MIN3_U16 ||
opcode == Opcode::V_MAD_I16; opcode == Opcode::V_MAX3_I16 || opcode == Opcode::V_MAX3_U16 ||
opcode == Opcode::V_MED3_I16 || opcode == Opcode::V_MED3_U16 ||
opcode == Opcode::V_MAD_I16 || opcode == Opcode::V_MAD_U16;
} }
bool IsNativeVop3B16BinaryOpcode(Opcode opcode) { bool IsNativeVop3B16BinaryOpcode(Opcode opcode) {
@@ -1335,6 +1350,7 @@ uint32_t NativeVop3SourceCount(Opcode opcode) {
case Opcode::V_SUB_NC_I16: case Opcode::V_SUB_NC_I16:
case Opcode::V_LSHLREV_B64: case Opcode::V_LSHLREV_B64:
case Opcode::V_LSHRREV_B64: case Opcode::V_LSHRREV_B64:
case Opcode::V_ASHRREV_I64:
case Opcode::V_LSHLREV_B16: case Opcode::V_LSHLREV_B16:
case Opcode::V_LSHRREV_B16: case Opcode::V_LSHRREV_B16:
case Opcode::V_ASHRREV_I16: case Opcode::V_ASHRREV_I16:
@@ -1523,7 +1539,10 @@ bool SupportsNativeVop3ResultModifiers(Opcode opcode) {
bool SupportsNativeVop3Clamp(Opcode opcode) { bool SupportsNativeVop3Clamp(Opcode opcode) {
return SupportsNativeVop3ResultModifiers(opcode) || UsesInexactClampControl(opcode) || return SupportsNativeVop3ResultModifiers(opcode) || UsesInexactClampControl(opcode) ||
opcode == Opcode::V_MAD_I16; opcode == Opcode::V_MAD_I16 || opcode == Opcode::V_MAD_U16 ||
opcode == Opcode::V_MIN3_I16 || opcode == Opcode::V_MAX3_I16 ||
opcode == Opcode::V_MAX3_U16 || opcode == Opcode::V_MED3_U16 ||
opcode == Opcode::V_MUL_HI_I32_I24 || opcode == Opcode::V_MUL_HI_U32_U24;
} }
bool HasUnsupportedNativeVop3Modifiers(Opcode opcode, bool permlane, bool mad_mix, bool HasUnsupportedNativeVop3Modifiers(Opcode opcode, bool permlane, bool mad_mix,
@@ -1554,7 +1573,17 @@ bool HasUnsupportedNativeVop3Modifiers(Opcode opcode, bool permlane, bool mad_mi
return clamp != 0u || omod != 0u || neg != 0u; return clamp != 0u || omod != 0u || neg != 0u;
} }
switch (opcode) { switch (opcode) {
case Opcode::V_MUL_HI_I32_I24:
case Opcode::V_MUL_HI_U32_U24:
// The high product fits in 32 bits, so clamp has no effect.
return abs != 0u || op_sel != 0u || omod != 0u || neg != 0u;
case Opcode::V_LDEXP_F32: return (abs & ~1u) != 0u || op_sel != 0u || (neg & ~1u) != 0u; case Opcode::V_LDEXP_F32: return (abs & ~1u) != 0u || op_sel != 0u || (neg & ~1u) != 0u;
case Opcode::V_CVT_F32_UBYTE0:
case Opcode::V_CVT_F32_UBYTE1:
case Opcode::V_CVT_F32_UBYTE2:
case Opcode::V_CVT_F32_UBYTE3:
// Byte conversions ignore input modifiers and support all output modifiers.
return op_sel != 0u;
case Opcode::V_INTERP_P1LL_F16: case Opcode::V_INTERP_P1LL_F16:
case Opcode::V_INTERP_P2_F16: case Opcode::V_INTERP_P2_F16:
return abs != 0u || (op_sel & 0x7u) != 0u || clamp != 0u || omod != 0u || neg != 0u; return abs != 0u || (op_sel & 0x7u) != 0u || clamp != 0u || omod != 0u || neg != 0u;
@@ -20,12 +20,12 @@ void Translator::Integer16Binary(const Decoder::Instruction& inst, IR::ValueOpco
Write16Bits(DestinationOperand(inst), result); Write16Bits(DestinationOperand(inst), result);
} }
void Translator::V_MAD_I16(const Decoder::Instruction& inst) { void Translator::Integer16Mad(const Decoder::Instruction& inst, bool sign) {
const auto lhs = ReadU16AsU32(inst.src0, true); const auto lhs = ReadU16AsU32(inst.src0, sign);
const auto rhs = ReadU16AsU32(inst.src1, true); const auto rhs = ReadU16AsU32(inst.src1, sign);
auto result = ir.IAdd(ir.IMul(lhs, rhs), ReadU16AsU32(inst.src2, true)); auto result = ir.IAdd(ir.IMul(lhs, rhs), ReadU16AsU32(inst.src2, sign));
if (inst.dst.clamp) { if (inst.dst.clamp) {
result = Saturate16(result, true); result = Saturate16(result, sign);
} }
Write16Bits(DestinationOperand(inst), result); Write16Bits(DestinationOperand(inst), result);
} }
@@ -147,6 +147,12 @@ void Translator::SimpleInteger(const Decoder::Instruction& inst, IR::ValueOpcode
const auto arg_type = IR::ArgTypeOf(opcode, index); const auto arg_type = IR::ArgTypeOf(opcode, index);
const auto& operand = SourceAt(inst, reverse && index < 2u ? 1u - index : index); const auto& operand = SourceAt(inst, reverse && index < 2u ? 1u - index : index);
args[index] = ReadOperand(operand, arg_type == IR::Type::Void ? type : arg_type); args[index] = ReadOperand(operand, arg_type == IR::Type::Void ? type : arg_type);
if (opcode == IR::ValueOpcode::ShiftRightArithmetic64 && index == 0u &&
operand.kind == Decoder::OperandKind::LiteralConstant) {
args[index] = ir.ConstructU64(
ReadU32(operand),
IR::U32(IR::Value((operand.value & 0x80000000u) ? 0xffffffffu : 0u)));
}
if (mask_shift_count && index == 1u) { if (mask_shift_count && index == 1u) {
args[index] = ir.BitwiseAnd(IR::U32(args[index]), IR::U32(IR::Value(31u))); args[index] = ir.BitwiseAnd(IR::U32(args[index]), IR::U32(IR::Value(31u)));
} }
@@ -263,7 +269,7 @@ void Translator::V_FFBH_32(const Decoder::Instruction& inst, bool sign) {
WriteOperand(DestinationOperand(inst), result); WriteOperand(DestinationOperand(inst), result);
} }
void Translator::Integer24(const Decoder::Instruction& inst, bool sign, bool addend) { void Translator::Integer24(const Decoder::Instruction& inst, bool sign, bool addend, bool high) {
const auto extract24 = [&](IR::U32 value) { const auto extract24 = [&](IR::U32 value) {
return IR::U32( return IR::U32(
ir.Emit(sign ? IR::ValueOpcode::BitFieldSExtract : IR::ValueOpcode::BitFieldUExtract, ir.Emit(sign ? IR::ValueOpcode::BitFieldSExtract : IR::ValueOpcode::BitFieldUExtract,
@@ -271,7 +277,9 @@ void Translator::Integer24(const Decoder::Instruction& inst, bool sign, bool add
}; };
const auto lhs = extract24(ReadU32(inst.src0)); const auto lhs = extract24(ReadU32(inst.src0));
const auto rhs = extract24(ReadU32(inst.src1)); const auto rhs = extract24(ReadU32(inst.src1));
auto result = ir.IMul(lhs, rhs); const auto multiply = high ? (sign ? IR::ValueOpcode::SMulHi : IR::ValueOpcode::UMulHi)
: IR::ValueOpcode::IMul32;
auto result = IR::U32(ir.Emit(multiply, {lhs, rhs}));
if (addend) { if (addend) {
result = ir.IAdd(result, ReadU32(inst.src2)); result = ir.IAdd(result, ReadU32(inst.src2));
} }
@@ -369,7 +377,7 @@ void Translator::S_BITREPLICATE_B64_B32(const Decoder::Instruction& inst) {
WriteOperand(DestinationOperand(inst), ir.ConstructU64(replicate(low), replicate(high))); WriteOperand(DestinationOperand(inst), ir.ConstructU64(replicate(low), replicate(high)));
} }
void Translator::S_QUADMASK_B64(const Decoder::Instruction& inst) { void Translator::S_QUADMASK(const Decoder::Instruction& inst, bool is64) {
const auto compact = [&](IR::U32 value) { const auto compact = [&](IR::U32 value) {
auto bits = ir.BitwiseOr(value, ir.ShiftRightLogical(value, IR::U32(IR::Value(1u)))); auto bits = ir.BitwiseOr(value, ir.ShiftRightLogical(value, IR::U32(IR::Value(1u))));
bits = ir.BitwiseOr(bits, ir.ShiftRightLogical(bits, IR::U32(IR::Value(2u)))); bits = ir.BitwiseOr(bits, ir.ShiftRightLogical(bits, IR::U32(IR::Value(2u))));
@@ -382,12 +390,17 @@ void Translator::S_QUADMASK_B64(const Decoder::Instruction& inst) {
ir.BitwiseOr(bits, ir.ShiftRightLogical(bits, IR::U32(IR::Value(12u)))), ir.BitwiseOr(bits, ir.ShiftRightLogical(bits, IR::U32(IR::Value(12u)))),
IR::U32(IR::Value(0xffu))); IR::U32(IR::Value(0xffu)));
}; };
IR::U32 quads;
if (is64) {
const auto source = ReadU32Pair(inst.src0); const auto source = ReadU32Pair(inst.src0);
const auto quads = ir.BitwiseOr( quads = ir.BitwiseOr(
compact(source[0]), ir.ShiftLeftLogical(compact(source[1]), IR::U32(IR::Value(8u)))); compact(source[0]), ir.ShiftLeftLogical(compact(source[1]), IR::U32(IR::Value(8u))));
const auto result = ir.ConstructU64(quads, IR::U32(IR::Value(0u))); WriteOperand(DestinationOperand(inst), ir.ConstructU64(quads, IR::U32(IR::Value(0u))));
WriteOperand(DestinationOperand(inst), result); } else {
ir.SetScc(IR::U1(ir.Emit(IR::ValueOpcode::INotEqual64, {result, IR::Value(uint64_t {0})}))); quads = compact(ReadU32(inst.src0));
WriteOperand(DestinationOperand(inst), quads);
}
ir.SetScc(ir.INotEqual(quads, IR::U32(IR::Value(0u))));
} }
void Translator::BFM_B32(const Decoder::Instruction& inst) { void Translator::BFM_B32(const Decoder::Instruction& inst) {
@@ -248,7 +248,8 @@ void Translator::EmitScalar(const Decoder::Instruction& inst) {
case O::S_BITSET0_B64: return S_BITSET_B64(inst, false); case O::S_BITSET0_B64: return S_BITSET_B64(inst, false);
case O::S_BITSET1_B64: return S_BITSET_B64(inst, true); case O::S_BITSET1_B64: return S_BITSET_B64(inst, true);
case O::S_BITREPLICATE_B64_B32: return S_BITREPLICATE_B64_B32(inst); case O::S_BITREPLICATE_B64_B32: return S_BITREPLICATE_B64_B32(inst);
case O::S_QUADMASK_B64: return S_QUADMASK_B64(inst); case O::S_QUADMASK_B32: return S_QUADMASK(inst, false);
case O::S_QUADMASK_B64: return S_QUADMASK(inst, true);
case O::S_BFM_B32: return BFM_B32(inst); case O::S_BFM_B32: return BFM_B32(inst);
case O::S_BFM_B64: return S_BFM_B64(inst); case O::S_BFM_B64: return S_BFM_B64(inst);
case O::S_BFE_U32: return S_BFE_U32(inst, false); case O::S_BFE_U32: return S_BFE_U32(inst, false);
@@ -1165,6 +1165,14 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
: IR::U32(IR::Value(0u)); : IR::U32(IR::Value(0u));
entry_ir.SetExecLo(initial_lo); entry_ir.SetExecLo(initial_lo);
entry_ir.SetExecHi(initial_hi); entry_ir.SetExecHi(initial_hi);
const auto active_lane_count = [&] {
auto count = IR::U32(entry_ir.Emit(IR::ValueOpcode::BitCount32, {initial_lo}));
if (options.wave_size == 64u) {
count = entry_ir.IAdd(count, IR::U32(entry_ir.Emit(
IR::ValueOpcode::BitCount32, {initial_hi})));
}
return count;
};
if (options.stage == ShaderType::Compute) { if (options.stage == ShaderType::Compute) {
const auto* cs = options.input_info.compute; const auto* cs = options.input_info.compute;
const auto thread_ids = const auto thread_ids =
@@ -1302,7 +1310,7 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
builtin(IR::StageInputKind::WorkgroupId, 1)); builtin(IR::StageInputKind::WorkgroupId, 1));
} }
} else if (options.stage == ShaderType::Local) { } else if (options.stage == ShaderType::Local) {
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(3), IR::U32(IR::Value(64u))); entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(3), active_lane_count());
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(2), entry_ir.SetVectorReg(static_cast<IR::VectorReg>(2),
builtin(IR::StageInputKind::VertexIndex)); builtin(IR::StageInputKind::VertexIndex));
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(3), IR::U32(IR::Value(0u))); entry_ir.SetVectorReg(static_cast<IR::VectorReg>(3), IR::U32(IR::Value(0u)));
@@ -1380,15 +1388,10 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
} else if (options.stage == ShaderType::Vertex) { } else if (options.stage == ShaderType::Vertex) {
// Vulkan owns primitive assembly; each vertex subgroup is one NGG wave. // Vulkan owns primitive assembly; each vertex subgroup is one NGG wave.
// ESVertCount describes input vertices, independently of lane ordinals. // ESVertCount describes input vertices, independently of lane ordinals.
auto vertex_count = IR::U32(entry_ir.Emit(IR::ValueOpcode::BitCount32, {initial_lo}));
if (options.wave_size == 64u) {
vertex_count = entry_ir.IAdd(vertex_count, IR::U32(entry_ir.Emit(
IR::ValueOpcode::BitCount32, {initial_hi})));
}
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(2), entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(2),
IR::U32(IR::Value(options.wave_size << 12u))); IR::U32(IR::Value(options.wave_size << 12u)));
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(3), entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(3),
entry_ir.BitwiseOr(vertex_count, IR::U32(IR::Value(1u << 28u)))); entry_ir.BitwiseOr(active_lane_count(), IR::U32(IR::Value(1u << 28u))));
// Vulkan adds an indirect record's bases to the indices; the hardware passes them // Vulkan adds an indirect record's bases to the indices; the hardware passes them
// only in the user SGPRs the packet patches. SH_NOP leaves those SGPRs unchanged; // only in the user SGPRs the packet patches. SH_NOP leaves those SGPRs unchanged;
// it does not make the record's bases zero. // it does not make the record's bases zero.
@@ -167,7 +167,7 @@ private:
void Integer16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool arithmetic); void Integer16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool arithmetic);
void Integer16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign); void Integer16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign);
void Integer16Ternary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign); void Integer16Ternary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign);
void V_MAD_I16(const Decoder::Instruction& inst); void Integer16Mad(const Decoder::Instruction& inst, bool sign);
void PackedInteger16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode, void PackedInteger16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
bool arithmetic); bool arithmetic);
void PackedInteger16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode); void PackedInteger16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
@@ -187,7 +187,7 @@ private:
void V_XOR3_B32(const Decoder::Instruction& inst); void V_XOR3_B32(const Decoder::Instruction& inst);
void S_FF_I32_B64(const Decoder::Instruction& inst, bool find_zero, bool from_msb, bool sign); void S_FF_I32_B64(const Decoder::Instruction& inst, bool find_zero, bool from_msb, bool sign);
void V_FFBH_32(const Decoder::Instruction& inst, bool sign); void V_FFBH_32(const Decoder::Instruction& inst, bool sign);
void Integer24(const Decoder::Instruction& inst, bool sign, bool addend); void Integer24(const Decoder::Instruction& inst, bool sign, bool addend, bool high = false);
void V_MAD_U64_U32(const Decoder::Instruction& inst); void V_MAD_U64_U32(const Decoder::Instruction& inst);
void V_SAD_U32(const Decoder::Instruction& inst); void V_SAD_U32(const Decoder::Instruction& inst);
void V_ADD3_U32(const Decoder::Instruction& inst); void V_ADD3_U32(const Decoder::Instruction& inst);
@@ -196,7 +196,7 @@ private:
void V_BCNT_U32_B32(const Decoder::Instruction& inst); void V_BCNT_U32_B32(const Decoder::Instruction& inst);
void V_MBCNT_U32_B32(const Decoder::Instruction& inst, bool low); void V_MBCNT_U32_B32(const Decoder::Instruction& inst, bool low);
void S_BITREPLICATE_B64_B32(const Decoder::Instruction& inst); void S_BITREPLICATE_B64_B32(const Decoder::Instruction& inst);
void S_QUADMASK_B64(const Decoder::Instruction& inst); void S_QUADMASK(const Decoder::Instruction& inst, bool is64);
void BFM_B32(const Decoder::Instruction& inst); void BFM_B32(const Decoder::Instruction& inst);
IR::U32 RightMask32(IR::U32 count); IR::U32 RightMask32(IR::U32 count);
void S_BFM_B64(const Decoder::Instruction& inst); void S_BFM_B64(const Decoder::Instruction& inst);
@@ -375,9 +375,14 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
case O::V_SUB_NC_U16: case O::V_SUB_NC_U16:
case O::V_SUB_NC_I16: return Integer16Binary(inst, IR::ValueOpcode::ISub32, false); case O::V_SUB_NC_I16: return Integer16Binary(inst, IR::ValueOpcode::ISub32, false);
case O::V_MUL_LO_U16: return Integer16Binary(inst, IR::ValueOpcode::IMul32, false); case O::V_MUL_LO_U16: return Integer16Binary(inst, IR::ValueOpcode::IMul32, false);
case O::V_MAD_I16: return V_MAD_I16(inst); case O::V_MAD_I16: return Integer16Mad(inst, true);
case O::V_MAD_U16: return Integer16Mad(inst, false);
case O::V_MED3_I16: return Integer16Ternary(inst, IR::ValueOpcode::SMedTri32, true); case O::V_MED3_I16: return Integer16Ternary(inst, IR::ValueOpcode::SMedTri32, true);
case O::V_MIN3_U16: return Integer16Ternary(inst, IR::ValueOpcode::UMinTri32, false); case O::V_MIN3_U16: return Integer16Ternary(inst, IR::ValueOpcode::UMinTri32, false);
case O::V_MIN3_I16: return Integer16Ternary(inst, IR::ValueOpcode::SMinTri32, true);
case O::V_MAX3_I16: return Integer16Ternary(inst, IR::ValueOpcode::SMaxTri32, true);
case O::V_MAX3_U16: return Integer16Ternary(inst, IR::ValueOpcode::UMaxTri32, false);
case O::V_MED3_U16: return Integer16Ternary(inst, IR::ValueOpcode::UMedTri32, false);
case O::V_MIN_I16: return Integer16Binary(inst, IR::ValueOpcode::SMin32, true); case O::V_MIN_I16: return Integer16Binary(inst, IR::ValueOpcode::SMin32, true);
case O::V_MAX_I16: return Integer16Binary(inst, IR::ValueOpcode::SMax32, true); case O::V_MAX_I16: return Integer16Binary(inst, IR::ValueOpcode::SMax32, true);
case O::V_MIN_U16: return Integer16Binary(inst, IR::ValueOpcode::UMin32, false); case O::V_MIN_U16: return Integer16Binary(inst, IR::ValueOpcode::UMin32, false);
@@ -565,6 +570,9 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
case O::V_LSHRREV_B64: case O::V_LSHRREV_B64:
return SimpleInteger(inst, IR::ValueOpcode::ShiftRightLogical64, IR::Type::U64, true, return SimpleInteger(inst, IR::ValueOpcode::ShiftRightLogical64, IR::Type::U64, true,
false, false); false, false);
case O::V_ASHRREV_I64:
return SimpleInteger(inst, IR::ValueOpcode::ShiftRightArithmetic64, IR::Type::U64, true,
false, false);
case O::V_XNOR_B32: case O::V_XNOR_B32:
return ComposedIntegerBinary(inst, IR::ValueOpcode::BitwiseXor32, false, true, false); return ComposedIntegerBinary(inst, IR::ValueOpcode::BitwiseXor32, false, true, false);
@@ -578,6 +586,8 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
case O::V_MAD_U32_U24: return Integer24(inst, false, true); case O::V_MAD_U32_U24: return Integer24(inst, false, true);
case O::V_MUL_I32_I24: return Integer24(inst, true, false); case O::V_MUL_I32_I24: return Integer24(inst, true, false);
case O::V_MUL_U32_U24: return Integer24(inst, false, false); case O::V_MUL_U32_U24: return Integer24(inst, false, false);
case O::V_MUL_HI_I32_I24: return Integer24(inst, true, false, true);
case O::V_MUL_HI_U32_U24: return Integer24(inst, false, false, true);
case O::V_MAD_U64_U32: return V_MAD_U64_U32(inst); case O::V_MAD_U64_U32: return V_MAD_U64_U32(inst);
case O::V_SAD_U32: return V_SAD_U32(inst); case O::V_SAD_U32: return V_SAD_U32(inst);
case O::V_ADD3_U32: return V_ADD3_U32(inst); case O::V_ADD3_U32: return V_ADD3_U32(inst);
@@ -406,6 +406,11 @@
<property name="text"><string>Enable network connection</string></property> <property name="text"><string>Enable network connection</string></property>
</widget> </widget>
</item> </item>
<item>
<widget class="QCheckBox" name="checkBox_psn_emulation">
<property name="text"><string>Emulate PSN (compatibility)</string></property>
</widget>
</item>
<item> <item>
<spacer name="networkSpacer"> <spacer name="networkSpacer">
<property name="orientation"><enum>Qt::Vertical</enum></property> <property name="orientation"><enum>Qt::Vertical</enum></property>
+2
View File
@@ -128,6 +128,7 @@ public:
bool perf_overlay_enabled = false; bool perf_overlay_enabled = false;
bool skip_notice_screen = false; bool skip_notice_screen = false;
bool network_connected = false; bool network_connected = false;
bool psn_emulation_enabled = false;
int vblank_frequency = 60; int vblank_frequency = 60;
int console_language = DEFAULT_CONSOLE_LANGUAGE; int console_language = DEFAULT_CONSOLE_LANGUAGE;
bool vulkan_validation_enabled = false; bool vulkan_validation_enabled = false;
@@ -171,6 +172,7 @@ public:
perf_overlay_enabled = other.perf_overlay_enabled; perf_overlay_enabled = other.perf_overlay_enabled;
skip_notice_screen = other.skip_notice_screen; skip_notice_screen = other.skip_notice_screen;
network_connected = other.network_connected; network_connected = other.network_connected;
psn_emulation_enabled = other.psn_emulation_enabled;
vblank_frequency = other.vblank_frequency; vblank_frequency = other.vblank_frequency;
console_language = other.console_language; console_language = other.console_language;
vulkan_validation_enabled = other.vulkan_validation_enabled; vulkan_validation_enabled = other.vulkan_validation_enabled;
+2
View File
@@ -42,6 +42,7 @@ QVariantMap Configuration::GameSettings() const {
KYTY_CFG_SET(perf_overlay_enabled); KYTY_CFG_SET(perf_overlay_enabled);
KYTY_CFG_SET(skip_notice_screen); KYTY_CFG_SET(skip_notice_screen);
KYTY_CFG_SET(network_connected); KYTY_CFG_SET(network_connected);
KYTY_CFG_SET(psn_emulation_enabled);
KYTY_CFG_SET(vblank_frequency); KYTY_CFG_SET(vblank_frequency);
KYTY_CFG_SET(console_language); KYTY_CFG_SET(console_language);
KYTY_CFG_SET(vulkan_validation_enabled); KYTY_CFG_SET(vulkan_validation_enabled);
@@ -96,6 +97,7 @@ void Configuration::ReadGameSettingsValues(const Settings& s) {
KYTY_CFG_GET(perf_overlay_enabled); KYTY_CFG_GET(perf_overlay_enabled);
KYTY_CFG_GET(skip_notice_screen); KYTY_CFG_GET(skip_notice_screen);
KYTY_CFG_GET(network_connected); KYTY_CFG_GET(network_connected);
KYTY_CFG_GET(psn_emulation_enabled);
vblank_frequency = s.value("vblank_frequency", vblank_frequency).toInt(); vblank_frequency = s.value("vblank_frequency", vblank_frequency).toInt();
console_language = s.value("console_language", console_language).toInt(); console_language = s.value("console_language", console_language).toInt();
if (console_language < 0 || console_language > MAX_CONSOLE_LANGUAGE) { if (console_language < 0 || console_language > MAX_CONSOLE_LANGUAGE) {
@@ -298,6 +298,7 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
m_ui->checkBox_shader_cache->setChecked(info.shader_cache_enabled); m_ui->checkBox_shader_cache->setChecked(info.shader_cache_enabled);
m_ui->checkBox_skip_notice_screen->setChecked(info.skip_notice_screen); m_ui->checkBox_skip_notice_screen->setChecked(info.skip_notice_screen);
m_ui->checkBox_network_connected->setChecked(info.network_connected); m_ui->checkBox_network_connected->setChecked(info.network_connected);
m_ui->checkBox_psn_emulation->setChecked(info.psn_emulation_enabled);
m_ui->checkBox_trophy_notifications->setChecked(info.trophy_enabled); m_ui->checkBox_trophy_notifications->setChecked(info.trophy_enabled);
m_ui->spinBox_trophy_duration->setValue(info.trophy_duration_seconds); m_ui->spinBox_trophy_duration->setValue(info.trophy_duration_seconds);
m_ui->checkBox_perf_overlay->setChecked(info.perf_overlay_enabled); m_ui->checkBox_perf_overlay->setChecked(info.perf_overlay_enabled);
@@ -533,6 +534,7 @@ static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui, boo
info.shader_cache_enabled = ui.checkBox_shader_cache->isChecked(); info.shader_cache_enabled = ui.checkBox_shader_cache->isChecked();
info.skip_notice_screen = ui.checkBox_skip_notice_screen->isChecked(); info.skip_notice_screen = ui.checkBox_skip_notice_screen->isChecked();
info.network_connected = ui.checkBox_network_connected->isChecked(); info.network_connected = ui.checkBox_network_connected->isChecked();
info.psn_emulation_enabled = ui.checkBox_psn_emulation->isChecked();
info.trophy_enabled = ui.checkBox_trophy_notifications->isChecked(); info.trophy_enabled = ui.checkBox_trophy_notifications->isChecked();
info.trophy_duration_seconds = ui.spinBox_trophy_duration->value(); info.trophy_duration_seconds = ui.spinBox_trophy_duration->value();
info.perf_overlay_enabled = ui.checkBox_perf_overlay->isChecked(); info.perf_overlay_enabled = ui.checkBox_perf_overlay->isChecked();
+1
View File
@@ -276,6 +276,7 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
AddFlag(args, info.perf_overlay_enabled, "--perf-overlay"); AddFlag(args, info.perf_overlay_enabled, "--perf-overlay");
AddFlag(args, info.skip_notice_screen, "--skip-notice-screen"); AddFlag(args, info.skip_notice_screen, "--skip-notice-screen");
AddFlag(args, info.network_connected, "--network-connected"); AddFlag(args, info.network_connected, "--network-connected");
AddFlag(args, info.psn_emulation_enabled, "--emulate-psn");
AddFlag(args, info.tessellation_enabled, "--tessellation"); AddFlag(args, info.tessellation_enabled, "--tessellation");
AddFlag(args, info.occlusion_queries_enabled, "--occlusion-queries"); AddFlag(args, info.occlusion_queries_enabled, "--occlusion-queries");
AddFlag(args, info.shader_cache_enabled, "--shader-cache"); AddFlag(args, info.shader_cache_enabled, "--shader-cache");
+54 -32
View File
@@ -1685,12 +1685,34 @@ LIB_VERSION("NpAuth", 1, "NpAuth", 1, 1);
constexpr int NP_AUTH_MAX_REQUEST_NUM = 16; constexpr int NP_AUTH_MAX_REQUEST_NUM = 16;
constexpr int NP_AUTH_REQUEST_ID_OFFSET = 0x10000000; constexpr int NP_AUTH_REQUEST_ID_OFFSET = 0x10000000;
constexpr int NP_AUTH_ERROR_INVALID_ARGUMENT = -2141912319; /* 0x80550301 */ constexpr int NP_AUTH_ERROR_INVALID_ARGUMENT = -2141912319; /* 0x80550301 */
constexpr int NP_AUTH_ERROR_INVALID_SIZE = -2141912318; /* 0x80550302 */
constexpr int NP_AUTH_ERROR_ABORTED = -2141912316; /* 0x80550304 */ constexpr int NP_AUTH_ERROR_ABORTED = -2141912316; /* 0x80550304 */
constexpr int NP_AUTH_ERROR_REQUEST_MAX = -2141912315; /* 0x80550305 */ constexpr int NP_AUTH_ERROR_REQUEST_MAX = -2141912315; /* 0x80550305 */
constexpr int NP_AUTH_ERROR_REQUEST_NOT_FOUND = -2141912314; /* 0x80550306 */ constexpr int NP_AUTH_ERROR_REQUEST_NOT_FOUND = -2141912314; /* 0x80550306 */
constexpr int NP_AUTH_ERROR_INVALID_ID = -2141912313; /* 0x80550307 */ constexpr int NP_AUTH_ERROR_INVALID_ID = -2141912313; /* 0x80550307 */
constexpr int NP_AUTH_ERROR_SERVICE_DOWN = -2141912063; /* 0x80550401 */
constexpr int NP_ERROR_SIGNED_OUT = -2141913082; /* 0x80550006 */ constexpr int NP_ERROR_SIGNED_OUT = -2141913082; /* 0x80550006 */
struct NpAuthGetAuthorizationCodeParameterV3 {
size_t size;
int user_id;
uint8_t padding[4];
const void* client_id;
const char* scope;
};
struct NpAuthGetIdTokenParameterV3 {
size_t size;
int user_id;
uint8_t padding[4];
const void* client_id;
const void* client_secret;
const char* scope;
};
static_assert(sizeof(NpAuthGetAuthorizationCodeParameterV3) == 32);
static_assert(sizeof(NpAuthGetIdTokenParameterV3) == 40);
enum class NpAuthRequestState { enum class NpAuthRequestState {
None = 0, None = 0,
Ready, Ready,
@@ -1822,7 +1844,7 @@ static int np_auth_wait_or_poll_async(int req_id, int* result) {
return 0; return 0;
} }
static int np_auth_complete_signed_out(NpAuthRequest* request) { static int np_auth_complete_unavailable(NpAuthRequest* request, int user_id) {
if (request->state == NpAuthRequestState::Complete) { if (request->state == NpAuthRequestState::Complete) {
request->result = NP_AUTH_ERROR_INVALID_ARGUMENT; request->result = NP_AUTH_ERROR_INVALID_ARGUMENT;
return NP_AUTH_ERROR_INVALID_ARGUMENT; return NP_AUTH_ERROR_INVALID_ARGUMENT;
@@ -1833,9 +1855,11 @@ static int np_auth_complete_signed_out(NpAuthRequest* request) {
} }
request->state = NpAuthRequestState::Complete; request->state = NpAuthRequestState::Complete;
request->result = NP_ERROR_SIGNED_OUT; // Emulating sign-in cannot provide a real authorization code or ID token.
request->result = Network::NpManager::IsSignedIn(user_id) ? NP_AUTH_ERROR_SERVICE_DOWN
: NP_ERROR_SIGNED_OUT;
return request->async ? 0 : NP_ERROR_SIGNED_OUT; return request->async ? 0 : request->result;
} }
static int KYTY_SYSV_ABI NpAuthWaitAsync(int req_id, int* result) { static int KYTY_SYSV_ABI NpAuthWaitAsync(int req_id, int* result) {
@@ -1856,8 +1880,9 @@ static int KYTY_SYSV_ABI NpAuthPollAsync(int req_id, int* result) {
return np_auth_wait_or_poll_async(req_id, result); return np_auth_wait_or_poll_async(req_id, result);
} }
static int KYTY_SYSV_ABI NpAuthGetAuthorizationCodeV3(int req_id, const void* param, static int KYTY_SYSV_ABI NpAuthGetAuthorizationCodeV3(
void* auth_code, int* issuer_id) { int req_id, const NpAuthGetAuthorizationCodeParameterV3* param, void* auth_code,
int* issuer_id) {
PRINT_NAME(); PRINT_NAME();
LOGF("\t req_id = %d\n", req_id); LOGF("\t req_id = %d\n", req_id);
@@ -1865,9 +1890,17 @@ static int KYTY_SYSV_ABI NpAuthGetAuthorizationCodeV3(int req_id, const void* pa
LOGF("\t auth_code = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(auth_code)); LOGF("\t auth_code = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(auth_code));
LOGF("\t issuer_id = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(issuer_id)); LOGF("\t issuer_id = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(issuer_id));
if (auth_code != nullptr) { if (req_id <= 0 || param == nullptr || auth_code == nullptr) {
memset(auth_code, 0, 136); return NP_AUTH_ERROR_INVALID_ARGUMENT;
} }
if (param->size < sizeof(*param)) {
return NP_AUTH_ERROR_INVALID_SIZE;
}
if (param->client_id == nullptr || param->scope == nullptr) {
return NP_AUTH_ERROR_INVALID_ARGUMENT;
}
memset(auth_code, 0, 136);
if (issuer_id != nullptr) { if (issuer_id != nullptr) {
*issuer_id = 0; *issuer_id = 0;
} }
@@ -1877,46 +1910,35 @@ static int KYTY_SYSV_ABI NpAuthGetAuthorizationCodeV3(int req_id, const void* pa
if (request == nullptr) { if (request == nullptr) {
return NP_AUTH_ERROR_REQUEST_NOT_FOUND; return NP_AUTH_ERROR_REQUEST_NOT_FOUND;
} }
if (request->state == NpAuthRequestState::Aborted) { return np_auth_complete_unavailable(request, param->user_id);
// request->result = 0;
// return 0;
request->result = NP_AUTH_ERROR_ABORTED;
return NP_AUTH_ERROR_ABORTED;
}
// request->state = NpAuthRequestState::Complete;
// request->result = 0;
// return 0;
return np_auth_complete_signed_out(request);
} }
static int KYTY_SYSV_ABI NpAuthGetIdTokenV3(int req_id, const void* param, void* id_token) { static int KYTY_SYSV_ABI NpAuthGetIdTokenV3(int req_id, const NpAuthGetIdTokenParameterV3* param,
void* id_token) {
PRINT_NAME(); PRINT_NAME();
LOGF("\t req_id = %d\n", req_id); LOGF("\t req_id = %d\n", req_id);
LOGF("\t param = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(param)); LOGF("\t param = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(param));
LOGF("\t id_token = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(id_token)); LOGF("\t id_token = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(id_token));
if (id_token != nullptr) { if (req_id <= 0 || param == nullptr || id_token == nullptr) {
memset(id_token, 0, 4104); return NP_AUTH_ERROR_INVALID_ARGUMENT;
} }
if (param->size < sizeof(*param)) {
return NP_AUTH_ERROR_INVALID_SIZE;
}
if (param->client_id == nullptr || param->client_secret == nullptr || param->scope == nullptr) {
return NP_AUTH_ERROR_INVALID_ARGUMENT;
}
memset(id_token, 0, 4104);
std::scoped_lock lock(g_np_auth_request_mutex); std::scoped_lock lock(g_np_auth_request_mutex);
auto* request = np_auth_find_request(req_id); auto* request = np_auth_find_request(req_id);
if (request == nullptr) { if (request == nullptr) {
return NP_AUTH_ERROR_REQUEST_NOT_FOUND; return NP_AUTH_ERROR_REQUEST_NOT_FOUND;
} }
if (request->state == NpAuthRequestState::Aborted) { return np_auth_complete_unavailable(request, param->user_id);
// request->result = 0;
// return 0;
request->result = NP_AUTH_ERROR_ABORTED;
return NP_AUTH_ERROR_ABORTED;
}
// request->state = NpAuthRequestState::Complete;
// request->result = 0;
// return 0;
return np_auth_complete_signed_out(request);
} }
LIB_DEFINE(InitNet_1_NpAuth) { LIB_DEFINE(InitNet_1_NpAuth) {
+70 -46
View File
@@ -4002,6 +4002,11 @@ struct NpCountryCode {
char padding[1]; char padding[1];
}; };
struct NpLanguageCode {
char code[36];
char padding[12];
};
struct NpAgeRestriction { struct NpAgeRestriction {
NpCountryCode country_code; NpCountryCode country_code;
int8_t age; int8_t age;
@@ -4056,8 +4061,23 @@ constexpr int np_error_aborted = -2141913070; /* 0x8055
constexpr int np_error_request_max = -2141913069; /* 0x80550013 */ constexpr int np_error_request_max = -2141913069; /* 0x80550013 */
constexpr int np_error_request_not_found = -2141913068; /* 0x80550014 */ constexpr int np_error_request_not_found = -2141913068; /* 0x80550014 */
constexpr int np_error_invalid_id = -2141913067; /* 0x80550015 */ constexpr int np_error_invalid_id = -2141913067; /* 0x80550015 */
constexpr int np_error_network_not_available = -2141872086; /* 0x8055a02a */
constexpr int np_request_max = 128; constexpr int np_request_max = 128;
constexpr uint32_t np_state_signed_out = 1; constexpr uint32_t np_state_signed_out = 1;
constexpr uint32_t np_state_signed_in = 2;
// A simulated account belongs only to the primary user and survives network loss.
bool IsSignedIn(int user_id) {
return Config::PsnEmulationEnabled() && user_id == Config::GetUserId();
}
static int np_reachability_result(int user_id) {
if (!IsSignedIn(user_id)) {
return np_error_signed_out;
}
// This is an explicit compatibility simulation, not a connection to PSN.
return NetCtl::NetCtlConnected() ? OK : np_error_network_not_available;
}
struct NpStateCallback { struct NpStateCallback {
NpStateCallbackA callback = nullptr; NpStateCallbackA callback = nullptr;
@@ -4117,7 +4137,7 @@ static NpRequest* np_get_request_locked(int req_id) {
return &g_np_requests[index]; return &g_np_requests[index];
} }
static int np_complete_signed_out_locked(NpRequest* request) { static int np_complete_locked(NpRequest* request, int result) {
if (request->state == NpRequestState::Complete) { if (request->state == NpRequestState::Complete) {
request->result = np_error_invalid_argument; request->result = np_error_invalid_argument;
return np_error_invalid_argument; return np_error_invalid_argument;
@@ -4128,9 +4148,9 @@ static int np_complete_signed_out_locked(NpRequest* request) {
} }
request->state = NpRequestState::Complete; request->state = NpRequestState::Complete;
request->result = np_error_signed_out; request->result = result;
return (request->async ? OK : np_error_signed_out); return (request->async ? OK : result);
} }
int KYTY_SYSV_ABI NpCheckCallback() { int KYTY_SYSV_ABI NpCheckCallback() {
@@ -4144,7 +4164,9 @@ int KYTY_SYSV_ABI NpCheckCallback() {
} }
} }
for (const auto& entry: pending) { for (const auto& entry: pending) {
entry.callback(Config::GetUserId(), np_state_signed_out, entry.userdata); entry.callback(Config::GetUserId(),
IsSignedIn(Config::GetUserId()) ? np_state_signed_in : np_state_signed_out,
entry.userdata);
} }
return OK; return OK;
@@ -4239,12 +4261,13 @@ int KYTY_SYSV_ABI NpGetNpId(int user_id, NpId* np_id) {
EXIT_NOT_IMPLEMENTED(np_id == nullptr); EXIT_NOT_IMPLEMENTED(np_id == nullptr);
// int s = snprintf(np_id->handle.data, 16, "Kyty");
// EXIT_NOT_IMPLEMENTED(s >= 16);
// np_id->handle.term = 0;
std::memset(np_id, 0, sizeof(*np_id)); std::memset(np_id, 0, sizeof(*np_id));
// return OK; if (IsSignedIn(user_id)) {
std::memcpy(np_id->handle.data, "Kyty", 4);
return OK;
}
return np_error_signed_out; return np_error_signed_out;
} }
@@ -4255,12 +4278,13 @@ int KYTY_SYSV_ABI NpGetOnlineId(int user_id, NpOnlineId* online_id) {
EXIT_NOT_IMPLEMENTED(online_id == nullptr); EXIT_NOT_IMPLEMENTED(online_id == nullptr);
// int s = snprintf(online_id->data, 16, "Kyty");
// EXIT_NOT_IMPLEMENTED(s >= 16);
// online_id->term = 0;
std::memset(online_id, 0, sizeof(*online_id)); std::memset(online_id, 0, sizeof(*online_id));
// return OK; if (IsSignedIn(user_id)) {
std::memcpy(online_id->data, "Kyty", 4);
return OK;
}
return np_error_signed_out; return np_error_signed_out;
} }
@@ -4271,10 +4295,13 @@ int KYTY_SYSV_ABI NpGetAccountIdA(int user_id, uint64_t* account_id) {
EXIT_NOT_IMPLEMENTED(account_id == nullptr); EXIT_NOT_IMPLEMENTED(account_id == nullptr);
// *account_id = 0x00000000feedfaceull;
*account_id = 0; *account_id = 0;
// return OK; if (IsSignedIn(user_id)) {
*account_id = 0x00000000feedfaceull;
return OK;
}
return np_error_signed_out; return np_error_signed_out;
} }
@@ -4287,10 +4314,12 @@ int KYTY_SYSV_ABI NpGetAccountCountryA(int user_id, void* country_code) {
auto* code = static_cast<NpCountryCode*>(country_code); auto* code = static_cast<NpCountryCode*>(country_code);
std::memset(code, 0, sizeof(*code)); std::memset(code, 0, sizeof(*code));
// code->data[0] = '';
// code->data[1] = 'S';
// return OK; if (IsSignedIn(user_id)) {
std::memcpy(code->data, "US", 2);
return OK;
}
return np_error_signed_out; return np_error_signed_out;
} }
@@ -4305,11 +4334,14 @@ int KYTY_SYSV_ABI NpGetAccountAge(int req_id, int user_id, uint8_t* age) {
return np_error_invalid_argument; return np_error_invalid_argument;
} }
// *age = 21; std::lock_guard lock(g_np_request_mutex);
*age = 0; auto* request = np_get_request_locked(req_id);
if (request == nullptr) {
return np_error_request_not_found;
}
// return OK; *age = IsSignedIn(user_id) ? 21 : 0;
return np_error_signed_out; return np_complete_locked(request, IsSignedIn(user_id) ? OK : np_error_signed_out);
} }
int KYTY_SYSV_ABI NpGetAccountLanguage2(int req_id, int user_id, void* language_code) { int KYTY_SYSV_ABI NpGetAccountLanguage2(int req_id, int user_id, void* language_code) {
@@ -4325,11 +4357,17 @@ int KYTY_SYSV_ABI NpGetAccountLanguage2(int req_id, int user_id, void* language_
std::lock_guard lock(g_np_request_mutex); std::lock_guard lock(g_np_request_mutex);
if (np_get_request_locked(req_id) == nullptr) { auto* request = np_get_request_locked(req_id);
if (request == nullptr) {
return np_error_request_not_found; return np_error_request_not_found;
} }
return np_error_signed_out; auto* code = static_cast<NpLanguageCode*>(language_code);
std::memset(code, 0, sizeof(*code));
if (IsSignedIn(user_id)) {
std::memcpy(code->code, "en-US", 5);
}
return np_complete_locked(request, IsSignedIn(user_id) ? OK : np_error_signed_out);
} }
int KYTY_SYSV_ABI NpCreateRequest() { int KYTY_SYSV_ABI NpCreateRequest() {
@@ -4428,8 +4466,9 @@ int KYTY_SYSV_ABI NpCheckNpAvailability(int req_id, const char* user, void* resu
return np_error_request_not_found; return np_error_request_not_found;
} }
// return OK; return np_complete_locked(request, std::strcmp(user, "Kyty") == 0
return np_complete_signed_out_locked(request); ? np_reachability_result(Config::GetUserId())
: np_error_signed_out);
} }
int KYTY_SYSV_ABI NpCheckNpReachability(int req_id, int user_id) { int KYTY_SYSV_ABI NpCheckNpReachability(int req_id, int user_id) {
@@ -4451,8 +4490,7 @@ int KYTY_SYSV_ABI NpCheckNpReachability(int req_id, int user_id) {
return np_error_request_not_found; return np_error_request_not_found;
} }
// return OK; return np_complete_locked(request, np_reachability_result(user_id));
return np_complete_signed_out_locked(request);
} }
int KYTY_SYSV_ABI NpPollAsync(int req_id, int* result) { int KYTY_SYSV_ABI NpPollAsync(int req_id, int* result) {
@@ -4509,23 +4547,10 @@ int KYTY_SYSV_ABI NpCheckPremium(int req_id, const NpCheckPremiumParameter* para
return np_error_request_not_found; return np_error_request_not_found;
} }
if (request->state == NpRequestState::Complete) {
request->result = np_error_invalid_argument;
return np_error_invalid_argument;
}
if (request->state == NpRequestState::Aborted) {
return np_error_aborted;
}
std::memset(result, 0, sizeof(*result)); std::memset(result, 0, sizeof(*result));
// result->authorized = true;
// request->state = NpRequestState::Complete; // The simulated account has no premium eligibility.
// request->result = OK; return np_complete_locked(request, np_reachability_result(param->user_id));
// return OK;
return np_complete_signed_out_locked(request);
} }
int KYTY_SYSV_ABI NpGetState(int user_id, uint32_t* state) { int KYTY_SYSV_ABI NpGetState(int user_id, uint32_t* state) {
@@ -4535,7 +4560,7 @@ int KYTY_SYSV_ABI NpGetState(int user_id, uint32_t* state) {
LOGF("\t user_id = %d\n", user_id); LOGF("\t user_id = %d\n", user_id);
*state = np_state_signed_out; *state = IsSignedIn(user_id) ? np_state_signed_in : np_state_signed_out;
return OK; return OK;
} }
@@ -4549,8 +4574,7 @@ int KYTY_SYSV_ABI NpGetNpReachabilityState(int user_id, uint32_t* state) {
LOGF("\t user_id = %d\n", user_id); LOGF("\t user_id = %d\n", user_id);
// *state = 2; // SCE_NP_REACHABILITY_STATE_REACHABLE *state = np_reachability_result(user_id) == OK ? 2 : 0; // REACHABLE / UNAVAILABLE
*state = 0; // SCE_NP_REACHABILITY_STATE_UNAVAILABLE
return OK; return OK;
} }
@@ -4564,7 +4588,7 @@ int KYTY_SYSV_ABI NpHasSignedUp(int user_id, bool* has_signed_up) {
LOGF("\t user_id = %d\n", user_id); LOGF("\t user_id = %d\n", user_id);
*has_signed_up = false; *has_signed_up = IsSignedIn(user_id);
return OK; return OK;
} }
+2
View File
@@ -182,6 +182,8 @@ struct NpCheckPremiumResult;
using NpStateCallbackA = KYTY_SYSV_ABI void (*)(int user_id, uint32_t state, void* userdata); using NpStateCallbackA = KYTY_SYSV_ABI void (*)(int user_id, uint32_t state, void* userdata);
bool IsSignedIn(int user_id);
int KYTY_SYSV_ABI NpCheckCallback(); int KYTY_SYSV_ABI NpCheckCallback();
int KYTY_SYSV_ABI NpSetNpTitleId(const NpTitleId* title_id, const NpTitleSecret* title_secret); int KYTY_SYSV_ABI NpSetNpTitleId(const NpTitleId* title_id, const NpTitleSecret* title_secret);
int KYTY_SYSV_ABI NpSetContentRestriction(const NpContentRestriction* restriction); int KYTY_SYSV_ABI NpSetContentRestriction(const NpContentRestriction* restriction);
+56 -17
View File
@@ -546,6 +546,7 @@ struct Ngs2VoiceInternal {
}; };
struct Module { struct Module {
std::vector<uint8_t> param, work, state; std::vector<uint8_t> param, work, state;
std::vector<float> channel_matrix;
void KYTY_SYSV_ABI (*handler)(Ngs2UserFxProcessContext*) = nullptr; void KYTY_SYSV_ABI (*handler)(Ngs2UserFxProcessContext*) = nullptr;
std::array<uintptr_t, 3> user_data {}; std::array<uintptr_t, 3> user_data {};
uint32_t flags = 1; uint32_t flags = 1;
@@ -563,6 +564,7 @@ struct Ngs2VoiceInternal {
std::vector<std::vector<float>> matrices; std::vector<std::vector<float>> matrices;
std::vector<Module> modules; std::vector<Module> modules;
std::vector<float> samples; std::vector<float> samples;
std::vector<float> mix_samples;
std::vector<float> output_matrix; std::vector<float> output_matrix;
uint32_t channels = 0; uint32_t channels = 0;
uint32_t output_id = 0; uint32_t output_id = 0;
@@ -614,6 +616,27 @@ struct Ngs2VoiceInternal {
return samples.data() + size_t(id) * grain * channels; return samples.data() + size_t(id) * grain * channels;
} }
void MixChannels(const std::vector<float>& matrix, uint32_t buffer, uint32_t grain) {
if (matrix.empty()) {
return;
}
EXIT_NOT_IMPLEMENTED(channels > 37);
auto* input = Buffer(buffer, grain);
mix_samples.assign(size_t(channels) * grain, 0.0f);
for (uint32_t dst = 0; dst < channels; ++dst) {
for (uint32_t src = 0; src < channels; ++src) {
const float level = matrix[dst * 37 + src];
if (level == 0.0f) {
continue;
}
for (uint32_t i = 0; i < grain; ++i) {
mix_samples[dst * grain + i] += input[src * grain + i] * level;
}
}
}
std::copy(mix_samples.begin(), mix_samples.end(), input);
}
void ResetSetupState() { void ResetSetupState() {
SetEvent(4); SetEvent(4);
std::ranges::fill(ports, Port {}); std::ranges::fill(ports, Port {});
@@ -1850,6 +1873,10 @@ static void Ngs2RenderVoice(Ngs2VoiceInternal& voice, const std::vector<Ngs2Voic
for (size_t m = 0; m < voice.modules.size(); ++m) { for (size_t m = 0; m < voice.modules.size(); ++m) {
const auto& info = custom.module[m]; const auto& info = custom.module[m];
auto& module = voice.modules[m]; auto& module = voice.modules[m];
if (info.module_id == 0x17) {
voice.MixChannels(module.channel_matrix, info.source_buffer_id, grain);
continue;
}
if (info.module_id != 0x19 && info.module_id != 0x1f) { if (info.module_id != 0x19 && info.module_id != 0x1f) {
if (!module.render_warned) { if (!module.render_warned) {
Log::WriteToConsoleAndLog(fmt::sprintf( Log::WriteToConsoleAndLog(fmt::sprintf(
@@ -1934,7 +1961,8 @@ int KYTY_SYSV_ABI Ngs2SystemRender(uintptr_t system_handle, const Ngs2RenderBuff
EXIT_NOT_IMPLEMENTED(voice->output_id >= num_buffer_info); EXIT_NOT_IMPLEMENTED(voice->output_id >= num_buffer_info);
const auto& output = buffer_info[voice->output_id]; const auto& output = buffer_info[voice->output_id];
EXIT_NOT_IMPLEMENTED(output.buffer == nullptr); EXIT_NOT_IMPLEMENTED(output.buffer == nullptr);
EXIT_NOT_IMPLEMENTED((output.waveform_type != 0x12 && output.waveform_type != 0x18) || EXIT_NOT_IMPLEMENTED(output.waveform_type != 0x12 && output.waveform_type != 0x18);
EXIT_NOT_IMPLEMENTED(voice->rack->type == Ngs2RackType::Mastering &&
output.num_channels != voice->channels); output.num_channels != voice->channels);
EXIT_NOT_IMPLEMENTED(output.buffer_size < grain * output.num_channels * EXIT_NOT_IMPLEMENTED(output.buffer_size < grain * output.num_channels *
(output.waveform_type == 0x12 ? sizeof(int16_t) (output.waveform_type == 0x12 ? sizeof(int16_t)
@@ -1954,6 +1982,9 @@ int KYTY_SYSV_ABI Ngs2SystemRender(uintptr_t system_handle, const Ngs2RenderBuff
const float level = voice->output_matrix.empty() const float level = voice->output_matrix.empty()
? (src == dst ? 1.0f : 0.0f) ? (src == dst ? 1.0f : 0.0f)
: voice->output_matrix.at(src * output.num_channels + dst); : voice->output_matrix.at(src * output.num_channels + dst);
if (level == 0.0f) {
continue;
}
for (uint32_t i = 0; i < grain; ++i) { for (uint32_t i = 0; i < grain; ++i) {
pcm[i * output.num_channels + dst] += voice->samples[src * grain + i] * level; pcm[i * output.num_channels + dst] += voice->samples[src * grain + i] * level;
} }
@@ -2452,7 +2483,7 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
voice->rack->option.custom_sampler.custom_rack_option.module[index].module_id; voice->rack->option.custom_sampler.custom_rack_option.module[index].module_id;
auto& module = voice->modules[index]; auto& module = voice->modules[index];
if ((param->id & 0xffffffe0u) != (0x40000000u | module_id << 8u) || if ((param->id & 0xffffffe0u) != (0x40000000u | module_id << 8u) ||
(module_id != 0x19 && module_id != 0x1f)) { (module_id != 0x17 && module_id != 0x19 && module_id != 0x1f)) {
if (!module.control_warned) { if (!module.control_warned) {
Log::WriteToConsoleAndLog(fmt::sprintf( Log::WriteToConsoleAndLog(fmt::sprintf(
"warning: NGS2 custom control 0x%08" PRIx32 " is ignored\n", "warning: NGS2 custom control 0x%08" PRIx32 " is ignored\n",
@@ -2461,6 +2492,16 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
} }
break; break;
} }
if (module_id == 0x17) {
EXIT_NOT_IMPLEMENTED(param->size != sizeof(Ngs2VoiceParamHeader) +
37 * 37 * sizeof(float));
const auto* levels = reinterpret_cast<const float*>(param + 1);
module.channel_matrix.resize(37 * 37);
std::transform(levels, levels + module.channel_matrix.size(),
module.channel_matrix.begin(),
[](float level) { return std::clamp(level, -32.0f, 32.0f); });
break;
}
if (module_id == 0x19) { if (module_id == 0x19) {
struct FxParam { struct FxParam {
Ngs2VoiceParamHeader header; Ngs2VoiceParamHeader header;
@@ -2660,24 +2701,22 @@ int KYTY_SYSV_ABI Ngs2VoiceGetState(uintptr_t voice_handle, Ngs2VoiceState* stat
submixer->voice_state.state_flags = Ngs2GetStateFlags(voice); submixer->voice_state.state_flags = Ngs2GetStateFlags(voice);
break; break;
} }
case Ngs2RackType::CustomMastering: { case Ngs2RackType::CustomMastering:
const auto configured_size =
voice->rack->option.custom_mastering.custom_rack_option.state_size;
EXIT_NOT_IMPLEMENTED(configured_size < sizeof(Ngs2CustomMasteringVoiceState));
EXIT_NOT_IMPLEMENTED(state_size != configured_size);
std::memset(state, 0, state_size);
auto* mastering = reinterpret_cast<Ngs2CustomMasteringVoiceState*>(state);
mastering->voice_state.state_flags = Ngs2GetStateFlags(voice);
break;
}
case Ngs2RackType::CustomSampler: { case Ngs2RackType::CustomSampler: {
const bool sampler = voice->rack->type == Ngs2RackType::CustomSampler;
const auto configured_size = const auto configured_size =
voice->rack->option.custom_sampler.custom_rack_option.state_size; voice->rack->option.custom_sampler.custom_rack_option.state_size;
EXIT_NOT_IMPLEMENTED(configured_size < sizeof(Ngs2CustomSamplerVoiceState)); EXIT_NOT_IMPLEMENTED(configured_size < (sampler ? sizeof(Ngs2CustomSamplerVoiceState)
EXIT_NOT_IMPLEMENTED(state_size != configured_size); : sizeof(Ngs2CustomMasteringVoiceState)));
std::memset(state, 0, state_size); if (state_size < configured_size) {
auto* sampler = reinterpret_cast<Ngs2CustomSamplerVoiceState*>(state); return static_cast<int32_t>(0x804a8011u);
Ngs2FillSamplerState(*voice, *sampler); }
std::memset(state, 0, configured_size);
if (sampler) {
Ngs2FillSamplerState(*voice, *reinterpret_cast<Ngs2CustomSamplerVoiceState*>(state));
} else {
state->state_flags = Ngs2GetStateFlags(voice);
}
break; break;
} }
case Ngs2RackType::Sampler: { case Ngs2RackType::Sampler: {
+2
View File
@@ -79,6 +79,7 @@ static void PrintUsage() {
::printf(" --console-language <0-29> Console language. Default: 1 (English US).\n"); ::printf(" --console-language <0-29> Console language. Default: 1 (English US).\n");
::printf(" --vulkan-validation Enable Vulkan validation.\n"); ::printf(" --vulkan-validation Enable Vulkan validation.\n");
::printf(" --network-connected Use the host local network.\n"); ::printf(" --network-connected Use the host local network.\n");
::printf(" --emulate-psn Simulate PSN sign-in for compatibility.\n");
::printf(" --gpu-assisted-validation Bounds-check shader accesses on the GPU.\n" ::printf(" --gpu-assisted-validation Bounds-check shader accesses on the GPU.\n"
" Implies --vulkan-validation; very slow.\n"); " Implies --vulkan-validation; very slow.\n");
::printf(" --shader-validation Enable shader validation.\n"); ::printf(" --shader-validation Enable shader validation.\n");
@@ -206,6 +207,7 @@ static constexpr Flag FLAGS[] = {
{"--vr", &ConfigOptions::vr_enabled}, {"--vr", &ConfigOptions::vr_enabled},
{"--amd-cpu", &ConfigOptions::amd_cpu_enabled}, {"--amd-cpu", &ConfigOptions::amd_cpu_enabled},
{"--network-connected", &ConfigOptions::network_connected}, {"--network-connected", &ConfigOptions::network_connected},
{"--emulate-psn", &ConfigOptions::psn_emulation_enabled},
{"--vulkan-validation", &ConfigOptions::vulkan_validation_enabled}, {"--vulkan-validation", &ConfigOptions::vulkan_validation_enabled},
{"--gpu-assisted-validation", &ConfigOptions::gpu_assisted_validation_enabled}, {"--gpu-assisted-validation", &ConfigOptions::gpu_assisted_validation_enabled},
{"--shader-validation", &ConfigOptions::shader_validation_enabled}, {"--shader-validation", &ConfigOptions::shader_validation_enabled},
+267
View File
@@ -73,6 +73,10 @@ namespace Libs::LibNpCommerce {
void InitNet_1_NpCommerce(Loader::SymbolDatabase *symbols); void InitNet_1_NpCommerce(Loader::SymbolDatabase *symbols);
} }
namespace Libs::LibNpAuth {
void InitNet_1_NpAuth(Loader::SymbolDatabase *symbols);
}
namespace Libs::LibNpEntitlementAccess { namespace Libs::LibNpEntitlementAccess {
void InitNet_1_NpEntitlementAccess(Loader::SymbolDatabase *symbols); void InitNet_1_NpEntitlementAccess(Loader::SymbolDatabase *symbols);
} }
@@ -2082,6 +2086,256 @@ void TestNetCtlHostIpv4() {
} }
#endif #endif
template <int Mode>
void KYTY_SYSV_ABI RecordNpState(int user_id, uint32_t state, void* arg) {
auto& event = *static_cast<std::array<int, 3>*>(arg);
++event[0];
event[1] = user_id;
event[2] = static_cast<int>(state);
}
void TestNpEmulation() {
using namespace Libs::Network::NpManager;
using namespace Libs::Network::NetCtl;
constexpr int SignedOut = static_cast<int>(0x80550006u);
constexpr int NetworkUnavailable = static_cast<int>(0x8055a02au);
constexpr int Aborted = static_cast<int>(0x80550012u);
struct AsyncParameters {
size_t size = sizeof(AsyncParameters);
uint64_t affinity = 0;
int priority = 0;
uint8_t padding[4] {};
} async_parameters;
struct PremiumParameters {
size_t size = sizeof(PremiumParameters);
int user_id = 0;
uint8_t padding[4] {};
uint64_t features = 1;
uint8_t reserved[32] {};
} premium_parameters;
struct PremiumResult {
bool authorized = true;
uint8_t reserved[32] {};
} premium_result;
const auto create_request = [&] (bool async) {
const int request = async
? NpCreateAsyncRequest(reinterpret_cast<const NpCreateAsyncRequestParameter*>(
&async_parameters))
: NpCreateRequest();
Check(request > 0, "create NP request");
return request;
};
const auto check_request = [&] (bool async, int expected, auto operation) {
const int request = create_request(async);
Check(operation(request) == (async ? OK : expected),
"NP call separates asynchronous submission from its execution result");
if (async) {
int result = 123;
Check(NpPollAsync(request, &result) == OK && result == expected,
"NP polling reports the completed operation result");
}
Check(NpDeleteRequest(request) == OK, "release completed NP request");
};
Config::ConfigOptions options;
options.printf_direction = Config::LogDirection::Silent;
Check(!options.network_connected && !options.psn_emulation_enabled,
"host networking and PSN emulation both require explicit opt-in");
static std::array<std::array<int, 3>, 4> events {};
const std::array<NpStateCallbackA, 4> callbacks {
RecordNpState<0>, RecordNpState<1>, RecordNpState<2>, RecordNpState<3>};
int mode = 0;
for (bool network : {false, true}) {
int previous_network_state = -1;
for (bool emulate : {false, true}) {
options.network_connected = network;
options.psn_emulation_enabled = emulate;
Config::Load(options);
Check(Config::IsNetworkConnected() == network && Config::PsnEmulationEnabled() == emulate,
"loading PSN emulation does not change the independent network option");
Check(NetCtlInit() == OK, "initialize network state for NP mode");
int network_state = -1;
Check(NetCtlGetState(&network_state) == OK && (network || network_state == 0),
"PSN emulation cannot connect disabled host networking");
Check(previous_network_state == -1 || previous_network_state == network_state,
"PSN emulation does not change host network connectivity");
previous_network_state = network_state;
const bool connected = network_state == 3;
Check(NpRegisterStateCallbackA(callbacks[mode], &events[mode]) > 0 &&
NpCheckCallback() == OK && events[mode][0] == 1 &&
events[mode][1] == Config::GetUserId() &&
events[mode][2] == (emulate ? 2 : 1),
"initial NP callback matches the selected emulated sign-in state");
++mode;
for (int player = 0; player < Config::MAX_PLAYERS; ++player) {
const int user_id = Config::GetPlayerUserId(player);
const bool signed_in = emulate && player == 0;
const int identity_result = signed_in ? OK : SignedOut;
const int online_result = !signed_in ? SignedOut : connected ? OK : NetworkUnavailable;
uint32_t state = 99;
bool signed_up = false;
Check(NpGetState(user_id, &state) == OK && state == (signed_in ? 2u : 1u) &&
NpHasSignedUp(user_id, &signed_up) == OK && signed_up == signed_in,
"only the primary emulated user has a persistent signed-in account");
Check(NpGetNpReachabilityState(user_id, &state) == OK &&
state == (signed_in && connected ? 2u : 0u),
"NP reachability requires both the emulated account and host connectivity");
uint64_t account_id = 0;
std::array<char, 20> online_id {};
std::array<char, 36> np_id {};
std::array<char, 4> country {};
Check(NpGetAccountIdA(user_id, &account_id) == identity_result &&
NpGetOnlineId(user_id, reinterpret_cast<NpOnlineId*>(online_id.data())) ==
identity_result &&
NpGetNpId(user_id, reinterpret_cast<NpId*>(np_id.data())) == identity_result &&
NpGetAccountCountryA(user_id, country.data()) == identity_result,
"NP identity and account-country getters agree on primary and guest sign-in");
if (signed_in) {
Check(account_id != 0 && online_id[0] != 0 && online_id[16] == 0 &&
std::strcmp(online_id.data(), np_id.data()) == 0 &&
country[0] != 0 && country[1] != 0 && country[2] == 0,
"emulated account identity and country remain usable while disconnected");
}
for (bool async : {false, true}) {
check_request(async, online_result, [&] (int request) {
return NpCheckNpReachability(request, user_id);
});
uint8_t age = 0;
check_request(async, identity_result, [&] (int request) {
return NpGetAccountAge(request, user_id, &age);
});
std::array<char, 48> language {};
check_request(async, identity_result, [&] (int request) {
return NpGetAccountLanguage2(request, user_id, language.data());
});
Check(!signed_in || (age > 0 && language[0] != 0 && language[35] == 0),
"signed-in account metadata is available independently of host networking");
premium_parameters.user_id = user_id;
premium_result.authorized = true;
check_request(async, online_result, [&] (int request) {
return NpCheckPremium(request,
reinterpret_cast<const NpCheckPremiumParameter*>(&premium_parameters),
reinterpret_cast<NpCheckPremiumResult*>(&premium_result));
});
Check(!premium_result.authorized, "PSN emulation does not grant premium eligibility");
if (player == 0) {
check_request(async, online_result, [&] (int request) {
return NpCheckNpAvailability(request, "Kyty", nullptr);
});
}
}
}
const int aborted = create_request(true);
int result = 123;
Check(NpAbortRequest(aborted) == OK &&
NpCheckNpReachability(aborted, Config::GetUserId()) == Aborted &&
NpPollAsync(aborted, &result) == OK && result == Aborted &&
NpDeleteRequest(aborted) == OK,
"NP emulation does not turn an aborted request into a successful one");
NetCtlTerm();
}
}
Config::Load(Config::ConfigOptions {});
}
void TestNpAuthEmulation() {
Loader::SymbolDatabase symbols;
Libs::LibNpAuth::InitNet_1_NpAuth(&symbols);
const auto resolve = [&] (const char* nid) {
const auto* symbol = symbols.FindByNid(nid, Loader::SymbolType::Func);
Check(symbol != nullptr, "resolve NP authentication exports");
return symbol->vaddr;
};
const auto create = reinterpret_cast<int(KYTY_SYSV_ABI*)()>(resolve("6bwFkosYRQg"));
const auto create_async = reinterpret_cast<int(KYTY_SYSV_ABI*)(const void*)>(
resolve("N+mr7GjTvr8"));
const auto destroy = reinterpret_cast<int(KYTY_SYSV_ABI*)(int)>(resolve("H8wG9Bk-nPc"));
const auto poll = reinterpret_cast<int(KYTY_SYSV_ABI*)(int, int*)>(resolve("gjSyfzSsDcE"));
const auto get_code = reinterpret_cast<int(KYTY_SYSV_ABI*)(int, const void*, void*, int*)>(
resolve("KI4dHLlTNl0"));
const auto get_token = reinterpret_cast<int(KYTY_SYSV_ABI*)(int, const void*, void*)>(
resolve("RdsFVsgSpZY"));
constexpr int InvalidArgument = static_cast<int>(0x80550301u);
constexpr int InvalidSize = static_cast<int>(0x80550302u);
constexpr int ServiceUnavailable = static_cast<int>(0x80550401u);
constexpr int SignedOut = static_cast<int>(0x80550006u);
struct AuthorizationCodeParameters {
size_t size = sizeof(AuthorizationCodeParameters);
int user_id = 0;
uint8_t padding[4] {};
const void* client_id = "test-client";
const char* scope = "test-scope";
} code_parameters;
struct IdTokenParameters {
size_t size = sizeof(IdTokenParameters);
int user_id = 0;
uint8_t padding[4] {};
const void* client_id = "test-client";
const void* client_secret = "test-secret";
const char* scope = "test-scope";
} token_parameters;
std::array<uint8_t, 136> code {};
std::array<uint8_t, 4104> token {};
const std::array<uint64_t, 3> async_parameters {24, 0, 0};
int issuer = 0;
const int invalid_request = create();
code_parameters.size = 0;
token_parameters.size = 0;
Check(invalid_request > 0 &&
get_code(invalid_request, nullptr, code.data(), &issuer) == InvalidArgument &&
get_token(invalid_request, nullptr, token.data()) == InvalidArgument &&
get_code(invalid_request, &code_parameters, code.data(), &issuer) == InvalidSize &&
get_token(invalid_request, &token_parameters, token.data()) == InvalidSize &&
destroy(invalid_request) == OK,
"NP authentication rejects missing and undersized user parameters");
code_parameters.size = sizeof(code_parameters);
token_parameters.size = sizeof(token_parameters);
Config::ConfigOptions options;
options.printf_direction = Config::LogDirection::Silent;
for (bool network : {false, true}) {
for (bool emulate : {false, true}) {
options.network_connected = network;
options.psn_emulation_enabled = emulate;
Config::Load(options);
for (int player = 0; player < Config::MAX_PLAYERS; ++player) {
code_parameters.user_id = Config::GetPlayerUserId(player);
token_parameters.user_id = code_parameters.user_id;
const int expected = emulate && player == 0 ? ServiceUnavailable : SignedOut;
for (bool async : {false, true}) {
for (bool id_token : {false, true}) {
const int request = async ? create_async(async_parameters.data()) : create();
Check(request > 0, "create NP authentication request");
code.fill(0xff);
token.fill(0xff);
issuer = -1;
const int result = id_token
? get_token(request, &token_parameters, token.data())
: get_code(request, &code_parameters, code.data(), &issuer);
Check(result == (async ? OK : expected),
"NP authentication distinguishes an unavailable service from sign-out");
if (async) {
int completion = 123;
Check(poll(request, &completion) == OK && completion == expected,
"asynchronous NP authentication delivers the actual failure");
}
const auto zero = [] (uint8_t value) { return value == 0; };
Check(id_token ? std::all_of(token.begin(), token.end(), zero)
: std::all_of(code.begin(), code.end(), zero) && issuer == 0,
"PSN emulation does not fabricate authorization codes or ID tokens");
Check(destroy(request) == OK, "release NP authentication request");
}
}
}
}
}
Config::Load(Config::ConfigOptions {});
}
void CheckEtherAddressFormatting() { void CheckEtherAddressFormatting() {
Loader::SymbolDatabase symbols; Loader::SymbolDatabase symbols;
Libs::LibNet::InitNet_1_Net(&symbols); Libs::LibNet::InitNet_1_Net(&symbols);
@@ -2458,6 +2712,17 @@ int main(int argc, char** argv) {
Config::Load(options); Config::Load(options);
subsystems.Initialize<Log::Lifecycle>(); subsystems.Initialize<Log::Lifecycle>();
if (argc == 2 && std::string_view(argv[1]) == "--network-only") {
#if defined(__linux__)
TestNetCtlHostIpv4();
#endif
TestNpEmulation();
TestNpAuthEmulation();
subsystems.Destroy();
std::printf("KernelFileSystemTests: network cases passed\n");
return 0;
}
TestMutexHandoff(); TestMutexHandoff();
if (argc == 2 && std::string_view(argv[1]) == "--mutex-only") { if (argc == 2 && std::string_view(argv[1]) == "--mutex-only") {
subsystems.Destroy(); subsystems.Destroy();
@@ -2503,6 +2768,8 @@ int main(int argc, char** argv) {
#if defined(__linux__) #if defined(__linux__)
TestNetCtlHostIpv4(); TestNetCtlHostIpv4();
#endif #endif
TestNpEmulation();
TestNpAuthEmulation();
TestNpWebApi2Memory(); TestNpWebApi2Memory();
TestNpCommerceDialog(); TestNpCommerceDialog();
graphics.reset(); graphics.reset();
+124 -19
View File
@@ -751,6 +751,26 @@ void TestCustomSideChainUserFx() {
"user FX did not receive the side-chain buffer after the source channels"); "user FX did not receive the side-chain buffer after the source channels");
} }
struct RenderRack {
std::unique_ptr<std::byte[]> storage;
uintptr_t handle = 0;
RenderRack(Ngs2Internal& system, uint32_t id, const Ngs2RackOption& option) {
Ngs2ContextBufferInfo buffer {};
Check(Ngs2RackQueryBufferSize(id, &option, &buffer) == OK, "rack size query failed");
storage = std::make_unique<std::byte[]>(buffer.host_buffer_size);
buffer.host_buffer = storage.get();
Check(Ngs2RackCreate(reinterpret_cast<uintptr_t>(&system), id, &option, &buffer,
&handle) == OK, "render rack creation failed");
}
~RenderRack() { Check(Ngs2RackDestroy(handle, nullptr) == OK, "render rack cleanup failed"); }
Ngs2VoiceInternal& Voice() {
return *reinterpret_cast<Ngs2VoiceInternal*>(reinterpret_cast<Ngs2RackInternal*>(handle) + 1);
}
};
void TestPcm16RenderOutput() { void TestPcm16RenderOutput() {
Ngs2Internal system; Ngs2Internal system;
system.option = Ngs2DefaultSystemOption(); system.option = Ngs2DefaultSystemOption();
@@ -760,23 +780,11 @@ void TestPcm16RenderOutput() {
Ngs2FillDefaultRackOption(0x3000, &mastering_option); Ngs2FillDefaultRackOption(0x3000, &mastering_option);
sampler_option.common.max_voices = 2; sampler_option.common.max_voices = 2;
sampler_option.common.max_ports = 1; sampler_option.common.max_ports = 1;
alignas(Ngs2RackInternal) alignas(Ngs2VoiceInternal) RenderRack sampler(system, 0x1000, sampler_option.common);
std::byte sampler_storage[sizeof(Ngs2RackInternal) + 2 * sizeof(Ngs2VoiceInternal)]; RenderRack master(system, 0x3000, mastering_option.common);
alignas(Ngs2RackInternal) alignas(Ngs2VoiceInternal)
std::byte mastering_storage[sizeof(Ngs2RackInternal) + sizeof(Ngs2VoiceInternal)];
Ngs2ContextBufferInfo sampler_buffer {sampler_storage, sizeof(sampler_storage)};
Ngs2ContextBufferInfo mastering_buffer {mastering_storage, sizeof(mastering_storage)};
uintptr_t sampler_handle = 0, mastering_handle = 0;
const auto system_handle = reinterpret_cast<uintptr_t>(&system); const auto system_handle = reinterpret_cast<uintptr_t>(&system);
Check(Ngs2RackCreate(system_handle, 0x1000, &sampler_option.common, &sampler_buffer, auto* samplers = &sampler.Voice();
&sampler_handle) == OK && auto& mastering = master.Voice();
Ngs2RackCreate(system_handle, 0x3000, &mastering_option.common,
&mastering_buffer, &mastering_handle) == OK,
"PCM16 render rack creation failed");
auto* samplers = reinterpret_cast<Ngs2VoiceInternal*>(
reinterpret_cast<Ngs2RackInternal*>(sampler_handle) + 1);
auto& mastering = *reinterpret_cast<Ngs2VoiceInternal*>(
reinterpret_cast<Ngs2RackInternal*>(mastering_handle) + 1);
mastering.channels = 1; mastering.channels = 1;
mastering.state = Ngs2VoicePlayState::Playing; mastering.state = Ngs2VoicePlayState::Playing;
const int16_t source[2][4] = {{16384, -16384, 32767, -32768}, const int16_t source[2][4] = {{16384, -16384, 32767, -32768},
@@ -797,9 +805,104 @@ void TestPcm16RenderOutput() {
Check(Ngs2SystemRender(system_handle, &output, 1) == OK && Check(Ngs2SystemRender(system_handle, &output, 1) == OK &&
std::ranges::all_of(output_samples, [](int16_t sample) { return sample == 0; }), std::ranges::all_of(output_samples, [](int16_t sample) { return sample == 0; }),
"PCM16 output retained samples from the previous render"); "PCM16 output retained samples from the previous render");
Check(Ngs2RackDestroy(mastering_handle, nullptr) == OK && }
Ngs2RackDestroy(sampler_handle, nullptr) == OK,
"PCM16 render rack cleanup failed"); void TestCustomMasteringChannelMixer() {
Ngs2Internal system;
system.option = Ngs2DefaultSystemOption();
system.option.num_grain_samples = 6;
Ngs2RackOptionUnion sampler_option {};
Ngs2FillDefaultRackOption(0x1000, &sampler_option);
sampler_option.common.max_voices = 1;
sampler_option.common.max_ports = 1;
Ngs2CustomMasteringRackOption master_option {};
auto& custom = master_option.custom_rack_option;
custom.rack_option.size = sizeof(master_option);
custom.rack_option.max_voices = 1;
custom.rack_option.max_voice_channels = 6;
custom.num_buffers = 1;
custom.num_modules = 1;
custom.module[0].module_id = 0x17;
RenderRack sampler(system, 0x1000, sampler_option.common);
RenderRack master(system, 0x4003, custom.rack_option);
auto& source = sampler.Voice();
auto& output = master.Voice();
output.channels = 6;
output.SetEvent(1);
struct {
Ngs2VoiceParamHeader header {5484, 0, 0x40001700};
float levels[37][37] {};
} matrix;
constexpr float center = 0.70710677f;
matrix.levels[0][0] = matrix.levels[1][1] = 1.0f;
matrix.levels[0][2] = matrix.levels[1][2] = center;
matrix.levels[0][4] = matrix.levels[1][5] = center;
int16_t impulses[6][6] {};
for (uint32_t c = 0; c < 6; ++c) impulses[c][c] = 8192;
for (unsigned pass = 0; pass < 2; ++pass) {
if (pass == 1) {
std::memset(matrix.levels, 0, sizeof(matrix.levels));
matrix.levels[0][1] = 64.0f;
matrix.levels[1][0] = -64.0f;
}
Check(Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&output), &matrix.header) == OK,
"custom channel mixer control failed");
source.SetupSampler({0x12, 6, 48000});
source.blocks.push_back({reinterpret_cast<const uint8_t*>(impulses),
{0, sizeof(impulses), 0, 0, 6}});
source.ports[0].dest = &output;
source.SetEvent(1);
std::array<float, 14> pcm;
pcm.fill(99.0f);
Ngs2RenderBufferInfo buffer {pcm.data() + 1, 12 * sizeof(float), 0x18, 2};
Check(Ngs2SystemRender(reinterpret_cast<uintptr_t>(&system), &buffer, 1) == OK,
"custom mastering rejected stereo output");
const std::array<float, 12> expected = pass == 0
? std::array<float, 12> {0.25f, 0, 0, 0.25f, center / 4, center / 4,
0, 0, center / 4, 0, 0, center / 4}
: std::array<float, 12> {0, -8, 8, 0};
Check(pcm.front() == 99 && pcm.back() == 99 &&
std::equal(expected.begin(), expected.end(), pcm.begin() + 1),
"custom mixer lost channel routing, in-place input, clamping or output bounds");
}
}
void TestCustomVoiceStateCapacity() {
for (auto type: {Ngs2RackType::CustomSampler, Ngs2RackType::CustomMastering}) {
Fixture f(48000, 1, Ngs2RackType::CustomSampler);
f.rack.type = type;
f.rack.option.custom_sampler.custom_rack_option.state_size = 212;
f.voice.state_flags = 3;
f.voice.decoded_samples = 37;
f.voice.decoded_bytes = 128;
alignas(Ngs2CustomSamplerVoiceState) std::array<uint8_t, 384> storage;
auto* state = reinterpret_cast<Ngs2VoiceState*>(storage.data() + 8);
const auto handle = reinterpret_cast<uintptr_t>(&f.voice);
for (size_t capacity: {212u, 368u}) {
storage.fill(0xa5);
Check(Ngs2VoiceGetState(handle, state, capacity) == OK && state->state_flags == 3 &&
state->error_code == 0 &&
std::all_of(storage.begin(), storage.begin() + 8,
[](uint8_t v) { return v == 0xa5; }) &&
std::all_of(storage.begin() + 220, storage.end(),
[](uint8_t v) { return v == 0xa5; }),
"oversized custom state buffer was rejected or overwritten past configured state");
if (type == Ngs2RackType::CustomSampler) {
const auto* sampler = reinterpret_cast<const Ngs2CustomSamplerVoiceState*>(state);
Check(sampler->num_decoded_samples == 37 && sampler->decoded_data_size == 128,
"custom sampler state omitted playback progress");
}
const size_t base_size = type == Ngs2RackType::CustomSampler
? sizeof(Ngs2CustomSamplerVoiceState) : sizeof(Ngs2CustomMasteringVoiceState);
Check(std::all_of(storage.begin() + 8 + base_size, storage.begin() + 220,
[](uint8_t v) { return v == 0; }),
"configured custom state extension was not initialized");
}
storage.fill(0xa5);
Check(Ngs2VoiceGetState(handle, state, 211) == static_cast<int32_t>(0x804a8011u) &&
std::ranges::all_of(storage, [](uint8_t v) { return v == 0xa5; }),
"undersized custom state buffer was accepted or modified");
}
} }
void TestPitchLoopAndSkip() { void TestPitchLoopAndSkip() {
@@ -1700,6 +1803,8 @@ int main() {
TestAtrac9Vibration(); TestAtrac9Vibration();
TestAtrac9Discrete(); TestAtrac9Discrete();
TestPcm16RenderOutput(); TestPcm16RenderOutput();
TestCustomMasteringChannelMixer();
TestCustomVoiceStateCapacity();
TestSubmixerSetupAndRouting(); TestSubmixerSetupAndRouting();
TestCustomSideChainUserFx(); TestCustomSideChainUserFx();
TestWaveformBlockGeometry(); TestWaveformBlockGeometry();
File diff suppressed because it is too large. Load diff
+82
View File
@@ -2397,6 +2397,50 @@ void TestNewShaderRecompilerVop3LaneReadDestinationEncoding() {
CheckSpirvBinaryValidates(result.spirv); CheckSpirvBinaryValidates(result.spirv);
} }
void TestVop3FloatConversionDecode() {
namespace Decoder = ShaderRecompiler::Decoder;
const std::pair<uint32_t, Decoder::Opcode> conversions[] = {
{0x18b, Decoder::Opcode::V_CVT_F32_F16},
{0x191, Decoder::Opcode::V_CVT_F32_UBYTE0},
{0x192, Decoder::Opcode::V_CVT_F32_UBYTE1},
{0x193, Decoder::Opcode::V_CVT_F32_UBYTE2},
{0x194, Decoder::Opcode::V_CVT_F32_UBYTE3},
};
for (const auto &[encoding, opcode] : conversions) {
for (const uint32_t source : {261u, 12u, 128u, 255u}) {
const uint32_t code[] = {
EncodeVop3Word0(encoding, 20, 0, 1, true),
EncodeVop3Word1(source, 0, 0) | (2u << 27u) | (1u << 29u),
0xa5a53e00u,
};
Decoder::Instruction inst;
Decoder::DecodeInstruction(code, 0, inst);
Check(inst.opcode == opcode && inst.family == Decoder::Family::VOP3 &&
inst.src_count == 1 && inst.word_count == (source == 255u ? 3u : 2u),
"VOP3 conversion lost its opcode, unary source, or literal length");
Check(inst.dst.kind == Decoder::OperandKind::Vgpr && inst.dst.reg == 20 &&
inst.dst.sdwa_sel == 6 && inst.dst.clamp && inst.dst.omod == 2,
"VOP3 conversion lost its full-width destination or output modifiers");
const bool half = opcode == Decoder::Opcode::V_CVT_F32_F16;
Check(inst.src0.absolute == half && inst.src0.negate == half,
"VOP3 conversion must apply half-float modifiers and ignore byte modifiers");
Decoder::Operand expected_source;
Decoder::DecodeScalarSource(source, 0, expected_source);
Check(inst.src0.kind == expected_source.kind && inst.src0.reg == expected_source.reg &&
(source != 255u || inst.src0.value == code[2]),
"VOP3 conversion changed its source register or literal value");
}
for (const uint32_t op_sel : {1u, 2u, 4u, 8u}) {
const uint32_t code[] = {EncodeVop3Word0(encoding, 20, op_sel),
EncodeVop3Word1(261, 0, 0)};
Decoder::Instruction inst;
Decoder::DecodeInstruction(code, 0, inst);
Check(inst.opcode == Decoder::Opcode::UNSUPPORTED,
"VOP3 conversion accepted unsupported OP_SEL bits");
}
}
}
void TestNewShaderRecompilerMoreAluFamilies() { void TestNewShaderRecompilerMoreAluFamilies() {
const uint32_t shader[] = { const uint32_t shader[] = {
EncodeSopk(0x00, 9, 7), // s_movk_i32 s9, 7 EncodeSopk(0x00, 9, 7), // s_movk_i32 s9, 7
@@ -10550,6 +10594,43 @@ void TestNewShaderRecompilerNativeU64Translation() {
"captured VOP3 V_LSHLREV_B64 was not present in the decoded dump"); "captured VOP3 V_LSHLREV_B64 was not present in the decoded dump");
CheckSpirvBinaryValidates(result.spirv); CheckSpirvBinaryValidates(result.spirv);
const uint32_t ashrrev_i64_shader[] = {
0xd7010001u, 0x0000d50cu,
EncodeSopp(0x01),
};
Decoder::Instruction decoded_ashrrev_i64;
ShaderRecompiler::Decoder::DecodeInstruction(ashrrev_i64_shader, 0u,
decoded_ashrrev_i64);
Check(decoded_ashrrev_i64.opcode == Decoder::Opcode::V_ASHRREV_I64 &&
decoded_ashrrev_i64.dst.kind == Decoder::OperandKind::Vgpr &&
decoded_ashrrev_i64.dst.reg == 1u &&
decoded_ashrrev_i64.src_count == 2u &&
decoded_ashrrev_i64.src0.kind == Decoder::OperandKind::Vgpr &&
decoded_ashrrev_i64.src0.reg == 12u &&
decoded_ashrrev_i64.src1.kind == Decoder::OperandKind::VccLo,
"decoder rejected VOP3 V_ASHRREV_I64 fields");
result = RecompileForTest(ashrrev_i64_shader, options);
Check((result.decoded_dump.find("V_ASHRREV_I64 v1, v12, vcc_lo") != std::string::npos),
"VOP3 V_ASHRREV_I64 was not present in the decoded dump");
CheckSpirvBinaryValidates(result.spirv);
const uint32_t ashrrev_captured[] = {0xd7010007u, 0x00020498u, EncodeSopp(0x01)};
Decoder::Instruction decoded_ashrrev_captured;
ShaderRecompiler::Decoder::DecodeInstruction(ashrrev_captured, 0u,
decoded_ashrrev_captured);
Check(decoded_ashrrev_captured.opcode == Decoder::Opcode::V_ASHRREV_I64 &&
decoded_ashrrev_captured.family == Decoder::Family::VOP3 &&
decoded_ashrrev_captured.src_count == 2u &&
decoded_ashrrev_captured.dst.kind == Decoder::OperandKind::Vgpr &&
decoded_ashrrev_captured.dst.reg == 7u &&
decoded_ashrrev_captured.src0.kind == Decoder::OperandKind::IntegerInlineConstant &&
decoded_ashrrev_captured.src0.value == 24u &&
decoded_ashrrev_captured.src1.kind == Decoder::OperandKind::Vgpr &&
decoded_ashrrev_captured.src1.reg == 2u,
"captured VOP3 V_ASHRREV_I64 opcode 0x301 was not decoded");
result = RecompileForTest(ashrrev_captured, options);
CheckSpirvBinaryValidates(result.spirv);
const uint32_t cmpx_i64_shader[] = { const uint32_t cmpx_i64_shader[] = {
0xd4b5007eu, 0x00020e80u, // v_cmpx_ne_i64 exec, 0, v[7:8] 0xd4b5007eu, 0x00020e80u, // v_cmpx_ne_i64 exec, 0, v[7:8]
EncodeSopp(0x01), EncodeSopp(0x01),
@@ -17121,6 +17202,7 @@ int main() {
using namespace Libs::Graphics; using namespace Libs::Graphics;
EnsureConfigInitialized(); EnsureConfigInitialized();
TestVop3FloatConversionDecode();
TestRayTracingInstructions(); TestRayTracingInstructions();
TestResourceDescriptorClassification(); TestResourceDescriptorClassification();
TestShaderBufferResourceSize(); TestShaderBufferResourceSize();