This commit is contained in:
Franco 2023-09-17 21:37:21 -03:00
commit 80aafb0f7d
172 changed files with 9055 additions and 3201 deletions

View file

@ -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)

View file

@ -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
View 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

View file

@ -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)

View file

@ -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(

View file

@ -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
*/

View file

@ -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)
}
}

View file

@ -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)

View file

@ -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

View file

@ -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),

View file

@ -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"

View file

@ -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
}

View file

@ -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
)
}
}
}
}
}

View file

@ -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) {

View file

@ -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)

View file

@ -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
}

View file

@ -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)
}

View file

@ -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"

View file

@ -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 {

View file

@ -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()
}
}
}
}

View file

@ -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)
}
}
}
}

View file

@ -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"
}
}

View file

@ -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)

View file

@ -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("")
)

View file

@ -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

View file

@ -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"
}
}

View file

@ -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
}
}
}

View file

@ -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)
}
}
}
}
}

View file

@ -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)

View file

@ -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)
}
}

View file

@ -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);

View file

@ -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();
}

View 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>

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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>

View file

@ -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()

View file

@ -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

View file

@ -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

View file

@ -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;
}
}

View file

@ -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};
};

View file

@ -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;
}

View file

@ -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.
*

View 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

View 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

View 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

View 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

View 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

View file

@ -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

View 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

View file

@ -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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View file

@ -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},

View file

@ -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);
}
}

View file

@ -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},

View file

@ -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},

View file

@ -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) {

View file

@ -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

View file

@ -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;
}

View file

@ -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
}

View file

@ -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;

View file

@ -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) \

View file

@ -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

View file

@ -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();
});

View file

@ -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,

View file

@ -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
*/

View file

@ -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 {

View file

@ -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()

View file

@ -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);
}

View file

@ -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()>;

View file

@ -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]);
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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;

View file

@ -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) {

View file

@ -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(

View file

@ -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;

View file

@ -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;

View file

@ -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();

View file

@ -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

View file

@ -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.

View file

@ -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{};

View file

@ -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();
}

View file

@ -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);

View file

@ -62,7 +62,7 @@ enum class ErrorModule : u32 {
XCD = 108,
TMP451 = 109,
NIFM = 110,
Hwopus = 111,
HwOpus = 111,
LSM6DS3 = 112,
Bluetooth = 113,
VI = 114,

View file

@ -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);

View file

@ -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;

View file

@ -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:

View file

@ -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 {

View file

@ -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

View file

@ -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(&param, 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(&params, 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(&params, 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(&params, ctx.ReadBuffer().data(), ctx.GetReadBufferSize());
std::memcpy(&params, 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(&params, 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(&params, 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);
}

View file

@ -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

View file

@ -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