Compare commits

..

77 Commits

Author SHA1 Message Date
german77
ed5f1a45b7 npad: Disable vibration check if disabled 2021-07-10 20:06:07 -05:00
Fernando S
7dca756f30 Merge pull request #6573 from lat9nq/cpu-settings-cleanup-2
core,common,yuzu qt: Add CPU accuracy option 'Auto'
2021-07-09 21:45:45 +02:00
Morph
2794242331 Merge pull request #6581 from lat9nq/isolate-debug-settings
yuzu qt: config: Only save renderer_debug as a global setting
2021-07-09 10:52:56 -04:00
lat9nq
420987c5bf yuzu qt: config: Only save renderer_debug as a global setting
This is a bug fix. Enabling graphics debug mode, then saving a custom
configuration causes graphics debugging to be saved and read from the
custom configuration.

Isolate it the same way we isolate the CPU settings.
2021-07-09 10:49:56 -04:00
Ameer J
975a7b3a78 Merge pull request #6563 from ReinUsesLisp/thread-worker
common: Add stateful thread worker and unique function utilities
2021-07-08 19:20:57 -04:00
ReinUsesLisp
0ddbbb64e5 common/thread_worker: Stop workers on stop_token when waiting 2021-07-08 19:03:26 -03:00
ReinUsesLisp
da34d37044 common/thread_worker: Add support for stateful threads 2021-07-08 19:03:26 -03:00
FernandoS27
c147e9a90e common/thread_worker: Simplify logic 2021-07-08 19:03:26 -03:00
FernandoS27
a10e112e64 common/thread_worker: Fix data race 2021-07-08 19:03:26 -03:00
ReinUsesLisp
bf5b5c1bf4 common/thread_worker: Use unique function 2021-07-08 19:03:26 -03:00
ReinUsesLisp
2c8d337418 common: Add unique function 2021-07-08 19:03:19 -03:00
ReinUsesLisp
f28dd32275 common/thread_worker: Add wait for requests method 2021-07-08 19:00:39 -03:00
lat9nq
dc06e11a7b settings, arm_dynarmic, yuzu qt: Move CPU debugging option
Decouples the CPU debugging mode from the enumeration to its own
boolean. After this, it moves the CPU Debugging tab over to a sub tab
underneath the Debug tab in the configuration UI.
2021-07-08 16:56:44 -04:00
lat9nq
eebf39b3c0 arm_dynarmic_64: Re-add fastmem_address_space_bits to Auto setting 2021-07-08 15:14:45 -04:00
lat9nq
c8b8674ffc settings, yuzu qt: Add migration code for CPU accuracy
Old CPU Accuracy setting won't translate well into since we're adding
one at the beginning of the list. On first boot with the new setting,
just use the default setting.
2021-07-08 14:56:09 -04:00
lat9nq
87b6e14d7c arm_dynarmic{32,64}: Fixes from test build
Now sets optimizations regardless of the Settings. Drops unsafe fastmem
optimization.
2021-07-08 14:56:09 -04:00
lat9nq
7ab5767157 core,common,yuzu qt: Add CPU accuracy option 'Auto'
The current CPU accuracy settings in yuzu are fairly polarized and
require more than common knowledge to know what the optimal settings for
yuzu would be. This adds a curated option called 'Auto' that applies a
few at the moment known-good unsafe optimizations to Dynarmic.
2021-07-08 14:56:09 -04:00
Ameer J
5edc96f4a4 Merge pull request #6539 from lat9nq/default-setting
general: Move most settings' defaults and labels into their definition
2021-07-08 14:46:31 -04:00
lat9nq
a949ee0410 general: Code formatting improvements
Slight improvements to readability.

Dropped suggestions for string_view (settings.h:101), pass by value
(settings.h:82), reverting double to a float (config.cpp:316), and other
smaller ones, some out of scope.

Addresses review feedback.

Co-authored-by: Ameer J <52414509+ameerj@users.noreply.github.com>
2021-07-08 14:07:10 -04:00
Feng Chen
c7ad195fd3 Out of bound blit (#6531)
* Fix out of bound blit error

* Fix code read

* Fix ci error

Co-authored-by: Feng Chen <chen.feng@gloritysolutions.com>
2021-07-08 11:06:09 -07:00
Morph
92a3daf029 Merge pull request #6564 from Kelebek1/Audio
Support more PCM formats
2021-07-08 12:14:58 -04:00
Morph
91a4a924b1 Merge pull request #6569 from Kelebek1/Vol
audio_core: Preserve front channel volume after 6 to 2 downmix
2021-07-08 12:09:21 -04:00
Kelebek1
7636fefb71 audio_core: Preserve front channel volume after 6 to 2 downmix
Many games report 6 channel output while only providing data for 2. We only output 2-channel audio regardless, and in the downmixing, front left/right only provide 36% of their volume. This is done assuming all of the other channels also contain valid data, but in many games they don't. This PR alters the downmixing to preserve front left/right, so volume is not lost.

This improves volume in Link's Awakening, New Super Mario Bros U, Disgaea 6, Super Kirby Clash.
2021-07-08 17:07:23 +01:00
bunnei
8542f2f3fc Merge pull request #6567 from Kelebek1/Audio2
[audren] Report 2 channels active rather than 1
2021-07-07 14:52:08 -07:00
Rodrigo Locatti
4d0bdef17d Merge pull request #6570 from lat9nq/bind-image-true
util_shaders: Fix BindImageTexture
2021-07-07 16:18:05 -03:00
lat9nq
2f0e1f5d02 util_shaders: Fix BindImageTexture
According to
https://gitlab.freedesktop.org/mesa/mesa/-/issues/3820#note_753371 we
need to set these to true for use with 3D textures.

Fixes BOTW teleporting on RadeonSI and iris.
2021-07-07 14:09:55 -04:00
bunnei
2eb018c80f Merge pull request #6562 from Morph1984/flush-behavior
common: fs: More misc. changes
2021-07-07 00:40:31 -07:00
bunnei
eb3cb3af35 Merge pull request #6497 from FernandoS27/scotty-doesnt-know
GPU Memory Manager - Correct handling of non continuous backing memory.
2021-07-06 17:26:21 -07:00
bunnei
b07423f6e2 Merge pull request #6566 from Morph1984/sign-compare-as-error
CMakeLists: Treat -Wsign-compare as an error on GCC/Clang
2021-07-06 11:23:33 -07:00
Kelebek1
b9f915e07a Report 2 channels active. Fixes Tales of Vesperia's mono channel audio. 2021-07-06 18:52:49 +01:00
Kelebek1
dbcc093d88 Support more PCM formats. Fixes Ys IX audio. 2021-07-06 18:43:23 +01:00
Morph
ebb82b0b83 CMakeLists: Treat -Wsign-compare as an error on GCC/Clang
Treats (un)signed comparison mismatches as errors to be consistent with MSVC
2021-07-06 12:50:09 -04:00
Morph
a59ae5e702 common: logging: backend: Close the file after exceeding the write limit
There's no point in keeping the file open after the write limit is exceeded. This allows the file to be committed to the disk shortly after it is closed and avoids redundantly checking whether or not the write limit is exceeded.
2021-07-06 05:59:47 -04:00
Morph
14ab50defb common: fs: file: Revert Flush to its previous behavior and add Commit
It became apparent that logging can continuously spam errors that trigger file flushing.
Since committing the files to disk is an expensive operation, this causes unnecessarily high disk usage.
As such, we will revert Flush() to the previous behavior and add a Commit() member function in the event that this behavior is needed.
2021-07-06 05:59:47 -04:00
Morph
d299d5531f common: fs: file: Flush the file in GetSize
This ensures that GetSize always retrieves the correct file size after a write operation.
2021-07-06 05:59:47 -04:00
bunnei
bf50345d4c Merge pull request #6537 from Morph1984/warnings
general: Enforce multiple warnings in MSVC
2021-07-05 17:09:23 -07:00
bunnei
3d03a6ae02 Merge pull request #6556 from Morph1984/default-mii
service: mii: Retrieve the correct default miis.
2021-07-05 13:51:00 -07:00
Ameer J
c770fa9823 Merge pull request #6540 from Kelebek1/nvdec
Slightly refactor NVDEC and codecs for readability and safety
2021-07-05 16:06:09 -04:00
Morph
942c0d6cdd Merge pull request #6561 from german77/analog_fix
input_common: Add missing modifier callback to analog from button
2021-07-05 12:47:42 -04:00
german77
c19ec2edd9 input_common: Add missing modifier callback to analog from button 2021-07-05 11:39:42 -05:00
Morph
dd44089f87 Merge pull request #6559 from german77/compilation_warnings
Replace usages of deprecated member functions in QMouseEvent and QWheelEvent
2021-07-05 11:23:11 -04:00
german77
b188d7792a profiler: Fix deprecated functions 2021-07-05 10:15:35 -05:00
Mai M
669cef2da3 Merge pull request #6552 from Morph1984/c4189-msvc
CMakeLists: Enforce C4189 on MSVC
2021-07-04 22:16:28 -04:00
Fernando Sahmkow
c6a9e91784 Texture Cache: Fix collision with multiple overlaps of the same sparse texture. 2021-07-04 22:32:36 +02:00
Fernando Sahmkow
a8a0927d42 Texture Cache: Fix GCC & Clang. 2021-07-04 22:32:35 +02:00
Fernando Sahmkow
8f9f142956 Texture Cache: Address feedback. 2021-07-04 22:32:35 +02:00
Fernando Sahmkow
fd98fcf7f0 Texture Cache: Improve accuracy of sparse texture detection. 2021-07-04 22:32:35 +02:00
Fernando Sahmkow
38165fb7e3 Texture Cache: Initial Implementation of Sparse Textures. 2021-07-04 22:32:03 +02:00
Ameer J
eb0e10cff2 Merge pull request #6553 from FernandoS27/bite-a-bat-change-the-world
TextureCache: Fix 1D to 2D overlapps.
2021-07-04 13:20:40 -04:00
Morph
5dfa313d2c service: mii: Retrieve the correct default miis.
We were including the first 2 default miis which are not meant to be shown in games. With this change, we properly retrieve the 6 default miis shown in games, with 3 of each gender.
2021-07-04 05:38:34 -04:00
Fernando Sahmkow
0aab55d26a TextureCacheOGL: Implement Image Copies for 1D and 1D Array. 2021-07-03 14:40:29 +02:00
Fernando Sahmkow
ebaa7e391c TextureCache: Fix 1D to 2D overlapps. 2021-07-03 14:01:54 +02:00
Morph
3a3f4983b6 CMakeLists: Enforce C4189
This supplements C4101 by detecting initialized but unreferenced local variables
2021-07-03 05:51:31 -04:00
bunnei
2fc0a760f0 Merge pull request #6498 from Kelebek1/Audio
[audio_core] Decouple audio update and processing, and process at variable rate
2021-07-03 00:24:33 -07:00
lat9nq
38f658d21e config: Read UISettings as basic settings
I must have been asleep or something. These need to be read with the new
ReadBasicSetting function.
2021-07-02 01:06:30 -04:00
lat9nq
cf1cd3321d settings: Set resolution_factor default to 1
Fixes Disgaea 6 Demo issues.
2021-07-01 12:06:12 -04:00
Kelebek1
208a04dcff Slightly refactor NVDEC and codecs for readability and safety 2021-07-01 06:22:05 +01:00
Kelebek1
b455043e45 Fix XC2/VOEZ crashing, add audio looping and a few misc fixes 2021-07-01 06:01:01 +01:00
Ameer J
bab400daaf Merge pull request #6459 from lat9nq/ubuntu-fixes
cmake: Improve Linux dependency checking for externals
2021-06-30 21:47:57 -04:00
lat9nq
299c5594e6 yuzu_cmd: config: Pass a reference in
Also adds documentation for the ReadSetting function.

Address review comments.

Co-authored-by: Mai M. <mathew1800@gmail.com>
2021-06-30 20:16:01 -04:00
Morph
39be4c3026 Merge pull request #6471 from lat9nq/dump-as-mod
yuzu qt, core: Support LayeredFS mods from SDMC directory
2021-06-29 00:10:31 -04:00
lat9nq
0e5c74bc9e core, input_common: Miscellaneous fixes
bcat: Fix settings access

telemetry_session: Fix settings accesses

So this is what I get for testing with the web service disabled.

touch_from_button: Fix settings access for clang
2021-06-28 20:56:17 -04:00
lat9nq
7a8de138df yuzu qt: Make most UISettings a BasicSetting
For simple primitive settings, moves their defaults and labels to
definition time.

Also fixes typo and clang-format

yuzu qt: config: Fix rng_seed
2021-06-28 19:13:53 -04:00
lat9nq
b91b76df4f general: Make most settings a BasicSetting
Creates a new BasicSettings class in common/settings, and forces setting
a default and label for each setting that uses it in common/settings.
Moves defaults and labels from both frontends into common settings.
Creates a helper function in each frontend to facillitate reading the
settings now with the new default and label properties.

Settings::Setting is also now a subclass of Settings::BasicSetting. Also
adds documentation for both Setting and BasicSetting.
2021-06-28 17:32:17 -04:00
Morph
ec68cba440 Merge pull request #6502 from ameerj/vendor-title
main: Add GPU Vendor name to running title bar
2021-06-28 14:51:49 -04:00
Morph
511ee03a21 patch_manager: Do not apply LayeredFS mods when dumping
We should not apply any mods when dumping a game's RomFS.
2021-06-28 10:14:36 -04:00
Morph
6ac978426c filesystem: Open a read-only directory for SDMC mods
This prevents mod files from being locked due to the read-only share flag in Windows.
2021-06-28 10:08:08 -04:00
lat9nq
844e0114b0 core: Simplify SDMC mod loading
If someone else wants to support other mod formats in the SDMC
directory, that can be added later. For now, just allow RomFS modding
here and force people to do other types of mods the old way.

Addresses review comments.

Co-authored-by: LC <mathew1800@gmail.com>
2021-06-28 10:08:08 -04:00
lat9nq
1664c74a6c core: Support LayeredFS mod from SDMC directory
Enables loading a mod directly from `[yuzu data
directory]/sdmc/atmosphere/contents/[title_id]`. For use with some
homebrew mod managers.
2021-06-28 10:08:07 -04:00
lat9nq
bfecd395d4 yuzu qt: Add option to dump to SDMC directory
Enables dumping the RomFS to SDMC directory, specifically '[yuzu data
directory]/sdmc/atmosphere/contents/[title_id]/romfs'.
2021-06-28 10:08:07 -04:00
Kelebek1
0857d6a3db Decouple audio processing and run at variable rate
Currently, processing of audio samples is called from AudioRenderer's Update method, using a fixed 4 buffers to process the given samples. Games call Update at variable rates, depending on framerate and/or sample count, which causes inconsistency in audio processing. From what I've seen, 60 FPS games update every ~0.004s, but 30 FPS/160 sample games update somewhere between 0.02 and 0.04, 5-10x slower. Not enough samples get fed to the backend, leading to a lot of audio skipping.

This PR seeks to address this by de-coupling the audio consumption and the audio update. Update remains the same without calling for buffer queuing, and the consume now schedules itself to run based on the sample rate and count.
2021-06-27 15:58:07 +01:00
lat9nq
35b17fa5e0 configuration: Defer to common/settings for per-game settings defaults
Avoids double-setting defaults, and avoids potential accidents when
inconsistently setting the default on new settings.
2021-06-26 02:45:14 -04:00
lat9nq
20e51402b0 common: Force defaults for Settings::Setting's
Requires a default value when creating each per-game setting.
2021-06-26 02:43:38 -04:00
lat9nq
a01459df3d gl_device: Expand on Mesa driver names
Makes this list a bit more capable at identifying Mesa drivers. Tries to
deal with two of the overloaded vendor strings in a more generic
fashion.
2021-06-20 23:04:07 -04:00
ameerj
fb16cbb17e video_core: Add GPU vendor name to window title bar 2021-06-20 23:04:07 -04:00
lat9nq
2bb0cc8624 cmake: Check dependencies for Linux Qt package
Currently Qt will download whether or not the target system supports the
package. Normally this isn't an issue since the package manager would
work out the dependencies for us, but in this case we must make sure
everything is in place before downloading the package.

This checks for the package's requirements, as well as tries to provides
hints as to what is required on some of the more cryptic dependencies.
2021-06-13 03:13:10 -04:00
lat9nq
932c0184a7 cmake: Fix find_program usage for 3.15
yuzu requires CMake 3.15 yet find_program was using REQUIRED, which is
only available on 3.18 and later. Instead, we check for
"<VAR>-NOTFOUND".

In addition, check for additional requirements before building libusb or
FFmpeg with autotools. Otherwise, CMake configuration will pass yet
compilation will fail.
2021-06-13 01:15:54 -04:00
124 changed files with 3356 additions and 1757 deletions

View File

@@ -253,11 +253,82 @@ if(ENABLE_QT)
# Check for system Qt on Linux, fallback to bundled Qt
if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
if (NOT YUZU_USE_BUNDLED_QT)
find_package(Qt5 ${QT_VERSION} COMPONENTS Widgets QUIET)
if (NOT Qt5_FOUND)
set(YUZU_USE_BUNDLED_QT ON CACHE BOOL "Download bundled Qt" FORCE)
find_package(Qt5 ${QT_VERSION} COMPONENTS Widgets)
if (NOT Qt5_FOUND OR YUZU_USE_BUNDLED_QT)
# Check for dependencies, then enable bundled Qt download
# Check that the system GLIBCXX version is compatible
find_program(OBJDUMP objdump)
if ("${OBJDUMP}" STREQUAL "OBJDUMP-NOTFOUND")
message(FATAL_ERROR "Required program `objdump` not found.")
endif()
find_library(LIBSTDCXX libstdc++.so.6)
execute_process(
COMMAND
${OBJDUMP} -T ${LIBSTDCXX}
COMMAND
grep GLIBCXX_3.4.28
COMMAND
sed "s/[0-9a-f]*.* //"
COMMAND
sed "s/ .*//"
COMMAND
sort -u
OUTPUT_VARIABLE
GLIBCXX_MET
)
if (NOT GLIBCXX_MET)
message(FATAL_ERROR "Qt too old or not found, and bundled Qt package is not \
compatible with this system. Either install Qt ${QT_VERSION}, or provide the path \
to Qt by setting the variable Qt5_ROOT.")
endif()
# Check for headers
Include(FindPkgConfig REQUIRED)
pkg_check_modules(QT_DEP_GLU QUIET glu>=9.0.0)
if (NOT QT_DEP_GLU_FOUND)
message(FATAL_ERROR "Qt bundled pacakge dependency `glu` not found. \
Perhaps `libglu1-mesa-dev` needs to be installed?")
endif()
pkg_check_modules(QT_DEP_MESA QUIET dri>=20.0.8)
if (NOT QT_DEP_MESA_FOUND)
message(FATAL_ERROR "Qt bundled pacakge dependency `dri` not found. \
Perhaps `mesa-common-dev` needs to be installed?")
endif()
# Check for X libraries
set(BUNDLED_QT_REQUIREMENTS
libxcb-icccm.so.4
libxcb-image.so.0
libxcb-keysyms.so.1
libxcb-randr.so.0
libxcb-render-util.so.0
libxcb-render.so.0
libxcb-shape.so.0
libxcb-shm.so.0
libxcb-sync.so.1
libxcb-xfixes.so.0
libxcb-xinerama.so.0
libxcb-xkb.so.1
libxcb.so.1
libxkbcommon-x11.so.0
libxkbcommon.so.0
)
set(UNRESOLVED_QT_DEPS "")
foreach (REQUIREMENT ${BUNDLED_QT_REQUIREMENTS})
find_library(BUNDLED_QT_${REQUIREMENT} ${REQUIREMENT})
if ("${BUNDLED_QT_${REQUIREMENT}}" STREQUAL "BUNDLED_QT_${REQUIREMENT}-NOTFOUND")
set(UNRESOLVED_QT_DEPS ${UNRESOLVED_QT_DEPS} ${REQUIREMENT})
endif()
unset(BUNDLED_QT_${REQUIREMENT})
endforeach()
unset(BUNDLED_QT_REQUIREMENTS)
if (NOT "${UNRESOLVED_QT_DEPS}" STREQUAL "")
message(FATAL_ERROR "Bundled Qt package missing required dependencies: ${UNRESOLVED_QT_DEPS}")
endif()
set(YUZU_USE_BUNDLED_QT ON CACHE BOOL "Download bundled Qt" FORCE)
endif()
if (YUZU_USE_BUNDLED_QT)
# Binary package currently does not support Qt webengine, so make sure it's disabled
@@ -473,7 +544,15 @@ if (YUZU_USE_BUNDLED_FFMPEG)
# FFmpeg has source that requires one of nasm or yasm to assemble it.
# REQUIRED throws an error if not found here during configuration rather than during compilation.
find_program(ASSEMBLER NAMES nasm yasm REQUIRED)
find_program(ASSEMBLER NAMES nasm yasm)
if ("${ASSEMBLER}" STREQUAL "ASSEMBLER-NOTFOUND")
message(FATAL_ERROR "One of either `nasm` or `yasm` not found but is required.")
endif()
find_program(AUTOCONF autoconf)
if ("${AUTOCONF}" STREQUAL "AUTOCONF-NOTFOUND")
message(FATAL_ERROR "Required program `autoconf` not found.")
endif()
set(FFmpeg_PREFIX ${PROJECT_SOURCE_DIR}/externals/ffmpeg)
set(FFmpeg_BUILD_DIR ${PROJECT_BINARY_DIR}/externals/ffmpeg)

View File

@@ -5,6 +5,17 @@ if (MINGW OR (${CMAKE_SYSTEM_NAME} MATCHES "Linux") OR APPLE)
# GNU toolchains for some reason doesn't work with the later half of this CMakeLists after
# updating to 1.0.24, so we do it the old-fashioned way for now.
# Require autoconf and libtoolize here, rather than crash during compilation
find_program(AUTOCONF autoconf)
if ("${AUTOCONF}" STREQUAL "AUTOCONF-NOTFOUND")
message(FATAL_ERROR "Required program `autoconf` not found.")
endif()
find_program(LIBTOOLIZE libtoolize)
if ("${LIBTOOLIZE}" STREQUAL "LIBTOOLIZE-NOTFOUND")
message(FATAL_ERROR "Required program `libtoolize` not found.")
endif()
set(LIBUSB_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/libusb")
set(LIBUSB_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libusb")

View File

@@ -55,6 +55,7 @@ if (MSVC)
/we4018 # 'expression': signed/unsigned mismatch
/we4062 # Enumerator 'identifier' in a switch of enum 'enumeration' is not handled
/we4101 # 'identifier': unreferenced local variable
/we4189 # 'identifier': local variable is initialized but not referenced
/we4265 # 'class': class has virtual functions, but destructor is not virtual
/we4267 # 'var': conversion from 'size_t' to 'type', possible loss of data
/we4305 # 'context': truncation from 'type1' to 'type2'
@@ -81,6 +82,7 @@ else()
-Werror=missing-declarations
-Werror=missing-field-initializers
-Werror=reorder
-Werror=sign-compare
-Werror=switch
-Werror=uninitialized
-Werror=unused-function

View File

@@ -51,9 +51,6 @@ if (NOT MSVC)
target_compile_options(audio_core PRIVATE
-Werror=conversion
-Werror=ignored-qualifiers
-Werror=implicit-fallthrough
-Werror=reorder
-Werror=sign-compare
-Werror=shadow
-Werror=unused-parameter
-Werror=unused-variable

View File

@@ -30,7 +30,8 @@ StreamPtr AudioOut::OpenStream(Core::Timing::CoreTiming& core_timing, u32 sample
u32 num_channels, std::string&& name,
Stream::ReleaseCallback&& release_callback) {
if (!sink) {
sink = CreateSinkFromID(Settings::values.sink_id, Settings::values.audio_device_id);
sink = CreateSinkFromID(Settings::values.sink_id.GetValue(),
Settings::values.audio_device_id.GetValue());
}
return std::make_shared<Stream>(

View File

@@ -12,6 +12,7 @@
#include "audio_core/voice_context.h"
#include "common/logging/log.h"
#include "common/settings.h"
#include "core/core_timing.h"
#include "core/memory.h"
namespace {
@@ -28,10 +29,9 @@ namespace {
(static_cast<float>(r_channel) * r_mix_amount)));
}
[[nodiscard]] static constexpr std::tuple<s16, s16> Mix6To2(s16 fl_channel, s16 fr_channel,
s16 fc_channel,
[[maybe_unused]] s16 lf_channel,
s16 bl_channel, s16 br_channel) {
[[maybe_unused, nodiscard]] static constexpr std::tuple<s16, s16> Mix6To2(
s16 fl_channel, s16 fr_channel, s16 fc_channel, [[maybe_unused]] s16 lf_channel, s16 bl_channel,
s16 br_channel) {
// Front channels are mixed 36.94%, Center channels are mixed to be 26.12% & the back channels
// are mixed to be 36.94%
@@ -56,11 +56,11 @@ namespace {
const std::array<float_le, 4>& coeff) {
const auto left =
static_cast<float>(fl_channel) * coeff[0] + static_cast<float>(fc_channel) * coeff[1] +
static_cast<float>(lf_channel) * coeff[2] + static_cast<float>(bl_channel) * coeff[0];
static_cast<float>(lf_channel) * coeff[2] + static_cast<float>(bl_channel) * coeff[3];
const auto right =
static_cast<float>(fr_channel) * coeff[0] + static_cast<float>(fc_channel) * coeff[1] +
static_cast<float>(lf_channel) * coeff[2] + static_cast<float>(br_channel) * coeff[0];
static_cast<float>(lf_channel) * coeff[2] + static_cast<float>(br_channel) * coeff[3];
return {ClampToS16(static_cast<s32>(left)), ClampToS16(static_cast<s32>(right))};
}
@@ -68,7 +68,9 @@ namespace {
} // namespace
namespace AudioCore {
AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_,
constexpr s32 NUM_BUFFERS = 2;
AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing_, Core::Memory::Memory& memory_,
AudioCommon::AudioRendererParameter params,
Stream::ReleaseCallback&& release_callback,
std::size_t instance_number)
@@ -77,7 +79,8 @@ AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory
sink_context(params.sink_count), splitter_context(),
voices(params.voice_count), memory{memory_},
command_generator(worker_params, voice_context, mix_context, splitter_context, effect_context,
memory) {
memory),
core_timing{core_timing_} {
behavior_info.SetUserRevision(params.revision);
splitter_context.Initialize(behavior_info, params.splitter_count,
params.num_splitter_send_channels);
@@ -86,16 +89,27 @@ AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory
stream = audio_out->OpenStream(
core_timing, params.sample_rate, AudioCommon::STREAM_NUM_CHANNELS,
fmt::format("AudioRenderer-Instance{}", instance_number), std::move(release_callback));
audio_out->StartStream(stream);
QueueMixedBuffer(0);
QueueMixedBuffer(1);
QueueMixedBuffer(2);
QueueMixedBuffer(3);
process_event = Core::Timing::CreateEvent(
fmt::format("AudioRenderer-Instance{}-Process", instance_number),
[this](std::uintptr_t, std::chrono::nanoseconds) { ReleaseAndQueueBuffers(); });
for (s32 i = 0; i < NUM_BUFFERS; ++i) {
QueueMixedBuffer(i);
}
}
AudioRenderer::~AudioRenderer() = default;
ResultCode AudioRenderer::Start() {
audio_out->StartStream(stream);
ReleaseAndQueueBuffers();
return ResultSuccess;
}
ResultCode AudioRenderer::Stop() {
audio_out->StopStream(stream);
return ResultSuccess;
}
u32 AudioRenderer::GetSampleRate() const {
return worker_params.sample_rate;
}
@@ -114,7 +128,7 @@ Stream::State AudioRenderer::GetStreamState() const {
ResultCode AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params,
std::vector<u8>& output_params) {
std::scoped_lock lock{mutex};
InfoUpdater info_updater{input_params, output_params, behavior_info};
if (!info_updater.UpdateBehaviorInfo(behavior_info)) {
@@ -194,9 +208,6 @@ ResultCode AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_param
LOG_ERROR(Audio, "Audio buffers were not consumed!");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
ReleaseAndQueueBuffers();
return ResultSuccess;
}
@@ -220,10 +231,8 @@ void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) {
command_generator.PostCommand();
// Base sample size
std::size_t BUFFER_SIZE{worker_params.sample_count};
// Samples
std::vector<s16> buffer(BUFFER_SIZE * stream->GetNumChannels());
// Make sure to clear our samples
std::memset(buffer.data(), 0, buffer.size() * sizeof(s16));
// Samples, making sure to clear
std::vector<s16> buffer(BUFFER_SIZE * stream->GetNumChannels(), 0);
if (sink_context.InUse()) {
const auto stream_channel_count = stream->GetNumChannels();
@@ -231,7 +240,7 @@ void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) {
const auto channel_count = buffer_offsets.size();
const auto& final_mix = mix_context.GetFinalMixInfo();
const auto& in_params = final_mix.GetInParams();
std::vector<s32*> mix_buffers(channel_count);
std::vector<std::span<s32>> mix_buffers(channel_count);
for (std::size_t i = 0; i < channel_count; i++) {
mix_buffers[i] =
command_generator.GetMixBuffer(in_params.buffer_offset + buffer_offsets[i]);
@@ -284,18 +293,11 @@ void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) {
buffer[i * stream_channel_count + 0] = Mix2To1(fl_sample, fr_sample);
} else if (stream_channel_count == 2) {
// Mix all channels into 2 channels
if (sink_context.HasDownMixingCoefficients()) {
const auto [left, right] = Mix6To2WithCoefficients(
fl_sample, fr_sample, fc_sample, lf_sample, bl_sample, br_sample,
sink_context.GetDownmixCoefficients());
buffer[i * stream_channel_count + 0] = left;
buffer[i * stream_channel_count + 1] = right;
} else {
const auto [left, right] = Mix6To2(fl_sample, fr_sample, fc_sample,
lf_sample, bl_sample, br_sample);
buffer[i * stream_channel_count + 0] = left;
buffer[i * stream_channel_count + 1] = right;
}
const auto [left, right] = Mix6To2WithCoefficients(
fl_sample, fr_sample, fc_sample, lf_sample, bl_sample, br_sample,
sink_context.GetDownmixCoefficients());
buffer[i * stream_channel_count + 0] = left;
buffer[i * stream_channel_count + 1] = right;
} else if (stream_channel_count == 6) {
// Pass through
buffer[i * stream_channel_count + 0] = fl_sample;
@@ -315,10 +317,24 @@ void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) {
}
void AudioRenderer::ReleaseAndQueueBuffers() {
const auto released_buffers{audio_out->GetTagsAndReleaseBuffers(stream)};
for (const auto& tag : released_buffers) {
QueueMixedBuffer(tag);
if (!stream->IsPlaying()) {
return;
}
{
std::scoped_lock lock{mutex};
const auto released_buffers{audio_out->GetTagsAndReleaseBuffers(stream)};
for (const auto& tag : released_buffers) {
QueueMixedBuffer(tag);
}
}
const f32 sample_rate = static_cast<f32>(GetSampleRate());
const f32 sample_count = static_cast<f32>(GetSampleCount());
const f32 consume_rate = sample_rate / (sample_count * (sample_count / 240));
const s32 ms = (1000 / static_cast<s32>(consume_rate)) - 1;
const std::chrono::milliseconds next_event_time(std::max(ms / NUM_BUFFERS, 1));
core_timing.ScheduleEvent(next_event_time, process_event, {});
}
} // namespace AudioCore

View File

@@ -6,6 +6,7 @@
#include <array>
#include <memory>
#include <mutex>
#include <vector>
#include "audio_core/behavior_info.h"
@@ -45,6 +46,8 @@ public:
[[nodiscard]] ResultCode UpdateAudioRenderer(const std::vector<u8>& input_params,
std::vector<u8>& output_params);
[[nodiscard]] ResultCode Start();
[[nodiscard]] ResultCode Stop();
void QueueMixedBuffer(Buffer::Tag tag);
void ReleaseAndQueueBuffers();
[[nodiscard]] u32 GetSampleRate() const;
@@ -68,6 +71,9 @@ private:
Core::Memory::Memory& memory;
CommandGenerator command_generator;
std::size_t elapsed_frame_count{};
Core::Timing::CoreTiming& core_timing;
std::shared_ptr<Core::Timing::EventType> process_event;
std::mutex mutex;
};
} // namespace AudioCore

View File

@@ -31,7 +31,7 @@ constexpr std::array<f32, AudioCommon::I3DL2REVERB_TAPS> EARLY_GAIN{
0.72867f, 0.69794f, 0.5464f, 0.24563f, 0.45214f, 0.44042f};
template <std::size_t N>
void ApplyMix(s32* output, const s32* input, s32 gain, s32 sample_count) {
void ApplyMix(std::span<s32> output, std::span<const s32> input, s32 gain, s32 sample_count) {
for (std::size_t i = 0; i < static_cast<std::size_t>(sample_count); i += N) {
for (std::size_t j = 0; j < N; j++) {
output[i + j] +=
@@ -40,7 +40,8 @@ void ApplyMix(s32* output, const s32* input, s32 gain, s32 sample_count) {
}
}
s32 ApplyMixRamp(s32* output, const s32* input, float gain, float delta, s32 sample_count) {
s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, float gain, float delta,
s32 sample_count) {
s32 x = 0;
for (s32 i = 0; i < sample_count; i++) {
x = static_cast<s32>(static_cast<float>(input[i]) * gain);
@@ -50,20 +51,22 @@ s32 ApplyMixRamp(s32* output, const s32* input, float gain, float delta, s32 sam
return x;
}
void ApplyGain(s32* output, const s32* input, s32 gain, s32 delta, s32 sample_count) {
void ApplyGain(std::span<s32> output, std::span<const s32> input, s32 gain, s32 delta,
s32 sample_count) {
for (s32 i = 0; i < sample_count; i++) {
output[i] = static_cast<s32>((static_cast<s64>(input[i]) * gain + 0x4000) >> 15);
gain += delta;
}
}
void ApplyGainWithoutDelta(s32* output, const s32* input, s32 gain, s32 sample_count) {
void ApplyGainWithoutDelta(std::span<s32> output, std::span<const s32> input, s32 gain,
s32 sample_count) {
for (s32 i = 0; i < sample_count; i++) {
output[i] = static_cast<s32>((static_cast<s64>(input[i]) * gain + 0x4000) >> 15);
}
}
s32 ApplyMixDepop(s32* output, s32 first_sample, s32 delta, s32 sample_count) {
s32 ApplyMixDepop(std::span<s32> output, s32 first_sample, s32 delta, s32 sample_count) {
const bool positive = first_sample > 0;
auto final_sample = std::abs(first_sample);
for (s32 i = 0; i < sample_count; i++) {
@@ -128,10 +131,10 @@ constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_6CH{4, 0, 0, 1, 1, 1, 1,
1, 1, 1, 0, 0, 0, 0, 3, 3, 3};
template <std::size_t CHANNEL_COUNT>
void ApplyReverbGeneric(I3dl2ReverbState& state,
const std::array<const s32*, AudioCommon::MAX_CHANNEL_COUNT>& input,
const std::array<s32*, AudioCommon::MAX_CHANNEL_COUNT>& output,
s32 sample_count) {
void ApplyReverbGeneric(
I3dl2ReverbState& state,
const std::array<std::span<const s32>, AudioCommon::MAX_CHANNEL_COUNT>& input,
const std::array<std::span<s32>, AudioCommon::MAX_CHANNEL_COUNT>& output, s32 sample_count) {
auto GetTapLookup = []() {
if constexpr (CHANNEL_COUNT == 1) {
@@ -400,7 +403,10 @@ void CommandGenerator::GenerateDataSourceCommand(ServerVoiceInfo& voice_info, Vo
}
} else {
switch (in_params.sample_format) {
case SampleFormat::Pcm8:
case SampleFormat::Pcm16:
case SampleFormat::Pcm32:
case SampleFormat::PcmFloat:
DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(channel), dsp_state, channel,
worker_params.sample_rate, worker_params.sample_count,
in_params.node_id);
@@ -454,8 +460,8 @@ void CommandGenerator::GenerateBiquadFilterCommand([[maybe_unused]] s32 mix_buff
"input_mix_buffer={}, output_mix_buffer={}",
node_id, input_offset, output_offset);
}
const auto* input = GetMixBuffer(input_offset);
auto* output = GetMixBuffer(output_offset);
std::span<const s32> input = GetMixBuffer(input_offset);
std::span<s32> output = GetMixBuffer(output_offset);
// Biquad filter parameters
const auto [n0, n1, n2] = params.numerator;
@@ -548,8 +554,8 @@ void CommandGenerator::GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, E
return;
}
std::array<const s32*, AudioCommon::MAX_CHANNEL_COUNT> input{};
std::array<s32*, AudioCommon::MAX_CHANNEL_COUNT> output{};
std::array<std::span<const s32>, AudioCommon::MAX_CHANNEL_COUNT> input{};
std::array<std::span<s32>, AudioCommon::MAX_CHANNEL_COUNT> output{};
const auto status = params.status;
for (s32 i = 0; i < channel_count; i++) {
@@ -584,7 +590,8 @@ void CommandGenerator::GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, E
for (s32 i = 0; i < channel_count; i++) {
// Only copy if the buffer input and output do not match!
if ((mix_buffer_offset + params.input[i]) != (mix_buffer_offset + params.output[i])) {
std::memcpy(output[i], input[i], worker_params.sample_count * sizeof(s32));
std::memcpy(output[i].data(), input[i].data(),
worker_params.sample_count * sizeof(s32));
}
}
}
@@ -600,8 +607,8 @@ void CommandGenerator::GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset,
for (s32 i = 0; i < channel_count; i++) {
// TODO(ogniK): Actually implement biquad filter
if (params.input[i] != params.output[i]) {
const auto* input = GetMixBuffer(mix_buffer_offset + params.input[i]);
auto* output = GetMixBuffer(mix_buffer_offset + params.output[i]);
std::span<const s32> input = GetMixBuffer(mix_buffer_offset + params.input[i]);
std::span<s32> output = GetMixBuffer(mix_buffer_offset + params.output[i]);
ApplyMix<1>(output, input, 32768, worker_params.sample_count);
}
}
@@ -640,14 +647,15 @@ void CommandGenerator::GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* inf
if (samples_read != static_cast<int>(worker_params.sample_count) &&
samples_read <= params.sample_count) {
std::memset(GetMixBuffer(output_index), 0, params.sample_count - samples_read);
std::memset(GetMixBuffer(output_index).data(), 0,
params.sample_count - samples_read);
}
} else {
AuxInfoDSP empty{};
memory.WriteBlock(aux->GetSendInfo(), &empty, sizeof(AuxInfoDSP));
memory.WriteBlock(aux->GetRecvInfo(), &empty, sizeof(AuxInfoDSP));
if (output_index != input_index) {
std::memcpy(GetMixBuffer(output_index), GetMixBuffer(input_index),
std::memcpy(GetMixBuffer(output_index).data(), GetMixBuffer(input_index).data(),
worker_params.sample_count * sizeof(s32));
}
}
@@ -665,7 +673,7 @@ ServerSplitterDestinationData* CommandGenerator::GetDestinationData(s32 splitter
}
s32 CommandGenerator::WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples,
const s32* data, u32 sample_count, u32 write_offset,
std::span<const s32> data, u32 sample_count, u32 write_offset,
u32 write_count) {
if (max_samples == 0) {
return 0;
@@ -675,14 +683,14 @@ s32 CommandGenerator::WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u3
return 0;
}
std::size_t data_offset{};
s32 data_offset{};
u32 remaining = sample_count;
while (remaining > 0) {
// Get position in buffer
const auto base = send_buffer + (offset * sizeof(u32));
const auto samples_to_grab = std::min(max_samples - offset, remaining);
// Write to output
memory.WriteBlock(base, (data + data_offset), samples_to_grab * sizeof(u32));
memory.WriteBlock(base, (data.data() + data_offset), samples_to_grab * sizeof(u32));
offset = (offset + samples_to_grab) % max_samples;
remaining -= samples_to_grab;
data_offset += samples_to_grab;
@@ -695,7 +703,7 @@ s32 CommandGenerator::WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u3
}
s32 CommandGenerator::ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples,
s32* out_data, u32 sample_count, u32 read_offset,
std::span<s32> out_data, u32 sample_count, u32 read_offset,
u32 read_count) {
if (max_samples == 0) {
return 0;
@@ -707,15 +715,16 @@ s32 CommandGenerator::ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u3
}
u32 remaining = sample_count;
s32 data_offset{};
while (remaining > 0) {
const auto base = recv_buffer + (offset * sizeof(u32));
const auto samples_to_grab = std::min(max_samples - offset, remaining);
std::vector<s32> buffer(samples_to_grab);
memory.ReadBlock(base, buffer.data(), buffer.size() * sizeof(u32));
std::memcpy(out_data, buffer.data(), buffer.size() * sizeof(u32));
out_data += samples_to_grab;
std::memcpy(out_data.data() + data_offset, buffer.data(), buffer.size() * sizeof(u32));
offset = (offset + samples_to_grab) % max_samples;
remaining -= samples_to_grab;
data_offset += samples_to_grab;
}
if (read_count != 0) {
@@ -795,7 +804,7 @@ void CommandGenerator::UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbSta
state.lowpass_1 = 0.0f;
} else {
const auto a = 1.0f - hf_gain;
const auto b = 2.0f * (1.0f - hf_gain * CosD(256.0f * info.hf_reference /
const auto b = 2.0f * (2.0f - hf_gain * CosD(256.0f * info.hf_reference /
static_cast<f32>(info.sample_rate)));
const auto c = std::sqrt(b * b - 4.0f * a * a);
@@ -843,7 +852,7 @@ void CommandGenerator::UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbSta
}
const auto max_early_delay = state.early_delay_line.GetMaxDelay();
const auto reflection_time = 1000.0f * (0.0098f * info.reverb_delay + 0.02f);
const auto reflection_time = 1000.0f * (0.9998f * info.reverb_delay + 0.02f);
for (std::size_t tap = 0; tap < AudioCommon::I3DL2REVERB_TAPS; tap++) {
const auto length = AudioCommon::CalculateDelaySamples(
sample_rate, 1000.0f * info.reflection_delay + reflection_time * EARLY_TAP_TIMES[tap]);
@@ -962,8 +971,8 @@ void CommandGenerator::GenerateMixCommand(std::size_t output_offset, std::size_t
node_id, input_offset, output_offset, volume);
}
auto* output = GetMixBuffer(output_offset);
const auto* input = GetMixBuffer(input_offset);
std::span<s32> output = GetMixBuffer(output_offset);
std::span<const s32> input = GetMixBuffer(input_offset);
const s32 gain = static_cast<s32>(volume * 32768.0f);
// Mix with loop unrolling
@@ -1003,8 +1012,10 @@ void CommandGenerator::GenerateFinalMixCommand() {
}
}
s32 CommandGenerator::DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
s32 sample_count, s32 channel, std::size_t mix_offset) {
template <typename T>
s32 CommandGenerator::DecodePcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
s32 sample_start_offset, s32 sample_end_offset, s32 sample_count,
s32 channel, std::size_t mix_offset) {
const auto& in_params = voice_info.GetInParams();
const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
if (wave_buffer.buffer_address == 0) {
@@ -1013,39 +1024,50 @@ s32 CommandGenerator::DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_s
if (wave_buffer.buffer_size == 0) {
return 0;
}
if (wave_buffer.end_sample_offset < wave_buffer.start_sample_offset) {
if (sample_end_offset < sample_start_offset) {
return 0;
}
const auto samples_remaining =
(wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) - dsp_state.offset;
const auto samples_remaining = (sample_end_offset - sample_start_offset) - dsp_state.offset;
const auto start_offset =
((wave_buffer.start_sample_offset + dsp_state.offset) * in_params.channel_count) *
sizeof(s16);
((dsp_state.offset + sample_start_offset) * in_params.channel_count) * sizeof(T);
const auto buffer_pos = wave_buffer.buffer_address + start_offset;
const auto samples_processed = std::min(sample_count, samples_remaining);
if (in_params.channel_count == 1) {
std::vector<s16> buffer(samples_processed);
memory.ReadBlock(buffer_pos, buffer.data(), buffer.size() * sizeof(s16));
for (std::size_t i = 0; i < buffer.size(); i++) {
sample_buffer[mix_offset + i] = buffer[i];
}
} else {
const auto channel_count = in_params.channel_count;
std::vector<s16> buffer(samples_processed * channel_count);
memory.ReadBlock(buffer_pos, buffer.data(), buffer.size() * sizeof(s16));
const auto channel_count = in_params.channel_count;
std::vector<T> buffer(samples_processed * channel_count);
memory.ReadBlock(buffer_pos, buffer.data(), buffer.size() * sizeof(T));
if constexpr (std::is_floating_point_v<T>) {
for (std::size_t i = 0; i < static_cast<std::size_t>(samples_processed); i++) {
sample_buffer[mix_offset + i] = static_cast<s32>(buffer[i * channel_count + channel] *
std::numeric_limits<s16>::max());
}
} else if constexpr (sizeof(T) == 1) {
for (std::size_t i = 0; i < static_cast<std::size_t>(samples_processed); i++) {
sample_buffer[mix_offset + i] =
static_cast<s32>(static_cast<f32>(buffer[i * channel_count + channel] /
std::numeric_limits<s8>::max()) *
std::numeric_limits<s16>::max());
}
} else if constexpr (sizeof(T) == 2) {
for (std::size_t i = 0; i < static_cast<std::size_t>(samples_processed); i++) {
sample_buffer[mix_offset + i] = buffer[i * channel_count + channel];
}
} else {
for (std::size_t i = 0; i < static_cast<std::size_t>(samples_processed); i++) {
sample_buffer[mix_offset + i] =
static_cast<s32>(static_cast<f32>(buffer[i * channel_count + channel] /
std::numeric_limits<s32>::max()) *
std::numeric_limits<s16>::max());
}
}
return samples_processed;
}
s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
s32 sample_count, [[maybe_unused]] s32 channel,
std::size_t mix_offset) {
s32 sample_start_offset, s32 sample_end_offset, s32 sample_count,
[[maybe_unused]] s32 channel, std::size_t mix_offset) {
const auto& in_params = voice_info.GetInParams();
const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
if (wave_buffer.buffer_address == 0) {
@@ -1054,7 +1076,7 @@ s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_s
if (wave_buffer.buffer_size == 0) {
return 0;
}
if (wave_buffer.end_sample_offset < wave_buffer.start_sample_offset) {
if (sample_end_offset < sample_start_offset) {
return 0;
}
@@ -1079,10 +1101,9 @@ s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_s
s32 coef1 = coeffs[idx * 2];
s32 coef2 = coeffs[idx * 2 + 1];
const auto samples_remaining =
(wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) - dsp_state.offset;
const auto samples_remaining = (sample_end_offset - sample_start_offset) - dsp_state.offset;
const auto samples_processed = std::min(sample_count, samples_remaining);
const auto sample_pos = wave_buffer.start_sample_offset + dsp_state.offset;
const auto sample_pos = dsp_state.offset + sample_start_offset;
const auto samples_remaining_in_frame = sample_pos % SAMPLES_PER_FRAME;
auto position_in_frame = ((sample_pos / SAMPLES_PER_FRAME) * NIBBLES_PER_SAMPLE) +
@@ -1157,12 +1178,14 @@ s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_s
return samples_processed;
}
s32* CommandGenerator::GetMixBuffer(std::size_t index) {
return mix_buffer.data() + (index * worker_params.sample_count);
std::span<s32> CommandGenerator::GetMixBuffer(std::size_t index) {
return std::span<s32>(mix_buffer.data() + (index * worker_params.sample_count),
worker_params.sample_count);
}
const s32* CommandGenerator::GetMixBuffer(std::size_t index) const {
return mix_buffer.data() + (index * worker_params.sample_count);
std::span<const s32> CommandGenerator::GetMixBuffer(std::size_t index) const {
return std::span<const s32>(mix_buffer.data() + (index * worker_params.sample_count),
worker_params.sample_count);
}
std::size_t CommandGenerator::GetMixChannelBufferOffset(s32 channel) const {
@@ -1173,15 +1196,15 @@ std::size_t CommandGenerator::GetTotalMixBufferCount() const {
return worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT;
}
s32* CommandGenerator::GetChannelMixBuffer(s32 channel) {
std::span<s32> CommandGenerator::GetChannelMixBuffer(s32 channel) {
return GetMixBuffer(worker_params.mix_buffer_count + channel);
}
const s32* CommandGenerator::GetChannelMixBuffer(s32 channel) const {
std::span<const s32> CommandGenerator::GetChannelMixBuffer(s32 channel) const {
return GetMixBuffer(worker_params.mix_buffer_count + channel);
}
void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* output,
void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, std::span<s32> output,
VoiceState& dsp_state, s32 channel,
s32 target_sample_rate, s32 sample_count,
s32 node_id) {
@@ -1193,7 +1216,7 @@ void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* o
node_id, channel, in_params.sample_format, sample_count, in_params.sample_rate,
in_params.mix_id, in_params.splitter_info_id);
}
ASSERT_OR_EXECUTE(output != nullptr, { return; });
ASSERT_OR_EXECUTE(output.data() != nullptr, { return; });
const auto resample_rate = static_cast<s32>(
static_cast<float>(in_params.sample_rate) / static_cast<float>(target_sample_rate) *
@@ -1210,9 +1233,9 @@ void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* o
}
std::size_t temp_mix_offset{};
bool is_buffer_completed{false};
s32 samples_output{};
auto samples_remaining = sample_count;
while (samples_remaining > 0 && !is_buffer_completed) {
while (samples_remaining > 0) {
const auto samples_to_output = std::min(samples_remaining, min_required_samples);
const auto samples_to_read = (samples_to_output * resample_rate + dsp_state.fraction) >> 15;
@@ -1229,24 +1252,53 @@ void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* o
const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
// No more data can be read
if (!dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index]) {
is_buffer_completed = true;
break;
}
if (in_params.sample_format == SampleFormat::Adpcm && dsp_state.offset == 0 &&
wave_buffer.context_address != 0 && wave_buffer.context_size != 0) {
// TODO(ogniK): ADPCM loop context
memory.ReadBlock(wave_buffer.context_address, &dsp_state.context,
sizeof(ADPCMContext));
}
s32 samples_offset_start;
s32 samples_offset_end;
if (dsp_state.loop_count > 0 && wave_buffer.loop_start_sample != 0 &&
wave_buffer.loop_end_sample != 0 &&
wave_buffer.loop_start_sample <= wave_buffer.loop_end_sample) {
samples_offset_start = wave_buffer.loop_start_sample;
samples_offset_end = wave_buffer.loop_end_sample;
} else {
samples_offset_start = wave_buffer.start_sample_offset;
samples_offset_end = wave_buffer.end_sample_offset;
}
s32 samples_decoded{0};
switch (in_params.sample_format) {
case SampleFormat::Pcm8:
samples_decoded =
DecodePcm<s8>(voice_info, dsp_state, samples_offset_start, samples_offset_end,
samples_to_read - samples_read, channel, temp_mix_offset);
break;
case SampleFormat::Pcm16:
samples_decoded = DecodePcm16(voice_info, dsp_state, samples_to_read - samples_read,
channel, temp_mix_offset);
samples_decoded =
DecodePcm<s16>(voice_info, dsp_state, samples_offset_start, samples_offset_end,
samples_to_read - samples_read, channel, temp_mix_offset);
break;
case SampleFormat::Pcm32:
samples_decoded =
DecodePcm<s32>(voice_info, dsp_state, samples_offset_start, samples_offset_end,
samples_to_read - samples_read, channel, temp_mix_offset);
break;
case SampleFormat::PcmFloat:
samples_decoded =
DecodePcm<f32>(voice_info, dsp_state, samples_offset_start, samples_offset_end,
samples_to_read - samples_read, channel, temp_mix_offset);
break;
case SampleFormat::Adpcm:
samples_decoded = DecodeAdpcm(voice_info, dsp_state, samples_to_read - samples_read,
channel, temp_mix_offset);
samples_decoded =
DecodeAdpcm(voice_info, dsp_state, samples_offset_start, samples_offset_end,
samples_to_read - samples_read, channel, temp_mix_offset);
break;
default:
UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format);
@@ -1257,15 +1309,19 @@ void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* o
dsp_state.offset += samples_decoded;
dsp_state.played_sample_count += samples_decoded;
if (dsp_state.offset >=
(wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) ||
if (dsp_state.offset >= (samples_offset_end - samples_offset_start) ||
samples_decoded == 0) {
// Reset our sample offset
dsp_state.offset = 0;
if (wave_buffer.is_looping) {
if (samples_decoded == 0) {
dsp_state.loop_count++;
if (wave_buffer.loop_count > 0 &&
(dsp_state.loop_count > wave_buffer.loop_count || samples_decoded == 0)) {
// End of our buffer
is_buffer_completed = true;
voice_info.SetWaveBufferCompleted(dsp_state, wave_buffer);
}
if (samples_decoded == 0) {
break;
}
@@ -1273,35 +1329,29 @@ void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* o
dsp_state.played_sample_count = 0;
}
} else {
// Update our wave buffer states
dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index] = false;
dsp_state.wave_buffer_consumed++;
dsp_state.wave_buffer_index =
(dsp_state.wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
if (wave_buffer.end_of_stream) {
dsp_state.played_sample_count = 0;
}
voice_info.SetWaveBufferCompleted(dsp_state, wave_buffer);
}
}
}
if (in_params.behavior_flags.is_pitch_and_src_skipped.Value()) {
// No need to resample
std::memcpy(output, sample_buffer.data(), samples_read * sizeof(s32));
std::memcpy(output.data() + samples_output, sample_buffer.data(),
samples_read * sizeof(s32));
} else {
std::fill(sample_buffer.begin() + temp_mix_offset,
sample_buffer.begin() + temp_mix_offset + (samples_to_read - samples_read),
0);
AudioCore::Resample(output, sample_buffer.data(), resample_rate, dsp_state.fraction,
samples_to_output);
AudioCore::Resample(output.data() + samples_output, sample_buffer.data(), resample_rate,
dsp_state.fraction, samples_to_output);
// Resample
for (std::size_t i = 0; i < AudioCommon::MAX_SAMPLE_HISTORY; i++) {
dsp_state.sample_history[i] = sample_buffer[samples_to_read + i];
}
}
output += samples_to_output;
samples_remaining -= samples_to_output;
samples_output += samples_to_output;
}
}

View File

@@ -5,6 +5,7 @@
#pragma once
#include <array>
#include <span>
#include "audio_core/common.h"
#include "audio_core/voice_context.h"
#include "common/common_types.h"
@@ -41,10 +42,10 @@ public:
void PreCommand();
void PostCommand();
[[nodiscard]] s32* GetChannelMixBuffer(s32 channel);
[[nodiscard]] const s32* GetChannelMixBuffer(s32 channel) const;
[[nodiscard]] s32* GetMixBuffer(std::size_t index);
[[nodiscard]] const s32* GetMixBuffer(std::size_t index) const;
[[nodiscard]] std::span<s32> GetChannelMixBuffer(s32 channel);
[[nodiscard]] std::span<const s32> GetChannelMixBuffer(s32 channel) const;
[[nodiscard]] std::span<s32> GetMixBuffer(std::size_t index);
[[nodiscard]] std::span<const s32> GetMixBuffer(std::size_t index) const;
[[nodiscard]] std::size_t GetMixChannelBufferOffset(s32 channel) const;
[[nodiscard]] std::size_t GetTotalMixBufferCount() const;
@@ -77,21 +78,24 @@ private:
void GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled);
[[nodiscard]] ServerSplitterDestinationData* GetDestinationData(s32 splitter_id, s32 index);
s32 WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples, const s32* data,
u32 sample_count, u32 write_offset, u32 write_count);
s32 ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples, s32* out_data,
u32 sample_count, u32 read_offset, u32 read_count);
s32 WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples,
std::span<const s32> data, u32 sample_count, u32 write_offset,
u32 write_count);
s32 ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples,
std::span<s32> out_data, u32 sample_count, u32 read_offset, u32 read_count);
void InitializeI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state,
std::vector<u8>& work_buffer);
void UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, bool should_clear);
// DSP Code
s32 DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count,
s32 channel, std::size_t mix_offset);
s32 DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count,
s32 channel, std::size_t mix_offset);
void DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* output, VoiceState& dsp_state,
s32 channel, s32 target_sample_rate, s32 sample_count, s32 node_id);
template <typename T>
s32 DecodePcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_start_offset,
s32 sample_end_offset, s32 sample_count, s32 channel, std::size_t mix_offset);
s32 DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_start_offset,
s32 sample_end_offset, s32 sample_count, s32 channel, std::size_t mix_offset);
void DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, std::span<s32> output,
VoiceState& dsp_state, s32 channel, s32 target_sample_rate,
s32 sample_count, s32 node_id);
AudioCommon::AudioRendererParameter& worker_params;
VoiceContext& voice_context;

View File

@@ -189,9 +189,6 @@ bool InfoUpdater::UpdateVoices(VoiceContext& voice_context,
if (voice_in_params.is_new) {
// Default our values for our voice
voice_info.Initialize();
if (channel_count == 0 || channel_count > AudioCommon::MAX_CHANNEL_COUNT) {
continue;
}
// Zero out our voice states
for (std::size_t channel = 0; channel < channel_count; channel++) {

View File

@@ -15,10 +15,17 @@ std::size_t SinkContext::GetCount() const {
void SinkContext::UpdateMainSink(const SinkInfo::InParams& in) {
ASSERT(in.type == SinkTypes::Device);
has_downmix_coefs = in.device.down_matrix_enabled;
if (has_downmix_coefs) {
if (in.device.down_matrix_enabled) {
downmix_coefficients = in.device.down_matrix_coef;
} else {
downmix_coefficients = {
1.0f, // front
0.707f, // center
0.0f, // lfe
0.707f, // back
};
}
in_use = in.in_use;
use_count = in.device.input_count;
buffers = in.device.input;
@@ -34,10 +41,6 @@ std::vector<u8> SinkContext::OutputBuffers() const {
return buffer_ret;
}
bool SinkContext::HasDownMixingCoefficients() const {
return has_downmix_coefs;
}
const DownmixCoefficients& SinkContext::GetDownmixCoefficients() const {
return downmix_coefficients;
}

View File

@@ -84,7 +84,6 @@ public:
[[nodiscard]] bool InUse() const;
[[nodiscard]] std::vector<u8> OutputBuffers() const;
[[nodiscard]] bool HasDownMixingCoefficients() const;
[[nodiscard]] const DownmixCoefficients& GetDownmixCoefficients() const;
private:
@@ -92,7 +91,6 @@ private:
s32 use_count{};
std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> buffers{};
std::size_t sink_count{};
bool has_downmix_coefs{false};
DownmixCoefficients downmix_coefficients{};
};
} // namespace AudioCore

View File

@@ -66,7 +66,7 @@ void ServerVoiceInfo::Initialize() {
in_params.last_volume = 0.0f;
in_params.biquad_filter.fill({});
in_params.wave_buffer_count = 0;
in_params.wave_bufffer_head = 0;
in_params.wave_buffer_head = 0;
in_params.mix_id = AudioCommon::NO_MIX;
in_params.splitter_info_id = AudioCommon::NO_SPLITTER;
in_params.additional_params_address = 0;
@@ -75,7 +75,7 @@ void ServerVoiceInfo::Initialize() {
out_params.played_sample_count = 0;
out_params.wave_buffer_consumed = 0;
in_params.voice_drop_flag = false;
in_params.buffer_mapped = false;
in_params.buffer_mapped = true;
in_params.wave_buffer_flush_request_count = 0;
in_params.was_biquad_filter_enabled.fill(false);
@@ -126,7 +126,7 @@ void ServerVoiceInfo::UpdateParameters(const VoiceInfo::InParams& voice_in,
in_params.volume = voice_in.volume;
in_params.biquad_filter = voice_in.biquad_filter;
in_params.wave_buffer_count = voice_in.wave_buffer_count;
in_params.wave_bufffer_head = voice_in.wave_buffer_head;
in_params.wave_buffer_head = voice_in.wave_buffer_head;
if (behavior_info.IsFlushVoiceWaveBuffersSupported()) {
const auto in_request_count = in_params.wave_buffer_flush_request_count;
const auto voice_request_count = voice_in.wave_buffer_flush_request_count;
@@ -185,14 +185,16 @@ void ServerVoiceInfo::UpdateWaveBuffers(
wave_buffer.buffer_size = 0;
wave_buffer.context_address = 0;
wave_buffer.context_size = 0;
wave_buffer.loop_start_sample = 0;
wave_buffer.loop_end_sample = 0;
wave_buffer.sent_to_dsp = true;
}
// Mark all our wave buffers as invalid
for (std::size_t channel = 0; channel < static_cast<std::size_t>(in_params.channel_count);
channel++) {
for (auto& is_valid : voice_states[channel]->is_wave_buffer_valid) {
is_valid = false;
for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; ++i) {
voice_states[channel]->is_wave_buffer_valid[i] = false;
}
}
}
@@ -211,7 +213,7 @@ void ServerVoiceInfo::UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer,
const WaveBuffer& in_wave_buffer, SampleFormat sample_format,
bool is_buffer_valid,
[[maybe_unused]] BehaviorInfo& behavior_info) {
if (!is_buffer_valid && out_wavebuffer.sent_to_dsp) {
if (!is_buffer_valid && out_wavebuffer.sent_to_dsp && out_wavebuffer.buffer_address != 0) {
out_wavebuffer.buffer_address = 0;
out_wavebuffer.buffer_size = 0;
}
@@ -219,11 +221,40 @@ void ServerVoiceInfo::UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer,
if (!in_wave_buffer.sent_to_server || !in_params.buffer_mapped) {
// Validate sample offset sizings
if (sample_format == SampleFormat::Pcm16) {
const auto buffer_size = in_wave_buffer.buffer_size;
if (in_wave_buffer.start_sample_offset < 0 || in_wave_buffer.end_sample_offset < 0 ||
(buffer_size < (sizeof(s16) * in_wave_buffer.start_sample_offset)) ||
(buffer_size < (sizeof(s16) * in_wave_buffer.end_sample_offset))) {
const s64 buffer_size = static_cast<s64>(in_wave_buffer.buffer_size);
const s64 start = sizeof(s16) * in_wave_buffer.start_sample_offset;
const s64 end = sizeof(s16) * in_wave_buffer.end_sample_offset;
if (0 > start || start > buffer_size || 0 > end || end > buffer_size) {
// TODO(ogniK): Write error info
LOG_ERROR(Audio,
"PCM16 wavebuffer has an invalid size. Buffer has size 0x{:08X}, but "
"offsets were "
"{:08X} - 0x{:08X}",
buffer_size, sizeof(s16) * in_wave_buffer.start_sample_offset,
sizeof(s16) * in_wave_buffer.end_sample_offset);
return;
}
} else if (sample_format == SampleFormat::Adpcm) {
const s64 buffer_size = static_cast<s64>(in_wave_buffer.buffer_size);
const s64 start_frames = in_wave_buffer.start_sample_offset / 14;
const s64 start_extra = in_wave_buffer.start_sample_offset % 14 == 0
? 0
: (in_wave_buffer.start_sample_offset % 14) / 2 + 1 +
(in_wave_buffer.start_sample_offset % 2);
const s64 start = start_frames * 8 + start_extra;
const s64 end_frames = in_wave_buffer.end_sample_offset / 14;
const s64 end_extra = in_wave_buffer.end_sample_offset % 14 == 0
? 0
: (in_wave_buffer.end_sample_offset % 14) / 2 + 1 +
(in_wave_buffer.end_sample_offset % 2);
const s64 end = end_frames * 8 + end_extra;
if (in_wave_buffer.start_sample_offset < 0 || start > buffer_size ||
in_wave_buffer.end_sample_offset < 0 || end > buffer_size) {
LOG_ERROR(Audio,
"ADPMC wavebuffer has an invalid size. Buffer has size 0x{:08X}, but "
"offsets were "
"{:08X} - 0x{:08X}",
in_wave_buffer.buffer_size, start, end);
return;
}
}
@@ -239,29 +270,34 @@ void ServerVoiceInfo::UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer,
out_wavebuffer.buffer_size = in_wave_buffer.buffer_size;
out_wavebuffer.context_address = in_wave_buffer.context_address;
out_wavebuffer.context_size = in_wave_buffer.context_size;
out_wavebuffer.loop_start_sample = in_wave_buffer.loop_start_sample;
out_wavebuffer.loop_end_sample = in_wave_buffer.loop_end_sample;
in_params.buffer_mapped =
in_wave_buffer.buffer_address != 0 && in_wave_buffer.buffer_size != 0;
// TODO(ogniK): Pool mapper attachment
// TODO(ogniK): IsAdpcmLoopContextBugFixed
if (sample_format == SampleFormat::Adpcm && in_wave_buffer.context_address != 0 &&
in_wave_buffer.context_size != 0 && behavior_info.IsAdpcmLoopContextBugFixed()) {
} else {
out_wavebuffer.context_address = 0;
out_wavebuffer.context_size = 0;
}
}
}
void ServerVoiceInfo::WriteOutStatus(
VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in,
std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states) {
if (voice_in.is_new) {
if (voice_in.is_new || in_params.is_new) {
in_params.is_new = true;
voice_out.wave_buffer_consumed = 0;
voice_out.played_sample_count = 0;
voice_out.voice_dropped = false;
} else if (!in_params.is_new) {
voice_out.wave_buffer_consumed = voice_states[0]->wave_buffer_consumed;
voice_out.played_sample_count = voice_states[0]->played_sample_count;
voice_out.voice_dropped = in_params.voice_drop_flag;
} else {
voice_out.wave_buffer_consumed = 0;
voice_out.played_sample_count = 0;
voice_out.voice_dropped = false;
const auto& state = voice_states[0];
voice_out.wave_buffer_consumed = state->wave_buffer_consumed;
voice_out.played_sample_count = state->played_sample_count;
voice_out.voice_dropped = state->voice_dropped;
}
}
@@ -283,7 +319,8 @@ ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() {
bool ServerVoiceInfo::ShouldSkip() const {
// TODO(ogniK): Handle unmapped wave buffers or parameters
return !in_params.in_use || (in_params.wave_buffer_count == 0) || in_params.voice_drop_flag;
return !in_params.in_use || in_params.wave_buffer_count == 0 || !in_params.buffer_mapped ||
in_params.voice_drop_flag;
}
bool ServerVoiceInfo::UpdateForCommandGeneration(VoiceContext& voice_context) {
@@ -381,7 +418,7 @@ bool ServerVoiceInfo::UpdateParametersForCommandGeneration(
void ServerVoiceInfo::FlushWaveBuffers(
u8 flush_count, std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states,
s32 channel_count) {
auto wave_head = in_params.wave_bufffer_head;
auto wave_head = in_params.wave_buffer_head;
for (u8 i = 0; i < flush_count; i++) {
in_params.wave_buffer[wave_head].sent_to_dsp = true;
@@ -401,6 +438,17 @@ bool ServerVoiceInfo::HasValidWaveBuffer(const VoiceState* state) const {
return std::find(valid_wb.begin(), valid_wb.end(), true) != valid_wb.end();
}
void ServerVoiceInfo::SetWaveBufferCompleted(VoiceState& dsp_state,
const ServerWaveBuffer& wave_buffer) {
dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index] = false;
dsp_state.wave_buffer_consumed++;
dsp_state.wave_buffer_index = (dsp_state.wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
dsp_state.loop_count = 0;
if (wave_buffer.end_of_stream) {
dsp_state.played_sample_count = 0;
}
}
VoiceContext::VoiceContext(std::size_t voice_count_) : voice_count{voice_count_} {
for (std::size_t i = 0; i < voice_count; i++) {
voice_channel_resources.emplace_back(static_cast<s32>(i));

View File

@@ -60,10 +60,12 @@ struct WaveBuffer {
u8 is_looping{};
u8 end_of_stream{};
u8 sent_to_server{};
INSERT_PADDING_BYTES(5);
INSERT_PADDING_BYTES(1);
s32 loop_count{};
u64 context_address{};
u64 context_size{};
INSERT_PADDING_BYTES(8);
u32 loop_start_sample{};
u32 loop_end_sample{};
};
static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer is an invalid size");
@@ -76,6 +78,9 @@ struct ServerWaveBuffer {
bool end_of_stream{};
VAddr context_address{};
std::size_t context_size{};
s32 loop_count{};
u32 loop_start_sample{};
u32 loop_end_sample{};
bool sent_to_dsp{true};
};
@@ -108,6 +113,7 @@ struct VoiceState {
u32 external_context_size;
bool is_external_context_used;
bool voice_dropped;
s32 loop_count;
};
class VoiceChannelResource {
@@ -206,7 +212,7 @@ public:
float last_volume{};
std::array<BiquadFilterParameter, AudioCommon::MAX_BIQUAD_FILTERS> biquad_filter{};
s32 wave_buffer_count{};
s16 wave_bufffer_head{};
s16 wave_buffer_head{};
INSERT_PADDING_BYTES(2);
BehaviorFlags behavior_flags{};
VAddr additional_params_address{};
@@ -252,6 +258,7 @@ public:
void FlushWaveBuffers(u8 flush_count,
std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states,
s32 channel_count);
void SetWaveBufferCompleted(VoiceState& dsp_state, const ServerWaveBuffer& wave_buffer);
private:
std::vector<s16> stored_samples;

View File

@@ -180,7 +180,6 @@ add_library(common STATIC
thread.cpp
thread.h
thread_queue_list.h
thread_worker.cpp
thread_worker.h
threadsafe_queue.h
time_zone.cpp
@@ -188,6 +187,7 @@ add_library(common STATIC
tiny_mt.h
tree.h
uint128.h
unique_function.h
uuid.cpp
uuid.h
vector_math.h

View File

@@ -306,9 +306,9 @@ bool IOFile::Flush() const {
errno = 0;
#ifdef _WIN32
const auto flush_result = std::fflush(file) == 0 && _commit(fileno(file)) == 0;
const auto flush_result = std::fflush(file) == 0;
#else
const auto flush_result = std::fflush(file) == 0 && fsync(fileno(file)) == 0;
const auto flush_result = std::fflush(file) == 0;
#endif
if (!flush_result) {
@@ -320,6 +320,28 @@ bool IOFile::Flush() const {
return flush_result;
}
bool IOFile::Commit() const {
if (!IsOpen()) {
return false;
}
errno = 0;
#ifdef _WIN32
const auto commit_result = std::fflush(file) == 0 && _commit(fileno(file)) == 0;
#else
const auto commit_result = std::fflush(file) == 0 && fsync(fileno(file)) == 0;
#endif
if (!commit_result) {
const auto ec = std::error_code{errno, std::generic_category()};
LOG_ERROR(Common_Filesystem, "Failed to commit the file at path={}, ec_message={}",
PathToUTF8String(file_path), ec.message());
}
return commit_result;
}
bool IOFile::SetSize(u64 size) const {
if (!IsOpen()) {
return false;
@@ -347,6 +369,9 @@ u64 IOFile::GetSize() const {
return 0;
}
// Flush any unwritten buffered data into the file prior to retrieving the file size.
std::fflush(file);
std::error_code ec;
const auto file_size = fs::file_size(file_path, ec);

View File

@@ -396,12 +396,21 @@ public:
[[nodiscard]] size_t WriteString(std::span<const char> string) const;
/**
* Attempts to flush any unwritten buffered data into the file and flush the file into the disk.
* Attempts to flush any unwritten buffered data into the file.
*
* @returns True if the flush was successful, false otherwise.
*/
bool Flush() const;
/**
* Attempts to commit the file into the disk.
* Note that this is an expensive operation as this forces the operating system to write
* the contents of the file associated with the file descriptor into the disk.
*
* @returns True if the commit was successful, false otherwise.
*/
bool Commit() const;
/**
* Resizes the file to a given size.
* If the file is resized to a smaller size, the remainder of the file is discarded.

View File

@@ -171,19 +171,22 @@ FileBackend::FileBackend(const std::filesystem::path& filename) {
FileBackend::~FileBackend() = default;
void FileBackend::Write(const Entry& entry) {
using namespace Common::Literals;
// prevent logs from going over the maximum size (in case its spamming and the user doesn't
// know)
constexpr std::size_t MAX_BYTES_WRITTEN = 100_MiB;
constexpr std::size_t MAX_BYTES_WRITTEN_EXTENDED = 1_GiB;
if (!file->IsOpen()) {
return;
}
if (Settings::values.extended_logging && bytes_written > MAX_BYTES_WRITTEN_EXTENDED) {
return;
} else if (!Settings::values.extended_logging && bytes_written > MAX_BYTES_WRITTEN) {
using namespace Common::Literals;
// Prevent logs from exceeding a set maximum size in the event that log entries are spammed.
constexpr std::size_t MAX_BYTES_WRITTEN = 100_MiB;
constexpr std::size_t MAX_BYTES_WRITTEN_EXTENDED = 1_GiB;
const bool write_limit_exceeded =
bytes_written > MAX_BYTES_WRITTEN_EXTENDED ||
(bytes_written > MAX_BYTES_WRITTEN && !Settings::values.extended_logging);
// Close the file after the write limit is exceeded.
if (write_limit_exceeded) {
file->Close();
return;
}

View File

@@ -41,7 +41,7 @@ void LogSettings() {
LOG_INFO(Config, "yuzu Configuration:");
log_setting("Controls_UseDockedMode", values.use_docked_mode.GetValue());
log_setting("System_RngSeed", values.rng_seed.GetValue().value_or(0));
log_setting("System_CurrentUser", values.current_user);
log_setting("System_CurrentUser", values.current_user.GetValue());
log_setting("System_LanguageIndex", values.language_index.GetValue());
log_setting("System_RegionIndex", values.region_index.GetValue());
log_setting("System_TimeZoneIndex", values.time_zone_index.GetValue());
@@ -61,18 +61,18 @@ void LogSettings() {
log_setting("Renderer_UseAsynchronousShaders", values.use_asynchronous_shaders.GetValue());
log_setting("Renderer_UseGarbageCollection", values.use_caches_gc.GetValue());
log_setting("Renderer_AnisotropicFilteringLevel", values.max_anisotropy.GetValue());
log_setting("Audio_OutputEngine", values.sink_id);
log_setting("Audio_OutputEngine", values.sink_id.GetValue());
log_setting("Audio_EnableAudioStretching", values.enable_audio_stretching.GetValue());
log_setting("Audio_OutputDevice", values.audio_device_id);
log_setting("DataStorage_UseVirtualSd", values.use_virtual_sd);
log_setting("Audio_OutputDevice", values.audio_device_id.GetValue());
log_setting("DataStorage_UseVirtualSd", values.use_virtual_sd.GetValue());
log_path("DataStorage_CacheDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir));
log_path("DataStorage_ConfigDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir));
log_path("DataStorage_LoadDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::LoadDir));
log_path("DataStorage_NANDDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir));
log_path("DataStorage_SDMCDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir));
log_setting("Debugging_ProgramArgs", values.program_args);
log_setting("Services_BCATBackend", values.bcat_backend);
log_setting("Services_BCATBoxcatLocal", values.bcat_boxcat_local);
log_setting("Debugging_ProgramArgs", values.program_args.GetValue());
log_setting("Services_BCATBackend", values.bcat_backend.GetValue());
log_setting("Services_BCATBoxcatLocal", values.bcat_boxcat_local.GetValue());
}
bool IsConfiguringGlobal() {
@@ -93,8 +93,8 @@ bool IsGPULevelHigh() {
}
bool IsFastmemEnabled() {
if (values.cpu_accuracy.GetValue() == CPUAccuracy::DebugMode) {
return values.cpuopt_fastmem;
if (values.cpu_debug_mode) {
return static_cast<bool>(values.cpuopt_fastmem);
}
return true;
}

View File

@@ -10,10 +10,12 @@
#include <map>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "common/common_types.h"
#include "common/settings_input.h"
#include "input_common/udp/client.h"
namespace Settings {
@@ -29,73 +31,240 @@ enum class GPUAccuracy : u32 {
};
enum class CPUAccuracy : u32 {
Accurate = 0,
Unsafe = 1,
DebugMode = 2,
Auto = 0,
Accurate = 1,
Unsafe = 2,
};
/** The BasicSetting class is a simple resource manager. It defines a label and default value
* alongside the actual value of the setting for simpler and less-error prone use with frontend
* configurations. Setting a default value and label is required, though subclasses may deviate from
* this requirement.
*/
template <typename Type>
class Setting final {
class BasicSetting {
protected:
BasicSetting() = default;
/**
* Only sets the setting to the given initializer, leaving the other members to their default
* initializers.
*
* @param global_val Initial value of the setting
*/
explicit BasicSetting(const Type& global_val) : global{global_val} {}
public:
Setting() = default;
explicit Setting(Type val) : global{val} {}
~Setting() = default;
void SetGlobal(bool to_global) {
use_global = to_global;
}
bool UsingGlobal() const {
return use_global;
}
Type GetValue(bool need_global = false) const {
if (use_global || need_global) {
return global;
}
return local;
}
void SetValue(const Type& value) {
if (use_global) {
global = value;
} else {
local = value;
}
/**
* Sets a default value, label, and setting value.
*
* @param default_val Intial value of the setting, and default value of the setting
* @param name Label for the setting
*/
explicit BasicSetting(const Type& default_val, const std::string& name)
: default_value{default_val}, global{default_val}, label{name} {}
~BasicSetting() = default;
/**
* Returns a reference to the setting's value.
*
* @returns A reference to the setting
*/
[[nodiscard]] const Type& GetValue() const {
return global;
}
private:
bool use_global = true;
Type global{};
Type local{};
/**
* Sets the setting to the given value.
*
* @param value The desired value
*/
void SetValue(const Type& value) {
Type temp{value};
std::swap(global, temp);
}
/**
* Returns the value that this setting was created with.
*
* @returns A reference to the default value
*/
[[nodiscard]] const Type& GetDefault() const {
return default_value;
}
/**
* Returns the label this setting was created with.
*
* @returns A reference to the label
*/
[[nodiscard]] const std::string& GetLabel() const {
return label;
}
/**
* Assigns a value to the setting.
*
* @param value The desired setting value
*
* @returns A reference to the setting
*/
const Type& operator=(const Type& value) {
Type temp{value};
std::swap(global, temp);
return global;
}
/**
* Returns a reference to the setting.
*
* @returns A reference to the setting
*/
explicit operator const Type&() const {
return global;
}
protected:
const Type default_value{}; ///< The default value
Type global{}; ///< The setting
const std::string label{}; ///< The setting's label
};
/**
* The InputSetting class allows for getting a reference to either the global or local members.
* The Setting class is a slightly more complex version of the BasicSetting class. This adds a
* custom setting to switch to when a guest application specifically requires it. The effect is that
* other components of the emulator can access the setting's intended value without any need for the
* component to ask whether the custom or global setting is needed at the moment.
*
* By default, the global setting is used.
*
* Like the BasicSetting, this requires setting a default value and label to use.
*/
template <typename Type>
class Setting final : public BasicSetting<Type> {
public:
/**
* Sets a default value, label, and setting value.
*
* @param default_val Intial value of the setting, and default value of the setting
* @param name Label for the setting
*/
explicit Setting(const Type& default_val, const std::string& name)
: BasicSetting<Type>(default_val, name) {}
~Setting() = default;
/**
* Tells this setting to represent either the global or custom setting when other member
* functions are used.
*
* @param to_global Whether to use the global or custom setting.
*/
void SetGlobal(bool to_global) {
use_global = to_global;
}
/**
* Returns whether this setting is using the global setting or not.
*
* @returns The global state
*/
[[nodiscard]] bool UsingGlobal() const {
return use_global;
}
/**
* Returns either the global or custom setting depending on the values of this setting's global
* state or if the global value was specifically requested.
*
* @param need_global Request global value regardless of setting's state; defaults to false
*
* @returns The required value of the setting
*/
[[nodiscard]] const Type& GetValue(bool need_global = false) const {
if (use_global || need_global) {
return this->global;
}
return custom;
}
/**
* Sets the current setting value depending on the global state.
*
* @param value The new value
*/
void SetValue(const Type& value) {
Type temp{value};
if (use_global) {
std::swap(this->global, temp);
} else {
std::swap(custom, temp);
}
}
/**
* Assigns the current setting value depending on the global state.
*
* @param value The new value
*
* @returns A reference to the current setting value
*/
const Type& operator=(const Type& value) {
Type temp{value};
if (use_global) {
std::swap(this->global, temp);
return this->global;
}
std::swap(custom, temp);
return custom;
}
/**
* Returns the current setting value depending on the global state.
*
* @returns A reference to the current setting value
*/
explicit operator const Type&() const {
if (use_global) {
return this->global;
}
return custom;
}
private:
bool use_global{true}; ///< The setting's global state
Type custom{}; ///< The custom value of the setting
};
/**
* The InputSetting class allows for getting a reference to either the global or custom members.
* This is required as we cannot easily modify the values of user-defined types within containers
* using the SetValue() member function found in the Setting class. The primary purpose of this
* class is to store an array of 10 PlayerInput structs for both the global and local (per-game)
* setting and allows for easily accessing and modifying both settings.
* class is to store an array of 10 PlayerInput structs for both the global and custom setting and
* allows for easily accessing and modifying both settings.
*/
template <typename Type>
class InputSetting final {
public:
InputSetting() = default;
explicit InputSetting(Type val) : global{val} {}
explicit InputSetting(Type val) : BasicSetting<Type>(val) {}
~InputSetting() = default;
void SetGlobal(bool to_global) {
use_global = to_global;
}
bool UsingGlobal() const {
[[nodiscard]] bool UsingGlobal() const {
return use_global;
}
Type& GetValue(bool need_global = false) {
[[nodiscard]] Type& GetValue(bool need_global = false) {
if (use_global || need_global) {
return global;
}
return local;
return custom;
}
private:
bool use_global = true;
Type global{};
Type local{};
bool use_global{true}; ///< The setting's global state
Type global{}; ///< The setting
Type custom{}; ///< The custom setting value
};
struct TouchFromButtonMap {
@@ -105,144 +274,158 @@ struct TouchFromButtonMap {
struct Values {
// Audio
std::string audio_device_id;
std::string sink_id;
bool audio_muted;
Setting<bool> enable_audio_stretching;
Setting<float> volume;
BasicSetting<std::string> audio_device_id{"auto", "output_device"};
BasicSetting<std::string> sink_id{"auto", "output_engine"};
BasicSetting<bool> audio_muted{false, "audio_muted"};
Setting<bool> enable_audio_stretching{true, "enable_audio_stretching"};
Setting<float> volume{1.0f, "volume"};
// Core
Setting<bool> use_multi_core;
Setting<bool> use_multi_core{true, "use_multi_core"};
// Cpu
Setting<CPUAccuracy> cpu_accuracy;
Setting<CPUAccuracy> cpu_accuracy{CPUAccuracy::Auto, "cpu_accuracy"};
// TODO: remove cpu_accuracy_first_time, migration setting added 8 July 2021
BasicSetting<bool> cpu_accuracy_first_time{true, "cpu_accuracy_first_time"};
BasicSetting<bool> cpu_debug_mode{false, "cpu_debug_mode"};
bool cpuopt_page_tables;
bool cpuopt_block_linking;
bool cpuopt_return_stack_buffer;
bool cpuopt_fast_dispatcher;
bool cpuopt_context_elimination;
bool cpuopt_const_prop;
bool cpuopt_misc_ir;
bool cpuopt_reduce_misalign_checks;
bool cpuopt_fastmem;
BasicSetting<bool> cpuopt_page_tables{true, "cpuopt_page_tables"};
BasicSetting<bool> cpuopt_block_linking{true, "cpuopt_block_linking"};
BasicSetting<bool> cpuopt_return_stack_buffer{true, "cpuopt_return_stack_buffer"};
BasicSetting<bool> cpuopt_fast_dispatcher{true, "cpuopt_fast_dispatcher"};
BasicSetting<bool> cpuopt_context_elimination{true, "cpuopt_context_elimination"};
BasicSetting<bool> cpuopt_const_prop{true, "cpuopt_const_prop"};
BasicSetting<bool> cpuopt_misc_ir{true, "cpuopt_misc_ir"};
BasicSetting<bool> cpuopt_reduce_misalign_checks{true, "cpuopt_reduce_misalign_checks"};
BasicSetting<bool> cpuopt_fastmem{true, "cpuopt_fastmem"};
Setting<bool> cpuopt_unsafe_unfuse_fma;
Setting<bool> cpuopt_unsafe_reduce_fp_error;
Setting<bool> cpuopt_unsafe_ignore_standard_fpcr;
Setting<bool> cpuopt_unsafe_inaccurate_nan;
Setting<bool> cpuopt_unsafe_fastmem_check;
Setting<bool> cpuopt_unsafe_unfuse_fma{true, "cpuopt_unsafe_unfuse_fma"};
Setting<bool> cpuopt_unsafe_reduce_fp_error{true, "cpuopt_unsafe_reduce_fp_error"};
Setting<bool> cpuopt_unsafe_ignore_standard_fpcr{true, "cpuopt_unsafe_ignore_standard_fpcr"};
Setting<bool> cpuopt_unsafe_inaccurate_nan{true, "cpuopt_unsafe_inaccurate_nan"};
Setting<bool> cpuopt_unsafe_fastmem_check{true, "cpuopt_unsafe_fastmem_check"};
// Renderer
Setting<RendererBackend> renderer_backend;
bool renderer_debug;
Setting<int> vulkan_device;
Setting<RendererBackend> renderer_backend{RendererBackend::OpenGL, "backend"};
BasicSetting<bool> renderer_debug{false, "debug"};
Setting<int> vulkan_device{0, "vulkan_device"};
Setting<u16> resolution_factor{1};
Setting<int> fullscreen_mode;
Setting<int> aspect_ratio;
Setting<int> max_anisotropy;
Setting<bool> use_frame_limit;
Setting<u16> frame_limit;
Setting<bool> use_disk_shader_cache;
Setting<GPUAccuracy> gpu_accuracy;
Setting<bool> use_asynchronous_gpu_emulation;
Setting<bool> use_nvdec_emulation;
Setting<bool> accelerate_astc;
Setting<bool> use_vsync;
Setting<bool> disable_fps_limit;
Setting<bool> use_assembly_shaders;
Setting<bool> use_asynchronous_shaders;
Setting<bool> use_fast_gpu_time;
Setting<bool> use_caches_gc;
Setting<u16> resolution_factor{1, "resolution_factor"};
// *nix platforms may have issues with the borderless windowed fullscreen mode.
// Default to exclusive fullscreen on these platforms for now.
Setting<int> fullscreen_mode{
#ifdef _WIN32
0,
#else
1,
#endif
"fullscreen_mode"};
Setting<int> aspect_ratio{0, "aspect_ratio"};
Setting<int> max_anisotropy{0, "max_anisotropy"};
Setting<bool> use_frame_limit{true, "use_frame_limit"};
Setting<u16> frame_limit{100, "frame_limit"};
Setting<bool> use_disk_shader_cache{true, "use_disk_shader_cache"};
Setting<GPUAccuracy> gpu_accuracy{GPUAccuracy::High, "gpu_accuracy"};
Setting<bool> use_asynchronous_gpu_emulation{true, "use_asynchronous_gpu_emulation"};
Setting<bool> use_nvdec_emulation{true, "use_nvdec_emulation"};
Setting<bool> accelerate_astc{true, "accelerate_astc"};
Setting<bool> use_vsync{true, "use_vsync"};
Setting<bool> disable_fps_limit{false, "disable_fps_limit"};
Setting<bool> use_assembly_shaders{false, "use_assembly_shaders"};
Setting<bool> use_asynchronous_shaders{false, "use_asynchronous_shaders"};
Setting<bool> use_fast_gpu_time{true, "use_fast_gpu_time"};
Setting<bool> use_caches_gc{false, "use_caches_gc"};
Setting<float> bg_red;
Setting<float> bg_green;
Setting<float> bg_blue;
Setting<float> bg_red{0.0f, "bg_red"};
Setting<float> bg_green{0.0f, "bg_green"};
Setting<float> bg_blue{0.0f, "bg_blue"};
// System
Setting<std::optional<u32>> rng_seed;
Setting<std::optional<u32>> rng_seed{std::optional<u32>(), "rng_seed"};
// Measured in seconds since epoch
std::optional<std::chrono::seconds> custom_rtc;
// Set on game boot, reset on stop. Seconds difference between current time and `custom_rtc`
std::chrono::seconds custom_rtc_differential;
s32 current_user;
Setting<s32> language_index;
Setting<s32> region_index;
Setting<s32> time_zone_index;
Setting<s32> sound_index;
BasicSetting<s32> current_user{0, "current_user"};
Setting<s32> language_index{1, "language_index"};
Setting<s32> region_index{1, "region_index"};
Setting<s32> time_zone_index{0, "time_zone_index"};
Setting<s32> sound_index{1, "sound_index"};
// Controls
InputSetting<std::array<PlayerInput, 10>> players;
Setting<bool> use_docked_mode;
Setting<bool> use_docked_mode{true, "use_docked_mode"};
Setting<bool> vibration_enabled;
Setting<bool> enable_accurate_vibrations;
Setting<bool> vibration_enabled{true, "vibration_enabled"};
Setting<bool> enable_accurate_vibrations{false, "enable_accurate_vibrations"};
Setting<bool> motion_enabled;
std::string motion_device;
std::string udp_input_servers;
Setting<bool> motion_enabled{true, "motion_enabled"};
BasicSetting<std::string> motion_device{"engine:motion_emu,update_period:100,sensitivity:0.01",
"motion_device"};
BasicSetting<std::string> udp_input_servers{InputCommon::CemuhookUDP::DEFAULT_SRV,
"udp_input_servers"};
bool mouse_panning;
float mouse_panning_sensitivity;
bool mouse_enabled;
BasicSetting<bool> mouse_panning{false, "mouse_panning"};
BasicSetting<float> mouse_panning_sensitivity{1.0f, "mouse_panning_sensitivity"};
BasicSetting<bool> mouse_enabled{false, "mouse_enabled"};
std::string mouse_device;
MouseButtonsRaw mouse_buttons;
bool emulate_analog_keyboard;
bool keyboard_enabled;
BasicSetting<bool> emulate_analog_keyboard{false, "emulate_analog_keyboard"};
BasicSetting<bool> keyboard_enabled{false, "keyboard_enabled"};
KeyboardKeysRaw keyboard_keys;
KeyboardModsRaw keyboard_mods;
bool debug_pad_enabled;
BasicSetting<bool> debug_pad_enabled{false, "debug_pad_enabled"};
ButtonsRaw debug_pad_buttons;
AnalogsRaw debug_pad_analogs;
TouchscreenInput touchscreen;
bool use_touch_from_button;
std::string touch_device;
int touch_from_button_map_index;
BasicSetting<bool> use_touch_from_button{false, "use_touch_from_button"};
BasicSetting<std::string> touch_device{"min_x:100,min_y:50,max_x:1800,max_y:850",
"touch_device"};
BasicSetting<int> touch_from_button_map_index{0, "touch_from_button_map"};
std::vector<TouchFromButtonMap> touch_from_button_maps;
std::atomic_bool is_device_reload_pending{true};
// Data Storage
bool use_virtual_sd;
bool gamecard_inserted;
bool gamecard_current_game;
std::string gamecard_path;
BasicSetting<bool> use_virtual_sd{true, "use_virtual_sd"};
BasicSetting<bool> gamecard_inserted{false, "gamecard_inserted"};
BasicSetting<bool> gamecard_current_game{false, "gamecard_current_game"};
BasicSetting<std::string> gamecard_path{std::string(), "gamecard_path"};
// Debugging
bool record_frame_times;
bool use_gdbstub;
u16 gdbstub_port;
std::string program_args;
bool dump_exefs;
bool dump_nso;
bool enable_fs_access_log;
bool reporting_services;
bool quest_flag;
bool disable_macro_jit;
bool extended_logging;
bool use_debug_asserts;
bool use_auto_stub;
BasicSetting<bool> use_gdbstub{false, "use_gdbstub"};
BasicSetting<u16> gdbstub_port{0, "gdbstub_port"};
BasicSetting<std::string> program_args{std::string(), "program_args"};
BasicSetting<bool> dump_exefs{false, "dump_exefs"};
BasicSetting<bool> dump_nso{false, "dump_nso"};
BasicSetting<bool> enable_fs_access_log{false, "enable_fs_access_log"};
BasicSetting<bool> reporting_services{false, "reporting_services"};
BasicSetting<bool> quest_flag{false, "quest_flag"};
BasicSetting<bool> disable_macro_jit{false, "disable_macro_jit"};
BasicSetting<bool> extended_logging{false, "extended_logging"};
BasicSetting<bool> use_debug_asserts{false, "use_debug_asserts"};
BasicSetting<bool> use_auto_stub{false, "use_auto_stub"};
// Miscellaneous
std::string log_filter;
bool use_dev_keys;
BasicSetting<std::string> log_filter{"*:Info", "log_filter"};
BasicSetting<bool> use_dev_keys{false, "use_dev_keys"};
// Services
std::string bcat_backend;
bool bcat_boxcat_local;
BasicSetting<std::string> bcat_backend{"none", "bcat_backend"};
BasicSetting<bool> bcat_boxcat_local{false, "bcat_boxcat_local"};
// WebService
bool enable_telemetry;
std::string web_api_url;
std::string yuzu_username;
std::string yuzu_token;
BasicSetting<bool> enable_telemetry{true, "enable_telemetry"};
BasicSetting<std::string> web_api_url{"https://api.yuzu-emu.org", "web_api_url"};
BasicSetting<std::string> yuzu_username{std::string(), "yuzu_username"};
BasicSetting<std::string> yuzu_token{std::string(), "yuzu_token"};
// Add-Ons
std::map<u64, std::vector<std::string>> disabled_addons;

View File

@@ -1,58 +0,0 @@
// Copyright 2020 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/thread.h"
#include "common/thread_worker.h"
namespace Common {
ThreadWorker::ThreadWorker(std::size_t num_workers, const std::string& name) {
for (std::size_t i = 0; i < num_workers; ++i)
threads.emplace_back([this, thread_name{std::string{name}}] {
Common::SetCurrentThreadName(thread_name.c_str());
// Wait for first request
{
std::unique_lock lock{queue_mutex};
condition.wait(lock, [this] { return stop || !requests.empty(); });
}
while (true) {
std::function<void()> task;
{
std::unique_lock lock{queue_mutex};
condition.wait(lock, [this] { return stop || !requests.empty(); });
if (stop || requests.empty()) {
return;
}
task = std::move(requests.front());
requests.pop();
}
task();
}
});
}
ThreadWorker::~ThreadWorker() {
{
std::unique_lock lock{queue_mutex};
stop = true;
}
condition.notify_all();
for (std::thread& thread : threads) {
thread.join();
}
}
void ThreadWorker::QueueWork(std::function<void()>&& work) {
{
std::unique_lock lock{queue_mutex};
requests.emplace(work);
}
condition.notify_one();
}
} // namespace Common

View File

@@ -7,24 +7,110 @@
#include <atomic>
#include <functional>
#include <mutex>
#include <stop_token>
#include <string>
#include <thread>
#include <type_traits>
#include <vector>
#include <queue>
#include "common/thread.h"
#include "common/unique_function.h"
namespace Common {
class ThreadWorker final {
template <class StateType = void>
class StatefulThreadWorker {
static constexpr bool with_state = !std::is_same_v<StateType, void>;
struct DummyCallable {
int operator()() const noexcept {
return 0;
}
};
using Task =
std::conditional_t<with_state, UniqueFunction<void, StateType*>, UniqueFunction<void>>;
using StateMaker = std::conditional_t<with_state, std::function<StateType()>, DummyCallable>;
public:
explicit ThreadWorker(std::size_t num_workers, const std::string& name);
~ThreadWorker();
void QueueWork(std::function<void()>&& work);
explicit StatefulThreadWorker(size_t num_workers, std::string name, StateMaker func = {})
: workers_queued{num_workers}, thread_name{std::move(name)} {
const auto lambda = [this, func](std::stop_token stop_token) {
Common::SetCurrentThreadName(thread_name.c_str());
{
std::conditional_t<with_state, StateType, int> state{func()};
while (!stop_token.stop_requested()) {
Task task;
{
std::unique_lock lock{queue_mutex};
if (requests.empty()) {
wait_condition.notify_all();
}
condition.wait(lock, stop_token, [this] { return !requests.empty(); });
if (stop_token.stop_requested()) {
break;
}
task = std::move(requests.front());
requests.pop();
}
if constexpr (with_state) {
task(&state);
} else {
task();
}
++work_done;
}
}
++workers_stopped;
wait_condition.notify_all();
};
threads.reserve(num_workers);
for (size_t i = 0; i < num_workers; ++i) {
threads.emplace_back(lambda);
}
}
StatefulThreadWorker& operator=(const StatefulThreadWorker&) = delete;
StatefulThreadWorker(const StatefulThreadWorker&) = delete;
StatefulThreadWorker& operator=(StatefulThreadWorker&&) = delete;
StatefulThreadWorker(StatefulThreadWorker&&) = delete;
void QueueWork(Task work) {
{
std::unique_lock lock{queue_mutex};
requests.emplace(std::move(work));
++work_scheduled;
}
condition.notify_one();
}
void WaitForRequests(std::stop_token stop_token = {}) {
std::stop_callback callback(stop_token, [this] {
for (auto& thread : threads) {
thread.request_stop();
}
});
std::unique_lock lock{queue_mutex};
wait_condition.wait(lock, [this] {
return workers_stopped >= workers_queued || work_done >= work_scheduled;
});
}
private:
std::vector<std::thread> threads;
std::queue<std::function<void()>> requests;
std::queue<Task> requests;
std::mutex queue_mutex;
std::condition_variable condition;
std::atomic_bool stop{};
std::condition_variable_any condition;
std::condition_variable wait_condition;
std::atomic<size_t> work_scheduled{};
std::atomic<size_t> work_done{};
std::atomic<size_t> workers_stopped{};
std::atomic<size_t> workers_queued{};
std::string thread_name;
std::vector<std::jthread> threads;
};
using ThreadWorker = StatefulThreadWorker<>;
} // namespace Common

View File

@@ -0,0 +1,62 @@
// Copyright 2021 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <utility>
namespace Common {
/// General purpose function wrapper similar to std::function.
/// Unlike std::function, the captured values don't have to be copyable.
/// This class can be moved but not copied.
template <typename ResultType, typename... Args>
class UniqueFunction {
class CallableBase {
public:
virtual ~CallableBase() = default;
virtual ResultType operator()(Args&&...) = 0;
};
template <typename Functor>
class Callable final : public CallableBase {
public:
Callable(Functor&& functor_) : functor{std::move(functor_)} {}
~Callable() override = default;
ResultType operator()(Args&&... args) override {
return functor(std::forward<Args>(args)...);
}
private:
Functor functor;
};
public:
UniqueFunction() = default;
template <typename Functor>
UniqueFunction(Functor&& functor)
: callable{std::make_unique<Callable<Functor>>(std::move(functor))} {}
UniqueFunction& operator=(UniqueFunction&& rhs) noexcept = default;
UniqueFunction(UniqueFunction&& rhs) noexcept = default;
UniqueFunction& operator=(const UniqueFunction&) = delete;
UniqueFunction(const UniqueFunction&) = delete;
ResultType operator()(Args&&... args) const {
return (*callable)(std::forward<Args>(args)...);
}
explicit operator bool() const noexcept {
return static_cast<bool>(callable);
}
private:
std::unique_ptr<CallableBase> callable;
};
} // namespace Common

View File

@@ -667,8 +667,6 @@ else()
target_compile_options(core PRIVATE
-Werror=conversion
-Werror=ignored-qualifiers
-Werror=implicit-fallthrough
-Werror=sign-compare
-Werror=shadow
$<$<CXX_COMPILER_ID:GNU>:-Werror=class-memaccess>

View File

@@ -150,7 +150,7 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
config.far_code_offset = 400_MiB;
// Safe optimizations
if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::DebugMode) {
if (Settings::values.cpu_debug_mode) {
if (!Settings::values.cpuopt_page_tables) {
config.page_table = nullptr;
}
@@ -183,20 +183,28 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
// Unsafe optimizations
if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Unsafe) {
config.unsafe_optimizations = true;
if (Settings::values.cpuopt_unsafe_unfuse_fma.GetValue()) {
if (Settings::values.cpuopt_unsafe_unfuse_fma) {
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
}
if (Settings::values.cpuopt_unsafe_reduce_fp_error.GetValue()) {
if (Settings::values.cpuopt_unsafe_reduce_fp_error) {
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP;
}
if (Settings::values.cpuopt_unsafe_ignore_standard_fpcr.GetValue()) {
if (Settings::values.cpuopt_unsafe_ignore_standard_fpcr) {
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreStandardFPCRValue;
}
if (Settings::values.cpuopt_unsafe_inaccurate_nan.GetValue()) {
if (Settings::values.cpuopt_unsafe_inaccurate_nan) {
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN;
}
}
// Curated optimizations
if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Auto) {
config.unsafe_optimizations = true;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreStandardFPCRValue;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN;
}
return std::make_unique<Dynarmic::A32::Jit>(config);
}

View File

@@ -190,7 +190,7 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*
config.far_code_offset = 400_MiB;
// Safe optimizations
if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::DebugMode) {
if (Settings::values.cpu_debug_mode) {
if (!Settings::values.cpuopt_page_tables) {
config.page_table = nullptr;
}
@@ -223,20 +223,28 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*
// Unsafe optimizations
if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Unsafe) {
config.unsafe_optimizations = true;
if (Settings::values.cpuopt_unsafe_unfuse_fma.GetValue()) {
if (Settings::values.cpuopt_unsafe_unfuse_fma) {
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
}
if (Settings::values.cpuopt_unsafe_reduce_fp_error.GetValue()) {
if (Settings::values.cpuopt_unsafe_reduce_fp_error) {
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP;
}
if (Settings::values.cpuopt_unsafe_inaccurate_nan.GetValue()) {
if (Settings::values.cpuopt_unsafe_inaccurate_nan) {
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN;
}
if (Settings::values.cpuopt_unsafe_fastmem_check.GetValue()) {
if (Settings::values.cpuopt_unsafe_fastmem_check) {
config.fastmem_address_space_bits = 64;
}
}
// Curated optimizations
if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Auto) {
config.unsafe_optimizations = true;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN;
config.fastmem_address_space_bits = 64;
}
return std::make_shared<Dynarmic::A64::Jit>(config);
}

View File

@@ -263,9 +263,9 @@ struct System::Impl {
if (Settings::values.gamecard_inserted) {
if (Settings::values.gamecard_current_game) {
fs_controller.SetGameCard(GetGameFileFromPath(virtual_filesystem, filepath));
} else if (!Settings::values.gamecard_path.empty()) {
fs_controller.SetGameCard(
GetGameFileFromPath(virtual_filesystem, Settings::values.gamecard_path));
} else if (!Settings::values.gamecard_path.GetValue().empty()) {
const auto gamecard_path = Settings::values.gamecard_path.GetValue();
fs_controller.SetGameCard(GetGameFileFromPath(virtual_filesystem, gamecard_path));
}
}

View File

@@ -345,8 +345,10 @@ std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(
static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type,
const Service::FileSystem::FileSystemController& fs_controller) {
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id);
if ((type != ContentRecordType::Program && type != ContentRecordType::Data) ||
load_dir == nullptr || load_dir->GetSize() <= 0) {
((load_dir == nullptr || load_dir->GetSize() <= 0) &&
(sdmc_load_dir == nullptr || sdmc_load_dir->GetSize() <= 0))) {
return;
}
@@ -356,7 +358,10 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
}
const auto& disabled = Settings::values.disabled_addons[title_id];
auto patch_dirs = load_dir->GetSubdirectories();
std::vector<VirtualDir> patch_dirs = load_dir->GetSubdirectories();
if (std::find(disabled.cbegin(), disabled.cend(), "SDMC") == disabled.cend()) {
patch_dirs.push_back(sdmc_load_dir);
}
std::sort(patch_dirs.begin(), patch_dirs.end(),
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
@@ -402,7 +407,7 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
}
VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type,
VirtualFile update_raw) const {
VirtualFile update_raw, bool apply_layeredfs) const {
const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}",
title_id, static_cast<u8>(type));
@@ -442,7 +447,9 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content
}
// LayeredFS
ApplyLayeredFS(romfs, title_id, type, fs_controller);
if (apply_layeredfs) {
ApplyLayeredFS(romfs, title_id, type, fs_controller);
}
return romfs;
}
@@ -524,6 +531,15 @@ PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile u
}
}
// SDMC mod directory (RomFS LayeredFS)
const auto sdmc_mod_dir = fs_controller.GetSDMCModificationLoadRoot(title_id);
if (sdmc_mod_dir != nullptr && sdmc_mod_dir->GetSize() > 0 &&
IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "romfs"))) {
const auto mod_disabled =
std::find(disabled.begin(), disabled.end(), "SDMC") != disabled.end();
out.insert_or_assign(mod_disabled ? "[D] SDMC" : "SDMC", "LayeredFS");
}
// DLC
const auto dlc_entries =
content_provider.ListEntriesFilter(TitleType::AOC, ContentRecordType::Data);

View File

@@ -64,7 +64,8 @@ public:
// - LayeredFS
[[nodiscard]] VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset,
ContentRecordType type = ContentRecordType::Program,
VirtualFile update_raw = nullptr) const;
VirtualFile update_raw = nullptr,
bool apply_layeredfs = true) const;
// Returns a vector of pairs between patch names and patch versions.
// i.e. Update 3.2.2 will return {"Update", "3.2.2"}

View File

@@ -12,23 +12,32 @@ namespace FileSys {
constexpr u64 SDMC_TOTAL_SIZE = 0x10000000000; // 1 TiB
SDMCFactory::SDMCFactory(VirtualDir dir_)
: dir(std::move(dir_)), contents(std::make_unique<RegisteredCache>(
GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents/registered"),
[](const VirtualFile& file, const NcaID& id) {
return NAX{file, id}.GetDecrypted();
})),
SDMCFactory::SDMCFactory(VirtualDir sd_dir_, VirtualDir sd_mod_dir_)
: sd_dir(std::move(sd_dir_)), sd_mod_dir(std::move(sd_mod_dir_)),
contents(std::make_unique<RegisteredCache>(
GetOrCreateDirectoryRelative(sd_dir, "/Nintendo/Contents/registered"),
[](const VirtualFile& file, const NcaID& id) {
return NAX{file, id}.GetDecrypted();
})),
placeholder(std::make_unique<PlaceholderCache>(
GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents/placehld"))) {}
GetOrCreateDirectoryRelative(sd_dir, "/Nintendo/Contents/placehld"))) {}
SDMCFactory::~SDMCFactory() = default;
ResultVal<VirtualDir> SDMCFactory::Open() const {
return MakeResult<VirtualDir>(dir);
return MakeResult<VirtualDir>(sd_dir);
}
VirtualDir SDMCFactory::GetSDMCModificationLoadRoot(u64 title_id) const {
// LayeredFS doesn't work on updates and title id-less homebrew
if (title_id == 0 || (title_id & 0xFFF) == 0x800) {
return nullptr;
}
return GetOrCreateDirectoryRelative(sd_mod_dir, fmt::format("/{:016X}", title_id));
}
VirtualDir SDMCFactory::GetSDMCContentDirectory() const {
return GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents");
return GetOrCreateDirectoryRelative(sd_dir, "/Nintendo/Contents");
}
RegisteredCache* SDMCFactory::GetSDMCContents() const {
@@ -40,11 +49,11 @@ PlaceholderCache* SDMCFactory::GetSDMCPlaceholder() const {
}
VirtualDir SDMCFactory::GetImageDirectory() const {
return GetOrCreateDirectoryRelative(dir, "/Nintendo/Album");
return GetOrCreateDirectoryRelative(sd_dir, "/Nintendo/Album");
}
u64 SDMCFactory::GetSDMCFreeSpace() const {
return GetSDMCTotalSpace() - dir->GetSize();
return GetSDMCTotalSpace() - sd_dir->GetSize();
}
u64 SDMCFactory::GetSDMCTotalSpace() const {

View File

@@ -16,11 +16,12 @@ class PlaceholderCache;
/// File system interface to the SDCard archive
class SDMCFactory {
public:
explicit SDMCFactory(VirtualDir dir);
explicit SDMCFactory(VirtualDir sd_dir_, VirtualDir sd_mod_dir_);
~SDMCFactory();
ResultVal<VirtualDir> Open() const;
VirtualDir GetSDMCModificationLoadRoot(u64 title_id) const;
VirtualDir GetSDMCContentDirectory() const;
RegisteredCache* GetSDMCContents() const;
@@ -32,7 +33,8 @@ public:
u64 GetSDMCTotalSpace() const;
private:
VirtualDir dir;
VirtualDir sd_dir;
VirtualDir sd_mod_dir;
std::unique_ptr<RegisteredCache> contents;
std::unique_ptr<PlaceholderCache> placeholder;

View File

@@ -13,7 +13,7 @@ ProfileSelectApplet::~ProfileSelectApplet() = default;
void DefaultProfileSelectApplet::SelectProfile(
std::function<void(std::optional<Common::UUID>)> callback) const {
Service::Account::ProfileManager manager;
callback(manager.GetUser(Settings::values.current_user).value_or(Common::UUID{}));
callback(manager.GetUser(Settings::values.current_user.GetValue()).value_or(Common::UUID{}));
LOG_INFO(Service_ACC, "called, selecting current user instead of prompting...");
}

View File

@@ -48,7 +48,8 @@ ProfileManager::ProfileManager() {
CreateNewUser(UUID::Generate(), "yuzu");
}
auto current = std::clamp<int>(Settings::values.current_user, 0, MAX_USERS - 1);
auto current =
std::clamp<int>(static_cast<s32>(Settings::values.current_user), 0, MAX_USERS - 1);
// If user index don't exist. Load the first user and change the active user
if (!UserExistsIndex(current)) {

View File

@@ -1443,7 +1443,7 @@ void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) {
params.is_account_selected = 1;
Account::ProfileManager profile_manager{};
const auto uuid = profile_manager.GetUser(Settings::values.current_user);
const auto uuid = profile_manager.GetUser(static_cast<s32>(Settings::values.current_user));
ASSERT(uuid);
params.current_user = uuid->uuid;

View File

@@ -96,7 +96,7 @@ private:
void RequestUpdateImpl(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "(STUBBED) called");
std::vector<u8> output_params(ctx.GetWriteBufferSize());
std::vector<u8> output_params(ctx.GetWriteBufferSize(), 0);
auto result = renderer->UpdateAudioRenderer(ctx.ReadBuffer(), output_params);
if (result.IsSuccess()) {
@@ -110,17 +110,19 @@ private:
void Start(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
const auto result = renderer->Start();
rb.Push(ResultSuccess);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void Stop(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
const auto result = renderer->Stop();
rb.Push(ResultSuccess);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void QuerySystemEvent(Kernel::HLERequestContext& ctx) {
@@ -288,7 +290,7 @@ private:
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push<u32>(1);
rb.Push<u32>(2);
}
// Should be similar to QueryAudioDeviceOutputEvent

View File

@@ -579,7 +579,7 @@ void Module::Interface::CreateDeliveryCacheStorageServiceWithApplicationId(
std::unique_ptr<Backend> CreateBackendFromSettings([[maybe_unused]] Core::System& system,
DirectoryGetter getter) {
#ifdef YUZU_ENABLE_BOXCAT
if (Settings::values.bcat_backend == "boxcat") {
if (Settings::values.bcat_backend.GetValue() == "boxcat") {
return std::make_unique<Boxcat>(system.GetAppletManager(), std::move(getter));
}
#endif

View File

@@ -703,6 +703,16 @@ FileSys::VirtualDir FileSystemController::GetModificationLoadRoot(u64 title_id)
return bis_factory->GetModificationLoadRoot(title_id);
}
FileSys::VirtualDir FileSystemController::GetSDMCModificationLoadRoot(u64 title_id) const {
LOG_TRACE(Service_FS, "Opening SDMC mod load root for tid={:016X}", title_id);
if (sdmc_factory == nullptr) {
return nullptr;
}
return sdmc_factory->GetSDMCModificationLoadRoot(title_id);
}
FileSys::VirtualDir FileSystemController::GetModificationDumpRoot(u64 title_id) const {
LOG_TRACE(Service_FS, "Opening mod dump root for tid={:016X}", title_id);
@@ -733,20 +743,23 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
}
using YuzuPath = Common::FS::YuzuPath;
const auto sdmc_dir_path = Common::FS::GetYuzuPath(YuzuPath::SDMCDir);
const auto sdmc_load_dir_path = sdmc_dir_path / "atmosphere/contents";
const auto rw_mode = FileSys::Mode::ReadWrite;
auto nand_directory =
vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::NANDDir), rw_mode);
auto sd_directory =
vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::SDMCDir), rw_mode);
auto sd_directory = vfs.OpenDirectory(Common::FS::PathToUTF8String(sdmc_dir_path), rw_mode);
auto load_directory =
vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::LoadDir), FileSys::Mode::Read);
auto sd_load_directory =
vfs.OpenDirectory(Common::FS::PathToUTF8String(sdmc_load_dir_path), FileSys::Mode::Read);
auto dump_directory =
vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::DumpDir), rw_mode);
if (bis_factory == nullptr) {
bis_factory =
std::make_unique<FileSys::BISFactory>(nand_directory, load_directory, dump_directory);
bis_factory = std::make_unique<FileSys::BISFactory>(
nand_directory, std::move(load_directory), std::move(dump_directory));
system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SysNAND,
bis_factory->GetSystemNANDContents());
system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::UserNAND,
@@ -759,7 +772,8 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
}
if (sdmc_factory == nullptr) {
sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory));
sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory),
std::move(sd_load_directory));
system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SDMC,
sdmc_factory->GetSDMCContents());
}

View File

@@ -115,6 +115,7 @@ public:
FileSys::VirtualDir GetContentDirectory(ContentStorageId id) const;
FileSys::VirtualDir GetImageDirectory(ImageDirectoryId id) const;
FileSys::VirtualDir GetSDMCModificationLoadRoot(u64 title_id) const;
FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) const;
FileSys::VirtualDir GetModificationDumpRoot(u64 title_id) const;

View File

@@ -941,6 +941,11 @@ void Controller_NPad::InitializeVibrationDevice(const DeviceHandle& vibration_de
void Controller_NPad::InitializeVibrationDeviceAtIndex(std::size_t npad_index,
std::size_t device_index) {
if (!Settings::values.vibration_enabled.GetValue()) {
vibration_devices_mounted[npad_index][device_index] = false;
return;
}
if (vibrations[npad_index][device_index]) {
vibration_devices_mounted[npad_index][device_index] =
vibrations[npad_index][device_index]->GetStatus() == 1;

View File

@@ -20,6 +20,7 @@ namespace {
constexpr ResultCode ERROR_CANNOT_FIND_ENTRY{ErrorModule::Mii, 4};
constexpr std::size_t BaseMiiCount{2};
constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()};
constexpr MiiStoreData::Name DefaultMiiName{u'y', u'u', u'z', u'u'};
@@ -415,7 +416,7 @@ u32 MiiManager::GetCount(SourceFlag source_flag) const {
count += 0;
}
if ((source_flag & SourceFlag::Default) != SourceFlag::None) {
count += DefaultMiiCount;
count += (DefaultMiiCount - BaseMiiCount);
}
return static_cast<u32>(count);
}
@@ -445,7 +446,7 @@ ResultVal<std::vector<MiiInfoElement>> MiiManager::GetDefault(SourceFlag source_
return MakeResult(std::move(result));
}
for (std::size_t index = 0; index < DefaultMiiCount; index++) {
for (std::size_t index = BaseMiiCount; index < DefaultMiiCount; index++) {
result.emplace_back(BuildDefault(index), Source::Default);
}

View File

@@ -179,7 +179,7 @@ private:
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
if (Settings::values.bcat_backend == "none") {
if (Settings::values.bcat_backend.GetValue() == "none") {
rb.PushEnum(RequestState::NotSubmitted);
} else {
rb.PushEnum(RequestState::Connected);
@@ -384,7 +384,7 @@ private:
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
if (Settings::values.bcat_backend == "none") {
if (Settings::values.bcat_backend.GetValue() == "none") {
rb.Push<u8>(0);
} else {
rb.Push<u8>(1);
@@ -395,7 +395,7 @@ private:
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
if (Settings::values.bcat_backend == "none") {
if (Settings::values.bcat_backend.GetValue() == "none") {
rb.Push<u8>(0);
} else {
rb.Push<u8>(1);

View File

@@ -160,7 +160,7 @@ void SET::GetQuestFlag(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push(static_cast<u32>(Settings::values.quest_flag));
rb.Push(static_cast<u32>(Settings::values.quest_flag.GetValue()));
}
void SET::GetLanguageCode(Kernel::HLERequestContext& ctx) {

View File

@@ -155,8 +155,8 @@ static bool LoadNroImpl(Kernel::KProcess& process, const std::vector<u8>& data)
codeset.segments[i].size = PageAlignSize(nro_header.segments[i].size);
}
if (!Settings::values.program_args.empty()) {
const auto arg_data = Settings::values.program_args;
if (!Settings::values.program_args.GetValue().empty()) {
const auto arg_data = Settings::values.program_args.GetValue();
codeset.DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE;
NSOArgumentHeader args_header{
NSO_ARGUMENT_DATA_ALLOCATION_SIZE, static_cast<u32_le>(arg_data.size()), {}};

View File

@@ -104,8 +104,8 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
codeset.segments[i].size = nso_header.segments[i].size;
}
if (should_pass_arguments && !Settings::values.program_args.empty()) {
const auto arg_data{Settings::values.program_args};
if (should_pass_arguments && !Settings::values.program_args.GetValue().empty()) {
const auto arg_data{Settings::values.program_args.GetValue()};
codeset.DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE;
NSOArgumentHeader args_header{

View File

@@ -397,7 +397,7 @@ void Reporter::ClearFSAccessLog() const {
}
bool Reporter::IsReportingEnabled() const {
return Settings::values.reporting_services;
return Settings::values.reporting_services.GetValue();
}
} // namespace Core

View File

@@ -135,7 +135,7 @@ u64 RegenerateTelemetryId() {
bool VerifyLogin(const std::string& username, const std::string& token) {
#ifdef ENABLE_WEB_SERVICE
return WebService::VerifyLogin(Settings::values.web_api_url, username, token);
return WebService::VerifyLogin(Settings::values.web_api_url.GetValue(), username, token);
#else
return false;
#endif
@@ -152,7 +152,8 @@ TelemetrySession::~TelemetrySession() {
#ifdef ENABLE_WEB_SERVICE
auto backend = std::make_unique<WebService::TelemetryJson>(
Settings::values.web_api_url, Settings::values.yuzu_username, Settings::values.yuzu_token);
Settings::values.web_api_url.GetValue(), Settings::values.yuzu_username.GetValue(),
Settings::values.yuzu_token.GetValue());
#else
auto backend = std::make_unique<Telemetry::NullVisitor>();
#endif
@@ -212,7 +213,7 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader,
// Log user configuration information
constexpr auto field_type = Telemetry::FieldType::UserConfig;
AddField(field_type, "Audio_SinkId", Settings::values.sink_id);
AddField(field_type, "Audio_SinkId", Settings::values.sink_id.GetValue());
AddField(field_type, "Audio_EnableAudioStretching",
Settings::values.enable_audio_stretching.GetValue());
AddField(field_type, "Core_UseMultiCore", Settings::values.use_multi_core.GetValue());
@@ -242,7 +243,8 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader,
bool TelemetrySession::SubmitTestcase() {
#ifdef ENABLE_WEB_SERVICE
auto backend = std::make_unique<WebService::TelemetryJson>(
Settings::values.web_api_url, Settings::values.yuzu_username, Settings::values.yuzu_token);
Settings::values.web_api_url.GetValue(), Settings::values.yuzu_username.GetValue(),
Settings::values.yuzu_token.GetValue());
field_collection.Accept(*backend);
return backend->SubmitTestcase();
#else

View File

@@ -44,10 +44,7 @@ else()
-Werror
-Werror=conversion
-Werror=ignored-qualifiers
-Werror=implicit-fallthrough
-Werror=reorder
-Werror=shadow
-Werror=sign-compare
$<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-parameter>
$<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-variable>
-Werror=unused-variable

View File

@@ -27,6 +27,7 @@ public:
down->SetCallback(callbacks);
left->SetCallback(callbacks);
right->SetCallback(callbacks);
modifier->SetCallback(callbacks);
}
bool IsAngleGreater(float old_angle, float new_angle) const {

View File

@@ -84,7 +84,8 @@ public:
std::lock_guard lock{mutex};
const auto axis_value =
static_cast<float>(mouse_input->GetMouseState(button).axis.at(axis));
return axis_value * Settings::values.mouse_panning_sensitivity / (100.0f * range);
const float sensitivity = Settings::values.mouse_panning_sensitivity.GetValue();
return axis_value * sensitivity / (100.0f * range);
}
std::pair<float, float> GetAnalog(u32 analog_axis_x, u32 analog_axis_y) const {

View File

@@ -13,7 +13,7 @@ class TouchFromButtonDevice final : public Input::TouchDevice {
public:
TouchFromButtonDevice() {
const auto button_index =
static_cast<std::size_t>(Settings::values.touch_from_button_map_index);
static_cast<u64>(Settings::values.touch_from_button_map_index.GetValue());
const auto& buttons = Settings::values.touch_from_button_maps[button_index].buttons;
for (const auto& config_entry : buttons) {

View File

@@ -201,7 +201,7 @@ bool Client::DeviceConnected(std::size_t pad) const {
void Client::ReloadSockets() {
Reset();
std::stringstream servers_ss(Settings::values.udp_input_servers);
std::stringstream servers_ss(static_cast<std::string>(Settings::values.udp_input_servers));
std::string server_token;
std::size_t client = 0;
while (std::getline(servers_ss, server_token, ',')) {
@@ -370,7 +370,7 @@ std::optional<std::size_t> Client::GetUnusedFingerID() const {
void Client::UpdateTouchInput(Response::TouchPad& touch_pad, std::size_t client, std::size_t id) {
// TODO: Use custom calibration per device
const Common::ParamPackage touch_param(Settings::values.touch_device);
const Common::ParamPackage touch_param(Settings::values.touch_device.GetValue());
const u16 min_x = static_cast<u16>(touch_param.Get("min_x", 100));
const u16 min_y = static_cast<u16>(touch_param.Get("min_y", 50));
const u16 max_x = static_cast<u16>(touch_param.Get("max_x", 1800));

View File

@@ -5,6 +5,7 @@ add_executable(tests
common/host_memory.cpp
common/param_package.cpp
common/ring_buffer.cpp
common/unique_function.cpp
core/core_timing.cpp
core/network/network.cpp
tests.cpp

View File

@@ -0,0 +1,108 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <string>
#include <catch2/catch.hpp>
#include "common/unique_function.h"
namespace {
struct Noisy {
Noisy() : state{"Default constructed"} {}
Noisy(Noisy&& rhs) noexcept : state{"Move constructed"} {
rhs.state = "Moved away";
}
Noisy& operator=(Noisy&& rhs) noexcept {
state = "Move assigned";
rhs.state = "Moved away";
}
Noisy(const Noisy&) : state{"Copied constructed"} {}
Noisy& operator=(const Noisy&) {
state = "Copied assigned";
}
std::string state;
};
} // Anonymous namespace
TEST_CASE("UniqueFunction", "[common]") {
SECTION("Capture reference") {
int value = 0;
Common::UniqueFunction<void> func = [&value] { value = 5; };
func();
REQUIRE(value == 5);
}
SECTION("Capture pointer") {
int value = 0;
int* pointer = &value;
Common::UniqueFunction<void> func = [pointer] { *pointer = 5; };
func();
REQUIRE(value == 5);
}
SECTION("Move object") {
Noisy noisy;
REQUIRE(noisy.state == "Default constructed");
Common::UniqueFunction<void> func = [noisy = std::move(noisy)] {
REQUIRE(noisy.state == "Move constructed");
};
REQUIRE(noisy.state == "Moved away");
func();
}
SECTION("Move construct function") {
int value = 0;
Common::UniqueFunction<void> func = [&value] { value = 5; };
Common::UniqueFunction<void> new_func = std::move(func);
new_func();
REQUIRE(value == 5);
}
SECTION("Move assign function") {
int value = 0;
Common::UniqueFunction<void> func = [&value] { value = 5; };
Common::UniqueFunction<void> new_func;
new_func = std::move(func);
new_func();
REQUIRE(value == 5);
}
SECTION("Default construct then assign function") {
int value = 0;
Common::UniqueFunction<void> func;
func = [&value] { value = 5; };
func();
REQUIRE(value == 5);
}
SECTION("Pass arguments") {
int result = 0;
Common::UniqueFunction<void, int, int> func = [&result](int a, int b) { result = a + b; };
func(5, 4);
REQUIRE(result == 9);
}
SECTION("Pass arguments and return value") {
Common::UniqueFunction<int, int, int> func = [](int a, int b) { return a + b; };
REQUIRE(func(5, 4) == 9);
}
SECTION("Destructor") {
int num_destroyed = 0;
struct Foo {
Foo(int* num_) : num{num_} {}
Foo(Foo&& rhs) : num{std::exchange(rhs.num, nullptr)} {}
Foo(const Foo&) = delete;
~Foo() {
if (num) {
++*num;
}
}
int* num = nullptr;
};
Foo object{&num_destroyed};
{
Common::UniqueFunction<void> func = [object = std::move(object)] {};
REQUIRE(num_destroyed == 0);
}
REQUIRE(num_destroyed == 1);
}
}

View File

@@ -103,8 +103,7 @@ void CDmaPusher::ExecuteCommand(u32 state_offset, u32 data) {
case ThiMethod::SetMethod1:
LOG_DEBUG(Service_NVDRV, "NVDEC method 0x{:X}",
static_cast<u32>(nvdec_thi_state.method_0));
nvdec_processor->ProcessMethod(static_cast<Nvdec::Method>(nvdec_thi_state.method_0),
data);
nvdec_processor->ProcessMethod(nvdec_thi_state.method_0, data);
break;
default:
break;

View File

@@ -23,8 +23,8 @@ void AVFrameDeleter(AVFrame* ptr) {
av_free(ptr);
}
Codec::Codec(GPU& gpu_)
: gpu(gpu_), h264_decoder(std::make_unique<Decoder::H264>(gpu)),
Codec::Codec(GPU& gpu_, const NvdecCommon::NvdecRegisters& regs)
: gpu(gpu_), state{regs}, h264_decoder(std::make_unique<Decoder::H264>(gpu)),
vp9_decoder(std::make_unique<Decoder::VP9>(gpu)) {}
Codec::~Codec() {
@@ -43,46 +43,48 @@ Codec::~Codec() {
avcodec_close(av_codec_ctx);
}
void Codec::SetTargetCodec(NvdecCommon::VideoCodec codec) {
if (current_codec != codec) {
LOG_INFO(Service_NVDRV, "NVDEC video codec initialized to {}", static_cast<u32>(codec));
current_codec = codec;
void Codec::Initialize() {
AVCodecID codec{AV_CODEC_ID_NONE};
switch (current_codec) {
case NvdecCommon::VideoCodec::H264:
codec = AV_CODEC_ID_H264;
break;
case NvdecCommon::VideoCodec::Vp9:
codec = AV_CODEC_ID_VP9;
break;
default:
return;
}
av_codec = avcodec_find_decoder(codec);
av_codec_ctx = avcodec_alloc_context3(av_codec);
av_opt_set(av_codec_ctx->priv_data, "tune", "zerolatency", 0);
// TODO(ameerj): libavcodec gpu hw acceleration
const auto av_error = avcodec_open2(av_codec_ctx, av_codec, nullptr);
if (av_error < 0) {
LOG_ERROR(Service_NVDRV, "avcodec_open2() Failed.");
avcodec_close(av_codec_ctx);
return;
}
initialized = true;
return;
}
void Codec::StateWrite(u32 offset, u64 arguments) {
u8* const state_offset = reinterpret_cast<u8*>(&state) + offset * sizeof(u64);
std::memcpy(state_offset, &arguments, sizeof(u64));
void Codec::SetTargetCodec(NvdecCommon::VideoCodec codec) {
if (current_codec != codec) {
current_codec = codec;
LOG_INFO(Service_NVDRV, "NVDEC video codec initialized to {}", GetCurrentCodecName());
}
}
void Codec::Decode() {
bool is_first_frame = false;
const bool is_first_frame = !initialized;
if (!initialized) {
if (current_codec == NvdecCommon::VideoCodec::H264) {
av_codec = avcodec_find_decoder(AV_CODEC_ID_H264);
} else if (current_codec == NvdecCommon::VideoCodec::Vp9) {
av_codec = avcodec_find_decoder(AV_CODEC_ID_VP9);
} else {
LOG_ERROR(Service_NVDRV, "Unknown video codec {}", current_codec);
return;
}
av_codec_ctx = avcodec_alloc_context3(av_codec);
av_opt_set(av_codec_ctx->priv_data, "tune", "zerolatency", 0);
// TODO(ameerj): libavcodec gpu hw acceleration
const auto av_error = avcodec_open2(av_codec_ctx, av_codec, nullptr);
if (av_error < 0) {
LOG_ERROR(Service_NVDRV, "avcodec_open2() Failed.");
avcodec_close(av_codec_ctx);
return;
}
initialized = true;
is_first_frame = true;
Initialize();
}
bool vp9_hidden_frame = false;
bool vp9_hidden_frame = false;
AVPacket packet{};
av_init_packet(&packet);
std::vector<u8> frame_data;
@@ -95,7 +97,7 @@ void Codec::Decode() {
}
packet.data = frame_data.data();
packet.size = static_cast<int>(frame_data.size());
packet.size = static_cast<s32>(frame_data.size());
avcodec_send_packet(av_codec_ctx, &packet);
@@ -127,4 +129,21 @@ NvdecCommon::VideoCodec Codec::GetCurrentCodec() const {
return current_codec;
}
std::string_view Codec::GetCurrentCodecName() const {
switch (current_codec) {
case NvdecCommon::VideoCodec::None:
return "None";
case NvdecCommon::VideoCodec::H264:
return "H264";
case NvdecCommon::VideoCodec::Vp8:
return "VP8";
case NvdecCommon::VideoCodec::H265:
return "H265";
case NvdecCommon::VideoCodec::Vp9:
return "VP9";
default:
return "Unknown";
}
};
} // namespace Tegra

View File

@@ -34,15 +34,15 @@ class VP9;
class Codec {
public:
explicit Codec(GPU& gpu);
explicit Codec(GPU& gpu, const NvdecCommon::NvdecRegisters& regs);
~Codec();
/// Initialize the codec, returning success or failure
void Initialize();
/// Sets NVDEC video stream codec
void SetTargetCodec(NvdecCommon::VideoCodec codec);
/// Populate NvdecRegisters state with argument value at the provided offset
void StateWrite(u32 offset, u64 arguments);
/// Call decoders to construct headers, decode AVFrame with ffmpeg
void Decode();
@@ -51,6 +51,8 @@ public:
/// Returns the value of current_codec
[[nodiscard]] NvdecCommon::VideoCodec GetCurrentCodec() const;
/// Return name of the current codec
[[nodiscard]] std::string_view GetCurrentCodecName() const;
private:
bool initialized{};
@@ -60,10 +62,10 @@ private:
AVCodecContext* av_codec_ctx{nullptr};
GPU& gpu;
const NvdecCommon::NvdecRegisters& state;
std::unique_ptr<Decoder::H264> h264_decoder;
std::unique_ptr<Decoder::VP9> vp9_decoder;
NvdecCommon::NvdecRegisters state{};
std::queue<AVFramePtr> av_frames{};
};

View File

@@ -45,135 +45,130 @@ H264::~H264() = default;
const std::vector<u8>& H264::ComposeFrameHeader(const NvdecCommon::NvdecRegisters& state,
bool is_first_frame) {
H264DecoderContext context{};
H264DecoderContext context;
gpu.MemoryManager().ReadBlock(state.picture_info_offset, &context, sizeof(H264DecoderContext));
const s32 frame_number = static_cast<s32>((context.h264_parameter_set.flags >> 46) & 0x1ffff);
const s64 frame_number = context.h264_parameter_set.frame_number.Value();
if (!is_first_frame && frame_number != 0) {
frame.resize(context.frame_data_size);
frame.resize(context.stream_len);
gpu.MemoryManager().ReadBlock(state.frame_bitstream_offset, frame.data(), frame.size());
} else {
/// Encode header
H264BitWriter writer{};
writer.WriteU(1, 24);
writer.WriteU(0, 1);
writer.WriteU(3, 2);
writer.WriteU(7, 5);
writer.WriteU(100, 8);
writer.WriteU(0, 8);
writer.WriteU(31, 8);
writer.WriteUe(0);
const auto chroma_format_idc =
static_cast<u32>((context.h264_parameter_set.flags >> 12) & 3);
writer.WriteUe(chroma_format_idc);
if (chroma_format_idc == 3) {
writer.WriteBit(false);
}
writer.WriteUe(0);
writer.WriteUe(0);
writer.WriteBit(false); // QpprimeYZeroTransformBypassFlag
writer.WriteBit(false); // Scaling matrix present flag
const auto order_cnt_type = static_cast<u32>((context.h264_parameter_set.flags >> 14) & 3);
writer.WriteUe(static_cast<u32>((context.h264_parameter_set.flags >> 8) & 0xf));
writer.WriteUe(order_cnt_type);
if (order_cnt_type == 0) {
writer.WriteUe(context.h264_parameter_set.log2_max_pic_order_cnt);
} else if (order_cnt_type == 1) {
writer.WriteBit(context.h264_parameter_set.delta_pic_order_always_zero_flag != 0);
writer.WriteSe(0);
writer.WriteSe(0);
writer.WriteUe(0);
}
const s32 pic_height = context.h264_parameter_set.pic_height_in_map_units /
(context.h264_parameter_set.frame_mbs_only_flag ? 1 : 2);
writer.WriteUe(16);
writer.WriteBit(false);
writer.WriteUe(context.h264_parameter_set.pic_width_in_mbs - 1);
writer.WriteUe(pic_height - 1);
writer.WriteBit(context.h264_parameter_set.frame_mbs_only_flag != 0);
if (!context.h264_parameter_set.frame_mbs_only_flag) {
writer.WriteBit(((context.h264_parameter_set.flags >> 0) & 1) != 0);
}
writer.WriteBit(((context.h264_parameter_set.flags >> 1) & 1) != 0);
writer.WriteBit(false); // Frame cropping flag
writer.WriteBit(false); // VUI parameter present flag
writer.End();
// H264 PPS
writer.WriteU(1, 24);
writer.WriteU(0, 1);
writer.WriteU(3, 2);
writer.WriteU(8, 5);
writer.WriteUe(0);
writer.WriteUe(0);
writer.WriteBit(context.h264_parameter_set.entropy_coding_mode_flag != 0);
writer.WriteBit(false);
writer.WriteUe(0);
writer.WriteUe(context.h264_parameter_set.num_refidx_l0_default_active);
writer.WriteUe(context.h264_parameter_set.num_refidx_l1_default_active);
writer.WriteBit(((context.h264_parameter_set.flags >> 2) & 1) != 0);
writer.WriteU(static_cast<s32>((context.h264_parameter_set.flags >> 32) & 0x3), 2);
s32 pic_init_qp = static_cast<s32>((context.h264_parameter_set.flags >> 16) & 0x3f);
pic_init_qp = (pic_init_qp << 26) >> 26;
writer.WriteSe(pic_init_qp);
writer.WriteSe(0);
s32 chroma_qp_index_offset =
static_cast<s32>((context.h264_parameter_set.flags >> 22) & 0x1f);
chroma_qp_index_offset = (chroma_qp_index_offset << 27) >> 27;
writer.WriteSe(chroma_qp_index_offset);
writer.WriteBit(context.h264_parameter_set.deblocking_filter_control_flag != 0);
writer.WriteBit(((context.h264_parameter_set.flags >> 3) & 1) != 0);
writer.WriteBit(context.h264_parameter_set.redundant_pic_count_flag != 0);
writer.WriteBit(context.h264_parameter_set.transform_8x8_mode_flag != 0);
writer.WriteBit(true);
for (s32 index = 0; index < 6; index++) {
writer.WriteBit(true);
const auto matrix_x4 =
std::vector<u8>(context.scaling_matrix_4.begin(), context.scaling_matrix_4.end());
writer.WriteScalingList(matrix_x4, index * 16, 16);
}
if (context.h264_parameter_set.transform_8x8_mode_flag) {
for (s32 index = 0; index < 2; index++) {
writer.WriteBit(true);
const auto matrix_x8 = std::vector<u8>(context.scaling_matrix_8.begin(),
context.scaling_matrix_8.end());
writer.WriteScalingList(matrix_x8, index * 64, 64);
}
}
s32 chroma_qp_index_offset2 =
static_cast<s32>((context.h264_parameter_set.flags >> 27) & 0x1f);
chroma_qp_index_offset2 = (chroma_qp_index_offset2 << 27) >> 27;
writer.WriteSe(chroma_qp_index_offset2);
writer.End();
const auto& encoded_header = writer.GetByteArray();
frame.resize(encoded_header.size() + context.frame_data_size);
std::memcpy(frame.data(), encoded_header.data(), encoded_header.size());
gpu.MemoryManager().ReadBlock(state.frame_bitstream_offset,
frame.data() + encoded_header.size(),
context.frame_data_size);
return frame;
}
// Encode header
H264BitWriter writer{};
writer.WriteU(1, 24);
writer.WriteU(0, 1);
writer.WriteU(3, 2);
writer.WriteU(7, 5);
writer.WriteU(100, 8);
writer.WriteU(0, 8);
writer.WriteU(31, 8);
writer.WriteUe(0);
const u32 chroma_format_idc =
static_cast<u32>(context.h264_parameter_set.chroma_format_idc.Value());
writer.WriteUe(chroma_format_idc);
if (chroma_format_idc == 3) {
writer.WriteBit(false);
}
writer.WriteUe(0);
writer.WriteUe(0);
writer.WriteBit(false); // QpprimeYZeroTransformBypassFlag
writer.WriteBit(false); // Scaling matrix present flag
writer.WriteUe(static_cast<u32>(context.h264_parameter_set.log2_max_frame_num_minus4.Value()));
const auto order_cnt_type =
static_cast<u32>(context.h264_parameter_set.pic_order_cnt_type.Value());
writer.WriteUe(order_cnt_type);
if (order_cnt_type == 0) {
writer.WriteUe(context.h264_parameter_set.log2_max_pic_order_cnt_lsb_minus4);
} else if (order_cnt_type == 1) {
writer.WriteBit(context.h264_parameter_set.delta_pic_order_always_zero_flag != 0);
writer.WriteSe(0);
writer.WriteSe(0);
writer.WriteUe(0);
}
const s32 pic_height = context.h264_parameter_set.frame_height_in_map_units /
(context.h264_parameter_set.frame_mbs_only_flag ? 1 : 2);
writer.WriteUe(16);
writer.WriteBit(false);
writer.WriteUe(context.h264_parameter_set.pic_width_in_mbs - 1);
writer.WriteUe(pic_height - 1);
writer.WriteBit(context.h264_parameter_set.frame_mbs_only_flag != 0);
if (!context.h264_parameter_set.frame_mbs_only_flag) {
writer.WriteBit(context.h264_parameter_set.flags.mbaff_frame.Value() != 0);
}
writer.WriteBit(context.h264_parameter_set.flags.direct_8x8_inference.Value() != 0);
writer.WriteBit(false); // Frame cropping flag
writer.WriteBit(false); // VUI parameter present flag
writer.End();
// H264 PPS
writer.WriteU(1, 24);
writer.WriteU(0, 1);
writer.WriteU(3, 2);
writer.WriteU(8, 5);
writer.WriteUe(0);
writer.WriteUe(0);
writer.WriteBit(context.h264_parameter_set.entropy_coding_mode_flag != 0);
writer.WriteBit(false);
writer.WriteUe(0);
writer.WriteUe(context.h264_parameter_set.num_refidx_l0_default_active);
writer.WriteUe(context.h264_parameter_set.num_refidx_l1_default_active);
writer.WriteBit(context.h264_parameter_set.flags.weighted_pred.Value() != 0);
writer.WriteU(static_cast<s32>(context.h264_parameter_set.weighted_bipred_idc.Value()), 2);
s32 pic_init_qp = static_cast<s32>(context.h264_parameter_set.pic_init_qp_minus26.Value());
writer.WriteSe(pic_init_qp);
writer.WriteSe(0);
s32 chroma_qp_index_offset =
static_cast<s32>(context.h264_parameter_set.chroma_qp_index_offset.Value());
writer.WriteSe(chroma_qp_index_offset);
writer.WriteBit(context.h264_parameter_set.deblocking_filter_control_present_flag != 0);
writer.WriteBit(context.h264_parameter_set.flags.constrained_intra_pred.Value() != 0);
writer.WriteBit(context.h264_parameter_set.redundant_pic_cnt_present_flag != 0);
writer.WriteBit(context.h264_parameter_set.transform_8x8_mode_flag != 0);
writer.WriteBit(true);
for (s32 index = 0; index < 6; index++) {
writer.WriteBit(true);
std::span<const u8> matrix{context.weight_scale};
writer.WriteScalingList(matrix, index * 16, 16);
}
if (context.h264_parameter_set.transform_8x8_mode_flag) {
for (s32 index = 0; index < 2; index++) {
writer.WriteBit(true);
std::span<const u8> matrix{context.weight_scale_8x8};
writer.WriteScalingList(matrix, index * 64, 64);
}
}
s32 chroma_qp_index_offset2 =
static_cast<s32>(context.h264_parameter_set.second_chroma_qp_index_offset.Value());
writer.WriteSe(chroma_qp_index_offset2);
writer.End();
const auto& encoded_header = writer.GetByteArray();
frame.resize(encoded_header.size() + context.stream_len);
std::memcpy(frame.data(), encoded_header.data(), encoded_header.size());
gpu.MemoryManager().ReadBlock(state.frame_bitstream_offset,
frame.data() + encoded_header.size(), context.stream_len);
return frame;
}
@@ -202,7 +197,7 @@ void H264BitWriter::WriteBit(bool state) {
WriteBits(state ? 1 : 0, 1);
}
void H264BitWriter::WriteScalingList(const std::vector<u8>& list, s32 start, s32 count) {
void H264BitWriter::WriteScalingList(std::span<const u8> list, s32 start, s32 count) {
std::vector<u8> scan(count);
if (count == 16) {
std::memcpy(scan.data(), zig_zag_scan.data(), scan.size());

View File

@@ -20,7 +20,9 @@
#pragma once
#include <span>
#include <vector>
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "video_core/command_classes/nvdec_common.h"
@@ -48,7 +50,7 @@ public:
/// Based on section 7.3.2.1.1.1 and Table 7-4 in the H.264 specification
/// Writes the scaling matrices of the sream
void WriteScalingList(const std::vector<u8>& list, s32 start, s32 count);
void WriteScalingList(std::span<const u8> list, s32 start, s32 count);
/// Return the bitstream as a vector.
[[nodiscard]] std::vector<u8>& GetByteArray();
@@ -78,40 +80,110 @@ public:
const NvdecCommon::NvdecRegisters& state, bool is_first_frame = false);
private:
struct H264ParameterSet {
u32 log2_max_pic_order_cnt{};
u32 delta_pic_order_always_zero_flag{};
u32 frame_mbs_only_flag{};
u32 pic_width_in_mbs{};
u32 pic_height_in_map_units{};
INSERT_PADDING_WORDS(1);
u32 entropy_coding_mode_flag{};
u32 bottom_field_pic_order_flag{};
u32 num_refidx_l0_default_active{};
u32 num_refidx_l1_default_active{};
u32 deblocking_filter_control_flag{};
u32 redundant_pic_count_flag{};
u32 transform_8x8_mode_flag{};
INSERT_PADDING_WORDS(9);
u64 flags{};
u32 frame_number{};
u32 frame_number2{};
};
static_assert(sizeof(H264ParameterSet) == 0x68, "H264ParameterSet is an invalid size");
struct H264DecoderContext {
INSERT_PADDING_BYTES(0x48);
u32 frame_data_size{};
INSERT_PADDING_BYTES(0xc);
H264ParameterSet h264_parameter_set{};
INSERT_PADDING_BYTES(0x100);
std::array<u8, 0x60> scaling_matrix_4;
std::array<u8, 0x80> scaling_matrix_8;
};
static_assert(sizeof(H264DecoderContext) == 0x2a0, "H264DecoderContext is an invalid size");
std::vector<u8> frame;
GPU& gpu;
struct H264ParameterSet {
s32 log2_max_pic_order_cnt_lsb_minus4; ///< 0x00
s32 delta_pic_order_always_zero_flag; ///< 0x04
s32 frame_mbs_only_flag; ///< 0x08
u32 pic_width_in_mbs; ///< 0x0C
u32 frame_height_in_map_units; ///< 0x10
union { ///< 0x14
BitField<0, 2, u32> tile_format;
BitField<2, 3, u32> gob_height;
};
u32 entropy_coding_mode_flag; ///< 0x18
s32 pic_order_present_flag; ///< 0x1C
s32 num_refidx_l0_default_active; ///< 0x20
s32 num_refidx_l1_default_active; ///< 0x24
s32 deblocking_filter_control_present_flag; ///< 0x28
s32 redundant_pic_cnt_present_flag; ///< 0x2C
u32 transform_8x8_mode_flag; ///< 0x30
u32 pitch_luma; ///< 0x34
u32 pitch_chroma; ///< 0x38
u32 luma_top_offset; ///< 0x3C
u32 luma_bot_offset; ///< 0x40
u32 luma_frame_offset; ///< 0x44
u32 chroma_top_offset; ///< 0x48
u32 chroma_bot_offset; ///< 0x4C
u32 chroma_frame_offset; ///< 0x50
u32 hist_buffer_size; ///< 0x54
union { ///< 0x58
union {
BitField<0, 1, u64> mbaff_frame;
BitField<1, 1, u64> direct_8x8_inference;
BitField<2, 1, u64> weighted_pred;
BitField<3, 1, u64> constrained_intra_pred;
BitField<4, 1, u64> ref_pic;
BitField<5, 1, u64> field_pic;
BitField<6, 1, u64> bottom_field;
BitField<7, 1, u64> second_field;
} flags;
BitField<8, 4, u64> log2_max_frame_num_minus4;
BitField<12, 2, u64> chroma_format_idc;
BitField<14, 2, u64> pic_order_cnt_type;
BitField<16, 6, s64> pic_init_qp_minus26;
BitField<22, 5, s64> chroma_qp_index_offset;
BitField<27, 5, s64> second_chroma_qp_index_offset;
BitField<32, 2, u64> weighted_bipred_idc;
BitField<34, 7, u64> curr_pic_idx;
BitField<41, 5, u64> curr_col_idx;
BitField<46, 16, u64> frame_number;
BitField<62, 1, u64> frame_surfaces;
BitField<63, 1, u64> output_memory_layout;
};
};
static_assert(sizeof(H264ParameterSet) == 0x60, "H264ParameterSet is an invalid size");
struct H264DecoderContext {
INSERT_PADDING_WORDS_NOINIT(18); ///< 0x0000
u32 stream_len; ///< 0x0048
INSERT_PADDING_WORDS_NOINIT(3); ///< 0x004C
H264ParameterSet h264_parameter_set; ///< 0x0058
INSERT_PADDING_WORDS_NOINIT(66); ///< 0x00B8
std::array<u8, 0x60> weight_scale; ///< 0x01C0
std::array<u8, 0x80> weight_scale_8x8; ///< 0x0220
};
static_assert(sizeof(H264DecoderContext) == 0x2A0, "H264DecoderContext is an invalid size");
#define ASSERT_POSITION(field_name, position) \
static_assert(offsetof(H264ParameterSet, field_name) == position, \
"Field " #field_name " has invalid position")
ASSERT_POSITION(log2_max_pic_order_cnt_lsb_minus4, 0x00);
ASSERT_POSITION(delta_pic_order_always_zero_flag, 0x04);
ASSERT_POSITION(frame_mbs_only_flag, 0x08);
ASSERT_POSITION(pic_width_in_mbs, 0x0C);
ASSERT_POSITION(frame_height_in_map_units, 0x10);
ASSERT_POSITION(tile_format, 0x14);
ASSERT_POSITION(entropy_coding_mode_flag, 0x18);
ASSERT_POSITION(pic_order_present_flag, 0x1C);
ASSERT_POSITION(num_refidx_l0_default_active, 0x20);
ASSERT_POSITION(num_refidx_l1_default_active, 0x24);
ASSERT_POSITION(deblocking_filter_control_present_flag, 0x28);
ASSERT_POSITION(redundant_pic_cnt_present_flag, 0x2C);
ASSERT_POSITION(transform_8x8_mode_flag, 0x30);
ASSERT_POSITION(pitch_luma, 0x34);
ASSERT_POSITION(pitch_chroma, 0x38);
ASSERT_POSITION(luma_top_offset, 0x3C);
ASSERT_POSITION(luma_bot_offset, 0x40);
ASSERT_POSITION(luma_frame_offset, 0x44);
ASSERT_POSITION(chroma_top_offset, 0x48);
ASSERT_POSITION(chroma_bot_offset, 0x4C);
ASSERT_POSITION(chroma_frame_offset, 0x50);
ASSERT_POSITION(hist_buffer_size, 0x54);
ASSERT_POSITION(flags, 0x58);
#undef ASSERT_POSITION
#define ASSERT_POSITION(field_name, position) \
static_assert(offsetof(H264DecoderContext, field_name) == position, \
"Field " #field_name " has invalid position")
ASSERT_POSITION(stream_len, 0x48);
ASSERT_POSITION(h264_parameter_set, 0x58);
ASSERT_POSITION(weight_scale, 0x1C0);
#undef ASSERT_POSITION
};
} // namespace Decoder

View File

@@ -354,7 +354,7 @@ void VP9::WriteMvProbabilityUpdate(VpxRangeEncoder& writer, u8 new_prob, u8 old_
}
Vp9PictureInfo VP9::GetVp9PictureInfo(const NvdecCommon::NvdecRegisters& state) {
PictureInfo picture_info{};
PictureInfo picture_info;
gpu.MemoryManager().ReadBlock(state.picture_info_offset, &picture_info, sizeof(PictureInfo));
Vp9PictureInfo vp9_info = picture_info.Convert();
@@ -370,7 +370,7 @@ Vp9PictureInfo VP9::GetVp9PictureInfo(const NvdecCommon::NvdecRegisters& state)
}
void VP9::InsertEntropy(u64 offset, Vp9EntropyProbs& dst) {
EntropyProbs entropy{};
EntropyProbs entropy;
gpu.MemoryManager().ReadBlock(offset, &entropy, sizeof(EntropyProbs));
entropy.Convert(dst);
}

View File

@@ -15,10 +15,10 @@ class GPU;
namespace Decoder {
struct Vp9FrameDimensions {
s16 width{};
s16 height{};
s16 luma_pitch{};
s16 chroma_pitch{};
s16 width;
s16 height;
s16 luma_pitch;
s16 chroma_pitch;
};
static_assert(sizeof(Vp9FrameDimensions) == 0x8, "Vp9 Vp9FrameDimensions is an invalid size");
@@ -49,87 +49,87 @@ enum class TxMode {
};
struct Segmentation {
u8 enabled{};
u8 update_map{};
u8 temporal_update{};
u8 abs_delta{};
std::array<u32, 8> feature_mask{};
std::array<std::array<s16, 4>, 8> feature_data{};
u8 enabled;
u8 update_map;
u8 temporal_update;
u8 abs_delta;
std::array<u32, 8> feature_mask;
std::array<std::array<s16, 4>, 8> feature_data;
};
static_assert(sizeof(Segmentation) == 0x64, "Segmentation is an invalid size");
struct LoopFilter {
u8 mode_ref_delta_enabled{};
std::array<s8, 4> ref_deltas{};
std::array<s8, 2> mode_deltas{};
u8 mode_ref_delta_enabled;
std::array<s8, 4> ref_deltas;
std::array<s8, 2> mode_deltas;
};
static_assert(sizeof(LoopFilter) == 0x7, "LoopFilter is an invalid size");
struct Vp9EntropyProbs {
std::array<u8, 36> y_mode_prob{};
std::array<u8, 64> partition_prob{};
std::array<u8, 1728> coef_probs{};
std::array<u8, 8> switchable_interp_prob{};
std::array<u8, 28> inter_mode_prob{};
std::array<u8, 4> intra_inter_prob{};
std::array<u8, 5> comp_inter_prob{};
std::array<u8, 10> single_ref_prob{};
std::array<u8, 5> comp_ref_prob{};
std::array<u8, 6> tx_32x32_prob{};
std::array<u8, 4> tx_16x16_prob{};
std::array<u8, 2> tx_8x8_prob{};
std::array<u8, 3> skip_probs{};
std::array<u8, 3> joints{};
std::array<u8, 2> sign{};
std::array<u8, 20> classes{};
std::array<u8, 2> class_0{};
std::array<u8, 20> prob_bits{};
std::array<u8, 12> class_0_fr{};
std::array<u8, 6> fr{};
std::array<u8, 2> class_0_hp{};
std::array<u8, 2> high_precision{};
std::array<u8, 36> y_mode_prob; ///< 0x0000
std::array<u8, 64> partition_prob; ///< 0x0024
std::array<u8, 1728> coef_probs; ///< 0x0064
std::array<u8, 8> switchable_interp_prob; ///< 0x0724
std::array<u8, 28> inter_mode_prob; ///< 0x072C
std::array<u8, 4> intra_inter_prob; ///< 0x0748
std::array<u8, 5> comp_inter_prob; ///< 0x074C
std::array<u8, 10> single_ref_prob; ///< 0x0751
std::array<u8, 5> comp_ref_prob; ///< 0x075B
std::array<u8, 6> tx_32x32_prob; ///< 0x0760
std::array<u8, 4> tx_16x16_prob; ///< 0x0766
std::array<u8, 2> tx_8x8_prob; ///< 0x076A
std::array<u8, 3> skip_probs; ///< 0x076C
std::array<u8, 3> joints; ///< 0x076F
std::array<u8, 2> sign; ///< 0x0772
std::array<u8, 20> classes; ///< 0x0774
std::array<u8, 2> class_0; ///< 0x0788
std::array<u8, 20> prob_bits; ///< 0x078A
std::array<u8, 12> class_0_fr; ///< 0x079E
std::array<u8, 6> fr; ///< 0x07AA
std::array<u8, 2> class_0_hp; ///< 0x07B0
std::array<u8, 2> high_precision; ///< 0x07B2
};
static_assert(sizeof(Vp9EntropyProbs) == 0x7B4, "Vp9EntropyProbs is an invalid size");
struct Vp9PictureInfo {
bool is_key_frame{};
bool intra_only{};
bool last_frame_was_key{};
bool frame_size_changed{};
bool error_resilient_mode{};
bool last_frame_shown{};
bool show_frame{};
std::array<s8, 4> ref_frame_sign_bias{};
s32 base_q_index{};
s32 y_dc_delta_q{};
s32 uv_dc_delta_q{};
s32 uv_ac_delta_q{};
bool lossless{};
s32 transform_mode{};
bool allow_high_precision_mv{};
s32 interp_filter{};
s32 reference_mode{};
s8 comp_fixed_ref{};
std::array<s8, 2> comp_var_ref{};
s32 log2_tile_cols{};
s32 log2_tile_rows{};
bool segment_enabled{};
bool segment_map_update{};
bool segment_map_temporal_update{};
s32 segment_abs_delta{};
std::array<u32, 8> segment_feature_enable{};
std::array<std::array<s16, 4>, 8> segment_feature_data{};
bool mode_ref_delta_enabled{};
bool use_prev_in_find_mv_refs{};
std::array<s8, 4> ref_deltas{};
std::array<s8, 2> mode_deltas{};
Vp9EntropyProbs entropy{};
Vp9FrameDimensions frame_size{};
u8 first_level{};
u8 sharpness_level{};
u32 bitstream_size{};
std::array<u64, 4> frame_offsets{};
std::array<bool, 4> refresh_frame{};
bool is_key_frame;
bool intra_only;
bool last_frame_was_key;
bool frame_size_changed;
bool error_resilient_mode;
bool last_frame_shown;
bool show_frame;
std::array<s8, 4> ref_frame_sign_bias;
s32 base_q_index;
s32 y_dc_delta_q;
s32 uv_dc_delta_q;
s32 uv_ac_delta_q;
bool lossless;
s32 transform_mode;
bool allow_high_precision_mv;
s32 interp_filter;
s32 reference_mode;
s8 comp_fixed_ref;
std::array<s8, 2> comp_var_ref;
s32 log2_tile_cols;
s32 log2_tile_rows;
bool segment_enabled;
bool segment_map_update;
bool segment_map_temporal_update;
s32 segment_abs_delta;
std::array<u32, 8> segment_feature_enable;
std::array<std::array<s16, 4>, 8> segment_feature_data;
bool mode_ref_delta_enabled;
bool use_prev_in_find_mv_refs;
std::array<s8, 4> ref_deltas;
std::array<s8, 2> mode_deltas;
Vp9EntropyProbs entropy;
Vp9FrameDimensions frame_size;
u8 first_level;
u8 sharpness_level;
u32 bitstream_size;
std::array<u64, 4> frame_offsets;
std::array<bool, 4> refresh_frame;
};
struct Vp9FrameContainer {
@@ -138,35 +138,35 @@ struct Vp9FrameContainer {
};
struct PictureInfo {
INSERT_PADDING_WORDS(12);
u32 bitstream_size{};
INSERT_PADDING_WORDS(5);
Vp9FrameDimensions last_frame_size{};
Vp9FrameDimensions golden_frame_size{};
Vp9FrameDimensions alt_frame_size{};
Vp9FrameDimensions current_frame_size{};
u32 vp9_flags{};
std::array<s8, 4> ref_frame_sign_bias{};
u8 first_level{};
u8 sharpness_level{};
u8 base_q_index{};
u8 y_dc_delta_q{};
u8 uv_ac_delta_q{};
u8 uv_dc_delta_q{};
u8 lossless{};
u8 tx_mode{};
u8 allow_high_precision_mv{};
u8 interp_filter{};
u8 reference_mode{};
s8 comp_fixed_ref{};
std::array<s8, 2> comp_var_ref{};
u8 log2_tile_cols{};
u8 log2_tile_rows{};
Segmentation segmentation{};
LoopFilter loop_filter{};
INSERT_PADDING_BYTES(5);
u32 surface_params{};
INSERT_PADDING_WORDS(3);
INSERT_PADDING_WORDS_NOINIT(12); ///< 0x00
u32 bitstream_size; ///< 0x30
INSERT_PADDING_WORDS_NOINIT(5); ///< 0x34
Vp9FrameDimensions last_frame_size; ///< 0x48
Vp9FrameDimensions golden_frame_size; ///< 0x50
Vp9FrameDimensions alt_frame_size; ///< 0x58
Vp9FrameDimensions current_frame_size; ///< 0x60
u32 vp9_flags; ///< 0x68
std::array<s8, 4> ref_frame_sign_bias; ///< 0x6C
u8 first_level; ///< 0x70
u8 sharpness_level; ///< 0x71
u8 base_q_index; ///< 0x72
u8 y_dc_delta_q; ///< 0x73
u8 uv_ac_delta_q; ///< 0x74
u8 uv_dc_delta_q; ///< 0x75
u8 lossless; ///< 0x76
u8 tx_mode; ///< 0x77
u8 allow_high_precision_mv; ///< 0x78
u8 interp_filter; ///< 0x79
u8 reference_mode; ///< 0x7A
s8 comp_fixed_ref; ///< 0x7B
std::array<s8, 2> comp_var_ref; ///< 0x7C
u8 log2_tile_cols; ///< 0x7E
u8 log2_tile_rows; ///< 0x7F
Segmentation segmentation; ///< 0x80
LoopFilter loop_filter; ///< 0xE4
INSERT_PADDING_BYTES_NOINIT(5); ///< 0xEB
u32 surface_params; ///< 0xF0
INSERT_PADDING_WORDS_NOINIT(3); ///< 0xF4
[[nodiscard]] Vp9PictureInfo Convert() const {
return {
@@ -176,6 +176,7 @@ struct PictureInfo {
.frame_size_changed = (vp9_flags & FrameFlags::FrameSizeChanged) != 0,
.error_resilient_mode = (vp9_flags & FrameFlags::ErrorResilientMode) != 0,
.last_frame_shown = (vp9_flags & FrameFlags::LastShowFrame) != 0,
.show_frame = false,
.ref_frame_sign_bias = ref_frame_sign_bias,
.base_q_index = base_q_index,
.y_dc_delta_q = y_dc_delta_q,
@@ -204,45 +205,48 @@ struct PictureInfo {
!(vp9_flags == (FrameFlags::LastFrameIsKeyFrame)),
.ref_deltas = loop_filter.ref_deltas,
.mode_deltas = loop_filter.mode_deltas,
.entropy{},
.frame_size = current_frame_size,
.first_level = first_level,
.sharpness_level = sharpness_level,
.bitstream_size = bitstream_size,
.frame_offsets{},
.refresh_frame{},
};
}
};
static_assert(sizeof(PictureInfo) == 0x100, "PictureInfo is an invalid size");
struct EntropyProbs {
INSERT_PADDING_BYTES(1024);
std::array<u8, 28> inter_mode_prob{};
std::array<u8, 4> intra_inter_prob{};
INSERT_PADDING_BYTES(80);
std::array<u8, 2> tx_8x8_prob{};
std::array<u8, 4> tx_16x16_prob{};
std::array<u8, 6> tx_32x32_prob{};
std::array<u8, 4> y_mode_prob_e8{};
std::array<std::array<u8, 8>, 4> y_mode_prob_e0e7{};
INSERT_PADDING_BYTES(64);
std::array<u8, 64> partition_prob{};
INSERT_PADDING_BYTES(10);
std::array<u8, 8> switchable_interp_prob{};
std::array<u8, 5> comp_inter_prob{};
std::array<u8, 3> skip_probs{};
INSERT_PADDING_BYTES(1);
std::array<u8, 3> joints{};
std::array<u8, 2> sign{};
std::array<u8, 2> class_0{};
std::array<u8, 6> fr{};
std::array<u8, 2> class_0_hp{};
std::array<u8, 2> high_precision{};
std::array<u8, 20> classes{};
std::array<u8, 12> class_0_fr{};
std::array<u8, 20> pred_bits{};
std::array<u8, 10> single_ref_prob{};
std::array<u8, 5> comp_ref_prob{};
INSERT_PADDING_BYTES(17);
std::array<u8, 2304> coef_probs{};
INSERT_PADDING_BYTES_NOINIT(1024); ///< 0x0000
std::array<u8, 28> inter_mode_prob; ///< 0x0400
std::array<u8, 4> intra_inter_prob; ///< 0x041C
INSERT_PADDING_BYTES_NOINIT(80); ///< 0x0420
std::array<u8, 2> tx_8x8_prob; ///< 0x0470
std::array<u8, 4> tx_16x16_prob; ///< 0x0472
std::array<u8, 6> tx_32x32_prob; ///< 0x0476
std::array<u8, 4> y_mode_prob_e8; ///< 0x047C
std::array<std::array<u8, 8>, 4> y_mode_prob_e0e7; ///< 0x0480
INSERT_PADDING_BYTES_NOINIT(64); ///< 0x04A0
std::array<u8, 64> partition_prob; ///< 0x04E0
INSERT_PADDING_BYTES_NOINIT(10); ///< 0x0520
std::array<u8, 8> switchable_interp_prob; ///< 0x052A
std::array<u8, 5> comp_inter_prob; ///< 0x0532
std::array<u8, 3> skip_probs; ///< 0x0537
INSERT_PADDING_BYTES_NOINIT(1); ///< 0x053A
std::array<u8, 3> joints; ///< 0x053B
std::array<u8, 2> sign; ///< 0x053E
std::array<u8, 2> class_0; ///< 0x0540
std::array<u8, 6> fr; ///< 0x0542
std::array<u8, 2> class_0_hp; ///< 0x0548
std::array<u8, 2> high_precision; ///< 0x054A
std::array<u8, 20> classes; ///< 0x054C
std::array<u8, 12> class_0_fr; ///< 0x0560
std::array<u8, 20> pred_bits; ///< 0x056C
std::array<u8, 10> single_ref_prob; ///< 0x0580
std::array<u8, 5> comp_ref_prob; ///< 0x058A
INSERT_PADDING_BYTES_NOINIT(17); ///< 0x058F
std::array<u8, 2304> coef_probs; ///< 0x05A0
void Convert(Vp9EntropyProbs& fc) {
fc.inter_mode_prob = inter_mode_prob;
@@ -293,10 +297,45 @@ struct RefPoolElement {
};
struct FrameContexts {
s64 from{};
bool adapted{};
Vp9EntropyProbs probs{};
s64 from;
bool adapted;
Vp9EntropyProbs probs;
};
#define ASSERT_POSITION(field_name, position) \
static_assert(offsetof(Vp9EntropyProbs, field_name) == position, \
"Field " #field_name " has invalid position")
ASSERT_POSITION(partition_prob, 0x0024);
ASSERT_POSITION(switchable_interp_prob, 0x0724);
ASSERT_POSITION(sign, 0x0772);
ASSERT_POSITION(class_0_fr, 0x079E);
ASSERT_POSITION(high_precision, 0x07B2);
#undef ASSERT_POSITION
#define ASSERT_POSITION(field_name, position) \
static_assert(offsetof(PictureInfo, field_name) == position, \
"Field " #field_name " has invalid position")
ASSERT_POSITION(bitstream_size, 0x30);
ASSERT_POSITION(last_frame_size, 0x48);
ASSERT_POSITION(first_level, 0x70);
ASSERT_POSITION(segmentation, 0x80);
ASSERT_POSITION(loop_filter, 0xE4);
ASSERT_POSITION(surface_params, 0xF0);
#undef ASSERT_POSITION
#define ASSERT_POSITION(field_name, position) \
static_assert(offsetof(EntropyProbs, field_name) == position, \
"Field " #field_name " has invalid position")
ASSERT_POSITION(inter_mode_prob, 0x400);
ASSERT_POSITION(tx_8x8_prob, 0x470);
ASSERT_POSITION(partition_prob, 0x4E0);
ASSERT_POSITION(class_0, 0x540);
ASSERT_POSITION(class_0_fr, 0x560);
ASSERT_POSITION(coef_probs, 0x5A0);
#undef ASSERT_POSITION
}; // namespace Decoder
}; // namespace Tegra

View File

@@ -8,22 +8,21 @@
namespace Tegra {
Nvdec::Nvdec(GPU& gpu_) : gpu(gpu_), codec(std::make_unique<Codec>(gpu)) {}
#define NVDEC_REG_INDEX(field_name) \
(offsetof(NvdecCommon::NvdecRegisters, field_name) / sizeof(u64))
Nvdec::Nvdec(GPU& gpu_) : gpu(gpu_), state{}, codec(std::make_unique<Codec>(gpu, state)) {}
Nvdec::~Nvdec() = default;
void Nvdec::ProcessMethod(Method method, u32 argument) {
if (method == Method::SetVideoCodec) {
codec->StateWrite(static_cast<u32>(method), argument);
} else {
codec->StateWrite(static_cast<u32>(method), static_cast<u64>(argument) << 8);
}
void Nvdec::ProcessMethod(u32 method, u32 argument) {
state.reg_array[method] = static_cast<u64>(argument) << 8;
switch (method) {
case Method::SetVideoCodec:
case NVDEC_REG_INDEX(set_codec_id):
codec->SetTargetCodec(static_cast<NvdecCommon::VideoCodec>(argument));
break;
case Method::Execute:
case NVDEC_REG_INDEX(execute):
Execute();
break;
}

View File

@@ -14,16 +14,11 @@ class GPU;
class Nvdec {
public:
enum class Method : u32 {
SetVideoCodec = 0x80,
Execute = 0xc0,
};
explicit Nvdec(GPU& gpu);
~Nvdec();
/// Writes the method into the state, Invoke Execute() if encountered
void ProcessMethod(Method method, u32 argument);
void ProcessMethod(u32 method, u32 argument);
/// Return most recently decoded frame
[[nodiscard]] AVFramePtr GetFrame();
@@ -33,6 +28,7 @@ private:
void Execute();
GPU& gpu;
NvdecCommon::NvdecRegisters state;
std::unique_ptr<Codec> codec;
};
} // namespace Tegra

View File

@@ -4,40 +4,13 @@
#pragma once
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
namespace Tegra::NvdecCommon {
struct NvdecRegisters {
INSERT_PADDING_WORDS(256);
u64 set_codec_id{};
INSERT_PADDING_WORDS(254);
u64 set_platform_id{};
u64 picture_info_offset{};
u64 frame_bitstream_offset{};
u64 frame_number{};
u64 h264_slice_data_offsets{};
u64 h264_mv_dump_offset{};
INSERT_PADDING_WORDS(6);
u64 frame_stats_offset{};
u64 h264_last_surface_luma_offset{};
u64 h264_last_surface_chroma_offset{};
std::array<u64, 17> surface_luma_offset{};
std::array<u64, 17> surface_chroma_offset{};
INSERT_PADDING_WORDS(132);
u64 vp9_entropy_probs_offset{};
u64 vp9_backward_updates_offset{};
u64 vp9_last_frame_segmap_offset{};
u64 vp9_curr_frame_segmap_offset{};
INSERT_PADDING_WORDS(2);
u64 vp9_last_frame_mvs_offset{};
u64 vp9_curr_frame_mvs_offset{};
INSERT_PADDING_WORDS(2);
};
static_assert(sizeof(NvdecRegisters) == (0xBC0), "NvdecRegisters is incorrect size");
enum class VideoCodec : u32 {
enum class VideoCodec : u64 {
None = 0x0,
H264 = 0x3,
Vp8 = 0x5,
@@ -45,4 +18,76 @@ enum class VideoCodec : u32 {
Vp9 = 0x9,
};
// NVDEC should use a 32-bit address space, but is mapped to 64-bit,
// doubling the sizes here is compensating for that.
struct NvdecRegisters {
static constexpr std::size_t NUM_REGS = 0x178;
union {
struct {
INSERT_PADDING_WORDS_NOINIT(256); ///< 0x0000
VideoCodec set_codec_id; ///< 0x0400
INSERT_PADDING_WORDS_NOINIT(126); ///< 0x0408
u64 execute; ///< 0x0600
INSERT_PADDING_WORDS_NOINIT(126); ///< 0x0608
struct { ///< 0x0800
union {
BitField<0, 3, VideoCodec> codec;
BitField<4, 1, u64> gp_timer_on;
BitField<13, 1, u64> mb_timer_on;
BitField<14, 1, u64> intra_frame_pslc;
BitField<17, 1, u64> all_intra_frame;
};
} control_params;
u64 picture_info_offset; ///< 0x0808
u64 frame_bitstream_offset; ///< 0x0810
u64 frame_number; ///< 0x0818
u64 h264_slice_data_offsets; ///< 0x0820
u64 h264_mv_dump_offset; ///< 0x0828
INSERT_PADDING_WORDS_NOINIT(6); ///< 0x0830
u64 frame_stats_offset; ///< 0x0848
u64 h264_last_surface_luma_offset; ///< 0x0850
u64 h264_last_surface_chroma_offset; ///< 0x0858
std::array<u64, 17> surface_luma_offset; ///< 0x0860
std::array<u64, 17> surface_chroma_offset; ///< 0x08E8
INSERT_PADDING_WORDS_NOINIT(132); ///< 0x0970
u64 vp9_entropy_probs_offset; ///< 0x0B80
u64 vp9_backward_updates_offset; ///< 0x0B88
u64 vp9_last_frame_segmap_offset; ///< 0x0B90
u64 vp9_curr_frame_segmap_offset; ///< 0x0B98
INSERT_PADDING_WORDS_NOINIT(2); ///< 0x0BA0
u64 vp9_last_frame_mvs_offset; ///< 0x0BA8
u64 vp9_curr_frame_mvs_offset; ///< 0x0BB0
INSERT_PADDING_WORDS_NOINIT(2); ///< 0x0BB8
};
std::array<u64, NUM_REGS> reg_array;
};
};
static_assert(sizeof(NvdecRegisters) == (0xBC0), "NvdecRegisters is incorrect size");
#define ASSERT_REG_POSITION(field_name, position) \
static_assert(offsetof(NvdecRegisters, field_name) == position * sizeof(u64), \
"Field " #field_name " has invalid position")
ASSERT_REG_POSITION(set_codec_id, 0x80);
ASSERT_REG_POSITION(execute, 0xC0);
ASSERT_REG_POSITION(control_params, 0x100);
ASSERT_REG_POSITION(picture_info_offset, 0x101);
ASSERT_REG_POSITION(frame_bitstream_offset, 0x102);
ASSERT_REG_POSITION(frame_number, 0x103);
ASSERT_REG_POSITION(h264_slice_data_offsets, 0x104);
ASSERT_REG_POSITION(frame_stats_offset, 0x109);
ASSERT_REG_POSITION(h264_last_surface_luma_offset, 0x10A);
ASSERT_REG_POSITION(h264_last_surface_chroma_offset, 0x10B);
ASSERT_REG_POSITION(surface_luma_offset, 0x10C);
ASSERT_REG_POSITION(surface_chroma_offset, 0x11D);
ASSERT_REG_POSITION(vp9_entropy_probs_offset, 0x170);
ASSERT_REG_POSITION(vp9_backward_updates_offset, 0x171);
ASSERT_REG_POSITION(vp9_last_frame_segmap_offset, 0x172);
ASSERT_REG_POSITION(vp9_curr_frame_segmap_offset, 0x173);
ASSERT_REG_POSITION(vp9_last_frame_mvs_offset, 0x175);
ASSERT_REG_POSITION(vp9_curr_frame_mvs_offset, 0x176);
#undef ASSERT_REG_POSITION
} // namespace Tegra::NvdecCommon

View File

@@ -7,6 +7,10 @@
#include "video_core/engines/fermi_2d.h"
#include "video_core/memory_manager.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/surface.h"
using VideoCore::Surface::BytesPerBlock;
using VideoCore::Surface::PixelFormatFromRenderTargetFormat;
namespace Tegra::Engines {
@@ -49,7 +53,7 @@ void Fermi2D::Blit() {
UNIMPLEMENTED_IF_MSG(regs.clip_enable != 0, "Clipped blit enabled");
const auto& args = regs.pixels_from_memory;
const Config config{
Config config{
.operation = regs.operation,
.filter = args.sample_mode.filter,
.dst_x0 = args.dst_x0,
@@ -61,7 +65,21 @@ void Fermi2D::Blit() {
.src_x1 = static_cast<s32>((args.du_dx * args.dst_width + args.src_x0) >> 32),
.src_y1 = static_cast<s32>((args.dv_dy * args.dst_height + args.src_y0) >> 32),
};
if (!rasterizer->AccelerateSurfaceCopy(regs.src, regs.dst, config)) {
Surface src = regs.src;
const auto bytes_per_pixel = BytesPerBlock(PixelFormatFromRenderTargetFormat(src.format));
const auto need_align_to_pitch =
src.linear == Tegra::Engines::Fermi2D::MemoryLayout::Pitch &&
static_cast<s32>(src.width) == config.src_x1 &&
config.src_x1 > static_cast<s32>(src.pitch / bytes_per_pixel) && config.src_x0 > 0;
if (need_align_to_pitch) {
auto address = src.Address() + config.src_x0 * bytes_per_pixel;
src.addr_upper = static_cast<u32>(address >> 32);
src.addr_lower = static_cast<u32>(address);
src.width -= config.src_x0;
config.src_x1 -= config.src_x0;
config.src_x0 = 0;
}
if (!rasterizer->AccelerateSurfaceCopy(src, regs.dst, config)) {
UNIMPLEMENTED();
}
}

View File

@@ -18,7 +18,10 @@ set(SHADER_FILES
vulkan_uint8.comp
)
find_program(GLSLANGVALIDATOR "glslangValidator" REQUIRED)
find_program(GLSLANGVALIDATOR "glslangValidator")
if ("${GLSLANGVALIDATOR}" STREQUAL "GLSLANGVALIDATOR-NOTFOUND")
message(FATAL_ERROR "Required program `glslangValidator` not found.")
endif()
set(GLSL_FLAGS "")
set(QUIET_FLAG "--quiet")

View File

@@ -69,11 +69,16 @@ void MemoryManager::Unmap(GPUVAddr gpu_addr, std::size_t size) {
} else {
UNREACHABLE_MSG("Unmapping non-existent GPU address=0x{:x}", gpu_addr);
}
// Flush and invalidate through the GPU interface, to be asynchronous if possible.
const std::optional<VAddr> cpu_addr = GpuToCpuAddress(gpu_addr);
ASSERT(cpu_addr);
rasterizer->UnmapMemory(*cpu_addr, size);
const auto submapped_ranges = GetSubmappedRange(gpu_addr, size);
for (const auto& map : submapped_ranges) {
// Flush and invalidate through the GPU interface, to be asynchronous if possible.
const std::optional<VAddr> cpu_addr = GpuToCpuAddress(map.first);
ASSERT(cpu_addr);
rasterizer->UnmapMemory(*cpu_addr, map.second);
}
UpdateRange(gpu_addr, PageEntry::State::Unmapped, size);
}
@@ -127,8 +132,14 @@ void MemoryManager::SetPageEntry(GPUVAddr gpu_addr, PageEntry page_entry, std::s
//// Lock the new page
// TryLockPage(page_entry, size);
auto& current_page = page_table[PageEntryIndex(gpu_addr)];
page_table[PageEntryIndex(gpu_addr)] = page_entry;
if ((!current_page.IsValid() && page_entry.IsValid()) ||
current_page.ToAddress() != page_entry.ToAddress()) {
rasterizer->ModifyGPUMemory(gpu_addr, size);
}
current_page = page_entry;
}
std::optional<GPUVAddr> MemoryManager::FindFreeRange(std::size_t size, std::size_t align,
@@ -174,6 +185,19 @@ std::optional<VAddr> MemoryManager::GpuToCpuAddress(GPUVAddr gpu_addr) const {
return page_entry.ToAddress() + (gpu_addr & page_mask);
}
std::optional<VAddr> MemoryManager::GpuToCpuAddress(GPUVAddr addr, std::size_t size) const {
size_t page_index{addr >> page_bits};
const size_t page_last{(addr + size + page_size - 1) >> page_bits};
while (page_index < page_last) {
const auto page_addr{GpuToCpuAddress(page_index << page_bits)};
if (page_addr && *page_addr != 0) {
return page_addr;
}
++page_index;
}
return std::nullopt;
}
template <typename T>
T MemoryManager::Read(GPUVAddr addr) const {
if (auto page_pointer{GetPointer(addr)}; page_pointer) {
@@ -370,4 +394,79 @@ bool MemoryManager::IsGranularRange(GPUVAddr gpu_addr, std::size_t size) const {
return page <= Core::Memory::PAGE_SIZE;
}
bool MemoryManager::IsContinousRange(GPUVAddr gpu_addr, std::size_t size) const {
size_t page_index{gpu_addr >> page_bits};
const size_t page_last{(gpu_addr + size + page_size - 1) >> page_bits};
std::optional<VAddr> old_page_addr{};
while (page_index != page_last) {
const auto page_addr{GpuToCpuAddress(page_index << page_bits)};
if (!page_addr || *page_addr == 0) {
return false;
}
if (old_page_addr) {
if (*old_page_addr + page_size != *page_addr) {
return false;
}
}
old_page_addr = page_addr;
++page_index;
}
return true;
}
bool MemoryManager::IsFullyMappedRange(GPUVAddr gpu_addr, std::size_t size) const {
size_t page_index{gpu_addr >> page_bits};
const size_t page_last{(gpu_addr + size + page_size - 1) >> page_bits};
while (page_index < page_last) {
if (!page_table[page_index].IsValid() || page_table[page_index].ToAddress() == 0) {
return false;
}
++page_index;
}
return true;
}
std::vector<std::pair<GPUVAddr, std::size_t>> MemoryManager::GetSubmappedRange(
GPUVAddr gpu_addr, std::size_t size) const {
std::vector<std::pair<GPUVAddr, std::size_t>> result{};
size_t page_index{gpu_addr >> page_bits};
size_t remaining_size{size};
size_t page_offset{gpu_addr & page_mask};
std::optional<std::pair<GPUVAddr, std::size_t>> last_segment{};
std::optional<VAddr> old_page_addr{};
const auto extend_size = [this, &last_segment, &page_index](std::size_t bytes) {
if (!last_segment) {
GPUVAddr new_base_addr = page_index << page_bits;
last_segment = {new_base_addr, bytes};
} else {
last_segment->second += bytes;
}
};
const auto split = [this, &last_segment, &result] {
if (last_segment) {
result.push_back(*last_segment);
last_segment = std::nullopt;
}
};
while (remaining_size > 0) {
const size_t num_bytes{std::min(page_size - page_offset, remaining_size)};
const auto page_addr{GpuToCpuAddress(page_index << page_bits)};
if (!page_addr) {
split();
} else if (old_page_addr) {
if (*old_page_addr + page_size != *page_addr) {
split();
}
extend_size(num_bytes);
} else {
extend_size(num_bytes);
}
++page_index;
page_offset = 0;
remaining_size -= num_bytes;
}
split();
return result;
}
} // namespace Tegra

View File

@@ -76,6 +76,8 @@ public:
[[nodiscard]] std::optional<VAddr> GpuToCpuAddress(GPUVAddr addr) const;
[[nodiscard]] std::optional<VAddr> GpuToCpuAddress(GPUVAddr addr, std::size_t size) const;
template <typename T>
[[nodiscard]] T Read(GPUVAddr addr) const;
@@ -112,10 +114,28 @@ public:
void WriteBlockUnsafe(GPUVAddr gpu_dest_addr, const void* src_buffer, std::size_t size);
/**
* IsGranularRange checks if a gpu region can be simply read with a pointer.
* Checks if a gpu region can be simply read with a pointer.
*/
[[nodiscard]] bool IsGranularRange(GPUVAddr gpu_addr, std::size_t size) const;
/**
* Checks if a gpu region is mapped by a single range of cpu addresses.
*/
[[nodiscard]] bool IsContinousRange(GPUVAddr gpu_addr, std::size_t size) const;
/**
* Checks if a gpu region is mapped entirely.
*/
[[nodiscard]] bool IsFullyMappedRange(GPUVAddr gpu_addr, std::size_t size) const;
/**
* Returns a vector with all the subranges of cpu addresses mapped beneath.
* if the region is continous, a single pair will be returned. If it's unmapped, an empty vector
* will be returned;
*/
std::vector<std::pair<GPUVAddr, std::size_t>> GetSubmappedRange(GPUVAddr gpu_addr,
std::size_t size) const;
[[nodiscard]] GPUVAddr Map(VAddr cpu_addr, GPUVAddr gpu_addr, std::size_t size);
[[nodiscard]] GPUVAddr MapAllocate(VAddr cpu_addr, std::size_t size, std::size_t align);
[[nodiscard]] GPUVAddr MapAllocate32(VAddr cpu_addr, std::size_t size);

View File

@@ -87,6 +87,9 @@ public:
/// Unmap memory range
virtual void UnmapMemory(VAddr addr, u64 size) = 0;
/// Remap GPU memory range. This means underneath backing memory changed
virtual void ModifyGPUMemory(GPUVAddr addr, u64 size) = 0;
/// Notify rasterizer that any caches of the specified region should be flushed to Switch memory
/// and invalidated
virtual void FlushAndInvalidateRegion(VAddr addr, u64 size) = 0;

View File

@@ -42,6 +42,8 @@ public:
[[nodiscard]] virtual RasterizerInterface* ReadRasterizer() = 0;
[[nodiscard]] virtual std::string GetDeviceVendor() const = 0;
// Getter/setter functions:
// ------------------------

View File

@@ -202,13 +202,13 @@ Device::Device() {
LOG_ERROR(Render_OpenGL, "OpenGL 4.6 is not available");
throw std::runtime_error{"Insufficient version"};
}
const std::string_view vendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR));
vendor_name = reinterpret_cast<const char*>(glGetString(GL_VENDOR));
const std::string_view version = reinterpret_cast<const char*>(glGetString(GL_VERSION));
const std::vector extensions = GetExtensions();
const bool is_nvidia = vendor == "NVIDIA Corporation";
const bool is_amd = vendor == "ATI Technologies Inc.";
const bool is_intel = vendor == "Intel";
const bool is_nvidia = vendor_name == "NVIDIA Corporation";
const bool is_amd = vendor_name == "ATI Technologies Inc.";
const bool is_intel = vendor_name == "Intel";
#ifdef __unix__
const bool is_linux = true;
@@ -275,6 +275,56 @@ Device::Device() {
}
}
std::string Device::GetVendorName() const {
if (vendor_name == "NVIDIA Corporation") {
return "NVIDIA";
}
if (vendor_name == "ATI Technologies Inc.") {
return "AMD";
}
if (vendor_name == "Intel") {
// For Mesa, `Intel` is an overloaded vendor string that could mean crocus or iris.
// Simply return `INTEL` for those as well as the Windows driver.
return "INTEL";
}
if (vendor_name == "Intel Open Source Technology Center") {
return "I965";
}
if (vendor_name == "Mesa Project") {
return "I915";
}
if (vendor_name == "Mesa/X.org") {
// This vendor string is overloaded between llvmpipe, softpipe, and virgl, so just return
// MESA instead of one of those driver names.
return "MESA";
}
if (vendor_name == "AMD") {
return "RADEONSI";
}
if (vendor_name == "nouveau") {
return "NOUVEAU";
}
if (vendor_name == "X.Org") {
return "R600";
}
if (vendor_name == "Collabora Ltd") {
return "ZINK";
}
if (vendor_name == "Intel Corporation") {
return "OPENSWR";
}
if (vendor_name == "Microsoft Corporation") {
return "D3D12";
}
if (vendor_name == "NVIDIA") {
// Mesa's tegra driver reports `NVIDIA`. Only present in this list because the default
// strategy would have returned `NVIDIA` here for this driver, the same result as the
// proprietary driver.
return "TEGRA";
}
return vendor_name;
}
Device::Device(std::nullptr_t) {
max_uniform_buffers.fill(std::numeric_limits<u32>::max());
uniform_buffer_alignment = 4;

View File

@@ -22,6 +22,8 @@ public:
explicit Device();
explicit Device(std::nullptr_t);
[[nodiscard]] std::string GetVendorName() const;
u32 GetMaxUniformBuffers(Tegra::Engines::ShaderType shader_type) const noexcept {
return max_uniform_buffers[static_cast<std::size_t>(shader_type)];
}
@@ -130,6 +132,7 @@ private:
static bool TestVariableAoffi();
static bool TestPreciseBug();
std::string vendor_name;
std::array<u32, Tegra::Engines::MaxShaderTypes> max_uniform_buffers{};
std::array<BaseBindings, Tegra::Engines::MaxShaderTypes> base_bindings{};
size_t uniform_buffer_alignment{};

View File

@@ -611,6 +611,13 @@ void RasterizerOpenGL::UnmapMemory(VAddr addr, u64 size) {
shader_cache.OnCPUWrite(addr, size);
}
void RasterizerOpenGL::ModifyGPUMemory(GPUVAddr addr, u64 size) {
{
std::scoped_lock lock{texture_cache.mutex};
texture_cache.UnmapGPUMemory(addr, size);
}
}
void RasterizerOpenGL::SignalSemaphore(GPUVAddr addr, u32 value) {
if (!gpu.IsAsync()) {
gpu_memory.Write<u32>(addr, value);

View File

@@ -80,6 +80,7 @@ public:
void OnCPUWrite(VAddr addr, u64 size) override;
void SyncGuestHost() override;
void UnmapMemory(VAddr addr, u64 size) override;
void ModifyGPUMemory(GPUVAddr addr, u64 size) override;
void SignalSemaphore(GPUVAddr addr, u32 value) override;
void SignalSyncPoint(u32 value) override;
void ReleaseFences() override;

View File

@@ -342,6 +342,20 @@ void ApplySwizzle(GLuint handle, PixelFormat format, std::array<SwizzleSource, 4
[[nodiscard]] CopyOrigin MakeCopyOrigin(VideoCommon::Offset3D offset,
VideoCommon::SubresourceLayers subresource, GLenum target) {
switch (target) {
case GL_TEXTURE_1D:
return CopyOrigin{
.level = static_cast<GLint>(subresource.base_level),
.x = static_cast<GLint>(offset.x),
.y = static_cast<GLint>(0),
.z = static_cast<GLint>(0),
};
case GL_TEXTURE_1D_ARRAY:
return CopyOrigin{
.level = static_cast<GLint>(subresource.base_level),
.x = static_cast<GLint>(offset.x),
.y = static_cast<GLint>(0),
.z = static_cast<GLint>(subresource.base_layer),
};
case GL_TEXTURE_2D_ARRAY:
case GL_TEXTURE_2D_MULTISAMPLE_ARRAY:
return CopyOrigin{
@@ -367,6 +381,18 @@ void ApplySwizzle(GLuint handle, PixelFormat format, std::array<SwizzleSource, 4
VideoCommon::SubresourceLayers dst_subresource,
GLenum target) {
switch (target) {
case GL_TEXTURE_1D:
return CopyRegion{
.width = static_cast<GLsizei>(extent.width),
.height = static_cast<GLsizei>(1),
.depth = static_cast<GLsizei>(1),
};
case GL_TEXTURE_1D_ARRAY:
return CopyRegion{
.width = static_cast<GLsizei>(extent.width),
.height = static_cast<GLsizei>(1),
.depth = static_cast<GLsizei>(dst_subresource.num_layers),
};
case GL_TEXTURE_2D_ARRAY:
case GL_TEXTURE_2D_MULTISAMPLE_ARRAY:
return CopyRegion{

View File

@@ -70,6 +70,10 @@ public:
return &rasterizer;
}
[[nodiscard]] std::string GetDeviceVendor() const override {
return device.GetVendorName();
}
private:
/// Initializes the OpenGL state and creates persistent objects.
void InitOpenGLObjects();

View File

@@ -261,9 +261,9 @@ void UtilShaders::CopyBC4(Image& dst_image, Image& src_image, std::span<const Im
glUniform3ui(LOC_SRC_OFFSET, copy.src_offset.x, copy.src_offset.y, copy.src_offset.z);
glUniform3ui(LOC_DST_OFFSET, copy.dst_offset.x, copy.dst_offset.y, copy.dst_offset.z);
glBindImageTexture(BINDING_INPUT_IMAGE, src_image.StorageHandle(),
copy.src_subresource.base_level, GL_FALSE, 0, GL_READ_ONLY, GL_RG32UI);
copy.src_subresource.base_level, GL_TRUE, 0, GL_READ_ONLY, GL_RG32UI);
glBindImageTexture(BINDING_OUTPUT_IMAGE, dst_image.StorageHandle(),
copy.dst_subresource.base_level, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8UI);
copy.dst_subresource.base_level, GL_TRUE, 0, GL_WRITE_ONLY, GL_RGBA8UI);
glDispatchCompute(copy.extent.width, copy.extent.height, copy.extent.depth);
}
program_manager.RestoreGuestCompute();

View File

@@ -103,7 +103,7 @@ RendererVulkan::RendererVulkan(Core::TelemetrySession& telemetry_session_,
gpu(gpu_),
library(OpenLibrary()),
instance(CreateInstance(library, dld, VK_API_VERSION_1_1, render_window.GetWindowInfo().type,
true, Settings::values.renderer_debug)),
true, Settings::values.renderer_debug.GetValue())),
debug_callback(Settings::values.renderer_debug ? CreateDebugCallback(instance) : nullptr),
surface(CreateSurface(instance, render_window)),
device(CreateDevice(instance, dld, *surface)),

View File

@@ -47,6 +47,10 @@ public:
return &rasterizer;
}
[[nodiscard]] std::string GetDeviceVendor() const override {
return device.GetDriverName();
}
private:
void Report() const;

View File

@@ -557,6 +557,13 @@ void RasterizerVulkan::UnmapMemory(VAddr addr, u64 size) {
pipeline_cache.OnCPUWrite(addr, size);
}
void RasterizerVulkan::ModifyGPUMemory(GPUVAddr addr, u64 size) {
{
std::scoped_lock lock{texture_cache.mutex};
texture_cache.UnmapGPUMemory(addr, size);
}
}
void RasterizerVulkan::SignalSemaphore(GPUVAddr addr, u32 value) {
if (!gpu.IsAsync()) {
gpu_memory.Write<u32>(addr, value);

View File

@@ -72,6 +72,7 @@ public:
void OnCPUWrite(VAddr addr, u64 size) override;
void SyncGuestHost() override;
void UnmapMemory(VAddr addr, u64 size) override;
void ModifyGPUMemory(GPUVAddr addr, u64 size) override;
void SignalSemaphore(GPUVAddr addr, u32 value) override;
void SignalSyncPoint(u32 value) override;
void ReleaseFences() override;

View File

@@ -69,6 +69,9 @@ ImageBase::ImageBase(const ImageInfo& info_, GPUVAddr gpu_addr_, VAddr cpu_addr_
}
}
ImageMapView::ImageMapView(GPUVAddr gpu_addr_, VAddr cpu_addr_, size_t size_, ImageId image_id_)
: gpu_addr{gpu_addr_}, cpu_addr{cpu_addr_}, size{size_}, image_id{image_id_} {}
std::optional<SubresourceBase> ImageBase::TryFindBase(GPUVAddr other_addr) const noexcept {
if (other_addr < gpu_addr) {
// Subresource address can't be lower than the base

View File

@@ -25,12 +25,14 @@ enum class ImageFlagBits : u32 {
Strong = 1 << 5, ///< Exists in the image table, the dimensions are can be trusted
Registered = 1 << 6, ///< True when the image is registered
Picked = 1 << 7, ///< Temporary flag to mark the image as picked
Remapped = 1 << 8, ///< Image has been remapped.
Sparse = 1 << 9, ///< Image has non continous submemory.
// Garbage Collection Flags
BadOverlap = 1 << 8, ///< This image overlaps other but doesn't fit, has higher
///< garbage collection priority
Alias = 1 << 9, ///< This image has aliases and has priority on garbage
///< collection
BadOverlap = 1 << 10, ///< This image overlaps other but doesn't fit, has higher
///< garbage collection priority
Alias = 1 << 11, ///< This image has aliases and has priority on garbage
///< collection
};
DECLARE_ENUM_FLAG_OPERATORS(ImageFlagBits)
@@ -57,6 +59,12 @@ struct ImageBase {
return cpu_addr < overlap_end && overlap_cpu_addr < cpu_addr_end;
}
[[nodiscard]] bool OverlapsGPU(GPUVAddr overlap_gpu_addr, size_t overlap_size) const noexcept {
const VAddr overlap_end = overlap_gpu_addr + overlap_size;
const GPUVAddr gpu_addr_end = gpu_addr + guest_size_bytes;
return gpu_addr < overlap_end && overlap_gpu_addr < gpu_addr_end;
}
void CheckBadOverlapState();
void CheckAliasState();
@@ -84,6 +92,29 @@ struct ImageBase {
std::vector<AliasedImage> aliased_images;
std::vector<ImageId> overlapping_images;
ImageMapId map_view_id{};
};
struct ImageMapView {
explicit ImageMapView(GPUVAddr gpu_addr, VAddr cpu_addr, size_t size, ImageId image_id);
[[nodiscard]] bool Overlaps(VAddr overlap_cpu_addr, size_t overlap_size) const noexcept {
const VAddr overlap_end = overlap_cpu_addr + overlap_size;
const VAddr cpu_addr_end = cpu_addr + size;
return cpu_addr < overlap_end && overlap_cpu_addr < cpu_addr_end;
}
[[nodiscard]] bool OverlapsGPU(GPUVAddr overlap_gpu_addr, size_t overlap_size) const noexcept {
const GPUVAddr overlap_end = overlap_gpu_addr + overlap_size;
const GPUVAddr gpu_addr_end = gpu_addr + size;
return gpu_addr < overlap_end && overlap_gpu_addr < gpu_addr_end;
}
GPUVAddr gpu_addr;
VAddr cpu_addr;
size_t size;
ImageId image_id;
bool picked{};
};
struct ImageAllocBase {

View File

@@ -13,6 +13,7 @@
#include <span>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
@@ -152,12 +153,13 @@ public:
/// Remove images in a region
void UnmapMemory(VAddr cpu_addr, size_t size);
/// Remove images in a region
void UnmapGPUMemory(GPUVAddr gpu_addr, size_t size);
/// Blit an image with the given parameters
void BlitImage(const Tegra::Engines::Fermi2D::Surface& dst,
const Tegra::Engines::Fermi2D::Surface& src,
const Tegra::Engines::Fermi2D::Config& copy,
std::optional<Region2D> src_region_override = {},
std::optional<Region2D> dst_region_override = {});
const Tegra::Engines::Fermi2D::Config& copy);
/// Invalidate the contents of the color buffer index
/// These contents become unspecified, the cache can assume aggressive optimizations.
@@ -190,7 +192,22 @@ public:
private:
/// Iterate over all page indices in a range
template <typename Func>
static void ForEachPage(VAddr addr, size_t size, Func&& func) {
static void ForEachCPUPage(VAddr addr, size_t size, Func&& func) {
static constexpr bool RETURNS_BOOL = std::is_same_v<std::invoke_result<Func, u64>, bool>;
const u64 page_end = (addr + size - 1) >> PAGE_BITS;
for (u64 page = addr >> PAGE_BITS; page <= page_end; ++page) {
if constexpr (RETURNS_BOOL) {
if (func(page)) {
break;
}
} else {
func(page);
}
}
}
template <typename Func>
static void ForEachGPUPage(GPUVAddr addr, size_t size, Func&& func) {
static constexpr bool RETURNS_BOOL = std::is_same_v<std::invoke_result<Func, u64>, bool>;
const u64 page_end = (addr + size - 1) >> PAGE_BITS;
for (u64 page = addr >> PAGE_BITS; page <= page_end; ++page) {
@@ -220,7 +237,7 @@ private:
FramebufferId GetFramebufferId(const RenderTargets& key);
/// Refresh the contents (pixel data) of an image
void RefreshContents(Image& image);
void RefreshContents(Image& image, ImageId image_id);
/// Upload data from guest to an image
template <typename StagingBuffer>
@@ -269,6 +286,16 @@ private:
template <typename Func>
void ForEachImageInRegion(VAddr cpu_addr, size_t size, Func&& func);
template <typename Func>
void ForEachImageInRegionGPU(GPUVAddr gpu_addr, size_t size, Func&& func);
template <typename Func>
void ForEachSparseImageInRegion(GPUVAddr gpu_addr, size_t size, Func&& func);
/// Iterates over all the images in a region calling func
template <typename Func>
void ForEachSparseSegment(ImageBase& image, Func&& func);
/// Find or create an image view in the given image with the passed parameters
[[nodiscard]] ImageViewId FindOrEmplaceImageView(ImageId image_id, const ImageViewInfo& info);
@@ -279,10 +306,10 @@ private:
void UnregisterImage(ImageId image);
/// Track CPU reads and writes for image
void TrackImage(ImageBase& image);
void TrackImage(ImageBase& image, ImageId image_id);
/// Stop tracking CPU reads and writes for image
void UntrackImage(ImageBase& image);
void UntrackImage(ImageBase& image, ImageId image_id);
/// Delete image from the cache
void DeleteImage(ImageId image);
@@ -340,7 +367,13 @@ private:
std::unordered_map<TSCEntry, SamplerId> samplers;
std::unordered_map<RenderTargets, FramebufferId> framebuffers;
std::unordered_map<u64, std::vector<ImageId>, IdentityHash<u64>> page_table;
std::unordered_map<u64, std::vector<ImageMapId>, IdentityHash<u64>> page_table;
std::unordered_map<u64, std::vector<ImageId>, IdentityHash<u64>> gpu_page_table;
std::unordered_map<u64, std::vector<ImageId>, IdentityHash<u64>> sparse_page_table;
std::unordered_map<ImageId, std::vector<ImageViewId>> sparse_views;
VAddr virtual_invalid_space{};
bool has_deleted_images = false;
u64 total_used_memory = 0;
@@ -349,6 +382,7 @@ private:
u64 critical_memory;
SlotVector<Image> slot_images;
SlotVector<ImageMapView> slot_map_views;
SlotVector<ImageView> slot_image_views;
SlotVector<ImageAlloc> slot_image_allocs;
SlotVector<Sampler> slot_samplers;
@@ -459,7 +493,7 @@ void TextureCache<P>::RunGarbageCollector() {
}
}
if (True(image->flags & ImageFlagBits::Tracked)) {
UntrackImage(*image);
UntrackImage(*image, image_id);
}
UnregisterImage(image_id);
DeleteImage(image_id);
@@ -658,7 +692,9 @@ void TextureCache<P>::WriteMemory(VAddr cpu_addr, size_t size) {
return;
}
image.flags |= ImageFlagBits::CpuModified;
UntrackImage(image);
if (True(image.flags & ImageFlagBits::Tracked)) {
UntrackImage(image, image_id);
}
});
}
@@ -695,19 +731,34 @@ void TextureCache<P>::UnmapMemory(VAddr cpu_addr, size_t size) {
for (const ImageId id : deleted_images) {
Image& image = slot_images[id];
if (True(image.flags & ImageFlagBits::Tracked)) {
UntrackImage(image);
UntrackImage(image, id);
}
UnregisterImage(id);
DeleteImage(id);
}
}
template <class P>
void TextureCache<P>::UnmapGPUMemory(GPUVAddr gpu_addr, size_t size) {
std::vector<ImageId> deleted_images;
ForEachImageInRegionGPU(gpu_addr, size,
[&](ImageId id, Image&) { deleted_images.push_back(id); });
for (const ImageId id : deleted_images) {
Image& image = slot_images[id];
if (True(image.flags & ImageFlagBits::Remapped)) {
continue;
}
image.flags |= ImageFlagBits::Remapped;
if (True(image.flags & ImageFlagBits::Tracked)) {
UntrackImage(image, id);
}
}
}
template <class P>
void TextureCache<P>::BlitImage(const Tegra::Engines::Fermi2D::Surface& dst,
const Tegra::Engines::Fermi2D::Surface& src,
const Tegra::Engines::Fermi2D::Config& copy,
std::optional<Region2D> src_override,
std::optional<Region2D> dst_override) {
const Tegra::Engines::Fermi2D::Config& copy) {
const BlitImages images = GetBlitImages(dst, src);
const ImageId dst_id = images.dst_id;
const ImageId src_id = images.src_id;
@@ -718,47 +769,25 @@ void TextureCache<P>::BlitImage(const Tegra::Engines::Fermi2D::Surface& dst,
const ImageBase& src_image = slot_images[src_id];
// TODO: Deduplicate
const std::optional dst_base = dst_image.TryFindBase(dst.Address());
const SubresourceRange dst_range{.base = dst_base.value(), .extent = {1, 1}};
const ImageViewInfo dst_view_info(ImageViewType::e2D, images.dst_format, dst_range);
const auto [dst_framebuffer_id, dst_view_id] = RenderTargetFromImage(dst_id, dst_view_info);
const auto [src_samples_x, src_samples_y] = SamplesLog2(src_image.info.num_samples);
// out of bounds texture blit checking
const bool use_override = src_override.has_value();
const s32 src_x0 = copy.src_x0 >> src_samples_x;
s32 src_x1 = use_override ? src_override->end.x : copy.src_x1 >> src_samples_x;
const s32 src_y0 = copy.src_y0 >> src_samples_y;
const s32 src_y1 = copy.src_y1 >> src_samples_y;
const auto src_width = static_cast<s32>(src_image.info.size.width);
const bool width_oob = src_x1 > src_width;
const auto width_diff = width_oob ? src_x1 - src_width : 0;
if (width_oob) {
src_x1 = src_width;
}
const Region2D src_dimensions{
Offset2D{.x = src_x0, .y = src_y0},
Offset2D{.x = src_x1, .y = src_y1},
};
const auto src_region = use_override ? *src_override : src_dimensions;
const std::optional src_base = src_image.TryFindBase(src.Address());
const SubresourceRange src_range{.base = src_base.value(), .extent = {1, 1}};
const ImageViewInfo src_view_info(ImageViewType::e2D, images.src_format, src_range);
const auto [src_framebuffer_id, src_view_id] = RenderTargetFromImage(src_id, src_view_info);
const auto [dst_samples_x, dst_samples_y] = SamplesLog2(dst_image.info.num_samples);
const s32 dst_x0 = copy.dst_x0 >> dst_samples_x;
const s32 dst_x1 = copy.dst_x1 >> dst_samples_x;
const s32 dst_y0 = copy.dst_y0 >> dst_samples_y;
const s32 dst_y1 = copy.dst_y1 >> dst_samples_y;
const Region2D dst_dimensions{
Offset2D{.x = dst_x0, .y = dst_y0},
Offset2D{.x = dst_x1 - width_diff, .y = dst_y1},
const auto [src_samples_x, src_samples_y] = SamplesLog2(src_image.info.num_samples);
const Region2D src_region{
Offset2D{.x = copy.src_x0 >> src_samples_x, .y = copy.src_y0 >> src_samples_y},
Offset2D{.x = copy.src_x1 >> src_samples_x, .y = copy.src_y1 >> src_samples_y},
};
const std::optional dst_base = dst_image.TryFindBase(dst.Address());
const SubresourceRange dst_range{.base = dst_base.value(), .extent = {1, 1}};
const ImageViewInfo dst_view_info(ImageViewType::e2D, images.dst_format, dst_range);
const auto [dst_framebuffer_id, dst_view_id] = RenderTargetFromImage(dst_id, dst_view_info);
const auto [dst_samples_x, dst_samples_y] = SamplesLog2(dst_image.info.num_samples);
const Region2D dst_region{
Offset2D{.x = copy.dst_x0 >> dst_samples_x, .y = copy.dst_y0 >> dst_samples_y},
Offset2D{.x = copy.dst_x1 >> dst_samples_x, .y = copy.dst_y1 >> dst_samples_y},
};
const auto dst_region = use_override ? *dst_override : dst_dimensions;
// Always call this after src_framebuffer_id was queried, as the address might be invalidated.
Framebuffer* const dst_framebuffer = &slot_framebuffers[dst_framebuffer_id];
@@ -775,21 +804,6 @@ void TextureCache<P>::BlitImage(const Tegra::Engines::Fermi2D::Surface& dst,
runtime.BlitImage(dst_framebuffer, dst_view, src_view, dst_region, src_region, copy.filter,
copy.operation);
}
if (width_oob) {
// Continue copy of the oob region of the texture on the next row
auto oob_src = src;
oob_src.height++;
const Region2D src_region_override{
Offset2D{.x = 0, .y = src_y0 + 1},
Offset2D{.x = width_diff, .y = src_y1 + 1},
};
const Region2D dst_region_override{
Offset2D{.x = dst_x1 - width_diff, .y = dst_y0},
Offset2D{.x = dst_x1, .y = dst_y1},
};
BlitImage(dst, oob_src, copy, src_region_override, dst_region_override);
}
}
template <class P>
@@ -833,9 +847,10 @@ typename P::ImageView* TextureCache<P>::TryFindFramebufferImageView(VAddr cpu_ad
if (it == page_table.end()) {
return nullptr;
}
const auto& image_ids = it->second;
for (const ImageId image_id : image_ids) {
const ImageBase& image = slot_images[image_id];
const auto& image_map_ids = it->second;
for (const ImageMapId map_id : image_map_ids) {
const ImageMapView& map = slot_map_views[map_id];
const ImageBase& image = slot_images[map.image_id];
if (image.cpu_addr != cpu_addr) {
continue;
}
@@ -915,13 +930,13 @@ bool TextureCache<P>::IsRegionGpuModified(VAddr addr, size_t size) {
}
template <class P>
void TextureCache<P>::RefreshContents(Image& image) {
void TextureCache<P>::RefreshContents(Image& image, ImageId image_id) {
if (False(image.flags & ImageFlagBits::CpuModified)) {
// Only upload modified images
return;
}
image.flags &= ~ImageFlagBits::CpuModified;
TrackImage(image);
TrackImage(image, image_id);
if (image.info.num_samples > 1) {
LOG_WARNING(HW_GPU, "MSAA image uploads are not implemented");
@@ -958,7 +973,7 @@ void TextureCache<P>::UploadImageContents(Image& image, StagingBuffer& staging)
template <class P>
ImageViewId TextureCache<P>::FindImageView(const TICEntry& config) {
if (!IsValidAddress(gpu_memory, config)) {
if (!IsValidEntry(gpu_memory, config)) {
return NULL_IMAGE_VIEW_ID;
}
const auto [pair, is_new] = image_views.try_emplace(config);
@@ -1000,14 +1015,20 @@ ImageId TextureCache<P>::FindOrInsertImage(const ImageInfo& info, GPUVAddr gpu_a
template <class P>
ImageId TextureCache<P>::FindImage(const ImageInfo& info, GPUVAddr gpu_addr,
RelaxedOptions options) {
const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
if (!cpu_addr) {
return ImageId{};
cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr, CalculateGuestSizeInBytes(info));
if (!cpu_addr) {
return ImageId{};
}
}
const bool broken_views = runtime.HasBrokenTextureViewFormats();
const bool native_bgr = runtime.HasNativeBgr();
ImageId image_id;
const auto lambda = [&](ImageId existing_image_id, ImageBase& existing_image) {
if (True(existing_image.flags & ImageFlagBits::Remapped)) {
return false;
}
if (info.type == ImageType::Linear || existing_image.info.type == ImageType::Linear) {
const bool strict_size = False(options & RelaxedOptions::Size) &&
True(existing_image.flags & ImageFlagBits::Strong);
@@ -1033,7 +1054,16 @@ ImageId TextureCache<P>::FindImage(const ImageInfo& info, GPUVAddr gpu_addr,
template <class P>
ImageId TextureCache<P>::InsertImage(const ImageInfo& info, GPUVAddr gpu_addr,
RelaxedOptions options) {
const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
if (!cpu_addr) {
const auto size = CalculateGuestSizeInBytes(info);
cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr, size);
if (!cpu_addr) {
const VAddr fake_addr = ~(1ULL << 40ULL) + virtual_invalid_space;
virtual_invalid_space += Common::AlignUp(size, 32);
cpu_addr = std::optional<VAddr>(fake_addr);
}
}
ASSERT_MSG(cpu_addr, "Tried to insert an image to an invalid gpu_addr=0x{:x}", gpu_addr);
const ImageId image_id = JoinImages(info, gpu_addr, *cpu_addr);
const Image& image = slot_images[image_id];
@@ -1053,11 +1083,14 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VA
const bool broken_views = runtime.HasBrokenTextureViewFormats();
const bool native_bgr = runtime.HasNativeBgr();
std::vector<ImageId> overlap_ids;
std::unordered_set<ImageId> overlaps_found;
std::vector<ImageId> left_aliased_ids;
std::vector<ImageId> right_aliased_ids;
std::unordered_set<ImageId> ignore_textures;
std::vector<ImageId> bad_overlap_ids;
ForEachImageInRegion(cpu_addr, size_bytes, [&](ImageId overlap_id, ImageBase& overlap) {
if (info.type != overlap.info.type) {
const auto region_check = [&](ImageId overlap_id, ImageBase& overlap) {
if (True(overlap.flags & ImageFlagBits::Remapped)) {
ignore_textures.insert(overlap_id);
return;
}
if (info.type == ImageType::Linear) {
@@ -1067,6 +1100,7 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VA
}
return;
}
overlaps_found.insert(overlap_id);
static constexpr bool strict_size = true;
const std::optional<OverlapResult> solution = ResolveOverlap(
new_info, gpu_addr, cpu_addr, overlap, strict_size, broken_views, native_bgr);
@@ -1090,12 +1124,40 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VA
bad_overlap_ids.push_back(overlap_id);
overlap.flags |= ImageFlagBits::BadOverlap;
}
});
};
ForEachImageInRegion(cpu_addr, size_bytes, region_check);
const auto region_check_gpu = [&](ImageId overlap_id, ImageBase& overlap) {
if (!overlaps_found.contains(overlap_id)) {
if (True(overlap.flags & ImageFlagBits::Remapped)) {
ignore_textures.insert(overlap_id);
}
if (overlap.gpu_addr == gpu_addr && overlap.guest_size_bytes == size_bytes) {
ignore_textures.insert(overlap_id);
}
}
};
ForEachSparseImageInRegion(gpu_addr, size_bytes, region_check_gpu);
const ImageId new_image_id = slot_images.insert(runtime, new_info, gpu_addr, cpu_addr);
Image& new_image = slot_images[new_image_id];
if (!gpu_memory.IsContinousRange(new_image.gpu_addr, new_image.guest_size_bytes)) {
new_image.flags |= ImageFlagBits::Sparse;
}
for (const ImageId overlap_id : ignore_textures) {
Image& overlap = slot_images[overlap_id];
if (True(overlap.flags & ImageFlagBits::GpuModified)) {
UNIMPLEMENTED();
}
if (True(overlap.flags & ImageFlagBits::Tracked)) {
UntrackImage(overlap, overlap_id);
}
UnregisterImage(overlap_id);
DeleteImage(overlap_id);
}
// TODO: Only upload what we need
RefreshContents(new_image);
RefreshContents(new_image, new_image_id);
for (const ImageId overlap_id : overlap_ids) {
Image& overlap = slot_images[overlap_id];
@@ -1107,7 +1169,7 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VA
runtime.CopyImage(new_image, overlap, copies);
}
if (True(overlap.flags & ImageFlagBits::Tracked)) {
UntrackImage(overlap);
UntrackImage(overlap, overlap_id);
}
UnregisterImage(overlap_id);
DeleteImage(overlap_id);
@@ -1242,7 +1304,8 @@ void TextureCache<P>::ForEachImageInRegion(VAddr cpu_addr, size_t size, Func&& f
using FuncReturn = typename std::invoke_result<Func, ImageId, Image&>::type;
static constexpr bool BOOL_BREAK = std::is_same_v<FuncReturn, bool>;
boost::container::small_vector<ImageId, 32> images;
ForEachPage(cpu_addr, size, [this, &images, cpu_addr, size, func](u64 page) {
boost::container::small_vector<ImageMapId, 32> maps;
ForEachCPUPage(cpu_addr, size, [this, &images, &maps, cpu_addr, size, func](u64 page) {
const auto it = page_table.find(page);
if (it == page_table.end()) {
if constexpr (BOOL_BREAK) {
@@ -1251,12 +1314,63 @@ void TextureCache<P>::ForEachImageInRegion(VAddr cpu_addr, size_t size, Func&& f
return;
}
}
for (const ImageMapId map_id : it->second) {
ImageMapView& map = slot_map_views[map_id];
if (map.picked) {
continue;
}
if (!map.Overlaps(cpu_addr, size)) {
continue;
}
map.picked = true;
maps.push_back(map_id);
Image& image = slot_images[map.image_id];
if (True(image.flags & ImageFlagBits::Picked)) {
continue;
}
image.flags |= ImageFlagBits::Picked;
images.push_back(map.image_id);
if constexpr (BOOL_BREAK) {
if (func(map.image_id, image)) {
return true;
}
} else {
func(map.image_id, image);
}
}
if constexpr (BOOL_BREAK) {
return false;
}
});
for (const ImageId image_id : images) {
slot_images[image_id].flags &= ~ImageFlagBits::Picked;
}
for (const ImageMapId map_id : maps) {
slot_map_views[map_id].picked = false;
}
}
template <class P>
template <typename Func>
void TextureCache<P>::ForEachImageInRegionGPU(GPUVAddr gpu_addr, size_t size, Func&& func) {
using FuncReturn = typename std::invoke_result<Func, ImageId, Image&>::type;
static constexpr bool BOOL_BREAK = std::is_same_v<FuncReturn, bool>;
boost::container::small_vector<ImageId, 8> images;
ForEachGPUPage(gpu_addr, size, [this, &images, gpu_addr, size, func](u64 page) {
const auto it = gpu_page_table.find(page);
if (it == gpu_page_table.end()) {
if constexpr (BOOL_BREAK) {
return false;
} else {
return;
}
}
for (const ImageId image_id : it->second) {
Image& image = slot_images[image_id];
if (True(image.flags & ImageFlagBits::Picked)) {
continue;
}
if (!image.Overlaps(cpu_addr, size)) {
if (!image.OverlapsGPU(gpu_addr, size)) {
continue;
}
image.flags |= ImageFlagBits::Picked;
@@ -1278,6 +1392,69 @@ void TextureCache<P>::ForEachImageInRegion(VAddr cpu_addr, size_t size, Func&& f
}
}
template <class P>
template <typename Func>
void TextureCache<P>::ForEachSparseImageInRegion(GPUVAddr gpu_addr, size_t size, Func&& func) {
using FuncReturn = typename std::invoke_result<Func, ImageId, Image&>::type;
static constexpr bool BOOL_BREAK = std::is_same_v<FuncReturn, bool>;
boost::container::small_vector<ImageId, 8> images;
ForEachGPUPage(gpu_addr, size, [this, &images, gpu_addr, size, func](u64 page) {
const auto it = sparse_page_table.find(page);
if (it == sparse_page_table.end()) {
if constexpr (BOOL_BREAK) {
return false;
} else {
return;
}
}
for (const ImageId image_id : it->second) {
Image& image = slot_images[image_id];
if (True(image.flags & ImageFlagBits::Picked)) {
continue;
}
if (!image.OverlapsGPU(gpu_addr, size)) {
continue;
}
image.flags |= ImageFlagBits::Picked;
images.push_back(image_id);
if constexpr (BOOL_BREAK) {
if (func(image_id, image)) {
return true;
}
} else {
func(image_id, image);
}
}
if constexpr (BOOL_BREAK) {
return false;
}
});
for (const ImageId image_id : images) {
slot_images[image_id].flags &= ~ImageFlagBits::Picked;
}
}
template <class P>
template <typename Func>
void TextureCache<P>::ForEachSparseSegment(ImageBase& image, Func&& func) {
using FuncReturn = typename std::invoke_result<Func, GPUVAddr, VAddr, size_t>::type;
static constexpr bool RETURNS_BOOL = std::is_same_v<FuncReturn, bool>;
const auto segments = gpu_memory.GetSubmappedRange(image.gpu_addr, image.guest_size_bytes);
for (auto& segment : segments) {
const auto gpu_addr = segment.first;
const auto size = segment.second;
std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
ASSERT(cpu_addr);
if constexpr (RETURNS_BOOL) {
if (func(gpu_addr, *cpu_addr, size)) {
break;
}
} else {
func(gpu_addr, *cpu_addr, size);
}
}
}
template <class P>
ImageViewId TextureCache<P>::FindOrEmplaceImageView(ImageId image_id, const ImageViewInfo& info) {
Image& image = slot_images[image_id];
@@ -1295,8 +1472,6 @@ void TextureCache<P>::RegisterImage(ImageId image_id) {
ASSERT_MSG(False(image.flags & ImageFlagBits::Registered),
"Trying to register an already registered image");
image.flags |= ImageFlagBits::Registered;
ForEachPage(image.cpu_addr, image.guest_size_bytes,
[this, image_id](u64 page) { page_table[page].push_back(image_id); });
u64 tentative_size = std::max(image.guest_size_bytes, image.unswizzled_size_bytes);
if ((IsPixelFormatASTC(image.info.format) &&
True(image.flags & ImageFlagBits::AcceleratedUpload)) ||
@@ -1304,6 +1479,27 @@ void TextureCache<P>::RegisterImage(ImageId image_id) {
tentative_size = EstimatedDecompressedSize(tentative_size, image.info.format);
}
total_used_memory += Common::AlignUp(tentative_size, 1024);
ForEachGPUPage(image.gpu_addr, image.guest_size_bytes,
[this, image_id](u64 page) { gpu_page_table[page].push_back(image_id); });
if (False(image.flags & ImageFlagBits::Sparse)) {
auto map_id =
slot_map_views.insert(image.gpu_addr, image.cpu_addr, image.guest_size_bytes, image_id);
ForEachCPUPage(image.cpu_addr, image.guest_size_bytes,
[this, map_id](u64 page) { page_table[page].push_back(map_id); });
image.map_view_id = map_id;
return;
}
std::vector<ImageViewId> sparse_maps{};
ForEachSparseSegment(
image, [this, image_id, &sparse_maps](GPUVAddr gpu_addr, VAddr cpu_addr, size_t size) {
auto map_id = slot_map_views.insert(gpu_addr, cpu_addr, size, image_id);
ForEachCPUPage(cpu_addr, size,
[this, map_id](u64 page) { page_table[page].push_back(map_id); });
sparse_maps.push_back(map_id);
});
sparse_views.emplace(image_id, std::move(sparse_maps));
ForEachGPUPage(image.gpu_addr, image.guest_size_bytes,
[this, image_id](u64 page) { sparse_page_table[page].push_back(image_id); });
}
template <class P>
@@ -1320,34 +1516,125 @@ void TextureCache<P>::UnregisterImage(ImageId image_id) {
tentative_size = EstimatedDecompressedSize(tentative_size, image.info.format);
}
total_used_memory -= Common::AlignUp(tentative_size, 1024);
ForEachPage(image.cpu_addr, image.guest_size_bytes, [this, image_id](u64 page) {
const auto page_it = page_table.find(page);
if (page_it == page_table.end()) {
UNREACHABLE_MSG("Unregistering unregistered page=0x{:x}", page << PAGE_BITS);
return;
}
std::vector<ImageId>& image_ids = page_it->second;
const auto vector_it = std::ranges::find(image_ids, image_id);
if (vector_it == image_ids.end()) {
UNREACHABLE_MSG("Unregistering unregistered image in page=0x{:x}", page << PAGE_BITS);
return;
}
image_ids.erase(vector_it);
const auto& clear_page_table =
[this, image_id](
u64 page,
std::unordered_map<u64, std::vector<ImageId>, IdentityHash<u64>>& selected_page_table) {
const auto page_it = selected_page_table.find(page);
if (page_it == selected_page_table.end()) {
UNREACHABLE_MSG("Unregistering unregistered page=0x{:x}", page << PAGE_BITS);
return;
}
std::vector<ImageId>& image_ids = page_it->second;
const auto vector_it = std::ranges::find(image_ids, image_id);
if (vector_it == image_ids.end()) {
UNREACHABLE_MSG("Unregistering unregistered image in page=0x{:x}",
page << PAGE_BITS);
return;
}
image_ids.erase(vector_it);
};
ForEachGPUPage(image.gpu_addr, image.guest_size_bytes,
[this, &clear_page_table](u64 page) { clear_page_table(page, gpu_page_table); });
if (False(image.flags & ImageFlagBits::Sparse)) {
const auto map_id = image.map_view_id;
ForEachCPUPage(image.cpu_addr, image.guest_size_bytes, [this, map_id](u64 page) {
const auto page_it = page_table.find(page);
if (page_it == page_table.end()) {
UNREACHABLE_MSG("Unregistering unregistered page=0x{:x}", page << PAGE_BITS);
return;
}
std::vector<ImageMapId>& image_map_ids = page_it->second;
const auto vector_it = std::ranges::find(image_map_ids, map_id);
if (vector_it == image_map_ids.end()) {
UNREACHABLE_MSG("Unregistering unregistered image in page=0x{:x}",
page << PAGE_BITS);
return;
}
image_map_ids.erase(vector_it);
});
slot_map_views.erase(map_id);
return;
}
ForEachGPUPage(image.gpu_addr, image.guest_size_bytes, [this, &clear_page_table](u64 page) {
clear_page_table(page, sparse_page_table);
});
auto it = sparse_views.find(image_id);
ASSERT(it != sparse_views.end());
auto& sparse_maps = it->second;
for (auto& map_view_id : sparse_maps) {
const auto& map_range = slot_map_views[map_view_id];
const VAddr cpu_addr = map_range.cpu_addr;
const std::size_t size = map_range.size;
ForEachCPUPage(cpu_addr, size, [this, image_id](u64 page) {
const auto page_it = page_table.find(page);
if (page_it == page_table.end()) {
UNREACHABLE_MSG("Unregistering unregistered page=0x{:x}", page << PAGE_BITS);
return;
}
std::vector<ImageMapId>& image_map_ids = page_it->second;
auto vector_it = image_map_ids.begin();
while (vector_it != image_map_ids.end()) {
ImageMapView& map = slot_map_views[*vector_it];
if (map.image_id != image_id) {
vector_it++;
continue;
}
if (!map.picked) {
map.picked = true;
}
vector_it = image_map_ids.erase(vector_it);
}
});
slot_map_views.erase(map_view_id);
}
sparse_views.erase(it);
}
template <class P>
void TextureCache<P>::TrackImage(ImageBase& image) {
void TextureCache<P>::TrackImage(ImageBase& image, ImageId image_id) {
ASSERT(False(image.flags & ImageFlagBits::Tracked));
image.flags |= ImageFlagBits::Tracked;
rasterizer.UpdatePagesCachedCount(image.cpu_addr, image.guest_size_bytes, 1);
if (False(image.flags & ImageFlagBits::Sparse)) {
rasterizer.UpdatePagesCachedCount(image.cpu_addr, image.guest_size_bytes, 1);
return;
}
if (True(image.flags & ImageFlagBits::Registered)) {
auto it = sparse_views.find(image_id);
ASSERT(it != sparse_views.end());
auto& sparse_maps = it->second;
for (auto& map_view_id : sparse_maps) {
const auto& map = slot_map_views[map_view_id];
const VAddr cpu_addr = map.cpu_addr;
const std::size_t size = map.size;
rasterizer.UpdatePagesCachedCount(cpu_addr, size, 1);
}
return;
}
ForEachSparseSegment(image,
[this]([[maybe_unused]] GPUVAddr gpu_addr, VAddr cpu_addr, size_t size) {
rasterizer.UpdatePagesCachedCount(cpu_addr, size, 1);
});
}
template <class P>
void TextureCache<P>::UntrackImage(ImageBase& image) {
void TextureCache<P>::UntrackImage(ImageBase& image, ImageId image_id) {
ASSERT(True(image.flags & ImageFlagBits::Tracked));
image.flags &= ~ImageFlagBits::Tracked;
rasterizer.UpdatePagesCachedCount(image.cpu_addr, image.guest_size_bytes, -1);
if (False(image.flags & ImageFlagBits::Sparse)) {
rasterizer.UpdatePagesCachedCount(image.cpu_addr, image.guest_size_bytes, -1);
return;
}
ASSERT(True(image.flags & ImageFlagBits::Registered));
auto it = sparse_views.find(image_id);
ASSERT(it != sparse_views.end());
auto& sparse_maps = it->second;
for (auto& map_view_id : sparse_maps) {
const auto& map = slot_map_views[map_view_id];
const VAddr cpu_addr = map.cpu_addr;
const std::size_t size = map.size;
rasterizer.UpdatePagesCachedCount(cpu_addr, size, -1);
}
}
template <class P>
@@ -1489,10 +1776,10 @@ void TextureCache<P>::PrepareImage(ImageId image_id, bool is_modification, bool
if (invalidate) {
image.flags &= ~(ImageFlagBits::CpuModified | ImageFlagBits::GpuModified);
if (False(image.flags & ImageFlagBits::Tracked)) {
TrackImage(image);
TrackImage(image, image_id);
}
} else {
RefreshContents(image);
RefreshContents(image, image_id);
SynchronizeAliases(image_id);
}
if (is_modification) {

View File

@@ -16,6 +16,7 @@ constexpr size_t MAX_MIP_LEVELS = 14;
constexpr SlotId CORRUPT_ID{0xfffffffe};
using ImageId = SlotId;
using ImageMapId = SlotId;
using ImageViewId = SlotId;
using ImageAllocId = SlotId;
using SamplerId = SlotId;

View File

@@ -664,6 +664,16 @@ LevelArray CalculateMipLevelOffsets(const ImageInfo& info) noexcept {
return offsets;
}
LevelArray CalculateMipLevelSizes(const ImageInfo& info) noexcept {
const u32 num_levels = info.resources.levels;
const LevelInfo level_info = MakeLevelInfo(info);
LevelArray sizes{};
for (u32 level = 0; level < num_levels; ++level) {
sizes[level] = CalculateLevelSize(level_info, level);
}
return sizes;
}
std::vector<u32> CalculateSliceOffsets(const ImageInfo& info) {
ASSERT(info.type == ImageType::e3D);
std::vector<u32> offsets;
@@ -776,14 +786,20 @@ std::vector<ImageCopy> MakeShrinkImageCopies(const ImageInfo& dst, const ImageIn
return copies;
}
bool IsValidAddress(const Tegra::MemoryManager& gpu_memory, const TICEntry& config) {
if (config.Address() == 0) {
bool IsValidEntry(const Tegra::MemoryManager& gpu_memory, const TICEntry& config) {
const GPUVAddr address = config.Address();
if (address == 0) {
return false;
}
if (config.Address() > (u64(1) << 48)) {
if (address > (1ULL << 48)) {
return false;
}
return gpu_memory.GpuToCpuAddress(config.Address()).has_value();
if (gpu_memory.GpuToCpuAddress(address).has_value()) {
return true;
}
const ImageInfo info{config};
const size_t guest_size_bytes = CalculateGuestSizeInBytes(info);
return gpu_memory.GpuToCpuAddress(address, guest_size_bytes).has_value();
}
std::vector<BufferImageCopy> UnswizzleImage(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr,

View File

@@ -40,6 +40,8 @@ struct OverlapResult {
[[nodiscard]] LevelArray CalculateMipLevelOffsets(const ImageInfo& info) noexcept;
[[nodiscard]] LevelArray CalculateMipLevelSizes(const ImageInfo& info) noexcept;
[[nodiscard]] std::vector<u32> CalculateSliceOffsets(const ImageInfo& info);
[[nodiscard]] std::vector<SubresourceBase> CalculateSliceSubresources(const ImageInfo& info);
@@ -55,7 +57,7 @@ struct OverlapResult {
const ImageInfo& src,
SubresourceBase base);
[[nodiscard]] bool IsValidAddress(const Tegra::MemoryManager& gpu_memory, const TICEntry& config);
[[nodiscard]] bool IsValidEntry(const Tegra::MemoryManager& gpu_memory, const TICEntry& config);
[[nodiscard]] std::vector<BufferImageCopy> UnswizzleImage(Tegra::MemoryManager& gpu_memory,
GPUVAddr gpu_addr, const ImageInfo& info,

View File

@@ -532,6 +532,27 @@ bool Device::IsFormatSupported(VkFormat wanted_format, VkFormatFeatureFlags want
return (supported_usage & wanted_usage) == wanted_usage;
}
std::string Device::GetDriverName() const {
switch (driver_id) {
case VK_DRIVER_ID_AMD_PROPRIETARY:
return "AMD";
case VK_DRIVER_ID_AMD_OPEN_SOURCE:
return "AMDVLK";
case VK_DRIVER_ID_MESA_RADV:
return "RADV";
case VK_DRIVER_ID_NVIDIA_PROPRIETARY:
return "NVIDIA";
case VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS:
return "INTEL";
case VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA:
return "ANV";
case VK_DRIVER_ID_MESA_LLVMPIPE:
return "LAVAPIPE";
default:
return vendor_name;
}
}
void Device::CheckSuitability(bool requires_swapchain) const {
std::bitset<REQUIRED_EXTENSIONS.size()> available_extensions;
bool has_swapchain = false;

View File

@@ -45,6 +45,9 @@ public:
/// Reports a shader to Nsight Aftermath.
void SaveShader(const std::vector<u32>& spirv) const;
/// Returns the name of the VkDriverId reported from Vulkan.
std::string GetDriverName() const;
/// Returns the dispatch loader with direct function pointers of the device.
const vk::DeviceDispatch& GetDispatchLoader() const {
return dld;

View File

@@ -52,6 +52,9 @@ add_executable(yuzu
configuration/configure_debug_controller.cpp
configuration/configure_debug_controller.h
configuration/configure_debug_controller.ui
configuration/configure_debug_tab.cpp
configuration/configure_debug_tab.h
configuration/configure_debug_tab.ui
configuration/configure_dialog.cpp
configuration/configure_dialog.h
configuration/configure_filesystem.cpp

File diff suppressed because it is too large Load Diff

View File

@@ -102,28 +102,75 @@ private:
void SaveUILayoutValues();
void SaveWebServiceValues();
/**
* Reads a setting from the qt_config.
*
* @param name The setting's identifier
* @param default_value The value to use when the setting is not already present in the config
*/
QVariant ReadSetting(const QString& name) const;
QVariant ReadSetting(const QString& name, const QVariant& default_value) const;
// Templated ReadSettingGlobal functions will also look for the use_global setting and set
// both the value and the global state properly
template <typename Type>
void ReadSettingGlobal(Settings::Setting<Type>& setting, const QString& name);
template <typename Type>
void ReadSettingGlobal(Settings::Setting<Type>& setting, const QString& name,
const QVariant& default_value);
/**
* Only reads a setting from the qt_config if the current config is a global config, or if the
* current config is a custom config and the setting is overriding the global setting. Otherwise
* it does nothing.
*
* @param setting The variable to be modified
* @param name The setting's identifier
* @param default_value The value to use when the setting is not already present in the config
*/
template <typename Type>
void ReadSettingGlobal(Type& setting, const QString& name, const QVariant& default_value) const;
// Templated WriteSettingGlobal functions will also write the global state if needed and will
// skip writing the actual setting if it defers to the global value
/**
* Writes a setting to the qt_config.
*
* @param name The setting's idetentifier
* @param value Value of the setting
* @param default_value Default of the setting if not present in qt_config
* @param use_global Specifies if the custom or global config should be in use, for custom
* configs
*/
void WriteSetting(const QString& name, const QVariant& value);
void WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value);
void WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value,
bool use_global);
/**
* Reads a value from the qt_config and applies it to the setting, using its label and default
* value. If the config is a custom config, this will also read the global state of the setting
* and apply that information to it.
*
* @param The setting
*/
template <typename Type>
void WriteSettingGlobal(const QString& name, const Settings::Setting<Type>& setting);
void ReadGlobalSetting(Settings::Setting<Type>& setting);
/**
* Sets a value to the qt_config using the setting's label and default value. If the config is a
* custom config, it will apply the global state, and the custom value if needed.
*
* @param The setting
*/
template <typename Type>
void WriteSettingGlobal(const QString& name, const Settings::Setting<Type>& setting,
const QVariant& default_value);
void WriteSettingGlobal(const QString& name, const QVariant& value, bool use_global,
const QVariant& default_value);
void WriteGlobalSetting(const Settings::Setting<Type>& setting);
/**
* Reads a value from the qt_config using the setting's label and default value and applies the
* value to the setting.
*
* @param The setting
*/
template <typename Type>
void ReadBasicSetting(Settings::BasicSetting<Type>& setting);
/** Sets a value from the setting in the qt_config using the setting's label and default value.
*
* @param The setting
*/
template <typename Type>
void WriteBasicSetting(const Settings::BasicSetting<Type>& setting);
ConfigType type;
std::unique_ptr<QSettings> qt_config;

View File

@@ -41,7 +41,7 @@
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
<number>11</number>
</property>
<widget class="ConfigureGeneral" name="generalTab">
<property name="accessibleName">
@@ -107,14 +107,6 @@
<string>CPU</string>
</attribute>
</widget>
<widget class="ConfigureCpuDebug" name="cpuDebugTab">
<property name="accessibleName">
<string>Debug</string>
</property>
<attribute name="title">
<string>Debug</string>
</attribute>
</widget>
<widget class="ConfigureGraphics" name="graphicsTab">
<property name="accessibleName">
<string>Graphics</string>
@@ -139,7 +131,7 @@
<string>Audio</string>
</attribute>
</widget>
<widget class="ConfigureDebug" name="debugTab">
<widget class="ConfigureDebugTab" name="debugTab">
<property name="accessibleName">
<string>Debug</string>
</property>
@@ -207,24 +199,12 @@
<header>configuration/configure_audio.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureDebug</class>
<extends>QWidget</extends>
<header>configuration/configure_debug.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureCpu</class>
<extends>QWidget</extends>
<header>configuration/configure_cpu.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureCpuDebug</class>
<extends>QWidget</extends>
<header>configuration/configure_cpu_debug.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureGraphics</class>
<extends>QWidget</extends>
@@ -267,6 +247,12 @@
<header>configuration/configure_service.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureDebugTab</class>
<extends>QWidget</extends>
<header>configuration/configure_debug_tab.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections>
@@ -275,12 +261,32 @@
<signal>accepted()</signal>
<receiver>ConfigureDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>20</x>
<y>20</y>
</hint>
<hint type="destinationlabel">
<x>20</x>
<y>20</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ConfigureDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>20</x>
<y>20</y>
</hint>
<hint type="destinationlabel">
<x>20</x>
<y>20</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -69,7 +69,7 @@ void ConfigureAudio::SetOutputSinkFromSinkID() {
[[maybe_unused]] const QSignalBlocker blocker(ui->output_sink_combo_box);
int new_sink_index = 0;
const QString sink_id = QString::fromStdString(Settings::values.sink_id);
const QString sink_id = QString::fromStdString(Settings::values.sink_id.GetValue());
for (int index = 0; index < ui->output_sink_combo_box->count(); index++) {
if (ui->output_sink_combo_box->itemText(index) == sink_id) {
new_sink_index = index;
@@ -83,7 +83,7 @@ void ConfigureAudio::SetOutputSinkFromSinkID() {
void ConfigureAudio::SetAudioDeviceFromDeviceID() {
int new_device_index = -1;
const QString device_id = QString::fromStdString(Settings::values.audio_device_id);
const QString device_id = QString::fromStdString(Settings::values.audio_device_id.GetValue());
for (int index = 0; index < ui->audio_device_combo_box->count(); index++) {
if (ui->audio_device_combo_box->itemText(index) == device_id) {
new_device_index = index;
@@ -106,9 +106,9 @@ void ConfigureAudio::ApplyConfiguration() {
Settings::values.sink_id =
ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex())
.toStdString();
Settings::values.audio_device_id =
Settings::values.audio_device_id.SetValue(
ui->audio_device_combo_box->itemText(ui->audio_device_combo_box->currentIndex())
.toStdString();
.toStdString());
// Guard if during game and set to game-specific value
if (Settings::values.volume.UsingGlobal()) {

View File

@@ -20,8 +20,6 @@ ConfigureCpu::ConfigureCpu(QWidget* parent) : QWidget(parent), ui(new Ui::Config
SetConfiguration();
connect(ui->accuracy, qOverload<int>(&QComboBox::activated), this,
&ConfigureCpu::AccuracyUpdated);
connect(ui->accuracy, qOverload<int>(&QComboBox::currentIndexChanged), this,
&ConfigureCpu::UpdateGroup);
}
@@ -58,20 +56,6 @@ void ConfigureCpu::SetConfiguration() {
UpdateGroup(ui->accuracy->currentIndex());
}
void ConfigureCpu::AccuracyUpdated(int index) {
if (Settings::IsConfiguringGlobal() &&
static_cast<Settings::CPUAccuracy>(index) == Settings::CPUAccuracy::DebugMode) {
const auto result = QMessageBox::warning(this, tr("Setting CPU to Debug Mode"),
tr("CPU Debug Mode is only intended for developer "
"use. Are you sure you want to enable this?"),
QMessageBox::Yes | QMessageBox::No);
if (result == QMessageBox::No) {
ui->accuracy->setCurrentIndex(static_cast<int>(Settings::CPUAccuracy::Accurate));
UpdateGroup(static_cast<int>(Settings::CPUAccuracy::Accurate));
}
}
}
void ConfigureCpu::UpdateGroup(int index) {
if (!Settings::IsConfiguringGlobal()) {
index -= ConfigurationShared::USE_GLOBAL_OFFSET;
@@ -134,8 +118,6 @@ void ConfigureCpu::SetupPerGameUI() {
ConfigurationShared::SetColoredComboBox(
ui->accuracy, ui->widget_accuracy,
static_cast<u32>(Settings::values.cpu_accuracy.GetValue(true)));
ui->accuracy->removeItem(static_cast<u32>(Settings::CPUAccuracy::DebugMode) +
ConfigurationShared::USE_GLOBAL_OFFSET);
ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_unfuse_fma,
Settings::values.cpuopt_unsafe_unfuse_fma,

View File

@@ -29,7 +29,6 @@ private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
void AccuracyUpdated(int index);
void UpdateGroup(int index);
void SetConfiguration();

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>321</height>
<width>448</width>
<height>433</height>
</rect>
</property>
<property name="windowTitle">
@@ -17,7 +17,7 @@
<item>
<layout class="QVBoxLayout">
<item>
<widget class="QGroupBox">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>General</string>
</property>
@@ -34,6 +34,11 @@
</item>
<item>
<widget class="QComboBox" name="accuracy">
<item>
<property name="text">
<string>Auto</string>
</property>
</item>
<item>
<property name="text">
<string>Accurate</string>
@@ -44,11 +49,6 @@
<string>Unsafe</string>
</property>
</item>
<item>
<property name="text">
<string>Enable Debug Mode</string>
</property>
</item>
</widget>
</item>
</layout>
@@ -57,7 +57,7 @@
<item>
<widget class="QLabel" name="label_recommended_accuracy">
<property name="text">
<string>We recommend setting accuracy to &quot;Accurate&quot;.</string>
<string>We recommend setting accuracy to &quot;Auto&quot;.</string>
</property>
<property name="wordWrap">
<bool>false</bool>

View File

@@ -24,23 +24,26 @@ void ConfigureCpuDebug::SetConfiguration() {
const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn();
ui->cpuopt_page_tables->setEnabled(runtime_lock);
ui->cpuopt_page_tables->setChecked(Settings::values.cpuopt_page_tables);
ui->cpuopt_page_tables->setChecked(Settings::values.cpuopt_page_tables.GetValue());
ui->cpuopt_block_linking->setEnabled(runtime_lock);
ui->cpuopt_block_linking->setChecked(Settings::values.cpuopt_block_linking);
ui->cpuopt_block_linking->setChecked(Settings::values.cpuopt_block_linking.GetValue());
ui->cpuopt_return_stack_buffer->setEnabled(runtime_lock);
ui->cpuopt_return_stack_buffer->setChecked(Settings::values.cpuopt_return_stack_buffer);
ui->cpuopt_return_stack_buffer->setChecked(
Settings::values.cpuopt_return_stack_buffer.GetValue());
ui->cpuopt_fast_dispatcher->setEnabled(runtime_lock);
ui->cpuopt_fast_dispatcher->setChecked(Settings::values.cpuopt_fast_dispatcher);
ui->cpuopt_fast_dispatcher->setChecked(Settings::values.cpuopt_fast_dispatcher.GetValue());
ui->cpuopt_context_elimination->setEnabled(runtime_lock);
ui->cpuopt_context_elimination->setChecked(Settings::values.cpuopt_context_elimination);
ui->cpuopt_context_elimination->setChecked(
Settings::values.cpuopt_context_elimination.GetValue());
ui->cpuopt_const_prop->setEnabled(runtime_lock);
ui->cpuopt_const_prop->setChecked(Settings::values.cpuopt_const_prop);
ui->cpuopt_const_prop->setChecked(Settings::values.cpuopt_const_prop.GetValue());
ui->cpuopt_misc_ir->setEnabled(runtime_lock);
ui->cpuopt_misc_ir->setChecked(Settings::values.cpuopt_misc_ir);
ui->cpuopt_misc_ir->setChecked(Settings::values.cpuopt_misc_ir.GetValue());
ui->cpuopt_reduce_misalign_checks->setEnabled(runtime_lock);
ui->cpuopt_reduce_misalign_checks->setChecked(Settings::values.cpuopt_reduce_misalign_checks);
ui->cpuopt_reduce_misalign_checks->setChecked(
Settings::values.cpuopt_reduce_misalign_checks.GetValue());
ui->cpuopt_fastmem->setEnabled(runtime_lock);
ui->cpuopt_fastmem->setChecked(Settings::values.cpuopt_fastmem);
ui->cpuopt_fastmem->setChecked(Settings::values.cpuopt_fastmem.GetValue());
}
void ConfigureCpuDebug::ApplyConfiguration() {

View File

@@ -6,7 +6,6 @@
#include <memory>
#include <QWidget>
#include "common/settings.h"
namespace Ui {
class ConfigureCpuDebug;

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>321</height>
<width>592</width>
<height>503</height>
</rect>
</property>
<property name="windowTitle">
@@ -17,140 +17,132 @@
<item>
<layout class="QVBoxLayout">
<item>
<widget class="QGroupBox">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Toggle CPU Optimizations</string>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QLabel">
<property name="wordWrap">
<bool>1</bool>
</property>
<widget class="QLabel" name="label">
<property name="text">
<string>
&lt;div&gt;
&lt;b&gt;For debugging only.&lt;/b&gt;
&lt;br&gt;
If you're not sure what these do, keep all of these enabled.
&lt;br&gt;
These settings, when disabled, only take effect when CPU Accuracy is "Debug Mode".
&lt;/div&gt;
</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;For debugging only.&lt;/span&gt;&lt;br/&gt;If you're not sure what these do, keep all of these enabled. &lt;br/&gt;These settings, when disabled, only take effect when CPU Debugging is enabled. &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cpuopt_page_tables">
<property name="text">
<string>Enable inline page tables</string>
</property>
<property name="toolTip">
<string>
&lt;div style="white-space: nowrap"&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
&lt;div style="white-space: nowrap"&gt;Enabling it inlines accesses to PageTable::pointers into emitted code.&lt;/div&gt;
&lt;div style="white-space: nowrap"&gt;Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.&lt;/div&gt;
&lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
&lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it inlines accesses to PageTable::pointers into emitted code.&lt;/div&gt;
&lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.&lt;/div&gt;
</string>
</property>
<property name="text">
<string>Enable inline page tables</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cpuopt_block_linking">
<property name="text">
<string>Enable block linking</string>
</property>
<property name="toolTip">
<string>
&lt;div&gt;This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.&lt;/div&gt;
</string>
</property>
<property name="text">
<string>Enable block linking</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cpuopt_return_stack_buffer">
<property name="text">
<string>Enable return stack buffer</string>
</property>
<property name="toolTip">
<string>
&lt;div&gt;This optimization avoids dispatcher lookups by keeping track potential return addresses of BL instructions. This approximates what happens with a return stack buffer on a real CPU.&lt;/div&gt;
</string>
</property>
<property name="text">
<string>Enable return stack buffer</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cpuopt_fast_dispatcher">
<property name="text">
<string>Enable fast dispatcher</string>
</property>
<property name="toolTip">
<string>
&lt;div&gt;Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.&lt;/div&gt;
</string>
</property>
<property name="text">
<string>Enable fast dispatcher</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cpuopt_context_elimination">
<property name="text">
<string>Enable context elimination</string>
</property>
<property name="toolTip">
<string>
&lt;div&gt;Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.&lt;/div&gt;
</string>
</property>
<property name="text">
<string>Enable context elimination</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cpuopt_const_prop">
<property name="text">
<string>Enable constant propagation</string>
</property>
<property name="toolTip">
<string>
&lt;div&gt;Enables IR optimizations that involve constant propagation.&lt;/div&gt;
</string>
</property>
<property name="text">
<string>Enable constant propagation</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cpuopt_misc_ir">
<property name="text">
<string>Enable miscellaneous optimizations</string>
</property>
<property name="toolTip">
<string>
&lt;div&gt;Enables miscellaneous IR optimizations.&lt;/div&gt;
</string>
</property>
<property name="text">
<string>Enable miscellaneous optimizations</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cpuopt_reduce_misalign_checks">
<property name="text">
<string>Enable misalignment check reduction</string>
</property>
<property name="toolTip">
<string>
&lt;div style="white-space: nowrap"&gt;When enabled, a misalignment is only triggered when an access crosses a page boundary.&lt;/div&gt;
&lt;div style="white-space: nowrap"&gt;When disabled, a misalignment is triggered on all misaligned accesses.&lt;/div&gt;
&lt;div style=&quot;white-space: nowrap&quot;&gt;When enabled, a misalignment is only triggered when an access crosses a page boundary.&lt;/div&gt;
&lt;div style=&quot;white-space: nowrap&quot;&gt;When disabled, a misalignment is triggered on all misaligned accesses.&lt;/div&gt;
</string>
</property>
<property name="text">
<string>Enable misalignment check reduction</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cpuopt_fastmem">
<property name="text">
<string>Enable Host MMU Emulation</string>
</property>
<property name="toolTip">
<string>
&lt;div style="white-space: nowrap"&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
&lt;div style="white-space: nowrap"&gt;Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU.&lt;/div&gt;
&lt;div style="white-space: nowrap"&gt;Disabling this forces all memory accesses to use Software MMU Emulation.&lt;/div&gt;
&lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
&lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU.&lt;/div&gt;
&lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all memory accesses to use Software MMU Emulation.&lt;/div&gt;
</string>
</property>
<property name="text">
<string>Enable Host MMU Emulation</string>
</property>
</widget>
</item>
</layout>

View File

@@ -31,20 +31,23 @@ void ConfigureDebug::SetConfiguration() {
const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn();
ui->toggle_console->setEnabled(runtime_lock);
ui->toggle_console->setChecked(UISettings::values.show_console);
ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter));
ui->homebrew_args_edit->setText(QString::fromStdString(Settings::values.program_args));
ui->toggle_console->setChecked(UISettings::values.show_console.GetValue());
ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter.GetValue()));
ui->homebrew_args_edit->setText(
QString::fromStdString(Settings::values.program_args.GetValue()));
ui->fs_access_log->setEnabled(runtime_lock);
ui->fs_access_log->setChecked(Settings::values.enable_fs_access_log);
ui->reporting_services->setChecked(Settings::values.reporting_services);
ui->quest_flag->setChecked(Settings::values.quest_flag);
ui->use_debug_asserts->setChecked(Settings::values.use_debug_asserts);
ui->use_auto_stub->setChecked(Settings::values.use_auto_stub);
ui->fs_access_log->setChecked(Settings::values.enable_fs_access_log.GetValue());
ui->reporting_services->setChecked(Settings::values.reporting_services.GetValue());
ui->quest_flag->setChecked(Settings::values.quest_flag.GetValue());
ui->use_debug_asserts->setChecked(Settings::values.use_debug_asserts.GetValue());
ui->use_auto_stub->setChecked(Settings::values.use_auto_stub.GetValue());
ui->enable_graphics_debugging->setEnabled(runtime_lock);
ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug);
ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug.GetValue());
ui->enable_cpu_debugging->setEnabled(runtime_lock);
ui->enable_cpu_debugging->setChecked(Settings::values.cpu_debug_mode.GetValue());
ui->disable_macro_jit->setEnabled(runtime_lock);
ui->disable_macro_jit->setChecked(Settings::values.disable_macro_jit);
ui->extended_logging->setChecked(Settings::values.extended_logging);
ui->disable_macro_jit->setChecked(Settings::values.disable_macro_jit.GetValue());
ui->extended_logging->setChecked(Settings::values.extended_logging.GetValue());
}
void ConfigureDebug::ApplyConfiguration() {
@@ -57,11 +60,12 @@ void ConfigureDebug::ApplyConfiguration() {
Settings::values.use_debug_asserts = ui->use_debug_asserts->isChecked();
Settings::values.use_auto_stub = ui->use_auto_stub->isChecked();
Settings::values.renderer_debug = ui->enable_graphics_debugging->isChecked();
Settings::values.cpu_debug_mode = ui->enable_cpu_debugging->isChecked();
Settings::values.disable_macro_jit = ui->disable_macro_jit->isChecked();
Settings::values.extended_logging = ui->extended_logging->isChecked();
Debugger::ToggleConsole();
Common::Log::Filter filter;
filter.ParseFilterString(Settings::values.log_filter);
filter.ParseFilterString(Settings::values.log_filter.GetValue());
Common::Log::SetGlobalFilter(filter);
}

Some files were not shown because too many files have changed in this diff Show More