Merge branch 'master' of https://github.com/boludoz/yuzu
This commit is contained in:
commit
80aafb0f7d
172 changed files with 9055 additions and 3201 deletions
|
@ -49,6 +49,8 @@ option(YUZU_TESTS "Compile tests" "${BUILD_TESTING}")
|
|||
|
||||
option(YUZU_USE_PRECOMPILED_HEADERS "Use precompiled headers" ON)
|
||||
|
||||
option(YUZU_DOWNLOAD_ANDROID_VVL "Download validation layer binary for android" ON)
|
||||
|
||||
CMAKE_DEPENDENT_OPTION(YUZU_ROOM "Compile LDN room server" ON "NOT ANDROID" OFF)
|
||||
|
||||
CMAKE_DEPENDENT_OPTION(YUZU_CRASH_DUMPS "Compile Windows crash dump (Minidump) support" OFF "WIN32" OFF)
|
||||
|
@ -61,6 +63,8 @@ option(YUZU_ENABLE_LTO "Enable link-time optimization" OFF)
|
|||
|
||||
option(YUZU_DOWNLOAD_TIME_ZONE_DATA "Always download time zone binaries" OFF)
|
||||
|
||||
option(YUZU_ENABLE_PORTABLE "Allow yuzu to enable portable mode if a user folder is found in the CWD" ON)
|
||||
|
||||
CMAKE_DEPENDENT_OPTION(YUZU_USE_FASTER_LD "Check if a faster linker is available" ON "NOT WIN32" OFF)
|
||||
|
||||
CMAKE_DEPENDENT_OPTION(USE_SYSTEM_MOLTENVK "Use the system MoltenVK lib (instead of the bundled one)" OFF "APPLE" OFF)
|
||||
|
@ -77,6 +81,24 @@ if (ANDROID OR WIN32 OR APPLE)
|
|||
endif()
|
||||
option(ENABLE_OPENSSL "Enable OpenSSL backend for ISslConnection" ${DEFAULT_ENABLE_OPENSSL})
|
||||
|
||||
if (ANDROID AND YUZU_DOWNLOAD_ANDROID_VVL)
|
||||
set(vvl_version "sdk-1.3.261.1")
|
||||
set(vvl_zip_file "${CMAKE_BINARY_DIR}/externals/vvl-android.zip")
|
||||
if (NOT EXISTS "${vvl_zip_file}")
|
||||
# Download and extract validation layer release to externals directory
|
||||
set(vvl_base_url "https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/download")
|
||||
file(DOWNLOAD "${vvl_base_url}/${vvl_version}/android-binaries-${vvl_version}-android.zip"
|
||||
"${vvl_zip_file}" SHOW_PROGRESS)
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${vvl_zip_file}"
|
||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals")
|
||||
endif()
|
||||
|
||||
# Copy the arm64 binary to src/android/app/main/jniLibs
|
||||
set(vvl_lib_path "${CMAKE_CURRENT_SOURCE_DIR}/src/android/app/src/main/jniLibs/arm64-v8a/")
|
||||
file(COPY "${CMAKE_BINARY_DIR}/externals/android-binaries-${vvl_version}/arm64-v8a/libVkLayer_khronos_validation.so"
|
||||
DESTINATION "${vvl_lib_path}")
|
||||
endif()
|
||||
|
||||
# On Android, fetch and compile libcxx before doing anything else
|
||||
if (ANDROID)
|
||||
set(CMAKE_SKIP_INSTALL_RULES ON)
|
||||
|
|
3
externals/CMakeLists.txt
vendored
3
externals/CMakeLists.txt
vendored
|
@ -177,6 +177,9 @@ target_include_directories(stb PUBLIC ./stb)
|
|||
add_library(bc_decoder bc_decoder/bc_decoder.cpp)
|
||||
target_include_directories(bc_decoder PUBLIC ./bc_decoder)
|
||||
|
||||
add_library(renderdoc INTERFACE)
|
||||
target_include_directories(renderdoc SYSTEM INTERFACE ./renderdoc)
|
||||
|
||||
if (ANDROID)
|
||||
if (ARCHITECTURE_arm64)
|
||||
add_subdirectory(libadrenotools)
|
||||
|
|
744
externals/renderdoc/renderdoc_app.h
vendored
Normal file
744
externals/renderdoc/renderdoc_app.h
vendored
Normal file
|
@ -0,0 +1,744 @@
|
|||
// SPDX-FileCopyrightText: Baldur Karlsson
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/******************************************************************************
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019-2023 Baldur Karlsson
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Documentation for the API is available at https://renderdoc.org/docs/in_application_api.html
|
||||
//
|
||||
|
||||
#if !defined(RENDERDOC_NO_STDINT)
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
|
||||
#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER)
|
||||
#define RENDERDOC_CC __cdecl
|
||||
#elif defined(__linux__)
|
||||
#define RENDERDOC_CC
|
||||
#elif defined(__APPLE__)
|
||||
#define RENDERDOC_CC
|
||||
#else
|
||||
#error "Unknown platform"
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constants not used directly in below API
|
||||
|
||||
// This is a GUID/magic value used for when applications pass a path where shader debug
|
||||
// information can be found to match up with a stripped shader.
|
||||
// the define can be used like so: const GUID RENDERDOC_ShaderDebugMagicValue =
|
||||
// RENDERDOC_ShaderDebugMagicValue_value
|
||||
#define RENDERDOC_ShaderDebugMagicValue_struct \
|
||||
{ \
|
||||
0xeab25520, 0x6670, 0x4865, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \
|
||||
}
|
||||
|
||||
// as an alternative when you want a byte array (assuming x86 endianness):
|
||||
#define RENDERDOC_ShaderDebugMagicValue_bytearray \
|
||||
{ \
|
||||
0x20, 0x55, 0xb2, 0xea, 0x70, 0x66, 0x65, 0x48, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \
|
||||
}
|
||||
|
||||
// truncated version when only a uint64_t is available (e.g. Vulkan tags):
|
||||
#define RENDERDOC_ShaderDebugMagicValue_truncated 0x48656670eab25520ULL
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// RenderDoc capture options
|
||||
//
|
||||
|
||||
typedef enum RENDERDOC_CaptureOption
|
||||
{
|
||||
// Allow the application to enable vsync
|
||||
//
|
||||
// Default - enabled
|
||||
//
|
||||
// 1 - The application can enable or disable vsync at will
|
||||
// 0 - vsync is force disabled
|
||||
eRENDERDOC_Option_AllowVSync = 0,
|
||||
|
||||
// Allow the application to enable fullscreen
|
||||
//
|
||||
// Default - enabled
|
||||
//
|
||||
// 1 - The application can enable or disable fullscreen at will
|
||||
// 0 - fullscreen is force disabled
|
||||
eRENDERDOC_Option_AllowFullscreen = 1,
|
||||
|
||||
// Record API debugging events and messages
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - Enable built-in API debugging features and records the results into
|
||||
// the capture, which is matched up with events on replay
|
||||
// 0 - no API debugging is forcibly enabled
|
||||
eRENDERDOC_Option_APIValidation = 2,
|
||||
eRENDERDOC_Option_DebugDeviceMode = 2, // deprecated name of this enum
|
||||
|
||||
// Capture CPU callstacks for API events
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - Enables capturing of callstacks
|
||||
// 0 - no callstacks are captured
|
||||
eRENDERDOC_Option_CaptureCallstacks = 3,
|
||||
|
||||
// When capturing CPU callstacks, only capture them from actions.
|
||||
// This option does nothing without the above option being enabled
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - Only captures callstacks for actions.
|
||||
// Ignored if CaptureCallstacks is disabled
|
||||
// 0 - Callstacks, if enabled, are captured for every event.
|
||||
eRENDERDOC_Option_CaptureCallstacksOnlyDraws = 4,
|
||||
eRENDERDOC_Option_CaptureCallstacksOnlyActions = 4,
|
||||
|
||||
// Specify a delay in seconds to wait for a debugger to attach, after
|
||||
// creating or injecting into a process, before continuing to allow it to run.
|
||||
//
|
||||
// 0 indicates no delay, and the process will run immediately after injection
|
||||
//
|
||||
// Default - 0 seconds
|
||||
//
|
||||
eRENDERDOC_Option_DelayForDebugger = 5,
|
||||
|
||||
// Verify buffer access. This includes checking the memory returned by a Map() call to
|
||||
// detect any out-of-bounds modification, as well as initialising buffers with undefined contents
|
||||
// to a marker value to catch use of uninitialised memory.
|
||||
//
|
||||
// NOTE: This option is only valid for OpenGL and D3D11. Explicit APIs such as D3D12 and Vulkan do
|
||||
// not do the same kind of interception & checking and undefined contents are really undefined.
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - Verify buffer access
|
||||
// 0 - No verification is performed, and overwriting bounds may cause crashes or corruption in
|
||||
// RenderDoc.
|
||||
eRENDERDOC_Option_VerifyBufferAccess = 6,
|
||||
|
||||
// The old name for eRENDERDOC_Option_VerifyBufferAccess was eRENDERDOC_Option_VerifyMapWrites.
|
||||
// This option now controls the filling of uninitialised buffers with 0xdddddddd which was
|
||||
// previously always enabled
|
||||
eRENDERDOC_Option_VerifyMapWrites = eRENDERDOC_Option_VerifyBufferAccess,
|
||||
|
||||
// Hooks any system API calls that create child processes, and injects
|
||||
// RenderDoc into them recursively with the same options.
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - Hooks into spawned child processes
|
||||
// 0 - Child processes are not hooked by RenderDoc
|
||||
eRENDERDOC_Option_HookIntoChildren = 7,
|
||||
|
||||
// By default RenderDoc only includes resources in the final capture necessary
|
||||
// for that frame, this allows you to override that behaviour.
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - all live resources at the time of capture are included in the capture
|
||||
// and available for inspection
|
||||
// 0 - only the resources referenced by the captured frame are included
|
||||
eRENDERDOC_Option_RefAllResources = 8,
|
||||
|
||||
// **NOTE**: As of RenderDoc v1.1 this option has been deprecated. Setting or
|
||||
// getting it will be ignored, to allow compatibility with older versions.
|
||||
// In v1.1 the option acts as if it's always enabled.
|
||||
//
|
||||
// By default RenderDoc skips saving initial states for resources where the
|
||||
// previous contents don't appear to be used, assuming that writes before
|
||||
// reads indicate previous contents aren't used.
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - initial contents at the start of each captured frame are saved, even if
|
||||
// they are later overwritten or cleared before being used.
|
||||
// 0 - unless a read is detected, initial contents will not be saved and will
|
||||
// appear as black or empty data.
|
||||
eRENDERDOC_Option_SaveAllInitials = 9,
|
||||
|
||||
// In APIs that allow for the recording of command lists to be replayed later,
|
||||
// RenderDoc may choose to not capture command lists before a frame capture is
|
||||
// triggered, to reduce overheads. This means any command lists recorded once
|
||||
// and replayed many times will not be available and may cause a failure to
|
||||
// capture.
|
||||
//
|
||||
// NOTE: This is only true for APIs where multithreading is difficult or
|
||||
// discouraged. Newer APIs like Vulkan and D3D12 will ignore this option
|
||||
// and always capture all command lists since the API is heavily oriented
|
||||
// around it and the overheads have been reduced by API design.
|
||||
//
|
||||
// 1 - All command lists are captured from the start of the application
|
||||
// 0 - Command lists are only captured if their recording begins during
|
||||
// the period when a frame capture is in progress.
|
||||
eRENDERDOC_Option_CaptureAllCmdLists = 10,
|
||||
|
||||
// Mute API debugging output when the API validation mode option is enabled
|
||||
//
|
||||
// Default - enabled
|
||||
//
|
||||
// 1 - Mute any API debug messages from being displayed or passed through
|
||||
// 0 - API debugging is displayed as normal
|
||||
eRENDERDOC_Option_DebugOutputMute = 11,
|
||||
|
||||
// Option to allow vendor extensions to be used even when they may be
|
||||
// incompatible with RenderDoc and cause corrupted replays or crashes.
|
||||
//
|
||||
// Default - inactive
|
||||
//
|
||||
// No values are documented, this option should only be used when absolutely
|
||||
// necessary as directed by a RenderDoc developer.
|
||||
eRENDERDOC_Option_AllowUnsupportedVendorExtensions = 12,
|
||||
|
||||
// Define a soft memory limit which some APIs may aim to keep overhead under where
|
||||
// possible. Anything above this limit will where possible be saved directly to disk during
|
||||
// capture.
|
||||
// This will cause increased disk space use (which may cause a capture to fail if disk space is
|
||||
// exhausted) as well as slower capture times.
|
||||
//
|
||||
// Not all memory allocations may be deferred like this so it is not a guarantee of a memory
|
||||
// limit.
|
||||
//
|
||||
// Units are in MBs, suggested values would range from 200MB to 1000MB.
|
||||
//
|
||||
// Default - 0 Megabytes
|
||||
eRENDERDOC_Option_SoftMemoryLimit = 13,
|
||||
} RENDERDOC_CaptureOption;
|
||||
|
||||
// Sets an option that controls how RenderDoc behaves on capture.
|
||||
//
|
||||
// Returns 1 if the option and value are valid
|
||||
// Returns 0 if either is invalid and the option is unchanged
|
||||
typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionU32)(RENDERDOC_CaptureOption opt, uint32_t val);
|
||||
typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionF32)(RENDERDOC_CaptureOption opt, float val);
|
||||
|
||||
// Gets the current value of an option as a uint32_t
|
||||
//
|
||||
// If the option is invalid, 0xffffffff is returned
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionU32)(RENDERDOC_CaptureOption opt);
|
||||
|
||||
// Gets the current value of an option as a float
|
||||
//
|
||||
// If the option is invalid, -FLT_MAX is returned
|
||||
typedef float(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionF32)(RENDERDOC_CaptureOption opt);
|
||||
|
||||
typedef enum RENDERDOC_InputButton
|
||||
{
|
||||
// '0' - '9' matches ASCII values
|
||||
eRENDERDOC_Key_0 = 0x30,
|
||||
eRENDERDOC_Key_1 = 0x31,
|
||||
eRENDERDOC_Key_2 = 0x32,
|
||||
eRENDERDOC_Key_3 = 0x33,
|
||||
eRENDERDOC_Key_4 = 0x34,
|
||||
eRENDERDOC_Key_5 = 0x35,
|
||||
eRENDERDOC_Key_6 = 0x36,
|
||||
eRENDERDOC_Key_7 = 0x37,
|
||||
eRENDERDOC_Key_8 = 0x38,
|
||||
eRENDERDOC_Key_9 = 0x39,
|
||||
|
||||
// 'A' - 'Z' matches ASCII values
|
||||
eRENDERDOC_Key_A = 0x41,
|
||||
eRENDERDOC_Key_B = 0x42,
|
||||
eRENDERDOC_Key_C = 0x43,
|
||||
eRENDERDOC_Key_D = 0x44,
|
||||
eRENDERDOC_Key_E = 0x45,
|
||||
eRENDERDOC_Key_F = 0x46,
|
||||
eRENDERDOC_Key_G = 0x47,
|
||||
eRENDERDOC_Key_H = 0x48,
|
||||
eRENDERDOC_Key_I = 0x49,
|
||||
eRENDERDOC_Key_J = 0x4A,
|
||||
eRENDERDOC_Key_K = 0x4B,
|
||||
eRENDERDOC_Key_L = 0x4C,
|
||||
eRENDERDOC_Key_M = 0x4D,
|
||||
eRENDERDOC_Key_N = 0x4E,
|
||||
eRENDERDOC_Key_O = 0x4F,
|
||||
eRENDERDOC_Key_P = 0x50,
|
||||
eRENDERDOC_Key_Q = 0x51,
|
||||
eRENDERDOC_Key_R = 0x52,
|
||||
eRENDERDOC_Key_S = 0x53,
|
||||
eRENDERDOC_Key_T = 0x54,
|
||||
eRENDERDOC_Key_U = 0x55,
|
||||
eRENDERDOC_Key_V = 0x56,
|
||||
eRENDERDOC_Key_W = 0x57,
|
||||
eRENDERDOC_Key_X = 0x58,
|
||||
eRENDERDOC_Key_Y = 0x59,
|
||||
eRENDERDOC_Key_Z = 0x5A,
|
||||
|
||||
// leave the rest of the ASCII range free
|
||||
// in case we want to use it later
|
||||
eRENDERDOC_Key_NonPrintable = 0x100,
|
||||
|
||||
eRENDERDOC_Key_Divide,
|
||||
eRENDERDOC_Key_Multiply,
|
||||
eRENDERDOC_Key_Subtract,
|
||||
eRENDERDOC_Key_Plus,
|
||||
|
||||
eRENDERDOC_Key_F1,
|
||||
eRENDERDOC_Key_F2,
|
||||
eRENDERDOC_Key_F3,
|
||||
eRENDERDOC_Key_F4,
|
||||
eRENDERDOC_Key_F5,
|
||||
eRENDERDOC_Key_F6,
|
||||
eRENDERDOC_Key_F7,
|
||||
eRENDERDOC_Key_F8,
|
||||
eRENDERDOC_Key_F9,
|
||||
eRENDERDOC_Key_F10,
|
||||
eRENDERDOC_Key_F11,
|
||||
eRENDERDOC_Key_F12,
|
||||
|
||||
eRENDERDOC_Key_Home,
|
||||
eRENDERDOC_Key_End,
|
||||
eRENDERDOC_Key_Insert,
|
||||
eRENDERDOC_Key_Delete,
|
||||
eRENDERDOC_Key_PageUp,
|
||||
eRENDERDOC_Key_PageDn,
|
||||
|
||||
eRENDERDOC_Key_Backspace,
|
||||
eRENDERDOC_Key_Tab,
|
||||
eRENDERDOC_Key_PrtScrn,
|
||||
eRENDERDOC_Key_Pause,
|
||||
|
||||
eRENDERDOC_Key_Max,
|
||||
} RENDERDOC_InputButton;
|
||||
|
||||
// Sets which key or keys can be used to toggle focus between multiple windows
|
||||
//
|
||||
// If keys is NULL or num is 0, toggle keys will be disabled
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_SetFocusToggleKeys)(RENDERDOC_InputButton *keys, int num);
|
||||
|
||||
// Sets which key or keys can be used to capture the next frame
|
||||
//
|
||||
// If keys is NULL or num is 0, captures keys will be disabled
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureKeys)(RENDERDOC_InputButton *keys, int num);
|
||||
|
||||
typedef enum RENDERDOC_OverlayBits
|
||||
{
|
||||
// This single bit controls whether the overlay is enabled or disabled globally
|
||||
eRENDERDOC_Overlay_Enabled = 0x1,
|
||||
|
||||
// Show the average framerate over several seconds as well as min/max
|
||||
eRENDERDOC_Overlay_FrameRate = 0x2,
|
||||
|
||||
// Show the current frame number
|
||||
eRENDERDOC_Overlay_FrameNumber = 0x4,
|
||||
|
||||
// Show a list of recent captures, and how many captures have been made
|
||||
eRENDERDOC_Overlay_CaptureList = 0x8,
|
||||
|
||||
// Default values for the overlay mask
|
||||
eRENDERDOC_Overlay_Default = (eRENDERDOC_Overlay_Enabled | eRENDERDOC_Overlay_FrameRate |
|
||||
eRENDERDOC_Overlay_FrameNumber | eRENDERDOC_Overlay_CaptureList),
|
||||
|
||||
// Enable all bits
|
||||
eRENDERDOC_Overlay_All = ~0U,
|
||||
|
||||
// Disable all bits
|
||||
eRENDERDOC_Overlay_None = 0,
|
||||
} RENDERDOC_OverlayBits;
|
||||
|
||||
// returns the overlay bits that have been set
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetOverlayBits)();
|
||||
// sets the overlay bits with an and & or mask
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_MaskOverlayBits)(uint32_t And, uint32_t Or);
|
||||
|
||||
// this function will attempt to remove RenderDoc's hooks in the application.
|
||||
//
|
||||
// Note: that this can only work correctly if done immediately after
|
||||
// the module is loaded, before any API work happens. RenderDoc will remove its
|
||||
// injected hooks and shut down. Behaviour is undefined if this is called
|
||||
// after any API functions have been called, and there is still no guarantee of
|
||||
// success.
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_RemoveHooks)();
|
||||
|
||||
// DEPRECATED: compatibility for code compiled against pre-1.4.1 headers.
|
||||
typedef pRENDERDOC_RemoveHooks pRENDERDOC_Shutdown;
|
||||
|
||||
// This function will unload RenderDoc's crash handler.
|
||||
//
|
||||
// If you use your own crash handler and don't want RenderDoc's handler to
|
||||
// intercede, you can call this function to unload it and any unhandled
|
||||
// exceptions will pass to the next handler.
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_UnloadCrashHandler)();
|
||||
|
||||
// Sets the capture file path template
|
||||
//
|
||||
// pathtemplate is a UTF-8 string that gives a template for how captures will be named
|
||||
// and where they will be saved.
|
||||
//
|
||||
// Any extension is stripped off the path, and captures are saved in the directory
|
||||
// specified, and named with the filename and the frame number appended. If the
|
||||
// directory does not exist it will be created, including any parent directories.
|
||||
//
|
||||
// If pathtemplate is NULL, the template will remain unchanged
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// SetCaptureFilePathTemplate("my_captures/example");
|
||||
//
|
||||
// Capture #1 -> my_captures/example_frame123.rdc
|
||||
// Capture #2 -> my_captures/example_frame456.rdc
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFilePathTemplate)(const char *pathtemplate);
|
||||
|
||||
// returns the current capture path template, see SetCaptureFileTemplate above, as a UTF-8 string
|
||||
typedef const char *(RENDERDOC_CC *pRENDERDOC_GetCaptureFilePathTemplate)();
|
||||
|
||||
// DEPRECATED: compatibility for code compiled against pre-1.1.2 headers.
|
||||
typedef pRENDERDOC_SetCaptureFilePathTemplate pRENDERDOC_SetLogFilePathTemplate;
|
||||
typedef pRENDERDOC_GetCaptureFilePathTemplate pRENDERDOC_GetLogFilePathTemplate;
|
||||
|
||||
// returns the number of captures that have been made
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetNumCaptures)();
|
||||
|
||||
// This function returns the details of a capture, by index. New captures are added
|
||||
// to the end of the list.
|
||||
//
|
||||
// filename will be filled with the absolute path to the capture file, as a UTF-8 string
|
||||
// pathlength will be written with the length in bytes of the filename string
|
||||
// timestamp will be written with the time of the capture, in seconds since the Unix epoch
|
||||
//
|
||||
// Any of the parameters can be NULL and they'll be skipped.
|
||||
//
|
||||
// The function will return 1 if the capture index is valid, or 0 if the index is invalid
|
||||
// If the index is invalid, the values will be unchanged
|
||||
//
|
||||
// Note: when captures are deleted in the UI they will remain in this list, so the
|
||||
// capture path may not exist anymore.
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCapture)(uint32_t idx, char *filename,
|
||||
uint32_t *pathlength, uint64_t *timestamp);
|
||||
|
||||
// Sets the comments associated with a capture file. These comments are displayed in the
|
||||
// UI program when opening.
|
||||
//
|
||||
// filePath should be a path to the capture file to add comments to. If set to NULL or ""
|
||||
// the most recent capture file created made will be used instead.
|
||||
// comments should be a NULL-terminated UTF-8 string to add as comments.
|
||||
//
|
||||
// Any existing comments will be overwritten.
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFileComments)(const char *filePath,
|
||||
const char *comments);
|
||||
|
||||
// returns 1 if the RenderDoc UI is connected to this application, 0 otherwise
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsTargetControlConnected)();
|
||||
|
||||
// DEPRECATED: compatibility for code compiled against pre-1.1.1 headers.
|
||||
// This was renamed to IsTargetControlConnected in API 1.1.1, the old typedef is kept here for
|
||||
// backwards compatibility with old code, it is castable either way since it's ABI compatible
|
||||
// as the same function pointer type.
|
||||
typedef pRENDERDOC_IsTargetControlConnected pRENDERDOC_IsRemoteAccessConnected;
|
||||
|
||||
// This function will launch the Replay UI associated with the RenderDoc library injected
|
||||
// into the running application.
|
||||
//
|
||||
// if connectTargetControl is 1, the Replay UI will be launched with a command line parameter
|
||||
// to connect to this application
|
||||
// cmdline is the rest of the command line, as a UTF-8 string. E.g. a captures to open
|
||||
// if cmdline is NULL, the command line will be empty.
|
||||
//
|
||||
// returns the PID of the replay UI if successful, 0 if not successful.
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_LaunchReplayUI)(uint32_t connectTargetControl,
|
||||
const char *cmdline);
|
||||
|
||||
// RenderDoc can return a higher version than requested if it's backwards compatible,
|
||||
// this function returns the actual version returned. If a parameter is NULL, it will be
|
||||
// ignored and the others will be filled out.
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_GetAPIVersion)(int *major, int *minor, int *patch);
|
||||
|
||||
// Requests that the replay UI show itself (if hidden or not the current top window). This can be
|
||||
// used in conjunction with IsTargetControlConnected and LaunchReplayUI to intelligently handle
|
||||
// showing the UI after making a capture.
|
||||
//
|
||||
// This will return 1 if the request was successfully passed on, though it's not guaranteed that
|
||||
// the UI will be on top in all cases depending on OS rules. It will return 0 if there is no current
|
||||
// target control connection to make such a request, or if there was another error
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_ShowReplayUI)();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Capturing functions
|
||||
//
|
||||
|
||||
// A device pointer is a pointer to the API's root handle.
|
||||
//
|
||||
// This would be an ID3D11Device, HGLRC/GLXContext, ID3D12Device, etc
|
||||
typedef void *RENDERDOC_DevicePointer;
|
||||
|
||||
// A window handle is the OS's native window handle
|
||||
//
|
||||
// This would be an HWND, GLXDrawable, etc
|
||||
typedef void *RENDERDOC_WindowHandle;
|
||||
|
||||
// A helper macro for Vulkan, where the device handle cannot be used directly.
|
||||
//
|
||||
// Passing the VkInstance to this macro will return the RENDERDOC_DevicePointer to use.
|
||||
//
|
||||
// Specifically, the value needed is the dispatch table pointer, which sits as the first
|
||||
// pointer-sized object in the memory pointed to by the VkInstance. Thus we cast to a void** and
|
||||
// indirect once.
|
||||
#define RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(inst) (*((void **)(inst)))
|
||||
|
||||
// This sets the RenderDoc in-app overlay in the API/window pair as 'active' and it will
|
||||
// respond to keypresses. Neither parameter can be NULL
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_SetActiveWindow)(RENDERDOC_DevicePointer device,
|
||||
RENDERDOC_WindowHandle wndHandle);
|
||||
|
||||
// capture the next frame on whichever window and API is currently considered active
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_TriggerCapture)();
|
||||
|
||||
// capture the next N frames on whichever window and API is currently considered active
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_TriggerMultiFrameCapture)(uint32_t numFrames);
|
||||
|
||||
// When choosing either a device pointer or a window handle to capture, you can pass NULL.
|
||||
// Passing NULL specifies a 'wildcard' match against anything. This allows you to specify
|
||||
// any API rendering to a specific window, or a specific API instance rendering to any window,
|
||||
// or in the simplest case of one window and one API, you can just pass NULL for both.
|
||||
//
|
||||
// In either case, if there are two or more possible matching (device,window) pairs it
|
||||
// is undefined which one will be captured.
|
||||
//
|
||||
// Note: for headless rendering you can pass NULL for the window handle and either specify
|
||||
// a device pointer or leave it NULL as above.
|
||||
|
||||
// Immediately starts capturing API calls on the specified device pointer and window handle.
|
||||
//
|
||||
// If there is no matching thing to capture (e.g. no supported API has been initialised),
|
||||
// this will do nothing.
|
||||
//
|
||||
// The results are undefined (including crashes) if two captures are started overlapping,
|
||||
// even on separate devices and/oror windows.
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_StartFrameCapture)(RENDERDOC_DevicePointer device,
|
||||
RENDERDOC_WindowHandle wndHandle);
|
||||
|
||||
// Returns whether or not a frame capture is currently ongoing anywhere.
|
||||
//
|
||||
// This will return 1 if a capture is ongoing, and 0 if there is no capture running
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsFrameCapturing)();
|
||||
|
||||
// Ends capturing immediately.
|
||||
//
|
||||
// This will return 1 if the capture succeeded, and 0 if there was an error capturing.
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_EndFrameCapture)(RENDERDOC_DevicePointer device,
|
||||
RENDERDOC_WindowHandle wndHandle);
|
||||
|
||||
// Ends capturing immediately and discard any data stored without saving to disk.
|
||||
//
|
||||
// This will return 1 if the capture was discarded, and 0 if there was an error or no capture
|
||||
// was in progress
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_DiscardFrameCapture)(RENDERDOC_DevicePointer device,
|
||||
RENDERDOC_WindowHandle wndHandle);
|
||||
|
||||
// Only valid to be called between a call to StartFrameCapture and EndFrameCapture. Gives a custom
|
||||
// title to the capture produced which will be displayed in the UI.
|
||||
//
|
||||
// If multiple captures are ongoing, this title will be applied to the first capture to end after
|
||||
// this call. The second capture to end will have no title, unless this function is called again.
|
||||
//
|
||||
// Calling this function has no effect if no capture is currently running, and if it is called
|
||||
// multiple times only the last title will be used.
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureTitle)(const char *title);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// RenderDoc API versions
|
||||
//
|
||||
|
||||
// RenderDoc uses semantic versioning (http://semver.org/).
|
||||
//
|
||||
// MAJOR version is incremented when incompatible API changes happen.
|
||||
// MINOR version is incremented when functionality is added in a backwards-compatible manner.
|
||||
// PATCH version is incremented when backwards-compatible bug fixes happen.
|
||||
//
|
||||
// Note that this means the API returned can be higher than the one you might have requested.
|
||||
// e.g. if you are running against a newer RenderDoc that supports 1.0.1, it will be returned
|
||||
// instead of 1.0.0. You can check this with the GetAPIVersion entry point
|
||||
typedef enum RENDERDOC_Version
|
||||
{
|
||||
eRENDERDOC_API_Version_1_0_0 = 10000, // RENDERDOC_API_1_0_0 = 1 00 00
|
||||
eRENDERDOC_API_Version_1_0_1 = 10001, // RENDERDOC_API_1_0_1 = 1 00 01
|
||||
eRENDERDOC_API_Version_1_0_2 = 10002, // RENDERDOC_API_1_0_2 = 1 00 02
|
||||
eRENDERDOC_API_Version_1_1_0 = 10100, // RENDERDOC_API_1_1_0 = 1 01 00
|
||||
eRENDERDOC_API_Version_1_1_1 = 10101, // RENDERDOC_API_1_1_1 = 1 01 01
|
||||
eRENDERDOC_API_Version_1_1_2 = 10102, // RENDERDOC_API_1_1_2 = 1 01 02
|
||||
eRENDERDOC_API_Version_1_2_0 = 10200, // RENDERDOC_API_1_2_0 = 1 02 00
|
||||
eRENDERDOC_API_Version_1_3_0 = 10300, // RENDERDOC_API_1_3_0 = 1 03 00
|
||||
eRENDERDOC_API_Version_1_4_0 = 10400, // RENDERDOC_API_1_4_0 = 1 04 00
|
||||
eRENDERDOC_API_Version_1_4_1 = 10401, // RENDERDOC_API_1_4_1 = 1 04 01
|
||||
eRENDERDOC_API_Version_1_4_2 = 10402, // RENDERDOC_API_1_4_2 = 1 04 02
|
||||
eRENDERDOC_API_Version_1_5_0 = 10500, // RENDERDOC_API_1_5_0 = 1 05 00
|
||||
eRENDERDOC_API_Version_1_6_0 = 10600, // RENDERDOC_API_1_6_0 = 1 06 00
|
||||
} RENDERDOC_Version;
|
||||
|
||||
// API version changelog:
|
||||
//
|
||||
// 1.0.0 - initial release
|
||||
// 1.0.1 - Bugfix: IsFrameCapturing() was returning false for captures that were triggered
|
||||
// by keypress or TriggerCapture, instead of Start/EndFrameCapture.
|
||||
// 1.0.2 - Refactor: Renamed eRENDERDOC_Option_DebugDeviceMode to eRENDERDOC_Option_APIValidation
|
||||
// 1.1.0 - Add feature: TriggerMultiFrameCapture(). Backwards compatible with 1.0.x since the new
|
||||
// function pointer is added to the end of the struct, the original layout is identical
|
||||
// 1.1.1 - Refactor: Renamed remote access to target control (to better disambiguate from remote
|
||||
// replay/remote server concept in replay UI)
|
||||
// 1.1.2 - Refactor: Renamed "log file" in function names to just capture, to clarify that these
|
||||
// are captures and not debug logging files. This is the first API version in the v1.0
|
||||
// branch.
|
||||
// 1.2.0 - Added feature: SetCaptureFileComments() to add comments to a capture file that will be
|
||||
// displayed in the UI program on load.
|
||||
// 1.3.0 - Added feature: New capture option eRENDERDOC_Option_AllowUnsupportedVendorExtensions
|
||||
// which allows users to opt-in to allowing unsupported vendor extensions to function.
|
||||
// Should be used at the user's own risk.
|
||||
// Refactor: Renamed eRENDERDOC_Option_VerifyMapWrites to
|
||||
// eRENDERDOC_Option_VerifyBufferAccess, which now also controls initialisation to
|
||||
// 0xdddddddd of uninitialised buffer contents.
|
||||
// 1.4.0 - Added feature: DiscardFrameCapture() to discard a frame capture in progress and stop
|
||||
// capturing without saving anything to disk.
|
||||
// 1.4.1 - Refactor: Renamed Shutdown to RemoveHooks to better clarify what is happening
|
||||
// 1.4.2 - Refactor: Renamed 'draws' to 'actions' in callstack capture option.
|
||||
// 1.5.0 - Added feature: ShowReplayUI() to request that the replay UI show itself if connected
|
||||
// 1.6.0 - Added feature: SetCaptureTitle() which can be used to set a title for a
|
||||
// capture made with StartFrameCapture() or EndFrameCapture()
|
||||
|
||||
typedef struct RENDERDOC_API_1_6_0
|
||||
{
|
||||
pRENDERDOC_GetAPIVersion GetAPIVersion;
|
||||
|
||||
pRENDERDOC_SetCaptureOptionU32 SetCaptureOptionU32;
|
||||
pRENDERDOC_SetCaptureOptionF32 SetCaptureOptionF32;
|
||||
|
||||
pRENDERDOC_GetCaptureOptionU32 GetCaptureOptionU32;
|
||||
pRENDERDOC_GetCaptureOptionF32 GetCaptureOptionF32;
|
||||
|
||||
pRENDERDOC_SetFocusToggleKeys SetFocusToggleKeys;
|
||||
pRENDERDOC_SetCaptureKeys SetCaptureKeys;
|
||||
|
||||
pRENDERDOC_GetOverlayBits GetOverlayBits;
|
||||
pRENDERDOC_MaskOverlayBits MaskOverlayBits;
|
||||
|
||||
// Shutdown was renamed to RemoveHooks in 1.4.1.
|
||||
// These unions allow old code to continue compiling without changes
|
||||
union
|
||||
{
|
||||
pRENDERDOC_Shutdown Shutdown;
|
||||
pRENDERDOC_RemoveHooks RemoveHooks;
|
||||
};
|
||||
pRENDERDOC_UnloadCrashHandler UnloadCrashHandler;
|
||||
|
||||
// Get/SetLogFilePathTemplate was renamed to Get/SetCaptureFilePathTemplate in 1.1.2.
|
||||
// These unions allow old code to continue compiling without changes
|
||||
union
|
||||
{
|
||||
// deprecated name
|
||||
pRENDERDOC_SetLogFilePathTemplate SetLogFilePathTemplate;
|
||||
// current name
|
||||
pRENDERDOC_SetCaptureFilePathTemplate SetCaptureFilePathTemplate;
|
||||
};
|
||||
union
|
||||
{
|
||||
// deprecated name
|
||||
pRENDERDOC_GetLogFilePathTemplate GetLogFilePathTemplate;
|
||||
// current name
|
||||
pRENDERDOC_GetCaptureFilePathTemplate GetCaptureFilePathTemplate;
|
||||
};
|
||||
|
||||
pRENDERDOC_GetNumCaptures GetNumCaptures;
|
||||
pRENDERDOC_GetCapture GetCapture;
|
||||
|
||||
pRENDERDOC_TriggerCapture TriggerCapture;
|
||||
|
||||
// IsRemoteAccessConnected was renamed to IsTargetControlConnected in 1.1.1.
|
||||
// This union allows old code to continue compiling without changes
|
||||
union
|
||||
{
|
||||
// deprecated name
|
||||
pRENDERDOC_IsRemoteAccessConnected IsRemoteAccessConnected;
|
||||
// current name
|
||||
pRENDERDOC_IsTargetControlConnected IsTargetControlConnected;
|
||||
};
|
||||
pRENDERDOC_LaunchReplayUI LaunchReplayUI;
|
||||
|
||||
pRENDERDOC_SetActiveWindow SetActiveWindow;
|
||||
|
||||
pRENDERDOC_StartFrameCapture StartFrameCapture;
|
||||
pRENDERDOC_IsFrameCapturing IsFrameCapturing;
|
||||
pRENDERDOC_EndFrameCapture EndFrameCapture;
|
||||
|
||||
// new function in 1.1.0
|
||||
pRENDERDOC_TriggerMultiFrameCapture TriggerMultiFrameCapture;
|
||||
|
||||
// new function in 1.2.0
|
||||
pRENDERDOC_SetCaptureFileComments SetCaptureFileComments;
|
||||
|
||||
// new function in 1.4.0
|
||||
pRENDERDOC_DiscardFrameCapture DiscardFrameCapture;
|
||||
|
||||
// new function in 1.5.0
|
||||
pRENDERDOC_ShowReplayUI ShowReplayUI;
|
||||
|
||||
// new function in 1.6.0
|
||||
pRENDERDOC_SetCaptureTitle SetCaptureTitle;
|
||||
} RENDERDOC_API_1_6_0;
|
||||
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_0_0;
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_0_1;
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_0_2;
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_1_0;
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_1_1;
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_1_2;
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_2_0;
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_3_0;
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_4_0;
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_4_1;
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_4_2;
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_5_0;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// RenderDoc API entry point
|
||||
//
|
||||
// This entry point can be obtained via GetProcAddress/dlsym if RenderDoc is available.
|
||||
//
|
||||
// The name is the same as the typedef - "RENDERDOC_GetAPI"
|
||||
//
|
||||
// This function is not thread safe, and should not be called on multiple threads at once.
|
||||
// Ideally, call this once as early as possible in your application's startup, before doing
|
||||
// any API work, since some configuration functionality etc has to be done also before
|
||||
// initialising any APIs.
|
||||
//
|
||||
// Parameters:
|
||||
// version is a single value from the RENDERDOC_Version above.
|
||||
//
|
||||
// outAPIPointers will be filled out with a pointer to the corresponding struct of function
|
||||
// pointers.
|
||||
//
|
||||
// Returns:
|
||||
// 1 - if the outAPIPointers has been filled with a pointer to the API struct requested
|
||||
// 0 - if the requested version is not supported or the arguments are invalid.
|
||||
//
|
||||
typedef int(RENDERDOC_CC *pRENDERDOC_GetAPI)(RENDERDOC_Version version, void **outAPIPointers);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
|
@ -85,6 +85,7 @@ if (MSVC)
|
|||
/wd4100 # 'identifier': unreferenced formal parameter
|
||||
/wd4324 # 'struct_name': structure was padded due to __declspec(align())
|
||||
/wd4201 # nonstandard extension used : nameless struct/union
|
||||
/wd4702 # unreachable code (when used with LTO)
|
||||
)
|
||||
|
||||
if (USE_CCACHE OR YUZU_USE_PRECOMPILED_HEADERS)
|
||||
|
|
|
@ -77,13 +77,30 @@ android {
|
|||
buildConfigField("String", "BRANCH", "\"${getBranch()}\"")
|
||||
}
|
||||
|
||||
val keystoreFile = System.getenv("ANDROID_KEYSTORE_FILE")
|
||||
if (keystoreFile != null) {
|
||||
signingConfigs {
|
||||
create("release") {
|
||||
storeFile = file(keystoreFile)
|
||||
storePassword = System.getenv("ANDROID_KEYSTORE_PASS")
|
||||
keyAlias = System.getenv("ANDROID_KEY_ALIAS")
|
||||
keyPassword = System.getenv("ANDROID_KEYSTORE_PASS")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Define build types, which are orthogonal to product flavors.
|
||||
buildTypes {
|
||||
|
||||
// Signed by release key, allowing for upload to Play Store.
|
||||
release {
|
||||
signingConfig = if (keystoreFile != null) {
|
||||
signingConfigs.getByName("release")
|
||||
} else {
|
||||
signingConfigs.getByName("debug")
|
||||
}
|
||||
|
||||
resValue("string", "app_name_suffixed", "yuzu")
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
isMinifyEnabled = true
|
||||
isDebuggable = false
|
||||
proguardFiles(
|
||||
|
|
|
@ -307,21 +307,6 @@ object NativeLibrary {
|
|||
*/
|
||||
external fun isPaused(): Boolean
|
||||
|
||||
/**
|
||||
* Mutes emulation sound
|
||||
*/
|
||||
external fun muteAudio(): Boolean
|
||||
|
||||
/**
|
||||
* Unmutes emulation sound
|
||||
*/
|
||||
external fun unmuteAudio(): Boolean
|
||||
|
||||
/**
|
||||
* Returns true if emulation audio is muted.
|
||||
*/
|
||||
external fun isMuted(): Boolean
|
||||
|
||||
/**
|
||||
* Returns the performance stats for the current game
|
||||
*/
|
||||
|
|
|
@ -332,7 +332,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
pictureInPictureActions.add(pauseRemoteAction)
|
||||
}
|
||||
|
||||
if (NativeLibrary.isMuted()) {
|
||||
if (BooleanSetting.AUDIO_MUTED.boolean) {
|
||||
val unmuteIcon = Icon.createWithResource(
|
||||
this@EmulationActivity,
|
||||
R.drawable.ic_pip_unmute
|
||||
|
@ -389,9 +389,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
if (!NativeLibrary.isPaused()) NativeLibrary.pauseEmulation()
|
||||
}
|
||||
if (intent.action == actionUnmute) {
|
||||
if (NativeLibrary.isMuted()) NativeLibrary.unmuteAudio()
|
||||
if (BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(false)
|
||||
} else if (intent.action == actionMute) {
|
||||
if (!NativeLibrary.isMuted()) NativeLibrary.muteAudio()
|
||||
if (!BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(true)
|
||||
}
|
||||
buildPictureInPictureParams()
|
||||
}
|
||||
|
@ -417,7 +417,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
} catch (ignored: Exception) {
|
||||
}
|
||||
// Always resume audio, since there is no UI button
|
||||
if (NativeLibrary.isMuted()) NativeLibrary.unmuteAudio()
|
||||
if (BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
package org.yuzu.yuzu_emu.adapters
|
||||
|
||||
import android.content.Intent
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.LayerDrawable
|
||||
import android.net.Uri
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
|
@ -15,7 +16,10 @@ import android.widget.Toast
|
|||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.pm.ShortcutInfoCompat
|
||||
import androidx.core.content.pm.ShortcutManagerCompat
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.core.graphics.drawable.toDrawable
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.navigation.findNavController
|
||||
|
@ -87,11 +91,24 @@ class GameAdapter(private val activity: AppCompatActivity) :
|
|||
action = Intent.ACTION_VIEW
|
||||
data = Uri.parse(holder.game.path)
|
||||
}
|
||||
|
||||
val layerDrawable = ResourcesCompat.getDrawable(
|
||||
YuzuApplication.appContext.resources,
|
||||
R.drawable.shortcut,
|
||||
null
|
||||
) as LayerDrawable
|
||||
layerDrawable.setDrawableByLayerId(
|
||||
R.id.shortcut_foreground,
|
||||
GameIconUtils.getGameIcon(holder.game).toDrawable(YuzuApplication.appContext.resources)
|
||||
)
|
||||
val inset = YuzuApplication.appContext.resources
|
||||
.getDimensionPixelSize(R.dimen.icon_inset)
|
||||
layerDrawable.setLayerInset(1, inset, inset, inset, inset)
|
||||
val shortcut = ShortcutInfoCompat.Builder(YuzuApplication.appContext, holder.game.path)
|
||||
.setShortLabel(holder.game.title)
|
||||
.setIcon(
|
||||
IconCompat.createWithBitmap(
|
||||
(holder.binding.imageGameScreen.drawable as BitmapDrawable).bitmap
|
||||
IconCompat.createWithAdaptiveBitmap(
|
||||
layerDrawable.toBitmap(config = Bitmap.Config.ARGB_8888)
|
||||
)
|
||||
)
|
||||
.setIntent(openIntent)
|
||||
|
|
|
@ -10,8 +10,12 @@ import android.view.ViewGroup
|
|||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding
|
||||
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
|
||||
|
@ -86,7 +90,11 @@ class HomeSettingAdapter(
|
|||
binding.optionIcon.alpha = 0.5f
|
||||
}
|
||||
|
||||
option.details.observe(viewLifecycle) { updateOptionDetails(it) }
|
||||
viewLifecycle.lifecycleScope.launch {
|
||||
viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
option.details.collect { updateOptionDetails(it) }
|
||||
}
|
||||
}
|
||||
binding.optionDetail.postDelayed(
|
||||
{
|
||||
binding.optionDetail.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||
|
|
|
@ -10,6 +10,7 @@ enum class BooleanSetting(
|
|||
override val category: Settings.Category,
|
||||
override val androidDefault: Boolean? = null
|
||||
) : AbstractBooleanSetting {
|
||||
AUDIO_MUTED("audio_muted", Settings.Category.Audio),
|
||||
CPU_DEBUG_MODE("cpu_debug_mode", Settings.Category.Cpu),
|
||||
FASTMEM("cpuopt_fastmem", Settings.Category.Cpu),
|
||||
FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.Category.Cpu),
|
||||
|
|
|
@ -80,6 +80,17 @@ object Settings {
|
|||
const val SECTION_THEME = "Theme"
|
||||
const val SECTION_DEBUG = "Debug"
|
||||
|
||||
enum class MenuTag(val titleId: Int) {
|
||||
SECTION_ROOT(R.string.advanced_settings),
|
||||
SECTION_GENERAL(R.string.preferences_general),
|
||||
SECTION_SYSTEM(R.string.preferences_system),
|
||||
SECTION_RENDERER(R.string.preferences_graphics),
|
||||
SECTION_AUDIO(R.string.preferences_audio),
|
||||
SECTION_CPU(R.string.cpu),
|
||||
SECTION_THEME(R.string.preferences_theme),
|
||||
SECTION_DEBUG(R.string.preferences_debug);
|
||||
}
|
||||
|
||||
const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
|
||||
|
||||
const val PREF_OVERLAY_VERSION = "OverlayVersion"
|
||||
|
|
|
@ -3,10 +3,12 @@
|
|||
|
||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
|
||||
class SubmenuSetting(
|
||||
titleId: Int,
|
||||
descriptionId: Int,
|
||||
val menuKey: String
|
||||
val menuKey: Settings.MenuTag
|
||||
) : SettingsItem(emptySetting, titleId, descriptionId) {
|
||||
override val type = TYPE_SUBMENU
|
||||
}
|
||||
|
|
|
@ -13,9 +13,14 @@ import androidx.appcompat.app.AppCompatActivity
|
|||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.navArgs
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.IOException
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
|
||||
|
@ -66,25 +71,39 @@ class SettingsActivity : AppCompatActivity() {
|
|||
)
|
||||
}
|
||||
|
||||
settingsViewModel.shouldRecreate.observe(this) {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldRecreate(false)
|
||||
recreate()
|
||||
lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.shouldRecreate.collectLatest {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldRecreate(false)
|
||||
recreate()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
settingsViewModel.shouldNavigateBack.observe(this) {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldNavigateBack(false)
|
||||
navigateBack()
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.shouldNavigateBack.collectLatest {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldNavigateBack(false)
|
||||
navigateBack()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
settingsViewModel.shouldShowResetSettingsDialog.observe(this) {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldShowResetSettingsDialog(false)
|
||||
ResetSettingsDialogFragment().show(
|
||||
supportFragmentManager,
|
||||
ResetSettingsDialogFragment.TAG
|
||||
)
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.shouldShowResetSettingsDialog.collectLatest {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldShowResetSettingsDialog(false)
|
||||
ResetSettingsDialogFragment().show(
|
||||
supportFragmentManager,
|
||||
ResetSettingsDialogFragment.TAG
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
package org.yuzu.yuzu_emu.features.settings.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
|
@ -13,14 +14,19 @@ import androidx.core.view.WindowInsetsCompat
|
|||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.divider.MaterialDividerItemDecoration
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.model.SettingsViewModel
|
||||
|
||||
class SettingsFragment : Fragment() {
|
||||
|
@ -51,15 +57,17 @@ class SettingsFragment : Fragment() {
|
|||
return binding.root
|
||||
}
|
||||
|
||||
// This is using the correct scope, lint is just acting up
|
||||
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
settingsAdapter = SettingsAdapter(this, requireContext())
|
||||
presenter = SettingsFragmentPresenter(
|
||||
settingsViewModel,
|
||||
settingsAdapter!!,
|
||||
args.menuTag,
|
||||
args.game?.gameId ?: ""
|
||||
args.menuTag
|
||||
)
|
||||
|
||||
binding.toolbarSettingsLayout.title = getString(args.menuTag.titleId)
|
||||
val dividerDecoration = MaterialDividerItemDecoration(
|
||||
requireContext(),
|
||||
LinearLayoutManager.VERTICAL
|
||||
|
@ -75,28 +83,31 @@ class SettingsFragment : Fragment() {
|
|||
settingsViewModel.setShouldNavigateBack(true)
|
||||
}
|
||||
|
||||
settingsViewModel.toolbarTitle.observe(viewLifecycleOwner) {
|
||||
if (it.isNotEmpty()) binding.toolbarSettingsLayout.title = it
|
||||
}
|
||||
|
||||
settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldReloadSettingsList(false)
|
||||
presenter.loadSettingsList()
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.shouldReloadSettingsList.collectLatest {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldReloadSettingsList(false)
|
||||
presenter.loadSettingsList()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
settingsViewModel.isUsingSearch.collectLatest {
|
||||
if (it) {
|
||||
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
|
||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
|
||||
} else {
|
||||
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
settingsViewModel.isUsingSearch.observe(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
|
||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
|
||||
} else {
|
||||
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||
}
|
||||
}
|
||||
|
||||
if (args.menuTag == SettingsFile.FILE_NAME_CONFIG) {
|
||||
if (args.menuTag == Settings.MenuTag.SECTION_ROOT) {
|
||||
binding.toolbarSettings.inflateMenu(R.menu.menu_settings)
|
||||
binding.toolbarSettings.setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
|
|
|
@ -6,7 +6,6 @@ package org.yuzu.yuzu_emu.features.settings.ui
|
|||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Build
|
||||
import android.text.TextUtils
|
||||
import android.widget.Toast
|
||||
import androidx.preference.PreferenceManager
|
||||
import org.yuzu.yuzu_emu.R
|
||||
|
@ -20,15 +19,13 @@ import org.yuzu.yuzu_emu.features.settings.model.LongSetting
|
|||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.*
|
||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||
import org.yuzu.yuzu_emu.model.SettingsViewModel
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
|
||||
class SettingsFragmentPresenter(
|
||||
private val settingsViewModel: SettingsViewModel,
|
||||
private val adapter: SettingsAdapter,
|
||||
private var menuTag: String,
|
||||
private var gameId: String
|
||||
private var menuTag: Settings.MenuTag
|
||||
) {
|
||||
private var settingsList = ArrayList<SettingsItem>()
|
||||
|
||||
|
@ -53,24 +50,15 @@ class SettingsFragmentPresenter(
|
|||
}
|
||||
|
||||
fun loadSettingsList() {
|
||||
if (!TextUtils.isEmpty(gameId)) {
|
||||
settingsViewModel.setToolbarTitle(
|
||||
context.getString(
|
||||
R.string.advanced_settings_game,
|
||||
gameId
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val sl = ArrayList<SettingsItem>()
|
||||
when (menuTag) {
|
||||
SettingsFile.FILE_NAME_CONFIG -> addConfigSettings(sl)
|
||||
Settings.SECTION_GENERAL -> addGeneralSettings(sl)
|
||||
Settings.SECTION_SYSTEM -> addSystemSettings(sl)
|
||||
Settings.SECTION_RENDERER -> addGraphicsSettings(sl)
|
||||
Settings.SECTION_AUDIO -> addAudioSettings(sl)
|
||||
Settings.SECTION_THEME -> addThemeSettings(sl)
|
||||
Settings.SECTION_DEBUG -> addDebugSettings(sl)
|
||||
Settings.MenuTag.SECTION_ROOT -> addConfigSettings(sl)
|
||||
Settings.MenuTag.SECTION_GENERAL -> addGeneralSettings(sl)
|
||||
Settings.MenuTag.SECTION_SYSTEM -> addSystemSettings(sl)
|
||||
Settings.MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
|
||||
Settings.MenuTag.SECTION_AUDIO -> addAudioSettings(sl)
|
||||
Settings.MenuTag.SECTION_THEME -> addThemeSettings(sl)
|
||||
Settings.MenuTag.SECTION_DEBUG -> addDebugSettings(sl)
|
||||
else -> {
|
||||
val context = YuzuApplication.appContext
|
||||
Toast.makeText(
|
||||
|
@ -86,13 +74,12 @@ class SettingsFragmentPresenter(
|
|||
}
|
||||
|
||||
private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
|
||||
settingsViewModel.setToolbarTitle(context.getString(R.string.advanced_settings))
|
||||
sl.apply {
|
||||
add(SubmenuSetting(R.string.preferences_general, 0, Settings.SECTION_GENERAL))
|
||||
add(SubmenuSetting(R.string.preferences_system, 0, Settings.SECTION_SYSTEM))
|
||||
add(SubmenuSetting(R.string.preferences_graphics, 0, Settings.SECTION_RENDERER))
|
||||
add(SubmenuSetting(R.string.preferences_audio, 0, Settings.SECTION_AUDIO))
|
||||
add(SubmenuSetting(R.string.preferences_debug, 0, Settings.SECTION_DEBUG))
|
||||
add(SubmenuSetting(R.string.preferences_general, 0, Settings.MenuTag.SECTION_GENERAL))
|
||||
add(SubmenuSetting(R.string.preferences_system, 0, Settings.MenuTag.SECTION_SYSTEM))
|
||||
add(SubmenuSetting(R.string.preferences_graphics, 0, Settings.MenuTag.SECTION_RENDERER))
|
||||
add(SubmenuSetting(R.string.preferences_audio, 0, Settings.MenuTag.SECTION_AUDIO))
|
||||
add(SubmenuSetting(R.string.preferences_debug, 0, Settings.MenuTag.SECTION_DEBUG))
|
||||
add(
|
||||
RunnableSetting(R.string.reset_to_default, 0, false) {
|
||||
settingsViewModel.setShouldShowResetSettingsDialog(true)
|
||||
|
@ -102,7 +89,6 @@ class SettingsFragmentPresenter(
|
|||
}
|
||||
|
||||
private fun addGeneralSettings(sl: ArrayList<SettingsItem>) {
|
||||
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_general))
|
||||
sl.apply {
|
||||
add(BooleanSetting.RENDERER_USE_SPEED_LIMIT.key)
|
||||
add(ShortSetting.RENDERER_SPEED_LIMIT.key)
|
||||
|
@ -112,7 +98,6 @@ class SettingsFragmentPresenter(
|
|||
}
|
||||
|
||||
private fun addSystemSettings(sl: ArrayList<SettingsItem>) {
|
||||
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_system))
|
||||
sl.apply {
|
||||
add(BooleanSetting.USE_DOCKED_MODE.key)
|
||||
add(IntSetting.REGION_INDEX.key)
|
||||
|
@ -123,7 +108,6 @@ class SettingsFragmentPresenter(
|
|||
}
|
||||
|
||||
private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
|
||||
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_graphics))
|
||||
sl.apply {
|
||||
add(IntSetting.RENDERER_ACCURACY.key)
|
||||
add(IntSetting.RENDERER_RESOLUTION.key)
|
||||
|
@ -140,7 +124,6 @@ class SettingsFragmentPresenter(
|
|||
}
|
||||
|
||||
private fun addAudioSettings(sl: ArrayList<SettingsItem>) {
|
||||
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_audio))
|
||||
sl.apply {
|
||||
add(IntSetting.AUDIO_OUTPUT_ENGINE.key)
|
||||
add(ByteSetting.AUDIO_VOLUME.key)
|
||||
|
@ -148,7 +131,6 @@ class SettingsFragmentPresenter(
|
|||
}
|
||||
|
||||
private fun addThemeSettings(sl: ArrayList<SettingsItem>) {
|
||||
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_theme))
|
||||
sl.apply {
|
||||
val theme: AbstractIntSetting = object : AbstractIntSetting {
|
||||
override val int: Int
|
||||
|
@ -261,7 +243,6 @@ class SettingsFragmentPresenter(
|
|||
}
|
||||
|
||||
private fun addDebugSettings(sl: ArrayList<SettingsItem>) {
|
||||
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_debug))
|
||||
sl.apply {
|
||||
add(HeaderSetting(R.string.gpu))
|
||||
add(IntSetting.RENDERER_BACKEND.key)
|
||||
|
|
|
@ -39,6 +39,7 @@ import androidx.window.layout.WindowLayoutInfo
|
|||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.slider.Slider
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
|
@ -49,7 +50,6 @@ import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
|
|||
import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||
import org.yuzu.yuzu_emu.model.Game
|
||||
import org.yuzu.yuzu_emu.model.EmulationViewModel
|
||||
import org.yuzu.yuzu_emu.overlay.InputOverlay
|
||||
|
@ -129,6 +129,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
return binding.root
|
||||
}
|
||||
|
||||
// This is using the correct scope, lint is just acting up
|
||||
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
binding.surfaceEmulation.holder.addCallback(this)
|
||||
binding.showFpsText.setTextColor(Color.YELLOW)
|
||||
|
@ -163,7 +165,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
R.id.menu_settings -> {
|
||||
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
|
||||
null,
|
||||
SettingsFile.FILE_NAME_CONFIG
|
||||
Settings.MenuTag.SECTION_ROOT
|
||||
)
|
||||
binding.root.findNavController().navigate(action)
|
||||
true
|
||||
|
@ -205,59 +207,80 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
}
|
||||
)
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
|
||||
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
WindowInfoTracker.getOrCreate(requireContext())
|
||||
.windowLayoutInfo(requireActivity())
|
||||
.collect { updateFoldableLayout(requireActivity() as EmulationActivity, it) }
|
||||
}
|
||||
}
|
||||
|
||||
GameIconUtils.loadGameIcon(game, binding.loadingImage)
|
||||
binding.loadingTitle.text = game.title
|
||||
binding.loadingTitle.isSelected = true
|
||||
binding.loadingText.isSelected = true
|
||||
|
||||
emulationViewModel.shaderProgress.observe(viewLifecycleOwner) {
|
||||
if (it > 0 && it != emulationViewModel.totalShaders.value!!) {
|
||||
binding.loadingProgressIndicator.isIndeterminate = false
|
||||
|
||||
if (it < binding.loadingProgressIndicator.max) {
|
||||
binding.loadingProgressIndicator.progress = it
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
WindowInfoTracker.getOrCreate(requireContext())
|
||||
.windowLayoutInfo(requireActivity())
|
||||
.collect {
|
||||
updateFoldableLayout(requireActivity() as EmulationActivity, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.shaderProgress.collectLatest {
|
||||
if (it > 0 && it != emulationViewModel.totalShaders.value) {
|
||||
binding.loadingProgressIndicator.isIndeterminate = false
|
||||
|
||||
if (it == emulationViewModel.totalShaders.value!!) {
|
||||
binding.loadingText.setText(R.string.loading)
|
||||
binding.loadingProgressIndicator.isIndeterminate = true
|
||||
if (it < binding.loadingProgressIndicator.max) {
|
||||
binding.loadingProgressIndicator.progress = it
|
||||
}
|
||||
}
|
||||
|
||||
if (it == emulationViewModel.totalShaders.value) {
|
||||
binding.loadingText.setText(R.string.loading)
|
||||
binding.loadingProgressIndicator.isIndeterminate = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
emulationViewModel.totalShaders.observe(viewLifecycleOwner) {
|
||||
binding.loadingProgressIndicator.max = it
|
||||
}
|
||||
emulationViewModel.shaderMessage.observe(viewLifecycleOwner) {
|
||||
if (it.isNotEmpty()) {
|
||||
binding.loadingText.text = it
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.totalShaders.collectLatest {
|
||||
binding.loadingProgressIndicator.max = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emulationViewModel.emulationStarted.observe(viewLifecycleOwner) { started ->
|
||||
if (started) {
|
||||
binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
|
||||
ViewUtils.showView(binding.surfaceInputOverlay)
|
||||
ViewUtils.hideView(binding.loadingIndicator)
|
||||
|
||||
// Setup overlay
|
||||
updateShowFpsOverlay()
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.shaderMessage.collectLatest {
|
||||
if (it.isNotEmpty()) {
|
||||
binding.loadingText.text = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.emulationStarted.collectLatest {
|
||||
if (it) {
|
||||
binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
|
||||
ViewUtils.showView(binding.surfaceInputOverlay)
|
||||
ViewUtils.hideView(binding.loadingIndicator)
|
||||
|
||||
emulationViewModel.isEmulationStopping.observe(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
binding.loadingText.setText(R.string.shutting_down)
|
||||
ViewUtils.showView(binding.loadingIndicator)
|
||||
ViewUtils.hideView(binding.inputContainer)
|
||||
ViewUtils.hideView(binding.showFpsText)
|
||||
// Setup overlay
|
||||
updateShowFpsOverlay()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.isEmulationStopping.collectLatest {
|
||||
if (it) {
|
||||
binding.loadingText.setText(R.string.shutting_down)
|
||||
ViewUtils.showView(binding.loadingIndicator)
|
||||
ViewUtils.hideView(binding.inputContainer)
|
||||
ViewUtils.hideView(binding.showFpsText)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -274,9 +297,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
if (EmulationMenuSettings.showOverlay &&
|
||||
emulationViewModel.emulationStarted.value == true
|
||||
) {
|
||||
if (EmulationMenuSettings.showOverlay && emulationViewModel.emulationStarted.value) {
|
||||
binding.surfaceInputOverlay.post {
|
||||
binding.surfaceInputOverlay.visibility = View.VISIBLE
|
||||
}
|
||||
|
|
|
@ -37,7 +37,6 @@ import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
|
|||
import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
|
||||
import org.yuzu.yuzu_emu.features.DocumentProvider
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||
import org.yuzu.yuzu_emu.model.HomeSetting
|
||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||
import org.yuzu.yuzu_emu.ui.main.MainActivity
|
||||
|
@ -78,7 +77,7 @@ class HomeSettingsFragment : Fragment() {
|
|||
{
|
||||
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
|
||||
null,
|
||||
SettingsFile.FILE_NAME_CONFIG
|
||||
Settings.MenuTag.SECTION_ROOT
|
||||
)
|
||||
binding.root.findNavController().navigate(action)
|
||||
}
|
||||
|
@ -100,7 +99,7 @@ class HomeSettingsFragment : Fragment() {
|
|||
{
|
||||
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
|
||||
null,
|
||||
Settings.SECTION_THEME
|
||||
Settings.MenuTag.SECTION_THEME
|
||||
)
|
||||
binding.root.findNavController().navigate(action)
|
||||
}
|
||||
|
|
|
@ -5,49 +5,75 @@ package org.yuzu.yuzu_emu.fragments
|
|||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
|
||||
import org.yuzu.yuzu_emu.model.TaskViewModel
|
||||
|
||||
class IndeterminateProgressDialogFragment : DialogFragment() {
|
||||
private val taskViewModel: TaskViewModel by activityViewModels()
|
||||
|
||||
private lateinit var binding: DialogProgressBarBinding
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val titleId = requireArguments().getInt(TITLE)
|
||||
|
||||
val progressBinding = DialogProgressBarBinding.inflate(layoutInflater)
|
||||
progressBinding.progressBar.isIndeterminate = true
|
||||
binding = DialogProgressBarBinding.inflate(layoutInflater)
|
||||
binding.progressBar.isIndeterminate = true
|
||||
val dialog = MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(titleId)
|
||||
.setView(progressBinding.root)
|
||||
.setView(binding.root)
|
||||
.create()
|
||||
dialog.setCanceledOnTouchOutside(false)
|
||||
|
||||
taskViewModel.isComplete.observe(this) { complete ->
|
||||
if (complete) {
|
||||
dialog.dismiss()
|
||||
when (val result = taskViewModel.result.value) {
|
||||
is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG).show()
|
||||
is MessageDialogFragment -> result.show(
|
||||
requireActivity().supportFragmentManager,
|
||||
MessageDialogFragment.TAG
|
||||
)
|
||||
}
|
||||
taskViewModel.clear()
|
||||
}
|
||||
}
|
||||
|
||||
if (taskViewModel.isRunning.value == false) {
|
||||
if (!taskViewModel.isRunning.value) {
|
||||
taskViewModel.runTask()
|
||||
}
|
||||
return dialog
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
taskViewModel.isComplete.collect {
|
||||
if (it) {
|
||||
dismiss()
|
||||
when (val result = taskViewModel.result.value) {
|
||||
is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG)
|
||||
.show()
|
||||
|
||||
is MessageDialogFragment -> result.show(
|
||||
requireActivity().supportFragmentManager,
|
||||
MessageDialogFragment.TAG
|
||||
)
|
||||
}
|
||||
taskViewModel.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "IndeterminateProgressDialogFragment"
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
|
@ -17,9 +18,13 @@ import androidx.core.view.updatePadding
|
|||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.preference.PreferenceManager
|
||||
import info.debatty.java.stringsimilarity.Jaccard
|
||||
import info.debatty.java.stringsimilarity.JaroWinkler
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Locale
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
|
@ -52,6 +57,8 @@ class SearchFragment : Fragment() {
|
|||
return binding.root
|
||||
}
|
||||
|
||||
// This is using the correct scope, lint is just acting up
|
||||
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
homeViewModel.setNavigationVisibility(visible = true, animated = false)
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||
|
@ -79,21 +86,32 @@ class SearchFragment : Fragment() {
|
|||
filterAndSearch()
|
||||
}
|
||||
|
||||
gamesViewModel.apply {
|
||||
searchFocused.observe(viewLifecycleOwner) { searchFocused ->
|
||||
if (searchFocused) {
|
||||
focusSearch()
|
||||
gamesViewModel.setSearchFocused(false)
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
gamesViewModel.searchFocused.collect {
|
||||
if (it) {
|
||||
focusSearch()
|
||||
gamesViewModel.setSearchFocused(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
games.observe(viewLifecycleOwner) { filterAndSearch() }
|
||||
searchedGames.observe(viewLifecycleOwner) {
|
||||
(binding.gridGamesSearch.adapter as GameAdapter).submitList(it)
|
||||
if (it.isEmpty()) {
|
||||
binding.noResultsView.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.noResultsView.visibility = View.GONE
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
gamesViewModel.games.collect { filterAndSearch() }
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
gamesViewModel.searchedGames.collect {
|
||||
(binding.gridGamesSearch.adapter as GameAdapter).submitList(it)
|
||||
if (it.isEmpty()) {
|
||||
binding.noResultsView.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.noResultsView.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -109,7 +127,7 @@ class SearchFragment : Fragment() {
|
|||
private inner class ScoredGame(val score: Double, val item: Game)
|
||||
|
||||
private fun filterAndSearch() {
|
||||
val baseList = gamesViewModel.games.value!!
|
||||
val baseList = gamesViewModel.games.value
|
||||
val filteredList: List<Game> = when (binding.chipGroup.checkedChipId) {
|
||||
R.id.chip_recently_played -> {
|
||||
baseList.filter {
|
||||
|
|
|
@ -15,10 +15,14 @@ import androidx.core.view.updatePadding
|
|||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.divider.MaterialDividerItemDecoration
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
import info.debatty.java.stringsimilarity.Cosine
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
|
@ -79,10 +83,14 @@ class SettingsSearchFragment : Fragment() {
|
|||
search()
|
||||
binding.settingsList.smoothScrollToPosition(0)
|
||||
}
|
||||
settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldReloadSettingsList(false)
|
||||
search()
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.shouldReloadSettingsList.collect {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldReloadSettingsList(false)
|
||||
search()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,10 +22,14 @@ import androidx.core.view.isVisible
|
|||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
|
||||
import com.google.android.material.transition.MaterialFadeThrough
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
|
@ -206,10 +210,14 @@ class SetupFragment : Fragment() {
|
|||
)
|
||||
}
|
||||
|
||||
homeViewModel.shouldPageForward.observe(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
pageForward()
|
||||
homeViewModel.setShouldPageForward(false)
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
homeViewModel.shouldPageForward.collect {
|
||||
if (it) {
|
||||
pageForward()
|
||||
homeViewModel.setShouldPageForward(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,28 +3,28 @@
|
|||
|
||||
package org.yuzu.yuzu_emu.model
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
class EmulationViewModel : ViewModel() {
|
||||
private val _emulationStarted = MutableLiveData(false)
|
||||
val emulationStarted: LiveData<Boolean> get() = _emulationStarted
|
||||
val emulationStarted: StateFlow<Boolean> get() = _emulationStarted
|
||||
private val _emulationStarted = MutableStateFlow(false)
|
||||
|
||||
private val _isEmulationStopping = MutableLiveData(false)
|
||||
val isEmulationStopping: LiveData<Boolean> get() = _isEmulationStopping
|
||||
val isEmulationStopping: StateFlow<Boolean> get() = _isEmulationStopping
|
||||
private val _isEmulationStopping = MutableStateFlow(false)
|
||||
|
||||
private val _shaderProgress = MutableLiveData(0)
|
||||
val shaderProgress: LiveData<Int> get() = _shaderProgress
|
||||
val shaderProgress: StateFlow<Int> get() = _shaderProgress
|
||||
private val _shaderProgress = MutableStateFlow(0)
|
||||
|
||||
private val _totalShaders = MutableLiveData(0)
|
||||
val totalShaders: LiveData<Int> get() = _totalShaders
|
||||
val totalShaders: StateFlow<Int> get() = _totalShaders
|
||||
private val _totalShaders = MutableStateFlow(0)
|
||||
|
||||
private val _shaderMessage = MutableLiveData("")
|
||||
val shaderMessage: LiveData<String> get() = _shaderMessage
|
||||
val shaderMessage: StateFlow<String> get() = _shaderMessage
|
||||
private val _shaderMessage = MutableStateFlow("")
|
||||
|
||||
fun setEmulationStarted(started: Boolean) {
|
||||
_emulationStarted.postValue(started)
|
||||
_emulationStarted.value = started
|
||||
}
|
||||
|
||||
fun setIsEmulationStopping(value: Boolean) {
|
||||
|
@ -50,10 +50,18 @@ class EmulationViewModel : ViewModel() {
|
|||
}
|
||||
|
||||
fun clear() {
|
||||
_emulationStarted.value = false
|
||||
_isEmulationStopping.value = false
|
||||
_shaderProgress.value = 0
|
||||
_totalShaders.value = 0
|
||||
_shaderMessage.value = ""
|
||||
setEmulationStarted(false)
|
||||
setIsEmulationStopping(false)
|
||||
setShaderProgress(0)
|
||||
setTotalShaders(0)
|
||||
setShaderMessage("")
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val KEY_EMULATION_STARTED = "EmulationStarted"
|
||||
const val KEY_IS_EMULATION_STOPPING = "IsEmulationStarting"
|
||||
const val KEY_SHADER_PROGRESS = "ShaderProgress"
|
||||
const val KEY_TOTAL_SHADERS = "TotalShaders"
|
||||
const val KEY_SHADER_MESSAGE = "ShaderMessage"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,13 +5,13 @@ package org.yuzu.yuzu_emu.model
|
|||
|
||||
import android.net.Uri
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.preference.PreferenceManager
|
||||
import java.util.Locale
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
|
@ -24,23 +24,23 @@ import org.yuzu.yuzu_emu.utils.GameHelper
|
|||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
class GamesViewModel : ViewModel() {
|
||||
private val _games = MutableLiveData<List<Game>>(emptyList())
|
||||
val games: LiveData<List<Game>> get() = _games
|
||||
val games: StateFlow<List<Game>> get() = _games
|
||||
private val _games = MutableStateFlow(emptyList<Game>())
|
||||
|
||||
private val _searchedGames = MutableLiveData<List<Game>>(emptyList())
|
||||
val searchedGames: LiveData<List<Game>> get() = _searchedGames
|
||||
val searchedGames: StateFlow<List<Game>> get() = _searchedGames
|
||||
private val _searchedGames = MutableStateFlow(emptyList<Game>())
|
||||
|
||||
private val _isReloading = MutableLiveData(false)
|
||||
val isReloading: LiveData<Boolean> get() = _isReloading
|
||||
val isReloading: StateFlow<Boolean> get() = _isReloading
|
||||
private val _isReloading = MutableStateFlow(false)
|
||||
|
||||
private val _shouldSwapData = MutableLiveData(false)
|
||||
val shouldSwapData: LiveData<Boolean> get() = _shouldSwapData
|
||||
val shouldSwapData: StateFlow<Boolean> get() = _shouldSwapData
|
||||
private val _shouldSwapData = MutableStateFlow(false)
|
||||
|
||||
private val _shouldScrollToTop = MutableLiveData(false)
|
||||
val shouldScrollToTop: LiveData<Boolean> get() = _shouldScrollToTop
|
||||
val shouldScrollToTop: StateFlow<Boolean> get() = _shouldScrollToTop
|
||||
private val _shouldScrollToTop = MutableStateFlow(false)
|
||||
|
||||
private val _searchFocused = MutableLiveData(false)
|
||||
val searchFocused: LiveData<Boolean> get() = _searchFocused
|
||||
val searchFocused: StateFlow<Boolean> get() = _searchFocused
|
||||
private val _searchFocused = MutableStateFlow(false)
|
||||
|
||||
init {
|
||||
// Ensure keys are loaded so that ROM metadata can be decrypted.
|
||||
|
@ -79,36 +79,36 @@ class GamesViewModel : ViewModel() {
|
|||
)
|
||||
)
|
||||
|
||||
_games.postValue(sortedList)
|
||||
_games.value = sortedList
|
||||
}
|
||||
|
||||
fun setSearchedGames(games: List<Game>) {
|
||||
_searchedGames.postValue(games)
|
||||
_searchedGames.value = games
|
||||
}
|
||||
|
||||
fun setShouldSwapData(shouldSwap: Boolean) {
|
||||
_shouldSwapData.postValue(shouldSwap)
|
||||
_shouldSwapData.value = shouldSwap
|
||||
}
|
||||
|
||||
fun setShouldScrollToTop(shouldScroll: Boolean) {
|
||||
_shouldScrollToTop.postValue(shouldScroll)
|
||||
_shouldScrollToTop.value = shouldScroll
|
||||
}
|
||||
|
||||
fun setSearchFocused(searchFocused: Boolean) {
|
||||
_searchFocused.postValue(searchFocused)
|
||||
_searchFocused.value = searchFocused
|
||||
}
|
||||
|
||||
fun reloadGames(directoryChanged: Boolean) {
|
||||
if (isReloading.value == true) {
|
||||
if (isReloading.value) {
|
||||
return
|
||||
}
|
||||
_isReloading.postValue(true)
|
||||
_isReloading.value = true
|
||||
|
||||
viewModelScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
NativeLibrary.resetRomMetadata()
|
||||
setGames(GameHelper.getGames())
|
||||
_isReloading.postValue(false)
|
||||
_isReloading.value = false
|
||||
|
||||
if (directoryChanged) {
|
||||
setShouldSwapData(true)
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
|
||||
package org.yuzu.yuzu_emu.model
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
data class HomeSetting(
|
||||
val titleId: Int,
|
||||
|
@ -14,5 +14,5 @@ data class HomeSetting(
|
|||
val isEnabled: () -> Boolean = { true },
|
||||
val disabledTitleId: Int = 0,
|
||||
val disabledMessageId: Int = 0,
|
||||
val details: LiveData<String> = MutableLiveData("")
|
||||
val details: StateFlow<String> = MutableStateFlow("")
|
||||
)
|
||||
|
|
|
@ -5,47 +5,43 @@ package org.yuzu.yuzu_emu.model
|
|||
|
||||
import android.net.Uri
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.preference.PreferenceManager
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.utils.GameHelper
|
||||
|
||||
class HomeViewModel : ViewModel() {
|
||||
private val _navigationVisible = MutableLiveData<Pair<Boolean, Boolean>>()
|
||||
val navigationVisible: LiveData<Pair<Boolean, Boolean>> get() = _navigationVisible
|
||||
val navigationVisible: StateFlow<Pair<Boolean, Boolean>> get() = _navigationVisible
|
||||
private val _navigationVisible = MutableStateFlow(Pair(false, false))
|
||||
|
||||
private val _statusBarShadeVisible = MutableLiveData(true)
|
||||
val statusBarShadeVisible: LiveData<Boolean> get() = _statusBarShadeVisible
|
||||
val statusBarShadeVisible: StateFlow<Boolean> get() = _statusBarShadeVisible
|
||||
private val _statusBarShadeVisible = MutableStateFlow(true)
|
||||
|
||||
private val _shouldPageForward = MutableLiveData(false)
|
||||
val shouldPageForward: LiveData<Boolean> get() = _shouldPageForward
|
||||
val shouldPageForward: StateFlow<Boolean> get() = _shouldPageForward
|
||||
private val _shouldPageForward = MutableStateFlow(false)
|
||||
|
||||
private val _gamesDir = MutableLiveData(
|
||||
val gamesDir: StateFlow<String> get() = _gamesDir
|
||||
private val _gamesDir = MutableStateFlow(
|
||||
Uri.parse(
|
||||
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||
.getString(GameHelper.KEY_GAME_PATH, "")
|
||||
).path ?: ""
|
||||
)
|
||||
val gamesDir: LiveData<String> get() = _gamesDir
|
||||
|
||||
var navigatedToSetup = false
|
||||
|
||||
init {
|
||||
_navigationVisible.value = Pair(false, false)
|
||||
}
|
||||
|
||||
fun setNavigationVisibility(visible: Boolean, animated: Boolean) {
|
||||
if (_navigationVisible.value?.first == visible) {
|
||||
if (navigationVisible.value.first == visible) {
|
||||
return
|
||||
}
|
||||
_navigationVisible.value = Pair(visible, animated)
|
||||
}
|
||||
|
||||
fun setStatusBarShadeVisibility(visible: Boolean) {
|
||||
if (_statusBarShadeVisible.value == visible) {
|
||||
if (statusBarShadeVisible.value == visible) {
|
||||
return
|
||||
}
|
||||
_statusBarShadeVisible.value = visible
|
||||
|
|
|
@ -3,48 +3,43 @@
|
|||
|
||||
package org.yuzu.yuzu_emu.model
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
|
||||
class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
|
||||
class SettingsViewModel : ViewModel() {
|
||||
var game: Game? = null
|
||||
|
||||
var shouldSave = false
|
||||
|
||||
var clickedItem: SettingsItem? = null
|
||||
|
||||
private val _toolbarTitle = MutableLiveData("")
|
||||
val toolbarTitle: LiveData<String> get() = _toolbarTitle
|
||||
val shouldRecreate: StateFlow<Boolean> get() = _shouldRecreate
|
||||
private val _shouldRecreate = MutableStateFlow(false)
|
||||
|
||||
private val _shouldRecreate = MutableLiveData(false)
|
||||
val shouldRecreate: LiveData<Boolean> get() = _shouldRecreate
|
||||
val shouldNavigateBack: StateFlow<Boolean> get() = _shouldNavigateBack
|
||||
private val _shouldNavigateBack = MutableStateFlow(false)
|
||||
|
||||
private val _shouldNavigateBack = MutableLiveData(false)
|
||||
val shouldNavigateBack: LiveData<Boolean> get() = _shouldNavigateBack
|
||||
val shouldShowResetSettingsDialog: StateFlow<Boolean> get() = _shouldShowResetSettingsDialog
|
||||
private val _shouldShowResetSettingsDialog = MutableStateFlow(false)
|
||||
|
||||
private val _shouldShowResetSettingsDialog = MutableLiveData(false)
|
||||
val shouldShowResetSettingsDialog: LiveData<Boolean> get() = _shouldShowResetSettingsDialog
|
||||
val shouldReloadSettingsList: StateFlow<Boolean> get() = _shouldReloadSettingsList
|
||||
private val _shouldReloadSettingsList = MutableStateFlow(false)
|
||||
|
||||
private val _shouldReloadSettingsList = MutableLiveData(false)
|
||||
val shouldReloadSettingsList: LiveData<Boolean> get() = _shouldReloadSettingsList
|
||||
val isUsingSearch: StateFlow<Boolean> get() = _isUsingSearch
|
||||
private val _isUsingSearch = MutableStateFlow(false)
|
||||
|
||||
private val _isUsingSearch = MutableLiveData(false)
|
||||
val isUsingSearch: LiveData<Boolean> get() = _isUsingSearch
|
||||
val sliderProgress: StateFlow<Int> get() = _sliderProgress
|
||||
private val _sliderProgress = MutableStateFlow(-1)
|
||||
|
||||
val sliderProgress = savedStateHandle.getStateFlow(KEY_SLIDER_PROGRESS, -1)
|
||||
val sliderTextValue: StateFlow<String> get() = _sliderTextValue
|
||||
private val _sliderTextValue = MutableStateFlow("")
|
||||
|
||||
val sliderTextValue = savedStateHandle.getStateFlow(KEY_SLIDER_TEXT_VALUE, "")
|
||||
|
||||
val adapterItemChanged = savedStateHandle.getStateFlow(KEY_ADAPTER_ITEM_CHANGED, -1)
|
||||
|
||||
fun setToolbarTitle(value: String) {
|
||||
_toolbarTitle.value = value
|
||||
}
|
||||
val adapterItemChanged: StateFlow<Int> get() = _adapterItemChanged
|
||||
private val _adapterItemChanged = MutableStateFlow(-1)
|
||||
|
||||
fun setShouldRecreate(value: Boolean) {
|
||||
_shouldRecreate.value = value
|
||||
|
@ -67,8 +62,8 @@ class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewMo
|
|||
}
|
||||
|
||||
fun setSliderTextValue(value: Float, units: String) {
|
||||
savedStateHandle[KEY_SLIDER_PROGRESS] = value
|
||||
savedStateHandle[KEY_SLIDER_TEXT_VALUE] = String.format(
|
||||
_sliderProgress.value = value.toInt()
|
||||
_sliderTextValue.value = String.format(
|
||||
YuzuApplication.appContext.getString(R.string.value_with_units),
|
||||
value.toInt().toString(),
|
||||
units
|
||||
|
@ -76,21 +71,15 @@ class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewMo
|
|||
}
|
||||
|
||||
fun setSliderProgress(value: Float) {
|
||||
savedStateHandle[KEY_SLIDER_PROGRESS] = value
|
||||
_sliderProgress.value = value.toInt()
|
||||
}
|
||||
|
||||
fun setAdapterItemChanged(value: Int) {
|
||||
savedStateHandle[KEY_ADAPTER_ITEM_CHANGED] = value
|
||||
_adapterItemChanged.value = value
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
game = null
|
||||
shouldSave = false
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val KEY_SLIDER_TEXT_VALUE = "SliderTextValue"
|
||||
const val KEY_SLIDER_PROGRESS = "SliderProgress"
|
||||
const val KEY_ADAPTER_ITEM_CHANGED = "AdapterItemChanged"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,29 +3,25 @@
|
|||
|
||||
package org.yuzu.yuzu_emu.model
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class TaskViewModel : ViewModel() {
|
||||
private val _result = MutableLiveData<Any>()
|
||||
val result: LiveData<Any> = _result
|
||||
val result: StateFlow<Any> get() = _result
|
||||
private val _result = MutableStateFlow(Any())
|
||||
|
||||
private val _isComplete = MutableLiveData<Boolean>()
|
||||
val isComplete: LiveData<Boolean> = _isComplete
|
||||
val isComplete: StateFlow<Boolean> get() = _isComplete
|
||||
private val _isComplete = MutableStateFlow(false)
|
||||
|
||||
private val _isRunning = MutableLiveData<Boolean>()
|
||||
val isRunning: LiveData<Boolean> = _isRunning
|
||||
val isRunning: StateFlow<Boolean> get() = _isRunning
|
||||
private val _isRunning = MutableStateFlow(false)
|
||||
|
||||
lateinit var task: () -> Any
|
||||
|
||||
init {
|
||||
clear()
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
_result.value = Any()
|
||||
_isComplete.value = false
|
||||
|
@ -33,15 +29,16 @@ class TaskViewModel : ViewModel() {
|
|||
}
|
||||
|
||||
fun runTask() {
|
||||
if (_isRunning.value == true) {
|
||||
if (isRunning.value) {
|
||||
return
|
||||
}
|
||||
_isRunning.value = true
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val res = task()
|
||||
_result.postValue(res)
|
||||
_isComplete.postValue(true)
|
||||
_result.value = res
|
||||
_isComplete.value = true
|
||||
_isRunning.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
package org.yuzu.yuzu_emu.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
|
@ -14,8 +15,12 @@ import androidx.core.view.WindowInsetsCompat
|
|||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.transition.MaterialFadeThrough
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.adapters.GameAdapter
|
||||
import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding
|
||||
|
@ -44,6 +49,8 @@ class GamesFragment : Fragment() {
|
|||
return binding.root
|
||||
}
|
||||
|
||||
// This is using the correct scope, lint is just acting up
|
||||
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
homeViewModel.setNavigationVisibility(visible = true, animated = false)
|
||||
|
||||
|
@ -80,37 +87,48 @@ class GamesFragment : Fragment() {
|
|||
if (_binding == null) {
|
||||
return@post
|
||||
}
|
||||
binding.swipeRefresh.isRefreshing = gamesViewModel.isReloading.value!!
|
||||
binding.swipeRefresh.isRefreshing = gamesViewModel.isReloading.value
|
||||
}
|
||||
}
|
||||
|
||||
gamesViewModel.apply {
|
||||
// Watch for when we get updates to any of our games lists
|
||||
isReloading.observe(viewLifecycleOwner) { isReloading ->
|
||||
binding.swipeRefresh.isRefreshing = isReloading
|
||||
}
|
||||
games.observe(viewLifecycleOwner) {
|
||||
(binding.gridGames.adapter as GameAdapter).submitList(it)
|
||||
if (it.isEmpty()) {
|
||||
binding.noticeText.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.noticeText.visibility = View.GONE
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
gamesViewModel.isReloading.collect { binding.swipeRefresh.isRefreshing = it }
|
||||
}
|
||||
}
|
||||
shouldSwapData.observe(viewLifecycleOwner) { shouldSwapData ->
|
||||
if (shouldSwapData) {
|
||||
(binding.gridGames.adapter as GameAdapter).submitList(
|
||||
gamesViewModel.games.value!!
|
||||
)
|
||||
gamesViewModel.setShouldSwapData(false)
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
gamesViewModel.games.collect {
|
||||
(binding.gridGames.adapter as GameAdapter).submitList(it)
|
||||
if (it.isEmpty()) {
|
||||
binding.noticeText.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.noticeText.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the user reselected the games menu item and then scroll to top of the list
|
||||
shouldScrollToTop.observe(viewLifecycleOwner) { shouldScroll ->
|
||||
if (shouldScroll) {
|
||||
scrollToTop()
|
||||
gamesViewModel.setShouldScrollToTop(false)
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
gamesViewModel.shouldSwapData.collect {
|
||||
if (it) {
|
||||
(binding.gridGames.adapter as GameAdapter).submitList(
|
||||
gamesViewModel.games.value
|
||||
)
|
||||
gamesViewModel.setShouldSwapData(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
gamesViewModel.shouldScrollToTop.collect {
|
||||
if (it) {
|
||||
scrollToTop()
|
||||
gamesViewModel.setShouldScrollToTop(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,9 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
|||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
|
@ -40,7 +42,6 @@ import org.yuzu.yuzu_emu.activities.EmulationActivity
|
|||
import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
|
||||
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||
import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
|
||||
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
|
||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||
|
@ -107,7 +108,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||
R.id.homeSettingsFragment -> {
|
||||
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
|
||||
null,
|
||||
SettingsFile.FILE_NAME_CONFIG
|
||||
Settings.MenuTag.SECTION_ROOT
|
||||
)
|
||||
navHostFragment.navController.navigate(action)
|
||||
}
|
||||
|
@ -115,16 +116,22 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||
}
|
||||
|
||||
// Prevents navigation from being drawn for a short time on recreation if set to hidden
|
||||
if (!homeViewModel.navigationVisible.value?.first!!) {
|
||||
if (!homeViewModel.navigationVisible.value.first) {
|
||||
binding.navigationView.visibility = View.INVISIBLE
|
||||
binding.statusBarShade.visibility = View.INVISIBLE
|
||||
}
|
||||
|
||||
homeViewModel.navigationVisible.observe(this) {
|
||||
showNavigation(it.first, it.second)
|
||||
}
|
||||
homeViewModel.statusBarShadeVisible.observe(this) { visible ->
|
||||
showStatusBarShade(visible)
|
||||
lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
homeViewModel.navigationVisible.collect { showNavigation(it.first, it.second) }
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
homeViewModel.statusBarShadeVisible.collect { showStatusBarShade(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dismiss previous notifications (should not happen unless a crash occurred)
|
||||
|
|
|
@ -6,9 +6,11 @@ package org.yuzu.yuzu_emu.utils
|
|||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.widget.ImageView
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.core.graphics.drawable.toDrawable
|
||||
import coil.ImageLoader
|
||||
import coil.decode.DataSource
|
||||
import coil.executeBlocking
|
||||
import coil.fetch.DrawableResult
|
||||
import coil.fetch.FetchResult
|
||||
import coil.fetch.Fetcher
|
||||
|
@ -74,4 +76,13 @@ object GameIconUtils {
|
|||
.build()
|
||||
imageLoader.enqueue(request)
|
||||
}
|
||||
|
||||
fun getGameIcon(game: Game): Bitmap {
|
||||
val request = ImageRequest.Builder(YuzuApplication.appContext)
|
||||
.data(game)
|
||||
.error(R.drawable.default_icon)
|
||||
.build()
|
||||
return imageLoader.executeBlocking(request)
|
||||
.drawable!!.toBitmap(config = Bitmap.Config.ARGB_8888)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -282,7 +282,7 @@ void Config::ReadValues() {
|
|||
std::stringstream ss(title_list);
|
||||
std::string line;
|
||||
while (std::getline(ss, line, '|')) {
|
||||
const auto title_id = std::stoul(line, nullptr, 16);
|
||||
const auto title_id = std::strtoul(line.c_str(), nullptr, 16);
|
||||
const auto disabled_list = config->Get("AddOns", "disabled_" + line, "");
|
||||
|
||||
std::stringstream inner_ss(disabled_list);
|
||||
|
|
|
@ -262,14 +262,12 @@ public:
|
|||
Core::SystemResultStatus InitializeEmulation(const std::string& filepath) {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
|
||||
// Loads the configuration.
|
||||
Config{};
|
||||
|
||||
// Create the render window.
|
||||
m_window = std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window,
|
||||
m_vulkan_library);
|
||||
|
||||
m_system.SetFilesystem(m_vfs);
|
||||
m_system.GetUserChannel().clear();
|
||||
|
||||
// Initialize system.
|
||||
jauto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>();
|
||||
|
@ -329,12 +327,13 @@ public:
|
|||
m_system.ShutdownMainProcess();
|
||||
m_detached_tasks.WaitForAllTasks();
|
||||
m_load_result = Core::SystemResultStatus::ErrorNotInitialized;
|
||||
m_window.reset();
|
||||
OnEmulationStopped(Core::SystemResultStatus::Success);
|
||||
return;
|
||||
}
|
||||
|
||||
// Tear down the render window.
|
||||
m_window.reset();
|
||||
|
||||
OnEmulationStopped(m_load_result);
|
||||
}
|
||||
|
||||
void PauseEmulation() {
|
||||
|
@ -671,18 +670,6 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused(JNIEnv* env, jclass claz
|
|||
return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused());
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_muteAduio(JNIEnv* env, jclass clazz) {
|
||||
Settings::values.audio_muted = true;
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_unmuteAudio(JNIEnv* env, jclass clazz) {
|
||||
Settings::values.audio_muted = false;
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isMuted(JNIEnv* env, jclass clazz) {
|
||||
return static_cast<jboolean>(Settings::values.audio_muted.GetValue());
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly(JNIEnv* env, jclass clazz) {
|
||||
return EmulationSession::GetInstance().IsHandheldOnly();
|
||||
}
|
||||
|
|
11
src/android/app/src/main/res/drawable/shortcut.xml
Normal file
11
src/android/app/src/main/res/drawable/shortcut.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item>
|
||||
<color android:color="@android:color/white" />
|
||||
</item>
|
||||
<item android:id="@+id/shortcut_foreground">
|
||||
<bitmap android:src="@drawable/default_icon" />
|
||||
</item>
|
||||
|
||||
</layer-list>
|
|
@ -27,7 +27,7 @@
|
|||
app:nullable="true" />
|
||||
<argument
|
||||
android:name="menuTag"
|
||||
app:argType="string" />
|
||||
app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" />
|
||||
</activity>
|
||||
|
||||
<action
|
||||
|
|
|
@ -82,7 +82,7 @@
|
|||
app:nullable="true" />
|
||||
<argument
|
||||
android:name="menuTag"
|
||||
app:argType="string" />
|
||||
app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" />
|
||||
</activity>
|
||||
|
||||
<action
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
android:label="SettingsFragment">
|
||||
<argument
|
||||
android:name="menuTag"
|
||||
app:argType="string" />
|
||||
app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" />
|
||||
<argument
|
||||
android:name="game"
|
||||
app:argType="org.yuzu.yuzu_emu.model.Game"
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
<dimen name="spacing_refresh_end">72dp</dimen>
|
||||
<dimen name="menu_width">256dp</dimen>
|
||||
<dimen name="card_width">165dp</dimen>
|
||||
<dimen name="icon_inset">24dp</dimen>
|
||||
|
||||
<dimen name="dialog_margin">20dp</dimen>
|
||||
<dimen name="elevated_app_bar">3dp</dimen>
|
||||
|
|
|
@ -10,6 +10,13 @@ add_library(audio_core STATIC
|
|||
adsp/apps/audio_renderer/command_buffer.h
|
||||
adsp/apps/audio_renderer/command_list_processor.cpp
|
||||
adsp/apps/audio_renderer/command_list_processor.h
|
||||
adsp/apps/opus/opus_decoder.cpp
|
||||
adsp/apps/opus/opus_decoder.h
|
||||
adsp/apps/opus/opus_decode_object.cpp
|
||||
adsp/apps/opus/opus_decode_object.h
|
||||
adsp/apps/opus/opus_multistream_decode_object.cpp
|
||||
adsp/apps/opus/opus_multistream_decode_object.h
|
||||
adsp/apps/opus/shared_memory.h
|
||||
audio_core.cpp
|
||||
audio_core.h
|
||||
audio_event.h
|
||||
|
@ -35,6 +42,13 @@ add_library(audio_core STATIC
|
|||
in/audio_in.h
|
||||
in/audio_in_system.cpp
|
||||
in/audio_in_system.h
|
||||
opus/hardware_opus.cpp
|
||||
opus/hardware_opus.h
|
||||
opus/decoder_manager.cpp
|
||||
opus/decoder_manager.h
|
||||
opus/decoder.cpp
|
||||
opus/decoder.h
|
||||
opus/parameters.h
|
||||
out/audio_out.cpp
|
||||
out/audio_out.h
|
||||
out/audio_out_system.cpp
|
||||
|
@ -214,7 +228,7 @@ else()
|
|||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(audio_core PUBLIC common core)
|
||||
target_link_libraries(audio_core PUBLIC common core Opus::opus)
|
||||
if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)
|
||||
target_link_libraries(audio_core PRIVATE dynarmic::dynarmic)
|
||||
endif()
|
||||
|
|
|
@ -7,12 +7,21 @@
|
|||
namespace AudioCore::ADSP {
|
||||
|
||||
ADSP::ADSP(Core::System& system, Sink::Sink& sink) {
|
||||
audio_renderer =
|
||||
std::make_unique<AudioRenderer::AudioRenderer>(system, system.ApplicationMemory(), sink);
|
||||
audio_renderer = std::make_unique<AudioRenderer::AudioRenderer>(system, sink);
|
||||
opus_decoder = std::make_unique<OpusDecoder::OpusDecoder>(system);
|
||||
opus_decoder->Send(Direction::DSP, OpusDecoder::Message::Start);
|
||||
if (opus_decoder->Receive(Direction::Host) != OpusDecoder::Message::StartOK) {
|
||||
LOG_ERROR(Service_Audio, "OpusDeocder failed to initialize.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
AudioRenderer::AudioRenderer& ADSP::AudioRenderer() {
|
||||
return *audio_renderer.get();
|
||||
}
|
||||
|
||||
OpusDecoder::OpusDecoder& ADSP::OpusDecoder() {
|
||||
return *opus_decoder.get();
|
||||
}
|
||||
|
||||
} // namespace AudioCore::ADSP
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "audio_core/adsp/apps/audio_renderer/audio_renderer.h"
|
||||
#include "audio_core/adsp/apps/opus/opus_decoder.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core {
|
||||
|
@ -40,10 +41,12 @@ public:
|
|||
~ADSP() = default;
|
||||
|
||||
AudioRenderer::AudioRenderer& AudioRenderer();
|
||||
OpusDecoder::OpusDecoder& OpusDecoder();
|
||||
|
||||
private:
|
||||
/// AudioRenderer app
|
||||
std::unique_ptr<AudioRenderer::AudioRenderer> audio_renderer{};
|
||||
std::unique_ptr<OpusDecoder::OpusDecoder> opus_decoder{};
|
||||
};
|
||||
|
||||
} // namespace ADSP
|
||||
|
|
|
@ -14,13 +14,12 @@
|
|||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
|
||||
MICROPROFILE_DEFINE(Audio_Renderer, "Audio", "DSP", MP_RGB(60, 19, 97));
|
||||
MICROPROFILE_DEFINE(Audio_Renderer, "Audio", "DSP_AudioRenderer", MP_RGB(60, 19, 97));
|
||||
|
||||
namespace AudioCore::ADSP::AudioRenderer {
|
||||
|
||||
AudioRenderer::AudioRenderer(Core::System& system_, Core::Memory::Memory& memory_,
|
||||
Sink::Sink& sink_)
|
||||
: system{system_}, memory{memory_}, sink{sink_} {}
|
||||
AudioRenderer::AudioRenderer(Core::System& system_, Sink::Sink& sink_)
|
||||
: system{system_}, sink{sink_} {}
|
||||
|
||||
AudioRenderer::~AudioRenderer() {
|
||||
Stop();
|
||||
|
@ -33,8 +32,8 @@ void AudioRenderer::Start() {
|
|||
|
||||
main_thread = std::jthread([this](std::stop_token stop_token) { Main(stop_token); });
|
||||
|
||||
mailbox.Send(Direction::DSP, {Message::InitializeOK, {}});
|
||||
if (mailbox.Receive(Direction::Host).msg != Message::InitializeOK) {
|
||||
mailbox.Send(Direction::DSP, Message::InitializeOK);
|
||||
if (mailbox.Receive(Direction::Host) != Message::InitializeOK) {
|
||||
LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown "
|
||||
"message response from ADSP!");
|
||||
return;
|
||||
|
@ -47,8 +46,8 @@ void AudioRenderer::Stop() {
|
|||
return;
|
||||
}
|
||||
|
||||
mailbox.Send(Direction::DSP, {Message::Shutdown, {}});
|
||||
if (mailbox.Receive(Direction::Host).msg != Message::Shutdown) {
|
||||
mailbox.Send(Direction::DSP, Message::Shutdown);
|
||||
if (mailbox.Receive(Direction::Host) != Message::Shutdown) {
|
||||
LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown "
|
||||
"message response from ADSP!");
|
||||
}
|
||||
|
@ -67,29 +66,34 @@ void AudioRenderer::Stop() {
|
|||
|
||||
void AudioRenderer::Signal() {
|
||||
signalled_tick = system.CoreTiming().GetGlobalTimeNs().count();
|
||||
Send(Direction::DSP, {Message::Render, {}});
|
||||
Send(Direction::DSP, Message::Render);
|
||||
}
|
||||
|
||||
void AudioRenderer::Wait() {
|
||||
auto received = Receive(Direction::Host);
|
||||
if (received.msg != Message::RenderResponse) {
|
||||
auto msg = Receive(Direction::Host);
|
||||
if (msg != Message::RenderResponse) {
|
||||
LOG_ERROR(Service_Audio,
|
||||
"Did not receive the expected render response from the AudioRenderer! Expected "
|
||||
"{}, got {}",
|
||||
Message::RenderResponse, received.msg);
|
||||
Message::RenderResponse, msg);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioRenderer::Send(Direction dir, MailboxMessage message) {
|
||||
void AudioRenderer::Send(Direction dir, u32 message) {
|
||||
mailbox.Send(dir, std::move(message));
|
||||
}
|
||||
|
||||
MailboxMessage AudioRenderer::Receive(Direction dir, bool block) {
|
||||
return mailbox.Receive(dir, block);
|
||||
u32 AudioRenderer::Receive(Direction dir) {
|
||||
return mailbox.Receive(dir);
|
||||
}
|
||||
|
||||
void AudioRenderer::SetCommandBuffer(s32 session_id, CommandBuffer& buffer) noexcept {
|
||||
command_buffers[session_id] = buffer;
|
||||
void AudioRenderer::SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u64 time_limit,
|
||||
u64 applet_resource_user_id, bool reset) noexcept {
|
||||
command_buffers[session_id].buffer = buffer;
|
||||
command_buffers[session_id].size = size;
|
||||
command_buffers[session_id].time_limit = time_limit;
|
||||
command_buffers[session_id].applet_resource_user_id = applet_resource_user_id;
|
||||
command_buffers[session_id].reset_buffer = reset;
|
||||
}
|
||||
|
||||
u32 AudioRenderer::GetRemainCommandCount(s32 session_id) const noexcept {
|
||||
|
@ -115,7 +119,7 @@ void AudioRenderer::CreateSinkStreams() {
|
|||
}
|
||||
|
||||
void AudioRenderer::Main(std::stop_token stop_token) {
|
||||
static constexpr char name[]{"AudioRenderer"};
|
||||
static constexpr char name[]{"DSP_AudioRenderer_Main"};
|
||||
MicroProfileOnThreadCreate(name);
|
||||
Common::SetCurrentThreadName(name);
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
|
||||
|
@ -123,28 +127,28 @@ void AudioRenderer::Main(std::stop_token stop_token) {
|
|||
// TODO: Create buffer map/unmap thread + mailbox
|
||||
// TODO: Create gMix devices, initialize them here
|
||||
|
||||
if (mailbox.Receive(Direction::DSP).msg != Message::InitializeOK) {
|
||||
if (mailbox.Receive(Direction::DSP) != Message::InitializeOK) {
|
||||
LOG_ERROR(Service_Audio,
|
||||
"ADSP Audio Renderer -- Failed to receive initialize message from host!");
|
||||
return;
|
||||
}
|
||||
|
||||
mailbox.Send(Direction::Host, {Message::InitializeOK, {}});
|
||||
mailbox.Send(Direction::Host, Message::InitializeOK);
|
||||
|
||||
// 0.12 seconds (2,304,000 / 19,200,000)
|
||||
constexpr u64 max_process_time{2'304'000ULL};
|
||||
|
||||
while (!stop_token.stop_requested()) {
|
||||
auto received{mailbox.Receive(Direction::DSP)};
|
||||
switch (received.msg) {
|
||||
auto msg{mailbox.Receive(Direction::DSP)};
|
||||
switch (msg) {
|
||||
case Message::Shutdown:
|
||||
mailbox.Send(Direction::Host, {Message::Shutdown, {}});
|
||||
mailbox.Send(Direction::Host, Message::Shutdown);
|
||||
return;
|
||||
|
||||
case Message::Render: {
|
||||
if (system.IsShuttingDown()) [[unlikely]] {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
mailbox.Send(Direction::Host, {Message::RenderResponse, {}});
|
||||
mailbox.Send(Direction::Host, Message::RenderResponse);
|
||||
continue;
|
||||
}
|
||||
std::array<bool, MaxRendererSessions> buffers_reset{};
|
||||
|
@ -200,13 +204,12 @@ void AudioRenderer::Main(std::stop_token stop_token) {
|
|||
}
|
||||
}
|
||||
|
||||
mailbox.Send(Direction::Host, {Message::RenderResponse, {}});
|
||||
mailbox.Send(Direction::Host, Message::RenderResponse);
|
||||
} break;
|
||||
|
||||
default:
|
||||
LOG_WARNING(Service_Audio,
|
||||
"ADSP AudioRenderer received an invalid message, msg={:02X}!",
|
||||
received.msg);
|
||||
"ADSP AudioRenderer received an invalid message, msg={:02X}!", msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,13 +17,6 @@
|
|||
|
||||
namespace Core {
|
||||
class System;
|
||||
namespace Timing {
|
||||
struct EventType;
|
||||
}
|
||||
namespace Memory {
|
||||
class Memory;
|
||||
}
|
||||
class System;
|
||||
} // namespace Core
|
||||
|
||||
namespace AudioCore {
|
||||
|
@ -34,19 +27,19 @@ class Sink;
|
|||
namespace ADSP::AudioRenderer {
|
||||
|
||||
enum Message : u32 {
|
||||
Invalid = 0x00,
|
||||
MapUnmap_Map = 0x01,
|
||||
MapUnmap_MapResponse = 0x02,
|
||||
MapUnmap_Unmap = 0x03,
|
||||
MapUnmap_UnmapResponse = 0x04,
|
||||
MapUnmap_InvalidateCache = 0x05,
|
||||
MapUnmap_InvalidateCacheResponse = 0x06,
|
||||
MapUnmap_Shutdown = 0x07,
|
||||
MapUnmap_ShutdownResponse = 0x08,
|
||||
InitializeOK = 0x16,
|
||||
RenderResponse = 0x20,
|
||||
Render = 0x2A,
|
||||
Shutdown = 0x34,
|
||||
Invalid = 0,
|
||||
MapUnmap_Map = 1,
|
||||
MapUnmap_MapResponse = 2,
|
||||
MapUnmap_Unmap = 3,
|
||||
MapUnmap_UnmapResponse = 4,
|
||||
MapUnmap_InvalidateCache = 5,
|
||||
MapUnmap_InvalidateCacheResponse = 6,
|
||||
MapUnmap_Shutdown = 7,
|
||||
MapUnmap_ShutdownResponse = 8,
|
||||
InitializeOK = 22,
|
||||
RenderResponse = 32,
|
||||
Render = 42,
|
||||
Shutdown = 52,
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -54,7 +47,7 @@ enum Message : u32 {
|
|||
*/
|
||||
class AudioRenderer {
|
||||
public:
|
||||
explicit AudioRenderer(Core::System& system, Core::Memory::Memory& memory, Sink::Sink& sink);
|
||||
explicit AudioRenderer(Core::System& system, Sink::Sink& sink);
|
||||
~AudioRenderer();
|
||||
|
||||
/**
|
||||
|
@ -72,10 +65,11 @@ public:
|
|||
void Signal();
|
||||
void Wait();
|
||||
|
||||
void Send(Direction dir, MailboxMessage message);
|
||||
MailboxMessage Receive(Direction dir, bool block = true);
|
||||
void Send(Direction dir, u32 message);
|
||||
u32 Receive(Direction dir);
|
||||
|
||||
void SetCommandBuffer(s32 session_id, CommandBuffer& buffer) noexcept;
|
||||
void SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u64 time_limit,
|
||||
u64 applet_resource_user_id, bool reset) noexcept;
|
||||
u32 GetRemainCommandCount(s32 session_id) const noexcept;
|
||||
void ClearRemainCommandCount(s32 session_id) noexcept;
|
||||
u64 GetRenderingStartTick(s32 session_id) const noexcept;
|
||||
|
@ -93,9 +87,7 @@ private:
|
|||
|
||||
/// Core system
|
||||
Core::System& system;
|
||||
/// Memory
|
||||
Core::Memory::Memory& memory;
|
||||
/// The output sink the AudioRenderer will use
|
||||
/// The output sink the AudioRenderer will send samples to
|
||||
Sink::Sink& sink;
|
||||
/// The active mailbox
|
||||
Mailbox mailbox;
|
||||
|
@ -103,11 +95,13 @@ private:
|
|||
std::jthread main_thread{};
|
||||
/// The current state
|
||||
std::atomic<bool> running{};
|
||||
/// Shared memory of input command buffers, set by host, read by DSP
|
||||
std::array<CommandBuffer, MaxRendererSessions> command_buffers{};
|
||||
/// The command lists to process
|
||||
std::array<CommandListProcessor, MaxRendererSessions> command_list_processors{};
|
||||
/// The streams which will receive the processed samples
|
||||
std::array<Sink::SinkStream*, MaxRendererSessions> streams{};
|
||||
/// CPU Tick when the DSP was signalled to process, uses time rather than tick
|
||||
u64 signalled_tick{0};
|
||||
};
|
||||
|
||||
|
|
|
@ -37,11 +37,6 @@ u32 CommandListProcessor::GetRemainingCommandCount() const {
|
|||
return command_count - processed_command_count;
|
||||
}
|
||||
|
||||
void CommandListProcessor::SetBuffer(const CpuAddr buffer, const u64 size) {
|
||||
commands = reinterpret_cast<u8*>(buffer + sizeof(Renderer::CommandListHeader));
|
||||
commands_buffer_size = size;
|
||||
}
|
||||
|
||||
Sink::SinkStream* CommandListProcessor::GetOutputSinkStream() const {
|
||||
return stream;
|
||||
}
|
||||
|
|
|
@ -56,14 +56,6 @@ public:
|
|||
*/
|
||||
u32 GetRemainingCommandCount() const;
|
||||
|
||||
/**
|
||||
* Set the command buffer.
|
||||
*
|
||||
* @param buffer - The buffer to use.
|
||||
* @param size - The size of the buffer.
|
||||
*/
|
||||
void SetBuffer(CpuAddr buffer, u64 size);
|
||||
|
||||
/**
|
||||
* Get the stream for this command list.
|
||||
*
|
||||
|
|
107
src/audio_core/adsp/apps/opus/opus_decode_object.cpp
Normal file
107
src/audio_core/adsp/apps/opus/opus_decode_object.cpp
Normal file
|
@ -0,0 +1,107 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/adsp/apps/opus/opus_decode_object.h"
|
||||
#include "common/assert.h"
|
||||
|
||||
namespace AudioCore::ADSP::OpusDecoder {
|
||||
namespace {
|
||||
bool IsValidChannelCount(u32 channel_count) {
|
||||
return channel_count == 1 || channel_count == 2;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
u32 OpusDecodeObject::GetWorkBufferSize(u32 channel_count) {
|
||||
if (!IsValidChannelCount(channel_count)) {
|
||||
return 0;
|
||||
}
|
||||
return static_cast<u32>(sizeof(OpusDecodeObject)) + opus_decoder_get_size(channel_count);
|
||||
}
|
||||
|
||||
OpusDecodeObject& OpusDecodeObject::Initialize(u64 buffer, u64 buffer2) {
|
||||
auto* new_decoder = reinterpret_cast<OpusDecodeObject*>(buffer);
|
||||
auto* comparison = reinterpret_cast<OpusDecodeObject*>(buffer2);
|
||||
|
||||
if (new_decoder->magic == DecodeObjectMagic) {
|
||||
if (!new_decoder->initialized ||
|
||||
(new_decoder->initialized && new_decoder->self == comparison)) {
|
||||
new_decoder->state_valid = true;
|
||||
}
|
||||
} else {
|
||||
new_decoder->initialized = false;
|
||||
new_decoder->state_valid = true;
|
||||
}
|
||||
return *new_decoder;
|
||||
}
|
||||
|
||||
s32 OpusDecodeObject::InitializeDecoder(u32 sample_rate, u32 channel_count) {
|
||||
if (!state_valid) {
|
||||
return OPUS_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (initialized) {
|
||||
return OPUS_OK;
|
||||
}
|
||||
|
||||
// Unfortunately libopus does not expose the OpusDecoder struct publicly, so we can't include
|
||||
// it in this class. Nintendo does not allocate memory, which is why we have a workbuffer
|
||||
// provided.
|
||||
// We could use _create and have libopus allocate it for us, but then we have to separately
|
||||
// track which decoder is being used between this and multistream in order to call the correct
|
||||
// destroy from the host side.
|
||||
// This is a bit cringe, but is safe as these objects are only ever initialized inside the given
|
||||
// workbuffer, and GetWorkBufferSize will guarantee there's enough space to follow.
|
||||
decoder = (LibOpusDecoder*)(this + 1);
|
||||
s32 ret = opus_decoder_init(decoder, sample_rate, channel_count);
|
||||
if (ret == OPUS_OK) {
|
||||
magic = DecodeObjectMagic;
|
||||
initialized = true;
|
||||
state_valid = true;
|
||||
self = this;
|
||||
final_range = 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
s32 OpusDecodeObject::Shutdown() {
|
||||
if (!state_valid) {
|
||||
return OPUS_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (initialized) {
|
||||
magic = 0x0;
|
||||
initialized = false;
|
||||
state_valid = false;
|
||||
self = nullptr;
|
||||
final_range = 0;
|
||||
decoder = nullptr;
|
||||
}
|
||||
return OPUS_OK;
|
||||
}
|
||||
|
||||
s32 OpusDecodeObject::ResetDecoder() {
|
||||
return opus_decoder_ctl(decoder, OPUS_RESET_STATE);
|
||||
}
|
||||
|
||||
s32 OpusDecodeObject::Decode(u32& out_sample_count, u64 output_data, u64 output_data_size,
|
||||
u64 input_data, u64 input_data_size) {
|
||||
ASSERT(initialized);
|
||||
out_sample_count = 0;
|
||||
|
||||
if (!state_valid) {
|
||||
return OPUS_INVALID_STATE;
|
||||
}
|
||||
|
||||
auto ret_code_or_samples = opus_decode(
|
||||
decoder, reinterpret_cast<const u8*>(input_data), static_cast<opus_int32>(input_data_size),
|
||||
reinterpret_cast<opus_int16*>(output_data), static_cast<opus_int32>(output_data_size), 0);
|
||||
|
||||
if (ret_code_or_samples < OPUS_OK) {
|
||||
return ret_code_or_samples;
|
||||
}
|
||||
|
||||
out_sample_count = ret_code_or_samples;
|
||||
return opus_decoder_ctl(decoder, OPUS_GET_FINAL_RANGE_REQUEST, &final_range);
|
||||
}
|
||||
|
||||
} // namespace AudioCore::ADSP::OpusDecoder
|
38
src/audio_core/adsp/apps/opus/opus_decode_object.h
Normal file
38
src/audio_core/adsp/apps/opus/opus_decode_object.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <opus.h>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::ADSP::OpusDecoder {
|
||||
using LibOpusDecoder = ::OpusDecoder;
|
||||
static constexpr u32 DecodeObjectMagic = 0xDEADBEEF;
|
||||
|
||||
class OpusDecodeObject {
|
||||
public:
|
||||
static u32 GetWorkBufferSize(u32 channel_count);
|
||||
static OpusDecodeObject& Initialize(u64 buffer, u64 buffer2);
|
||||
|
||||
s32 InitializeDecoder(u32 sample_rate, u32 channel_count);
|
||||
s32 Shutdown();
|
||||
s32 ResetDecoder();
|
||||
s32 Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data,
|
||||
u64 input_data_size);
|
||||
u32 GetFinalRange() const noexcept {
|
||||
return final_range;
|
||||
}
|
||||
|
||||
private:
|
||||
u32 magic;
|
||||
bool initialized;
|
||||
bool state_valid;
|
||||
OpusDecodeObject* self;
|
||||
u32 final_range;
|
||||
LibOpusDecoder* decoder;
|
||||
};
|
||||
static_assert(std::is_trivially_constructible_v<OpusDecodeObject>);
|
||||
|
||||
} // namespace AudioCore::ADSP::OpusDecoder
|
269
src/audio_core/adsp/apps/opus/opus_decoder.cpp
Normal file
269
src/audio_core/adsp/apps/opus/opus_decoder.cpp
Normal file
|
@ -0,0 +1,269 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
|
||||
#include "audio_core/adsp/apps/opus/opus_decode_object.h"
|
||||
#include "audio_core/adsp/apps/opus/opus_multistream_decode_object.h"
|
||||
#include "audio_core/adsp/apps/opus/shared_memory.h"
|
||||
#include "audio_core/audio_core.h"
|
||||
#include "audio_core/common/common.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
|
||||
MICROPROFILE_DEFINE(OpusDecoder, "Audio", "DSP_OpusDecoder", MP_RGB(60, 19, 97));
|
||||
|
||||
namespace AudioCore::ADSP::OpusDecoder {
|
||||
|
||||
namespace {
|
||||
constexpr size_t OpusStreamCountMax = 255;
|
||||
|
||||
bool IsValidChannelCount(u32 channel_count) {
|
||||
return channel_count == 1 || channel_count == 2;
|
||||
}
|
||||
|
||||
bool IsValidMultiStreamChannelCount(u32 channel_count) {
|
||||
return channel_count <= OpusStreamCountMax;
|
||||
}
|
||||
|
||||
bool IsValidMultiStreamStreamCounts(s32 total_stream_count, s32 sterero_stream_count) {
|
||||
return IsValidMultiStreamChannelCount(total_stream_count) && total_stream_count > 0 &&
|
||||
sterero_stream_count > 0 && sterero_stream_count <= total_stream_count;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
OpusDecoder::OpusDecoder(Core::System& system_) : system{system_} {
|
||||
init_thread = std::jthread([this](std::stop_token stop_token) { Init(stop_token); });
|
||||
}
|
||||
|
||||
OpusDecoder::~OpusDecoder() {
|
||||
if (!running) {
|
||||
init_thread.request_stop();
|
||||
return;
|
||||
}
|
||||
|
||||
// Shutdown the thread
|
||||
Send(Direction::DSP, Message::Shutdown);
|
||||
auto msg = Receive(Direction::Host);
|
||||
ASSERT_MSG(msg == Message::ShutdownOK, "Expected Opus shutdown code {}, got {}",
|
||||
Message::ShutdownOK, msg);
|
||||
main_thread.request_stop();
|
||||
main_thread.join();
|
||||
running = false;
|
||||
}
|
||||
|
||||
void OpusDecoder::Send(Direction dir, u32 message) {
|
||||
mailbox.Send(dir, std::move(message));
|
||||
}
|
||||
|
||||
u32 OpusDecoder::Receive(Direction dir, std::stop_token stop_token) {
|
||||
return mailbox.Receive(dir, stop_token);
|
||||
}
|
||||
|
||||
void OpusDecoder::Init(std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName("DSP_OpusDecoder_Init");
|
||||
|
||||
if (Receive(Direction::DSP, stop_token) != Message::Start) {
|
||||
LOG_ERROR(Service_Audio,
|
||||
"DSP OpusDecoder failed to receive Start message. Opus initialization failed.");
|
||||
return;
|
||||
}
|
||||
main_thread = std::jthread([this](std::stop_token st) { Main(st); });
|
||||
running = true;
|
||||
Send(Direction::Host, Message::StartOK);
|
||||
}
|
||||
|
||||
void OpusDecoder::Main(std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName("DSP_OpusDecoder_Main");
|
||||
|
||||
while (!stop_token.stop_requested()) {
|
||||
auto msg = Receive(Direction::DSP, stop_token);
|
||||
switch (msg) {
|
||||
case Shutdown:
|
||||
Send(Direction::Host, Message::ShutdownOK);
|
||||
return;
|
||||
|
||||
case GetWorkBufferSize: {
|
||||
auto channel_count = static_cast<s32>(shared_memory->host_send_data[0]);
|
||||
|
||||
ASSERT(IsValidChannelCount(channel_count));
|
||||
|
||||
shared_memory->dsp_return_data[0] = OpusDecodeObject::GetWorkBufferSize(channel_count);
|
||||
Send(Direction::Host, Message::GetWorkBufferSizeOK);
|
||||
} break;
|
||||
|
||||
case InitializeDecodeObject: {
|
||||
auto buffer = shared_memory->host_send_data[0];
|
||||
auto buffer_size = shared_memory->host_send_data[1];
|
||||
auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]);
|
||||
auto channel_count = static_cast<s32>(shared_memory->host_send_data[3]);
|
||||
|
||||
ASSERT(sample_rate >= 0);
|
||||
ASSERT(IsValidChannelCount(channel_count));
|
||||
ASSERT(buffer_size >= OpusDecodeObject::GetWorkBufferSize(channel_count));
|
||||
|
||||
auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer);
|
||||
shared_memory->dsp_return_data[0] =
|
||||
decoder_object.InitializeDecoder(sample_rate, channel_count);
|
||||
|
||||
Send(Direction::Host, Message::InitializeDecodeObjectOK);
|
||||
} break;
|
||||
|
||||
case ShutdownDecodeObject: {
|
||||
auto buffer = shared_memory->host_send_data[0];
|
||||
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
||||
|
||||
auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer);
|
||||
shared_memory->dsp_return_data[0] = decoder_object.Shutdown();
|
||||
|
||||
Send(Direction::Host, Message::ShutdownDecodeObjectOK);
|
||||
} break;
|
||||
|
||||
case DecodeInterleaved: {
|
||||
auto start_time = system.CoreTiming().GetGlobalTimeUs();
|
||||
|
||||
auto buffer = shared_memory->host_send_data[0];
|
||||
auto input_data = shared_memory->host_send_data[1];
|
||||
auto input_data_size = shared_memory->host_send_data[2];
|
||||
auto output_data = shared_memory->host_send_data[3];
|
||||
auto output_data_size = shared_memory->host_send_data[4];
|
||||
auto final_range = static_cast<u32>(shared_memory->host_send_data[5]);
|
||||
auto reset_requested = shared_memory->host_send_data[6];
|
||||
|
||||
u32 decoded_samples{0};
|
||||
|
||||
auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer);
|
||||
s32 error_code{OPUS_OK};
|
||||
if (reset_requested) {
|
||||
error_code = decoder_object.ResetDecoder();
|
||||
}
|
||||
|
||||
if (error_code == OPUS_OK) {
|
||||
error_code = decoder_object.Decode(decoded_samples, output_data, output_data_size,
|
||||
input_data, input_data_size);
|
||||
}
|
||||
|
||||
if (error_code == OPUS_OK) {
|
||||
if (final_range && decoder_object.GetFinalRange() != final_range) {
|
||||
error_code = OPUS_INVALID_PACKET;
|
||||
}
|
||||
}
|
||||
|
||||
auto end_time = system.CoreTiming().GetGlobalTimeUs();
|
||||
shared_memory->dsp_return_data[0] = error_code;
|
||||
shared_memory->dsp_return_data[1] = decoded_samples;
|
||||
shared_memory->dsp_return_data[2] = (end_time - start_time).count();
|
||||
|
||||
Send(Direction::Host, Message::DecodeInterleavedOK);
|
||||
} break;
|
||||
|
||||
case MapMemory: {
|
||||
[[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
|
||||
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
||||
Send(Direction::Host, Message::MapMemoryOK);
|
||||
} break;
|
||||
|
||||
case UnmapMemory: {
|
||||
[[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
|
||||
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
||||
Send(Direction::Host, Message::UnmapMemoryOK);
|
||||
} break;
|
||||
|
||||
case GetWorkBufferSizeForMultiStream: {
|
||||
auto total_stream_count = static_cast<s32>(shared_memory->host_send_data[0]);
|
||||
auto stereo_stream_count = static_cast<s32>(shared_memory->host_send_data[1]);
|
||||
|
||||
ASSERT(IsValidMultiStreamStreamCounts(total_stream_count, stereo_stream_count));
|
||||
|
||||
shared_memory->dsp_return_data[0] = OpusMultiStreamDecodeObject::GetWorkBufferSize(
|
||||
total_stream_count, stereo_stream_count);
|
||||
Send(Direction::Host, Message::GetWorkBufferSizeForMultiStreamOK);
|
||||
} break;
|
||||
|
||||
case InitializeMultiStreamDecodeObject: {
|
||||
auto buffer = shared_memory->host_send_data[0];
|
||||
auto buffer_size = shared_memory->host_send_data[1];
|
||||
auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]);
|
||||
auto channel_count = static_cast<s32>(shared_memory->host_send_data[3]);
|
||||
auto total_stream_count = static_cast<s32>(shared_memory->host_send_data[4]);
|
||||
auto stereo_stream_count = static_cast<s32>(shared_memory->host_send_data[5]);
|
||||
// Nintendo seem to have a bug here, they try to use &host_send_data[6] for the channel
|
||||
// mappings, but [6] is never set, and there is not enough room in the argument data for
|
||||
// more than 40 channels, when 255 are possible.
|
||||
// It also means the mapping values are undefined, though likely always 0,
|
||||
// and the mappings given by the game are ignored. The mappings are copied to this
|
||||
// dedicated buffer host side, so let's do as intended.
|
||||
auto mappings = shared_memory->channel_mapping.data();
|
||||
|
||||
ASSERT(IsValidMultiStreamStreamCounts(total_stream_count, stereo_stream_count));
|
||||
ASSERT(sample_rate >= 0);
|
||||
ASSERT(buffer_size >= OpusMultiStreamDecodeObject::GetWorkBufferSize(
|
||||
total_stream_count, stereo_stream_count));
|
||||
|
||||
auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer);
|
||||
shared_memory->dsp_return_data[0] = decoder_object.InitializeDecoder(
|
||||
sample_rate, total_stream_count, channel_count, stereo_stream_count, mappings);
|
||||
|
||||
Send(Direction::Host, Message::InitializeMultiStreamDecodeObjectOK);
|
||||
} break;
|
||||
|
||||
case ShutdownMultiStreamDecodeObject: {
|
||||
auto buffer = shared_memory->host_send_data[0];
|
||||
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
||||
|
||||
auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer);
|
||||
shared_memory->dsp_return_data[0] = decoder_object.Shutdown();
|
||||
|
||||
Send(Direction::Host, Message::ShutdownMultiStreamDecodeObjectOK);
|
||||
} break;
|
||||
|
||||
case DecodeInterleavedForMultiStream: {
|
||||
auto start_time = system.CoreTiming().GetGlobalTimeUs();
|
||||
|
||||
auto buffer = shared_memory->host_send_data[0];
|
||||
auto input_data = shared_memory->host_send_data[1];
|
||||
auto input_data_size = shared_memory->host_send_data[2];
|
||||
auto output_data = shared_memory->host_send_data[3];
|
||||
auto output_data_size = shared_memory->host_send_data[4];
|
||||
auto final_range = static_cast<u32>(shared_memory->host_send_data[5]);
|
||||
auto reset_requested = shared_memory->host_send_data[6];
|
||||
|
||||
u32 decoded_samples{0};
|
||||
|
||||
auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer);
|
||||
s32 error_code{OPUS_OK};
|
||||
if (reset_requested) {
|
||||
error_code = decoder_object.ResetDecoder();
|
||||
}
|
||||
|
||||
if (error_code == OPUS_OK) {
|
||||
error_code = decoder_object.Decode(decoded_samples, output_data, output_data_size,
|
||||
input_data, input_data_size);
|
||||
}
|
||||
|
||||
if (error_code == OPUS_OK) {
|
||||
if (final_range && decoder_object.GetFinalRange() != final_range) {
|
||||
error_code = OPUS_INVALID_PACKET;
|
||||
}
|
||||
}
|
||||
|
||||
auto end_time = system.CoreTiming().GetGlobalTimeUs();
|
||||
shared_memory->dsp_return_data[0] = error_code;
|
||||
shared_memory->dsp_return_data[1] = decoded_samples;
|
||||
shared_memory->dsp_return_data[2] = (end_time - start_time).count();
|
||||
|
||||
Send(Direction::Host, Message::DecodeInterleavedForMultiStreamOK);
|
||||
} break;
|
||||
|
||||
default:
|
||||
LOG_ERROR(Service_Audio, "Invalid OpusDecoder command {}", msg);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace AudioCore::ADSP::OpusDecoder
|
92
src/audio_core/adsp/apps/opus/opus_decoder.h
Normal file
92
src/audio_core/adsp/apps/opus/opus_decoder.h
Normal file
|
@ -0,0 +1,92 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
#include "audio_core/adsp/apps/opus/shared_memory.h"
|
||||
#include "audio_core/adsp/mailbox.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
} // namespace Core
|
||||
|
||||
namespace AudioCore::ADSP::OpusDecoder {
|
||||
|
||||
enum Message : u32 {
|
||||
Invalid = 0,
|
||||
Start = 1,
|
||||
Shutdown = 2,
|
||||
StartOK = 11,
|
||||
ShutdownOK = 12,
|
||||
GetWorkBufferSize = 21,
|
||||
InitializeDecodeObject = 22,
|
||||
ShutdownDecodeObject = 23,
|
||||
DecodeInterleaved = 24,
|
||||
MapMemory = 25,
|
||||
UnmapMemory = 26,
|
||||
GetWorkBufferSizeForMultiStream = 27,
|
||||
InitializeMultiStreamDecodeObject = 28,
|
||||
ShutdownMultiStreamDecodeObject = 29,
|
||||
DecodeInterleavedForMultiStream = 30,
|
||||
|
||||
GetWorkBufferSizeOK = 41,
|
||||
InitializeDecodeObjectOK = 42,
|
||||
ShutdownDecodeObjectOK = 43,
|
||||
DecodeInterleavedOK = 44,
|
||||
MapMemoryOK = 45,
|
||||
UnmapMemoryOK = 46,
|
||||
GetWorkBufferSizeForMultiStreamOK = 47,
|
||||
InitializeMultiStreamDecodeObjectOK = 48,
|
||||
ShutdownMultiStreamDecodeObjectOK = 49,
|
||||
DecodeInterleavedForMultiStreamOK = 50,
|
||||
};
|
||||
|
||||
/**
|
||||
* The AudioRenderer application running on the ADSP.
|
||||
*/
|
||||
class OpusDecoder {
|
||||
public:
|
||||
explicit OpusDecoder(Core::System& system);
|
||||
~OpusDecoder();
|
||||
|
||||
bool IsRunning() const noexcept {
|
||||
return running;
|
||||
}
|
||||
|
||||
void Send(Direction dir, u32 message);
|
||||
u32 Receive(Direction dir, std::stop_token stop_token = {});
|
||||
|
||||
void SetSharedMemory(SharedMemory& shared_memory_) {
|
||||
shared_memory = &shared_memory_;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* Initializing thread, launched at audio_core boot to avoid blocking the main emu boot thread.
|
||||
*/
|
||||
void Init(std::stop_token stop_token);
|
||||
/**
|
||||
* Main OpusDecoder thread, responsible for processing the incoming Opus packets.
|
||||
*/
|
||||
void Main(std::stop_token stop_token);
|
||||
|
||||
/// Core system
|
||||
Core::System& system;
|
||||
/// Mailbox to communicate messages with the host, drives the main thread
|
||||
Mailbox mailbox;
|
||||
/// Init thread
|
||||
std::jthread init_thread{};
|
||||
/// Main thread
|
||||
std::jthread main_thread{};
|
||||
/// The current state
|
||||
bool running{};
|
||||
/// Structure shared with the host, input data set by the host before sending a mailbox message,
|
||||
/// and the responses are written back by the OpusDecoder.
|
||||
SharedMemory* shared_memory{};
|
||||
};
|
||||
|
||||
} // namespace AudioCore::ADSP::OpusDecoder
|
111
src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp
Normal file
111
src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp
Normal file
|
@ -0,0 +1,111 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/adsp/apps/opus/opus_multistream_decode_object.h"
|
||||
#include "common/assert.h"
|
||||
|
||||
namespace AudioCore::ADSP::OpusDecoder {
|
||||
|
||||
namespace {
|
||||
bool IsValidChannelCount(u32 channel_count) {
|
||||
return channel_count == 1 || channel_count == 2;
|
||||
}
|
||||
|
||||
bool IsValidStreamCounts(u32 total_stream_count, u32 stereo_stream_count) {
|
||||
return total_stream_count > 0 && stereo_stream_count > 0 &&
|
||||
stereo_stream_count <= total_stream_count && IsValidChannelCount(total_stream_count);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
u32 OpusMultiStreamDecodeObject::GetWorkBufferSize(u32 total_stream_count,
|
||||
u32 stereo_stream_count) {
|
||||
if (IsValidStreamCounts(total_stream_count, stereo_stream_count)) {
|
||||
return static_cast<u32>(sizeof(OpusMultiStreamDecodeObject)) +
|
||||
opus_multistream_decoder_get_size(total_stream_count, stereo_stream_count);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
OpusMultiStreamDecodeObject& OpusMultiStreamDecodeObject::Initialize(u64 buffer, u64 buffer2) {
|
||||
auto* new_decoder = reinterpret_cast<OpusMultiStreamDecodeObject*>(buffer);
|
||||
auto* comparison = reinterpret_cast<OpusMultiStreamDecodeObject*>(buffer2);
|
||||
|
||||
if (new_decoder->magic == DecodeMultiStreamObjectMagic) {
|
||||
if (!new_decoder->initialized ||
|
||||
(new_decoder->initialized && new_decoder->self == comparison)) {
|
||||
new_decoder->state_valid = true;
|
||||
}
|
||||
} else {
|
||||
new_decoder->initialized = false;
|
||||
new_decoder->state_valid = true;
|
||||
}
|
||||
return *new_decoder;
|
||||
}
|
||||
|
||||
s32 OpusMultiStreamDecodeObject::InitializeDecoder(u32 sample_rate, u32 total_stream_count,
|
||||
u32 channel_count, u32 stereo_stream_count,
|
||||
u8* mappings) {
|
||||
if (!state_valid) {
|
||||
return OPUS_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (initialized) {
|
||||
return OPUS_OK;
|
||||
}
|
||||
|
||||
// See OpusDecodeObject::InitializeDecoder for an explanation of this
|
||||
decoder = (LibOpusMSDecoder*)(this + 1);
|
||||
s32 ret = opus_multistream_decoder_init(decoder, sample_rate, channel_count, total_stream_count,
|
||||
stereo_stream_count, mappings);
|
||||
if (ret == OPUS_OK) {
|
||||
magic = DecodeMultiStreamObjectMagic;
|
||||
initialized = true;
|
||||
state_valid = true;
|
||||
self = this;
|
||||
final_range = 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
s32 OpusMultiStreamDecodeObject::Shutdown() {
|
||||
if (!state_valid) {
|
||||
return OPUS_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (initialized) {
|
||||
magic = 0x0;
|
||||
initialized = false;
|
||||
state_valid = false;
|
||||
self = nullptr;
|
||||
final_range = 0;
|
||||
decoder = nullptr;
|
||||
}
|
||||
return OPUS_OK;
|
||||
}
|
||||
|
||||
s32 OpusMultiStreamDecodeObject::ResetDecoder() {
|
||||
return opus_multistream_decoder_ctl(decoder, OPUS_RESET_STATE);
|
||||
}
|
||||
|
||||
s32 OpusMultiStreamDecodeObject::Decode(u32& out_sample_count, u64 output_data,
|
||||
u64 output_data_size, u64 input_data, u64 input_data_size) {
|
||||
ASSERT(initialized);
|
||||
out_sample_count = 0;
|
||||
|
||||
if (!state_valid) {
|
||||
return OPUS_INVALID_STATE;
|
||||
}
|
||||
|
||||
auto ret_code_or_samples = opus_multistream_decode(
|
||||
decoder, reinterpret_cast<const u8*>(input_data), static_cast<opus_int32>(input_data_size),
|
||||
reinterpret_cast<opus_int16*>(output_data), static_cast<opus_int32>(output_data_size), 0);
|
||||
|
||||
if (ret_code_or_samples < OPUS_OK) {
|
||||
return ret_code_or_samples;
|
||||
}
|
||||
|
||||
out_sample_count = ret_code_or_samples;
|
||||
return opus_multistream_decoder_ctl(decoder, OPUS_GET_FINAL_RANGE_REQUEST, &final_range);
|
||||
}
|
||||
|
||||
} // namespace AudioCore::ADSP::OpusDecoder
|
|
@ -0,0 +1,39 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <opus_multistream.h>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::ADSP::OpusDecoder {
|
||||
using LibOpusMSDecoder = ::OpusMSDecoder;
|
||||
static constexpr u32 DecodeMultiStreamObjectMagic = 0xDEADBEEF;
|
||||
|
||||
class OpusMultiStreamDecodeObject {
|
||||
public:
|
||||
static u32 GetWorkBufferSize(u32 total_stream_count, u32 stereo_stream_count);
|
||||
static OpusMultiStreamDecodeObject& Initialize(u64 buffer, u64 buffer2);
|
||||
|
||||
s32 InitializeDecoder(u32 sample_rate, u32 total_stream_count, u32 channel_count,
|
||||
u32 stereo_stream_count, u8* mappings);
|
||||
s32 Shutdown();
|
||||
s32 ResetDecoder();
|
||||
s32 Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data,
|
||||
u64 input_data_size);
|
||||
u32 GetFinalRange() const noexcept {
|
||||
return final_range;
|
||||
}
|
||||
|
||||
private:
|
||||
u32 magic;
|
||||
bool initialized;
|
||||
bool state_valid;
|
||||
OpusMultiStreamDecodeObject* self;
|
||||
u32 final_range;
|
||||
LibOpusMSDecoder* decoder;
|
||||
};
|
||||
static_assert(std::is_trivially_constructible_v<OpusMultiStreamDecodeObject>);
|
||||
|
||||
} // namespace AudioCore::ADSP::OpusDecoder
|
17
src/audio_core/adsp/apps/opus/shared_memory.h
Normal file
17
src/audio_core/adsp/apps/opus/shared_memory.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::ADSP::OpusDecoder {
|
||||
|
||||
struct SharedMemory {
|
||||
std::array<u8, 0x100> channel_mapping{};
|
||||
std::array<u64, 16> host_send_data{};
|
||||
std::array<u64, 16> dsp_return_data{};
|
||||
};
|
||||
|
||||
} // namespace AudioCore::ADSP::OpusDecoder
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
|
||||
#include "common/bounded_threadsafe_queue.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
|
@ -19,11 +21,6 @@ enum class Direction : u32 {
|
|||
DSP,
|
||||
};
|
||||
|
||||
struct MailboxMessage {
|
||||
u32 msg;
|
||||
std::span<u8> data;
|
||||
};
|
||||
|
||||
class Mailbox {
|
||||
public:
|
||||
void Initialize(AppMailboxId id_) {
|
||||
|
@ -35,25 +32,19 @@ public:
|
|||
return id;
|
||||
}
|
||||
|
||||
void Send(Direction dir, MailboxMessage&& message) {
|
||||
void Send(Direction dir, u32 message) {
|
||||
auto& queue = dir == Direction::Host ? host_queue : adsp_queue;
|
||||
queue.EmplaceWait(std::move(message));
|
||||
queue.EmplaceWait(message);
|
||||
}
|
||||
|
||||
MailboxMessage Receive(Direction dir, bool block = true) {
|
||||
u32 Receive(Direction dir, std::stop_token stop_token = {}) {
|
||||
auto& queue = dir == Direction::Host ? host_queue : adsp_queue;
|
||||
MailboxMessage t;
|
||||
if (block) {
|
||||
queue.PopWait(t);
|
||||
} else {
|
||||
queue.TryPop(t);
|
||||
}
|
||||
return t;
|
||||
return queue.PopWait(stop_token);
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
id = AppMailboxId::Invalid;
|
||||
MailboxMessage t;
|
||||
u32 t{};
|
||||
while (host_queue.TryPop(t)) {
|
||||
}
|
||||
while (adsp_queue.TryPop(t)) {
|
||||
|
@ -62,8 +53,8 @@ public:
|
|||
|
||||
private:
|
||||
AppMailboxId id{0};
|
||||
Common::SPSCQueue<MailboxMessage> host_queue;
|
||||
Common::SPSCQueue<MailboxMessage> adsp_queue;
|
||||
Common::SPSCQueue<u32> host_queue;
|
||||
Common::SPSCQueue<u32> adsp_queue;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::ADSP
|
||||
|
|
179
src/audio_core/opus/decoder.cpp
Normal file
179
src/audio_core/opus/decoder.cpp
Normal file
|
@ -0,0 +1,179 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/opus/decoder.h"
|
||||
#include "audio_core/opus/hardware_opus.h"
|
||||
#include "audio_core/opus/parameters.h"
|
||||
#include "common/alignment.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/core.h"
|
||||
|
||||
namespace AudioCore::OpusDecoder {
|
||||
using namespace Service::Audio;
|
||||
namespace {
|
||||
OpusPacketHeader ReverseHeader(OpusPacketHeader header) {
|
||||
OpusPacketHeader out;
|
||||
out.size = Common::swap32(header.size);
|
||||
out.final_range = Common::swap32(header.final_range);
|
||||
return out;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
OpusDecoder::OpusDecoder(Core::System& system_, HardwareOpus& hardware_opus_)
|
||||
: system{system_}, hardware_opus{hardware_opus_} {}
|
||||
|
||||
OpusDecoder::~OpusDecoder() {
|
||||
if (decode_object_initialized) {
|
||||
hardware_opus.ShutdownDecodeObject(shared_buffer.get(), shared_buffer_size);
|
||||
}
|
||||
}
|
||||
|
||||
Result OpusDecoder::Initialize(OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory,
|
||||
u64 transfer_memory_size) {
|
||||
auto frame_size{params.use_large_frame_size ? 5760 : 1920};
|
||||
shared_buffer_size = transfer_memory_size;
|
||||
shared_buffer = std::make_unique<u8[]>(shared_buffer_size);
|
||||
shared_memory_mapped = true;
|
||||
|
||||
buffer_size =
|
||||
Common::AlignUp((frame_size * params.channel_count) / (48'000 / params.sample_rate), 16);
|
||||
|
||||
out_data = {shared_buffer.get() + shared_buffer_size - buffer_size, buffer_size};
|
||||
size_t in_data_size{0x600u};
|
||||
in_data = {out_data.data() - in_data_size, in_data_size};
|
||||
|
||||
ON_RESULT_FAILURE {
|
||||
if (shared_memory_mapped) {
|
||||
shared_memory_mapped = false;
|
||||
ASSERT(R_SUCCEEDED(hardware_opus.UnmapMemory(shared_buffer.get(), shared_buffer_size)));
|
||||
}
|
||||
};
|
||||
|
||||
R_TRY(hardware_opus.InitializeDecodeObject(params.sample_rate, params.channel_count,
|
||||
shared_buffer.get(), shared_buffer_size));
|
||||
|
||||
sample_rate = params.sample_rate;
|
||||
channel_count = params.channel_count;
|
||||
use_large_frame_size = params.use_large_frame_size;
|
||||
decode_object_initialized = true;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result OpusDecoder::Initialize(OpusMultiStreamParametersEx& params,
|
||||
Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size) {
|
||||
auto frame_size{params.use_large_frame_size ? 5760 : 1920};
|
||||
shared_buffer_size = transfer_memory_size;
|
||||
shared_buffer = std::make_unique<u8[]>(shared_buffer_size);
|
||||
shared_memory_mapped = true;
|
||||
|
||||
buffer_size =
|
||||
Common::AlignUp((frame_size * params.channel_count) / (48'000 / params.sample_rate), 16);
|
||||
|
||||
out_data = {shared_buffer.get() + shared_buffer_size - buffer_size, buffer_size};
|
||||
size_t in_data_size{Common::AlignUp(1500ull * params.total_stream_count, 64u)};
|
||||
in_data = {out_data.data() - in_data_size, in_data_size};
|
||||
|
||||
ON_RESULT_FAILURE {
|
||||
if (shared_memory_mapped) {
|
||||
shared_memory_mapped = false;
|
||||
ASSERT(R_SUCCEEDED(hardware_opus.UnmapMemory(shared_buffer.get(), shared_buffer_size)));
|
||||
}
|
||||
};
|
||||
|
||||
R_TRY(hardware_opus.InitializeMultiStreamDecodeObject(
|
||||
params.sample_rate, params.channel_count, params.total_stream_count,
|
||||
params.stereo_stream_count, params.mappings.data(), shared_buffer.get(),
|
||||
shared_buffer_size));
|
||||
|
||||
sample_rate = params.sample_rate;
|
||||
channel_count = params.channel_count;
|
||||
total_stream_count = params.total_stream_count;
|
||||
stereo_stream_count = params.stereo_stream_count;
|
||||
use_large_frame_size = params.use_large_frame_size;
|
||||
decode_object_initialized = true;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result OpusDecoder::DecodeInterleaved(u32* out_data_size, u64* out_time_taken,
|
||||
u32* out_sample_count, std::span<const u8> input_data,
|
||||
std::span<u8> output_data, bool reset) {
|
||||
u32 out_samples;
|
||||
u64 time_taken{};
|
||||
|
||||
R_UNLESS(input_data.size_bytes() > sizeof(OpusPacketHeader), ResultInputDataTooSmall);
|
||||
|
||||
auto* header_p{reinterpret_cast<const OpusPacketHeader*>(input_data.data())};
|
||||
OpusPacketHeader header{ReverseHeader(*header_p)};
|
||||
|
||||
R_UNLESS(in_data.size_bytes() >= header.size &&
|
||||
header.size + sizeof(OpusPacketHeader) <= input_data.size_bytes(),
|
||||
ResultBufferTooSmall);
|
||||
|
||||
if (!shared_memory_mapped) {
|
||||
R_TRY(hardware_opus.MapMemory(shared_buffer.get(), shared_buffer_size));
|
||||
shared_memory_mapped = true;
|
||||
}
|
||||
|
||||
std::memcpy(in_data.data(), input_data.data() + sizeof(OpusPacketHeader), header.size);
|
||||
|
||||
R_TRY(hardware_opus.DecodeInterleaved(out_samples, out_data.data(), out_data.size_bytes(),
|
||||
channel_count, in_data.data(), header.size,
|
||||
shared_buffer.get(), time_taken, reset));
|
||||
|
||||
std::memcpy(output_data.data(), out_data.data(), out_samples * channel_count * sizeof(s16));
|
||||
|
||||
*out_data_size = header.size + sizeof(OpusPacketHeader);
|
||||
*out_sample_count = out_samples;
|
||||
if (out_time_taken) {
|
||||
*out_time_taken = time_taken / 1000;
|
||||
}
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result OpusDecoder::SetContext([[maybe_unused]] std::span<const u8> context) {
|
||||
R_SUCCEED_IF(shared_memory_mapped);
|
||||
shared_memory_mapped = true;
|
||||
R_RETURN(hardware_opus.MapMemory(shared_buffer.get(), shared_buffer_size));
|
||||
}
|
||||
|
||||
Result OpusDecoder::DecodeInterleavedForMultiStream(u32* out_data_size, u64* out_time_taken,
|
||||
u32* out_sample_count,
|
||||
std::span<const u8> input_data,
|
||||
std::span<u8> output_data, bool reset) {
|
||||
u32 out_samples;
|
||||
u64 time_taken{};
|
||||
|
||||
R_UNLESS(input_data.size_bytes() > sizeof(OpusPacketHeader), ResultInputDataTooSmall);
|
||||
|
||||
auto* header_p{reinterpret_cast<const OpusPacketHeader*>(input_data.data())};
|
||||
OpusPacketHeader header{ReverseHeader(*header_p)};
|
||||
|
||||
LOG_ERROR(Service_Audio, "header size 0x{:X} input data size 0x{:X} in_data size 0x{:X}",
|
||||
header.size, input_data.size_bytes(), in_data.size_bytes());
|
||||
|
||||
R_UNLESS(in_data.size_bytes() >= header.size &&
|
||||
header.size + sizeof(OpusPacketHeader) <= input_data.size_bytes(),
|
||||
ResultBufferTooSmall);
|
||||
|
||||
if (!shared_memory_mapped) {
|
||||
R_TRY(hardware_opus.MapMemory(shared_buffer.get(), shared_buffer_size));
|
||||
shared_memory_mapped = true;
|
||||
}
|
||||
|
||||
std::memcpy(in_data.data(), input_data.data() + sizeof(OpusPacketHeader), header.size);
|
||||
|
||||
R_TRY(hardware_opus.DecodeInterleavedForMultiStream(
|
||||
out_samples, out_data.data(), out_data.size_bytes(), channel_count, in_data.data(),
|
||||
header.size, shared_buffer.get(), time_taken, reset));
|
||||
|
||||
std::memcpy(output_data.data(), out_data.data(), out_samples * channel_count * sizeof(s16));
|
||||
|
||||
*out_data_size = header.size + sizeof(OpusPacketHeader);
|
||||
*out_sample_count = out_samples;
|
||||
if (out_time_taken) {
|
||||
*out_time_taken = time_taken / 1000;
|
||||
}
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
} // namespace AudioCore::OpusDecoder
|
53
src/audio_core/opus/decoder.h
Normal file
53
src/audio_core/opus/decoder.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
|
||||
#include "audio_core/opus/parameters.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/kernel/k_transfer_memory.h"
|
||||
#include "core/hle/service/audio/errors.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace AudioCore::OpusDecoder {
|
||||
class HardwareOpus;
|
||||
|
||||
class OpusDecoder {
|
||||
public:
|
||||
explicit OpusDecoder(Core::System& system, HardwareOpus& hardware_opus_);
|
||||
~OpusDecoder();
|
||||
|
||||
Result Initialize(OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory,
|
||||
u64 transfer_memory_size);
|
||||
Result Initialize(OpusMultiStreamParametersEx& params, Kernel::KTransferMemory* transfer_memory,
|
||||
u64 transfer_memory_size);
|
||||
Result DecodeInterleaved(u32* out_data_size, u64* out_time_taken, u32* out_sample_count,
|
||||
std::span<const u8> input_data, std::span<u8> output_data, bool reset);
|
||||
Result SetContext([[maybe_unused]] std::span<const u8> context);
|
||||
Result DecodeInterleavedForMultiStream(u32* out_data_size, u64* out_time_taken,
|
||||
u32* out_sample_count, std::span<const u8> input_data,
|
||||
std::span<u8> output_data, bool reset);
|
||||
|
||||
private:
|
||||
Core::System& system;
|
||||
HardwareOpus& hardware_opus;
|
||||
std::unique_ptr<u8[]> shared_buffer{};
|
||||
u64 shared_buffer_size;
|
||||
std::span<u8> in_data{};
|
||||
std::span<u8> out_data{};
|
||||
u64 buffer_size{};
|
||||
s32 sample_rate{};
|
||||
s32 channel_count{};
|
||||
bool use_large_frame_size{false};
|
||||
s32 total_stream_count{};
|
||||
s32 stereo_stream_count{};
|
||||
bool shared_memory_mapped{false};
|
||||
bool decode_object_initialized{false};
|
||||
};
|
||||
|
||||
} // namespace AudioCore::OpusDecoder
|
102
src/audio_core/opus/decoder_manager.cpp
Normal file
102
src/audio_core/opus/decoder_manager.cpp
Normal file
|
@ -0,0 +1,102 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/adsp/apps/opus/opus_decoder.h"
|
||||
#include "audio_core/opus/decoder_manager.h"
|
||||
#include "common/alignment.h"
|
||||
#include "core/core.h"
|
||||
|
||||
namespace AudioCore::OpusDecoder {
|
||||
using namespace Service::Audio;
|
||||
|
||||
namespace {
|
||||
bool IsValidChannelCount(u32 channel_count) {
|
||||
return channel_count == 1 || channel_count == 2;
|
||||
}
|
||||
|
||||
bool IsValidMultiStreamChannelCount(u32 channel_count) {
|
||||
return channel_count > 0 && channel_count <= OpusStreamCountMax;
|
||||
}
|
||||
|
||||
bool IsValidSampleRate(u32 sample_rate) {
|
||||
return sample_rate == 8'000 || sample_rate == 12'000 || sample_rate == 16'000 ||
|
||||
sample_rate == 24'000 || sample_rate == 48'000;
|
||||
}
|
||||
|
||||
bool IsValidStreamCount(u32 channel_count, u32 total_stream_count, u32 stereo_stream_count) {
|
||||
return total_stream_count > 0 && stereo_stream_count > 0 &&
|
||||
stereo_stream_count <= total_stream_count &&
|
||||
total_stream_count + stereo_stream_count <= channel_count;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
OpusDecoderManager::OpusDecoderManager(Core::System& system_)
|
||||
: system{system_}, hardware_opus{system} {
|
||||
for (u32 i = 0; i < MaxChannels; i++) {
|
||||
required_workbuffer_sizes[i] = hardware_opus.GetWorkBufferSize(1 + i);
|
||||
}
|
||||
}
|
||||
|
||||
Result OpusDecoderManager::GetWorkBufferSize(OpusParameters& params, u64& out_size) {
|
||||
OpusParametersEx ex{
|
||||
.sample_rate = params.sample_rate,
|
||||
.channel_count = params.channel_count,
|
||||
.use_large_frame_size = false,
|
||||
};
|
||||
R_RETURN(GetWorkBufferSizeExEx(ex, out_size));
|
||||
}
|
||||
|
||||
Result OpusDecoderManager::GetWorkBufferSizeEx(OpusParametersEx& params, u64& out_size) {
|
||||
R_RETURN(GetWorkBufferSizeExEx(params, out_size));
|
||||
}
|
||||
|
||||
Result OpusDecoderManager::GetWorkBufferSizeExEx(OpusParametersEx& params, u64& out_size) {
|
||||
R_UNLESS(IsValidChannelCount(params.channel_count), ResultInvalidOpusChannelCount);
|
||||
R_UNLESS(IsValidSampleRate(params.sample_rate), ResultInvalidOpusSampleRate);
|
||||
|
||||
auto work_buffer_size{required_workbuffer_sizes[params.channel_count - 1]};
|
||||
auto frame_size{params.use_large_frame_size ? 5760 : 1920};
|
||||
work_buffer_size +=
|
||||
Common::AlignUp((frame_size * params.channel_count) / (48'000 / params.sample_rate), 64);
|
||||
out_size = work_buffer_size + 0x600;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result OpusDecoderManager::GetWorkBufferSizeForMultiStream(OpusMultiStreamParameters& params,
|
||||
u64& out_size) {
|
||||
OpusMultiStreamParametersEx ex{
|
||||
.sample_rate = params.sample_rate,
|
||||
.channel_count = params.channel_count,
|
||||
.total_stream_count = params.total_stream_count,
|
||||
.stereo_stream_count = params.stereo_stream_count,
|
||||
.use_large_frame_size = false,
|
||||
.mappings = {},
|
||||
};
|
||||
R_RETURN(GetWorkBufferSizeForMultiStreamExEx(ex, out_size));
|
||||
}
|
||||
|
||||
Result OpusDecoderManager::GetWorkBufferSizeForMultiStreamEx(OpusMultiStreamParametersEx& params,
|
||||
u64& out_size) {
|
||||
R_RETURN(GetWorkBufferSizeForMultiStreamExEx(params, out_size));
|
||||
}
|
||||
|
||||
Result OpusDecoderManager::GetWorkBufferSizeForMultiStreamExEx(OpusMultiStreamParametersEx& params,
|
||||
u64& out_size) {
|
||||
R_UNLESS(IsValidMultiStreamChannelCount(params.channel_count), ResultInvalidOpusChannelCount);
|
||||
R_UNLESS(IsValidSampleRate(params.sample_rate), ResultInvalidOpusSampleRate);
|
||||
R_UNLESS(IsValidStreamCount(params.channel_count, params.total_stream_count,
|
||||
params.stereo_stream_count),
|
||||
ResultInvalidOpusSampleRate);
|
||||
|
||||
auto work_buffer_size{hardware_opus.GetWorkBufferSizeForMultiStream(
|
||||
params.total_stream_count, params.stereo_stream_count)};
|
||||
auto frame_size{params.use_large_frame_size ? 5760 : 1920};
|
||||
work_buffer_size += Common::AlignUp(1500 * params.total_stream_count, 64);
|
||||
work_buffer_size +=
|
||||
Common::AlignUp((frame_size * params.channel_count) / (48'000 / params.sample_rate), 64);
|
||||
out_size = work_buffer_size;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
} // namespace AudioCore::OpusDecoder
|
38
src/audio_core/opus/decoder_manager.h
Normal file
38
src/audio_core/opus/decoder_manager.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "audio_core/opus/hardware_opus.h"
|
||||
#include "audio_core/opus/parameters.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/audio/errors.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace AudioCore::OpusDecoder {
|
||||
|
||||
class OpusDecoderManager {
|
||||
public:
|
||||
OpusDecoderManager(Core::System& system);
|
||||
|
||||
HardwareOpus& GetHardwareOpus() {
|
||||
return hardware_opus;
|
||||
}
|
||||
|
||||
Result GetWorkBufferSize(OpusParameters& params, u64& out_size);
|
||||
Result GetWorkBufferSizeEx(OpusParametersEx& params, u64& out_size);
|
||||
Result GetWorkBufferSizeExEx(OpusParametersEx& params, u64& out_size);
|
||||
Result GetWorkBufferSizeForMultiStream(OpusMultiStreamParameters& params, u64& out_size);
|
||||
Result GetWorkBufferSizeForMultiStreamEx(OpusMultiStreamParametersEx& params, u64& out_size);
|
||||
Result GetWorkBufferSizeForMultiStreamExEx(OpusMultiStreamParametersEx& params, u64& out_size);
|
||||
|
||||
private:
|
||||
Core::System& system;
|
||||
HardwareOpus hardware_opus;
|
||||
std::array<u64, MaxChannels> required_workbuffer_sizes{};
|
||||
};
|
||||
|
||||
} // namespace AudioCore::OpusDecoder
|
241
src/audio_core/opus/hardware_opus.cpp
Normal file
241
src/audio_core/opus/hardware_opus.cpp
Normal file
|
@ -0,0 +1,241 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "audio_core/audio_core.h"
|
||||
#include "audio_core/opus/hardware_opus.h"
|
||||
#include "core/core.h"
|
||||
|
||||
namespace AudioCore::OpusDecoder {
|
||||
namespace {
|
||||
using namespace Service::Audio;
|
||||
|
||||
static constexpr Result ResultCodeFromLibOpusErrorCode(u64 error_code) {
|
||||
s32 error{static_cast<s32>(error_code)};
|
||||
ASSERT(error <= OPUS_OK);
|
||||
switch (error) {
|
||||
case OPUS_ALLOC_FAIL:
|
||||
R_THROW(ResultLibOpusAllocFail);
|
||||
case OPUS_INVALID_STATE:
|
||||
R_THROW(ResultLibOpusInvalidState);
|
||||
case OPUS_UNIMPLEMENTED:
|
||||
R_THROW(ResultLibOpusUnimplemented);
|
||||
case OPUS_INVALID_PACKET:
|
||||
R_THROW(ResultLibOpusInvalidPacket);
|
||||
case OPUS_INTERNAL_ERROR:
|
||||
R_THROW(ResultLibOpusInternalError);
|
||||
case OPUS_BUFFER_TOO_SMALL:
|
||||
R_THROW(ResultBufferTooSmall);
|
||||
case OPUS_BAD_ARG:
|
||||
R_THROW(ResultLibOpusBadArg);
|
||||
case OPUS_OK:
|
||||
R_RETURN(ResultSuccess);
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
HardwareOpus::HardwareOpus(Core::System& system_)
|
||||
: system{system_}, opus_decoder{system.AudioCore().ADSP().OpusDecoder()} {
|
||||
opus_decoder.SetSharedMemory(shared_memory);
|
||||
}
|
||||
|
||||
u64 HardwareOpus::GetWorkBufferSize(u32 channel) {
|
||||
if (!opus_decoder.IsRunning()) {
|
||||
return 0;
|
||||
}
|
||||
std::scoped_lock l{mutex};
|
||||
shared_memory.host_send_data[0] = channel;
|
||||
opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::GetWorkBufferSize);
|
||||
auto msg = opus_decoder.Receive(ADSP::Direction::Host);
|
||||
if (msg != ADSP::OpusDecoder::Message::GetWorkBufferSizeOK) {
|
||||
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
|
||||
ADSP::OpusDecoder::Message::GetWorkBufferSizeOK, msg);
|
||||
return 0;
|
||||
}
|
||||
return shared_memory.dsp_return_data[0];
|
||||
}
|
||||
|
||||
u64 HardwareOpus::GetWorkBufferSizeForMultiStream(u32 total_stream_count, u32 stereo_stream_count) {
|
||||
std::scoped_lock l{mutex};
|
||||
shared_memory.host_send_data[0] = total_stream_count;
|
||||
shared_memory.host_send_data[1] = stereo_stream_count;
|
||||
opus_decoder.Send(ADSP::Direction::DSP,
|
||||
ADSP::OpusDecoder::Message::GetWorkBufferSizeForMultiStream);
|
||||
auto msg = opus_decoder.Receive(ADSP::Direction::Host);
|
||||
if (msg != ADSP::OpusDecoder::Message::GetWorkBufferSizeForMultiStreamOK) {
|
||||
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
|
||||
ADSP::OpusDecoder::Message::GetWorkBufferSizeForMultiStreamOK, msg);
|
||||
return 0;
|
||||
}
|
||||
return shared_memory.dsp_return_data[0];
|
||||
}
|
||||
|
||||
Result HardwareOpus::InitializeDecodeObject(u32 sample_rate, u32 channel_count, void* buffer,
|
||||
u64 buffer_size) {
|
||||
std::scoped_lock l{mutex};
|
||||
shared_memory.host_send_data[0] = (u64)buffer;
|
||||
shared_memory.host_send_data[1] = buffer_size;
|
||||
shared_memory.host_send_data[2] = sample_rate;
|
||||
shared_memory.host_send_data[3] = channel_count;
|
||||
|
||||
opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::InitializeDecodeObject);
|
||||
auto msg = opus_decoder.Receive(ADSP::Direction::Host);
|
||||
if (msg != ADSP::OpusDecoder::Message::InitializeDecodeObjectOK) {
|
||||
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
|
||||
ADSP::OpusDecoder::Message::InitializeDecodeObjectOK, msg);
|
||||
R_THROW(ResultInvalidOpusDSPReturnCode);
|
||||
}
|
||||
|
||||
R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
|
||||
}
|
||||
|
||||
Result HardwareOpus::InitializeMultiStreamDecodeObject(u32 sample_rate, u32 channel_count,
|
||||
u32 total_stream_count,
|
||||
u32 stereo_stream_count, void* mappings,
|
||||
void* buffer, u64 buffer_size) {
|
||||
std::scoped_lock l{mutex};
|
||||
shared_memory.host_send_data[0] = (u64)buffer;
|
||||
shared_memory.host_send_data[1] = buffer_size;
|
||||
shared_memory.host_send_data[2] = sample_rate;
|
||||
shared_memory.host_send_data[3] = channel_count;
|
||||
shared_memory.host_send_data[4] = total_stream_count;
|
||||
shared_memory.host_send_data[5] = stereo_stream_count;
|
||||
|
||||
ASSERT(channel_count <= MaxChannels);
|
||||
std::memcpy(shared_memory.channel_mapping.data(), mappings, channel_count * sizeof(u8));
|
||||
|
||||
opus_decoder.Send(ADSP::Direction::DSP,
|
||||
ADSP::OpusDecoder::Message::InitializeMultiStreamDecodeObject);
|
||||
auto msg = opus_decoder.Receive(ADSP::Direction::Host);
|
||||
if (msg != ADSP::OpusDecoder::Message::InitializeMultiStreamDecodeObjectOK) {
|
||||
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
|
||||
ADSP::OpusDecoder::Message::InitializeMultiStreamDecodeObjectOK, msg);
|
||||
R_THROW(ResultInvalidOpusDSPReturnCode);
|
||||
}
|
||||
|
||||
R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
|
||||
}
|
||||
|
||||
Result HardwareOpus::ShutdownDecodeObject(void* buffer, u64 buffer_size) {
|
||||
std::scoped_lock l{mutex};
|
||||
shared_memory.host_send_data[0] = (u64)buffer;
|
||||
shared_memory.host_send_data[1] = buffer_size;
|
||||
|
||||
opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::ShutdownDecodeObject);
|
||||
auto msg = opus_decoder.Receive(ADSP::Direction::Host);
|
||||
ASSERT_MSG(msg == ADSP::OpusDecoder::Message::ShutdownDecodeObjectOK,
|
||||
"Expected Opus shutdown code {}, got {}",
|
||||
ADSP::OpusDecoder::Message::ShutdownDecodeObjectOK, msg);
|
||||
|
||||
R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
|
||||
}
|
||||
|
||||
Result HardwareOpus::ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_size) {
|
||||
std::scoped_lock l{mutex};
|
||||
shared_memory.host_send_data[0] = (u64)buffer;
|
||||
shared_memory.host_send_data[1] = buffer_size;
|
||||
|
||||
opus_decoder.Send(ADSP::Direction::DSP,
|
||||
ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObject);
|
||||
auto msg = opus_decoder.Receive(ADSP::Direction::Host);
|
||||
ASSERT_MSG(msg == ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObjectOK,
|
||||
"Expected Opus shutdown code {}, got {}",
|
||||
ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObjectOK, msg);
|
||||
|
||||
R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
|
||||
}
|
||||
|
||||
Result HardwareOpus::DecodeInterleaved(u32& out_sample_count, void* output_data,
|
||||
u64 output_data_size, u32 channel_count, void* input_data,
|
||||
u64 input_data_size, void* buffer, u64& out_time_taken,
|
||||
bool reset) {
|
||||
std::scoped_lock l{mutex};
|
||||
shared_memory.host_send_data[0] = (u64)buffer;
|
||||
shared_memory.host_send_data[1] = (u64)input_data;
|
||||
shared_memory.host_send_data[2] = input_data_size;
|
||||
shared_memory.host_send_data[3] = (u64)output_data;
|
||||
shared_memory.host_send_data[4] = output_data_size;
|
||||
shared_memory.host_send_data[5] = 0;
|
||||
shared_memory.host_send_data[6] = reset;
|
||||
|
||||
opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::DecodeInterleaved);
|
||||
auto msg = opus_decoder.Receive(ADSP::Direction::Host);
|
||||
if (msg != ADSP::OpusDecoder::Message::DecodeInterleavedOK) {
|
||||
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
|
||||
ADSP::OpusDecoder::Message::DecodeInterleavedOK, msg);
|
||||
R_THROW(ResultInvalidOpusDSPReturnCode);
|
||||
}
|
||||
|
||||
auto error_code{static_cast<s32>(shared_memory.dsp_return_data[0])};
|
||||
if (error_code == OPUS_OK) {
|
||||
out_sample_count = static_cast<u32>(shared_memory.dsp_return_data[1]);
|
||||
out_time_taken = 1000 * shared_memory.dsp_return_data[2];
|
||||
}
|
||||
R_RETURN(ResultCodeFromLibOpusErrorCode(error_code));
|
||||
}
|
||||
|
||||
Result HardwareOpus::DecodeInterleavedForMultiStream(u32& out_sample_count, void* output_data,
|
||||
u64 output_data_size, u32 channel_count,
|
||||
void* input_data, u64 input_data_size,
|
||||
void* buffer, u64& out_time_taken,
|
||||
bool reset) {
|
||||
std::scoped_lock l{mutex};
|
||||
shared_memory.host_send_data[0] = (u64)buffer;
|
||||
shared_memory.host_send_data[1] = (u64)input_data;
|
||||
shared_memory.host_send_data[2] = input_data_size;
|
||||
shared_memory.host_send_data[3] = (u64)output_data;
|
||||
shared_memory.host_send_data[4] = output_data_size;
|
||||
shared_memory.host_send_data[5] = 0;
|
||||
shared_memory.host_send_data[6] = reset;
|
||||
|
||||
opus_decoder.Send(ADSP::Direction::DSP,
|
||||
ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStream);
|
||||
auto msg = opus_decoder.Receive(ADSP::Direction::Host);
|
||||
if (msg != ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK) {
|
||||
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
|
||||
ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK, msg);
|
||||
R_THROW(ResultInvalidOpusDSPReturnCode);
|
||||
}
|
||||
|
||||
auto error_code{static_cast<s32>(shared_memory.dsp_return_data[0])};
|
||||
if (error_code == OPUS_OK) {
|
||||
out_sample_count = static_cast<u32>(shared_memory.dsp_return_data[1]);
|
||||
out_time_taken = 1000 * shared_memory.dsp_return_data[2];
|
||||
}
|
||||
R_RETURN(ResultCodeFromLibOpusErrorCode(error_code));
|
||||
}
|
||||
|
||||
Result HardwareOpus::MapMemory(void* buffer, u64 buffer_size) {
|
||||
std::scoped_lock l{mutex};
|
||||
shared_memory.host_send_data[0] = (u64)buffer;
|
||||
shared_memory.host_send_data[1] = buffer_size;
|
||||
|
||||
opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::MapMemory);
|
||||
auto msg = opus_decoder.Receive(ADSP::Direction::Host);
|
||||
if (msg != ADSP::OpusDecoder::Message::MapMemoryOK) {
|
||||
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
|
||||
ADSP::OpusDecoder::Message::MapMemoryOK, msg);
|
||||
R_THROW(ResultInvalidOpusDSPReturnCode);
|
||||
}
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result HardwareOpus::UnmapMemory(void* buffer, u64 buffer_size) {
|
||||
std::scoped_lock l{mutex};
|
||||
shared_memory.host_send_data[0] = (u64)buffer;
|
||||
shared_memory.host_send_data[1] = buffer_size;
|
||||
|
||||
opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::UnmapMemory);
|
||||
auto msg = opus_decoder.Receive(ADSP::Direction::Host);
|
||||
if (msg != ADSP::OpusDecoder::Message::UnmapMemoryOK) {
|
||||
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
|
||||
ADSP::OpusDecoder::Message::UnmapMemoryOK, msg);
|
||||
R_THROW(ResultInvalidOpusDSPReturnCode);
|
||||
}
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
} // namespace AudioCore::OpusDecoder
|
45
src/audio_core/opus/hardware_opus.h
Normal file
45
src/audio_core/opus/hardware_opus.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <opus.h>
|
||||
|
||||
#include "audio_core/adsp/apps/opus/opus_decoder.h"
|
||||
#include "audio_core/adsp/apps/opus/shared_memory.h"
|
||||
#include "audio_core/adsp/mailbox.h"
|
||||
#include "core/hle/service/audio/errors.h"
|
||||
|
||||
namespace AudioCore::OpusDecoder {
|
||||
class HardwareOpus {
|
||||
public:
|
||||
HardwareOpus(Core::System& system);
|
||||
|
||||
u64 GetWorkBufferSize(u32 channel);
|
||||
u64 GetWorkBufferSizeForMultiStream(u32 total_stream_count, u32 stereo_stream_count);
|
||||
|
||||
Result InitializeDecodeObject(u32 sample_rate, u32 channel_count, void* buffer,
|
||||
u64 buffer_size);
|
||||
Result InitializeMultiStreamDecodeObject(u32 sample_rate, u32 channel_count,
|
||||
u32 totaL_stream_count, u32 stereo_stream_count,
|
||||
void* mappings, void* buffer, u64 buffer_size);
|
||||
Result ShutdownDecodeObject(void* buffer, u64 buffer_size);
|
||||
Result ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_size);
|
||||
Result DecodeInterleaved(u32& out_sample_count, void* output_data, u64 output_data_size,
|
||||
u32 channel_count, void* input_data, u64 input_data_size, void* buffer,
|
||||
u64& out_time_taken, bool reset);
|
||||
Result DecodeInterleavedForMultiStream(u32& out_sample_count, void* output_data,
|
||||
u64 output_data_size, u32 channel_count,
|
||||
void* input_data, u64 input_data_size, void* buffer,
|
||||
u64& out_time_taken, bool reset);
|
||||
Result MapMemory(void* buffer, u64 buffer_size);
|
||||
Result UnmapMemory(void* buffer, u64 buffer_size);
|
||||
|
||||
private:
|
||||
Core::System& system;
|
||||
std::mutex mutex;
|
||||
ADSP::OpusDecoder::OpusDecoder& opus_decoder;
|
||||
ADSP::OpusDecoder::SharedMemory shared_memory;
|
||||
};
|
||||
} // namespace AudioCore::OpusDecoder
|
54
src/audio_core/opus/parameters.h
Normal file
54
src/audio_core/opus/parameters.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::OpusDecoder {
|
||||
constexpr size_t OpusStreamCountMax = 255;
|
||||
constexpr size_t MaxChannels = 2;
|
||||
|
||||
struct OpusParameters {
|
||||
/* 0x00 */ u32 sample_rate;
|
||||
/* 0x04 */ u32 channel_count;
|
||||
}; // size = 0x8
|
||||
static_assert(sizeof(OpusParameters) == 0x8, "OpusParameters has the wrong size!");
|
||||
|
||||
struct OpusParametersEx {
|
||||
/* 0x00 */ u32 sample_rate;
|
||||
/* 0x04 */ u32 channel_count;
|
||||
/* 0x08 */ bool use_large_frame_size;
|
||||
/* 0x09 */ INSERT_PADDING_BYTES(7);
|
||||
}; // size = 0x10
|
||||
static_assert(sizeof(OpusParametersEx) == 0x10, "OpusParametersEx has the wrong size!");
|
||||
|
||||
struct OpusMultiStreamParameters {
|
||||
/* 0x00 */ u32 sample_rate;
|
||||
/* 0x04 */ u32 channel_count;
|
||||
/* 0x08 */ u32 total_stream_count;
|
||||
/* 0x0C */ u32 stereo_stream_count;
|
||||
/* 0x10 */ std::array<u8, OpusStreamCountMax + 1> mappings;
|
||||
}; // size = 0x110
|
||||
static_assert(sizeof(OpusMultiStreamParameters) == 0x110,
|
||||
"OpusMultiStreamParameters has the wrong size!");
|
||||
|
||||
struct OpusMultiStreamParametersEx {
|
||||
/* 0x00 */ u32 sample_rate;
|
||||
/* 0x04 */ u32 channel_count;
|
||||
/* 0x08 */ u32 total_stream_count;
|
||||
/* 0x0C */ u32 stereo_stream_count;
|
||||
/* 0x10 */ bool use_large_frame_size;
|
||||
/* 0x11 */ INSERT_PADDING_BYTES(7);
|
||||
/* 0x18 */ std::array<u8, OpusStreamCountMax + 1> mappings;
|
||||
}; // size = 0x118
|
||||
static_assert(sizeof(OpusMultiStreamParametersEx) == 0x118,
|
||||
"OpusMultiStreamParametersEx has the wrong size!");
|
||||
|
||||
struct OpusPacketHeader {
|
||||
/* 0x00 */ u32 size;
|
||||
/* 0x04 */ u32 final_range;
|
||||
}; // size = 0x8
|
||||
static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusPacketHeader has the wrong size!");
|
||||
} // namespace AudioCore::OpusDecoder
|
|
@ -20,6 +20,12 @@ void AdpcmDataSourceVersion1Command::Process(const AudioRenderer::CommandListPro
|
|||
auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
|
||||
for (auto& wave_buffer : wave_buffers) {
|
||||
wave_buffer.loop_start_offset = wave_buffer.start_offset;
|
||||
wave_buffer.loop_end_offset = wave_buffer.end_offset;
|
||||
wave_buffer.loop_count = wave_buffer.loop ? -1 : 0;
|
||||
}
|
||||
|
||||
DecodeFromWaveBuffersArgs args{
|
||||
.sample_format{SampleFormat::Adpcm},
|
||||
.output{out_buffer},
|
||||
|
|
|
@ -123,11 +123,13 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
|||
return 0;
|
||||
}
|
||||
|
||||
auto samples_to_process{
|
||||
std::min(req.end_offset - req.start_offset - req.offset, req.samples_to_read)};
|
||||
auto start_pos{req.start_offset + req.offset};
|
||||
auto samples_to_process{std::min(req.end_offset - start_pos, req.samples_to_read)};
|
||||
if (samples_to_process == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto samples_to_read{samples_to_process};
|
||||
auto start_pos{req.start_offset + req.offset};
|
||||
auto samples_remaining_in_frame{start_pos % SamplesPerFrame};
|
||||
auto position_in_frame{(start_pos / SamplesPerFrame) * NibblesPerFrame +
|
||||
samples_remaining_in_frame};
|
||||
|
@ -225,13 +227,24 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
|||
* @param args - The wavebuffer data, and information for how to decode it.
|
||||
*/
|
||||
void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args) {
|
||||
static constexpr auto EndWaveBuffer = [](auto& voice_state, auto& wavebuffer, auto& index,
|
||||
auto& played_samples, auto& consumed) -> void {
|
||||
voice_state.wave_buffer_valid[index] = false;
|
||||
voice_state.loop_count = 0;
|
||||
|
||||
if (wavebuffer.stream_ended) {
|
||||
played_samples = 0;
|
||||
}
|
||||
|
||||
index = (index + 1) % MaxWaveBuffers;
|
||||
consumed++;
|
||||
};
|
||||
auto& voice_state{*args.voice_state};
|
||||
auto remaining_sample_count{args.sample_count};
|
||||
auto fraction{voice_state.fraction};
|
||||
|
||||
const auto sample_rate_ratio{
|
||||
(Common::FixedPoint<49, 15>(args.source_sample_rate) / args.target_sample_rate) *
|
||||
args.pitch};
|
||||
const auto sample_rate_ratio{Common::FixedPoint<49, 15>(
|
||||
(f32)args.source_sample_rate / (f32)args.target_sample_rate * (f32)args.pitch)};
|
||||
const auto size_required{fraction + remaining_sample_count * sample_rate_ratio};
|
||||
|
||||
if (size_required < 0) {
|
||||
|
@ -298,22 +311,23 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
|
|||
auto end_offset{wavebuffer.end_offset};
|
||||
|
||||
if (wavebuffer.loop && voice_state.loop_count > 0 &&
|
||||
wavebuffer.loop_start_offset != 0 && wavebuffer.loop_end_offset != 0 &&
|
||||
wavebuffer.loop_start_offset <= wavebuffer.loop_end_offset) {
|
||||
start_offset = wavebuffer.loop_start_offset;
|
||||
end_offset = wavebuffer.loop_end_offset;
|
||||
}
|
||||
|
||||
DecodeArg decode_arg{.buffer{wavebuffer.buffer},
|
||||
.buffer_size{wavebuffer.buffer_size},
|
||||
.start_offset{start_offset},
|
||||
.end_offset{end_offset},
|
||||
.channel_count{args.channel_count},
|
||||
.coefficients{},
|
||||
.adpcm_context{nullptr},
|
||||
.target_channel{args.channel},
|
||||
.offset{offset},
|
||||
.samples_to_read{samples_to_read - samples_read}};
|
||||
DecodeArg decode_arg{
|
||||
.buffer{wavebuffer.buffer},
|
||||
.buffer_size{wavebuffer.buffer_size},
|
||||
.start_offset{start_offset},
|
||||
.end_offset{end_offset},
|
||||
.channel_count{args.channel_count},
|
||||
.coefficients{},
|
||||
.adpcm_context{nullptr},
|
||||
.target_channel{args.channel},
|
||||
.offset{offset},
|
||||
.samples_to_read{samples_to_read - samples_read},
|
||||
};
|
||||
|
||||
s32 samples_decoded{0};
|
||||
|
||||
|
@ -350,42 +364,30 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
|
|||
temp_buffer_pos += samples_decoded;
|
||||
offset += samples_decoded;
|
||||
|
||||
if (samples_decoded == 0 || offset >= end_offset - start_offset) {
|
||||
offset = 0;
|
||||
if (!wavebuffer.loop) {
|
||||
voice_state.wave_buffer_valid[wavebuffer_index] = false;
|
||||
voice_state.loop_count = 0;
|
||||
if (samples_decoded && offset < end_offset - start_offset) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (wavebuffer.stream_ended) {
|
||||
played_sample_count = 0;
|
||||
}
|
||||
|
||||
wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers;
|
||||
wavebuffers_consumed++;
|
||||
} else {
|
||||
voice_state.loop_count++;
|
||||
if (wavebuffer.loop_count >= 0 &&
|
||||
(voice_state.loop_count > wavebuffer.loop_count || samples_decoded == 0)) {
|
||||
voice_state.wave_buffer_valid[wavebuffer_index] = false;
|
||||
voice_state.loop_count = 0;
|
||||
|
||||
if (wavebuffer.stream_ended) {
|
||||
played_sample_count = 0;
|
||||
}
|
||||
|
||||
wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers;
|
||||
wavebuffers_consumed++;
|
||||
}
|
||||
|
||||
if (samples_decoded == 0) {
|
||||
is_buffer_starved = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) {
|
||||
played_sample_count = 0;
|
||||
}
|
||||
offset = 0;
|
||||
if (wavebuffer.loop) {
|
||||
voice_state.loop_count++;
|
||||
if (wavebuffer.loop_count >= 0 &&
|
||||
(voice_state.loop_count > wavebuffer.loop_count || samples_decoded == 0)) {
|
||||
EndWaveBuffer(voice_state, wavebuffer, wavebuffer_index, played_sample_count,
|
||||
wavebuffers_consumed);
|
||||
}
|
||||
|
||||
if (samples_decoded == 0) {
|
||||
is_buffer_starved = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) {
|
||||
played_sample_count = 0;
|
||||
}
|
||||
} else {
|
||||
EndWaveBuffer(voice_state, wavebuffer, wavebuffer_index, played_sample_count,
|
||||
wavebuffers_consumed);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,12 @@ void PcmFloatDataSourceVersion1Command::Process(
|
|||
auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
|
||||
processor.sample_count);
|
||||
|
||||
for (auto& wave_buffer : wave_buffers) {
|
||||
wave_buffer.loop_start_offset = wave_buffer.start_offset;
|
||||
wave_buffer.loop_end_offset = wave_buffer.end_offset;
|
||||
wave_buffer.loop_count = wave_buffer.loop ? -1 : 0;
|
||||
}
|
||||
|
||||
DecodeFromWaveBuffersArgs args{
|
||||
.sample_format{SampleFormat::PcmFloat},
|
||||
.output{out_buffer},
|
||||
|
|
|
@ -23,6 +23,12 @@ void PcmInt16DataSourceVersion1Command::Process(
|
|||
auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
|
||||
processor.sample_count);
|
||||
|
||||
for (auto& wave_buffer : wave_buffers) {
|
||||
wave_buffer.loop_start_offset = wave_buffer.start_offset;
|
||||
wave_buffer.loop_end_offset = wave_buffer.end_offset;
|
||||
wave_buffer.loop_count = wave_buffer.loop ? -1 : 0;
|
||||
}
|
||||
|
||||
DecodeFromWaveBuffersArgs args{
|
||||
.sample_format{SampleFormat::PcmInt16},
|
||||
.output{out_buffer},
|
||||
|
|
|
@ -609,17 +609,11 @@ void System::SendCommandToDsp() {
|
|||
time_limit_percent = 70.0f;
|
||||
}
|
||||
|
||||
AudioRenderer::CommandBuffer command_buffer{
|
||||
.buffer{translated_addr},
|
||||
.size{command_size},
|
||||
.time_limit{
|
||||
static_cast<u64>((time_limit_percent / 100) * 2'880'000.0 *
|
||||
(static_cast<f32>(render_time_limit_percent) / 100.0f))},
|
||||
.applet_resource_user_id{applet_resource_user_id},
|
||||
.reset_buffer{reset_command_buffers},
|
||||
};
|
||||
|
||||
audio_renderer.SetCommandBuffer(session_id, command_buffer);
|
||||
auto time_limit{
|
||||
static_cast<u64>((time_limit_percent / 100) * 2'880'000.0 *
|
||||
(static_cast<f32>(render_time_limit_percent) / 100.0f))};
|
||||
audio_renderer.SetCommandBuffer(session_id, translated_addr, command_size, time_limit,
|
||||
applet_resource_user_id, reset_command_buffers);
|
||||
reset_command_buffers = false;
|
||||
command_buffer_size = command_size;
|
||||
if (remaining_command_count == 0) {
|
||||
|
|
|
@ -26,12 +26,11 @@ add_library(common STATIC
|
|||
assert.h
|
||||
atomic_helpers.h
|
||||
atomic_ops.h
|
||||
detached_tasks.cpp
|
||||
detached_tasks.h
|
||||
bit_cast.h
|
||||
bit_field.h
|
||||
bit_set.h
|
||||
bit_util.h
|
||||
bounded_threadsafe_queue.h
|
||||
cityhash.cpp
|
||||
cityhash.h
|
||||
common_funcs.h
|
||||
|
@ -41,6 +40,8 @@ add_library(common STATIC
|
|||
container_hash.h
|
||||
demangle.cpp
|
||||
demangle.h
|
||||
detached_tasks.cpp
|
||||
detached_tasks.h
|
||||
div_ceil.h
|
||||
dynamic_library.cpp
|
||||
dynamic_library.h
|
||||
|
@ -151,6 +152,10 @@ add_library(common STATIC
|
|||
zstd_compression.h
|
||||
)
|
||||
|
||||
if (YUZU_ENABLE_PORTABLE)
|
||||
add_compile_definitions(YUZU_ENABLE_PORTABLE)
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
target_sources(common PRIVATE
|
||||
windows/timer_resolution.cpp
|
||||
|
|
|
@ -45,13 +45,13 @@ public:
|
|||
}
|
||||
|
||||
T PopWait() {
|
||||
T t;
|
||||
T t{};
|
||||
Pop<PopMode::Wait>(t);
|
||||
return t;
|
||||
}
|
||||
|
||||
T PopWait(std::stop_token stop_token) {
|
||||
T t;
|
||||
T t{};
|
||||
Pop<PopMode::WaitWithStopToken>(t, stop_token);
|
||||
return t;
|
||||
}
|
||||
|
|
|
@ -528,38 +528,41 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path,
|
|||
// Generic Filesystem Operations
|
||||
|
||||
bool Exists(const fs::path& path) {
|
||||
std::error_code ec;
|
||||
#ifdef ANDROID
|
||||
if (Android::IsContentUri(path)) {
|
||||
return Android::Exists(path);
|
||||
} else {
|
||||
return fs::exists(path);
|
||||
return fs::exists(path, ec);
|
||||
}
|
||||
#else
|
||||
return fs::exists(path);
|
||||
return fs::exists(path, ec);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool IsFile(const fs::path& path) {
|
||||
std::error_code ec;
|
||||
#ifdef ANDROID
|
||||
if (Android::IsContentUri(path)) {
|
||||
return !Android::IsDirectory(path);
|
||||
} else {
|
||||
return fs::is_regular_file(path);
|
||||
return fs::is_regular_file(path, ec);
|
||||
}
|
||||
#else
|
||||
return fs::is_regular_file(path);
|
||||
return fs::is_regular_file(path, ec);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool IsDir(const fs::path& path) {
|
||||
std::error_code ec;
|
||||
#ifdef ANDROID
|
||||
if (Android::IsContentUri(path)) {
|
||||
return Android::IsDirectory(path);
|
||||
} else {
|
||||
return fs::is_directory(path);
|
||||
return fs::is_directory(path, ec);
|
||||
}
|
||||
#else
|
||||
return fs::is_directory(path);
|
||||
return fs::is_directory(path, ec);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
@ -88,8 +88,9 @@ public:
|
|||
fs::path yuzu_path_config;
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifdef YUZU_ENABLE_PORTABLE
|
||||
yuzu_path = GetExeDirectory() / PORTABLE_DIR;
|
||||
|
||||
#endif
|
||||
if (!IsDir(yuzu_path)) {
|
||||
yuzu_path = GetAppDataRoamingDirectory() / YUZU_DIR;
|
||||
}
|
||||
|
@ -101,8 +102,9 @@ public:
|
|||
yuzu_path_cache = yuzu_path / CACHE_DIR;
|
||||
yuzu_path_config = yuzu_path / CONFIG_DIR;
|
||||
#else
|
||||
#ifdef YUZU_ENABLE_PORTABLE
|
||||
yuzu_path = GetCurrentDir() / PORTABLE_DIR;
|
||||
|
||||
#endif
|
||||
if (Exists(yuzu_path) && IsDir(yuzu_path)) {
|
||||
yuzu_path_cache = yuzu_path / CACHE_DIR;
|
||||
yuzu_path_config = yuzu_path / CONFIG_DIR;
|
||||
|
|
|
@ -112,7 +112,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
|
|||
SUB(Service, NCM) \
|
||||
SUB(Service, NFC) \
|
||||
SUB(Service, NFP) \
|
||||
SUB(Service, NGCT) \
|
||||
SUB(Service, NGC) \
|
||||
SUB(Service, NIFM) \
|
||||
SUB(Service, NIM) \
|
||||
SUB(Service, NOTIF) \
|
||||
|
|
|
@ -80,7 +80,7 @@ enum class Class : u8 {
|
|||
Service_NCM, ///< The NCM service
|
||||
Service_NFC, ///< The NFC (Near-field communication) service
|
||||
Service_NFP, ///< The NFP service
|
||||
Service_NGCT, ///< The NGCT (No Good Content for Terra) service
|
||||
Service_NGC, ///< The NGC (No Good Content) service
|
||||
Service_NIFM, ///< The NIFM (Network interface) service
|
||||
Service_NIM, ///< The NIM service
|
||||
Service_NOTIF, ///< The NOTIF (Notification) service
|
||||
|
|
|
@ -19,8 +19,8 @@
|
|||
namespace Common {
|
||||
|
||||
template <typename Condvar, typename Lock, typename Pred>
|
||||
void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred&& pred) {
|
||||
cv.wait(lock, token, std::move(pred));
|
||||
void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred&& pred) {
|
||||
cv.wait(lk, token, std::move(pred));
|
||||
}
|
||||
|
||||
template <typename Rep, typename Period>
|
||||
|
@ -332,13 +332,17 @@ private:
|
|||
namespace Common {
|
||||
|
||||
template <typename Condvar, typename Lock, typename Pred>
|
||||
void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred pred) {
|
||||
void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred pred) {
|
||||
if (token.stop_requested()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::stop_callback callback(token, [&] { cv.notify_all(); });
|
||||
cv.wait(lock, [&] { return pred() || token.stop_requested(); });
|
||||
std::stop_callback callback(token, [&] {
|
||||
{ std::scoped_lock lk2{*lk.mutex()}; }
|
||||
cv.notify_all();
|
||||
});
|
||||
|
||||
cv.wait(lk, [&] { return pred() || token.stop_requested(); });
|
||||
}
|
||||
|
||||
template <typename Rep, typename Period>
|
||||
|
@ -353,8 +357,10 @@ bool StoppableTimedWait(std::stop_token token, const std::chrono::duration<Rep,
|
|||
|
||||
std::stop_callback cb(token, [&] {
|
||||
// Wake up the waiting thread.
|
||||
std::unique_lock lk{m};
|
||||
stop_requested = true;
|
||||
{
|
||||
std::scoped_lock lk{m};
|
||||
stop_requested = true;
|
||||
}
|
||||
cv.notify_one();
|
||||
});
|
||||
|
||||
|
|
|
@ -348,6 +348,8 @@ struct Values {
|
|||
Category::RendererDebug};
|
||||
Setting<bool> disable_shader_loop_safety_checks{
|
||||
linkage, false, "disable_shader_loop_safety_checks", Category::RendererDebug};
|
||||
Setting<bool> enable_renderdoc_hotkey{linkage, false, "renderdoc_hotkey",
|
||||
Category::RendererDebug};
|
||||
|
||||
// System
|
||||
SwitchableSetting<Language, true> language_index{linkage,
|
||||
|
|
|
@ -225,6 +225,16 @@ public:
|
|||
*/
|
||||
[[nodiscard]] virtual constexpr u32 EnumIndex() const = 0;
|
||||
|
||||
/**
|
||||
* @returns True if the underlying type is a floating point storage
|
||||
*/
|
||||
[[nodiscard]] virtual constexpr bool IsFloatingPoint() const = 0;
|
||||
|
||||
/**
|
||||
* @returns True if the underlying type is an integer storage
|
||||
*/
|
||||
[[nodiscard]] virtual constexpr bool IsIntegral() const = 0;
|
||||
|
||||
/*
|
||||
* Switchable settings
|
||||
*/
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <string>
|
||||
#include <typeindex>
|
||||
#include <typeinfo>
|
||||
#include <fmt/core.h>
|
||||
#include "common/common_types.h"
|
||||
#include "common/settings_common.h"
|
||||
#include "common/settings_enums.h"
|
||||
|
@ -115,8 +116,12 @@ protected:
|
|||
} else if constexpr (std::is_same_v<Type, AudioEngine>) {
|
||||
// Compatibility with old AudioEngine setting being a string
|
||||
return CanonicalizeEnum(value_);
|
||||
} else if constexpr (std::is_floating_point_v<Type>) {
|
||||
return fmt::format("{:f}", value_);
|
||||
} else if constexpr (std::is_enum_v<Type>) {
|
||||
return std::to_string(static_cast<u32>(value_));
|
||||
} else {
|
||||
return std::to_string(static_cast<u64>(value_));
|
||||
return std::to_string(value_);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -180,13 +185,15 @@ public:
|
|||
this->SetValue(static_cast<u32>(std::stoul(input)));
|
||||
} else if constexpr (std::is_same_v<Type, bool>) {
|
||||
this->SetValue(input == "true");
|
||||
} else if constexpr (std::is_same_v<Type, AudioEngine>) {
|
||||
this->SetValue(ToEnum<Type>(input));
|
||||
} else if constexpr (std::is_same_v<Type, float>) {
|
||||
this->SetValue(std::stof(input));
|
||||
} else {
|
||||
this->SetValue(static_cast<Type>(std::stoll(input)));
|
||||
}
|
||||
} catch (std::invalid_argument&) {
|
||||
this->SetValue(this->GetDefault());
|
||||
} catch (std::out_of_range&) {
|
||||
this->SetValue(this->GetDefault());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -215,11 +222,27 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool IsFloatingPoint() const final {
|
||||
return std::is_floating_point_v<Type>;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool IsIntegral() const final {
|
||||
return std::is_integral_v<Type>;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string MinVal() const override final {
|
||||
return this->ToString(minimum);
|
||||
if constexpr (std::is_arithmetic_v<Type> && !ranged) {
|
||||
return this->ToString(std::numeric_limits<Type>::min());
|
||||
} else {
|
||||
return this->ToString(minimum);
|
||||
}
|
||||
}
|
||||
[[nodiscard]] std::string MaxVal() const override final {
|
||||
return this->ToString(maximum);
|
||||
if constexpr (std::is_arithmetic_v<Type> && !ranged) {
|
||||
return this->ToString(std::numeric_limits<Type>::max());
|
||||
} else {
|
||||
return this->ToString(maximum);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool Ranged() const override {
|
||||
|
|
|
@ -584,13 +584,23 @@ add_library(core STATIC
|
|||
hle/service/lm/lm.h
|
||||
hle/service/mig/mig.cpp
|
||||
hle/service/mig/mig.h
|
||||
hle/service/mii/types/char_info.cpp
|
||||
hle/service/mii/types/char_info.h
|
||||
hle/service/mii/types/core_data.cpp
|
||||
hle/service/mii/types/core_data.h
|
||||
hle/service/mii/types/raw_data.cpp
|
||||
hle/service/mii/types/raw_data.h
|
||||
hle/service/mii/types/store_data.cpp
|
||||
hle/service/mii/types/store_data.h
|
||||
hle/service/mii/types/ver3_store_data.cpp
|
||||
hle/service/mii/types/ver3_store_data.h
|
||||
hle/service/mii/mii.cpp
|
||||
hle/service/mii/mii.h
|
||||
hle/service/mii/mii_manager.cpp
|
||||
hle/service/mii/mii_manager.h
|
||||
hle/service/mii/raw_data.cpp
|
||||
hle/service/mii/raw_data.h
|
||||
hle/service/mii/types.h
|
||||
hle/service/mii/mii_result.h
|
||||
hle/service/mii/mii_types.h
|
||||
hle/service/mii/mii_util.h
|
||||
hle/service/mm/mm_u.cpp
|
||||
hle/service/mm/mm_u.h
|
||||
hle/service/mnpp/mnpp_app.cpp
|
||||
|
@ -617,8 +627,8 @@ add_library(core STATIC
|
|||
hle/service/nfp/nfp_interface.h
|
||||
hle/service/nfp/nfp_result.h
|
||||
hle/service/nfp/nfp_types.h
|
||||
hle/service/ngct/ngct.cpp
|
||||
hle/service/ngct/ngct.h
|
||||
hle/service/ngc/ngc.cpp
|
||||
hle/service/ngc/ngc.h
|
||||
hle/service/nifm/nifm.cpp
|
||||
hle/service/nifm/nifm.h
|
||||
hle/service/nim/nim.cpp
|
||||
|
@ -854,6 +864,8 @@ add_library(core STATIC
|
|||
telemetry_session.h
|
||||
tools/freezer.cpp
|
||||
tools/freezer.h
|
||||
tools/renderdoc.cpp
|
||||
tools/renderdoc.h
|
||||
)
|
||||
|
||||
if (MSVC)
|
||||
|
@ -869,6 +881,7 @@ else()
|
|||
-Werror=conversion
|
||||
|
||||
-Wno-sign-conversion
|
||||
-Wno-cast-function-type
|
||||
|
||||
$<$<CXX_COMPILER_ID:Clang>:-fsized-deallocation>
|
||||
)
|
||||
|
@ -877,7 +890,7 @@ endif()
|
|||
create_target_directory_groups(core)
|
||||
|
||||
target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core nx_tzdb)
|
||||
target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::opus)
|
||||
target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls renderdoc)
|
||||
if (MINGW)
|
||||
target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY})
|
||||
endif()
|
||||
|
|
|
@ -51,6 +51,7 @@
|
|||
#include "core/reporter.h"
|
||||
#include "core/telemetry_session.h"
|
||||
#include "core/tools/freezer.h"
|
||||
#include "core/tools/renderdoc.h"
|
||||
#include "network/network.h"
|
||||
#include "video_core/host1x/host1x.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
|
@ -281,6 +282,10 @@ struct System::Impl {
|
|||
microprofile_cpu[2] = MICROPROFILE_TOKEN(ARM_CPU2);
|
||||
microprofile_cpu[3] = MICROPROFILE_TOKEN(ARM_CPU3);
|
||||
|
||||
if (Settings::values.enable_renderdoc_hotkey) {
|
||||
renderdoc_api = std::make_unique<Tools::RenderdocAPI>();
|
||||
}
|
||||
|
||||
LOG_DEBUG(Core, "Initialized OK");
|
||||
|
||||
return SystemResultStatus::Success;
|
||||
|
@ -406,6 +411,7 @@ struct System::Impl {
|
|||
gpu_core->NotifyShutdown();
|
||||
}
|
||||
|
||||
Network::CancelPendingSocketOperations();
|
||||
kernel.SuspendApplication(true);
|
||||
if (services) {
|
||||
services->KillNVNFlinger();
|
||||
|
@ -427,6 +433,7 @@ struct System::Impl {
|
|||
debugger.reset();
|
||||
kernel.Shutdown();
|
||||
memory.Reset();
|
||||
Network::RestartSocketOperations();
|
||||
|
||||
if (auto room_member = room_network.GetRoomMember().lock()) {
|
||||
Network::GameInfo game_info{};
|
||||
|
@ -519,6 +526,8 @@ struct System::Impl {
|
|||
std::unique_ptr<Tools::Freezer> memory_freezer;
|
||||
std::array<u8, 0x20> build_id{};
|
||||
|
||||
std::unique_ptr<Tools::RenderdocAPI> renderdoc_api;
|
||||
|
||||
/// Frontend applets
|
||||
Service::AM::Applets::AppletManager applet_manager;
|
||||
|
||||
|
@ -562,6 +571,8 @@ struct System::Impl {
|
|||
|
||||
std::array<Core::GPUDirtyMemoryManager, Core::Hardware::NUM_CPU_CORES>
|
||||
gpu_dirty_memory_write_manager{};
|
||||
|
||||
std::deque<std::vector<u8>> user_channel;
|
||||
};
|
||||
|
||||
System::System() : impl{std::make_unique<Impl>(*this)} {}
|
||||
|
@ -1020,6 +1031,10 @@ const Network::RoomNetwork& System::GetRoomNetwork() const {
|
|||
return impl->room_network;
|
||||
}
|
||||
|
||||
Tools::RenderdocAPI& System::GetRenderdocAPI() {
|
||||
return *impl->renderdoc_api;
|
||||
}
|
||||
|
||||
void System::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) {
|
||||
return impl->kernel.RunServer(std::move(server_manager));
|
||||
}
|
||||
|
@ -1036,6 +1051,10 @@ void System::ExecuteProgram(std::size_t program_index) {
|
|||
}
|
||||
}
|
||||
|
||||
std::deque<std::vector<u8>>& System::GetUserChannel() {
|
||||
return impl->user_channel;
|
||||
}
|
||||
|
||||
void System::RegisterExitCallback(ExitCallback&& callback) {
|
||||
impl->exit_callback = std::move(callback);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
@ -101,6 +102,10 @@ namespace Network {
|
|||
class RoomNetwork;
|
||||
}
|
||||
|
||||
namespace Tools {
|
||||
class RenderdocAPI;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
|
||||
class ARM_Interface;
|
||||
|
@ -412,6 +417,8 @@ public:
|
|||
/// Gets an immutable reference to the Room Network.
|
||||
[[nodiscard]] const Network::RoomNetwork& GetRoomNetwork() const;
|
||||
|
||||
[[nodiscard]] Tools::RenderdocAPI& GetRenderdocAPI();
|
||||
|
||||
void SetExitLocked(bool locked);
|
||||
bool GetExitLocked() const;
|
||||
|
||||
|
@ -459,6 +466,12 @@ public:
|
|||
*/
|
||||
void ExecuteProgram(std::size_t program_index);
|
||||
|
||||
/**
|
||||
* Gets a reference to the user channel stack.
|
||||
* It is used to transfer data between programs.
|
||||
*/
|
||||
[[nodiscard]] std::deque<std::vector<u8>>& GetUserChannel();
|
||||
|
||||
/// Type used for the frontend to designate a callback for System to exit the application.
|
||||
using ExitCallback = std::function<void()>;
|
||||
|
||||
|
|
|
@ -724,14 +724,14 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti
|
|||
continue;
|
||||
}
|
||||
|
||||
const auto index = std::stoul(out[0].substr(8, 2), nullptr, 16);
|
||||
const auto index = std::strtoul(out[0].substr(8, 2).c_str(), nullptr, 16);
|
||||
keyblobs[index] = Common::HexStringToArray<0x90>(out[1]);
|
||||
} else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) {
|
||||
if (!ValidCryptoRevisionString(out[0], 18, 2)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16);
|
||||
const auto index = std::strtoul(out[0].substr(18, 2).c_str(), nullptr, 16);
|
||||
encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]);
|
||||
} else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) {
|
||||
eticket_extended_kek = Common::HexStringToArray<576>(out[1]);
|
||||
|
@ -750,7 +750,7 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti
|
|||
}
|
||||
if (out[0].compare(0, kv.second.size(), kv.second) == 0) {
|
||||
const auto index =
|
||||
std::stoul(out[0].substr(kv.second.size(), 2), nullptr, 16);
|
||||
std::strtoul(out[0].substr(kv.second.size(), 2).c_str(), nullptr, 16);
|
||||
const auto sub = kv.first.second;
|
||||
if (sub == 0) {
|
||||
s128_keys[{kv.first.first, index, 0}] =
|
||||
|
@ -770,7 +770,7 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti
|
|||
const auto& match = kak_names[j];
|
||||
if (out[0].compare(0, std::strlen(match), match) == 0) {
|
||||
const auto index =
|
||||
std::stoul(out[0].substr(std::strlen(match), 2), nullptr, 16);
|
||||
std::strtoul(out[0].substr(std::strlen(match), 2).c_str(), nullptr, 16);
|
||||
s128_keys[{S128KeyType::KeyArea, index, j}] =
|
||||
Common::HexStringToArray<16>(out[1]);
|
||||
}
|
||||
|
|
|
@ -165,7 +165,7 @@ static std::string EscapeStringSequences(std::string in) {
|
|||
void IPSwitchCompiler::ParseFlag(const std::string& line) {
|
||||
if (StartsWith(line, "@flag offset_shift ")) {
|
||||
// Offset Shift Flag
|
||||
offset_shift = std::stoll(line.substr(19), nullptr, 0);
|
||||
offset_shift = std::strtoll(line.substr(19).c_str(), nullptr, 0);
|
||||
} else if (StartsWith(line, "@little-endian")) {
|
||||
// Set values to read as little endian
|
||||
is_little_endian = true;
|
||||
|
@ -263,7 +263,7 @@ void IPSwitchCompiler::Parse() {
|
|||
// 11 - 8 hex digit offset + space + minimum two digit overwrite val
|
||||
if (patch_line.length() < 11)
|
||||
break;
|
||||
auto offset = std::stoul(patch_line.substr(0, 8), nullptr, 16);
|
||||
auto offset = std::strtoul(patch_line.substr(0, 8).c_str(), nullptr, 16);
|
||||
offset += static_cast<unsigned long>(offset_shift);
|
||||
|
||||
std::vector<u8> replace;
|
||||
|
|
|
@ -45,6 +45,10 @@ CNMT::CNMT(CNMTHeader header_, OptionalHeader opt_header_,
|
|||
|
||||
CNMT::~CNMT() = default;
|
||||
|
||||
const CNMTHeader& CNMT::GetHeader() const {
|
||||
return header;
|
||||
}
|
||||
|
||||
u64 CNMT::GetTitleID() const {
|
||||
return header.title_id;
|
||||
}
|
||||
|
|
|
@ -89,6 +89,7 @@ public:
|
|||
std::vector<ContentRecord> content_records_, std::vector<MetaRecord> meta_records_);
|
||||
~CNMT();
|
||||
|
||||
const CNMTHeader& GetHeader() const;
|
||||
u64 GetTitleID() const;
|
||||
u32 GetTitleVersion() const;
|
||||
TitleType GetType() const;
|
||||
|
|
|
@ -294,11 +294,11 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st
|
|||
return out;
|
||||
}
|
||||
|
||||
bool PatchManager::HasNSOPatch(const BuildID& build_id_) const {
|
||||
bool PatchManager::HasNSOPatch(const BuildID& build_id_, std::string_view name) const {
|
||||
const auto build_id_raw = Common::HexToString(build_id_);
|
||||
const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
|
||||
|
||||
LOG_INFO(Loader, "Querying NSO patch existence for build_id={}", build_id);
|
||||
LOG_INFO(Loader, "Querying NSO patch existence for build_id={}, name={}", build_id, name);
|
||||
|
||||
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
|
||||
if (load_dir == nullptr) {
|
||||
|
|
|
@ -52,7 +52,7 @@ public:
|
|||
|
||||
// Checks to see if PatchNSO() will have any effect given the NSO's build ID.
|
||||
// Used to prevent expensive copies in NSO loader.
|
||||
[[nodiscard]] bool HasNSOPatch(const BuildID& build_id) const;
|
||||
[[nodiscard]] bool HasNSOPatch(const BuildID& build_id, std::string_view name) const;
|
||||
|
||||
// Creates a CheatList object with all
|
||||
[[nodiscard]] std::vector<Core::Memory::CheatEntry> CreateCheatList(
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "common/fs/path_util.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/common_funcs.h"
|
||||
|
@ -625,7 +626,7 @@ InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_ex
|
|||
nca->GetTitleId() != title_id) {
|
||||
// Create fake cnmt for patch to multiprogram application
|
||||
const auto sub_nca_result =
|
||||
InstallEntry(*nca, TitleType::Update, overwrite_if_exists, copy);
|
||||
InstallEntry(*nca, cnmt.GetHeader(), record, overwrite_if_exists, copy);
|
||||
if (sub_nca_result != InstallResult::Success) {
|
||||
return sub_nca_result;
|
||||
}
|
||||
|
@ -672,6 +673,31 @@ InstallResult RegisteredCache::InstallEntry(const NCA& nca, TitleType type,
|
|||
return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id);
|
||||
}
|
||||
|
||||
InstallResult RegisteredCache::InstallEntry(const NCA& nca, const CNMTHeader& base_header,
|
||||
const ContentRecord& base_record,
|
||||
bool overwrite_if_exists, const VfsCopyFunction& copy) {
|
||||
const CNMTHeader header{
|
||||
.title_id = nca.GetTitleId(),
|
||||
.title_version = base_header.title_version,
|
||||
.type = base_header.type,
|
||||
.reserved = {},
|
||||
.table_offset = 0x10,
|
||||
.number_content_entries = 1,
|
||||
.number_meta_entries = 0,
|
||||
.attributes = 0,
|
||||
.reserved2 = {},
|
||||
.is_committed = 0,
|
||||
.required_download_system_version = 0,
|
||||
.reserved3 = {},
|
||||
};
|
||||
const OptionalHeader opt_header{0, 0};
|
||||
const CNMT new_cnmt(header, opt_header, {base_record}, {});
|
||||
if (!RawInstallYuzuMeta(new_cnmt)) {
|
||||
return InstallResult::ErrorMetaFailed;
|
||||
}
|
||||
return RawInstallNCA(nca, copy, overwrite_if_exists, base_record.nca_id);
|
||||
}
|
||||
|
||||
bool RegisteredCache::RemoveExistingEntry(u64 title_id) const {
|
||||
bool removed_data = false;
|
||||
|
||||
|
@ -726,7 +752,9 @@ bool RegisteredCache::RemoveExistingEntry(u64 title_id) const {
|
|||
for (u8 i = 0; i < 0x10; i++) {
|
||||
const auto meta_dir = dir->CreateDirectoryRelative("yuzu_meta");
|
||||
const auto filename = GetCNMTName(TitleType::Update, title_id + i);
|
||||
removed_data |= meta_dir->DeleteFile(filename);
|
||||
if (meta_dir->GetFile(filename)) {
|
||||
removed_data |= meta_dir->DeleteFile(filename);
|
||||
}
|
||||
}
|
||||
|
||||
return removed_data;
|
||||
|
|
|
@ -24,6 +24,7 @@ enum class NCAContentType : u8;
|
|||
enum class TitleType : u8;
|
||||
|
||||
struct ContentRecord;
|
||||
struct CNMTHeader;
|
||||
struct MetaRecord;
|
||||
class RegisteredCache;
|
||||
|
||||
|
@ -169,6 +170,10 @@ public:
|
|||
InstallResult InstallEntry(const NCA& nca, TitleType type, bool overwrite_if_exists = false,
|
||||
const VfsCopyFunction& copy = &VfsRawCopy);
|
||||
|
||||
InstallResult InstallEntry(const NCA& nca, const CNMTHeader& base_header,
|
||||
const ContentRecord& base_record, bool overwrite_if_exists = false,
|
||||
const VfsCopyFunction& copy = &VfsRawCopy);
|
||||
|
||||
// Removes an existing entry based on title id
|
||||
bool RemoveExistingEntry(u64 title_id) const;
|
||||
|
||||
|
|
|
@ -154,6 +154,14 @@ NpadIdType HIDCore::GetFirstDisconnectedNpadId() const {
|
|||
return NpadIdType::Player1;
|
||||
}
|
||||
|
||||
void HIDCore::SetLastActiveController(NpadIdType npad_id) {
|
||||
last_active_controller = npad_id;
|
||||
}
|
||||
|
||||
NpadIdType HIDCore::GetLastActiveController() const {
|
||||
return last_active_controller;
|
||||
}
|
||||
|
||||
void HIDCore::EnableAllControllerConfiguration() {
|
||||
player_1->EnableConfiguration();
|
||||
player_2->EnableConfiguration();
|
||||
|
|
|
@ -48,6 +48,12 @@ public:
|
|||
/// Returns the first disconnected npad id
|
||||
NpadIdType GetFirstDisconnectedNpadId() const;
|
||||
|
||||
/// Sets the npad id of the last active controller
|
||||
void SetLastActiveController(NpadIdType npad_id);
|
||||
|
||||
/// Returns the npad id of the last controller that pushed a button
|
||||
NpadIdType GetLastActiveController() const;
|
||||
|
||||
/// Sets all emulated controllers into configuring mode.
|
||||
void EnableAllControllerConfiguration();
|
||||
|
||||
|
@ -77,6 +83,7 @@ private:
|
|||
std::unique_ptr<EmulatedConsole> console;
|
||||
std::unique_ptr<EmulatedDevices> devices;
|
||||
NpadStyleTag supported_style_tag{NpadStyleSet::All};
|
||||
NpadIdType last_active_controller{NpadIdType::Handheld};
|
||||
};
|
||||
|
||||
} // namespace Core::HID
|
||||
|
|
|
@ -96,6 +96,7 @@ Result KProcess::Initialize(KProcess* process, Core::System& system, std::string
|
|||
process->m_is_suspended = false;
|
||||
process->m_schedule_count = 0;
|
||||
process->m_is_handle_table_initialized = false;
|
||||
process->m_is_hbl = false;
|
||||
|
||||
// Open a reference to the resource limit.
|
||||
process->m_resource_limit->Open();
|
||||
|
@ -351,12 +352,14 @@ Result KProcess::SetActivity(ProcessActivity activity) {
|
|||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size) {
|
||||
Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,
|
||||
bool is_hbl) {
|
||||
m_program_id = metadata.GetTitleID();
|
||||
m_ideal_core = metadata.GetMainThreadCore();
|
||||
m_is_64bit_process = metadata.Is64BitProgram();
|
||||
m_system_resource_size = metadata.GetSystemResourceSize();
|
||||
m_image_size = code_size;
|
||||
m_is_hbl = is_hbl;
|
||||
|
||||
if (metadata.GetAddressSpaceType() == FileSys::ProgramAddressSpaceType::Is39Bit) {
|
||||
// For 39-bit processes, the ASLR region starts at 0x800'0000 and is ~512GiB large.
|
||||
|
|
|
@ -338,7 +338,8 @@ public:
|
|||
* @returns ResultSuccess if all relevant metadata was able to be
|
||||
* loaded and parsed. Otherwise, an error code is returned.
|
||||
*/
|
||||
Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size);
|
||||
Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,
|
||||
bool is_hbl);
|
||||
|
||||
/**
|
||||
* Starts the main application thread for this process.
|
||||
|
@ -368,6 +369,10 @@ public:
|
|||
return GetProcessId();
|
||||
}
|
||||
|
||||
bool IsHbl() const {
|
||||
return m_is_hbl;
|
||||
}
|
||||
|
||||
bool IsSignaled() const override;
|
||||
|
||||
void DoWorkerTaskImpl();
|
||||
|
@ -525,6 +530,7 @@ private:
|
|||
bool m_is_immortal{};
|
||||
bool m_is_handle_table_initialized{};
|
||||
bool m_is_initialized{};
|
||||
bool m_is_hbl{};
|
||||
|
||||
std::atomic<u16> m_num_running_threads{};
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ Result OutputDebugString(Core::System& system, u64 address, u64 len) {
|
|||
|
||||
std::string str(len, '\0');
|
||||
GetCurrentMemory(system.Kernel()).ReadBlock(address, str.data(), str.size());
|
||||
LOG_DEBUG(Debug_Emulated, "{}", str);
|
||||
LOG_INFO(Debug_Emulated, "{}", str);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "core/core.h"
|
||||
#include "core/debugger/debugger.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/kernel/k_thread.h"
|
||||
#include "core/hle/kernel/svc.h"
|
||||
#include "core/hle/kernel/svc_types.h"
|
||||
|
@ -107,7 +108,10 @@ void Break(Core::System& system, BreakReason reason, u64 info1, u64 info2) {
|
|||
system.ArmInterface(static_cast<std::size_t>(thread_processor_id)).LogBacktrace();
|
||||
}
|
||||
|
||||
if (system.DebuggerEnabled()) {
|
||||
const bool is_hbl = GetCurrentProcess(system.Kernel()).IsHbl();
|
||||
const bool should_break = is_hbl || !notification_only;
|
||||
|
||||
if (system.DebuggerEnabled() && should_break) {
|
||||
auto* thread = system.Kernel().GetCurrentEmuThread();
|
||||
system.GetDebugger().NotifyThreadStopped(thread);
|
||||
thread->RequestSuspend(Kernel::SuspendType::Debug);
|
||||
|
|
|
@ -62,7 +62,7 @@ enum class ErrorModule : u32 {
|
|||
XCD = 108,
|
||||
TMP451 = 109,
|
||||
NIFM = 110,
|
||||
Hwopus = 111,
|
||||
HwOpus = 111,
|
||||
LSM6DS3 = 112,
|
||||
Bluetooth = 113,
|
||||
VI = 114,
|
||||
|
|
|
@ -46,7 +46,7 @@ constexpr Result ResultNoMessages{ErrorModule::AM, 3};
|
|||
constexpr Result ResultInvalidOffset{ErrorModule::AM, 503};
|
||||
|
||||
enum class LaunchParameterKind : u32 {
|
||||
ApplicationSpecific = 1,
|
||||
UserChannel = 1,
|
||||
AccountPreselectedUser = 2,
|
||||
};
|
||||
|
||||
|
@ -1386,7 +1386,7 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_)
|
|||
{25, &IApplicationFunctions::ExtendSaveData, "ExtendSaveData"},
|
||||
{26, &IApplicationFunctions::GetSaveDataSize, "GetSaveDataSize"},
|
||||
{27, &IApplicationFunctions::CreateCacheStorage, "CreateCacheStorage"},
|
||||
{28, nullptr, "GetSaveDataSizeMax"},
|
||||
{28, &IApplicationFunctions::GetSaveDataSizeMax, "GetSaveDataSizeMax"},
|
||||
{29, nullptr, "GetCacheStorageMax"},
|
||||
{30, &IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed, "BeginBlockingHomeButtonShortAndLongPressed"},
|
||||
{31, &IApplicationFunctions::EndBlockingHomeButtonShortAndLongPressed, "EndBlockingHomeButtonShortAndLongPressed"},
|
||||
|
@ -1518,27 +1518,26 @@ void IApplicationFunctions::PopLaunchParameter(HLERequestContext& ctx) {
|
|||
IPC::RequestParser rp{ctx};
|
||||
const auto kind = rp.PopEnum<LaunchParameterKind>();
|
||||
|
||||
LOG_DEBUG(Service_AM, "called, kind={:08X}", kind);
|
||||
LOG_INFO(Service_AM, "called, kind={:08X}", kind);
|
||||
|
||||
if (kind == LaunchParameterKind::ApplicationSpecific && !launch_popped_application_specific) {
|
||||
const auto backend = BCAT::CreateBackendFromSettings(system, [this](u64 tid) {
|
||||
return system.GetFileSystemController().GetBCATDirectory(tid);
|
||||
});
|
||||
const auto build_id_full = system.GetApplicationProcessBuildID();
|
||||
u64 build_id{};
|
||||
std::memcpy(&build_id, build_id_full.data(), sizeof(u64));
|
||||
|
||||
auto data =
|
||||
backend->GetLaunchParameter({system.GetApplicationProcessProgramID(), build_id});
|
||||
if (data.has_value()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushIpcInterface<IStorage>(system, std::move(*data));
|
||||
launch_popped_application_specific = true;
|
||||
if (kind == LaunchParameterKind::UserChannel) {
|
||||
auto channel = system.GetUserChannel();
|
||||
if (channel.empty()) {
|
||||
LOG_ERROR(Service_AM, "Attempted to load launch parameter but none was found!");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(AM::ResultNoDataInChannel);
|
||||
return;
|
||||
}
|
||||
|
||||
auto data = channel.back();
|
||||
channel.pop_back();
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushIpcInterface<IStorage>(system, std::move(data));
|
||||
} else if (kind == LaunchParameterKind::AccountPreselectedUser &&
|
||||
!launch_popped_account_preselect) {
|
||||
// TODO: Verify this is hw-accurate
|
||||
LaunchParameterAccountPreselectedUser params{};
|
||||
|
||||
params.magic = LAUNCH_PARAMETER_ACCOUNT_PRESELECTED_USER_MAGIC;
|
||||
|
@ -1550,7 +1549,6 @@ void IApplicationFunctions::PopLaunchParameter(HLERequestContext& ctx) {
|
|||
params.current_user = *uuid;
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
|
||||
rb.Push(ResultSuccess);
|
||||
|
||||
std::vector<u8> buffer(sizeof(LaunchParameterAccountPreselectedUser));
|
||||
|
@ -1558,12 +1556,11 @@ void IApplicationFunctions::PopLaunchParameter(HLERequestContext& ctx) {
|
|||
|
||||
rb.PushIpcInterface<IStorage>(system, std::move(buffer));
|
||||
launch_popped_account_preselect = true;
|
||||
return;
|
||||
} else {
|
||||
LOG_ERROR(Service_AM, "Unknown launch parameter kind.");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(AM::ResultNoDataInChannel);
|
||||
}
|
||||
|
||||
LOG_ERROR(Service_AM, "Attempted to load launch parameter but none was found!");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(AM::ResultNoDataInChannel);
|
||||
}
|
||||
|
||||
void IApplicationFunctions::CreateApplicationAndRequestToStartForQuest(HLERequestContext& ctx) {
|
||||
|
@ -1824,6 +1821,18 @@ void IApplicationFunctions::CreateCacheStorage(HLERequestContext& ctx) {
|
|||
rb.PushRaw(resp);
|
||||
}
|
||||
|
||||
void IApplicationFunctions::GetSaveDataSizeMax(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
|
||||
constexpr u64 size_max_normal = 0xFFFFFFF;
|
||||
constexpr u64 size_max_journal = 0xFFFFFFF;
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(size_max_normal);
|
||||
rb.Push(size_max_journal);
|
||||
}
|
||||
|
||||
void IApplicationFunctions::QueryApplicationPlayStatistics(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
|
||||
|
@ -1855,14 +1864,22 @@ void IApplicationFunctions::ExecuteProgram(HLERequestContext& ctx) {
|
|||
}
|
||||
|
||||
void IApplicationFunctions::ClearUserChannel(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
|
||||
system.GetUserChannel().clear();
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
||||
void IApplicationFunctions::UnpopToUserChannel(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto storage = rp.PopIpcInterface<IStorage>().lock();
|
||||
if (storage) {
|
||||
system.GetUserChannel().push_back(storage->GetData());
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
|
|
|
@ -316,6 +316,7 @@ private:
|
|||
void ExtendSaveData(HLERequestContext& ctx);
|
||||
void GetSaveDataSize(HLERequestContext& ctx);
|
||||
void CreateCacheStorage(HLERequestContext& ctx);
|
||||
void GetSaveDataSizeMax(HLERequestContext& ctx);
|
||||
void BeginBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx);
|
||||
void EndBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx);
|
||||
void BeginBlockingHomeButton(HLERequestContext& ctx);
|
||||
|
@ -339,7 +340,6 @@ private:
|
|||
|
||||
KernelHelpers::ServiceContext service_context;
|
||||
|
||||
bool launch_popped_application_specific = false;
|
||||
bool launch_popped_account_preselect = false;
|
||||
s32 previous_program_index{-1};
|
||||
Kernel::KEvent* gpu_error_detected_event;
|
||||
|
|
|
@ -85,15 +85,18 @@ void MiiEdit::Execute() {
|
|||
break;
|
||||
case MiiEditAppletMode::CreateMii:
|
||||
case MiiEditAppletMode::EditMii: {
|
||||
Service::Mii::MiiManager mii_manager;
|
||||
Mii::CharInfo char_info{};
|
||||
Mii::StoreData store_data{};
|
||||
store_data.BuildBase(Mii::Gender::Male);
|
||||
char_info.SetFromStoreData(store_data);
|
||||
|
||||
const MiiEditCharInfo char_info{
|
||||
const MiiEditCharInfo edit_char_info{
|
||||
.mii_info{applet_input_common.applet_mode == MiiEditAppletMode::EditMii
|
||||
? applet_input_v4.char_info.mii_info
|
||||
: mii_manager.BuildBase(Mii::Gender::Male)},
|
||||
: char_info},
|
||||
};
|
||||
|
||||
MiiEditOutputForCharInfoEditing(MiiEditResult::Success, char_info);
|
||||
MiiEditOutputForCharInfoEditing(MiiEditResult::Success, edit_char_info);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/mii/types.h"
|
||||
#include "common/uuid.h"
|
||||
#include "core/hle/service/mii/types/char_info.h"
|
||||
|
||||
namespace Service::AM::Applets {
|
||||
|
||||
|
|
|
@ -20,4 +20,16 @@ constexpr Result ResultNotSupported{ErrorModule::Audio, 513};
|
|||
constexpr Result ResultInvalidHandle{ErrorModule::Audio, 1536};
|
||||
constexpr Result ResultInvalidRevision{ErrorModule::Audio, 1537};
|
||||
|
||||
constexpr Result ResultLibOpusAllocFail{ErrorModule::HwOpus, 7};
|
||||
constexpr Result ResultInputDataTooSmall{ErrorModule::HwOpus, 8};
|
||||
constexpr Result ResultLibOpusInvalidState{ErrorModule::HwOpus, 6};
|
||||
constexpr Result ResultLibOpusUnimplemented{ErrorModule::HwOpus, 5};
|
||||
constexpr Result ResultLibOpusInvalidPacket{ErrorModule::HwOpus, 17};
|
||||
constexpr Result ResultLibOpusInternalError{ErrorModule::HwOpus, 4};
|
||||
constexpr Result ResultBufferTooSmall{ErrorModule::HwOpus, 3};
|
||||
constexpr Result ResultLibOpusBadArg{ErrorModule::HwOpus, 2};
|
||||
constexpr Result ResultInvalidOpusDSPReturnCode{ErrorModule::HwOpus, 259};
|
||||
constexpr Result ResultInvalidOpusSampleRate{ErrorModule::HwOpus, 1001};
|
||||
constexpr Result ResultInvalidOpusChannelCount{ErrorModule::HwOpus, 1002};
|
||||
|
||||
} // namespace Service::Audio
|
||||
|
|
|
@ -1,420 +1,506 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <opus.h>
|
||||
#include <opus_multistream.h>
|
||||
|
||||
#include "audio_core/opus/decoder.h"
|
||||
#include "audio_core/opus/parameters.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/scratch_buffer.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/service/audio/hwopus.h"
|
||||
#include "core/hle/service/ipc_helpers.h"
|
||||
|
||||
namespace Service::Audio {
|
||||
namespace {
|
||||
struct OpusDeleter {
|
||||
void operator()(OpusMSDecoder* ptr) const {
|
||||
opus_multistream_decoder_destroy(ptr);
|
||||
}
|
||||
};
|
||||
using namespace AudioCore::OpusDecoder;
|
||||
|
||||
using OpusDecoderPtr = std::unique_ptr<OpusMSDecoder, OpusDeleter>;
|
||||
|
||||
struct OpusPacketHeader {
|
||||
// Packet size in bytes.
|
||||
u32_be size;
|
||||
// Indicates the final range of the codec's entropy coder.
|
||||
u32_be final_range;
|
||||
};
|
||||
static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusHeader is an invalid size");
|
||||
|
||||
class OpusDecoderState {
|
||||
class IHardwareOpusDecoder final : public ServiceFramework<IHardwareOpusDecoder> {
|
||||
public:
|
||||
/// Describes extra behavior that may be asked of the decoding context.
|
||||
enum class ExtraBehavior {
|
||||
/// No extra behavior.
|
||||
None,
|
||||
|
||||
/// Resets the decoder context back to a freshly initialized state.
|
||||
ResetContext,
|
||||
};
|
||||
|
||||
enum class PerfTime {
|
||||
Disabled,
|
||||
Enabled,
|
||||
};
|
||||
|
||||
explicit OpusDecoderState(OpusDecoderPtr decoder_, u32 sample_rate_, u32 channel_count_)
|
||||
: decoder{std::move(decoder_)}, sample_rate{sample_rate_}, channel_count{channel_count_} {}
|
||||
|
||||
// Decodes interleaved Opus packets. Optionally allows reporting time taken to
|
||||
// perform the decoding, as well as any relevant extra behavior.
|
||||
void DecodeInterleaved(HLERequestContext& ctx, PerfTime perf_time,
|
||||
ExtraBehavior extra_behavior) {
|
||||
if (perf_time == PerfTime::Disabled) {
|
||||
DecodeInterleavedHelper(ctx, nullptr, extra_behavior);
|
||||
} else {
|
||||
u64 performance = 0;
|
||||
DecodeInterleavedHelper(ctx, &performance, extra_behavior);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void DecodeInterleavedHelper(HLERequestContext& ctx, u64* performance,
|
||||
ExtraBehavior extra_behavior) {
|
||||
u32 consumed = 0;
|
||||
u32 sample_count = 0;
|
||||
samples.resize_destructive(ctx.GetWriteBufferNumElements<opus_int16>());
|
||||
|
||||
if (extra_behavior == ExtraBehavior::ResetContext) {
|
||||
ResetDecoderContext();
|
||||
}
|
||||
|
||||
if (!DecodeOpusData(consumed, sample_count, ctx.ReadBuffer(), samples, performance)) {
|
||||
LOG_ERROR(Audio, "Failed to decode opus data");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
// TODO(ogniK): Use correct error code
|
||||
rb.Push(ResultUnknown);
|
||||
return;
|
||||
}
|
||||
|
||||
const u32 param_size = performance != nullptr ? 6 : 4;
|
||||
IPC::ResponseBuilder rb{ctx, param_size};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push<u32>(consumed);
|
||||
rb.Push<u32>(sample_count);
|
||||
if (performance) {
|
||||
rb.Push<u64>(*performance);
|
||||
}
|
||||
ctx.WriteBuffer(samples);
|
||||
}
|
||||
|
||||
bool DecodeOpusData(u32& consumed, u32& sample_count, std::span<const u8> input,
|
||||
std::span<opus_int16> output, u64* out_performance_time) const {
|
||||
const auto start_time = std::chrono::steady_clock::now();
|
||||
const std::size_t raw_output_sz = output.size() * sizeof(opus_int16);
|
||||
if (sizeof(OpusPacketHeader) > input.size()) {
|
||||
LOG_ERROR(Audio, "Input is smaller than the header size, header_sz={}, input_sz={}",
|
||||
sizeof(OpusPacketHeader), input.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
OpusPacketHeader hdr{};
|
||||
std::memcpy(&hdr, input.data(), sizeof(OpusPacketHeader));
|
||||
if (sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size) > input.size()) {
|
||||
LOG_ERROR(Audio, "Input does not fit in the opus header size. data_sz={}, input_sz={}",
|
||||
sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size), input.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto frame = input.data() + sizeof(OpusPacketHeader);
|
||||
const auto decoded_sample_count = opus_packet_get_nb_samples(
|
||||
frame, static_cast<opus_int32>(input.size() - sizeof(OpusPacketHeader)),
|
||||
static_cast<opus_int32>(sample_rate));
|
||||
if (decoded_sample_count * channel_count * sizeof(u16) > raw_output_sz) {
|
||||
LOG_ERROR(
|
||||
Audio,
|
||||
"Decoded data does not fit into the output data, decoded_sz={}, raw_output_sz={}",
|
||||
decoded_sample_count * channel_count * sizeof(u16), raw_output_sz);
|
||||
return false;
|
||||
}
|
||||
|
||||
const int frame_size = (static_cast<int>(raw_output_sz / sizeof(s16) / channel_count));
|
||||
const auto out_sample_count =
|
||||
opus_multistream_decode(decoder.get(), frame, hdr.size, output.data(), frame_size, 0);
|
||||
if (out_sample_count < 0) {
|
||||
LOG_ERROR(Audio,
|
||||
"Incorrect sample count received from opus_decode, "
|
||||
"output_sample_count={}, frame_size={}, data_sz_from_hdr={}",
|
||||
out_sample_count, frame_size, static_cast<u32>(hdr.size));
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto end_time = std::chrono::steady_clock::now() - start_time;
|
||||
sample_count = out_sample_count;
|
||||
consumed = static_cast<u32>(sizeof(OpusPacketHeader) + hdr.size);
|
||||
if (out_performance_time != nullptr) {
|
||||
*out_performance_time =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(end_time).count();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ResetDecoderContext() {
|
||||
ASSERT(decoder != nullptr);
|
||||
|
||||
opus_multistream_decoder_ctl(decoder.get(), OPUS_RESET_STATE);
|
||||
}
|
||||
|
||||
OpusDecoderPtr decoder;
|
||||
u32 sample_rate;
|
||||
u32 channel_count;
|
||||
Common::ScratchBuffer<opus_int16> samples;
|
||||
};
|
||||
|
||||
class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> {
|
||||
public:
|
||||
explicit IHardwareOpusDecoderManager(Core::System& system_, OpusDecoderState decoder_state_)
|
||||
: ServiceFramework{system_, "IHardwareOpusDecoderManager"}, decoder_state{
|
||||
std::move(decoder_state_)} {
|
||||
explicit IHardwareOpusDecoder(Core::System& system_, HardwareOpus& hardware_opus)
|
||||
: ServiceFramework{system_, "IHardwareOpusDecoder"},
|
||||
impl{std::make_unique<AudioCore::OpusDecoder::OpusDecoder>(system_, hardware_opus)} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IHardwareOpusDecoderManager::DecodeInterleavedOld, "DecodeInterleavedOld"},
|
||||
{1, nullptr, "SetContext"},
|
||||
{2, nullptr, "DecodeInterleavedForMultiStreamOld"},
|
||||
{3, nullptr, "SetContextForMultiStream"},
|
||||
{4, &IHardwareOpusDecoderManager::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"},
|
||||
{5, nullptr, "DecodeInterleavedForMultiStreamWithPerfOld"},
|
||||
{6, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleavedWithPerfAndResetOld"},
|
||||
{7, nullptr, "DecodeInterleavedForMultiStreamWithPerfAndResetOld"},
|
||||
{8, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleaved"},
|
||||
{9, &IHardwareOpusDecoderManager::DecodeInterleavedForMultiStream, "DecodeInterleavedForMultiStream"},
|
||||
{0, &IHardwareOpusDecoder::DecodeInterleavedOld, "DecodeInterleavedOld"},
|
||||
{1, &IHardwareOpusDecoder::SetContext, "SetContext"},
|
||||
{2, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamOld, "DecodeInterleavedForMultiStreamOld"},
|
||||
{3, &IHardwareOpusDecoder::SetContextForMultiStream, "SetContextForMultiStream"},
|
||||
{4, &IHardwareOpusDecoder::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"},
|
||||
{5, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamWithPerfOld, "DecodeInterleavedForMultiStreamWithPerfOld"},
|
||||
{6, &IHardwareOpusDecoder::DecodeInterleavedWithPerfAndResetOld, "DecodeInterleavedWithPerfAndResetOld"},
|
||||
{7, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamWithPerfAndResetOld, "DecodeInterleavedForMultiStreamWithPerfAndResetOld"},
|
||||
{8, &IHardwareOpusDecoder::DecodeInterleaved, "DecodeInterleaved"},
|
||||
{9, &IHardwareOpusDecoder::DecodeInterleavedForMultiStream, "DecodeInterleavedForMultiStream"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
Result Initialize(OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory,
|
||||
u64 transfer_memory_size) {
|
||||
return impl->Initialize(params, transfer_memory, transfer_memory_size);
|
||||
}
|
||||
|
||||
Result Initialize(OpusMultiStreamParametersEx& params, Kernel::KTransferMemory* transfer_memory,
|
||||
u64 transfer_memory_size) {
|
||||
return impl->Initialize(params, transfer_memory, transfer_memory_size);
|
||||
}
|
||||
|
||||
private:
|
||||
void DecodeInterleavedOld(HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Audio, "called");
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Disabled,
|
||||
OpusDecoderState::ExtraBehavior::None);
|
||||
auto input_data{ctx.ReadBuffer(0)};
|
||||
output_data.resize_destructive(ctx.GetWriteBufferSize());
|
||||
|
||||
u32 size{};
|
||||
u32 sample_count{};
|
||||
auto result =
|
||||
impl->DecodeInterleaved(&size, nullptr, &sample_count, input_data, output_data, false);
|
||||
|
||||
LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {}", size, sample_count);
|
||||
|
||||
ctx.WriteBuffer(output_data);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(result);
|
||||
rb.Push(size);
|
||||
rb.Push(sample_count);
|
||||
}
|
||||
|
||||
void SetContext(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
LOG_DEBUG(Service_Audio, "called");
|
||||
|
||||
auto input_data{ctx.ReadBuffer(0)};
|
||||
auto result = impl->SetContext(input_data);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(result);
|
||||
}
|
||||
|
||||
void DecodeInterleavedForMultiStreamOld(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
auto input_data{ctx.ReadBuffer(0)};
|
||||
output_data.resize_destructive(ctx.GetWriteBufferSize());
|
||||
|
||||
u32 size{};
|
||||
u32 sample_count{};
|
||||
auto result = impl->DecodeInterleavedForMultiStream(&size, nullptr, &sample_count,
|
||||
input_data, output_data, false);
|
||||
|
||||
LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {}", size, sample_count);
|
||||
|
||||
ctx.WriteBuffer(output_data);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(result);
|
||||
rb.Push(size);
|
||||
rb.Push(sample_count);
|
||||
}
|
||||
|
||||
void SetContextForMultiStream(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
LOG_DEBUG(Service_Audio, "called");
|
||||
|
||||
auto input_data{ctx.ReadBuffer(0)};
|
||||
auto result = impl->SetContext(input_data);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(result);
|
||||
}
|
||||
|
||||
void DecodeInterleavedWithPerfOld(HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Audio, "called");
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled,
|
||||
OpusDecoderState::ExtraBehavior::None);
|
||||
auto input_data{ctx.ReadBuffer(0)};
|
||||
output_data.resize_destructive(ctx.GetWriteBufferSize());
|
||||
|
||||
u32 size{};
|
||||
u32 sample_count{};
|
||||
u64 time_taken{};
|
||||
auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data,
|
||||
output_data, false);
|
||||
|
||||
LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {} time taken {}", size,
|
||||
sample_count, time_taken);
|
||||
|
||||
ctx.WriteBuffer(output_data);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(result);
|
||||
rb.Push(size);
|
||||
rb.Push(sample_count);
|
||||
rb.Push(time_taken);
|
||||
}
|
||||
|
||||
void DecodeInterleavedForMultiStreamWithPerfOld(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
auto input_data{ctx.ReadBuffer(0)};
|
||||
output_data.resize_destructive(ctx.GetWriteBufferSize());
|
||||
|
||||
u32 size{};
|
||||
u32 sample_count{};
|
||||
u64 time_taken{};
|
||||
auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count,
|
||||
input_data, output_data, false);
|
||||
|
||||
LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {} time taken {}", size,
|
||||
sample_count, time_taken);
|
||||
|
||||
ctx.WriteBuffer(output_data);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(result);
|
||||
rb.Push(size);
|
||||
rb.Push(sample_count);
|
||||
rb.Push(time_taken);
|
||||
}
|
||||
|
||||
void DecodeInterleavedWithPerfAndResetOld(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
auto reset{rp.Pop<bool>()};
|
||||
|
||||
auto input_data{ctx.ReadBuffer(0)};
|
||||
output_data.resize_destructive(ctx.GetWriteBufferSize());
|
||||
|
||||
u32 size{};
|
||||
u32 sample_count{};
|
||||
u64 time_taken{};
|
||||
auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data,
|
||||
output_data, reset);
|
||||
|
||||
LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}",
|
||||
reset, size, sample_count, time_taken);
|
||||
|
||||
ctx.WriteBuffer(output_data);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(result);
|
||||
rb.Push(size);
|
||||
rb.Push(sample_count);
|
||||
rb.Push(time_taken);
|
||||
}
|
||||
|
||||
void DecodeInterleavedForMultiStreamWithPerfAndResetOld(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
auto reset{rp.Pop<bool>()};
|
||||
|
||||
auto input_data{ctx.ReadBuffer(0)};
|
||||
output_data.resize_destructive(ctx.GetWriteBufferSize());
|
||||
|
||||
u32 size{};
|
||||
u32 sample_count{};
|
||||
u64 time_taken{};
|
||||
auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count,
|
||||
input_data, output_data, reset);
|
||||
|
||||
LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}",
|
||||
reset, size, sample_count, time_taken);
|
||||
|
||||
ctx.WriteBuffer(output_data);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(result);
|
||||
rb.Push(size);
|
||||
rb.Push(sample_count);
|
||||
rb.Push(time_taken);
|
||||
}
|
||||
|
||||
void DecodeInterleaved(HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Audio, "called");
|
||||
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto extra_behavior = rp.Pop<bool>() ? OpusDecoderState::ExtraBehavior::ResetContext
|
||||
: OpusDecoderState::ExtraBehavior::None;
|
||||
|
||||
decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, extra_behavior);
|
||||
auto reset{rp.Pop<bool>()};
|
||||
|
||||
auto input_data{ctx.ReadBuffer(0)};
|
||||
output_data.resize_destructive(ctx.GetWriteBufferSize());
|
||||
|
||||
u32 size{};
|
||||
u32 sample_count{};
|
||||
u64 time_taken{};
|
||||
auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data,
|
||||
output_data, reset);
|
||||
|
||||
LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}",
|
||||
reset, size, sample_count, time_taken);
|
||||
|
||||
ctx.WriteBuffer(output_data);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(result);
|
||||
rb.Push(size);
|
||||
rb.Push(sample_count);
|
||||
rb.Push(time_taken);
|
||||
}
|
||||
|
||||
void DecodeInterleavedForMultiStream(HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Audio, "called");
|
||||
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto extra_behavior = rp.Pop<bool>() ? OpusDecoderState::ExtraBehavior::ResetContext
|
||||
: OpusDecoderState::ExtraBehavior::None;
|
||||
|
||||
decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, extra_behavior);
|
||||
auto reset{rp.Pop<bool>()};
|
||||
|
||||
auto input_data{ctx.ReadBuffer(0)};
|
||||
output_data.resize_destructive(ctx.GetWriteBufferSize());
|
||||
|
||||
u32 size{};
|
||||
u32 sample_count{};
|
||||
u64 time_taken{};
|
||||
auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count,
|
||||
input_data, output_data, reset);
|
||||
|
||||
LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}",
|
||||
reset, size, sample_count, time_taken);
|
||||
|
||||
ctx.WriteBuffer(output_data);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(result);
|
||||
rb.Push(size);
|
||||
rb.Push(sample_count);
|
||||
rb.Push(time_taken);
|
||||
}
|
||||
|
||||
OpusDecoderState decoder_state;
|
||||
std::unique_ptr<AudioCore::OpusDecoder::OpusDecoder> impl;
|
||||
Common::ScratchBuffer<u8> output_data;
|
||||
};
|
||||
|
||||
std::size_t WorkerBufferSize(u32 channel_count) {
|
||||
ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
|
||||
constexpr int num_streams = 1;
|
||||
const int num_stereo_streams = channel_count == 2 ? 1 : 0;
|
||||
return opus_multistream_decoder_get_size(num_streams, num_stereo_streams);
|
||||
}
|
||||
|
||||
// Creates the mapping table that maps the input channels to the particular
|
||||
// output channels. In the stereo case, we map the left and right input channels
|
||||
// to the left and right output channels respectively.
|
||||
//
|
||||
// However, in the monophonic case, we only map the one available channel
|
||||
// to the sole output channel. We specify 255 for the would-be right channel
|
||||
// as this is a special value defined by Opus to indicate to the decoder to
|
||||
// ignore that channel.
|
||||
std::array<u8, 2> CreateMappingTable(u32 channel_count) {
|
||||
if (channel_count == 2) {
|
||||
return {{0, 1}};
|
||||
}
|
||||
|
||||
return {{0, 255}};
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
void HwOpus::GetWorkBufferSize(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto sample_rate = rp.Pop<u32>();
|
||||
const auto channel_count = rp.Pop<u32>();
|
||||
|
||||
LOG_DEBUG(Audio, "called with sample_rate={}, channel_count={}", sample_rate, channel_count);
|
||||
|
||||
ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 ||
|
||||
sample_rate == 12000 || sample_rate == 8000,
|
||||
"Invalid sample rate");
|
||||
ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
|
||||
|
||||
const u32 worker_buffer_sz = static_cast<u32>(WorkerBufferSize(channel_count));
|
||||
LOG_DEBUG(Audio, "worker_buffer_sz={}", worker_buffer_sz);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push<u32>(worker_buffer_sz);
|
||||
}
|
||||
|
||||
void HwOpus::GetWorkBufferSizeEx(HLERequestContext& ctx) {
|
||||
GetWorkBufferSize(ctx);
|
||||
}
|
||||
|
||||
void HwOpus::GetWorkBufferSizeExEx(HLERequestContext& ctx) {
|
||||
GetWorkBufferSizeEx(ctx);
|
||||
}
|
||||
|
||||
void HwOpus::GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx) {
|
||||
OpusMultiStreamParametersEx param;
|
||||
std::memcpy(¶m, ctx.ReadBuffer().data(), ctx.GetReadBufferSize());
|
||||
|
||||
const auto sample_rate = param.sample_rate;
|
||||
const auto channel_count = param.channel_count;
|
||||
const auto number_streams = param.number_streams;
|
||||
const auto number_stereo_streams = param.number_stereo_streams;
|
||||
|
||||
LOG_DEBUG(
|
||||
Audio,
|
||||
"called with sample_rate={}, channel_count={}, number_streams={}, number_stereo_streams={}",
|
||||
sample_rate, channel_count, number_streams, number_stereo_streams);
|
||||
|
||||
ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 ||
|
||||
sample_rate == 12000 || sample_rate == 8000,
|
||||
"Invalid sample rate");
|
||||
|
||||
const u32 worker_buffer_sz =
|
||||
static_cast<u32>(opus_multistream_decoder_get_size(number_streams, number_stereo_streams));
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push<u32>(worker_buffer_sz);
|
||||
}
|
||||
|
||||
void HwOpus::OpenHardwareOpusDecoder(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto sample_rate = rp.Pop<u32>();
|
||||
const auto channel_count = rp.Pop<u32>();
|
||||
const auto buffer_sz = rp.Pop<u32>();
|
||||
|
||||
LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}, buffer_size={}", sample_rate,
|
||||
channel_count, buffer_sz);
|
||||
auto params = rp.PopRaw<OpusParameters>();
|
||||
auto transfer_memory_size{rp.Pop<u32>()};
|
||||
auto transfer_memory_handle{ctx.GetCopyHandle(0)};
|
||||
auto transfer_memory{
|
||||
system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
|
||||
transfer_memory_handle)};
|
||||
|
||||
ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 ||
|
||||
sample_rate == 12000 || sample_rate == 8000,
|
||||
"Invalid sample rate");
|
||||
ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
|
||||
LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} transfer_memory_size 0x{:X}",
|
||||
params.sample_rate, params.channel_count, transfer_memory_size);
|
||||
|
||||
const std::size_t worker_sz = WorkerBufferSize(channel_count);
|
||||
ASSERT_MSG(buffer_sz >= worker_sz, "Worker buffer too large");
|
||||
auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())};
|
||||
|
||||
const int num_stereo_streams = channel_count == 2 ? 1 : 0;
|
||||
const auto mapping_table = CreateMappingTable(channel_count);
|
||||
|
||||
int error = 0;
|
||||
OpusDecoderPtr decoder{
|
||||
opus_multistream_decoder_create(sample_rate, static_cast<int>(channel_count), 1,
|
||||
num_stereo_streams, mapping_table.data(), &error)};
|
||||
if (error != OPUS_OK || decoder == nullptr) {
|
||||
LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
// TODO(ogniK): Use correct error code
|
||||
rb.Push(ResultUnknown);
|
||||
return;
|
||||
}
|
||||
OpusParametersEx ex{
|
||||
.sample_rate = params.sample_rate,
|
||||
.channel_count = params.channel_count,
|
||||
.use_large_frame_size = false,
|
||||
};
|
||||
auto result = decoder->Initialize(ex, transfer_memory.GetPointerUnsafe(), transfer_memory_size);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushIpcInterface<IHardwareOpusDecoderManager>(
|
||||
system, OpusDecoderState{std::move(decoder), sample_rate, channel_count});
|
||||
rb.Push(result);
|
||||
rb.PushIpcInterface(decoder);
|
||||
}
|
||||
|
||||
void HwOpus::GetWorkBufferSize(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
auto params = rp.PopRaw<OpusParameters>();
|
||||
|
||||
u64 size{};
|
||||
auto result = impl.GetWorkBufferSize(params, size);
|
||||
|
||||
LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} -- returned size 0x{:X}",
|
||||
params.sample_rate, params.channel_count, size);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(result);
|
||||
rb.Push(size);
|
||||
}
|
||||
|
||||
void HwOpus::OpenHardwareOpusDecoderForMultiStream(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
auto input{ctx.ReadBuffer()};
|
||||
OpusMultiStreamParameters params;
|
||||
std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParameters));
|
||||
|
||||
auto transfer_memory_size{rp.Pop<u32>()};
|
||||
auto transfer_memory_handle{ctx.GetCopyHandle(0)};
|
||||
auto transfer_memory{
|
||||
system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
|
||||
transfer_memory_handle)};
|
||||
|
||||
LOG_DEBUG(Service_Audio,
|
||||
"sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} "
|
||||
"transfer_memory_size 0x{:X}",
|
||||
params.sample_rate, params.channel_count, params.total_stream_count,
|
||||
params.stereo_stream_count, transfer_memory_size);
|
||||
|
||||
auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())};
|
||||
|
||||
OpusMultiStreamParametersEx ex{
|
||||
.sample_rate = params.sample_rate,
|
||||
.channel_count = params.channel_count,
|
||||
.total_stream_count = params.total_stream_count,
|
||||
.stereo_stream_count = params.stereo_stream_count,
|
||||
.use_large_frame_size = false,
|
||||
.mappings{},
|
||||
};
|
||||
std::memcpy(ex.mappings.data(), params.mappings.data(), sizeof(params.mappings));
|
||||
auto result = decoder->Initialize(ex, transfer_memory.GetPointerUnsafe(), transfer_memory_size);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(result);
|
||||
rb.PushIpcInterface(decoder);
|
||||
}
|
||||
|
||||
void HwOpus::GetWorkBufferSizeForMultiStream(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
auto input{ctx.ReadBuffer()};
|
||||
OpusMultiStreamParameters params;
|
||||
std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParameters));
|
||||
|
||||
u64 size{};
|
||||
auto result = impl.GetWorkBufferSizeForMultiStream(params, size);
|
||||
|
||||
LOG_DEBUG(Service_Audio, "size 0x{:X}", size);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(result);
|
||||
rb.Push(size);
|
||||
}
|
||||
|
||||
void HwOpus::OpenHardwareOpusDecoderEx(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto sample_rate = rp.Pop<u32>();
|
||||
const auto channel_count = rp.Pop<u32>();
|
||||
|
||||
LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}", sample_rate, channel_count);
|
||||
auto params = rp.PopRaw<OpusParametersEx>();
|
||||
auto transfer_memory_size{rp.Pop<u32>()};
|
||||
auto transfer_memory_handle{ctx.GetCopyHandle(0)};
|
||||
auto transfer_memory{
|
||||
system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
|
||||
transfer_memory_handle)};
|
||||
|
||||
ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 ||
|
||||
sample_rate == 12000 || sample_rate == 8000,
|
||||
"Invalid sample rate");
|
||||
ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
|
||||
LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} transfer_memory_size 0x{:X}",
|
||||
params.sample_rate, params.channel_count, transfer_memory_size);
|
||||
|
||||
const int num_stereo_streams = channel_count == 2 ? 1 : 0;
|
||||
const auto mapping_table = CreateMappingTable(channel_count);
|
||||
auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())};
|
||||
|
||||
int error = 0;
|
||||
OpusDecoderPtr decoder{
|
||||
opus_multistream_decoder_create(sample_rate, static_cast<int>(channel_count), 1,
|
||||
num_stereo_streams, mapping_table.data(), &error)};
|
||||
if (error != OPUS_OK || decoder == nullptr) {
|
||||
LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
// TODO(ogniK): Use correct error code
|
||||
rb.Push(ResultUnknown);
|
||||
return;
|
||||
}
|
||||
auto result =
|
||||
decoder->Initialize(params, transfer_memory.GetPointerUnsafe(), transfer_memory_size);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushIpcInterface<IHardwareOpusDecoderManager>(
|
||||
system, OpusDecoderState{std::move(decoder), sample_rate, channel_count});
|
||||
rb.Push(result);
|
||||
rb.PushIpcInterface(decoder);
|
||||
}
|
||||
|
||||
void HwOpus::GetWorkBufferSizeEx(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
auto params = rp.PopRaw<OpusParametersEx>();
|
||||
|
||||
u64 size{};
|
||||
auto result = impl.GetWorkBufferSizeEx(params, size);
|
||||
|
||||
LOG_DEBUG(Service_Audio, "size 0x{:X}", size);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(result);
|
||||
rb.Push(size);
|
||||
}
|
||||
|
||||
void HwOpus::OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
auto input{ctx.ReadBuffer()};
|
||||
OpusMultiStreamParametersEx params;
|
||||
std::memcpy(¶ms, ctx.ReadBuffer().data(), ctx.GetReadBufferSize());
|
||||
std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParametersEx));
|
||||
|
||||
const auto& sample_rate = params.sample_rate;
|
||||
const auto& channel_count = params.channel_count;
|
||||
auto transfer_memory_size{rp.Pop<u32>()};
|
||||
auto transfer_memory_handle{ctx.GetCopyHandle(0)};
|
||||
auto transfer_memory{
|
||||
system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(
|
||||
transfer_memory_handle)};
|
||||
|
||||
LOG_INFO(
|
||||
Audio,
|
||||
"called with sample_rate={}, channel_count={}, number_streams={}, number_stereo_streams={}",
|
||||
sample_rate, channel_count, params.number_streams, params.number_stereo_streams);
|
||||
LOG_DEBUG(Service_Audio,
|
||||
"sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} "
|
||||
"use_large_frame_size {}"
|
||||
"transfer_memory_size 0x{:X}",
|
||||
params.sample_rate, params.channel_count, params.total_stream_count,
|
||||
params.stereo_stream_count, params.use_large_frame_size, transfer_memory_size);
|
||||
|
||||
ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 ||
|
||||
sample_rate == 12000 || sample_rate == 8000,
|
||||
"Invalid sample rate");
|
||||
auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())};
|
||||
|
||||
int error = 0;
|
||||
OpusDecoderPtr decoder{opus_multistream_decoder_create(
|
||||
sample_rate, static_cast<int>(channel_count), params.number_streams,
|
||||
params.number_stereo_streams, params.channel_mappings.data(), &error)};
|
||||
if (error != OPUS_OK || decoder == nullptr) {
|
||||
LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
// TODO(ogniK): Use correct error code
|
||||
rb.Push(ResultUnknown);
|
||||
return;
|
||||
}
|
||||
auto result =
|
||||
decoder->Initialize(params, transfer_memory.GetPointerUnsafe(), transfer_memory_size);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushIpcInterface<IHardwareOpusDecoderManager>(
|
||||
system, OpusDecoderState{std::move(decoder), sample_rate, channel_count});
|
||||
rb.Push(result);
|
||||
rb.PushIpcInterface(decoder);
|
||||
}
|
||||
|
||||
HwOpus::HwOpus(Core::System& system_) : ServiceFramework{system_, "hwopus"} {
|
||||
void HwOpus::GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
auto input{ctx.ReadBuffer()};
|
||||
OpusMultiStreamParametersEx params;
|
||||
std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParametersEx));
|
||||
|
||||
u64 size{};
|
||||
auto result = impl.GetWorkBufferSizeForMultiStreamEx(params, size);
|
||||
|
||||
LOG_DEBUG(Service_Audio,
|
||||
"sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} "
|
||||
"use_large_frame_size {} -- returned size 0x{:X}",
|
||||
params.sample_rate, params.channel_count, params.total_stream_count,
|
||||
params.stereo_stream_count, params.use_large_frame_size, size);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(result);
|
||||
rb.Push(size);
|
||||
}
|
||||
|
||||
void HwOpus::GetWorkBufferSizeExEx(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
auto params = rp.PopRaw<OpusParametersEx>();
|
||||
|
||||
u64 size{};
|
||||
auto result = impl.GetWorkBufferSizeExEx(params, size);
|
||||
|
||||
LOG_DEBUG(Service_Audio, "size 0x{:X}", size);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(result);
|
||||
rb.Push(size);
|
||||
}
|
||||
|
||||
void HwOpus::GetWorkBufferSizeForMultiStreamExEx(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
auto input{ctx.ReadBuffer()};
|
||||
OpusMultiStreamParametersEx params;
|
||||
std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParametersEx));
|
||||
|
||||
u64 size{};
|
||||
auto result = impl.GetWorkBufferSizeForMultiStreamExEx(params, size);
|
||||
|
||||
LOG_DEBUG(Service_Audio, "size 0x{:X}", size);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(result);
|
||||
rb.Push(size);
|
||||
}
|
||||
|
||||
HwOpus::HwOpus(Core::System& system_)
|
||||
: ServiceFramework{system_, "hwopus"}, system{system_}, impl{system} {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &HwOpus::OpenHardwareOpusDecoder, "OpenHardwareOpusDecoder"},
|
||||
{1, &HwOpus::GetWorkBufferSize, "GetWorkBufferSize"},
|
||||
{2, nullptr, "OpenOpusDecoderForMultiStream"},
|
||||
{3, nullptr, "GetWorkBufferSizeForMultiStream"},
|
||||
{2, &HwOpus::OpenHardwareOpusDecoderForMultiStream, "OpenOpusDecoderForMultiStream"},
|
||||
{3, &HwOpus::GetWorkBufferSizeForMultiStream, "GetWorkBufferSizeForMultiStream"},
|
||||
{4, &HwOpus::OpenHardwareOpusDecoderEx, "OpenHardwareOpusDecoderEx"},
|
||||
{5, &HwOpus::GetWorkBufferSizeEx, "GetWorkBufferSizeEx"},
|
||||
{6, &HwOpus::OpenHardwareOpusDecoderForMultiStreamEx,
|
||||
"OpenHardwareOpusDecoderForMultiStreamEx"},
|
||||
{7, &HwOpus::GetWorkBufferSizeForMultiStreamEx, "GetWorkBufferSizeForMultiStreamEx"},
|
||||
{8, &HwOpus::GetWorkBufferSizeExEx, "GetWorkBufferSizeExEx"},
|
||||
{9, nullptr, "GetWorkBufferSizeForMultiStreamExEx"},
|
||||
{9, &HwOpus::GetWorkBufferSizeForMultiStreamExEx, "GetWorkBufferSizeForMultiStreamExEx"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "audio_core/opus/decoder_manager.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Core {
|
||||
|
@ -11,18 +12,6 @@ class System;
|
|||
|
||||
namespace Service::Audio {
|
||||
|
||||
struct OpusMultiStreamParametersEx {
|
||||
u32 sample_rate;
|
||||
u32 channel_count;
|
||||
u32 number_streams;
|
||||
u32 number_stereo_streams;
|
||||
u32 use_large_frame_size;
|
||||
u32 padding;
|
||||
std::array<u8, 0x100> channel_mappings;
|
||||
};
|
||||
static_assert(sizeof(OpusMultiStreamParametersEx) == 0x118,
|
||||
"OpusMultiStreamParametersEx has incorrect size");
|
||||
|
||||
class HwOpus final : public ServiceFramework<HwOpus> {
|
||||
public:
|
||||
explicit HwOpus(Core::System& system_);
|
||||
|
@ -30,12 +19,18 @@ public:
|
|||
|
||||
private:
|
||||
void OpenHardwareOpusDecoder(HLERequestContext& ctx);
|
||||
void OpenHardwareOpusDecoderEx(HLERequestContext& ctx);
|
||||
void OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx);
|
||||
void GetWorkBufferSize(HLERequestContext& ctx);
|
||||
void OpenHardwareOpusDecoderForMultiStream(HLERequestContext& ctx);
|
||||
void GetWorkBufferSizeForMultiStream(HLERequestContext& ctx);
|
||||
void OpenHardwareOpusDecoderEx(HLERequestContext& ctx);
|
||||
void GetWorkBufferSizeEx(HLERequestContext& ctx);
|
||||
void GetWorkBufferSizeExEx(HLERequestContext& ctx);
|
||||
void OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx);
|
||||
void GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx);
|
||||
void GetWorkBufferSizeExEx(HLERequestContext& ctx);
|
||||
void GetWorkBufferSizeForMultiStreamExEx(HLERequestContext& ctx);
|
||||
|
||||
Core::System& system;
|
||||
AudioCore::OpusDecoder::OpusDecoderManager impl;
|
||||
};
|
||||
|
||||
} // namespace Service::Audio
|
||||
|
|
|
@ -193,7 +193,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
|
|||
shared_memory->system_properties.use_minus.Assign(1);
|
||||
shared_memory->system_properties.is_charging_joy_dual.Assign(
|
||||
battery_level.dual.is_charging);
|
||||
shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::SwitchProController;
|
||||
shared_memory->applet_footer_type = AppletFooterUiType::SwitchProController;
|
||||
shared_memory->sixaxis_fullkey_properties.is_newly_assigned.Assign(1);
|
||||
break;
|
||||
case Core::HID::NpadStyleIndex::Handheld:
|
||||
|
@ -216,8 +216,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
|
|||
shared_memory->system_properties.is_charging_joy_right.Assign(
|
||||
battery_level.right.is_charging);
|
||||
shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual;
|
||||
shared_memory->applet_nfc_xcd.applet_footer.type =
|
||||
AppletFooterUiType::HandheldJoyConLeftJoyConRight;
|
||||
shared_memory->applet_footer_type = AppletFooterUiType::HandheldJoyConLeftJoyConRight;
|
||||
shared_memory->sixaxis_handheld_properties.is_newly_assigned.Assign(1);
|
||||
break;
|
||||
case Core::HID::NpadStyleIndex::JoyconDual:
|
||||
|
@ -247,19 +246,19 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
|
|||
shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual;
|
||||
|
||||
if (controller.is_dual_left_connected && controller.is_dual_right_connected) {
|
||||
shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDual;
|
||||
shared_memory->applet_footer_type = AppletFooterUiType::JoyDual;
|
||||
shared_memory->fullkey_color.fullkey = body_colors.left;
|
||||
shared_memory->battery_level_dual = battery_level.left.battery_level;
|
||||
shared_memory->system_properties.is_charging_joy_dual.Assign(
|
||||
battery_level.left.is_charging);
|
||||
} else if (controller.is_dual_left_connected) {
|
||||
shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDualLeftOnly;
|
||||
shared_memory->applet_footer_type = AppletFooterUiType::JoyDualLeftOnly;
|
||||
shared_memory->fullkey_color.fullkey = body_colors.left;
|
||||
shared_memory->battery_level_dual = battery_level.left.battery_level;
|
||||
shared_memory->system_properties.is_charging_joy_dual.Assign(
|
||||
battery_level.left.is_charging);
|
||||
} else {
|
||||
shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDualRightOnly;
|
||||
shared_memory->applet_footer_type = AppletFooterUiType::JoyDualRightOnly;
|
||||
shared_memory->fullkey_color.fullkey = body_colors.right;
|
||||
shared_memory->battery_level_dual = battery_level.right.battery_level;
|
||||
shared_memory->system_properties.is_charging_joy_dual.Assign(
|
||||
|
@ -278,7 +277,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
|
|||
shared_memory->system_properties.use_minus.Assign(1);
|
||||
shared_memory->system_properties.is_charging_joy_left.Assign(
|
||||
battery_level.left.is_charging);
|
||||
shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyLeftHorizontal;
|
||||
shared_memory->applet_footer_type = AppletFooterUiType::JoyLeftHorizontal;
|
||||
shared_memory->sixaxis_left_properties.is_newly_assigned.Assign(1);
|
||||
break;
|
||||
case Core::HID::NpadStyleIndex::JoyconRight:
|
||||
|
@ -293,7 +292,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
|
|||
shared_memory->system_properties.use_plus.Assign(1);
|
||||
shared_memory->system_properties.is_charging_joy_right.Assign(
|
||||
battery_level.right.is_charging);
|
||||
shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyRightHorizontal;
|
||||
shared_memory->applet_footer_type = AppletFooterUiType::JoyRightHorizontal;
|
||||
shared_memory->sixaxis_right_properties.is_newly_assigned.Assign(1);
|
||||
break;
|
||||
case Core::HID::NpadStyleIndex::GameCube:
|
||||
|
@ -314,12 +313,12 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
|
|||
case Core::HID::NpadStyleIndex::SNES:
|
||||
shared_memory->style_tag.lucia.Assign(1);
|
||||
shared_memory->device_type.fullkey.Assign(1);
|
||||
shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::Lucia;
|
||||
shared_memory->applet_footer_type = AppletFooterUiType::Lucia;
|
||||
break;
|
||||
case Core::HID::NpadStyleIndex::N64:
|
||||
shared_memory->style_tag.lagoon.Assign(1);
|
||||
shared_memory->device_type.fullkey.Assign(1);
|
||||
shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::Lagon;
|
||||
shared_memory->applet_footer_type = AppletFooterUiType::Lagon;
|
||||
break;
|
||||
case Core::HID::NpadStyleIndex::SegaGenesis:
|
||||
shared_memory->style_tag.lager.Assign(1);
|
||||
|
@ -419,9 +418,17 @@ void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) {
|
|||
std::scoped_lock lock{mutex};
|
||||
auto& controller = GetControllerFromNpadIdType(npad_id);
|
||||
const auto controller_type = controller.device->GetNpadStyleIndex();
|
||||
|
||||
if (!controller.device->IsConnected() && controller.is_connected) {
|
||||
DisconnectNpad(npad_id);
|
||||
return;
|
||||
}
|
||||
if (!controller.device->IsConnected()) {
|
||||
return;
|
||||
}
|
||||
if (controller.device->IsConnected() && !controller.is_connected) {
|
||||
InitNewlyAddedController(npad_id);
|
||||
}
|
||||
|
||||
// This function is unique to yuzu for the turbo buttons and motion to work properly
|
||||
controller.device->StatusUpdate();
|
||||
|
@ -468,6 +475,10 @@ void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) {
|
|||
pad_entry.npad_buttons.l.Assign(button_state.zl);
|
||||
pad_entry.npad_buttons.r.Assign(button_state.zr);
|
||||
}
|
||||
|
||||
if (pad_entry.npad_buttons.raw != Core::HID::NpadButton::None) {
|
||||
hid_core.SetLastActiveController(npad_id);
|
||||
}
|
||||
}
|
||||
|
||||
void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
|
||||
|
@ -736,14 +747,6 @@ void Controller_NPad::SetSupportedStyleSet(Core::HID::NpadStyleTag style_set) {
|
|||
|
||||
// Once SetSupportedStyleSet is called controllers are fully initialized
|
||||
is_controller_initialized = true;
|
||||
|
||||
// Connect all active controllers
|
||||
for (auto& controller : controller_data) {
|
||||
const auto& device = controller.device;
|
||||
if (device->IsConnected()) {
|
||||
AddNewControllerAt(device->GetNpadStyleIndex(), device->GetNpadIdType());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Core::HID::NpadStyleTag Controller_NPad::GetSupportedStyleSet() const {
|
||||
|
@ -1116,7 +1119,7 @@ Result Controller_NPad::DisconnectNpad(Core::HID::NpadIdType npad_id) {
|
|||
.left = {},
|
||||
.right = {},
|
||||
};
|
||||
shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::None;
|
||||
shared_memory->applet_footer_type = AppletFooterUiType::None;
|
||||
|
||||
controller.is_dual_left_connected = true;
|
||||
controller.is_dual_right_connected = true;
|
||||
|
@ -1508,6 +1511,31 @@ Core::HID::NpadButton Controller_NPad::GetAndResetPressState() {
|
|||
return static_cast<Core::HID::NpadButton>(press_state.exchange(0));
|
||||
}
|
||||
|
||||
void Controller_NPad::ApplyNpadSystemCommonPolicy() {
|
||||
Core::HID::NpadStyleTag styletag{};
|
||||
styletag.fullkey.Assign(1);
|
||||
styletag.handheld.Assign(1);
|
||||
styletag.joycon_dual.Assign(1);
|
||||
styletag.system_ext.Assign(1);
|
||||
styletag.system.Assign(1);
|
||||
SetSupportedStyleSet(styletag);
|
||||
|
||||
SetNpadHandheldActivationMode(NpadHandheldActivationMode::Dual);
|
||||
|
||||
supported_npad_id_types.clear();
|
||||
supported_npad_id_types.resize(10);
|
||||
supported_npad_id_types[0] = Core::HID::NpadIdType::Player1;
|
||||
supported_npad_id_types[1] = Core::HID::NpadIdType::Player2;
|
||||
supported_npad_id_types[2] = Core::HID::NpadIdType::Player3;
|
||||
supported_npad_id_types[3] = Core::HID::NpadIdType::Player4;
|
||||
supported_npad_id_types[4] = Core::HID::NpadIdType::Player5;
|
||||
supported_npad_id_types[5] = Core::HID::NpadIdType::Player6;
|
||||
supported_npad_id_types[6] = Core::HID::NpadIdType::Player7;
|
||||
supported_npad_id_types[7] = Core::HID::NpadIdType::Player8;
|
||||
supported_npad_id_types[8] = Core::HID::NpadIdType::Other;
|
||||
supported_npad_id_types[9] = Core::HID::NpadIdType::Handheld;
|
||||
}
|
||||
|
||||
bool Controller_NPad::IsControllerSupported(Core::HID::NpadStyleIndex controller) const {
|
||||
if (controller == Core::HID::NpadStyleIndex::Handheld) {
|
||||
const bool support_handheld =
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue