From fbd11dc28fb4f978ecfdc638aeb1577d5503128a Mon Sep 17 00:00:00 2001 From: Mcm190 Date: Wed, 4 Nov 2026 20:19:47 +0000 Subject: [PATCH] nx2: get Star Fox (#533) past boot and rendering Star Fox stopped on "Launching..." at 0 FPS, then rendered only black once it booted. - nx2_gsv: report 4 KiB native image alignment. The SDK rejected the window buffers (8 KiB required, 4 KiB offset), dropped the window and later called through a null pointer. - audio: implement hwopus command 100 (OpenHardwareAudioCodecClient) and the hwacod client/manager it opens, decoding Opus and multistream Opus with libopus into the shared completion ring. - lower_to_maxwell: lower TXD with a uniform-register handle (0xf6c) to TXD.B, I2F.U16 from a uniform register (0xd06), and ULDC U8/S8/U16/S16 with alignment matching the width. - kepler_compute: let the upscale fallback convert formats when no same-format pair exists (Star Fox: R16G16B16A16 to A8B8G8R8). Known issue: about a minute into gameplay it crashes with SIGFPE in RADV under TextureCacheRuntime::ReinterpretImage. Fixes #533 (boot and black screen only) Signed-off-by: Mcm190 --- src/core/CMakeLists.txt | 5 + .../audio/hardware_opus_decoder_manager.cpp | 8 + .../audio/hardware_opus_decoder_manager.h | 3 + .../audio/nx2_hardware_audio_codec_client.cpp | 109 ++++++++ .../audio/nx2_hardware_audio_codec_client.h | 40 +++ .../nx2_hardware_audio_codec_manager.cpp | 255 ++++++++++++++++++ .../audio/nx2_hardware_audio_codec_manager.h | 60 +++++ .../audio/nx2_hardware_audio_codec_types.h | 45 ++++ src/core/hle/service/nx2_gsv.h | 4 +- .../frontend/ampere/lower_to_maxwell.h | 52 ++-- src/video_core/engines/kepler_compute.cpp | 42 +-- 11 files changed, 590 insertions(+), 33 deletions(-) create mode 100644 src/core/hle/service/audio/nx2_hardware_audio_codec_client.cpp create mode 100644 src/core/hle/service/audio/nx2_hardware_audio_codec_client.h create mode 100644 src/core/hle/service/audio/nx2_hardware_audio_codec_manager.cpp create mode 100644 src/core/hle/service/audio/nx2_hardware_audio_codec_manager.h create mode 100644 src/core/hle/service/audio/nx2_hardware_audio_codec_types.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 1ff9f271d2..8940552efe 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -546,6 +546,11 @@ add_library(core STATIC hle/service/audio/hardware_opus_decoder_manager.h hle/service/audio/hardware_opus_decoder.cpp hle/service/audio/hardware_opus_decoder.h + hle/service/audio/nx2_hardware_audio_codec_client.cpp + hle/service/audio/nx2_hardware_audio_codec_client.h + hle/service/audio/nx2_hardware_audio_codec_manager.cpp + hle/service/audio/nx2_hardware_audio_codec_manager.h + hle/service/audio/nx2_hardware_audio_codec_types.h hle/service/bcat/backend/backend.cpp hle/service/bcat/backend/backend.h hle/service/bcat/bcat.cpp diff --git a/src/core/hle/service/audio/hardware_opus_decoder_manager.cpp b/src/core/hle/service/audio/hardware_opus_decoder_manager.cpp index b89e210012..3b2174b458 100644 --- a/src/core/hle/service/audio/hardware_opus_decoder_manager.cpp +++ b/src/core/hle/service/audio/hardware_opus_decoder_manager.cpp @@ -6,6 +6,7 @@ #include "core/hle/service/audio/hardware_opus_decoder.h" #include "core/hle/service/audio/hardware_opus_decoder_manager.h" +#include "core/hle/service/audio/nx2_hardware_audio_codec_client.h" #include "core/hle/service/cmif_serialization.h" namespace Service::Audio { @@ -26,6 +27,7 @@ IHardwareOpusDecoderManager::IHardwareOpusDecoderManager(Core::System& system_) {7, D<&IHardwareOpusDecoderManager::GetWorkBufferSizeForMultiStreamEx>, "GetWorkBufferSizeForMultiStreamEx"}, {8, D<&IHardwareOpusDecoderManager::GetWorkBufferSizeExEx>, "GetWorkBufferSizeExEx"}, {9, D<&IHardwareOpusDecoderManager::GetWorkBufferSizeForMultiStreamExEx>, "GetWorkBufferSizeForMultiStreamExEx"}, + {100, D<&IHardwareOpusDecoderManager::OpenHardwareAudioCodecClient>, "OpenHardwareAudioCodecClient"}, }; // clang-format on RegisterHandlers(functions); @@ -33,6 +35,12 @@ IHardwareOpusDecoderManager::IHardwareOpusDecoderManager(Core::System& system_) IHardwareOpusDecoderManager::~IHardwareOpusDecoderManager() = default; +Result IHardwareOpusDecoderManager::OpenHardwareAudioCodecClient( + Out> out_client) { + *out_client = std::make_shared(system); + R_SUCCEED(); +} + Result IHardwareOpusDecoderManager::OpenHardwareOpusDecoder( Out> out_decoder, OpusParameters params, u32 tmem_size, InCopyHandle tmem_handle) { diff --git a/src/core/hle/service/audio/hardware_opus_decoder_manager.h b/src/core/hle/service/audio/hardware_opus_decoder_manager.h index 4dd211e232..e1a96532bd 100644 --- a/src/core/hle/service/audio/hardware_opus_decoder_manager.h +++ b/src/core/hle/service/audio/hardware_opus_decoder_manager.h @@ -13,6 +13,7 @@ namespace Service::Audio { class IHardwareOpusDecoder; +class INx2HardwareAudioCodecClient; using AudioCore::OpusDecoder::OpusMultiStreamParameters; using AudioCore::OpusDecoder::OpusMultiStreamParametersEx; @@ -25,6 +26,8 @@ class IHardwareOpusDecoderManager final : public ServiceFramework> out_client); Result OpenHardwareOpusDecoder(Out> out_decoder, OpusParameters params, u32 tmem_size, InCopyHandle tmem_handle); diff --git a/src/core/hle/service/audio/nx2_hardware_audio_codec_client.cpp b/src/core/hle/service/audio/nx2_hardware_audio_codec_client.cpp new file mode 100644 index 0000000000..e6eff5268c --- /dev/null +++ b/src/core/hle/service/audio/nx2_hardware_audio_codec_client.cpp @@ -0,0 +1,109 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "audio_core/adsp/apps/opus/opus_decode_object.h" +#include "audio_core/adsp/apps/opus/opus_multistream_decode_object.h" +#include "common/alignment.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/kernel/k_transfer_memory.h" +#include "core/hle/service/audio/errors.h" +#include "core/hle/service/audio/nx2_hardware_audio_codec_client.h" +#include "core/hle/service/audio/nx2_hardware_audio_codec_manager.h" +#include "core/hle/service/cmif_serialization.h" +#include "core/memory.h" + +namespace Service::Audio { + +INx2HardwareAudioCodecClient::INx2HardwareAudioCodecClient(Core::System& system_) + : ServiceFramework{system_, "nx2:hwacod:client"} { + static const FunctionInfo functions[] = { + {0, D<&INx2HardwareAudioCodecClient::GetInstanceWorkBufferSize>, + "GetInstanceWorkBufferSize"}, + {1, D<&INx2HardwareAudioCodecClient::GetManagerWorkBufferSize>, "GetManagerWorkBufferSize"}, + {10, D<&INx2HardwareAudioCodecClient::OpenManager>, "OpenManager"}, + }; + RegisterHandlers(functions); +} + +Result INx2HardwareAudioCodecClient::GetInstanceWorkBufferSize( + Out out_size, + InLargeData parameter) { + using namespace AudioCore::ADSP::OpusDecoder; + const auto& p = *parameter; + R_UNLESS(p.sample_rate == 8000 || p.sample_rate == 12000 || p.sample_rate == 16000 || + p.sample_rate == 24000 || p.sample_rate == 48000, + ResultInvalidOpusSampleRate); + u32 size{}; + switch (p.codec) { + case 1: + R_UNLESS(p.channel_count == 1 || p.channel_count == 2, ResultInvalidOpusChannelCount); + size = OpusDecodeObject::GetWorkBufferSize(p.channel_count); + break; + case 2: + R_UNLESS(p.channel_count > 0 && p.channel_count <= 255 && p.stream_count > 0 && + p.stream_count <= 255 && p.stereo_stream_count <= p.stream_count && + p.stream_count + p.stereo_stream_count <= 255, + ResultInvalidOpusChannelCount); + size = + OpusMultiStreamDecodeObject::GetWorkBufferSize(p.stream_count, p.stereo_stream_count); + break; + default: + R_THROW(ResultNotSupported); + } + // Keep guest allocations independent of optional neural features in the host libopus. + *out_size = Common::AlignUp(size, 64); + LOG_DEBUG(Service_Audio, "hwacod codec={} rate={} channels={} instance size={:#x}", p.codec, + p.sample_rate, p.channel_count, *out_size); + R_SUCCEED(); +} + +Result INx2HardwareAudioCodecClient::GetManagerWorkBufferSize(Out out_size, u32 io_count) { + R_UNLESS(io_count > 0 && io_count <= 0xffff, ResultInvalidArgument); + // DSP state is host-owned; reserve one guest page. The SDK sizes the task rings separately. + *out_size = 0x1000; + R_SUCCEED(); +} + +Result INx2HardwareAudioCodecClient::OpenManager( + Out out_id, Out> out_manager, + OutCopyHandle out_event, + Nx2HardwareAudioCodecSharedMemory p, InCopyHandle transfer_memory, + InCopyHandle process, u64 transfer_size, u64 shared_address, u64 shared_size, + u64 properties) { + R_UNLESS(transfer_memory && process && transfer_memory->GetOwner() == process.Get(), + ResultInvalidHandle); + R_UNLESS(p.version == 1 && p.instance_count > 0 && p.instance_count <= 0xffff && + p.io_count > 0 && p.io_count <= 0xffff && next_manager_id <= 0xffff, + ResultInvalidArgument); + R_UNLESS(transfer_size <= transfer_memory->GetSize() && + p.transfer_memory_size <= transfer_size && p.shared_memory_size <= shared_size, + ResultInsufficientBuffer); + const auto fits = [](u64 offset, u64 size, u64 total) { + return offset <= total && size <= total - offset; + }; + const u64 io_size = u64{p.io_count} * (u64{p.input_capacity} + p.output_capacity); + const u64 ring_size = 0x80 + (u64{p.io_count} + 1) * 0x40; + R_UNLESS(p.input_capacity > 0 && p.input_capacity <= 1024 * 1024 && p.output_capacity > 0 && + p.output_capacity <= 4 * 1024 * 1024 && + fits(p.io_data_offset, p.io_data_size, p.shared_memory_size) && + io_size <= p.io_data_size && + fits(p.completion_ring_offset, p.completion_ring_size, p.shared_memory_size) && + ring_size <= p.completion_ring_size, + ResultInvalidAddressInfo); + R_UNLESS(process->GetMemory().IsValidVirtualAddressRange(shared_address, shared_size), + ResultInvalidAddressInfo); + auto manager = std::make_shared(system, process.Get(), p, + shared_address, next_manager_id); + *out_id = next_manager_id++; + *out_event = &manager->GetCompletionEvent(); + *out_manager = std::move(manager); + LOG_INFO(Service_Audio, + "hwacod manager {} instances={} buffers={} input={:#x} output={:#x} " + "shared={:#x}+{:#x} ring={:#x} properties={:#x}", + *out_id, p.instance_count, p.io_count, p.input_capacity, p.output_capacity, + shared_address, shared_size, p.completion_ring_offset, properties); + R_SUCCEED(); +} + +} // namespace Service::Audio diff --git a/src/core/hle/service/audio/nx2_hardware_audio_codec_client.h b/src/core/hle/service/audio/nx2_hardware_audio_codec_client.h new file mode 100644 index 0000000000..bc1a74adf6 --- /dev/null +++ b/src/core/hle/service/audio/nx2_hardware_audio_codec_client.h @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "core/hle/service/audio/nx2_hardware_audio_codec_types.h" +#include "core/hle/service/cmif_types.h" +#include "core/hle/service/service.h" + +namespace Kernel { +class KProcess; +class KReadableEvent; +class KTransferMemory; +} // namespace Kernel + +namespace Service::Audio { + +class INx2HardwareAudioCodecManager; + +class INx2HardwareAudioCodecClient final : public ServiceFramework { +public: + explicit INx2HardwareAudioCodecClient(Core::System& system); + +private: + Result GetInstanceWorkBufferSize( + Out out_size, + InLargeData parameter); + Result GetManagerWorkBufferSize(Out out_size, u32 io_count); + Result OpenManager(Out out_id, + Out> out_manager, + OutCopyHandle out_event, + Nx2HardwareAudioCodecSharedMemory parameter, + InCopyHandle transfer_memory, + InCopyHandle process, u64 transfer_size, + u64 shared_address, u64 shared_size, u64 properties); + + u32 next_manager_id{1}; +}; + +} // namespace Service::Audio diff --git a/src/core/hle/service/audio/nx2_hardware_audio_codec_manager.cpp b/src/core/hle/service/audio/nx2_hardware_audio_codec_manager.cpp new file mode 100644 index 0000000000..c0c31b3b42 --- /dev/null +++ b/src/core/hle/service/audio/nx2_hardware_audio_codec_manager.cpp @@ -0,0 +1,255 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include +#include + +#include +#include + +#include "core/core.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/service/audio/errors.h" +#include "core/hle/service/audio/nx2_hardware_audio_codec_manager.h" +#include "core/hle/service/cmif_serialization.h" +#include "core/memory.h" + +namespace Service::Audio { +namespace { + +constexpr u64 RingEntriesOffset = 0x80; +constexpr u64 RingReadIndexOffset = 0x44; +constexpr u64 RingWriteIndexOffset = 4; + +struct Completion { + u16 manager_id; + u16 instance_id; + u16 io_id; + u16 sequence; + u64 elapsed_ns; + u32 output_size; + u32 result; + std::array reserved; +}; +static_assert(sizeof(Completion) == 0x40); + +Result OpusResult(int result) { + switch (result) { + case OPUS_OK: + return ResultSuccess; + case OPUS_BAD_ARG: + return ResultLibOpusBadArg; + case OPUS_BUFFER_TOO_SMALL: + return ResultBufferTooSmall; + case OPUS_INTERNAL_ERROR: + return ResultLibOpusInternalError; + case OPUS_INVALID_PACKET: + return ResultLibOpusInvalidPacket; + case OPUS_UNIMPLEMENTED: + return ResultLibOpusUnimplemented; + case OPUS_INVALID_STATE: + return ResultLibOpusInvalidState; + case OPUS_ALLOC_FAIL: + return ResultLibOpusAllocFail; + default: + return ResultInvalidOpusDSPReturnCode; + } +} + +struct OpusDeleter { + void operator()(::OpusDecoder* decoder) const { + opus_decoder_destroy(decoder); + } + void operator()(::OpusMSDecoder* decoder) const { + opus_multistream_decoder_destroy(decoder); + } +}; + +} // namespace + +struct INx2HardwareAudioCodecManager::Instance { + u32 sample_rate{}; + u32 channel_count{}; + std::unique_ptr<::OpusDecoder, OpusDeleter> decoder; + std::unique_ptr<::OpusMSDecoder, OpusDeleter> multistream_decoder; +}; + +INx2HardwareAudioCodecManager::INx2HardwareAudioCodecManager( + Core::System& system_, Kernel::KProcess* process_, + const Nx2HardwareAudioCodecSharedMemory& params_, u64 shared_address_, u32 manager_id_) + : ServiceFramework{system_, "INx2HardwareAudioCodecManager"}, + service_context{system_, "INx2HardwareAudioCodecManager"}, + completion_event{service_context.CreateEvent("HardwareAudioCodecCompletion")}, + process{process_}, params{params_}, shared_address{shared_address_}, manager_id{manager_id_}, + instances(params.instance_count) { + static const FunctionInfo functions[] = { + {0, D<&INx2HardwareAudioCodecManager::InitializeInstance>, "InitializeInstance"}, + {1, D<&INx2HardwareAudioCodecManager::FinalizeInstance>, "FinalizeInstance"}, + {10, D<&INx2HardwareAudioCodecManager::Execute>, "Execute"}, + }; + RegisterHandlers(functions); + process->Open(system.Kernel()); + + auto& memory = process->GetMemory(); + const u64 ring_address = shared_address + params.completion_ring_offset; + memory.Write32(ring_address, 1); + memory.Write32(ring_address + RingWriteIndexOffset, write_index); +} + +INx2HardwareAudioCodecManager::~INx2HardwareAudioCodecManager() { + service_context.CloseEvent(completion_event); + process->Close(system.Kernel()); +} + +Kernel::KReadableEvent& INx2HardwareAudioCodecManager::GetCompletionEvent() { + return completion_event->GetReadableEvent(); +} + +Result INx2HardwareAudioCodecManager::InitializeInstance( + u32 instance_id, + InLargeData parameter) { + R_UNLESS(instance_id < instances.size(), ResultInvalidArgument); + const auto& p = *parameter; + R_UNLESS(p.sample_rate == 8000 || p.sample_rate == 12000 || p.sample_rate == 16000 || + p.sample_rate == 24000 || p.sample_rate == 48000, + ResultInvalidOpusSampleRate); + R_UNLESS(p.codec == 1 || p.codec == 2, ResultNotSupported); + R_UNLESS(p.channel_count >= 1 && p.channel_count <= (p.codec == 1 ? 2U : 255U), + ResultInvalidOpusChannelCount); + auto& instance = instances[instance_id]; + R_UNLESS(!instance.decoder && !instance.multistream_decoder, ResultLibOpusInvalidState); + + int error{}; + if (p.codec == 1) { + instance.decoder.reset(opus_decoder_create(p.sample_rate, p.channel_count, &error)); + } else { + R_UNLESS(p.stream_count >= 1 && p.stream_count <= 255 && + p.stereo_stream_count <= p.stream_count && + p.stream_count + p.stereo_stream_count <= 255, + ResultInvalidOpusChannelCount); + instance.multistream_decoder.reset(opus_multistream_decoder_create( + p.sample_rate, p.channel_count, p.stream_count, p.stereo_stream_count, + p.channel_mapping.data(), &error)); + } + R_TRY(OpusResult(error)); + instance.sample_rate = p.sample_rate; + instance.channel_count = p.channel_count; + LOG_DEBUG(Service_Audio, "Initialized codec instance {}: codec={} rate={} channels={}", + instance_id, p.codec, p.sample_rate, p.channel_count); + R_SUCCEED(); +} + +Result INx2HardwareAudioCodecManager::FinalizeInstance(u32 instance_id) { + R_UNLESS(instance_id < instances.size(), ResultInvalidArgument); + instances[instance_id] = {}; + R_SUCCEED(); +} + +Result INx2HardwareAudioCodecManager::Decode(const Task& task, u32& output_size) { + output_size = 0; + auto& instance = instances[task.instance_id]; + R_UNLESS(instance.decoder || instance.multistream_decoder, ResultLibOpusInvalidState); + R_UNLESS(task.input_size > 8, ResultInputDataTooSmall); + R_UNLESS(task.input_size <= params.input_capacity, ResultBufferTooSmall); + + const u64 stride = u64{params.input_capacity} + params.output_capacity; + const u64 input_address = shared_address + params.io_data_offset + stride * task.io_id; + auto& memory = process->GetMemory(); + input_buffer.resize(task.input_size); + R_UNLESS(memory.ReadBlock(input_address, input_buffer.data(), input_buffer.size()), + ResultInvalidAddressInfo); + const u32 packet_size = (u32{input_buffer[0]} << 24) | (u32{input_buffer[1]} << 16) | + (u32{input_buffer[2]} << 8) | input_buffer[3]; + R_UNLESS(packet_size > 0 && packet_size <= task.input_size - 8 && + packet_size <= std::numeric_limits::max(), + ResultLibOpusInvalidPacket); + + const u32 frame_capacity = std::min(params.output_capacity / (instance.channel_count * 2), + instance.sample_rate * 120 / 1000); + R_UNLESS(frame_capacity > 0, ResultBufferTooSmall); + output_buffer.resize(frame_capacity * instance.channel_count); + + int samples; + if (instance.decoder) { + if (task.reset) { + R_TRY(OpusResult(opus_decoder_ctl(instance.decoder.get(), OPUS_RESET_STATE))); + } + samples = opus_decode(instance.decoder.get(), input_buffer.data() + 8, packet_size, + output_buffer.data(), frame_capacity, 0); + } else { + if (task.reset) { + R_TRY(OpusResult( + opus_multistream_decoder_ctl(instance.multistream_decoder.get(), OPUS_RESET_STATE))); + } + samples = opus_multistream_decode(instance.multistream_decoder.get(), input_buffer.data() + 8, + packet_size, output_buffer.data(), frame_capacity, 0); + } + if (samples < 0) { + R_RETURN(OpusResult(samples)); + } + const u32 bytes = samples * instance.channel_count * sizeof(s16); + R_UNLESS(memory.WriteBlock(input_address + params.input_capacity, output_buffer.data(), bytes), + ResultInvalidAddressInfo); + output_size = bytes; + if (!logged_first_decode) { + s32 peak{}; + for (u32 i = 0; i < bytes / sizeof(s16); ++i) { + const s32 sample = output_buffer[i]; + peak = std::max(peak, sample < 0 ? -sample : sample); + } + LOG_INFO(Service_Audio, + "hwacod first decode: instance={} input={} output={} samples={} PCM peak={}", + task.instance_id, task.input_size, bytes, samples, peak); + logged_first_decode = true; + } + R_SUCCEED(); +} + +Result INx2HardwareAudioCodecManager::Execute(InArray tasks) { + R_SUCCEED_IF(tasks.empty()); + auto& memory = process->GetMemory(); + R_UNLESS(memory.IsValidVirtualAddressRange(shared_address, params.shared_memory_size), + ResultInvalidAddressInfo); + const u64 ring_address = shared_address + params.completion_ring_offset; + const u32 capacity = params.io_count + 1; + const u32 read_index = memory.Read32(ring_address + RingReadIndexOffset); + R_UNLESS(read_index < capacity, ResultInvalidArgument); + const u32 available = (read_index + capacity - write_index - 1) % capacity; + R_UNLESS(tasks.size() <= available, ResultBufferCountReached); + for (const auto& task : tasks) { + R_UNLESS(task.manager_id == manager_id && task.instance_id < instances.size() && + task.io_id < params.io_count && task.input_size <= params.input_capacity, + ResultInvalidArgument); + } + + for (const auto& task : tasks) { + Completion completion{}; + completion.manager_id = task.manager_id; + completion.instance_id = task.instance_id; + completion.io_id = task.io_id; + completion.sequence = task.sequence; + const auto start = std::chrono::steady_clock::now(); + const Result result = Decode(task, completion.output_size); + completion.result = result.raw; + completion.elapsed_ns = std::chrono::duration_cast( + std::chrono::steady_clock::now() - start) + .count(); + if (result.IsError()) { + LOG_WARNING(Service_Audio, "Codec instance {} task {} failed: {:08X}", task.instance_id, + task.sequence, result.raw); + } + R_UNLESS(memory.WriteBlock(ring_address + RingEntriesOffset + write_index * sizeof(Completion), + &completion, sizeof(completion)), + ResultInvalidAddressInfo); + write_index = (write_index + 1) % capacity; + memory.Write32(ring_address + RingWriteIndexOffset, write_index); + } + completion_event->Signal(system.Kernel()); + LOG_TRACE(Service_Audio, "Completed {} codec tasks", tasks.size()); + R_SUCCEED(); +} + +} // namespace Service::Audio diff --git a/src/core/hle/service/audio/nx2_hardware_audio_codec_manager.h b/src/core/hle/service/audio/nx2_hardware_audio_codec_manager.h new file mode 100644 index 0000000000..4c4b93ce70 --- /dev/null +++ b/src/core/hle/service/audio/nx2_hardware_audio_codec_manager.h @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "core/hle/service/audio/nx2_hardware_audio_codec_types.h" +#include "core/hle/service/cmif_types.h" +#include "core/hle/service/kernel_helpers.h" +#include "core/hle/service/service.h" + +namespace Service::Audio { + +class INx2HardwareAudioCodecManager final + : public ServiceFramework { +public: + explicit INx2HardwareAudioCodecManager(Core::System& system_, Kernel::KProcess* process_, + const Nx2HardwareAudioCodecSharedMemory& params_, + u64 shared_address_, u32 manager_id_); + ~INx2HardwareAudioCodecManager() override; + + Kernel::KReadableEvent& GetCompletionEvent(); + +private: + struct Task { + u16 manager_id; + u16 instance_id; + u16 io_id; + u16 sequence; + u32 input_size; + u8 reset; + u8 end_of_batch; + u16 reserved; + }; + static_assert(sizeof(Task) == 0x10); + + struct Instance; + + Result InitializeInstance( + u32 instance_id, + InLargeData parameter); + Result FinalizeInstance(u32 instance_id); + Result Execute(InArray tasks); + Result Decode(const Task& task, u32& output_size); + + KernelHelpers::ServiceContext service_context; + Kernel::KEvent* completion_event; + Kernel::KProcess* process; + Nx2HardwareAudioCodecSharedMemory params; + u64 shared_address; + u32 manager_id; + u32 write_index{}; + bool logged_first_decode{}; + std::vector instances; + std::vector input_buffer; + std::vector output_buffer; +}; + +} // namespace Service::Audio diff --git a/src/core/hle/service/audio/nx2_hardware_audio_codec_types.h b/src/core/hle/service/audio/nx2_hardware_audio_codec_types.h new file mode 100644 index 0000000000..20c32727c1 --- /dev/null +++ b/src/core/hle/service/audio/nx2_hardware_audio_codec_types.h @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include "common/common_types.h" + +namespace Service::Audio { + +struct Nx2HardwareAudioCodecParameter { + u64 codec; + u32 sample_rate; + u32 channel_count; + u64 frame_duration_ns; + u32 stream_count; + u32 stereo_stream_count; + std::array channel_mapping; +}; +static_assert(sizeof(Nx2HardwareAudioCodecParameter) == 0x120); + +struct Nx2HardwareAudioCodecSharedMemory { + u32 version; + u32 manager_size; + u32 manager_offset; + u32 instances_size; + u32 instances_offset; + u32 dsp_ring_size; + u32 dsp_ring_offset; + u32 transfer_memory_size; + u32 io_data_size; + u32 io_data_offset; + u32 completion_ring_size; + u32 completion_ring_offset; + u32 shared_memory_size; + u32 instance_size; + u32 instance_count; + u32 input_capacity; + u32 output_capacity; + u32 io_count; + std::array reserved; +}; +static_assert(sizeof(Nx2HardwareAudioCodecSharedMemory) == 0x60); + +} // namespace Service::Audio diff --git a/src/core/hle/service/nx2_gsv.h b/src/core/hle/service/nx2_gsv.h index e2060c90a7..fb6b249f2b 100644 --- a/src/core/hle/service/nx2_gsv.h +++ b/src/core/hle/service/nx2_gsv.h @@ -1499,7 +1499,9 @@ inline Nx2ImageLayout Nx2CalculateImageLayout(const u32* preference) { } const u64 block_rows = u64{1} << layout.block_height_log2; layout.size = layout.pitch * Common::AlignUp(u64{layout.height}, block_rows); - layout.alignment = std::max(64 * block_rows, 0x1000); + // Native image offsets are page-aligned, independently of the block height. Requiring + // a whole block rejects Star Fox's 4 KiB-aligned window buffers at 3840x2160. + layout.alignment = 0x1000; return layout; } diff --git a/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h b/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h index 66748f2bcd..2644b3e964 100644 --- a/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h +++ b/src/shader_recompiler/frontend/ampere/lower_to_maxwell.h @@ -181,7 +181,7 @@ inline LoweredProgram LowerToMaxwellWith(std::span code, u32 for (const auto& instruction : code) { const u32 op = instruction.Bits(0, 12); uses_uniform_bank |= op == 0x3c4 || op == 0x2bd || op == 0x896 || op == 0xf60 || op == 0xc11 || op == 0xd08 || op == 0xc0c || op == 0x986 || op == 0x9a8 || op == 0xc12 || op == 0xc17 || op == 0xe24 || op == 0xc24 || op == 0xd00 || op == 0xd09 || op == 0x9c3 || op == 0xc45 || op == 0x2a4 || op == 0x891 || op == 0x890 || op == 0x290 || op == 0x28c || op == 0x88c || op == 0x287 || op == 0x887 || op == 0x958 || op == 0xab9 || op == 0xabb || op == 0x899 || op == 0xc20 || op == 0xe21 || op == 0xe23 || op == 0xc02 || - op == 0x886 || op == 0x3c2 || op == 0xc82 || op == 0x882 || op == 0x292 || op == 0x892 || op == 0x291 || op == 0x981 || op == 0xc13 || op == 0xc10 || op == 0xc23 || op == 0x526 || op == 0x2be || op == 0x8a4 || op == 0xd05 || (op == 0x984 && instruction.Bits(91, 1)) || (op == 0x988 && instruction.Bits(91, 1)) || op == 0x89c || op == 0x883 || op == 0x2a5 || op == 0xd06 || op == 0xf63 || op == 0xf66 || op == 0xf69 || op == 0xc0b || op == 0x8a5 || op == 0x299 || op == 0xc09 || (op == 0x81c && instruction.Bits(67, 1)) || op == 0xc0a || + op == 0x886 || op == 0x3c2 || op == 0xc82 || op == 0x882 || op == 0x292 || op == 0x892 || op == 0x291 || op == 0x981 || op == 0xc13 || op == 0xc10 || op == 0xc23 || op == 0x526 || op == 0x2be || op == 0x8a4 || op == 0xd05 || (op == 0x984 && instruction.Bits(91, 1)) || (op == 0x988 && instruction.Bits(91, 1)) || op == 0x89c || op == 0x883 || op == 0x2a5 || op == 0xd06 || op == 0xf63 || op == 0xf66 || op == 0xf69 || op == 0xf6c || op == 0xc0b || op == 0x8a5 || op == 0x299 || op == 0xc09 || (op == 0x81c && instruction.Bits(67, 1)) || op == 0xc0a || (op == 0x83b && instruction.Bits(91, 1)); } const auto address_of = [](u32 index) { return 0x50U + index * 8 + ((index + 1) / 3) * 8; }; @@ -1002,15 +1002,16 @@ inline LoweredProgram LowerToMaxwellWith(std::span code, u32 emit(0x5c98000000000000ULL | predicate | target | (u64{source} << 20) | (u64{15} << 39)); break; } - case 0xab9: case 0xabb: { // ULDC U32/U64, direct or indexed. + case 0xab9: case 0xabb: { // ULDC U8/S8/U16/S16/U32/U64, direct or indexed. + // The narrow widths (Star Fox's final pass: ULDC.U16) go through LDC like U64. const u32 uniform_dest = insn.Bits(16, 8), index_reg = insn.Bits(24, 8); const u32 width = insn.Bits(73, 3); const u32 target = uniform_register(uniform_dest); const u32 index_target = opcode == 0xabb ? uniform_register(index_reg) : 255U; - if (target == ~0U || target == 255 || (width != 4 && width != 5) || + if (target == ~0U || target == 255 || width > 5 || (width == 5 && ((uniform_dest & 1) || uniform_register(uniform_dest + 1) != target + 1)) || - bank() >= 18 || insn.Bits(38, 16) % 4 || insn.Bits(62, 2) || - (insn.words[2] & ~0xe00U) != (opcode == 0xab9 ? 0U : 0x08000000U) || + bank() >= 18 || insn.Bits(38, 16) % (width < 2 ? 1 : width < 4 ? 2 : 4) || + insn.Bits(62, 2) || (insn.words[2] & ~0xe00U) != (opcode == 0xab9 ? 0U : 0x08000000U) || index_target == ~0U) return fail(index, "native uniform constant load mode"); if (opcode == 0xab9 && width == 4) { @@ -1083,7 +1084,11 @@ inline LoweredProgram LowerToMaxwellWith(std::span code, u32 (u64{insn.Bits(64, 8)} << 20) | (u64{maxwell} << 28)); break; } - case 0xb6c: { // TXD 2D or ARRAY_2D, any channels; the two destination pairs anywhere. + case 0xb6c: case 0xf6c: { // TXD 2D or ARRAY_2D, any channels; the two destination pairs anywhere. + // 0xf6c: the handle in a uniform register (bit 91), Maxwell TXD.B with the handle + // before the coordinates (Star Fox's scene passes). + const bool bindless = opcode == 0xf6c; + const u32 handle = bindless ? uniform_register(insn.Bits(40, 6)) : 255; const u32 binding = insn.Bits(54, 5), offset = insn.Bits(40, 14); const u32 coordinates = insn.Bits(24, 8), derivatives = insn.Bits(32, 8); // Ampere dimensions: 1 = 2D, 5 = ARRAY_2D; Maxwell TXD's are 2 and 3. The coordinates @@ -1094,23 +1099,37 @@ inline LoweredProgram LowerToMaxwellWith(std::span code, u32 // the channel mask: the first two enabled channels go to Rd, Rd+1, the others to the // second destination (RZ drops them; a third channel alone may be in an odd register). const u32 mask = insn.Bits(72, 4), channels = static_cast(std::popcount(mask)); - if ((insn.words[2] & ~(0xfffU | (1U << 26))) != 0x001e0000 || mask == 0 || insn.Bits(59, 2) || - (dimension != 1 && dimension != 5) || binding >= 18 || offset > 0x1fff || + const u32 coordinate_count = dimension == 5 ? 3 : 2; + if ((insn.words[2] & ~(0xfffU | (1U << 26))) != (bindless ? 0x081e0000U : 0x001e0000U) || + mask == 0 || insn.Bits(59, 2) || (dimension != 1 && dimension != 5) || + (bindless ? (handle == ~0U || handle >= 246 || insn.Bits(46, 13) || + coordinates + coordinate_count > 246) + : (binding >= 18 || offset > 0x1fff || coordinates >= 248)) || (channels > 1 && (dest & 1)) || dest >= 248 || (channels <= 2 && high != 255) || (high != 255 && ((channels > 3 && (high & 1)) || high >= 248)) || - coordinates >= 248 || derivatives >= 247) + derivatives >= 247) return fail(index, "native gradient texture mode"); - if (result.required_texture_buffer != ~0U && result.required_texture_buffer != binding) - return fail(index, "native texture descriptor buffer conflict"); - result.required_texture_buffer = binding; + if (!bindless) { + if (result.required_texture_buffer != ~0U && result.required_texture_buffer != binding) + return fail(index, "native texture descriptor buffer conflict"); + result.required_texture_buffer = binding; + } // Maxwell writes the channels to consecutive registers: when the second destination // doesn't follow Rd (Splatoon Raiders: R12/R18, R18/R44), write them to scratch and move // each into place. const bool consecutive = channels <= 2 || high == dest + 2; const u64 target = consecutive ? dest : 248; - emit(0xde00000000000000ULL | predicate | target | (u64{coordinates} << 8) | - (u64{derivatives} << 20) | (u64{dimension == 5 ? 3U : 2U} << 28) | (u64{mask} << 31) | - (u64{offset} << 36) | (u64{insn.Bits(90, 1)} << 49) | (u64{7} << 51)); + if (bindless) { + const auto copy = [&](u32 to, u32 from) { + emit(0x5c98000000000000ULL | predicate | to | (u64{15} << 39) | (u64{from} << 20)); + }; + copy(250, handle); + for (u32 i = 0; i < coordinate_count; ++i) copy(251 + i, coordinates + i); + } + emit((bindless ? 0xde40000000000000ULL : 0xde00000000000000ULL) | predicate | target | + (u64{bindless ? 250U : coordinates} << 8) | (u64{derivatives} << 20) | + (u64{dimension == 5 ? 3U : 2U} << 28) | (u64{mask} << 31) | + (u64{bindless ? 0U : offset} << 36) | (u64{insn.Bits(90, 1)} << 49) | (u64{7} << 51)); if (!consecutive) { for (u32 i = 0; i < channels; ++i) { if (i >= 2 && high == 255) break; @@ -2634,7 +2653,8 @@ inline LoweredProgram LowerToMaxwellWith(std::span code, u32 // F32), 84-85 the source size (0: 8, 1: 16 bits). const u32 source = opcode == 0xd06 ? uniform_register(insn.Bits(32, 8)) : insn.Bits(32, 8); const u64 is_signed = insn.Bits(74, 1); - if (opcode == 0x306 && (insn.words[2] & ~0x400U) == 0x00101000 && !(insn.words[1] & 0xcfffff00U)) { + if ((insn.words[2] & ~0x400U) == (opcode == 0xd06 ? 0x08101000U : 0x00101000U) && + !(insn.words[1] & 0xcfffff00U) && source != ~0U) { // I2F.{U,S}16 Rd, Rs(.H1): Maxwell's 16-bit source format, byte selector 0 or 2. emit(base(0x5cb8000000000000ULL) | (u64{2} << 8) | (u64{1} << 10) | (is_signed << 13) | (u64{source} << 20) | (u64{insn.Bits(60, 2) * 2} << 41)); diff --git a/src/video_core/engines/kepler_compute.cpp b/src/video_core/engines/kepler_compute.cpp index 23ffbad790..787c5c0e27 100644 --- a/src/video_core/engines/kepler_compute.cpp +++ b/src/video_core/engines/kepler_compute.cpp @@ -903,11 +903,23 @@ bool KeplerCompute::UpscaleInsteadOfNativeProgram() { if (tic_index > regs.tic.limit) return std::nullopt; return GetTICEntry(tic_index); }; - const auto upscale_pair = [](const std::optional& from, - const std::optional& to) { - return from && to && from->format == to->format && from->Address() != 0 && - to->Address() != 0 && to->Width() > from->Width() && from->IsBlockLinear() && - to->IsBlockLinear(); + using RTFormat = Tegra::RenderTargetFormat; + const auto rt_format = [](Texture::TextureFormat format) -> std::optional { + switch (format) { + case Texture::TextureFormat::A2B10G10R10: return RTFormat::A2B10G10R10_UNORM; + case Texture::TextureFormat::A8B8G8R8: return RTFormat::A8B8G8R8_UNORM; + case Texture::TextureFormat::B10G11R11: return RTFormat::B10G11R11_FLOAT; + case Texture::TextureFormat::R16G16B16A16: return RTFormat::R16G16B16A16_FLOAT; + default: return std::nullopt; + } + }; + const auto upscale_pair = [&](const std::optional& from, + const std::optional& to, bool convert = false) { + return from && to && + (from->format == to->format || + (convert && rt_format(from->format) && rt_format(to->format))) && + from->Address() != 0 && to->Address() != 0 && to->Width() > from->Width() && + from->IsBlockLinear() && to->IsBlockLinear(); }; std::optional input = image(0x90), output = image(0xa0); bool fortunes_weave = false; @@ -933,20 +945,18 @@ bool KeplerCompute::UpscaleInsteadOfNativeProgram() { } } } - if (!input || !output) - return false; - using RTFormat = Tegra::RenderTargetFormat; - RTFormat format{}; - switch (input->format) { - case Texture::TextureFormat::A2B10G10R10: format = RTFormat::A2B10G10R10_UNORM; break; - case Texture::TextureFormat::A8B8G8R8: format = RTFormat::A8B8G8R8_UNORM; break; - case Texture::TextureFormat::B10G11R11: format = RTFormat::B10G11R11_FLOAT; break; - case Texture::TextureFormat::R16G16B16A16: format = RTFormat::R16G16B16A16_FLOAT; break; - default: return false; + if (!input || !output) { + // Star Fox's upscaler reads an R16G16B16A16 frame at title 4's slots and writes an + // A8B8G8R8 one: with no same-format pair, convert between them in the blit. + input = image(0x90); + output = image(0xa0); + if (!upscale_pair(input, output, true)) return false; } + if (!rt_format(input->format) || !rt_format(output->format)) + return false; const auto surface = [&](const Texture::TICEntry& tic) { Fermi2D::Surface result{}; - result.format = format; + result.format = rt_format(tic.format).value_or(RTFormat{}); result.linear = Fermi2D::MemoryLayout::BlockLinear; result.block_width.Assign(tic.block_width); result.block_height.Assign(tic.block_height);