Compare commits

...

488 Commits

Author SHA1 Message Date
bunnei
7fb7540d69 input_common: Add more missing [[maybe_unused]] from #4927. 2020-11-23 20:50:35 -08:00
bunnei
d04abd39eb Fix warnings in core/frontend/input.h with [[maybe_unused]]
Fixes build break due to #4927
2020-11-23 18:11:21 -08:00
bunnei
e371d12af6 Merge pull request #4927 from lioncash/input-error
input_common: Treat warnings as errors
2020-11-23 17:25:53 -08:00
bunnei
5d1447897a Merge pull request #4451 from slashiee/extended-logging
logging/settings: Increase maximum log size to 100 MB and add extended logging option
2020-11-23 13:34:15 -08:00
bunnei
2b05c32343 Merge pull request #4969 from liushuyu/master
CI: move refreshenv to the configure step
2020-11-22 20:27:16 -08:00
liushuyu
b546640c41 CI: move refreshenv to the configure step...
... so that cmake can find the Vulkan SDK binaries
2020-11-22 16:19:34 -07:00
Lioncash
5c4774e8ce input_common: Treat warnings as errors
Migrates over warnings as errors for input common to match how the
common library treats warnings as errors.
2020-11-22 04:50:03 -05:00
bunnei
3a85bc1e77 Merge pull request #4944 from lioncash/system-rem
patch_manager: Remove usages of the global system instance
2020-11-21 22:12:34 -08:00
Morph
e13a91fa9b Merge pull request #4954 from lioncash/compare
gl_rasterizer: Make floating-point literal a float
2020-11-22 09:55:23 +08:00
bunnei
5502f39125 Merge pull request #4955 from lioncash/move3
async_shaders: std::move data within QueueVulkanShader()
2020-11-21 01:21:08 -08:00
Rodrigo Locatti
ba3dd7b78f Merge pull request #4960 from liushuyu/master
ci: install Vulkan SDK in MSVC build
2020-11-21 03:47:17 -03:00
bunnei
afd0e2ee87 Merge pull request #4907 from ogniK5377/nvdrv-cleanup
core: Make nvservices more standardized
2020-11-20 22:15:44 -08:00
liushuyu
185bf3fd28 ci: install Vulkan SDK in MSVC build 2020-11-20 23:01:59 -07:00
LC
d88baa746b Merge pull request #4957 from ReinUsesLisp/alpha-test-rt
gl_rasterizer: Remove warning of untested alpha test
2020-11-20 21:19:06 -05:00
ReinUsesLisp
acc14d233f gl_rasterizer: Remove warning of untested alpha test
Alpha test has been proven to only affect the first render target.
2020-11-20 23:17:40 -03:00
bunnei
b00f4abe36 Merge pull request #4953 from lioncash/shader-shadow
shader_bytecode: Eliminate variable shadowing
2020-11-20 16:58:14 -08:00
bunnei
c47c3d723f Merge pull request #4951 from bunnei/olsc-stub
hle: service: Stub OLSC Initialize and SetSaveDataBackupSettingEnabled functions.
2020-11-20 14:06:37 -08:00
bunnei
3794c91145 olsc: Move member initialization to after member functions. 2020-11-20 10:50:50 -08:00
Lioncash
01db5cf203 async_shaders: emplace threads into the worker thread vector
Same behavior, but constructs the threads in place instead of moving
them.
2020-11-20 04:46:56 -05:00
Lioncash
ba3916fc67 async_shaders: Simplify implementation of GetCompletedWork()
This is equivalent to moving all the contents and then clearing the
vector. This avoids a redundant allocation.
2020-11-20 04:44:44 -05:00
Lioncash
3fcc98e11a async_shaders: Simplify moving data into the pending queue 2020-11-20 04:41:29 -05:00
Lioncash
5b441fa25d async_shaders: std::move data within QueueVulkanShader()
Same behavior, but avoids redundant copies.

While we're at it, we can simplify the pushing of the parameters into
the pending queue.
2020-11-20 04:38:18 -05:00
Lioncash
8469b76630 gl_rasterizer: Make floating-point literal a float
Gets rid of an unnecessary expansion from float to double.
2020-11-20 04:24:33 -05:00
Lioncash
b7cd5d742e shader_bytecode: Make use of [[nodiscard]] where applicable
Ensures that all queried values are made use of.
2020-11-20 02:20:37 -05:00
Lioncash
56ecafc204 shader_bytecode: Eliminate variable shadowing 2020-11-20 02:13:45 -05:00
Morph
715f0c3b0c Merge pull request #4941 from lioncash/config
configure_input_player: Use static qualifier for IsProfileNameValid()
2020-11-20 14:16:01 +08:00
LC
bba7e8ea4b Merge pull request #4950 from german77/RumbleStrenght
Modify rumble amplification
2020-11-20 00:40:09 -05:00
LC
e883101999 Merge pull request #4952 from ReinUsesLisp/bit-cast
common/bit_cast: Add function matching std::bit_cast without constexpr
2020-11-20 00:39:30 -05:00
Rodrigo Locatti
1889b641d9 Merge pull request #4308 from ReinUsesLisp/maxwell-3d-funcs
maxwell_3d: Move code to separate functions and insert instead of push_back
2020-11-20 01:57:22 -03:00
ReinUsesLisp
3f2e605dd1 common/bit_cast: Add function matching std::bit_cast without constexpr
Add a std::bit_cast-like function archiving the same runtime results as
the standard function, without compile time support.

This allows us to use bit_cast while we wait for compiler support, it
can be trivially replaced in the future.
2020-11-20 01:52:37 -03:00
bunnei
6971d08893 Merge pull request #4948 from lioncash/page-resize
virtual_buffer: Do nothing on resize() calls with same sizes
2020-11-19 12:39:38 -08:00
bunnei
6e37676482 hle: service: Stub OLSC Initialize and SetSaveDataBackupSettingEnabled functions.
- Used by Animal Cross: New Horizons v1.6.0 update, minimal stub gets this update working.
2020-11-19 12:36:09 -08:00
german77
5b6545b141 Modify rumble amplification 2020-11-19 11:30:52 -06:00
Lioncash
412044960a virtual_buffer: Do nothing on resize() calls with same sizes
Prevents us from churning memory by freeing and reallocating a memory
block that would have already been adequate as is.
2020-11-19 07:54:03 -05:00
bunnei
92344da20c Merge pull request #4936 from lioncash/page
page_table: Allow page tables to be moved
2020-11-18 20:40:10 -08:00
Lioncash
6f8a06bac5 patch_manager: Remove usages of the global system instance
With this, only 19 usages of the global system instance remain within
the core library.

We're almost there.
2020-11-18 09:36:48 -05:00
Lioncash
be4fc777c0 configure_input_player: Use static qualifier for IsProfileNameValid()
This is a static member function, so we don't need use an existing
instance to call this function.
2020-11-17 23:12:44 -05:00
bunnei
abda366362 Merge pull request #4866 from Morph1984/mjolnir-p3-prod
Project Mjölnir: Part 3 - Controller Profiles and Vibration Rework
2020-11-17 20:02:27 -08:00
Lioncash
0ca91ced2d virtual_buffer: Add compile-time type-safety guarantees with VirtualBuffer
VirtualBuffer makes use of VirtualAlloc (on Windows) and mmap() (on
other platforms). Neither of these ensure that non-trivial objects are
properly constructed in the allocated memory.

To prevent potential undefined behavior occurring due to that, we can
add a static assert to loudly complain about cases where that is done.
2020-11-17 20:09:58 -05:00
Lioncash
b3c8997829 page_table: Allow page tables to be moved
Makes page tables and virtual buffers able to be moved, but not copied,
making the interface more flexible.

Previously, with the destructor specified, but no move assignment or
constructor specified, they wouldn't be implicitly generated.
2020-11-17 20:08:20 -05:00
Lioncash
3cfd962ef4 page_table: Add missing doxygen parameters to Resize()
Resolves two -Wdocumentation warnings.
2020-11-17 19:45:20 -05:00
Lioncash
0890451c55 page_table: Remove unnecessary header inclusions
Prevents indirect inclusions for these headers.
2020-11-17 19:43:27 -05:00
Chloe
2dc9dbb809 Merge pull request #4933 from lioncash/nodisc-gpu
[gpu, render_base, rasterizer_interface]: Make use of [[nodiscard]] where applicable
2020-11-18 01:40:03 +11:00
Lioncash
70812ec57b rasterizer_interface: Make use of [[nodiscard]] where applicable 2020-11-17 07:19:13 -05:00
Lioncash
a78021580d render_base: Make use of [[nodiscard]] where applicable 2020-11-17 07:19:12 -05:00
Lioncash
b928fca114 gpu: Make use of [[nodiscard]] where applicable 2020-11-17 07:19:09 -05:00
bunnei
8ace3959a5 Merge pull request #4929 from lioncash/nodiscard-input
motion_input: Mark member functions as [[nodiscard]] where applicable
2020-11-16 21:40:16 -08:00
Morph
e7e8a87927 sdl_impl: Pump SDL Events at 1000 Hz 2020-11-15 23:33:21 -05:00
Morph
b254d528bc configure_input: Accommodate for the mouse input device engine 2020-11-15 23:33:21 -05:00
Morph
ad50209383 hid: Reimplement Begin/EndPermitVibrationSession
Upon further investigation, these commands allow temporary vibrations even when the "Controller Vibration" system setting is disabled. As a result, vibrations are allowed when either the system setting or this flag is set to true. Therefore, we can only block vibrations when both flags are set to false.
2020-11-15 23:33:21 -05:00
Morph
d8ad2f3484 controllers/npad: Load input devices on init 2020-11-15 23:33:21 -05:00
Morph
6f5b942897 configure_input: Update the input profiles for other player tabs 2020-11-15 23:33:21 -05:00
Morph
97b2220a82 general: Fix compiler warnings on linux and miscellaneous changes 2020-11-15 23:33:21 -05:00
Morph
117bdc71e0 sdl_impl: Revert to the "old" method of mapping sticks
Not all controllers have a SDL_GameController binding. This caused controllers not present in the SDL GameController database to have buttons mapped instead of axes.

Furthermore, it was not possible to invert the axes when it could be useful such as emulating a horizontal single joycon or other potential cases. This allows us to invert the axes by reversing the order of mapping (vertical, then horizontal).
2020-11-15 23:33:21 -05:00
Morph
760a9e8693 applets/controller: Change the input button to create input profiles
Co-authored-by: Its-Rei <kupfel@gmail.com>
2020-11-15 23:33:21 -05:00
Morph
30e0d1c973 controllers/npad: Remove the old vibration filter
Previously we used a vibration filter that filters out amplitudes close to each other. It turns out there are cases where this results into vibrations that are too inaccurate. Remove this and move the 100Hz vibration filter (Only allowing a maximum of 100 vibrations per second) from sdl_impl to npad when enable_accurate_vibrations is set to false.
2020-11-15 23:33:21 -05:00
Morph
91c06dae1a input: Disconnect a controller prior to connecting a new one
Some games do not respond to a change in controller type if 1) The controller is not disconnected prior to being reconnected and/or 2) The controller is reconnected instantly after being disconnected.

Since it is not possible to change controllers instantly on hardware and requiring a disconnect prior to connecting a new one, we should emulate this as well with a small delay, fixing the aforementioned issue.
2020-11-15 23:33:21 -05:00
Morph
978ca65f59 hid: Implement InitializeVibrationDevice and IsVibrationDeviceMounted 2020-11-15 23:33:20 -05:00
Morph
e9e1876e82 input_common: Add VibrationDevice and VibrationDeviceFactory
A vibration device is an input device that returns an unsigned byte as status.
It represents whether the vibration device supports vibration or not.
If the status returns 1, it supports vibration. Otherwise, it does not support vibration.
2020-11-15 23:33:20 -05:00
Morph
38110dd485 configure_input: Add per-player vibration
Allows for enabling and modifying vibration and vibration strength per player.
Also adds a toggle for enabling/disabling accurate vibrations.

Co-authored-by: Its-Rei <kupfel@gmail.com>
2020-11-15 23:33:20 -05:00
Morph
d6a41cfc21 settings: Remove global vibration strength modifier
This will be replaced in favor of per-player vibration strength modifiers.
2020-11-15 23:33:20 -05:00
Morph
92fa5257c7 hid: Mark Begin/EndPermitVibrationSession as stubs
The implementation of these commands seem incomplete and causes rumble in Super Mario Party to stop working since only EndPermitVibrationSession is called. Thus, these are better off being marked as a stub until this can be investigated more thoroughly.
2020-11-15 23:33:20 -05:00
Morph
373408ae8c controllers/npad: Send an empty vibration on destruction/deactivation
This stops all controllers from continuously vibrating when emulation is stopped.
2020-11-15 23:33:20 -05:00
Morph
70f16f1722 hid: Stub IsVibrationDeviceMounted
- Used in Super Mario Odyssey
2020-11-15 23:33:20 -05:00
Morph
9b501af8e3 controllers/npad: Add heuristics to reduce rumble state changes
Sending too many state changes in a short period of time can cause massive performance issues.
As a result, we have to use several heuristics to reduce the number of state changes to minimize/eliminate this performance impact while maintaining the quality of these vibrations as much as possible.
2020-11-15 23:33:20 -05:00
Morph
652d6766d5 configure_input: Hook up the vibration percentage spinbox
This allows setting the vibration strength percentage anywhere from 1% to 100%.
Also hooks up the remaining motion button and checkbox in the Controller Applet.
2020-11-15 23:33:20 -05:00
Morph
e02ef3c3be controllers/npad: Stop games from vibrating incorrect controllers
Fixes vibration in 1-2 Switch and potentially other games where they would vibrate both players' joycons at the same time.
2020-11-15 23:33:20 -05:00
Morph
07b81f57ba hid: Fix controller rumble based on new research
This fixes the issue where rumble is only sent to the first controller.
Now, individual controllers can receive their own rumble commands.
2020-11-15 23:33:20 -05:00
Morph
31de52513e hid: Pop a struct of parameters instead of popping individual parameters
Some parameters need to be doubleword aligned due to the presence of the applet_resource_user_id.
Previously, this value was invalid in many commands where it was not doubleword aligned when popped.
2020-11-15 23:33:20 -05:00
Morph
e3c2749986 hid: Reorder all HID commands
Reorders all HID commands in command id order.
2020-11-15 23:33:20 -05:00
Morph
b92bf51ae1 hid: Implement GetVibrationDeviceInfo
The first u32 describes the vibration device type which is a Linear Resonant Actuator used in Nintendo Switch controller hardware.

The second u32 describes the vibration device position, in this case distinguishing between left and right vibration actuators.

Pro Controllers have 2 LRAs each that can vibrate independently of each other, which means they have 2 distinct vibration device handles to distinguish between the two actuators.

Similarly for joycons, the left joycon can be distinguished from the right joycon through the vibration device handle since each joycon has 1 LRA.
2020-11-15 23:33:20 -05:00
Morph
16e2e1c45f hid: Stub InitializeVibrationDevice 2020-11-15 23:33:20 -05:00
Morph
428ce8ec29 controllers/npad: Rename NPadType to NpadStyleSet
This more accurately represents the underlying type and avoids confusion with NpadType
2020-11-15 23:33:20 -05:00
Morph
0a966e2cac controllers/npad: Add DeviceHandle struct
A DeviceHandle describes a vibration device or six-axis sensor based on the npad type, npad id, and device index/position
2020-11-15 23:33:20 -05:00
Morph
ceb7b11f16 configure_input_player: Change "Defaults" button behavior
RestoreDefaults() now restores the selected devices' mappings using UpdateMappingWithDefaults().
This allows us to move the keyboard mapping from RestoreDefaults() to UpdateMappingWithDefaults().
2020-11-15 23:33:20 -05:00
Morph
8f2959f680 settings: Preparation for per-game input settings 2020-11-15 23:33:20 -05:00
Morph
8ead176639 udp/client: Reduce testing period to 5 seconds 2020-11-15 23:33:19 -05:00
Morph
64e174237e config: Migrate config files into config/custom
Co-authored-by: lat9nq <lat9nq@virginia.edu>
2020-11-15 23:33:19 -05:00
Morph
c0c4ed0d3b controllers/npad: Connect a controller on init if none are connected 2020-11-15 23:33:19 -05:00
Morph
5cafa70d3b applets/controller: Auto accept a valid single player configuration 2020-11-15 23:33:19 -05:00
Morph
484623cd61 bootmanager: Allow mouse clicks only if touch is disabled
Previously mouse clicks will not register when touch is disabled.
This rectifies that and allows mouse clicks to be mapped to other buttons if the touchscreen is disabled.
2020-11-15 23:33:19 -05:00
Morph
57d89e291d input_profiles: Implement input profiles 2020-11-15 23:33:19 -05:00
Morph
75eaab2e0f configure_input_player: Implement input exclusivity and persistence
With this, the "Input Devices" combobox should accurately reflect the input device being used and disallows inputs from other input devices unless the input device is set to "Any".
2020-11-15 23:33:19 -05:00
Morph
9d4edd4e88 ui/themes: Cleanup UI 2020-11-15 23:33:19 -05:00
Lioncash
0a50ba3bd1 motion_input: Mark constructor as explicit 2020-11-15 14:20:41 -05:00
Lioncash
cb826bcee7 motion_input: Mark member functions as [[nodiscard]] where applicable 2020-11-15 14:18:09 -05:00
LC
ce718522bc Merge pull request #4914 from lat9nq/gl-warnings
bootmanager: Log and show GL_RENDERER string when GPU is insufficient
2020-11-15 06:33:48 -05:00
bunnei
87f220efff Merge pull request #4895 from Morph1984/cave-story-plus-applet-fix
applets/controller: Introduce additional checks for mode and caller
2020-11-12 21:55:06 -08:00
ReinUsesLisp
622830f4e1 maxwell_3d: Use insert instead of loop push_back
This reduces the overhead of bounds checking on each element.
It won't reduce the cost of allocation because usually this vector's
capacity is usually large enough to hold whatever we push to it.
2020-11-11 19:52:19 -03:00
ReinUsesLisp
9ea8cffe35 maxwell_3d: Move code to separate functions
Deduplicate some code and put it in separate functions so it's easier to
understand and profile.
2020-11-11 19:52:19 -03:00
bunnei
c22d0d9945 Merge pull request #4901 from bunnei/caps-stub
hle: service: caps_u: Stub GetAlbumFileList3AaeAruid.
2020-11-09 21:20:08 -08:00
Chloe Marcec
fc4d692c50 Addressed issues 2020-11-10 15:57:36 +11:00
Chloe Marcec
31c12de0fe core: Make nvservices more standardized 2020-11-10 15:57:35 +11:00
lat9nq
c433c0a746 bootmanager: Address review comments
Changes QMessageBox usages to warnings, as the problems they bring to
light are being safely handled by the application and do not warrant
something of the "critical" level.

Changes LOG_CRITICAL to LOG_ERROR for the same reason. Preferring ERROR
to WARNING as yuzu is denying loading of any guest applications after
checking for these conditions.

Moved logging the GL_RENDERER string into GetUnsupportedGLExtensions()
to make more clear that unsupported extensions were already being
logged. Makes placement of the logs easier to understand later, as well.
2020-11-09 22:55:05 -05:00
lat9nq
945cfe234b bootmanager: Log and show GL_RENDERER string when GPU is insufficient
Changes the first message to not include the OpenGL version, as the
error is caused by OpenGL failing to load.

Adds a new check for OpenGL version 4.3. This will display a message
with a similar error as well as the GL_RENDERER string. Adds a CRITICAL
log message when triggered. This prevents a crash with yuzu trying to
use older OpenGL versions.

Modifies the unsupported extension message to output the GL_RENDERER
string in the message, as well as logging the string.
2020-11-09 22:12:41 -05:00
Rodrigo Locatti
9b24197ca0 Merge pull request #4909 from lioncash/interrupt
cpu_interrupt_handler: Mark move contructor/assignment as deleted
2020-11-08 22:09:40 -03:00
Rodrigo Locatti
8008b5ddc9 Merge pull request #4910 from lioncash/service
ipc_helpers: Remove usage of the global system instance
2020-11-08 19:11:31 -03:00
Lioncash
da7be67daf ipc_helpers: Remove usage of the global system instance
Resolves numerous deprecation warnings throughout the codebase due to
inclusion of this header. Now building core should be significantly less
noisy (and also relying on less global state).

This also uncovered quite a few modules that were relying on indirect
includes, which have also been fixed.
2020-11-08 15:58:11 -05:00
Lioncash
0aad914527 cpu_interrupt_handler: Mark move contructor/assignment as deleted
The interrupt handler contains a std::atomic_bool, which isn't copyable
or movable, so the special move member functions will always be deleted,
despite being defaulted.

This can resolve warnings on clang and GCC.
2020-11-08 15:37:04 -05:00
Morph
a6ecdf42bc applets: Rename LibraryAppletVersion to ControllerAppletVersion 2020-11-08 10:04:12 -05:00
Morph
9efbf5309f applets/controller: Pop normal data for StrapGuide and FirmwareUpdate 2020-11-08 09:35:25 -05:00
Morph
af1183a993 applets/controller: Introduce additional checks for mode and caller
Some games like Cave Story+ set invalid values in the ControllerPrivateArg's mode and caller fields.
Use other fields to determine the appropriate mode and caller should either or both fields be invalid.
2020-11-08 09:35:25 -05:00
Morph
88192af8ac applets/controller: Add ControllerUpdateFirmwareArg struct 2020-11-08 09:35:25 -05:00
bunnei
7bf9f9ae49 Merge pull request #4903 from bunnei/remove-gpu-integrity
video_core: dma_pusher: Remove integrity check on command lists.
2020-11-08 02:48:22 -08:00
Chloe
9f5facc3aa Merge pull request #4908 from lioncash/fmt
externals: Update fmt to 7.1.2
2020-11-08 20:26:03 +11:00
Lioncash
0785796372 externals: Update fmt to 7.1.2
Updates to the latest bugfix release of fmt.
2020-11-08 03:44:07 -05:00
LC
e829973742 Merge pull request #4906 from lat9nq/log-cpu-accuracy
settings: log value of CPU_Accuracy
2020-11-07 17:01:33 -05:00
lat9nq
1e149dc18b settings: log value of CPU_Accuracy 2020-11-07 16:14:10 -05:00
bunnei
dc5396a466 video_core: dma_pusher: Remove integrity check on command lists.
- This seems to cause softlocks in Breath of the Wild.
2020-11-07 00:08:19 -08:00
bunnei
af477fb8c5 Merge pull request #4888 from lioncash/unicorn-remove
core: Remove usage of unicorn
2020-11-06 22:39:05 -08:00
bunnei
a0d7a2732d hle: service: caps_u: Stub GetAlbumFileList3AaeAruid.
- This works similiar to GetAlbumContentsFileListForApplication.
- Since we do not implement the album, this should be safe to stub for now.
- Used by Super Smash Bros. Ultimate (newer updates) in World of Light.
2020-11-06 22:23:15 -08:00
bunnei
f6a89edb67 Merge pull request #4899 from lioncash/fiberimpl
common/fiber: Move all member variables into impl class
2020-11-06 20:01:03 -08:00
Lioncash
00fb79b2f3 common/fiber: Move all member variables into impl class
Hides all of the implementation details for users of the class. This has
the benefit of reducing includes and also making the fiber classes
movable again.
2020-11-06 20:36:32 -05:00
bunnei
91a45834fd Merge pull request #4891 from lioncash/clang2
General: Fix clang build
2020-11-06 10:33:13 -08:00
bunnei
0b75ec5316 Merge pull request #4894 from lioncash/fn
settings: Simplify initializer of resolution factor
2020-11-06 09:54:02 -08:00
Lioncash
c0ab5b79dc settings: Simplify initializer of resolution factor
This can use a braced initializer to accomplish the same thing with less
code.
2020-11-05 22:07:10 -05:00
bunnei
a111a9ae2c Merge pull request #4854 from ReinUsesLisp/cube-array-shadow
shader: Partially implement texture cube array shadow
2020-11-05 16:25:00 -08:00
Lioncash
6f006d051e General: Fix clang build
Allows building on clang to work again
2020-11-05 10:07:16 -05:00
bunnei
d62d28522b Merge pull request #4889 from lioncash/setting-global
core/settings: Move configuring_global behind an API
2020-11-04 17:09:19 -08:00
bunnei
087f52e872 Merge pull request #4858 from lioncash/initializer
General: Resolve a few missing initializer warnings
2020-11-04 12:10:10 -08:00
Lioncash
7aae6d6d2b core/settings: Move configuring_global behind an API
Rather than have directly modified global state here, we can make it an
implementation detail and have an interface that changes are queried
through.
2020-11-04 04:16:37 -05:00
Chloe
6bbbbe8f85 Merge pull request #4869 from bunnei/improve-gpu-sync
Improvements to GPU synchronization & various refactoring
2020-11-04 18:36:55 +11:00
Lioncash
fc6db97a09 core: Remove usage of unicorn
Unicorn long-since lost most of its use, due to dynarmic gaining support
for handling most instructions. At this point any further issues
encountered should be used to make dynarmic better.

This also allows us to remove our dependency on Python.
2020-11-03 20:22:05 -05:00
bunnei
4bfa411ddc Merge pull request #4874 from lioncash/nodiscard2
nvdec: Make use of [[nodiscard]] where applicable
2020-11-03 16:34:07 -08:00
bunnei
46fdc94586 Merge pull request #4887 from lioncash/common-build
microprofile: Silence warning in headers
2020-11-03 13:41:29 -08:00
Lioncash
ee21b5378b microprofile: Silence warning in headers
Silences a truncation warning by making the truncation explicit and
documenting the reason for it.
2020-11-03 15:07:13 -05:00
bunnei
222fe75401 Merge pull request #4873 from lioncash/common-error
common: Enable warnings as errors
2020-11-03 11:00:23 -08:00
bunnei
448e4d5c2a Merge pull request #4878 from bunnei/unload-nrr
hle: service: ldr: Implement UnloadNrr.
2020-11-03 08:52:40 -08:00
Lioncash
4a4b685a04 common: Enable warnings as errors
Cleans up common so that we can enable warnings as errors.
2020-11-02 15:50:58 -05:00
Lioncash
4f0f481f63 nvdec: Make use of [[nodiscard]] where applicable
Prevents bugs from occurring where the results of a function are
accidentally discarded
2020-11-02 02:45:15 -05:00
bunnei
1089d76736 Merge pull request #4865 from ameerj/async-threadcount
async_shaders: Increase Async worker thread count for >8 thread cpus
2020-11-01 01:54:01 -07:00
bunnei
848bdf8a40 fixup! hle service: nvdrv: nvhost_gpu: Update to use SyncpointManager and other improvements. 2020-11-01 01:52:38 -07:00
bunnei
7d2839d7a3 core: Initialize GPU before services. 2020-11-01 01:52:38 -07:00
bunnei
e67b8678f8 hle service: nvdrv: nvhost_gpu: Update to use SyncpointManager and other improvements.
- Refactor so that SubmitGPFIFO and KickoffPB use shared functionality.
- Implement add_wait and add_increment flags.
2020-11-01 01:52:38 -07:00
bunnei
c6e1c46ac7 video_core: dma_pusher: Add support for integrity checks.
- Log corrupted command lists, rather than crash.
2020-11-01 01:52:38 -07:00
bunnei
c64545d07a video_core: dma_pusher: Add support for prefetched command lists. 2020-11-01 01:52:38 -07:00
bunnei
1d4cbb92f2 service: hle: nvflinger: Fix potential shutdown crash when GPU is destroyed. 2020-11-01 01:52:38 -07:00
bunnei
6053b95552 video_core: gpu: Implement WaitFence and IncrementSyncPoint. 2020-11-01 01:52:37 -07:00
bunnei
66edfd61c6 hle service: nvdrv: nvhost_ctrl: Update to use SyncpointManager. 2020-11-01 01:52:37 -07:00
bunnei
4a3fd97e48 hle service: nvdrv: Update to instantiate SyncpointManager. 2020-11-01 01:52:34 -07:00
bunnei
d567b7e841 hle: service: nvdrv: Implement SyncpointManager, to manage syncpoints. 2020-11-01 01:51:54 -07:00
Levi Behunin
bca9591660 Rename to align with switchbrew and remove gpu function (#4714)
* Rename to align with switchbrew

* Rename to align with switchbrew and remove gpu function that checks if clearing should be done.
2020-11-01 01:24:17 -07:00
bunnei
98f68d06f1 Merge pull request #4853 from ReinUsesLisp/fcmp-imm
shader/arithmetic: Implement FCMP immediate + register variant
2020-10-31 01:25:02 -07:00
bunnei
a0e5cccb92 hle: service: ldr: Implement UnloadNrr.
- Used by Final Fantasy X/X-2 HD Remaster.
2020-10-31 01:22:53 -07:00
LC
6db0c0d8d9 Merge pull request #4872 from jbeich/clang
video_core: unbreak -Werror in NVDEC with Clang
2020-10-30 15:11:40 -04:00
Lioncash
14a97d082e CMakeLists: Resolve MSVC build failures
Prevents the compiler tripping up about Windows headers.
2020-10-30 14:57:58 -04:00
Jan Beich
50e52ade85 video_core: unbreak -Werror in NVDEC with Clang
src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp:41:15: error: unused variable 'OutOfMemory' [-Werror,-Wunused-const-variable]
constexpr u32 OutOfMemory{static_cast<u32>(-12)};
              ^
2020-10-30 16:43:10 +00:00
bunnei
8aa9ae5ba5 Merge pull request #4868 from lioncash/discard-error
General: Make ignoring a discarded return value an error
2020-10-30 00:35:40 -07:00
bunnei
131a75b65d Merge pull request #4867 from lioncash/vp9
VP9: Minor interface changes and safety improvements
2020-10-29 21:33:27 -07:00
Lioncash
11d0a6e7b8 General: Catch more expressions with no effect on MSVC
MSVC lets us fine-tune catching expressions with no side-effects a
little more.
2020-10-30 00:13:26 -04:00
Lioncash
26547d3e3b General: Make ignoring a discarded return value an error
Allows our CI to catch more potential bugs. This also removes the
[[nodiscard]] attribute of IOFile's Open member function. There are
cases where a file may want to be opened, but have the status of it
checked at a later time.
2020-10-30 00:13:21 -04:00
Lioncash
8049b8beb6 common/stream: Be explicit with copy and move operators 2020-10-29 22:57:35 -04:00
Lioncash
12eeffcb7c vp9: Be explicit with copy and move operators
It's deprecated in the language to autogenerate these if the destructor
for a type is specified, so we can explicitly specify how we want these
to be generated.
2020-10-29 22:57:35 -04:00
Lioncash
0d713cf8eb vp9: Mark functions with [[nodiscard]] where applicable
Prevents values from mistakenly being discarded in cases where it's a
bug to do so.
2020-10-29 22:57:32 -04:00
Lioncash
badea3b301 vp9: Provide a default initializer for "hidden" member
The API of VP9 exposes a WasFrameHidden() function which accesses this
member. Given the constructor previously didn't initialize this member,
it's a potential vector for an uninitialized read.

Instead, we can initialize this to a deterministic value to prevent that
from occurring.
2020-10-29 22:35:55 -04:00
Lioncash
f8543249f0 vp9: Make some member functions internally linked
These helper functions don't directly modify any member state and can be
hidden from view.
2020-10-29 22:34:46 -04:00
Lioncash
5553bd3ba2 General: Resolve a few missing initializer warnings
Resolves a few -Wmissing-initializer warnings.
2020-10-29 19:37:07 -04:00
bunnei
7dcf4c0018 Merge pull request #4831 from lioncash/fmt
externals: Update fmt to 7.1.0
2020-10-29 14:44:07 -07:00
bunnei
ef29bf4515 Merge pull request #4837 from lioncash/nvdec-2
nvdec: Minor tidying up
2020-10-29 12:28:07 -07:00
ameerj
3620206136 async_shaders: Increase Async worker thread count for 8+ thread cpus
Adds 1 async worker thread for every 2 available threads above 8
2020-10-29 14:16:45 -04:00
bunnei
2dbb144fc6 Merge pull request #4781 from german77/GChotplug
Add hotplug, rumble and fix 3rd party adapters for the GC adapter
2020-10-29 10:28:19 -07:00
David
89199ca215 Merge pull request #4859 from Morph1984/missing-ctime-include
kernel/process: Add missing <ctime> include
2020-10-29 19:03:19 +11:00
Morph
9cfc5fee2f kernel/process: Add missing <ctime> include
Fixes compilation on MSVC
2020-10-29 03:17:20 -04:00
LC
1a6b1bf1d7 Merge pull request #4857 from liushuyu/master
web_service: follow-up fix to #4842
2020-10-29 01:54:45 -04:00
bunnei
c5134cbf3a Merge pull request #4835 from lat9nq/rng-default-time
kernel: Use the current time as the default RNG seed
2020-10-28 22:51:29 -07:00
bunnei
c6d001c94f Merge pull request #4838 from lioncash/syncmgr
sync_manager: Amend parameter order of calls to SyncptIncr constructor
2020-10-28 22:49:22 -07:00
liushuyu
cf63eacc1a web_service: follow-up fix to #4842 ...
* The web_service http request is now fixed on Windows (R) platform.
* The issue is due to a complicated race-condition in `httplib`, a detailed
  explanation is available at https://github.com/yhirose/cpp-httplib/pull/701
* A pending Pull Request on `httplib` has been applied to remedy the
  said race-condition.
* The socket availability check is removed due to a behavioral chice of
  `httplib` that a socket will not be created before any actual request
  is sent.
2020-10-28 23:16:06 -06:00
german
5333db91c1 Add hotplug, rumble and fix 3rd party adapters for the GC adapter 2020-10-28 21:12:34 -05:00
LC
c20569ebdf Merge pull request #4856 from bunnei/webservice-socket-error
web_service: web_backend: Handle socket errors with GenericRequest.
2020-10-28 20:46:28 -04:00
bunnei
156556ddd2 web_service: web_backend: Handle socket errors with GenericRequest.
- Fixes a shutdown crash when we try to submit telemetry if there is a service issue.
2020-10-28 17:19:12 -07:00
LC
475d46bb64 Merge pull request #4855 from bunnei/cdma-pusher-log-fix
video_core: cdma_pusher: Add missing LOG_DEBUG field in ExecuteCommand.
2020-10-28 20:01:29 -04:00
bunnei
94eca09cf6 video_core: cdma_pusher: Add missing LOG_DEBUG field in ExecuteCommand. 2020-10-28 16:47:08 -07:00
bunnei
7af2cb4318 Merge pull request #4846 from lioncash/service-fn
service: Update function tables
2020-10-28 13:47:56 -07:00
ReinUsesLisp
657771bdcb shader: Partially implement texture cube array shadow
This implements texture cube arrays with shadow comparisons but doesn't
fix the asserts related to it.

Fixes out of bounds reads on swizzle constructors and makes them use
bounds checked ::at instead of the unsafe operator[].
2020-10-28 17:12:40 -03:00
ReinUsesLisp
44b552be71 shader/arithmetic: Implement FCMP immediate + register variant
Trivially add the encoding for this.
2020-10-28 17:05:41 -03:00
bunnei
663e221f99 Merge pull request #4845 from lioncash/inih
externals: Track upstream inih
2020-10-28 09:58:58 -07:00
LC
725fcbb368 Merge pull request #4851 from ReinUsesLisp/core-threads-race
hle/kernel: Remove unused registered_core_threads to fix data races
2020-10-28 04:54:35 -04:00
LC
a1f176ce52 Merge pull request #4850 from ReinUsesLisp/fiber-ptr-ref
common/fiber: Take shared_ptr<Fiber> by copy in YieldTo
2020-10-28 04:54:19 -04:00
LC
1fd22823bc Merge pull request #4849 from ReinUsesLisp/fix-fiber-test
tests: Fix data race in fibers test
2020-10-28 04:26:10 -04:00
LC
978e7897a3 Merge pull request #4848 from ReinUsesLisp/type-limits
video_core: Enforce -Werror=type-limits
2020-10-28 03:16:10 -04:00
LC
55ac6f7a2b Merge pull request #4847 from ReinUsesLisp/warn-move
video_core: Enforce -Wredundant-move and -Wpessimizing-move
2020-10-28 03:14:58 -04:00
ReinUsesLisp
79da90cea8 video_core: Enforce -Wredundant-move and -Wpessimizing-move
Silence three warnings and make them errors to avoid introducing more in the future.
2020-10-28 02:44:50 -03:00
ReinUsesLisp
4a451e5849 video_core: Enforce -Werror=type-limits
Silences one warning and avoids introducing more in the future.
2020-10-28 02:37:47 -03:00
ReinUsesLisp
cdb2480d39 common/fiber: Take shared_ptr<Fiber> by copy in YieldTo
YieldTo does not intend to modify the passed shared_ptrs.
Pass it by copy to keep a reference count while this function executes.
2020-10-28 02:02:44 -03:00
ReinUsesLisp
3fdb42e0b4 tests: Fix data race in fibers test
Previous to this commit, the tests were using operator[] from
unordered_map to query elements but this silently inserts empty elements
when they don't exist. If all threads were executed without concurrency,
this wouldn't be an issue, but the same unordered_map could be written
from two threads at the same time. This is a data race and makes some
previously inserted elements invisible for a short period of time,
causing them to insert and return an empty element. This default
constructed element (a zero) was used to index an array of fibers that
asserted when one of them was nullptr, shutting the test session off.

To address this issue, lock on thread id reads and writes. This could be
a shared mutex to allow concurrent reads, but the definition of
std::this_thread::get_id is fuzzy when using non-standard techniques
like fibers. I opted to use a standard mutex.

While we are at it, fix the included headers.
2020-10-28 01:41:24 -03:00
Lioncash
020519def8 service: Update function tables
Updates function tables according to info on SwitchBrew.
2020-10-27 21:19:46 -04:00
Lioncash
9a44c1ea27 externals: Update inih to r52 2020-10-27 19:52:48 -04:00
Lioncash
65e697de59 externals: Track mainline inih project 2020-10-27 19:52:48 -04:00
LC
7d27a7a511 Merge pull request #4842 from liushuyu/fix-web-srv
web_backend: fix a regression introduced in 39c8d18
2020-10-27 19:12:27 -04:00
liushuyu
eb84e0f63a externals: auto detect system OpenSSL 2020-10-27 14:20:20 -06:00
liushuyu
8e673cbb08 web_backend: fix a regression introduced in 39c8d18
* A regression was in 39c8d18 and token verification function was
  broken.
* The reason being `httplib` now requires OpenSSL 1.1+ API while
  LibreSSL 2.x provided OpenSSL 1.0 compatible API.
* The bundled LibreSSL has been updated to 3.2.2 so it now provides
  OpenSSL 1.1 compatible API now.
* Also the path hint has been added so that it will find the correct
  path to the CA certs on *nix systems.
* An option is provided so that *nix system distributions/providers can
  use their own SSL implementations when compiling Yuzu/Citra to
  (hopefully) complies with their maintenance guidelines.
* LURLParse is also removed since `httplib` can handle
  `scheme:host:port` string itself now.
2020-10-27 02:57:19 -06:00
Lioncash
047e77e2f0 sync_manager: Amend parameter order of calls to SyncptIncr constructor
Corrects some cases where the arguments would be incorrectly swapped.
2020-10-27 03:22:57 -04:00
Lioncash
cce14b4cd7 h264: Make WriteUe take a u32
Enforces the type of the desired value in calling code.
2020-10-27 03:21:53 -04:00
Lioncash
6291975731 vp9: std::move buffer within ComposeFrameHeader()
We can move the buffer here to avoid a heap reallocation
2020-10-27 02:27:31 -04:00
Lioncash
00decfbb07 vp9: Remove dead code 2020-10-27 02:26:17 -04:00
Lioncash
111802bbbb vp9: Join declarations with assignments 2020-10-27 02:26:03 -04:00
Lioncash
3b5d5fa86f vp9: Remove pessimizing moves
The move will already occur without std::move.
2020-10-27 02:21:40 -04:00
Lioncash
dcc26c54a5 vp9: Resolve variable shadowing 2020-10-27 02:20:17 -04:00
Lioncash
c04203b786 nvdec: Tidy up header includes
Prevents a few unnecessary inclusions.
2020-10-27 02:16:42 -04:00
bunnei
cd92a94965 Merge pull request #4805 from bunnei/update-defaults
yuzu: settings: Enable multicore, asynch GPU, and assembly shaders by default.
2020-10-26 23:14:09 -07:00
bunnei
941563f981 yuzu: settings: Enable multicore, asynch GPU, and assembly shaders by default.
- In general, this is now the preferred settings for most games.

# Conflicts:
#	src/yuzu/configuration/config.cpp
2020-10-26 23:13:05 -07:00
bunnei
d33399e1f4 Merge pull request #4729 from ameerj/nvdec-prod
video_core: NVDEC Implementation
2020-10-26 23:02:42 -07:00
ReinUsesLisp
ce69ff2890 hle/kernel: Remove unused registered_core_threads to fix data races
This member was only used on asserts and it triggered data races.
Remove it to fix them.
2020-10-27 01:55:39 -03:00
bunnei
c7f32931ee Merge pull request #4832 from bunnei/cpu-manager-microprofile-fix
core: cpu_manager: Add missing call to MicroProfileOnThreadExit().
2020-10-26 21:29:09 -07:00
bunnei
1828f82000 Merge pull request #4833 from bunnei/timezonemanager-explicit
hle: services: TimeZoneContentManager: This can be made explicit.
2020-10-26 21:28:45 -07:00
ameerj
eb67a45ca8 video_core: NVDEC Implementation
This commit aims to implement the NVDEC (Nvidia Decoder) functionality, with video frame decoding being handled by the FFmpeg library.

The process begins with Ioctl commands being sent to the NVDEC and VIC (Video Image Composer) emulated devices. These allocate the necessary GPU buffers for the frame data, along with providing information on the incoming video data. A Submit command then signals the GPU to process and decode the frame data.

To decode the frame, the respective codec's header must be manually composed from the information provided by NVDEC, then sent with the raw frame data to the ffmpeg library.

Currently, H264 and VP9 are supported, with VP9 having some minor artifacting issues related mainly to the reference frame composition in its uncompressed header.

Async GPU is not properly implemented at the moment.

Co-Authored-By: David <25727384+ogniK5377@users.noreply.github.com>
2020-10-26 23:07:36 -04:00
bunnei
9f08cea2c4 Merge pull request #4834 from lioncash/copy-fn
controller: Pass ControllerParameters by reference in ReconfigureControllers()
2020-10-26 18:49:26 -07:00
lat9nq
8bd246032a kernel: Use the current time as the default RNG seed
Use the current time, not zero, as the default RNG seed.
2020-10-26 21:42:11 -04:00
Lioncash
6b5f565324 controller: Pass ControllerParameters by reference in ReconfigureControllers()
Prevents unnecessary copies and heap reallocations from occurring.
2020-10-26 21:06:15 -04:00
bunnei
3984bb6def hle: services: TimeZoneContentManager: This can be made explicit. 2020-10-26 16:11:45 -07:00
bunnei
54aabb00b0 core: cpu_manager: Add missing call to MicroProfileOnThreadExit().
- Fixes an occasional crash when trying to launch subsequent games.
2020-10-26 16:09:15 -07:00
Lioncash
1dd4132eb1 externals: Update fmt to 7.1.0
Keeps the used version of the library up to date.
2020-10-26 18:34:44 -04:00
Rodrigo Locatti
2f6ba54483 Merge pull request #4827 from lioncash/trunc
controller: Convert led_patterns integer literals to bool literals
2020-10-25 16:56:30 -03:00
Rodrigo Locatti
ae3a755d13 Merge pull request #4828 from lioncash/lockguard
general: Use template deduction guides for lock_guard
2020-10-25 16:55:54 -03:00
Lioncash
98f4c5e7b8 general: Use template deduction guides for lock_guard
Same behavior, less code.
2020-10-25 13:53:22 -04:00
Lioncash
061a63547f controller: Convert led_patterns integer literals to bool literals
'bool' isn't always guaranteed to be the same size as an int, so this
can technically cause truncation warnings if we support other platforms.
2020-10-25 13:44:12 -04:00
LC
fe53ee26ce Merge pull request #4826 from Morph1984/resolve-warning
applets/profile_select: Resolve a warning in exec()
2020-10-25 11:47:09 -04:00
Morph
9afbcd9e8a applets/profile_select: Resolve a warning in exec()
Resolves a warning where not all control paths return a value.
2020-10-25 09:16:43 -04:00
bunnei
ab052cf684 Merge pull request #4817 from Kewlan/open-single-save-location
main/profile_select: Don't ask for profile when there's only one.
2020-10-24 03:02:19 -07:00
LC
6f6d83befa Merge pull request #4816 from Morph1984/controller-disconnect-fix
sdl_impl: Fix controller reconnection issues
2020-10-23 13:53:51 -04:00
bunnei
3e46934442 Merge pull request #4706 from ReinUsesLisp/cmake-host-shaders
video_core: Fix instances where msbuild always regenerated host shaders
2020-10-23 10:01:16 -07:00
bunnei
e7042163c8 Merge pull request #4792 from bunnei/rtc-fix
service: time: Update current time with changes to RTC setting.
2020-10-22 20:46:54 -07:00
Kewlan
85b5b816cf Don't ask for profile when there's only one. 2020-10-22 11:16:56 +02:00
Lioncash
ea20b5c970 core: Fix clang build pt.3
Should finally resolve building with clang.
2020-10-21 22:14:23 -04:00
Morph
2f852f182a sdl_impl: Fix controller reconnection issues
It turns out that after a controller is disconnected, there is a chance that events from the previous controller are sent/processed after it has been disconnected.
This causes the previously disconnected controller to reappear as connected due to GetSDLJoystickBySDLID() emplacing this controller back to the map.

Fix this by only returning an SDLJoystick if and only if it exists in the map.
2020-10-21 09:41:30 -04:00
bunnei
1fc61d09d3 Merge pull request #4811 from lioncash/warn-video
video_core: Conditially activate relevant compiler warnings
2020-10-20 20:59:37 -07:00
Lioncash
e408bd3b7c core: Fix clang build pt.2
Resolves the clang build issue in a more unintrusive way.
2020-10-20 22:16:03 -04:00
LC
2e74b79e89 Merge pull request #4814 from yuzu-emu/revert-4796-clang
Revert "core: Fix clang build"
2020-10-20 22:08:53 -04:00
bunnei
3d592972dc Revert "core: Fix clang build" 2020-10-20 19:07:39 -07:00
Lioncash
678d012c2c video_core: Conditially activate relevant compiler warnings
These compiler flags aren't shared with clang, so specifying these flags
unconditionally can lead to a bit of warning spam.

While we're in the area, we can also enable -Wunused-but-set-parameter
given this is almost always a bug.
2020-10-20 20:28:25 -04:00
Lioncash
fdd9154069 kernel: Fix build with recent compiler flag changes
This slipped through the cracks due to another change being merged
before the compiler flag changes.
2020-10-20 20:23:18 -04:00
LC
536c51912d Merge pull request #4807 from ReinUsesLisp/glasm-robust-ssbo
gl_arb_decompiler: Implement robust buffer operations
2020-10-20 19:19:38 -04:00
LC
88d5140cf2 Merge pull request #4796 from lioncash/clang
core: Fix clang build
2020-10-20 19:19:12 -04:00
bunnei
940c3bf68d Merge pull request #4390 from ogniK5377/get-applet-inf-stub
nifm: GetAppletInfo stub
2020-10-20 15:59:19 -07:00
LC
ea8345cdcd Merge pull request #4809 from Morph1984/mjolnir-p3
configure_input_player: Fix modifier buttons
2020-10-20 15:00:06 -04:00
bunnei
e03dc4d569 Merge pull request #4627 from Morph1984/fix-dinput-controller-disconnect
sdl_impl: Erase the SDLJoystick entry after removing a controller
2020-10-20 11:40:06 -07:00
Morph
ff82f3894a configure_input_player: Fix modifier buttons
Fix them for real this time, now they finally work.
2020-10-20 14:23:25 -04:00
ReinUsesLisp
f21a189148 gl_arb_decompiler: Implement robust buffer operations
This emulates the behavior we get on GLSL with regular SSBOs with a
pointer + length pair. It aims to be consistent with the crashes we
might get.

Out of bounds stores are ignored. Atomics are ignored and return zero.
Reads return zero.
2020-10-20 03:34:32 -03:00
David Marcec
298b50e220 Added remaining params 2020-10-20 16:45:01 +11:00
David Marcec
acd35e1b60 nifm: GetAppletInfo stub
Fixes crash for Catherine Full Body
2020-10-20 16:42:40 +11:00
bunnei
60bd54776a Merge pull request #4788 from ReinUsesLisp/lockfree-host-thread
kernel: Implement host thread register methods without locking
2020-10-19 22:31:52 -07:00
bunnei
e7a26ecec5 Merge pull request #4785 from Morph1984/fs-hades
filesystem: Fix CreateDirectory and DeleteFile
2020-10-19 16:04:01 -07:00
bunnei
f1ead11df7 Merge pull request #4204 from ReinUsesLisp/vulkan-1.0
renderer_vulkan: Create and properly use Vulkan 1.0 instances when 1.1 is not available
2020-10-19 14:18:54 -07:00
bunnei
598ef6b0b3 Merge pull request #4802 from lioncash/bcat
core: Add boxcat sources with target_sources
2020-10-19 12:40:49 -07:00
bunnei
54b977acaa Merge pull request #4783 from bunnei/nvdrv-freespace
hle: service: nvdrv: Implement nvhost_as_gpu::FreeSpace.
2020-10-18 12:41:18 -07:00
Lioncash
0ab7bfdfce core: Add boxcat sources with target_sources
Same behavior, minus a script variable.
2020-10-18 08:09:06 -04:00
bunnei
2190f1a2b7 Merge pull request #4801 from lioncash/missing-bound
mii/manager: Make use of unused lower bound in GetRandomValue()
2020-10-17 23:32:40 -07:00
bunnei
743fe1aea3 Merge pull request #4782 from ReinUsesLisp/remove-dyn-primitive
vk_graphics_pipeline: Manage primitive topology as fixed state
2020-10-17 22:14:17 -07:00
Lioncash
be1954e04c core: Fix clang build
Recent changes to the build system that made more warnings be flagged as
errors caused building via clang to break.

Fixes #4795
2020-10-17 19:50:39 -04:00
Lioncash
c1577f3448 mii/manager: Make use of unused lower bound in GetRandomValue()
Previously, the lower bound wasn't being used and zero was being used as
the lower bound every time this function was called.

This affects the outcome of some of the randomized entries a little bit,
for example, the lower-bound for beard and mustache flags was supposed
to be 1, not 0.

Aside from these cases, the bug didn't affect anything else.
2020-10-17 09:50:04 -04:00
bunnei
1eb908bc88 Merge pull request #4797 from bunnei/bcat-errors
service: bcat: Check client connection before interacting with socket.
2020-10-16 23:28:40 -07:00
bunnei
cb708631b6 Merge pull request #4798 from lioncash/input-copy
udp/client: Take std::function by const reference with TestCommunication()
2020-10-16 21:20:01 -07:00
bunnei
363c644730 service: bcat: Check client connection before interacting with socket.
- Fixes a crash when BCAT service is offline.
2020-10-16 21:16:56 -07:00
Lioncash
30b1e71066 udp/client: Make use of designated initializers in TestCommunication()
Same behavior, but makes the callback list nicer to look at.
2020-10-16 06:23:51 -04:00
Lioncash
36cfb234d5 udp/client: Take std::function by const reference with TestCommunication()
Avoids redundant copies.
2020-10-16 06:22:29 -04:00
Morph
7b3f5845d2 sdl_impl: Erase the SDLJoystick entry after removing a controller
Previously, disconnecting a controller still leaves a null SDLJoystick entry within the vector of SDLJoysticks mapped by GUID.
When a DirectInput device of the same GUID is reconnected, it adds that device to a new port causing non-detectable input.
Furthermore, opening the "Configure" menu would cause yuzu to crash since it first tries to resolve the name of a null SDLJoystick entry that was not removed.
Resolve this by properly erasing the SDLJoystick entry from the vector.
2020-10-16 00:04:59 -04:00
bunnei
64f967fd49 Merge pull request #4790 from lioncash/input-common
input_common/CMakeLists: Make some warnings errors
2020-10-15 20:59:34 -07:00
bunnei
dbd1662ae2 Merge pull request #4784 from bunnei/cancelbuffer
hle: service: vi: Implement BufferQueue::CancelBuffer.
2020-10-15 19:57:27 -07:00
Lioncash
046c0c91a3 input_common/CMakeLists: Make some warnings errors
Makes the input_common code warnings consistent with the rest of the
codebase.
2020-10-15 19:37:51 -04:00
bunnei
046cc81938 Merge pull request #4793 from bunnei/storeopencontext
service: acc: Stub IManagerForApplication::StoreOpenContext.
2020-10-14 20:42:18 -07:00
bunnei
1d714c8c7f service: acc: Stub IManagerForApplication::StoreOpenContext.
- Used by Super Mario 3D All-Stars.
2020-10-14 20:06:33 -07:00
bunnei
d47ac3ce09 Merge pull request #4772 from goldenx86/block-rdna
vk_device: Block VK_EXT_extended_dynamic_state for RDNA devices
2020-10-14 17:51:39 -07:00
bunnei
1f186f34a2 hle: service: vi: Implement BufferQueue::CancelBuffer.
- This is used by Super Mario 3D All-Stars.
2020-10-13 22:11:52 -07:00
bunnei
ca416a0fb8 Merge pull request #4787 from lioncash/conversion
audio_core/CMakeLists: Make warnings consistent with core
2020-10-13 15:30:30 -07:00
ReinUsesLisp
b9a9b83bee kernel: Implement host thread register methods without locking
Locks on GetCurrentHostThreadID were causing performance issues
according to Visual Studio's profiler. It was consuming twice the time
as arm_interface.Run(). The cost was not in the function itself but in
the lockinig it required.

Reimplement these functions using atomics and static storage instead of
an unordered_map. This is a side effect to avoid locking and using linked
lists for reads.

Replace unordered_map with a linear search.
2020-10-13 18:00:25 -03:00
Lioncash
9f9b64d280 audio_core/CMakeLists: Make warnings consistent with core
Normalizes the warnings shared between audio_core and core.
2020-10-13 16:36:58 -04:00
Rodrigo Locatti
c5b3c8d06b Merge pull request #4786 from lioncash/flags
core/CMakeLists: Make some warnings errors
2020-10-13 16:20:44 -03:00
Lioncash
39c8d18feb core/CMakeLists: Make some warnings errors
Makes our error coverage a little more consistent across the board by
applying it to Linux side of things as well. This also makes it more
consistent with the warning settings in other libraries in the project.

This also updates httplib to 0.7.9, as there are several warning
cleanups made that allow us to enable several warnings as errors.
2020-10-13 13:16:49 -04:00
ReinUsesLisp
e4e0abc418 vk_graphics_pipeline: Manage primitive topology as fixed state
Vulkan has requirements for primitive topologies that don't play nicely
with yuzu's. Since it's only 4 bits, we can move it to fixed state
without changing the size of the pipeline key.

- Fixes a regression on recent Nvidia drivers on Fire Emblem: Three
  Houses.
2020-10-13 04:08:33 -03:00
Morph
8db3feae19 filesystem: Fix CreateDirectory and DeleteFile
Add a check if dir is nullptr (does not exist)

Fixes save game creation in Hades
2020-10-13 00:57:04 -04:00
bunnei
62c6c9f6a6 service: time: Update current time with changes to RTC setting.
- This can be used to advance time, e.g. for Pokemon Sword/Shield pokejobs.
2020-10-12 18:09:15 -07:00
bunnei
d291fc1a51 Merge pull request #3929 from FearlessTobi/ticket-keys
file_sys/nsp: Make SetTicketKeys actually do something
2020-10-12 17:58:36 -07:00
bunnei
b260847218 hle: service: nvdrv: Implement nvhost_as_gpu::FreeSpace.
- This is used by Super Mario 3D All-Stars.
2020-10-12 17:39:14 -07:00
bunnei
4c348f4069 Merge pull request #4766 from ReinUsesLisp/tmml-cube
shader/texture: Implement CUBE texture type for TMML and fix arrays
2020-10-12 12:53:57 -07:00
bunnei
419a59a7b1 Merge pull request #4775 from ReinUsesLisp/enforce-class-memaccess
video_core: Enforce -Wclass-memaccess
2020-10-09 23:38:02 -07:00
bunnei
f250011ba0 Merge pull request #4757 from german77/BetterMotion
InputCommon: Add compatibility with only accelerometer and auto calibrate for drift
2020-10-09 23:37:08 -07:00
ReinUsesLisp
e1600b0962 video_core: Enforce -Wclass-memaccess 2020-10-09 16:46:11 -03:00
LC
61b246a3a9 Merge pull request #4771 from ReinUsesLisp/warn-unused-var
video_core: Enforce -Wunused-variable and -Wunused-but-set-variable
2020-10-08 21:10:31 -04:00
goldenx86
0120e5b1d9 vk_device: Block VK_EXT_extended_dynamic_state for RDNA devices
RDNA devices seem to crash when using VK_EXT_extended_dynamic_state in
the latest 20.9.2 proprietary Windows drivers. As a workaround, for now
we block device names corresponding to current RDNA released products.
2020-10-08 21:27:49 -03:00
bunnei
06e65de93c Merge pull request #4677 from german77/ShakeFromButton
InputCommon: Add random motion input for buttons
2020-10-08 10:18:39 -07:00
Rodrigo Locatti
7a99226785 Merge pull request #4765 from ReinUsesLisp/fix-sort-devices
renderer_vulkan/wrapper: Fix physical device sorting
2020-10-08 00:54:44 -03:00
ReinUsesLisp
dffaffaac1 shader/texture: Implement CUBE texture type for TMML and fix arrays
TMML takes an array argument that has no known meaning, this one appears
as the first component in gpr8 followed by s, t and r. Skip this
component when arrays are being used. Also implement CUBE texture types.

- Used by Pikmin 3: Deluxe Demo.
2020-10-07 23:17:46 -03:00
bunnei
3446eb79b5 Merge pull request #4731 from lat9nq/mingw-zstd-fix
common: Use system zstd on Linux
2020-10-07 17:57:14 -07:00
bunnei
92adb69fa7 Merge pull request #4736 from Morph1984/home-button-input-protection-stub
hid: Stub HomeButtonInputProtection service commands
2020-10-07 14:54:05 -07:00
ReinUsesLisp
cd3e959f23 renderer_vulkan/wrapper: Fix physical device sorting
The old code had a sort function that was invalid and it didn't work as
expected when the base vector had a different order (e.g. renderdoc was
attached).

This sorts devices as expected and fixes a debug assert on MSVC.
2020-10-07 17:13:22 -03:00
bunnei
cc0dc3280d Merge pull request #4710 from Morph1984/fix-integrated-updates
submission_package: Fix updates integrated into cartridge images.
2020-10-06 22:27:52 -07:00
bunnei
32b4627a9c Merge pull request #4737 from Morph1984/setshimlibraryversion-stub
capsrv: Stub 3 variants of SetShimLibraryVersion
2020-10-06 21:39:38 -07:00
bunnei
e9b81e9f01 Merge pull request #4727 from FrogTheFrog/patch-1
Reduce the "shake" requirements when configuring UDP.
2020-10-06 15:29:28 -07:00
bunnei
614bd0ee8c Merge pull request #4742 from german77/InputFilter
HID: Only use inputs corresponding to controller type
2020-10-05 21:40:09 -07:00
german
a54aee290f Address comments 2020-10-04 18:15:53 -05:00
bunnei
de4cadca1f Merge pull request #4648 from zhaobot/tx-update-20200911153748
Update translations (2020-09-11)
2020-10-03 22:30:38 -07:00
german
a220d8799e Add compatibility with only accelerometer and auto calibrate for drift 2020-10-03 22:22:01 -05:00
ReinUsesLisp
2a24b1c973 video_core: Enforce -Wunused-variable and -Wunused-but-set-variable 2020-10-02 21:19:35 -03:00
bunnei
182cf7d631 Merge pull request #4734 from german77/motionfusion
HID: Add Stub for EnableSixAxisSensorFusion
2020-10-01 22:39:39 -07:00
german
2f47b27654 Only use inputs corresponding to controller type 2020-10-01 19:39:53 -05:00
german
283616dbd8 Stubbed EnableSixAxisSensorFusion 2020-09-30 10:00:24 -05:00
David
4d0ae1a17a Merge pull request #4291 from german77/ImplementControllerRumble
input_common: First implementation of controller rumble
2020-09-30 21:24:11 +10:00
David
f7808f5658 Merge pull request #4726 from lioncash/applet
frontend/controller: Eliminate dependency on the global system instance
2020-09-30 21:22:45 +10:00
Morph
91bd2281bf caps_c: Stub SetShimLibraryVersion
- Used by caps_su SetShimLibraryVersion
2020-09-30 07:19:46 -04:00
Morph
7d287a6fb0 caps_u: Stub SetShimLibraryVersion
- Used in Super Smash Bros. Ultimate
2020-09-30 07:19:46 -04:00
Morph
9a251339dc caps_su: Properly stub SetShimLibraryVersion 2020-09-30 07:19:46 -04:00
Morph
6380731486 hid: Stub HomeButtonInputProtection service commands
- Used in 1-2 Switch. Given that we do not emulate the functionality of the home button yet, we can stub this for now.
2020-09-30 06:38:24 -04:00
LC
1ba0b077fc Merge pull request #4733 from ReinUsesLisp/game-list-leak
qt/game_list: Give GameListSearchField::KeyReleaseEater a parent
2020-09-30 05:54:18 -04:00
LC
cb56eaee41 Merge pull request #4732 from ReinUsesLisp/wall-clock-destr
common/wall_clock: Add virtual destructors
2020-09-30 05:53:12 -04:00
LC
3665a05488 Merge pull request #4735 from goldenx86/patch-1
Remove ext_extended_dynamic_state blacklist
2020-09-30 05:52:47 -04:00
bunnei
392c1b96bc Merge pull request #4705 from german77/SplitMotionPoller
HID: Use different timing for motion
2020-09-30 01:04:22 -07:00
Lukas Senionis
6ee1a784b8 Reduce the "shake" requirements when configuring UDP. 2020-09-30 10:30:33 +03:00
Matías Locatti
d7843b8ef2 Remove ext_extended_dynamic_state blacklist
Latest AMD 20.9.2 driver fixed this, there's no reason to keep it blocked, as the previous stable signed driver release doesn't include the extension.
2020-09-30 03:13:38 -03:00
ReinUsesLisp
771a9c21cc common/wall_clock: Add virtual destructors
From -fsanitize=address, this code wasn't calling the proper destructor.
Adding virtual destructors for each inherited class and the base class
fixes this bug.

While we are at it, mark the functions as final.
2020-09-30 02:53:34 -03:00
bunnei
385b5602e4 Update yuzu-patreon-step2.yml to set build timeout to 120 min
This is currently necessary to build ffmpeg
2020-09-29 22:17:50 -07:00
bunnei
a1e3f6e27b Merge pull request #4728 from Morph1984/applets-on-top
main: Allow applets to display on top while fullscreen
2020-09-29 15:46:44 -07:00
bunnei
09609dd50e Merge pull request #4721 from lioncash/genfn
codec: Make lookup table static constexpr
2020-09-29 15:16:24 -07:00
bunnei
2a82f1b08b Merge pull request #4722 from lioncash/casting
cubeb_sink: Use static_cast instead of reinterpret_cast in DataCallback()
2020-09-29 15:09:12 -07:00
bunnei
02ea62568f Merge pull request #1703 from DarkLordZach/nvdec-ioctl
nvdrv: Stub nvdec/vic ioctls to bypass nvdec movies
2020-09-29 15:00:48 -07:00
ReinUsesLisp
ae6df703f5 qt/game_list: Give GameListSearchField::KeyReleaseEater a parent
This fixes a memory leak as KeyReleaseEater's destructor was never
called.
2020-09-29 16:23:16 -03:00
german
ab88c2f611 First implementation of controller rumble 2020-09-29 10:38:25 -04:00
lat9nq
2cbce77b92 CMakeLists: use system zstd on Linux
From what I understand, this tells CMake to use the system, not conan,
version of zstd. Required to build on the coming MinGW Docker container.
2020-09-28 21:11:39 -04:00
lat9nq
9d665cb8db CMakeLists: fix for finding zstd on linux-mingw 2020-09-28 20:28:47 -04:00
bunnei
a8be822e8e Merge pull request #4719 from lioncash/audio-warn
audio_core: Resolve sign conversion warnings
2020-09-27 01:52:59 -07:00
Rodrigo Locatti
e5a1e0a76d Merge pull request #4724 from lat9nq/fix-vulkan-nvidia-allocate-2
vk_stream_buffer: Fix initializing Vulkan with NVIDIA on Linux
2020-09-26 23:52:49 +00:00
bunnei
442096298e Merge pull request #4703 from lioncash/desig7
shader/registry: Make use of designated initializers where applicable
2020-09-26 15:23:15 -07:00
Morph
86e4aa81e9 main: Allow applets to display on top while fullscreen
Using the Qt::WindowStaysOnTopHint flag allows these dialogs to show up on top while running in fullscreen. However, if yuzu goes out of focus (by alt-tabbing or otherwise), this flag does not seem to have an effect.
2020-09-26 06:55:47 -04:00
bunnei
fbb5ca2633 Merge pull request #4718 from lioncash/vk
vk_command_pool: Add missing header guard
2020-09-26 00:13:48 -07:00
bunnei
891090799c Merge pull request #4720 from lioncash/header
audio_core: Remove unnecessary inclusions
2020-09-25 22:04:26 -07:00
bunnei
ad76b00f1e Merge pull request #4723 from lioncash/typo
behavior_info: Fix typo Renerer -> Renderer
2020-09-25 22:03:59 -07:00
Lioncash
5c4e237902 core: Mark GetInstance() as deprecated
This way it's obvious that this function shouldn't be used in any future
code.
2020-09-25 19:23:23 -04:00
Lioncash
3e4a0a13cb frontend/controller: Eliminate dependency on the global system instance 2020-09-25 19:23:20 -04:00
german
2978232390 Add random motion input to keyboard 2020-09-25 17:59:52 -05:00
german
03b574ae22 Add random motion input to SDL 2020-09-25 17:59:52 -05:00
Lioncash
90c6141164 command_generator: Make lookup table static constexpr
Allows compilers to elide needing to push these values on the stack
every time the function is called.
2020-09-25 18:33:04 -04:00
lat9nq
ca26fd0f42 vk_stream_buffer: Fix initializing Vulkan with NVIDIA on Linux
The previous fix only partially solved the issue, as only certain GPUs that needed 9 or less MiB subtracted would work (i.e. GTX 980 Ti, GT 730). This takes from DXVK's example to divide `heap_size` by 2 to determine `allocable_size`. Additionally tested on my Quadro K4200, which previously required setting it to 12 to boot.
2020-09-25 17:42:59 -04:00
Lioncash
dc83ca8914 behavior_info: Fix typo Renerer -> Renderer 2020-09-25 17:14:02 -04:00
Lioncash
4073931305 cubeb_sink: Use static_cast instead of reinterpret_cast in DataCallback()
Conversions from void* to the proper data type are well-defined and
supported by static_cast. We don't need to use reinterpret_cast here.
2020-09-25 17:10:02 -04:00
Lioncash
7c0908f301 codec: Make lookup table static constexpr
Allows compilers to elide needing to push these values on the stack
every time the function is called.
2020-09-25 14:24:25 -04:00
Lioncash
966966dc02 audio_core: Remove unnecessary inclusions
Same behavior, but removes header dependencies where they don't need to
be.
2020-09-25 13:19:42 -04:00
Lioncash
8b4ecf22d4 audio_core: Resolve sign conversion warnings
While were at it, we can also enable sign conversion warnings and other
common warnings as errors to prevent these from creeping back into the
codebase.
2020-09-25 01:22:47 -04:00
Lioncash
111852a983 effect_context: Make use of explicit where applicable
While we're at it we can make the destructor of the base class virtual
to ensure that any polymorphism issues never occur.
2020-09-25 00:27:11 -04:00
Lioncash
940d85241b vk_command_pool: Move definition of Pool into the cpp file
Allows the implementation details to be changed without recompiling any
files that include this header.
2020-09-25 00:15:52 -04:00
Lioncash
4ed4bba305 vk_command_pool: Make use of override on destructor 2020-09-25 00:14:10 -04:00
Lioncash
e0f2db4376 vk_command_pool: Add missing header guard 2020-09-25 00:12:45 -04:00
LC
4d4afc1502 Merge pull request #4717 from lioncash/debug
service: Restore "unused" function
2020-09-25 00:07:19 -04:00
Lioncash
f3a1bf53f9 service: Restore "unused" function
Turns out this function is actually used, but within a trace log.
2020-09-25 00:06:40 -04:00
bunnei
2634e3c6eb Merge pull request #4711 from lioncash/move5
arithmetic_integer_immediate: Make use of std::move where applicable
2020-09-24 21:02:42 -07:00
Morph
3602df7f1f submission_package: Fix updates integrated into cartridge images. 2020-09-24 17:23:14 -04:00
bunnei
fa4294cc6f Merge pull request #4678 from Morph1984/LoadOpenContext-partial-impl
acc: Partially implement LoadOpenContext
2020-09-24 11:21:25 -07:00
Lioncash
e3a615a616 arithmetic_integer_immediate: Make use of std::move where applicable
Same behavior, minus any redundant atomic reference count increments and
decrements.
2020-09-24 13:28:45 -04:00
ReinUsesLisp
67af0323f0 video_core: Fix instances where msbuild always regenerated host shaders
When HEADER_GENERATOR was included in the DEPENDS section of custom
commands, msbuild assumed this was always modified. Changing this file
is not common so we can remove it from there.
2020-09-23 22:27:17 -03:00
bunnei
d66b897a6d Merge pull request #4674 from ReinUsesLisp/timeline-semaphores
renderer_vulkan: Make unconditional use of VK_KHR_timeline_semaphore
2020-09-23 18:24:27 -07:00
german
ddff03cff5 Use different timing for motion 2020-09-23 19:09:33 -05:00
bunnei
10e8acc451 Merge pull request #4618 from german77/GcAdapterAutoMap
Add automap feature for GC adapter
2020-09-23 15:55:26 -07:00
Lioncash
77532ebde3 shader/registry: Silence a -Wshadow warning 2020-09-23 15:10:25 -04:00
Lioncash
cd6f4f7eed shader/registry: Remove unnecessary namespace qualifiers
Using statements already make these unnecessary.
2020-09-23 15:08:34 -04:00
Rodrigo Locatti
8b0f334e0c Merge pull request #4702 from lioncash/doc-warn
memory: Resolve a -Wdocumentation warning
2020-09-23 19:07:47 +00:00
Rodrigo Locatti
c307ae2402 Merge pull request #4701 from lioncash/unused-proto
install_dialog: Remove unused function prototype
2020-09-23 19:07:27 +00:00
Rodrigo Locatti
6d9661939f Merge pull request #4700 from lioncash/copies
game_list: Eliminate redundant argument copies
2020-09-23 19:06:47 +00:00
Lioncash
ffeb4ef83e shader/registry: Make use of designated initializers where applicable
Same behavior, less repetition.
2020-09-23 15:06:25 -04:00
Lioncash
b14d344dfc memory: Resolve a -Wdocumentation warning
memory doesn't exist as a parameter any more.
2020-09-23 13:39:27 -04:00
Lioncash
aa35e51fcd install_dialog: Make use of [[nodiscard]] where applicable
Allows the compiler to warn against cases where the return value isn't
used (which would be a bug).
2020-09-23 13:22:04 -04:00
Lioncash
e107870bc8 install_dialog: Remove unused function prototype
This function doesn't have an implementation, so it can be removed to
prevent others from unintentionally using it.
2020-09-23 13:20:12 -04:00
Lioncash
f43a1da808 game_list: Make game list function naming consistent
Makes the naming consistent with the rest of the functions that are
present.
2020-09-23 11:28:11 -04:00
Lioncash
d264b7375c game_list: Eliminate redundant argument copies
Several functions can be taken by const reference to avoid copies
2020-09-23 11:20:12 -04:00
Rodrigo Locatti
b8219ec838 Merge pull request #4699 from lioncash/move3
control_flow: Make use of std::move in InsertBranch()
2020-09-23 06:20:59 +00:00
Lioncash
0dc6967ff1 control_flow: emplace elements in place within TryQuery()
Places data structures where they'll eventually be moved to to avoid
needing to even move them in the first place.
2020-09-22 22:54:36 -04:00
Lioncash
fcd0145eb5 control_flow: Make use of std::move in InsertBranch()
Avoids unnecessary atomic increments and decrements.
2020-09-22 22:48:09 -04:00
Rodrigo Locatti
2b863c9aa3 Merge pull request #4698 from lioncash/optional-null
General: Make use of std::nullopt where applicable
2020-09-22 23:37:51 +00:00
Lioncash
ff45c39578 General: Make use of std::nullopt where applicable
Allows some implementations to avoid completely zeroing out the internal
buffer of the optional, and instead only set the validity byte within
the structure.

This also makes it consistent how we return empty optionals.
2020-09-22 17:32:33 -04:00
bunnei
c07fd2898b Merge pull request #4697 from lioncash/copy5
ips_layer: Eliminate a redundant copy in Parse()
2020-09-22 13:42:47 -07:00
Lioncash
a881efbf26 ips_layer: Eliminate a redundant copy in Parse()
Prevents unnecessary copying of the line being parsed.
2020-09-22 16:38:45 -04:00
bunnei
53829d4cbd Merge pull request #4675 from Morph1984/fix-boot-multicontent
submission_package: Account for multi-content NSPs
2020-09-21 16:36:46 -07:00
Morph
7a504a9365 acc: Stub LoadOpenContext
This is used in multiple games such as:
- Clubhouse Games: 51 Worldwide Classics
- Grandia HD Collection
- XCOM 2 Collection
- Baldur's Gate 1/2
- Dr Kawashima's Brain Training
- Super Mario 3D All-Stars
2020-09-21 01:01:02 -04:00
Rodrigo Locatti
a2eb44db82 Merge pull request #4692 from ReinUsesLisp/remove-vsync
renderer_opengl: Remove emulated mailbox presentation
2020-09-21 00:21:32 +00:00
bunnei
754109fd54 Merge pull request #4683 from Morph1984/NpadHandheldActivationMode-impl
hid: Implement Get/SetNpadHandheldActivationMode
2020-09-20 12:39:20 -07:00
ReinUsesLisp
7003090187 renderer_opengl: Remove emulated mailbox presentation
Emulated mailbox presentation was causing performance issues on
Nvidia's OpenGL driver. Remove it.
2020-09-20 16:29:41 -03:00
bunnei
8a85a562ed Merge pull request #4643 from FearlessTobi/decrease-pad-update-interval
Test: Decrease pad_update_ns
2020-09-19 00:39:50 -07:00
ReinUsesLisp
4f5bbe56ba vk_query_cache: Hack counter destructor to avoid reserving queries
This is a hack to destroy all HostCounter instances before the base
class destructor is called. The query cache should be redesigned to have
a proper ownership model instead of using shared pointers.

For now, destroy the host counter hierarchy from the derived class
destructor.
2020-09-19 01:47:29 -03:00
ReinUsesLisp
58b0ae84b5 renderer_vulkan: Make unconditional use of VK_KHR_timeline_semaphore
This reworks how host<->device synchronization works on the Vulkan
backend. Instead of "protecting" resources with a fence and signalling
these as free when the fence is known to be signalled by the host GPU,
use timeline semaphores.

Vulkan timeline semaphores allow use to work on a subset of D3D12
fences. As far as we are concerned, timeline semaphores are a value set
by the host or the device that can be waited by either of them.

Taking advantange of this, we can have a monolithically increasing
atomic value for each submission to the graphics queue. Instead of
protecting resources with a fence, we simply store the current logical
tick (the atomic value stored in CPU memory). When we want to know if a
resource is free, it can be compared to the current GPU tick.

This greatly simplifies resource management code and the free status of
resources should have less false negatives.

To workaround bugs in validation layers, when these are attached there's
a thread waiting for timeline semaphores.
2020-09-19 01:46:37 -03:00
german
c5e257017f Add automap feature for GC adapter 2020-09-18 16:51:16 -05:00
Rodrigo Locatti
059dd724d6 Merge pull request #4684 from lioncash/desig4
fermi_2d: Make use of designated initializers
2020-09-18 19:14:56 +00:00
Lioncash
91bca9eb0b fermi_2d: Make use of designated initializers
Same behavior, less repetition. We can also ensure all members of Config
are initialized.
2020-09-18 13:55:21 -04:00
Morph
ab961e0701 hid: Implement Get/SetNpadHandheldActivationMode
- Used in Clubhouse Games: 51 Worldwide Classics
2020-09-18 10:10:30 -04:00
David
050a4a401b Merge pull request #4680 from Morph1984/fix-motion-mapping
configure_input_player: Fixes motion mapping using ConfigureButtonClick
2020-09-18 21:03:50 +10:00
Morph
70499b8cbd configure_input_player: Fixes motion mapping using ConfigureButtonClick 2020-09-18 03:56:31 -04:00
bunnei
8568f44ffa Merge pull request #4647 from Morph1984/readd-context-menu
configure_input_player: Re-add "Clear" context menu option
2020-09-17 22:59:13 -07:00
bunnei
669005b75e Merge pull request #4676 from Morph1984/GetPreviousProgramIndex-impl
am: Stub GetPreviousProgramIndex
2020-09-17 22:12:57 -07:00
Morph
40a72e9cd5 am: Stub GetPreviousProgramIndex
- Used in Super Mario 3D All-Stars
2020-09-17 22:03:02 -04:00
Morph
65d9def873 configure_input_player: Re-add "Clear" context menu option
The context menu was removed in Mjölnir Part 1 as part of the input rewrite as we were unaware of it's usage statistics.
However, as this was the only way to clear the inputs of individual buttons, this PR will re-add it back in.
2020-09-17 21:57:06 -04:00
Morph
41c2f5200c submission_package: Account for multi-content NSPs
Previously we assumed a submission package can only contain one Program NCA with a single TitleID.
However, Super Mario 3D All-Stars contains four Program NCAs, each with their unique TitleIDs.
This accounts for the existence of multi-content games such as this one.
- Fixes booting Super Mario 3D All-Stars from the games list.
2020-09-17 20:44:51 -04:00
Rodrigo Locatti
53fc5d0190 Merge pull request #4670 from lioncash/initializer
arm_dynarmic_cp15: Initialize member variables
2020-09-17 21:20:53 +00:00
Rodrigo Locatti
9bdca01c27 Merge pull request #4665 from lioncash/sm-kernel
service/sm: Eliminate dependency on the global system instance
2020-09-17 21:20:39 +00:00
Rodrigo Locatti
8100275309 Merge pull request #4666 from lioncash/unused-func
service: Remove unused funcation
2020-09-17 21:19:48 +00:00
Rodrigo Locatti
131532b570 Merge pull request #4671 from lioncash/nfp-copy
command_generator/nfp: Eliminate unnecessary copies
2020-09-17 21:19:12 +00:00
Rodrigo Locatti
31461589c5 Merge pull request #4672 from lioncash/narrowing
decoder/texture: Eliminate narrowing conversion in GetTldCode()
2020-09-17 21:17:54 +00:00
Rodrigo Locatti
9f51242524 Merge pull request #4673 from lioncash/fallthrough
decode/image: Eliminate switch fallthrough in DecodeImage()
2020-09-17 21:17:16 +00:00
bunnei
3f6d83b27c Merge pull request #4594 from german77/MotionHID
hid/configuration: Implement motion controls to HID
2020-09-17 12:39:01 -07:00
Lioncash
4944d48ee8 decode/image: Eliminate switch fallthrough in DecodeImage()
Fortunately this didn't result in any issues, given the block that code
was falling through to would immediately break.
2020-09-17 15:12:18 -04:00
Lioncash
ffc66f089d decoder/texture: Eliminate narrowing conversion in GetTldCode()
The assignment was previously truncating a u64 value to a bool.
2020-09-17 15:04:17 -04:00
Lioncash
362e2940be audio_core/command_generator: Use const references where applicable
In a lot of cases, we can make use of const references rather than
non-const references.

While we're in the area we can silence some truncation and sign
conversion warnings.
2020-09-17 13:52:55 -04:00
Lioncash
9539e4d8fd audio_core/command_generator: Avoid an unnecessary copy in GenerateFinalMixCommand() 2020-09-17 13:45:24 -04:00
Lioncash
aca3621146 nfp: Eliminate two unnecessary copies
GetAmiiboBuffer() returns by const reference, so we can use a reference
instead of taking the returned buffer by value.
2020-09-17 13:35:55 -04:00
Lioncash
1ee9ceb5af arm_dynarmic_cp15: Initialize member variables
Ensures that the member variables are always initialized to a
deterministic value on creation.
2020-09-17 13:03:49 -04:00
bunnei
382bf1faf4 Merge pull request #4668 from lioncash/port
control_metadata: Resolve typo in Portuguese language name
2020-09-17 09:55:01 -07:00
Lioncash
02b8b6677a control_metadata: Resolve typo in Portuguese language name
This isn't used anywhere, so this is a trivial fix.
2020-09-17 11:45:30 -04:00
Lioncash
8bbd82863d service: Remove unused funcation
This is now completely unused, so it can be removed.
2020-09-17 11:03:26 -04:00
Lioncash
057aa6275d service/sm: Slightly more efficient string name validation
We can check the end of the string first for null-termination, rather
than the beginning of the string.
2020-09-17 10:54:12 -04:00
Lioncash
78b1bc3b61 service/sm: Eliminate dependency on the global system instance 2020-09-17 10:43:54 -04:00
bunnei
fcd0925ecf Merge pull request #4653 from ReinUsesLisp/gc-warns
gc_adapter: Disable MSVC nonstandard extension warning on libusb.h
2020-09-16 22:33:58 -07:00
bunnei
1eae35621e Merge pull request #4663 from ReinUsesLisp/wswitch
video_core: Enforce -Werror=switch
2020-09-16 20:43:23 -07:00
Rodrigo Locatti
62de0220fe Merge pull request #4662 from lioncash/factory
bis_factory/romfs_factory: Eliminate dependencies on the global system instance
2020-09-16 23:43:30 +00:00
Lioncash
a62c1999c5 file_sys/romfs_factory: Eliminate usage of the global system accessor 2020-09-16 19:15:19 -04:00
Lioncash
0e80567bef file_sys/bis_factory: Eliminate usage of the global system accessor 2020-09-16 18:16:04 -04:00
Lioncash
aa8d6fc041 loader/nso: Remove unnecessary [[maybe_unused]] 2020-09-16 18:09:01 -04:00
Rodrigo Locatti
b0ae8265ea Merge pull request #4661 from lioncash/system-loader
core/loader: Remove dependencies on the global system instance
2020-09-16 20:59:44 +00:00
ReinUsesLisp
eb914b6c50 video_core: Enforce -Werror=switch
This forces us to fix all -Wswitch warnings in video_core.
2020-09-16 17:48:01 -03:00
Lioncash
113a3972a6 core/loader: Remove dependencies on the global system instance
Now all that remains is:

18 instances in file_sys code
14 instances in GDB stub code (this can be tossed wholesale)
4 instances in HLE code
2 instances in settings code.
2020-09-16 08:46:59 -04:00
Rodrigo Locatti
004bfefeb5 Merge pull request #4658 from lioncash/copy3
nca_patch: Reduce stack usage size within SearchBucketEntry()
2020-09-16 00:25:11 +00:00
Rodrigo Locatti
9cd1ea338b Merge pull request #4657 from lioncash/cheatparser
cheat_engine: Remove unnecessary system argument to CheatParser's Parse function
2020-09-16 00:24:14 +00:00
Lioncash
66fc037ef2 nca_patch: Significantly reduce the stack usage size within SearchBucketEntry()
Previously this function was using ~16KB of stack (16528 bytes), which
was caused by the function arguments being taken by value rather than by
reference.

We can make this significantly lighter on the stack by taking them by
reference.
2020-09-15 09:10:58 -04:00
Lioncash
99b372a6c5 nca_patch: Make SearchBucketEntry() internally linked
This is only used internally and doesn't depend on any class state, so
we can make it fully internal.
2020-09-15 09:06:46 -04:00
Lioncash
3a8464cde2 cheat_engine: Convert ExtractName into a non-template function
We don't need to create two separate instantiations of the same code, we
can simply make the character template argument a regular function
parameter.
2020-09-15 03:24:44 -04:00
Lioncash
ba7eb5abf4 cheat_engine: Remove unnecessary system argument to CheatParser's Parse function
This isn't used within the function at all in any implementations, so we
can remove it entirely.
2020-09-15 03:20:40 -04:00
Rodrigo Locatti
b5f4221c3d Merge pull request #4655 from lioncash/internal2
patch_manager: Minor cleanup
2020-09-15 01:57:13 +00:00
Lioncash
33e4a0b6c1 patch_manager: Resolve implicit truncations in FormatTitleVersion()
We make it explicit that we're truncating arithmetic here to resolve
compiler warnings (even if the sizes weren't u32/u64 arithmetic
generally promotes to int :<)
2020-09-14 19:19:59 -04:00
Lioncash
a4392c24cf patch_manager: Make use of type aliases
We can use these to avoid typing the same type redundantly. This way, if
these ever change, only a single location needs to be modified.
2020-09-14 19:17:50 -04:00
Lioncash
637ab14ae6 patch_manager: Make a few functions internally linked
These functions are only used within this translation unit, so we can
make them internally linked.
2020-09-14 19:04:51 -04:00
ReinUsesLisp
bc8ace9917 gc_adapter: Disable MSVC nonstandard extension warning on libusb.h
Pragma disable zero-sized array nonstandard extension warning on MSVC.
2020-09-14 19:38:08 -03:00
Rodrigo Locatti
0bac7b6a95 Merge pull request #4652 from lioncash/crypto
crypto/key_manager: Remove dependency on the global system accessor
2020-09-14 22:27:33 +00:00
Lioncash
e0dd440b1f crypto/key_manager: Remove dependency on the global system accessor
We can supply the content provider as an argument instead of hardcoding
a global accessor in the implementation.
2020-09-14 16:49:59 -04:00
Rodrigo Locatti
1a9774f824 Merge pull request #4651 from lioncash/kernel-global
kernel: Remove all dependencies on the global system instance
2020-09-14 20:39:05 +00:00
Lioncash
ec2a6e5ba8 kernel: Remove all dependencies on the global system instance
With this, the kernel finally doesn't depend directly on the global
system instance anymore.
2020-09-14 14:03:13 -04:00
bunnei
042567e4b2 Merge pull request #4636 from lioncash/kernel-hle
service: Remove two usages of the global system accessor
2020-09-14 09:17:10 -07:00
bunnei
5fc6bf96d8 Merge pull request #4323 from ReinUsesLisp/no-spin
kernel/scheduler: Use std::mutex instead of spin lock
2020-09-11 23:23:53 -07:00
bunnei
508f2072a9 Merge pull request #4645 from v1993/lgtm-less-packages
Remove bad and useless packages from LGTM build
2020-09-11 22:10:27 -07:00
bunnei
f4400f3ba2 Merge pull request #4638 from Morph1984/qt-5.12.8
cmake: Update to Qt 5.12.8
2020-09-11 15:19:29 -07:00
bunnei
ec634b6a88 Merge pull request #4634 from lioncash/blocking
bsd: Resolve a few warnings
2020-09-11 15:17:34 -07:00
The yuzu Community
b5784e9af2 Update translations (2020-09-11) 2020-09-11 15:38:10 +00:00
bunnei
324029d4f9 Merge pull request #4310 from ogniK5377/apollo-1-prod
audio_core: Apollo Part 1, AudioRenderer refactor
2020-09-11 10:57:27 -04:00
Valeri
9f6892271f Remove bad and useless packages from LGTM build
It still fails due to CMake version being 3.13.4, but at
least we are not ones to blame now.
2020-09-11 17:32:22 +03:00
bunnei
03179ecafe Merge pull request #4597 from Morph1984/mjolnir-p2
Project Mjölnir: Part 2 - Controller Applet
2020-09-10 19:28:23 -04:00
bunnei
41b8ecdeb6 Merge pull request #4608 from lioncash/sign3
configure_input_player: Resolve sign conversion warnings in UpdateMappingWithDefaults()
2020-09-10 13:56:16 -04:00
FearlessTobi
57162e1df3 Test: Decrease pad_update_ns
There have been reports of quite heavy input lag in the past.
Compared to Citra for example, our pad_update_ns value is very high.
So let's decrease it and see if it helps with this problem.
2020-09-10 16:38:53 +02:00
Rodrigo Locatti
663ea382da Merge pull request #4633 from ReinUsesLisp/gpu-init
video_core: Remove all Core::System references in renderer
2020-09-10 02:28:54 +00:00
bunnei
d90961122c Merge pull request #4635 from lioncash/gc-adap
gc_adapter: Make DeviceConnected() a const member function
2020-09-09 22:27:49 -04:00
Morph
09126f3a4a cmake: Update to Qt 5.12.8 2020-09-09 08:50:39 -04:00
Lioncash
ffdf8c0cb3 service: Remove two usages of the global system accessor
Removes more instances of reliance on global state.
2020-09-07 03:18:45 -04:00
Lioncash
40968e3993 bsd: Resolve unused value within SendToImpl
Previously the address provided to SendToImpl would never be propagated
to SendTo(). This fixes that.
2020-09-07 01:06:30 -04:00
Lioncash
cd643ab5c9 bsd: Resolve sign comparison warnings 2020-09-07 01:06:27 -04:00
Lioncash
180fa6859f sockets_translate: Make use of designated initializers
Same behavior, less typing.
2020-09-07 00:53:10 -04:00
Lioncash
188a3cf74c blocking_worker: Make use of templated lambda
We can simplify this a little by explicitly specifying the typename for
the lambda function.
2020-09-07 00:47:46 -04:00
Lioncash
9652973db2 blocking_worker: Resolve -Wdocumentation warning 2020-09-07 00:45:53 -04:00
ReinUsesLisp
9e87193725 video_core: Remove all Core::System references in renderer
Now that the GPU is initialized when video backends are initialized,
it's no longer needed to query components once the game is running: it
can be done when yuzu is booting.

This allows us to pass components between constructors and in the
process remove all Core::System references in the video backend.
2020-09-06 05:28:48 -03:00
Morph
5b6268d26a configure_input: Hook up the motion button and checkbox
This allows toggling motion on or off, and allows access to the motion configuration.
Also changes the [waiting] text for motion buttons to Shake! as this is how motion is connected to a player.
2020-09-05 09:46:34 -04:00
german
797564599f Minor cleanup 2020-09-05 09:42:21 -04:00
german
6ee8eab670 Add cemu hook changes related to PR #4609 2020-09-04 21:48:13 -05:00
german
0774b17846 Remove RealMotionDevice 2020-09-04 21:48:13 -05:00
Morph
8e18b61972 configure_input_player: Show/hide motion buttons based on the controller 2020-09-04 21:48:13 -05:00
Morph
df3cbd4758 controllers/npad: Simplify motion entry assignment
Simplifies the motion assignment in the Dual Joycon entry and assigns index 1 of the motion entry (Motion 2) for the right joycon.
2020-09-04 21:48:13 -05:00
german
ff679f3d17 Include HID and configuration changes related to motion 2020-09-04 21:48:03 -05:00
Morph
5043036688 Resolve spacing inconsistencies in style.qrc/qss files 2020-09-04 12:23:26 -04:00
Morph
b65456b958 applets/controller: Resolve several compiler warnings
Resolves -Wsign-compare and -Wunused-variable
2020-09-04 12:23:26 -04:00
Morph
076e4d44c3 Address feedback 2020-09-04 12:23:25 -04:00
Morph
1ec71b6ea0 clang-format 2020-09-04 12:23:25 -04:00
Morph
f95ea04995 applets/controller: Set min_players to have a minimum value of 1.
- Some games like Shipped have a minimum requirement of 0 connected players and is undesired behavior. We must require a minimum of 1 player connected regardless of what games may ask.
2020-09-04 12:23:25 -04:00
Morph
371226448a applets/controller: Modify heuristic to account for certain games
Now left and right joycons have the same priority (meaning both needs to be supported by the game).

Explanation of the new heuristic:
Assign left joycons to even player indices and right joycons to odd player indices.
We do this since Captain Toad Treasure Tracker expects a left joycon for Player 1 and a right Joycon for Player 2 in 2 Player Assist mode.
2020-09-04 12:23:25 -04:00
Morph
6597b3817c main: Apply settings after applet configuration is complete. 2020-09-04 12:23:25 -04:00
Morph
7299356f37 applets/controller: Implement fallback applet for the SDL frontend
Implement the fallback applet for the SDL frontend, connecting only the minimum amount of players required.
2020-09-04 12:23:25 -04:00
Morph
72b2f5d34f applets/controller: Load configuration prior to setting up connections
This avoids unintentionally changing the states of elements while loading them in.
2020-09-04 12:23:25 -04:00
Morph
aeec0f8a38 applets/controller: Make 8 a static constexpr value of NUM_PLAYERS
Avoids repetitive usages of the int literal '8' or calls to player_widgets.size()
2020-09-04 12:23:25 -04:00
Morph
5ce3015945 applets/controller: Implement "Explain Text"
"Explain Text" is additional text that is shown for each player in the controller applet.
2020-09-04 12:23:25 -04:00
Morph
5219615418 Project Mjölnir: Part 2 - Controller Applet
Co-authored-by: Its-Rei <kupfel@gmail.com>
2020-09-04 12:23:25 -04:00
Lioncash
92c162126b configure_input_player: Resolve sign conversion warnings in UpdateMappingWithDefaults()
Prevents sign mismatch warnings in the loop conditionals.
2020-08-29 16:43:12 -04:00
M&M
43ce33b6cc logging/settings: Increase maximum log size to 100 MB and add extended logging option
The extended logging option is automatically disabled on boot but can be enabled afterwards, allowing the log file to go up to 1 GB during that session.
This commit also fixes a few errors that are present in the general debug menu.
2020-08-24 21:39:56 -07:00
ReinUsesLisp
c5a78f4480 vk_device: Use Vulkan 1.0 properly
Enable the required capabilities to use Vulkan 1.0 without validation
errors and disable those that are not compatible with it.
2020-08-20 16:55:22 -03:00
ReinUsesLisp
29a0ca2391 renderer_vulkan: Create a Vulkan 1.0 instance when 1.1 is not available
This commit doesn't make yuzu compatible with Vulkan 1.0 yet, it only
creates an 1.0 instance.
2020-08-20 16:55:22 -03:00
David Marcec
80ac1331b5 Preliminary effects 2020-08-17 01:23:55 +10:00
David Marcec
1f1c3bddc0 Disable biquad filter 2020-08-14 23:20:20 +10:00
David Marcec
1b3d86c02f Reworked ADPCM decoder to allow better streaming 2020-08-14 21:04:28 +10:00
David Marcec
0947f613b1 mix buffer depopping 2020-08-01 16:25:08 +10:00
David Marcec
1b8fe7073b adpcm streaming 2020-07-30 18:16:57 +10:00
David Marcec
3dcbba38bf Fix perf regression 2020-07-25 21:46:25 +10:00
David Marcec
f4eb7dceaf Fix stream channel count when outputting to stereo 2020-07-25 13:31:43 +10:00
David Marcec
b924c71822 Address issues 2020-07-25 12:39:37 +10:00
David Marcec
8a497adf85 Queue extra mix buffer 2020-07-25 12:39:36 +10:00
David Marcec
d68856ab12 Disable time stretcher for time being 2020-07-25 12:39:35 +10:00
David Marcec
380658c21d audio_core: Apollo Part 1, AudioRenderer refactor 2020-07-25 12:39:34 +10:00
FearlessTobi
4d4bbe756f file_sys/nsp: Make SetTicketKeys actually do something
Previously, the method wasn't modifying any class state and therefore not having any effects when called.
Since this has been the case for a very long time now, I'm not sure if we couldn't just remove this method altogether.
2020-07-18 02:02:39 +02:00
ReinUsesLisp
9b38f4fc55 kernel/scheduler: Use std::mutex instead of spin lock
Profiling shows that this is a highly contested mutex, causing dimishing
results compared to a OS lock. std::mutex implementations can spin for a
while before falling back to an OS lock.

This avoids wasting precious CPU cycles in a no-op.
2020-07-12 21:27:24 -03:00
bunnei
1adf640d37 service: nvhost_vic: Ignore Submit commands. 2020-06-04 22:32:28 -04:00
Zach Hilman
34635a42c0 nvdrv: Stub nvdec/vic ioctls to bypass nvdec movies 2020-06-04 22:32:28 -04:00
533 changed files with 80695 additions and 9872 deletions

View File

@@ -4,9 +4,11 @@ parameters:
version: ''
steps:
- script: choco install vulkan-sdk
displayName: 'Install vulkan-sdk'
- script: python -m pip install --upgrade pip conan
displayName: 'Install conan'
- script: mkdir build && cd build && cmake -G "Visual Studio 16 2019" -A x64 --config Release -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_UNICORN=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DUSE_DISCORD_PRESENCE=ON -DDISPLAY_VERSION=${{ parameters['version'] }} .. && cd ..
- script: refreshenv && mkdir build && cd build && cmake -G "Visual Studio 16 2019" -A x64 --config Release -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_UNICORN=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DUSE_DISCORD_PRESENCE=ON -DDISPLAY_VERSION=${{ parameters['version'] }} .. && cd ..
displayName: 'Configure CMake'
- task: MSBuild@1
displayName: 'Build'

View File

@@ -9,6 +9,7 @@ stages:
displayName: 'build'
jobs:
- job: build
timeoutInMinutes: 120
displayName: 'windows-msvc'
pool:
vmImage: windows-2019

5
.gitmodules vendored
View File

@@ -1,15 +1,12 @@
[submodule "inih"]
path = externals/inih/inih
url = https://github.com/svn2github/inih
url = https://github.com/benhoyt/inih.git
[submodule "cubeb"]
path = externals/cubeb
url = https://github.com/kinetiknz/cubeb.git
[submodule "dynarmic"]
path = externals/dynarmic
url = https://github.com/MerryMage/dynarmic.git
[submodule "unicorn"]
path = externals/unicorn
url = https://github.com/yuzu-emu/unicorn
[submodule "soundtouch"]
path = externals/soundtouch
url = https://github.com/citra-emu/ext-soundtouch.git

View File

@@ -6,8 +6,5 @@ extraction:
packages:
- "libsdl2-dev"
- "qtmultimedia5-dev"
- "clang-format-10"
- "libtbb-dev"
- "libjack-jackd2-dev"
- "doxygen"
- "graphviz"

View File

@@ -4,16 +4,8 @@ cd /yuzu
# override Travis CI unreasonable ccache size
echo 'max_size = 3.0G' > "$HOME/.ccache/ccache.conf"
# Dirty hack to trick unicorn makefile into believing we are in a MINGW system
mv /bin/uname /bin/uname1 && echo -e '#!/bin/sh\necho MINGW64' >> /bin/uname
chmod +x /bin/uname
# Dirty hack to trick unicorn makefile into believing we have cmd
echo '' >> /bin/cmd
chmod +x /bin/cmd
mkdir build && cd build
cmake .. -G Ninja -DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MinGWCross.cmake" -DUSE_CCACHE=ON -DYUZU_USE_BUNDLED_UNICORN=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DCMAKE_BUILD_TYPE=Release
cmake .. -G Ninja -DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MinGWCross.cmake" -DUSE_CCACHE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DCMAKE_BUILD_TYPE=Release
ninja
# Clean up the dirty hacks

View File

@@ -3,7 +3,7 @@
cd /yuzu
mkdir build && cd build
cmake .. -G Ninja -DYUZU_USE_BUNDLED_UNICORN=ON -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON
cmake .. -G Ninja -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON
ninja
ccache -s

View File

@@ -4,13 +4,12 @@ set -o pipefail
export MACOSX_DEPLOYMENT_TARGET=10.14
export Qt5_DIR=$(brew --prefix)/opt/qt5
export UNICORNDIR=$(pwd)/externals/unicorn
export PATH="/usr/local/opt/ccache/libexec:$PATH"
# TODO: Build using ninja instead of make
mkdir build && cd build
cmake --version
cmake .. -DYUZU_USE_BUNDLED_UNICORN=ON -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DUSE_DISCORD_PRESENCE=ON
cmake .. -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DUSE_DISCORD_PRESENCE=ON
make -j4
ccache -s

View File

@@ -18,8 +18,6 @@ CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_QT "Download bundled Qt binaries" ON "EN
option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
option(YUZU_USE_BUNDLED_UNICORN "Build/Download bundled Unicorn" ON)
option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF)
option(YUZU_ENABLE_BOXCAT "Enable the Boxcat service, a yuzu high-level implementation of BCAT" ON)
@@ -161,7 +159,7 @@ macro(yuzu_find_packages)
# Cmake Pkg Prefix Version Conan Pkg
"Boost 1.73 boost/1.73.0"
"Catch2 2.13 catch2/2.13.0"
"fmt 7.0 fmt/7.0.3"
"fmt 7.1 fmt/7.1.2"
# can't use until https://github.com/bincrafters/community/issues/1173
#"libzip 1.5 libzip/1.5.2@bincrafters/stable"
"lz4 1.8 lz4/1.9.2"
@@ -210,7 +208,7 @@ if(ENABLE_QT)
set(QT_PREFIX_HINT)
if(YUZU_USE_BUNDLED_QT)
if ((MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1930) AND ARCHITECTURE_x86_64)
set(QT_VER qt-5.12.0-msvc2017_64)
set(QT_VER qt-5.12.8-msvc2017_64)
else()
message(FATAL_ERROR "No bundled Qt binaries for your toolchain. Disable YUZU_USE_BUNDLED_QT and provide your own.")
endif()
@@ -263,6 +261,7 @@ if (CONAN_REQUIRED_LIBS)
libzip:with_openssl=False
libzip:enable_windows_crypto=False
)
conan_check(VERSION 1.24.0 REQUIRED)
# Add the bincrafters remote
conan_add_remote(NAME bincrafters
@@ -354,85 +353,23 @@ if (NOT LIBUSB_FOUND)
set(LIBUSB_LIBRARIES usb)
endif()
# Use system installed ffmpeg.
if (NOT MSVC)
find_package(FFmpeg REQUIRED)
else()
set(FFMPEG_EXT_NAME "ffmpeg-4.2.1")
set(FFMPEG_PATH "${CMAKE_BINARY_DIR}/externals/${FFMPEG_EXT_NAME}")
download_bundled_external("ffmpeg/" ${FFMPEG_EXT_NAME} "")
set(FFMPEG_FOUND YES)
set(FFMPEG_INCLUDE_DIR "${FFMPEG_PATH}/include" CACHE PATH "Path to FFmpeg headers" FORCE)
set(FFMPEG_LIBRARY_DIR "${FFMPEG_PATH}/bin" CACHE PATH "Path to FFmpeg library" FORCE)
set(FFMPEG_DLL_DIR "${FFMPEG_PATH}/bin" CACHE PATH "Path to FFmpeg dll's" FORCE)
endif()
# Prefer the -pthread flag on Linux.
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
# If unicorn isn't found, msvc -> download bundled unicorn; everyone else -> build external
if (YUZU_USE_BUNDLED_UNICORN)
if (MSVC)
message(STATUS "unicorn not found, falling back to bundled")
# Detect toolchain and platform
if ((MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1930) AND ARCHITECTURE_x86_64)
set(UNICORN_VER "unicorn-yuzu")
else()
message(FATAL_ERROR "No bundled Unicorn binaries for your toolchain. Disable YUZU_USE_BUNDLED_UNICORN and provide your own.")
endif()
if (DEFINED UNICORN_VER)
download_bundled_external("unicorn/" ${UNICORN_VER} UNICORN_PREFIX)
endif()
if (DEFINED UNICORN_VER)
download_bundled_external("unicorn/" ${UNICORN_VER} UNICORN_PREFIX)
endif()
set(UNICORN_FOUND YES)
set(LIBUNICORN_INCLUDE_DIR "${UNICORN_PREFIX}/include" CACHE PATH "Path to Unicorn headers" FORCE)
set(LIBUNICORN_LIBRARY "${UNICORN_PREFIX}/lib/x64/unicorn_dynload.lib" CACHE PATH "Path to Unicorn library" FORCE)
set(UNICORN_DLL_DIR "${UNICORN_PREFIX}/lib/x64/" CACHE PATH "Path to unicorn.dll" FORCE)
else()
message(STATUS "unicorn not found, falling back to externals")
if (MINGW)
set(UNICORN_LIB_NAME "unicorn.a")
else()
set(UNICORN_LIB_NAME "libunicorn.a")
endif()
set(UNICORN_FOUND YES)
set(UNICORN_PREFIX ${PROJECT_SOURCE_DIR}/externals/unicorn)
set(LIBUNICORN_LIBRARY "${UNICORN_PREFIX}/${UNICORN_LIB_NAME}" CACHE PATH "Path to Unicorn library" FORCE)
set(LIBUNICORN_INCLUDE_DIR "${UNICORN_PREFIX}/include" CACHE PATH "Path to Unicorn headers" FORCE)
set(UNICORN_DLL_DIR "${UNICORN_PREFIX}/" CACHE PATH "Path to unicorn dynamic library" FORCE)
find_package(PythonInterp 2.7 REQUIRED)
if (MINGW)
# Intentionally call the unicorn makefile directly instead of using make.sh so that we can override the
# UNAME_S makefile variable to MINGW. This way we don't have to hack at the uname binary to build
# Additionally, overriding DO_WINDOWS_EXPORT prevents unicorn from patching the static unicorn.a by using msvc and cmd,
# which are both things we don't have in a mingw cross compiling environment.
add_custom_command(OUTPUT ${LIBUNICORN_LIBRARY}
COMMAND ${CMAKE_COMMAND} -E env UNICORN_ARCHS="aarch64" PYTHON="${PYTHON_EXECUTABLE}" CC=x86_64-w64-mingw32-gcc AR=x86_64-w64-mingw32-gcc-ar RANLIB=x86_64-w64-mingw32-gcc-ranlib make UNAME_S=MINGW DO_WINDOWS_EXPORT=0
WORKING_DIRECTORY ${UNICORN_PREFIX}
)
else()
add_custom_command(OUTPUT ${LIBUNICORN_LIBRARY}
COMMAND ${CMAKE_COMMAND} -E env UNICORN_ARCHS="aarch64" PYTHON="${PYTHON_EXECUTABLE}" /bin/sh make.sh macos-universal-no
WORKING_DIRECTORY ${UNICORN_PREFIX}
)
endif()
# ALL makes this custom target build every time
# but it won't actually build if LIBUNICORN_LIBRARY is up to date
add_custom_target(unicorn-build ALL
DEPENDS ${LIBUNICORN_LIBRARY}
)
unset(UNICORN_LIB_NAME)
endif()
else()
find_package(Unicorn REQUIRED)
endif()
if (UNICORN_FOUND)
add_library(unicorn INTERFACE)
add_dependencies(unicorn unicorn-build)
target_link_libraries(unicorn INTERFACE "${LIBUNICORN_LIBRARY}")
target_include_directories(unicorn INTERFACE "${LIBUNICORN_INCLUDE_DIR}")
else()
message(FATAL_ERROR "Could not find or build unicorn which is required.")
endif()
# Platform-specific library requirements
# ======================================

View File

@@ -0,0 +1,10 @@
function(copy_yuzu_FFmpeg_deps target_dir)
include(WindowsCopyFiles)
set(DLL_DEST "${CMAKE_BINARY_DIR}/bin/$<CONFIG>/")
windows_copy_files(${target_dir} ${FFMPEG_DLL_DIR} ${DLL_DEST}
avcodec-58.dll
avutil-56.dll
swresample-3.dll
swscale-5.dll
)
endfunction(copy_yuzu_FFmpeg_deps)

View File

@@ -1,9 +0,0 @@
function(copy_yuzu_unicorn_deps target_dir)
include(WindowsCopyFiles)
set(DLL_DEST "${CMAKE_BINARY_DIR}/bin/$<CONFIG>/")
windows_copy_files(${target_dir} ${UNICORN_DLL_DIR} ${DLL_DEST}
libgcc_s_seh-1.dll
libwinpthread-1.dll
unicorn.dll
)
endfunction(copy_yuzu_unicorn_deps)

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -21,5 +21,35 @@
<file alias="single_joycon_right_vertical">single_joycon_right_vertical.png</file>
<file alias="single_joycon_right_vertical_dark">single_joycon_right_vertical_dark.png</file>
<file alias="single_joycon_right_vertical_midnight">single_joycon_right_vertical_midnight.png</file>
<file alias="applet_dual_joycon">applet_dual_joycon.png</file>
<file alias="applet_dual_joycon_dark">applet_dual_joycon_dark.png</file>
<file alias="applet_dual_joycon_midnight">applet_dual_joycon_midnight.png</file>
<file alias="applet_handheld">applet_handheld.png</file>
<file alias="applet_handheld_dark">applet_handheld_dark.png</file>
<file alias="applet_handheld_midnight">applet_handheld_midnight.png</file>
<file alias="applet_pro_controller">applet_pro_controller.png</file>
<file alias="applet_pro_controller_dark">applet_pro_controller_dark.png</file>
<file alias="applet_pro_controller_midnight">applet_pro_controller_midnight.png</file>
<file alias="applet_joycon_left">applet_single_joycon_left.png</file>
<file alias="applet_joycon_left_dark">applet_single_joycon_left_dark.png</file>
<file alias="applet_joycon_left_midnight">applet_single_joycon_left_midnight.png</file>
<file alias="applet_joycon_right">applet_single_joycon_right.png</file>
<file alias="applet_joycon_right_dark">applet_single_joycon_right_dark.png</file>
<file alias="applet_joycon_right_midnight">applet_single_joycon_right_midnight.png</file>
<file alias="applet_dual_joycon_disabled">applet_dual_joycon_disabled.png</file>
<file alias="applet_dual_joycon_dark_disabled">applet_dual_joycon_dark_disabled.png</file>
<file alias="applet_dual_joycon_midnight_disabled">applet_dual_joycon_midnight_disabled.png</file>
<file alias="applet_handheld_disabled">applet_handheld_disabled.png</file>
<file alias="applet_handheld_dark_disabled">applet_handheld_dark_disabled.png</file>
<file alias="applet_handheld_midnight_disabled">applet_handheld_midnight_disabled.png</file>
<file alias="applet_pro_controller_disabled">applet_pro_controller_disabled.png</file>
<file alias="applet_pro_controller_dark_disabled">applet_pro_controller_dark_disabled.png</file>
<file alias="applet_pro_controller_midnight_disabled">applet_pro_controller_midnight_disabled.png</file>
<file alias="applet_joycon_left_disabled">applet_single_joycon_left_disabled.png</file>
<file alias="applet_joycon_left_dark_disabled">applet_single_joycon_left_dark_disabled.png</file>
<file alias="applet_joycon_left_midnight_disabled">applet_single_joycon_left_midnight_disabled.png</file>
<file alias="applet_joycon_right_disabled">applet_single_joycon_right_disabled.png</file>
<file alias="applet_joycon_right_dark_disabled">applet_single_joycon_right_dark_disabled.png</file>
<file alias="applet_joycon_right_midnight_disabled">applet_single_joycon_right_midnight_disabled.png</file>
</qresource>
</RCC>

4749
dist/languages/de.ts vendored Normal file

File diff suppressed because it is too large Load Diff

4757
dist/languages/es.ts vendored Normal file

File diff suppressed because it is too large Load Diff

4732
dist/languages/fr.ts vendored Normal file

File diff suppressed because it is too large Load Diff

4724
dist/languages/it.ts vendored Normal file

File diff suppressed because it is too large Load Diff

4752
dist/languages/ja_JP.ts vendored Normal file

File diff suppressed because it is too large Load Diff

4719
dist/languages/nl.ts vendored Normal file

File diff suppressed because it is too large Load Diff

4713
dist/languages/pl.ts vendored Normal file

File diff suppressed because it is too large Load Diff

4757
dist/languages/pt_BR.ts vendored Normal file

File diff suppressed because it is too large Load Diff

4725
dist/languages/pt_PT.ts vendored Normal file

File diff suppressed because it is too large Load Diff

4720
dist/languages/ru_RU.ts vendored Normal file

File diff suppressed because it is too large Load Diff

4747
dist/languages/zh_CN.ts vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,7 @@
QAbstractSpinBox {
min-height: 19px;
}
QPushButton#TogglableStatusBarButton {
color: #959595;
border: 1px solid transparent;
@@ -35,10 +39,103 @@ QPushButton#RendererStatusBarButton:!checked {
}
QPushButton#buttonRefreshDevices {
min-width: 20px;
min-height: 20px;
max-width: 20px;
max-height: 20px;
min-width: 21px;
min-height: 21px;
max-width: 21px;
max-height: 21px;
}
QWidget#bottomPerGameInput,
QWidget#topControllerApplet,
QWidget#bottomControllerApplet,
QGroupBox#groupPlayer1Connected:checked,
QGroupBox#groupPlayer2Connected:checked,
QGroupBox#groupPlayer3Connected:checked,
QGroupBox#groupPlayer4Connected:checked,
QGroupBox#groupPlayer5Connected:checked,
QGroupBox#groupPlayer6Connected:checked,
QGroupBox#groupPlayer7Connected:checked,
QGroupBox#groupPlayer8Connected:checked {
background-color: #f5f5f5;
}
QWidget#topControllerApplet {
border-bottom: 1px solid #828790
}
QWidget#bottomPerGameInput,
QWidget#bottomControllerApplet {
border-top: 1px solid #828790
}
QWidget#topPerGameInput,
QWidget#middleControllerApplet {
background-color: #fff;
}
QWidget#topPerGameInput QComboBox,
QWidget#middleControllerApplet QComboBox {
width: 120px;
}
QWidget#connectedControllers {
background: transparent;
}
QWidget#playersSupported,
QWidget#controllersSupported,
QWidget#controllerSupported1,
QWidget#controllerSupported2,
QWidget#controllerSupported3,
QWidget#controllerSupported4,
QWidget#controllerSupported5,
QWidget#controllerSupported6 {
border: none;
background: transparent;
}
QGroupBox#groupPlayer1Connected,
QGroupBox#groupPlayer2Connected,
QGroupBox#groupPlayer3Connected,
QGroupBox#groupPlayer4Connected,
QGroupBox#groupPlayer5Connected,
QGroupBox#groupPlayer6Connected,
QGroupBox#groupPlayer7Connected,
QGroupBox#groupPlayer8Connected {
border: 1px solid #828790;
border-radius: 3px;
padding: 0px;
min-height: 98px;
max-height: 98px;
}
QGroupBox#groupPlayer1Connected:unchecked,
QGroupBox#groupPlayer2Connected:unchecked,
QGroupBox#groupPlayer3Connected:unchecked,
QGroupBox#groupPlayer4Connected:unchecked,
QGroupBox#groupPlayer5Connected:unchecked,
QGroupBox#groupPlayer6Connected:unchecked,
QGroupBox#groupPlayer7Connected:unchecked,
QGroupBox#groupPlayer8Connected:unchecked {
border: 1px solid #d9d9d9;
}
QGroupBox#groupPlayer1Connected::title,
QGroupBox#groupPlayer2Connected::title,
QGroupBox#groupPlayer3Connected::title,
QGroupBox#groupPlayer4Connected::title,
QGroupBox#groupPlayer5Connected::title,
QGroupBox#groupPlayer6Connected::title,
QGroupBox#groupPlayer7Connected::title,
QGroupBox#groupPlayer8Connected::title {
subcontrol-origin: margin;
subcontrol-position: top left;
padding-left: 0px;
padding-right: 0px;
padding-top: 1px;
margin-left: 0px;
margin-right: -4px;
margin-bottom: 4px;
}
QCheckBox#checkboxPlayer1Connected,
@@ -52,6 +149,42 @@ QCheckBox#checkboxPlayer8Connected {
spacing: 0px;
}
QWidget#Player1LEDs QCheckBox,
QWidget#Player2LEDs QCheckBox,
QWidget#Player3LEDs QCheckBox,
QWidget#Player4LEDs QCheckBox,
QWidget#Player5LEDs QCheckBox,
QWidget#Player6LEDs QCheckBox,
QWidget#Player7LEDs QCheckBox,
QWidget#Player8LEDs QCheckBox {
spacing: 0px;
}
QWidget#Player1LEDs QCheckBox::indicator,
QWidget#Player2LEDs QCheckBox::indicator,
QWidget#Player3LEDs QCheckBox::indicator,
QWidget#Player4LEDs QCheckBox::indicator,
QWidget#Player5LEDs QCheckBox::indicator,
QWidget#Player6LEDs QCheckBox::indicator,
QWidget#Player7LEDs QCheckBox::indicator,
QWidget#Player8LEDs QCheckBox::indicator {
width: 6px;
height: 6px;
margin-left: 0px;
}
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer1Connected::indicator,
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer2Connected::indicator,
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer3Connected::indicator,
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer4Connected::indicator,
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer5Connected::indicator,
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer6Connected::indicator,
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer7Connected::indicator,
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer8Connected::indicator {
width: 12px;
height: 12px;
}
QCheckBox#checkboxPlayer1Connected::indicator,
QCheckBox#checkboxPlayer2Connected::indicator,
QCheckBox#checkboxPlayer3Connected::indicator,
@@ -64,6 +197,34 @@ QCheckBox#checkboxPlayer8Connected::indicator {
height: 14px;
}
QGroupBox#groupPlayer1Connected::indicator,
QGroupBox#groupPlayer2Connected::indicator,
QGroupBox#groupPlayer3Connected::indicator,
QGroupBox#groupPlayer4Connected::indicator,
QGroupBox#groupPlayer5Connected::indicator,
QGroupBox#groupPlayer6Connected::indicator,
QGroupBox#groupPlayer7Connected::indicator,
QGroupBox#groupPlayer8Connected::indicator {
width: 16px;
height: 16px;
}
QWidget#Player1LEDs QCheckBox::indicator:checked,
QWidget#Player2LEDs QCheckBox::indicator:checked,
QWidget#Player3LEDs QCheckBox::indicator:checked,
QWidget#Player4LEDs QCheckBox::indicator:checked,
QWidget#Player5LEDs QCheckBox::indicator:checked,
QWidget#Player6LEDs QCheckBox::indicator:checked,
QWidget#Player7LEDs QCheckBox::indicator:checked,
QWidget#Player8LEDs QCheckBox::indicator:checked,
QGroupBox#groupPlayer1Connected::indicator:checked,
QGroupBox#groupPlayer2Connected::indicator:checked,
QGroupBox#groupPlayer3Connected::indicator:checked,
QGroupBox#groupPlayer4Connected::indicator:checked,
QGroupBox#groupPlayer5Connected::indicator:checked,
QGroupBox#groupPlayer6Connected::indicator:checked,
QGroupBox#groupPlayer7Connected::indicator:checked,
QGroupBox#groupPlayer8Connected::indicator:checked,
QCheckBox#checkboxPlayer1Connected::indicator:checked,
QCheckBox#checkboxPlayer2Connected::indicator:checked,
QCheckBox#checkboxPlayer3Connected::indicator:checked,
@@ -74,11 +235,27 @@ QCheckBox#checkboxPlayer7Connected::indicator:checked,
QCheckBox#checkboxPlayer8Connected::indicator:checked,
QGroupBox#groupConnectedController::indicator:checked {
border-radius: 2px;
border: 1px solid black;
border: 1px solid #929192;
background: #39ff14;
image: none;
}
QWidget#Player1LEDs QCheckBox::indicator:unchecked,
QWidget#Player2LEDs QCheckBox::indicator:unchecked,
QWidget#Player3LEDs QCheckBox::indicator:unchecked,
QWidget#Player4LEDs QCheckBox::indicator:unchecked,
QWidget#Player5LEDs QCheckBox::indicator:unchecked,
QWidget#Player6LEDs QCheckBox::indicator:unchecked,
QWidget#Player7LEDs QCheckBox::indicator:unchecked,
QWidget#Player8LEDs QCheckBox::indicator:unchecked,
QGroupBox#groupPlayer1Connected::indicator:unchecked,
QGroupBox#groupPlayer2Connected::indicator:unchecked,
QGroupBox#groupPlayer3Connected::indicator:unchecked,
QGroupBox#groupPlayer4Connected::indicator:unchecked,
QGroupBox#groupPlayer5Connected::indicator:unchecked,
QGroupBox#groupPlayer6Connected::indicator:unchecked,
QGroupBox#groupPlayer7Connected::indicator:unchecked,
QGroupBox#groupPlayer8Connected::indicator:unchecked,
QCheckBox#checkboxPlayer1Connected::indicator:unchecked,
QCheckBox#checkboxPlayer2Connected::indicator:unchecked,
QCheckBox#checkboxPlayer3Connected::indicator:unchecked,
@@ -89,7 +266,18 @@ QCheckBox#checkboxPlayer7Connected::indicator:unchecked,
QCheckBox#checkboxPlayer8Connected::indicator:unchecked,
QGroupBox#groupConnectedController::indicator:unchecked {
border-radius: 2px;
border: 1px solid black;
border: 1px solid #929192;
background: transparent;
image: none;
}
QWidget#controllerPlayer1,
QWidget#controllerPlayer2,
QWidget#controllerPlayer3,
QWidget#controllerPlayer4,
QWidget#controllerPlayer5,
QWidget#controllerPlayer6,
QWidget#controllerPlayer7,
QWidget#controllerPlayer8 {
background: transparent;
}

View File

@@ -52,6 +52,6 @@
<file>rc/radio_unchecked.png</file>
</qresource>
<qresource prefix="qdarkstyle">
<file>style.qss</file>
<file>style.qss</file>
</qresource>
</RCC>

View File

@@ -99,12 +99,19 @@ QGroupBox::indicator:unchecked:disabled {
}
QRadioButton {
spacing: 5px;
outline: none;
color: #eff0f1;
spacing: 3px;
padding: 0px;
border: none;
outline: none;
margin-bottom: 2px;
}
QGroupBox QRadioButton {
padding-left: 0px;
padding-right: 7px;
}
QRadioButton:disabled {
color: #76797C;
}
@@ -522,13 +529,12 @@ QToolButton#qt_toolbar_ext_button {
QPushButton {
color: #eff0f1;
border-width: 1px;
border-color: #54575B;
border-style: solid;
padding: 6px 4px;
border: 1px solid #54575B;
border-radius: 2px;
padding: 5px 0px 5px 0px;
outline: none;
min-width: 100px;
min-height: 13px;
background-color: #232629;
}
@@ -553,8 +559,9 @@ QComboBox {
selection-background-color: #3daee9;
border: 1px solid #54575B;
border-radius: 2px;
padding: 4px 6px;
min-width: 75px;
padding: 0px 4px 0px 4px;
min-width: 60px;
min-height: 23px;
background-color: #232629;
}
@@ -608,26 +615,26 @@ QComboBox::down-arrow:focus {
}
QAbstractSpinBox {
padding: 4px 6px;
border: 1px solid #54575B;
background-color: #232629;
color: #eff0f1;
border-radius: 2px;
min-width: 75px;
min-width: 52px;
min-height: 23px;
}
QAbstractSpinBox:up-button {
background-color: transparent;
subcontrol-origin: border;
subcontrol-position: center right;
left: -6px;
left: -2px;
}
QAbstractSpinBox:down-button {
background-color: transparent;
subcontrol-origin: border;
subcontrol-position: center left;
right: -6px;
right: -2px;
}
QAbstractSpinBox::up-arrow,
@@ -1277,13 +1284,125 @@ QPushButton#RendererStatusBarButton:!checked {
}
QPushButton#buttonRefreshDevices {
min-width: 24px;
min-height: 24px;
max-width: 24px;
max-height: 24px;
min-width: 23px;
min-height: 23px;
max-width: 23px;
max-height: 23px;
padding: 0px 0px;
}
QSpinBox#spinboxLStickRange,
QSpinBox#spinboxRStickRange,
QSpinBox#vibrationSpinPlayer1,
QSpinBox#vibrationSpinPlayer2,
QSpinBox#vibrationSpinPlayer3,
QSpinBox#vibrationSpinPlayer4,
QSpinBox#vibrationSpinPlayer5,
QSpinBox#vibrationSpinPlayer6,
QSpinBox#vibrationSpinPlayer7,
QSpinBox#vibrationSpinPlayer8 {
min-width: 68px;
}
QDialog#ConfigureVibration QGroupBox::indicator,
QGroupBox#motionGroup::indicator,
QGroupBox#vibrationGroup::indicator {
margin-left: 0px;
}
QDialog#ConfigureVibration QGroupBox::title,
QGroupBox#motionGroup::title,
QGroupBox#vibrationGroup::title {
spacing: 2px;
padding-left: 1px;
padding-right: 1px;
}
QWidget#bottomPerGameInput,
QWidget#topControllerApplet,
QWidget#bottomControllerApplet,
QGroupBox#groupPlayer1Connected:checked,
QGroupBox#groupPlayer2Connected:checked,
QGroupBox#groupPlayer3Connected:checked,
QGroupBox#groupPlayer4Connected:checked,
QGroupBox#groupPlayer5Connected:checked,
QGroupBox#groupPlayer6Connected:checked,
QGroupBox#groupPlayer7Connected:checked,
QGroupBox#groupPlayer8Connected:checked {
background-color: #232629;
}
QWidget#topPerGameInput,
QWidget#middleControllerApplet {
background-color: #31363b;
}
QWidget#topPerGameInput QComboBox,
QWidget#middleControllerApplet QComboBox {
width: 120px;
}
QWidget#connectedControllers {
background: transparent;
}
QWidget#playersSupported,
QWidget#controllersSupported,
QWidget#controllerSupported1,
QWidget#controllerSupported2,
QWidget#controllerSupported3,
QWidget#controllerSupported4,
QWidget#controllerSupported5,
QWidget#controllerSupported6 {
border: none;
background: transparent;
}
QGroupBox#groupPlayer1Connected,
QGroupBox#groupPlayer2Connected,
QGroupBox#groupPlayer3Connected,
QGroupBox#groupPlayer4Connected,
QGroupBox#groupPlayer5Connected,
QGroupBox#groupPlayer6Connected,
QGroupBox#groupPlayer7Connected,
QGroupBox#groupPlayer8Connected {
border: 1px solid #76797c;
border-radius: 3px;
padding: 0px;
min-height: 98px;
max-height: 98px;
margin-top: 0px;
}
QGroupBox#groupPlayer1Connected:unchecked,
QGroupBox#groupPlayer2Connected:unchecked,
QGroupBox#groupPlayer3Connected:unchecked,
QGroupBox#groupPlayer4Connected:unchecked,
QGroupBox#groupPlayer5Connected:unchecked,
QGroupBox#groupPlayer6Connected:unchecked,
QGroupBox#groupPlayer7Connected:unchecked,
QGroupBox#groupPlayer8Connected:unchecked {
border: 1px solid #54575b;
}
QGroupBox#groupPlayer1Connected::title,
QGroupBox#groupPlayer2Connected::title,
QGroupBox#groupPlayer3Connected::title,
QGroupBox#groupPlayer4Connected::title,
QGroupBox#groupPlayer5Connected::title,
QGroupBox#groupPlayer6Connected::title,
QGroupBox#groupPlayer7Connected::title,
QGroupBox#groupPlayer8Connected::title {
subcontrol-origin: margin;
subcontrol-position: top left;
padding-left: 0px;
padding-right: 0px;
padding-top: 1px;
margin-left: -2px;
margin-right: -4px;
margin-bottom: 6px;
}
QCheckBox#checkboxPlayer1Connected,
QCheckBox#checkboxPlayer2Connected,
QCheckBox#checkboxPlayer3Connected,
@@ -1295,6 +1414,55 @@ QCheckBox#checkboxPlayer8Connected {
spacing: 0px;
}
QWidget#Player1LEDs,
QWidget#Player2LEDs,
QWidget#Player3LEDs,
QWidget#Player4LEDs,
QWidget#Player5LEDs,
QWidget#Player6LEDs,
QWidget#Player7LEDs,
QWidget#Player8LEDs {
background: transparent;
}
QWidget#Player1LEDs QCheckBox,
QWidget#Player2LEDs QCheckBox,
QWidget#Player3LEDs QCheckBox,
QWidget#Player4LEDs QCheckBox,
QWidget#Player5LEDs QCheckBox,
QWidget#Player6LEDs QCheckBox,
QWidget#Player7LEDs QCheckBox,
QWidget#Player8LEDs QCheckBox {
spacing: 0px;
margin-bottom: 0px;
margin-right: 0px;
}
QWidget#Player1LEDs QCheckBox::indicator,
QWidget#Player2LEDs QCheckBox::indicator,
QWidget#Player3LEDs QCheckBox::indicator,
QWidget#Player4LEDs QCheckBox::indicator,
QWidget#Player5LEDs QCheckBox::indicator,
QWidget#Player6LEDs QCheckBox::indicator,
QWidget#Player7LEDs QCheckBox::indicator,
QWidget#Player8LEDs QCheckBox::indicator {
width: 6px;
height: 6px;
margin-left: 0px;
}
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer1Connected::indicator,
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer2Connected::indicator,
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer3Connected::indicator,
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer4Connected::indicator,
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer5Connected::indicator,
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer6Connected::indicator,
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer7Connected::indicator,
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer8Connected::indicator {
width: 12px;
height: 12px;
}
QCheckBox#checkboxPlayer1Connected::indicator,
QCheckBox#checkboxPlayer2Connected::indicator,
QCheckBox#checkboxPlayer3Connected::indicator,
@@ -1307,6 +1475,34 @@ QCheckBox#checkboxPlayer8Connected::indicator {
height: 14px;
}
QGroupBox#groupPlayer1Connected::indicator,
QGroupBox#groupPlayer2Connected::indicator,
QGroupBox#groupPlayer3Connected::indicator,
QGroupBox#groupPlayer4Connected::indicator,
QGroupBox#groupPlayer5Connected::indicator,
QGroupBox#groupPlayer6Connected::indicator,
QGroupBox#groupPlayer7Connected::indicator,
QGroupBox#groupPlayer8Connected::indicator {
width: 16px;
height: 16px;
}
QWidget#Player1LEDs QCheckBox::indicator:checked,
QWidget#Player2LEDs QCheckBox::indicator:checked,
QWidget#Player3LEDs QCheckBox::indicator:checked,
QWidget#Player4LEDs QCheckBox::indicator:checked,
QWidget#Player5LEDs QCheckBox::indicator:checked,
QWidget#Player6LEDs QCheckBox::indicator:checked,
QWidget#Player7LEDs QCheckBox::indicator:checked,
QWidget#Player8LEDs QCheckBox::indicator:checked,
QGroupBox#groupPlayer1Connected::indicator:checked,
QGroupBox#groupPlayer2Connected::indicator:checked,
QGroupBox#groupPlayer3Connected::indicator:checked,
QGroupBox#groupPlayer4Connected::indicator:checked,
QGroupBox#groupPlayer5Connected::indicator:checked,
QGroupBox#groupPlayer6Connected::indicator:checked,
QGroupBox#groupPlayer7Connected::indicator:checked,
QGroupBox#groupPlayer8Connected::indicator:checked,
QCheckBox#checkboxPlayer1Connected::indicator:checked,
QCheckBox#checkboxPlayer2Connected::indicator:checked,
QCheckBox#checkboxPlayer3Connected::indicator:checked,
@@ -1322,6 +1518,22 @@ QGroupBox#groupConnectedController::indicator:checked {
image: none;
}
QWidget#Player1LEDs QCheckBox::indicator:unchecked,
QWidget#Player2LEDs QCheckBox::indicator:unchecked,
QWidget#Player3LEDs QCheckBox::indicator:unchecked,
QWidget#Player4LEDs QCheckBox::indicator:unchecked,
QWidget#Player5LEDs QCheckBox::indicator:unchecked,
QWidget#Player6LEDs QCheckBox::indicator:unchecked,
QWidget#Player7LEDs QCheckBox::indicator:unchecked,
QWidget#Player8LEDs QCheckBox::indicator:unchecked,
QGroupBox#groupPlayer1Connected::indicator:unchecked,
QGroupBox#groupPlayer2Connected::indicator:unchecked,
QGroupBox#groupPlayer3Connected::indicator:unchecked,
QGroupBox#groupPlayer4Connected::indicator:unchecked,
QGroupBox#groupPlayer5Connected::indicator:unchecked,
QGroupBox#groupPlayer6Connected::indicator:unchecked,
QGroupBox#groupPlayer7Connected::indicator:unchecked,
QGroupBox#groupPlayer8Connected::indicator:unchecked,
QCheckBox#checkboxPlayer1Connected::indicator:unchecked,
QCheckBox#checkboxPlayer2Connected::indicator:unchecked,
QCheckBox#checkboxPlayer3Connected::indicator:unchecked,
@@ -1337,39 +1549,15 @@ QGroupBox#groupConnectedController::indicator:unchecked {
image: none;
}
QSpinBox#spinboxLStickRange,
QSpinBox#spinboxRStickRange {
padding: 4px 0px 5px 0px;
min-width: 63px;
}
QSpinBox#vibrationSpin {
padding: 4px 0px 5px 0px;
min-width: 63px;
}
QSpinBox#spinboxLStickRange:up-button,
QSpinBox#spinboxRStickRange:up-button,
QSpinBox#vibrationSpin:up-button {
left: -2px;
}
QSpinBox#spinboxLStickRange:down-button,
QSpinBox#spinboxRStickRange:down-button,
QSpinBox#vibrationSpin:down-button {
right: -1px;
}
QGroupBox#motionGroup::indicator,
QGroupBox#vibrationGroup::indicator {
margin-left: 0px;
}
QGroupBox#motionGroup::title,
QGroupBox#vibrationGroup::title {
spacing: 2px;
padding-left: 1px;
padding-right: 1px;
QWidget#controllerPlayer1,
QWidget#controllerPlayer2,
QWidget#controllerPlayer3,
QWidget#controllerPlayer4,
QWidget#controllerPlayer5,
QWidget#controllerPlayer6,
QWidget#controllerPlayer7,
QWidget#controllerPlayer8 {
background: transparent;
}
/* touchscreen mapping widget */

View File

@@ -221,6 +221,6 @@
<file>rc/window_undock_pressed@2x.png</file>
</qresource>
<qresource prefix="qdarkstyle_midnight_blue">
<file>style.qss</file>
<file>style.qss</file>
</qresource>
</RCC>

View File

@@ -172,8 +172,8 @@ QCheckBox {
color: #F0F0F0;
spacing: 4px;
outline: none;
padding-top: 4px;
padding-bottom: 4px;
padding-top: 2px;
padding-bottom: 2px;
}
QCheckBox:focus {
@@ -235,19 +235,19 @@ https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qgroupbox
--------------------------------------------------------------------------- */
QGroupBox {
font-weight: bold;
border: 1px solid #32414B;
border-radius: 4px;
margin-top: 12px;
padding: 4px;
font-weight: bold;
border: 1px solid #32414B;
border-radius: 4px;
margin-top: 12px;
padding: 2px;
}
QGroupBox::title {
subcontrol-origin: margin;
subcontrol-position: top left;
padding-left: 3px;
padding-right: 5px;
padding-top: 4px;
subcontrol-origin: margin;
subcontrol-position: top left;
padding-left: 3px;
padding-right: 5px;
padding-top: 2px;
}
QGroupBox::indicator {
@@ -298,6 +298,11 @@ QRadioButton {
outline: none;
}
QGroupBox QRadioButton {
padding-left: 0px;
padding-right: 7px;
}
QRadioButton:focus {
border: none;
}
@@ -321,7 +326,6 @@ QRadioButton QWidget {
QRadioButton::indicator {
border: none;
outline: none;
margin-left: 4px;
height: 16px;
width: 16px;
}
@@ -785,14 +789,8 @@ QAbstractSpinBox {
background-color: #19232D;
border: 1px solid #32414B;
color: #F0F0F0;
/* This fixes 103, 111 */
padding-top: 2px;
/* This fixes 103, 111 */
padding-bottom: 2px;
padding-left: 4px;
padding-right: 4px;
border-radius: 4px;
/* min-width: 5px; removed to fix 109 */
min-height: 19px;
}
QAbstractSpinBox:up-button {
@@ -997,10 +995,11 @@ QPushButton {
border: 1px solid #32414B;
color: #F0F0F0;
border-radius: 4px;
padding: 3px;
padding: 3px 0px 3px 0px;
outline: none;
/* Issue #194 - Special case of QPushButton inside dialogs, for better UI */
min-width: 80px;
min-height: 13px;
}
QPushButton:disabled {
@@ -1008,14 +1007,14 @@ QPushButton:disabled {
border: 1px solid #32414B;
color: #787878;
border-radius: 4px;
padding: 3px;
padding: 3px 0px 3px 0px;
}
QPushButton:checked {
background-color: #32414B;
border: 1px solid #32414B;
border-radius: 4px;
padding: 3px;
padding: 3px 0px 3px 0px;
outline: none;
}
@@ -1024,7 +1023,7 @@ QPushButton:checked:disabled {
border: 1px solid #32414B;
color: #787878;
border-radius: 4px;
padding: 3px;
padding: 3px 0px 3px 0px;
outline: none;
}
@@ -1197,15 +1196,9 @@ QComboBox {
border: 1px solid #32414B;
border-radius: 4px;
selection-background-color: #1464A0;
padding-left: 4px;
padding-right: 36px;
/* 4 + 16*2 See scrollbar size */
/* Fixes #103, #111 */
min-height: 1.5em;
/* padding-top: 2px; removed to fix #132 */
/* padding-bottom: 2px; removed to fix #132 */
/* min-width: 75px; removed to fix #109 */
/* Needed to remove indicator - fix #132 */
padding: 0px 4px 0px 4px;
min-width: 60px;
min-height: 19px;
}
QComboBox QAbstractItemView {
@@ -2198,13 +2191,143 @@ QPushButton#RendererStatusBarButton:!checked {
}
QPushButton#buttonRefreshDevices {
min-width: 20px;
min-height: 20px;
max-width: 20px;
max-height: 20px;
min-width: 19px;
min-height: 19px;
max-width: 19px;
max-height: 19px;
padding: 0px 0px;
}
QSpinBox#spinboxLStickRange,
QSpinBox#spinboxRStickRange,
QSpinBox#vibrationSpinPlayer1,
QSpinBox#vibrationSpinPlayer2,
QSpinBox#vibrationSpinPlayer3,
QSpinBox#vibrationSpinPlayer4,
QSpinBox#vibrationSpinPlayer5,
QSpinBox#vibrationSpinPlayer6,
QSpinBox#vibrationSpinPlayer7,
QSpinBox#vibrationSpinPlayer8 {
min-width: 68px;
}
QDialog#ConfigureVibration QGroupBox::indicator,
QGroupBox#motionGroup::indicator,
QGroupBox#vibrationGroup::indicator {
margin-left: 0px;
}
QDialog#ConfigureVibration QGroupBox,
QWidget#bottomPerGameInput QGroupBox#motionGroup,
QWidget#bottomPerGameInput QGroupBox#vibrationGroup,
QWidget#bottomPerGameInput QGroupBox#inputConfigGroup {
padding: 0px;
}
QDialog#ConfigureVibration QGroupBox::title,
QGroupBox#motionGroup::title,
QGroupBox#vibrationGroup::title {
spacing: 2px;
padding-left: 1px;
padding-right: 1px;
}
QListWidget#selectorList {
background-color: #0f1922;
}
QSpinBox,
QLineEdit,
QTreeView#hotkey_list,
QScrollArea#scrollArea QTreeView {
background-color: #0f1922;
}
QWidget#bottomPerGameInput,
QWidget#topControllerApplet,
QWidget#bottomControllerApplet,
QGroupBox#groupPlayer1Connected:checked,
QGroupBox#groupPlayer2Connected:checked,
QGroupBox#groupPlayer3Connected:checked,
QGroupBox#groupPlayer4Connected:checked,
QGroupBox#groupPlayer5Connected:checked,
QGroupBox#groupPlayer6Connected:checked,
QGroupBox#groupPlayer7Connected:checked,
QGroupBox#groupPlayer8Connected:checked {
background-color: #0f1922;
}
QWidget#topPerGameInput,
QWidget#middleControllerApplet {
background-color: #19232d;
}
QWidget#topPerGameInput QComboBox,
QWidget#middleControllerApplet QComboBox {
width: 120px;
}
QWidget#connectedControllers {
background: transparent;
}
QWidget#playersSupported,
QWidget#controllersSupported,
QWidget#controllerSupported1,
QWidget#controllerSupported2,
QWidget#controllerSupported3,
QWidget#controllerSupported4,
QWidget#controllerSupported5,
QWidget#controllerSupported6 {
border: none;
background: transparent;
}
QGroupBox#groupPlayer1Connected,
QGroupBox#groupPlayer2Connected,
QGroupBox#groupPlayer3Connected,
QGroupBox#groupPlayer4Connected,
QGroupBox#groupPlayer5Connected,
QGroupBox#groupPlayer6Connected,
QGroupBox#groupPlayer7Connected,
QGroupBox#groupPlayer8Connected {
border: 1px solid #76797c;
border-radius: 3px;
padding: 0px;
min-height: 98px;
max-height: 98px;
margin-top: 0px;
}
QGroupBox#groupPlayer1Connected:unchecked,
QGroupBox#groupPlayer2Connected:unchecked,
QGroupBox#groupPlayer3Connected:unchecked,
QGroupBox#groupPlayer4Connected:unchecked,
QGroupBox#groupPlayer5Connected:unchecked,
QGroupBox#groupPlayer6Connected:unchecked,
QGroupBox#groupPlayer7Connected:unchecked,
QGroupBox#groupPlayer8Connected:unchecked {
border: 1px solid #32414b;
}
QGroupBox#groupPlayer1Connected::title,
QGroupBox#groupPlayer2Connected::title,
QGroupBox#groupPlayer3Connected::title,
QGroupBox#groupPlayer4Connected::title,
QGroupBox#groupPlayer5Connected::title,
QGroupBox#groupPlayer6Connected::title,
QGroupBox#groupPlayer7Connected::title,
QGroupBox#groupPlayer8Connected::title {
subcontrol-origin: margin;
subcontrol-position: top left;
padding-left: 0px;
padding-right: 0px;
padding-top: 1px;
margin-left: -2px;
margin-right: -4px;
margin-bottom: 6px;
}
QCheckBox#checkboxPlayer1Connected,
QCheckBox#checkboxPlayer2Connected,
@@ -2217,6 +2340,68 @@ QCheckBox#checkboxPlayer8Connected {
spacing: 0px;
}
QWidget#connectedControllers QLabel {
padding: 0px;
}
QWidget#Player1LEDs,
QWidget#Player2LEDs,
QWidget#Player3LEDs,
QWidget#Player4LEDs,
QWidget#Player5LEDs,
QWidget#Player6LEDs,
QWidget#Player7LEDs,
QWidget#Player8LEDs {
background: transparent;
}
QWidget#Player1LEDs QCheckBox,
QWidget#Player2LEDs QCheckBox,
QWidget#Player3LEDs QCheckBox,
QWidget#Player4LEDs QCheckBox,
QWidget#Player5LEDs QCheckBox,
QWidget#Player6LEDs QCheckBox,
QWidget#Player7LEDs QCheckBox,
QWidget#Player8LEDs QCheckBox,
QCheckBox#checkboxPlayer1Connected,
QCheckBox#checkboxPlayer2Connected,
QCheckBox#checkboxPlayer3Connected,
QCheckBox#checkboxPlayer4Connected,
QCheckBox#checkboxPlayer5Connected,
QCheckBox#checkboxPlayer6Connected,
QCheckBox#checkboxPlayer7Connected,
QCheckBox#checkboxPlayer8Connected {
spacing: 0px;
padding-top: 0px;
padding-bottom: 0px;
background: transparent;
}
QWidget#Player1LEDs QCheckBox::indicator,
QWidget#Player2LEDs QCheckBox::indicator,
QWidget#Player3LEDs QCheckBox::indicator,
QWidget#Player4LEDs QCheckBox::indicator,
QWidget#Player5LEDs QCheckBox::indicator,
QWidget#Player6LEDs QCheckBox::indicator,
QWidget#Player7LEDs QCheckBox::indicator,
QWidget#Player8LEDs QCheckBox::indicator {
width: 6px;
height: 6px;
margin-left: 0px;
}
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer1Connected::indicator,
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer2Connected::indicator,
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer3Connected::indicator,
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer4Connected::indicator,
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer5Connected::indicator,
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer6Connected::indicator,
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer7Connected::indicator,
QWidget#bottomPerGameInput QCheckBox#checkboxPlayer8Connected::indicator {
width: 12px;
height: 12px;
}
QCheckBox#checkboxPlayer1Connected::indicator,
QCheckBox#checkboxPlayer2Connected::indicator,
QCheckBox#checkboxPlayer3Connected::indicator,
@@ -2227,8 +2412,25 @@ QCheckBox#checkboxPlayer7Connected::indicator,
QCheckBox#checkboxPlayer8Connected::indicator {
width: 14px;
height: 14px;
margin-left: 0px;
}
QWidget#Player1LEDs QCheckBox::indicator:checked,
QWidget#Player2LEDs QCheckBox::indicator:checked,
QWidget#Player3LEDs QCheckBox::indicator:checked,
QWidget#Player4LEDs QCheckBox::indicator:checked,
QWidget#Player5LEDs QCheckBox::indicator:checked,
QWidget#Player6LEDs QCheckBox::indicator:checked,
QWidget#Player7LEDs QCheckBox::indicator:checked,
QWidget#Player8LEDs QCheckBox::indicator:checked,
QGroupBox#groupPlayer1Connected::indicator:checked,
QGroupBox#groupPlayer2Connected::indicator:checked,
QGroupBox#groupPlayer3Connected::indicator:checked,
QGroupBox#groupPlayer4Connected::indicator:checked,
QGroupBox#groupPlayer5Connected::indicator:checked,
QGroupBox#groupPlayer6Connected::indicator:checked,
QGroupBox#groupPlayer7Connected::indicator:checked,
QGroupBox#groupPlayer8Connected::indicator:checked,
QCheckBox#checkboxPlayer1Connected::indicator:checked,
QCheckBox#checkboxPlayer2Connected::indicator:checked,
QCheckBox#checkboxPlayer3Connected::indicator:checked,
@@ -2244,6 +2446,22 @@ QGroupBox#groupConnectedController::indicator:checked {
image: none;
}
QWidget#Player1LEDs QCheckBox::indicator:unchecked,
QWidget#Player2LEDs QCheckBox::indicator:unchecked,
QWidget#Player3LEDs QCheckBox::indicator:unchecked,
QWidget#Player4LEDs QCheckBox::indicator:unchecked,
QWidget#Player5LEDs QCheckBox::indicator:unchecked,
QWidget#Player6LEDs QCheckBox::indicator:unchecked,
QWidget#Player7LEDs QCheckBox::indicator:unchecked,
QWidget#Player8LEDs QCheckBox::indicator:unchecked,
QGroupBox#groupPlayer1Connected::indicator:unchecked,
QGroupBox#groupPlayer2Connected::indicator:unchecked,
QGroupBox#groupPlayer3Connected::indicator:unchecked,
QGroupBox#groupPlayer4Connected::indicator:unchecked,
QGroupBox#groupPlayer5Connected::indicator:unchecked,
QGroupBox#groupPlayer6Connected::indicator:unchecked,
QGroupBox#groupPlayer7Connected::indicator:unchecked,
QGroupBox#groupPlayer8Connected::indicator:unchecked,
QCheckBox#checkboxPlayer1Connected::indicator:unchecked,
QCheckBox#checkboxPlayer2Connected::indicator:unchecked,
QCheckBox#checkboxPlayer3Connected::indicator:unchecked,
@@ -2255,34 +2473,17 @@ QCheckBox#checkboxPlayer8Connected::indicator:unchecked,
QGroupBox#groupConnectedController::indicator:unchecked {
border-radius: 2px;
border: 1px solid #929192;
background: transparent;
background: #19232d;
image: none;
}
QSpinBox#spinboxLStickRange,
QSpinBox#spinboxRStickRange {
min-width: 38px;
QWidget#controllerPlayer1,
QWidget#controllerPlayer2,
QWidget#controllerPlayer3,
QWidget#controllerPlayer4,
QWidget#controllerPlayer5,
QWidget#controllerPlayer6,
QWidget#controllerPlayer7,
QWidget#controllerPlayer8 {
background: transparent;
}
QGroupBox#motionGroup::indicator,
QGroupBox#vibrationGroup::indicator {
margin-left: 0px;
}
QGroupBox#motionGroup::title,
QGroupBox#vibrationGroup::title {
spacing: 2px;
padding-left: 1px;
padding-right: 1px;
}
QListWidget#selectorList {
background-color: #0f1922;
}
QSpinBox,
QLineEdit,
QTreeView#hotkey_list,
QScrollArea#scrollArea QTreeView {
background-color: #0f1922;
}

View File

@@ -73,23 +73,29 @@ if (NOT LIBZIP_FOUND)
endif()
if (ENABLE_WEB_SERVICE)
# LibreSSL
set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "")
add_subdirectory(libressl EXCLUDE_FROM_ALL)
target_include_directories(ssl INTERFACE ./libressl/include)
target_compile_definitions(ssl PRIVATE -DHAVE_INET_NTOP)
get_directory_property(OPENSSL_LIBRARIES
DIRECTORY libressl
DEFINITION OPENSSL_LIBS)
# lurlparser
add_subdirectory(lurlparser EXCLUDE_FROM_ALL)
find_package(OpenSSL 1.1)
if (OPENSSL_FOUND)
set(OPENSSL_LIBRARIES OpenSSL::SSL OpenSSL::Crypto)
else()
# LibreSSL
set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "")
set(OPENSSLDIR "/etc/ssl/")
add_subdirectory(libressl EXCLUDE_FROM_ALL)
target_include_directories(ssl INTERFACE ./libressl/include)
target_compile_definitions(ssl PRIVATE -DHAVE_INET_NTOP)
get_directory_property(OPENSSL_LIBRARIES
DIRECTORY libressl
DEFINITION OPENSSL_LIBS)
endif()
# httplib
add_library(httplib INTERFACE)
target_include_directories(httplib INTERFACE ./httplib)
target_compile_definitions(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT)
target_link_libraries(httplib INTERFACE ${OPENSSL_LIBRARIES})
if (WIN32)
target_link_libraries(httplib INTERFACE crypt32 cryptui ws2_32)
endif()
endif()
# Opus

100
externals/find-modules/FindFFmpeg.cmake vendored Normal file
View File

@@ -0,0 +1,100 @@
# - Try to find ffmpeg libraries (libavcodec, libavformat and libavutil)
# Once done this will define
#
# FFMPEG_FOUND - system has ffmpeg or libav
# FFMPEG_INCLUDE_DIR - the ffmpeg include directory
# FFMPEG_LIBRARIES - Link these to use ffmpeg
# FFMPEG_LIBAVCODEC
# FFMPEG_LIBAVFORMAT
# FFMPEG_LIBAVUTIL
#
# Copyright (c) 2008 Andreas Schneider <mail@cynapses.org>
# Modified for other libraries by Lasse Kärkkäinen <tronic>
# Modified for Hedgewars by Stepik777
# Modified for FFmpeg-example Tuukka Pasanen 2018
# Modified for yuzu toastUnlimted 2020
#
# Redistribution and use is allowed according to the terms of the New
# BSD license.
#
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(FFMPEG
FOUND_VAR FFMPEG_FOUND
REQUIRED_VARS
FFMPEG_LIBRARY
FFMPEG_INCLUDE_DIR
VERSION_VAR FFMPEG_VERSION
)
if(FFMPEG_LIBRARIES AND FFMPEG_INCLUDE_DIR)
# in cache already
set(FFMPEG_FOUND TRUE)
else()
# use pkg-config to get the directories and then use these values
# in the FIND_PATH() and FIND_LIBRARY() calls
find_package(PkgConfig)
if(PKG_CONFIG_FOUND)
pkg_check_modules(_FFMPEG_AVCODEC libavcodec)
pkg_check_modules(_FFMPEG_AVUTIL libavutil)
pkg_check_modules(_FFMPEG_SWSCALE libswscale)
endif()
find_path(FFMPEG_AVCODEC_INCLUDE_DIR
NAMES libavcodec/avcodec.h
PATHS ${_FFMPEG_AVCODEC_INCLUDE_DIRS}
/usr/include
/usr/local/include
/opt/local/include
/sw/include
PATH_SUFFIXES ffmpeg libav)
find_library(FFMPEG_LIBAVCODEC
NAMES avcodec
PATHS ${_FFMPEG_AVCODEC_LIBRARY_DIRS}
/usr/lib
/usr/local/lib
/opt/local/lib
/sw/lib)
find_library(FFMPEG_LIBAVUTIL
NAMES avutil
PATHS ${_FFMPEG_AVUTIL_LIBRARY_DIRS}
/usr/lib
/usr/local/lib
/opt/local/lib
/sw/lib)
find_library(FFMPEG_LIBSWSCALE
NAMES swscale
PATHS ${_FFMPEG_SWSCALE_LIBRARY_DIRS}
/usr/lib
/usr/local/lib
/opt/local/lib
/sw/lib)
if(FFMPEG_LIBAVCODEC AND FFMPEG_LIBAVUTIL AND FFMPEG_LIBSWSCALE)
set(FFMPEG_FOUND TRUE)
endif()
if(FFMPEG_FOUND)
set(FFMPEG_INCLUDE_DIR ${FFMPEG_AVCODEC_INCLUDE_DIR})
set(FFMPEG_LIBRARIES
${FFMPEG_LIBAVCODEC}
${FFMPEG_LIBAVUTIL}
${FFMPEG_LIBSWSCALE})
endif()
if(FFMPEG_FOUND)
if(NOT FFMPEG_FIND_QUIETLY)
message(STATUS
"Found FFMPEG or Libav: ${FFMPEG_LIBRARIES}, ${FFMPEG_INCLUDE_DIR}")
endif()
else()
if(FFMPEG_FIND_REQUIRED)
message(FATAL_ERROR
"Could not find libavcodec or libavutil or libswscale")
endif()
endif()
endif()

View File

@@ -1,4 +1,4 @@
From https://github.com/yhirose/cpp-httplib/tree/fce8e6fefdab4ad48bc5b25c98e5ebfda4f3cf53
From https://github.com/yhirose/cpp-httplib/tree/ff5677ad197947177c158fe857caff4f0e242045 with https://github.com/yhirose/cpp-httplib/pull/701
MIT License

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +0,0 @@
add_library(lurlparser
LUrlParser.cpp
LUrlParser.h
)
create_target_directory_groups(lurlparser)
target_include_directories(lurlparser INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})

View File

@@ -1,265 +0,0 @@
/*
* Lightweight URL & URI parser (RFC 1738, RFC 3986)
* https://github.com/corporateshark/LUrlParser
*
* The MIT License (MIT)
*
* Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "LUrlParser.h"
#include <algorithm>
#include <cstring>
#include <stdlib.h>
// check if the scheme name is valid
static bool IsSchemeValid( const std::string& SchemeName )
{
for ( auto c : SchemeName )
{
if ( !isalpha( c ) && c != '+' && c != '-' && c != '.' ) return false;
}
return true;
}
bool LUrlParser::clParseURL::GetPort( int* OutPort ) const
{
if ( !IsValid() ) { return false; }
int Port = atoi( m_Port.c_str() );
if ( Port <= 0 || Port > 65535 ) { return false; }
if ( OutPort ) { *OutPort = Port; }
return true;
}
// based on RFC 1738 and RFC 3986
LUrlParser::clParseURL LUrlParser::clParseURL::ParseURL( const std::string& URL )
{
LUrlParser::clParseURL Result;
const char* CurrentString = URL.c_str();
/*
* <scheme>:<scheme-specific-part>
* <scheme> := [a-z\+\-\.]+
* For resiliency, programs interpreting URLs should treat upper case letters as equivalent to lower case in scheme names
*/
// try to read scheme
{
const char* LocalString = strchr( CurrentString, ':' );
if ( !LocalString )
{
return clParseURL( LUrlParserError_NoUrlCharacter );
}
// save the scheme name
Result.m_Scheme = std::string( CurrentString, LocalString - CurrentString );
if ( !IsSchemeValid( Result.m_Scheme ) )
{
return clParseURL( LUrlParserError_InvalidSchemeName );
}
// scheme should be lowercase
std::transform( Result.m_Scheme.begin(), Result.m_Scheme.end(), Result.m_Scheme.begin(), ::tolower );
// skip ':'
CurrentString = LocalString+1;
}
/*
* //<user>:<password>@<host>:<port>/<url-path>
* any ":", "@" and "/" must be normalized
*/
// skip "//"
if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError_NoDoubleSlash );
if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError_NoDoubleSlash );
// check if the user name and password are specified
bool bHasUserName = false;
const char* LocalString = CurrentString;
while ( *LocalString )
{
if ( *LocalString == '@' )
{
// user name and password are specified
bHasUserName = true;
break;
}
else if ( *LocalString == '/' )
{
// end of <host>:<port> specification
bHasUserName = false;
break;
}
LocalString++;
}
// user name and password
LocalString = CurrentString;
if ( bHasUserName )
{
// read user name
while ( *LocalString && *LocalString != ':' && *LocalString != '@' ) LocalString++;
Result.m_UserName = std::string( CurrentString, LocalString - CurrentString );
// proceed with the current pointer
CurrentString = LocalString;
if ( *CurrentString == ':' )
{
// skip ':'
CurrentString++;
// read password
LocalString = CurrentString;
while ( *LocalString && *LocalString != '@' ) LocalString++;
Result.m_Password = std::string( CurrentString, LocalString - CurrentString );
CurrentString = LocalString;
}
// skip '@'
if ( *CurrentString != '@' )
{
return clParseURL( LUrlParserError_NoAtSign );
}
CurrentString++;
}
bool bHasBracket = ( *CurrentString == '[' );
// go ahead, read the host name
LocalString = CurrentString;
while ( *LocalString )
{
if ( bHasBracket && *LocalString == ']' )
{
// end of IPv6 address
LocalString++;
break;
}
else if ( !bHasBracket && ( *LocalString == ':' || *LocalString == '/' ) )
{
// port number is specified
break;
}
LocalString++;
}
Result.m_Host = std::string( CurrentString, LocalString - CurrentString );
CurrentString = LocalString;
// is port number specified?
if ( *CurrentString == ':' )
{
CurrentString++;
// read port number
LocalString = CurrentString;
while ( *LocalString && *LocalString != '/' ) LocalString++;
Result.m_Port = std::string( CurrentString, LocalString - CurrentString );
CurrentString = LocalString;
}
// end of string
if ( !*CurrentString )
{
Result.m_ErrorCode = LUrlParserError_Ok;
return Result;
}
// skip '/'
if ( *CurrentString != '/' )
{
return clParseURL( LUrlParserError_NoSlash );
}
CurrentString++;
// parse the path
LocalString = CurrentString;
while ( *LocalString && *LocalString != '#' && *LocalString != '?' ) LocalString++;
Result.m_Path = std::string( CurrentString, LocalString - CurrentString );
CurrentString = LocalString;
// check for query
if ( *CurrentString == '?' )
{
// skip '?'
CurrentString++;
// read query
LocalString = CurrentString;
while ( *LocalString && *LocalString != '#' ) LocalString++;
Result.m_Query = std::string( CurrentString, LocalString - CurrentString );
CurrentString = LocalString;
}
// check for fragment
if ( *CurrentString == '#' )
{
// skip '#'
CurrentString++;
// read fragment
LocalString = CurrentString;
while ( *LocalString ) LocalString++;
Result.m_Fragment = std::string( CurrentString, LocalString - CurrentString );
CurrentString = LocalString;
}
Result.m_ErrorCode = LUrlParserError_Ok;
return Result;
}

View File

@@ -1,78 +0,0 @@
/*
* Lightweight URL & URI parser (RFC 1738, RFC 3986)
* https://github.com/corporateshark/LUrlParser
*
* The MIT License (MIT)
*
* Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
#include <string>
namespace LUrlParser
{
enum LUrlParserError
{
LUrlParserError_Ok = 0,
LUrlParserError_Uninitialized = 1,
LUrlParserError_NoUrlCharacter = 2,
LUrlParserError_InvalidSchemeName = 3,
LUrlParserError_NoDoubleSlash = 4,
LUrlParserError_NoAtSign = 5,
LUrlParserError_UnexpectedEndOfLine = 6,
LUrlParserError_NoSlash = 7,
};
class clParseURL
{
public:
LUrlParserError m_ErrorCode;
std::string m_Scheme;
std::string m_Host;
std::string m_Port;
std::string m_Path;
std::string m_Query;
std::string m_Fragment;
std::string m_UserName;
std::string m_Password;
clParseURL()
: m_ErrorCode( LUrlParserError_Uninitialized )
{}
/// return 'true' if the parsing was successful
bool IsValid() const { return m_ErrorCode == LUrlParserError_Ok; }
/// helper to convert the port number to int, return 'true' if the port is valid (within the 0..65535 range)
bool GetPort( int* OutPort ) const;
/// parse the URL
static clParseURL ParseURL( const std::string& URL );
private:
explicit clParseURL( LUrlParserError ErrorCode )
: m_ErrorCode( ErrorCode )
{}
};
} // namespace LUrlParser

View File

@@ -1,19 +0,0 @@
From https://github.com/corporateshark/LUrlParser/commit/455d5e2d27e3946f11ad0328fee9ee2628e6a8e2
MIT License
===
Lightweight URL & URI parser (RFC 1738, RFC 3986)
(C) Sergey Kosarevsky, 2015
@corporateshark sk@linderdaum.com
http://www.linderdaum.com
http://blog.linderdaum.com
=============================
A tiny and lightweight URL & URI parser (RFC 1738, RFC 3986) written in C++.

View File

@@ -902,8 +902,10 @@ inline uint16_t MicroProfileGetGroupIndex(MicroProfileToken t)
#include <windows.h>
#define snprintf _snprintf
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4244)
#endif
int64_t MicroProfileTicksPerSecondCpu()
{
static int64_t nTicksPerSecond = 0;
@@ -946,7 +948,11 @@ typedef HANDLE MicroProfileThread;
DWORD _stdcall ThreadTrampoline(void* pFunc)
{
MicroProfileThreadFunc F = (MicroProfileThreadFunc)pFunc;
return (uint32_t)F(0);
// The return value of F will always return a void*, however, this is for
// compatibility with pthreads. The underlying "address" of the pointer
// is always a 32-bit value, so this cast is safe to perform.
return static_cast<DWORD>(reinterpret_cast<uint64_t>(F(0)));
}
inline void MicroProfileThreadStart(MicroProfileThread* pThread, MicroProfileThreadFunc Func)
@@ -1742,10 +1748,10 @@ void MicroProfileFlip()
}
}
}
for(uint32_t i = 0; i < MICROPROFILE_MAX_GROUPS; ++i)
for(uint32_t j = 0; j < MICROPROFILE_MAX_GROUPS; ++j)
{
pLog->nGroupTicks[i] += nGroupTicks[i];
pFrameGroup[i] += nGroupTicks[i];
pLog->nGroupTicks[j] += nGroupTicks[j];
pFrameGroup[j] += nGroupTicks[j];
}
pLog->nStackPos = nStackPos;
}
@@ -3328,7 +3334,7 @@ bool MicroProfileIsLocalThread(uint32_t nThreadId)
#endif
#else
bool MicroProfileIsLocalThread(uint32_t nThreadId){return false;}
bool MicroProfileIsLocalThread([[maybe_unused]] uint32_t nThreadId) { return false; }
void MicroProfileStopContextSwitchTrace(){}
void MicroProfileStartContextSwitchTrace(){}
@@ -3576,7 +3582,7 @@ int MicroProfileGetGpuTickReference(int64_t* pOutCpu, int64_t* pOutGpu)
#undef S
#ifdef _WIN32
#ifdef _MSC_VER
#pragma warning(pop)
#endif

1
externals/unicorn vendored

Submodule externals/unicorn deleted from 73f4573535

View File

@@ -32,7 +32,6 @@ if (MSVC)
# /Zc:inline - Let codegen omit inline functions in object files
# /Zc:throwingNew - Let codegen assume `operator new` (without std::nothrow) will never return null
add_compile_options(
/W3
/MP
/Zi
/Zo
@@ -43,6 +42,13 @@ if (MSVC)
/Zc:externConstexpr
/Zc:inline
/Zc:throwingNew
# Warnings
/W3
/we4547 # 'operator' : operator before comma has no effect; expected operator with side-effect
/we4549 # 'operator1': operator before comma has no effect; did you intend 'operator2'?
/we4555 # Expression has no effect; expected expression with side-effect
/we4834 # Discarding return value of function with 'nodiscard' attribute
)
# /GS- - No stack buffer overflow checks
@@ -56,6 +62,7 @@ else()
-Werror=implicit-fallthrough
-Werror=missing-declarations
-Werror=reorder
-Werror=unused-result
-Wextra
-Wmissing-declarations
-Wno-attributes

View File

@@ -12,22 +12,54 @@ add_library(audio_core STATIC
buffer.h
codec.cpp
codec.h
command_generator.cpp
command_generator.h
common.h
effect_context.cpp
effect_context.h
info_updater.cpp
info_updater.h
memory_pool.cpp
memory_pool.h
mix_context.cpp
mix_context.h
null_sink.h
sink.h
sink_context.cpp
sink_context.h
sink_details.cpp
sink_details.h
sink_stream.h
splitter_context.cpp
splitter_context.h
stream.cpp
stream.h
time_stretch.cpp
time_stretch.h
voice_context.cpp
voice_context.h
$<$<BOOL:${ENABLE_CUBEB}>:cubeb_sink.cpp cubeb_sink.h>
)
create_target_directory_groups(audio_core)
if (NOT MSVC)
target_compile_options(audio_core PRIVATE
-Werror=conversion
-Werror=ignored-qualifiers
-Werror=implicit-fallthrough
-Werror=reorder
-Werror=sign-compare
-Werror=unused-variable
$<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-parameter>
$<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-variable>
-Wno-sign-conversion
)
endif()
target_link_libraries(audio_core PUBLIC common core)
target_link_libraries(audio_core PRIVATE SoundTouch)

View File

@@ -55,7 +55,8 @@ void Filter::Process(std::vector<s16>& signal) {
/// @param total_count The total number of biquads to be cascaded.
/// @param index 0-index of the biquad to calculate the Q value for.
static double CascadingBiquadQ(std::size_t total_count, std::size_t index) {
const double pole = M_PI * (2 * index + 1) / (4.0 * total_count);
const auto pole =
M_PI * static_cast<double>(2 * index + 1) / (4.0 * static_cast<double>(total_count));
return 1.0 / (2.0 * std::cos(pole));
}

View File

@@ -146,7 +146,7 @@ std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input,
return {};
if (ratio <= 0) {
LOG_CRITICAL(Audio, "Nonsensical interpolation ratio {}", ratio);
LOG_ERROR(Audio, "Nonsensical interpolation ratio {}", ratio);
return input;
}
@@ -164,7 +164,8 @@ std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input,
const std::size_t num_frames{input.size() / 2};
std::vector<s16> output;
output.reserve(static_cast<std::size_t>(input.size() / ratio + InterpolationState::taps));
output.reserve(static_cast<std::size_t>(static_cast<double>(input.size()) / ratio +
InterpolationState::taps));
for (std::size_t frame{}; frame < num_frames; ++frame) {
const std::size_t lut_index{(state.fraction >> 8) * InterpolationState::taps};
@@ -197,4 +198,36 @@ std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input,
return output;
}
void Resample(s32* output, const s32* input, s32 pitch, s32& fraction, std::size_t sample_count) {
const std::array<s16, 512>& lut = [pitch] {
if (pitch > 0xaaaa) {
return curve_lut0;
}
if (pitch <= 0x8000) {
return curve_lut1;
}
return curve_lut2;
}();
std::size_t index{};
for (std::size_t i = 0; i < sample_count; i++) {
const std::size_t lut_index{(static_cast<std::size_t>(fraction) >> 8) * 4};
const auto l0 = lut[lut_index + 0];
const auto l1 = lut[lut_index + 1];
const auto l2 = lut[lut_index + 2];
const auto l3 = lut[lut_index + 3];
const auto s0 = static_cast<s32>(input[index]);
const auto s1 = static_cast<s32>(input[index + 1]);
const auto s2 = static_cast<s32>(input[index + 2]);
const auto s3 = static_cast<s32>(input[index + 3]);
output[i] = (l0 * s0 + l1 * s1 + l2 * s2 + l3 * s3) >> 15;
fraction += pitch;
index += (fraction >> 15);
fraction &= 0x7fff;
}
}
} // namespace AudioCore

View File

@@ -38,4 +38,7 @@ inline std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16>
return Interpolate(state, std::move(input), ratio);
}
/// Nintendo Switchs DSP resampling algorithm. Based on a single channel
void Resample(s32* output, const s32* input, s32 pitch, s32& fraction, std::size_t sample_count);
} // namespace AudioCore

View File

@@ -2,95 +2,46 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "audio_core/algorithm/interpolate.h"
#include <vector>
#include "audio_core/audio_out.h"
#include "audio_core/audio_renderer.h"
#include "audio_core/codec.h"
#include "audio_core/common.h"
#include "common/assert.h"
#include "audio_core/info_updater.h"
#include "audio_core/voice_context.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/hle/kernel/writable_event.h"
#include "core/memory.h"
#include "core/settings.h"
namespace AudioCore {
constexpr u32 STREAM_SAMPLE_RATE{48000};
constexpr u32 STREAM_NUM_CHANNELS{2};
using VoiceChannelHolder = std::array<VoiceResourceInformation*, 6>;
class AudioRenderer::VoiceState {
public:
bool IsPlaying() const {
return is_in_use && info.play_state == PlayState::Started;
}
const VoiceOutStatus& GetOutStatus() const {
return out_status;
}
const VoiceInfo& GetInfo() const {
return info;
}
VoiceInfo& GetInfo() {
return info;
}
void SetWaveIndex(std::size_t index);
std::vector<s16> DequeueSamples(std::size_t sample_count, Core::Memory::Memory& memory,
const VoiceChannelHolder& voice_resources);
void UpdateState();
void RefreshBuffer(Core::Memory::Memory& memory, const VoiceChannelHolder& voice_resources);
private:
bool is_in_use{};
bool is_refresh_pending{};
std::size_t wave_index{};
std::size_t offset{};
Codec::ADPCMState adpcm_state{};
InterpolationState interp_state{};
std::vector<s16> samples;
VoiceOutStatus out_status{};
VoiceInfo info{};
};
class AudioRenderer::EffectState {
public:
const EffectOutStatus& GetOutStatus() const {
return out_status;
}
const EffectInStatus& GetInfo() const {
return info;
}
EffectInStatus& GetInfo() {
return info;
}
void UpdateState(Core::Memory::Memory& memory);
private:
EffectOutStatus out_status{};
EffectInStatus info{};
};
AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_,
AudioRendererParameter params,
AudioCommon::AudioRendererParameter params,
std::shared_ptr<Kernel::WritableEvent> buffer_event,
std::size_t instance_number)
: worker_params{params}, buffer_event{buffer_event}, voices(params.voice_count),
voice_resources(params.voice_count), effects(params.effect_count), memory{memory_} {
: worker_params{params}, buffer_event{buffer_event},
memory_pool_info(params.effect_count + params.voice_count * 4),
voice_context(params.voice_count), effect_context(params.effect_count), mix_context(),
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),
temp_mix_buffer(AudioCommon::TOTAL_TEMP_MIX_SIZE) {
behavior_info.SetUserRevision(params.revision);
splitter_context.Initialize(behavior_info, params.splitter_count,
params.num_splitter_send_channels);
mix_context.Initialize(behavior_info, params.submix_count + 1, params.effect_count);
audio_out = std::make_unique<AudioCore::AudioOut>();
stream = audio_out->OpenStream(core_timing, STREAM_SAMPLE_RATE, STREAM_NUM_CHANNELS,
fmt::format("AudioRenderer-Instance{}", instance_number),
[=]() { buffer_event->Signal(); });
stream =
audio_out->OpenStream(core_timing, params.sample_rate, AudioCommon::STREAM_NUM_CHANNELS,
fmt::format("AudioRenderer-Instance{}", instance_number),
[=]() { buffer_event->Signal(); });
audio_out->StartStream(stream);
QueueMixedBuffer(0);
QueueMixedBuffer(1);
QueueMixedBuffer(2);
QueueMixedBuffer(3);
}
AudioRenderer::~AudioRenderer() = default;
@@ -111,355 +62,200 @@ Stream::State AudioRenderer::GetStreamState() const {
return stream->GetState();
}
ResultVal<std::vector<u8>> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params) {
// Copy UpdateDataHeader struct
UpdateDataHeader config{};
std::memcpy(&config, input_params.data(), sizeof(UpdateDataHeader));
u32 memory_pool_count = worker_params.effect_count + (worker_params.voice_count * 4);
if (!behavior_info.UpdateInput(input_params, sizeof(UpdateDataHeader))) {
LOG_ERROR(Audio, "Failed to update behavior info input parameters");
return Audren::ERR_INVALID_PARAMETERS;
}
// Copy MemoryPoolInfo structs
std::vector<MemoryPoolInfo> mem_pool_info(memory_pool_count);
std::memcpy(mem_pool_info.data(),
input_params.data() + sizeof(UpdateDataHeader) + config.behavior_size,
memory_pool_count * sizeof(MemoryPoolInfo));
// Copy voice resources
const std::size_t voice_resource_offset{sizeof(UpdateDataHeader) + config.behavior_size +
config.memory_pools_size};
std::memcpy(voice_resources.data(), input_params.data() + voice_resource_offset,
sizeof(VoiceResourceInformation) * voice_resources.size());
// Copy VoiceInfo structs
std::size_t voice_offset{sizeof(UpdateDataHeader) + config.behavior_size +
config.memory_pools_size + config.voice_resource_size};
for (auto& voice : voices) {
std::memcpy(&voice.GetInfo(), input_params.data() + voice_offset, sizeof(VoiceInfo));
voice_offset += sizeof(VoiceInfo);
}
std::size_t effect_offset{sizeof(UpdateDataHeader) + config.behavior_size +
config.memory_pools_size + config.voice_resource_size +
config.voices_size};
for (auto& effect : effects) {
std::memcpy(&effect.GetInfo(), input_params.data() + effect_offset, sizeof(EffectInStatus));
effect_offset += sizeof(EffectInStatus);
}
// Update memory pool state
std::vector<MemoryPoolEntry> memory_pool(memory_pool_count);
for (std::size_t index = 0; index < memory_pool.size(); ++index) {
if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestAttach) {
memory_pool[index].state = MemoryPoolStates::Attached;
} else if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestDetach) {
memory_pool[index].state = MemoryPoolStates::Detached;
}
}
// Update voices
for (auto& voice : voices) {
voice.UpdateState();
if (!voice.GetInfo().is_in_use) {
continue;
}
if (voice.GetInfo().is_new) {
voice.SetWaveIndex(voice.GetInfo().wave_buffer_head);
}
}
for (auto& effect : effects) {
effect.UpdateState(memory);
}
// Release previous buffers and queue next ones for playback
ReleaseAndQueueBuffers();
// Copy output header
UpdateDataHeader response_data{worker_params};
if (behavior_info.IsElapsedFrameCountSupported()) {
response_data.render_info = sizeof(RendererInfo);
response_data.total_size += sizeof(RendererInfo);
}
std::vector<u8> output_params(response_data.total_size);
std::memcpy(output_params.data(), &response_data, sizeof(UpdateDataHeader));
// Copy output memory pool entries
std::memcpy(output_params.data() + sizeof(UpdateDataHeader), memory_pool.data(),
response_data.memory_pools_size);
// Copy output voice status
std::size_t voice_out_status_offset{sizeof(UpdateDataHeader) + response_data.memory_pools_size};
for (const auto& voice : voices) {
std::memcpy(output_params.data() + voice_out_status_offset, &voice.GetOutStatus(),
sizeof(VoiceOutStatus));
voice_out_status_offset += sizeof(VoiceOutStatus);
}
std::size_t effect_out_status_offset{
sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size +
response_data.voice_resource_size};
for (const auto& effect : effects) {
std::memcpy(output_params.data() + effect_out_status_offset, &effect.GetOutStatus(),
sizeof(EffectOutStatus));
effect_out_status_offset += sizeof(EffectOutStatus);
}
// Update behavior info output
const std::size_t behavior_out_status_offset{
sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size +
response_data.effects_size + response_data.sinks_size +
response_data.performance_manager_size};
if (!behavior_info.UpdateOutput(output_params, behavior_out_status_offset)) {
LOG_ERROR(Audio, "Failed to update behavior info output parameters");
return Audren::ERR_INVALID_PARAMETERS;
}
if (behavior_info.IsElapsedFrameCountSupported()) {
const std::size_t renderer_info_offset{
sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size +
response_data.effects_size + response_data.sinks_size +
response_data.performance_manager_size + response_data.behavior_size};
RendererInfo renderer_info{};
renderer_info.elasped_frame_count = elapsed_frame_count;
std::memcpy(output_params.data() + renderer_info_offset, &renderer_info,
sizeof(RendererInfo));
}
return MakeResult(output_params);
}
void AudioRenderer::VoiceState::SetWaveIndex(std::size_t index) {
wave_index = index & 3;
is_refresh_pending = true;
}
std::vector<s16> AudioRenderer::VoiceState::DequeueSamples(
std::size_t sample_count, Core::Memory::Memory& memory,
const VoiceChannelHolder& voice_resources) {
if (!IsPlaying()) {
return {};
}
if (is_refresh_pending) {
RefreshBuffer(memory, voice_resources);
}
const std::size_t max_size{samples.size() - offset};
const std::size_t dequeue_offset{offset};
std::size_t size{sample_count * STREAM_NUM_CHANNELS};
if (size > max_size) {
size = max_size;
}
out_status.played_sample_count += size / STREAM_NUM_CHANNELS;
offset += size;
const auto& wave_buffer{info.wave_buffer[wave_index]};
if (offset == samples.size()) {
offset = 0;
if (!wave_buffer.is_looping && wave_buffer.buffer_sz) {
SetWaveIndex(wave_index + 1);
}
if (wave_buffer.buffer_sz) {
out_status.wave_buffer_consumed++;
}
if (wave_buffer.end_of_stream || wave_buffer.buffer_sz == 0) {
info.play_state = PlayState::Paused;
}
}
return {samples.begin() + dequeue_offset, samples.begin() + dequeue_offset + size};
}
void AudioRenderer::VoiceState::UpdateState() {
if (is_in_use && !info.is_in_use) {
// No longer in use, reset state
is_refresh_pending = true;
wave_index = 0;
offset = 0;
out_status = {};
}
is_in_use = info.is_in_use;
}
void AudioRenderer::VoiceState::RefreshBuffer(Core::Memory::Memory& memory,
const VoiceChannelHolder& voice_resources) {
const auto wave_buffer_address = info.wave_buffer[wave_index].buffer_addr;
const auto wave_buffer_size = info.wave_buffer[wave_index].buffer_sz;
std::vector<s16> new_samples(wave_buffer_size / sizeof(s16));
memory.ReadBlock(wave_buffer_address, new_samples.data(), wave_buffer_size);
switch (static_cast<Codec::PcmFormat>(info.sample_format)) {
case Codec::PcmFormat::Int16: {
// PCM16 is played as-is
break;
}
case Codec::PcmFormat::Adpcm: {
// Decode ADPCM to PCM16
Codec::ADPCM_Coeff coeffs;
memory.ReadBlock(info.additional_params_addr, coeffs.data(), sizeof(Codec::ADPCM_Coeff));
new_samples = Codec::DecodeADPCM(reinterpret_cast<u8*>(new_samples.data()),
new_samples.size() * sizeof(s16), coeffs, adpcm_state);
break;
}
default:
UNIMPLEMENTED_MSG("Unimplemented sample_format={}", info.sample_format);
break;
}
switch (info.channel_count) {
case 1: {
// 1 channel is upsampled to 2 channel
samples.resize(new_samples.size() * 2);
for (std::size_t index = 0; index < new_samples.size(); ++index) {
auto sample = static_cast<float>(new_samples[index]);
if (voice_resources[0]->in_use) {
sample *= voice_resources[0]->mix_volumes[0];
}
samples[index * 2] = static_cast<s16>(sample * info.volume);
samples[index * 2 + 1] = static_cast<s16>(sample * info.volume);
}
break;
}
case 2: {
// 2 channel is played as is
samples = std::move(new_samples);
const std::size_t sample_count = (samples.size() / 2);
for (std::size_t index = 0; index < sample_count; ++index) {
const std::size_t index_l = index * 2;
const std::size_t index_r = index * 2 + 1;
auto sample_l = static_cast<float>(samples[index_l]);
auto sample_r = static_cast<float>(samples[index_r]);
if (voice_resources[0]->in_use) {
sample_l *= voice_resources[0]->mix_volumes[0];
}
if (voice_resources[1]->in_use) {
sample_r *= voice_resources[1]->mix_volumes[1];
}
samples[index_l] = static_cast<s16>(sample_l * info.volume);
samples[index_r] = static_cast<s16>(sample_r * info.volume);
}
break;
}
case 6: {
samples.resize((new_samples.size() / 6) * 2);
const std::size_t sample_count = samples.size() / 2;
for (std::size_t index = 0; index < sample_count; ++index) {
auto FL = static_cast<float>(new_samples[index * 6]);
auto FR = static_cast<float>(new_samples[index * 6 + 1]);
auto FC = static_cast<float>(new_samples[index * 6 + 2]);
auto BL = static_cast<float>(new_samples[index * 6 + 4]);
auto BR = static_cast<float>(new_samples[index * 6 + 5]);
if (voice_resources[0]->in_use) {
FL *= voice_resources[0]->mix_volumes[0];
}
if (voice_resources[1]->in_use) {
FR *= voice_resources[1]->mix_volumes[1];
}
if (voice_resources[2]->in_use) {
FC *= voice_resources[2]->mix_volumes[2];
}
if (voice_resources[4]->in_use) {
BL *= voice_resources[4]->mix_volumes[4];
}
if (voice_resources[5]->in_use) {
BR *= voice_resources[5]->mix_volumes[5];
}
samples[index * 2] =
static_cast<s16>((0.3694f * FL + 0.2612f * FC + 0.3694f * BL) * info.volume);
samples[index * 2 + 1] =
static_cast<s16>((0.3694f * FR + 0.2612f * FC + 0.3694f * BR) * info.volume);
}
break;
}
default:
UNIMPLEMENTED_MSG("Unimplemented channel_count={}", info.channel_count);
break;
}
// Only interpolate when necessary, expensive.
if (GetInfo().sample_rate != STREAM_SAMPLE_RATE) {
samples = Interpolate(interp_state, std::move(samples), GetInfo().sample_rate,
STREAM_SAMPLE_RATE);
}
is_refresh_pending = false;
}
void AudioRenderer::EffectState::UpdateState(Core::Memory::Memory& memory) {
if (info.is_new) {
out_status.state = EffectStatus::New;
} else {
if (info.type == Effect::Aux) {
ASSERT_MSG(memory.Read32(info.aux_info.return_buffer_info) == 0,
"Aux buffers tried to update");
ASSERT_MSG(memory.Read32(info.aux_info.send_buffer_info) == 0,
"Aux buffers tried to update");
ASSERT_MSG(memory.Read32(info.aux_info.return_buffer_base) == 0,
"Aux buffers tried to update");
ASSERT_MSG(memory.Read32(info.aux_info.send_buffer_base) == 0,
"Aux buffers tried to update");
}
}
}
static constexpr s16 ClampToS16(s32 value) {
return static_cast<s16>(std::clamp(value, -32768, 32767));
}
ResultCode AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params,
std::vector<u8>& output_params) {
InfoUpdater info_updater{input_params, output_params, behavior_info};
if (!info_updater.UpdateBehaviorInfo(behavior_info)) {
LOG_ERROR(Audio, "Failed to update behavior info input parameters");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
if (!info_updater.UpdateMemoryPools(memory_pool_info)) {
LOG_ERROR(Audio, "Failed to update memory pool parameters");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
if (!info_updater.UpdateVoiceChannelResources(voice_context)) {
LOG_ERROR(Audio, "Failed to update voice channel resource parameters");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
if (!info_updater.UpdateVoices(voice_context, memory_pool_info, 0)) {
LOG_ERROR(Audio, "Failed to update voice parameters");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
// TODO(ogniK): Deal with stopped audio renderer but updates still taking place
if (!info_updater.UpdateEffects(effect_context, true)) {
LOG_ERROR(Audio, "Failed to update effect parameters");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
if (behavior_info.IsSplitterSupported()) {
if (!info_updater.UpdateSplitterInfo(splitter_context)) {
LOG_ERROR(Audio, "Failed to update splitter parameters");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
}
auto mix_result = info_updater.UpdateMixes(mix_context, worker_params.mix_buffer_count,
splitter_context, effect_context);
if (mix_result.IsError()) {
LOG_ERROR(Audio, "Failed to update mix parameters");
return mix_result;
}
// TODO(ogniK): Sinks
if (!info_updater.UpdateSinks(sink_context)) {
LOG_ERROR(Audio, "Failed to update sink parameters");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
// TODO(ogniK): Performance buffer
if (!info_updater.UpdatePerformanceBuffer()) {
LOG_ERROR(Audio, "Failed to update performance buffer parameters");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
if (!info_updater.UpdateErrorInfo(behavior_info)) {
LOG_ERROR(Audio, "Failed to update error info");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
if (behavior_info.IsElapsedFrameCountSupported()) {
if (!info_updater.UpdateRendererInfo(elapsed_frame_count)) {
LOG_ERROR(Audio, "Failed to update renderer info");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
}
// TODO(ogniK): Statistics
if (!info_updater.WriteOutputHeader()) {
LOG_ERROR(Audio, "Failed to write output header");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
// TODO(ogniK): Check when all sections are implemented
if (!info_updater.CheckConsumedSize()) {
LOG_ERROR(Audio, "Audio buffers were not consumed!");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
ReleaseAndQueueBuffers();
return RESULT_SUCCESS;
}
void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) {
constexpr std::size_t BUFFER_SIZE{512};
command_generator.PreCommand();
// Clear mix buffers before our next operation
command_generator.ClearMixBuffers();
// If the splitter is not in use, sort our mixes
if (!splitter_context.UsingSplitter()) {
mix_context.SortInfo();
}
// Sort our voices
voice_context.SortInfo();
// Handle samples
command_generator.GenerateVoiceCommands();
command_generator.GenerateSubMixCommands();
command_generator.GenerateFinalMixCommands();
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));
for (auto& voice : voices) {
if (!voice.IsPlaying()) {
continue;
}
VoiceChannelHolder resources{};
for (u32 channel = 0; channel < voice.GetInfo().channel_count; channel++) {
const auto channel_resource_id = voice.GetInfo().voice_channel_resource_ids[channel];
resources[channel] = &voice_resources[channel_resource_id];
if (sink_context.InUse()) {
const auto stream_channel_count = stream->GetNumChannels();
const auto buffer_offsets = sink_context.OutputBuffers();
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);
for (std::size_t i = 0; i < channel_count; i++) {
mix_buffers[i] =
command_generator.GetMixBuffer(in_params.buffer_offset + buffer_offsets[i]);
}
std::size_t offset{};
s64 samples_remaining{BUFFER_SIZE};
while (samples_remaining > 0) {
const std::vector<s16> samples{
voice.DequeueSamples(samples_remaining, memory, resources)};
for (std::size_t i = 0; i < BUFFER_SIZE; i++) {
if (channel_count == 1) {
const auto sample = ClampToS16(mix_buffers[0][i]);
buffer[i * stream_channel_count + 0] = sample;
if (stream_channel_count > 1) {
buffer[i * stream_channel_count + 1] = sample;
}
if (stream_channel_count == 6) {
buffer[i * stream_channel_count + 2] = sample;
buffer[i * stream_channel_count + 4] = sample;
buffer[i * stream_channel_count + 5] = sample;
}
} else if (channel_count == 2) {
const auto l_sample = ClampToS16(mix_buffers[0][i]);
const auto r_sample = ClampToS16(mix_buffers[1][i]);
if (stream_channel_count == 1) {
buffer[i * stream_channel_count + 0] = l_sample;
} else if (stream_channel_count == 2) {
buffer[i * stream_channel_count + 0] = l_sample;
buffer[i * stream_channel_count + 1] = r_sample;
} else if (stream_channel_count == 6) {
buffer[i * stream_channel_count + 0] = l_sample;
buffer[i * stream_channel_count + 1] = r_sample;
if (samples.empty()) {
break;
}
buffer[i * stream_channel_count + 2] =
ClampToS16((static_cast<s32>(l_sample) + static_cast<s32>(r_sample)) / 2);
samples_remaining -= samples.size() / stream->GetNumChannels();
buffer[i * stream_channel_count + 4] = l_sample;
buffer[i * stream_channel_count + 5] = r_sample;
}
for (const auto& sample : samples) {
const s32 buffer_sample{buffer[offset]};
buffer[offset++] =
ClampToS16(buffer_sample + static_cast<s32>(sample * voice.GetInfo().volume));
} else if (channel_count == 6) {
const auto fl_sample = ClampToS16(mix_buffers[0][i]);
const auto fr_sample = ClampToS16(mix_buffers[1][i]);
const auto fc_sample = ClampToS16(mix_buffers[2][i]);
const auto lf_sample = ClampToS16(mix_buffers[3][i]);
const auto bl_sample = ClampToS16(mix_buffers[4][i]);
const auto br_sample = ClampToS16(mix_buffers[5][i]);
if (stream_channel_count == 1) {
buffer[i * stream_channel_count + 0] = fc_sample;
} else if (stream_channel_count == 2) {
buffer[i * stream_channel_count + 0] =
static_cast<s16>(0.3694f * static_cast<float>(fl_sample) +
0.2612f * static_cast<float>(fc_sample) +
0.3694f * static_cast<float>(bl_sample));
buffer[i * stream_channel_count + 1] =
static_cast<s16>(0.3694f * static_cast<float>(fr_sample) +
0.2612f * static_cast<float>(fc_sample) +
0.3694f * static_cast<float>(br_sample));
} else if (stream_channel_count == 6) {
buffer[i * stream_channel_count + 0] = fl_sample;
buffer[i * stream_channel_count + 1] = fr_sample;
buffer[i * stream_channel_count + 2] = fc_sample;
buffer[i * stream_channel_count + 3] = lf_sample;
buffer[i * stream_channel_count + 4] = bl_sample;
buffer[i * stream_channel_count + 5] = br_sample;
}
}
}
}
audio_out->QueueBuffer(stream, tag, std::move(buffer));
elapsed_frame_count++;
voice_context.UpdateStateByDspShared();
}
void AudioRenderer::ReleaseAndQueueBuffers() {

View File

@@ -9,12 +9,18 @@
#include <vector>
#include "audio_core/behavior_info.h"
#include "audio_core/command_generator.h"
#include "audio_core/common.h"
#include "audio_core/effect_context.h"
#include "audio_core/memory_pool.h"
#include "audio_core/mix_context.h"
#include "audio_core/sink_context.h"
#include "audio_core/splitter_context.h"
#include "audio_core/stream.h"
#include "audio_core/voice_context.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "core/hle/kernel/object.h"
#include "core/hle/result.h"
namespace Core::Timing {
@@ -30,220 +36,25 @@ class Memory;
}
namespace AudioCore {
using DSPStateHolder = std::array<VoiceState*, 6>;
class AudioOut;
enum class PlayState : u8 {
Started = 0,
Stopped = 1,
Paused = 2,
};
enum class Effect : u8 {
None = 0,
Aux = 2,
};
enum class EffectStatus : u8 {
None = 0,
New = 1,
};
struct AudioRendererParameter {
u32_le sample_rate;
u32_le sample_count;
u32_le mix_buffer_count;
u32_le submix_count;
u32_le voice_count;
u32_le sink_count;
u32_le effect_count;
u32_le performance_frame_count;
u8 is_voice_drop_enabled;
u8 unknown_21;
u8 unknown_22;
u8 execution_mode;
u32_le splitter_count;
u32_le num_splitter_send_channels;
u32_le unknown_30;
u32_le revision;
};
static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size");
enum class MemoryPoolStates : u32 { // Should be LE
Invalid = 0x0,
Unknown = 0x1,
RequestDetach = 0x2,
Detached = 0x3,
RequestAttach = 0x4,
Attached = 0x5,
Released = 0x6,
};
struct MemoryPoolEntry {
MemoryPoolStates state;
u32_le unknown_4;
u32_le unknown_8;
u32_le unknown_c;
};
static_assert(sizeof(MemoryPoolEntry) == 0x10, "MemoryPoolEntry has wrong size");
struct MemoryPoolInfo {
u64_le pool_address;
u64_le pool_size;
MemoryPoolStates pool_state;
INSERT_PADDING_WORDS(3); // Unknown
};
static_assert(sizeof(MemoryPoolInfo) == 0x20, "MemoryPoolInfo has wrong size");
struct BiquadFilter {
u8 enable;
INSERT_PADDING_BYTES(1);
std::array<s16_le, 3> numerator;
std::array<s16_le, 2> denominator;
};
static_assert(sizeof(BiquadFilter) == 0xc, "BiquadFilter has wrong size");
struct WaveBuffer {
u64_le buffer_addr;
u64_le buffer_sz;
s32_le start_sample_offset;
s32_le end_sample_offset;
u8 is_looping;
u8 end_of_stream;
u8 sent_to_server;
INSERT_PADDING_BYTES(5);
u64 context_addr;
u64 context_sz;
INSERT_PADDING_BYTES(8);
};
static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer has wrong size");
struct VoiceResourceInformation {
s32_le id{};
std::array<float_le, MAX_MIX_BUFFERS> mix_volumes{};
bool in_use{};
INSERT_PADDING_BYTES(11);
};
static_assert(sizeof(VoiceResourceInformation) == 0x70, "VoiceResourceInformation has wrong size");
struct VoiceInfo {
u32_le id;
u32_le node_id;
u8 is_new;
u8 is_in_use;
PlayState play_state;
u8 sample_format;
u32_le sample_rate;
u32_le priority;
u32_le sorting_order;
u32_le channel_count;
float_le pitch;
float_le volume;
std::array<BiquadFilter, 2> biquad_filter;
u32_le wave_buffer_count;
u32_le wave_buffer_head;
INSERT_PADDING_WORDS(1);
u64_le additional_params_addr;
u64_le additional_params_sz;
u32_le mix_id;
u32_le splitter_info_id;
std::array<WaveBuffer, 4> wave_buffer;
std::array<u32_le, 6> voice_channel_resource_ids;
INSERT_PADDING_BYTES(24);
};
static_assert(sizeof(VoiceInfo) == 0x170, "VoiceInfo is wrong size");
struct VoiceOutStatus {
u64_le played_sample_count;
u32_le wave_buffer_consumed;
u32_le voice_drops_count;
};
static_assert(sizeof(VoiceOutStatus) == 0x10, "VoiceOutStatus has wrong size");
struct AuxInfo {
std::array<u8, 24> input_mix_buffers;
std::array<u8, 24> output_mix_buffers;
u32_le mix_buffer_count;
u32_le sample_rate; // Stored in the aux buffer currently
u32_le sample_count;
u64_le send_buffer_info;
u64_le send_buffer_base;
u64_le return_buffer_info;
u64_le return_buffer_base;
};
static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size");
struct EffectInStatus {
Effect type;
u8 is_new;
u8 is_enabled;
INSERT_PADDING_BYTES(1);
u32_le mix_id;
u64_le buffer_base;
u64_le buffer_sz;
s32_le priority;
INSERT_PADDING_BYTES(4);
union {
std::array<u8, 0xa0> raw;
AuxInfo aux_info;
};
};
static_assert(sizeof(EffectInStatus) == 0xc0, "EffectInStatus is an invalid size");
struct EffectOutStatus {
EffectStatus state;
INSERT_PADDING_BYTES(0xf);
};
static_assert(sizeof(EffectOutStatus) == 0x10, "EffectOutStatus is an invalid size");
struct RendererInfo {
u64_le elasped_frame_count{};
INSERT_PADDING_WORDS(2);
};
static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size");
struct UpdateDataHeader {
UpdateDataHeader() {}
explicit UpdateDataHeader(const AudioRendererParameter& config) {
revision = Common::MakeMagic('R', 'E', 'V', '8'); // 9.2.0 Revision
behavior_size = 0xb0;
memory_pools_size = (config.effect_count + (config.voice_count * 4)) * 0x10;
voices_size = config.voice_count * 0x10;
voice_resource_size = 0x0;
effects_size = config.effect_count * 0x10;
mixes_size = 0x0;
sinks_size = config.sink_count * 0x20;
performance_manager_size = 0x10;
render_info = 0;
total_size = sizeof(UpdateDataHeader) + behavior_size + memory_pools_size + voices_size +
effects_size + sinks_size + performance_manager_size;
}
u32_le revision{};
u32_le behavior_size{};
u32_le memory_pools_size{};
u32_le voices_size{};
u32_le voice_resource_size{};
u32_le effects_size{};
u32_le mixes_size{};
u32_le sinks_size{};
u32_le performance_manager_size{};
u32_le splitter_size{};
u32_le render_info{};
INSERT_PADDING_WORDS(4);
u32_le total_size{};
};
static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has wrong size");
class AudioRenderer {
public:
AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_,
AudioRendererParameter params,
AudioCommon::AudioRendererParameter params,
std::shared_ptr<Kernel::WritableEvent> buffer_event, std::size_t instance_number);
~AudioRenderer();
ResultVal<std::vector<u8>> UpdateAudioRenderer(const std::vector<u8>& input_params);
ResultCode UpdateAudioRenderer(const std::vector<u8>& input_params,
std::vector<u8>& output_params);
void QueueMixedBuffer(Buffer::Tag tag);
void ReleaseAndQueueBuffers();
u32 GetSampleRate() const;
@@ -252,19 +63,23 @@ public:
Stream::State GetStreamState() const;
private:
class EffectState;
class VoiceState;
BehaviorInfo behavior_info{};
AudioRendererParameter worker_params;
AudioCommon::AudioRendererParameter worker_params;
std::shared_ptr<Kernel::WritableEvent> buffer_event;
std::vector<ServerMemoryPoolInfo> memory_pool_info;
VoiceContext voice_context;
EffectContext effect_context;
MixContext mix_context;
SinkContext sink_context;
SplitterContext splitter_context;
std::vector<VoiceState> voices;
std::vector<VoiceResourceInformation> voice_resources;
std::vector<EffectState> effects;
std::unique_ptr<AudioOut> audio_out;
StreamPtr stream;
Core::Memory::Memory& memory;
CommandGenerator command_generator;
std::size_t elapsed_frame_count{};
std::vector<s32> temp_mix_buffer{};
};
} // namespace AudioCore

View File

@@ -9,39 +9,11 @@
namespace AudioCore {
BehaviorInfo::BehaviorInfo() : process_revision(CURRENT_PROCESS_REVISION) {}
BehaviorInfo::BehaviorInfo() : process_revision(AudioCommon::CURRENT_PROCESS_REVISION) {}
BehaviorInfo::~BehaviorInfo() = default;
bool BehaviorInfo::UpdateInput(const std::vector<u8>& buffer, std::size_t offset) {
if (!CanConsumeBuffer(buffer.size(), offset, sizeof(InParams))) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
InParams params{};
std::memcpy(&params, buffer.data() + offset, sizeof(InParams));
if (!IsValidRevision(params.revision)) {
LOG_ERROR(Audio, "Invalid input revision, revision=0x{:08X}", params.revision);
return false;
}
if (user_revision != params.revision) {
LOG_ERROR(Audio,
"User revision differs from input revision, expecting 0x{:08X} but got 0x{:08X}",
user_revision, params.revision);
return false;
}
ClearError();
UpdateFlags(params.flags);
// TODO(ogniK): Check input params size when InfoUpdater is used
return true;
}
bool BehaviorInfo::UpdateOutput(std::vector<u8>& buffer, std::size_t offset) {
if (!CanConsumeBuffer(buffer.size(), offset, sizeof(OutParams))) {
if (!AudioCommon::CanConsumeBuffer(buffer.size(), offset, sizeof(OutParams))) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
@@ -65,36 +37,69 @@ void BehaviorInfo::SetUserRevision(u32_le revision) {
user_revision = revision;
}
u32_le BehaviorInfo::GetUserRevision() const {
return user_revision;
}
u32_le BehaviorInfo::GetProcessRevision() const {
return process_revision;
}
bool BehaviorInfo::IsAdpcmLoopContextBugFixed() const {
return IsRevisionSupported(2, user_revision);
return AudioCommon::IsRevisionSupported(2, user_revision);
}
bool BehaviorInfo::IsSplitterSupported() const {
return IsRevisionSupported(2, user_revision);
return AudioCommon::IsRevisionSupported(2, user_revision);
}
bool BehaviorInfo::IsLongSizePreDelaySupported() const {
return IsRevisionSupported(3, user_revision);
return AudioCommon::IsRevisionSupported(3, user_revision);
}
bool BehaviorInfo::IsAudioRenererProcessingTimeLimit80PercentSupported() const {
return IsRevisionSupported(5, user_revision);
bool BehaviorInfo::IsAudioRendererProcessingTimeLimit80PercentSupported() const {
return AudioCommon::IsRevisionSupported(5, user_revision);
}
bool BehaviorInfo::IsAudioRenererProcessingTimeLimit75PercentSupported() const {
return IsRevisionSupported(4, user_revision);
bool BehaviorInfo::IsAudioRendererProcessingTimeLimit75PercentSupported() const {
return AudioCommon::IsRevisionSupported(4, user_revision);
}
bool BehaviorInfo::IsAudioRenererProcessingTimeLimit70PercentSupported() const {
return IsRevisionSupported(1, user_revision);
bool BehaviorInfo::IsAudioRendererProcessingTimeLimit70PercentSupported() const {
return AudioCommon::IsRevisionSupported(1, user_revision);
}
bool BehaviorInfo::IsElapsedFrameCountSupported() const {
return IsRevisionSupported(5, user_revision);
return AudioCommon::IsRevisionSupported(5, user_revision);
}
bool BehaviorInfo::IsMemoryPoolForceMappingEnabled() const {
return (flags & 1) != 0;
}
bool BehaviorInfo::IsFlushVoiceWaveBuffersSupported() const {
return AudioCommon::IsRevisionSupported(5, user_revision);
}
bool BehaviorInfo::IsVoicePlayedSampleCountResetAtLoopPointSupported() const {
return AudioCommon::IsRevisionSupported(5, user_revision);
}
bool BehaviorInfo::IsVoicePitchAndSrcSkippedSupported() const {
return AudioCommon::IsRevisionSupported(5, user_revision);
}
bool BehaviorInfo::IsMixInParameterDirtyOnlyUpdateSupported() const {
return AudioCommon::IsRevisionSupported(7, user_revision);
}
bool BehaviorInfo::IsSplitterBugFixed() const {
return AudioCommon::IsRevisionSupported(5, user_revision);
}
void BehaviorInfo::CopyErrorInfo(BehaviorInfo::OutParams& dst) {
dst.error_count = static_cast<u32>(error_count);
std::copy(errors.begin(), errors.begin() + error_count, dst.errors.begin());
}
} // namespace AudioCore

View File

@@ -14,30 +14,6 @@
namespace AudioCore {
class BehaviorInfo {
public:
explicit BehaviorInfo();
~BehaviorInfo();
bool UpdateInput(const std::vector<u8>& buffer, std::size_t offset);
bool UpdateOutput(std::vector<u8>& buffer, std::size_t offset);
void ClearError();
void UpdateFlags(u64_le dest_flags);
void SetUserRevision(u32_le revision);
bool IsAdpcmLoopContextBugFixed() const;
bool IsSplitterSupported() const;
bool IsLongSizePreDelaySupported() const;
bool IsAudioRenererProcessingTimeLimit80PercentSupported() const;
bool IsAudioRenererProcessingTimeLimit75PercentSupported() const;
bool IsAudioRenererProcessingTimeLimit70PercentSupported() const;
bool IsElapsedFrameCountSupported() const;
bool IsMemoryPoolForceMappingEnabled() const;
private:
u32_le process_revision{};
u32_le user_revision{};
u64_le flags{};
struct ErrorInfo {
u32_le result{};
INSERT_PADDING_WORDS(1);
@@ -45,9 +21,6 @@ private:
};
static_assert(sizeof(ErrorInfo) == 0x10, "ErrorInfo is an invalid size");
std::array<ErrorInfo, 10> errors{};
std::size_t error_count{};
struct InParams {
u32_le revision{};
u32_le padding{};
@@ -61,6 +34,39 @@ private:
INSERT_PADDING_BYTES(12);
};
static_assert(sizeof(OutParams) == 0xb0, "OutParams is an invalid size");
explicit BehaviorInfo();
~BehaviorInfo();
bool UpdateOutput(std::vector<u8>& buffer, std::size_t offset);
void ClearError();
void UpdateFlags(u64_le dest_flags);
void SetUserRevision(u32_le revision);
u32_le GetUserRevision() const;
u32_le GetProcessRevision() const;
bool IsAdpcmLoopContextBugFixed() const;
bool IsSplitterSupported() const;
bool IsLongSizePreDelaySupported() const;
bool IsAudioRendererProcessingTimeLimit80PercentSupported() const;
bool IsAudioRendererProcessingTimeLimit75PercentSupported() const;
bool IsAudioRendererProcessingTimeLimit70PercentSupported() const;
bool IsElapsedFrameCountSupported() const;
bool IsMemoryPoolForceMappingEnabled() const;
bool IsFlushVoiceWaveBuffersSupported() const;
bool IsVoicePlayedSampleCountResetAtLoopPointSupported() const;
bool IsVoicePitchAndSrcSkippedSupported() const;
bool IsMixInParameterDirtyOnlyUpdateSupported() const;
bool IsSplitterBugFixed() const;
void CopyErrorInfo(OutParams& dst);
private:
u32_le process_revision{};
u32_le user_revision{};
u64_le flags{};
std::array<ErrorInfo, 10> errors{};
std::size_t error_count{};
};
} // namespace AudioCore

View File

@@ -16,8 +16,9 @@ std::vector<s16> DecodeADPCM(const u8* const data, std::size_t size, const ADPCM
constexpr std::size_t FRAME_LEN = 8;
constexpr std::size_t SAMPLES_PER_FRAME = 14;
constexpr std::array<int, 16> SIGNED_NIBBLES = {
{0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1}};
static constexpr std::array<int, 16> SIGNED_NIBBLES{
0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1,
};
const std::size_t sample_count = (size / FRAME_LEN) * SAMPLES_PER_FRAME;
const std::size_t ret_size =

View File

@@ -38,7 +38,7 @@ using ADPCM_Coeff = std::array<s16, 16>;
* @param state ADPCM state, this is updated with new state
* @return Decoded stereo signed PCM16 data, sample_count in length
*/
std::vector<s16> DecodeADPCM(const u8* const data, std::size_t size, const ADPCM_Coeff& coeff,
std::vector<s16> DecodeADPCM(const u8* data, std::size_t size, const ADPCM_Coeff& coeff,
ADPCMState& state);
}; // namespace AudioCore::Codec

View File

@@ -0,0 +1,977 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "audio_core/algorithm/interpolate.h"
#include "audio_core/command_generator.h"
#include "audio_core/effect_context.h"
#include "audio_core/mix_context.h"
#include "audio_core/voice_context.h"
#include "core/memory.h"
namespace AudioCore {
namespace {
constexpr std::size_t MIX_BUFFER_SIZE = 0x3f00;
constexpr std::size_t SCALED_MIX_BUFFER_SIZE = MIX_BUFFER_SIZE << 15ULL;
template <std::size_t N>
void ApplyMix(s32* output, 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] +=
static_cast<s32>((static_cast<s64>(input[i + j]) * gain + 0x4000) >> 15);
}
}
}
s32 ApplyMixRamp(s32* output, 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);
output[i] += x;
gain += delta;
}
return x;
}
void ApplyGain(s32* output, 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) {
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) {
const bool positive = first_sample > 0;
auto final_sample = std::abs(first_sample);
for (s32 i = 0; i < sample_count; i++) {
final_sample = static_cast<s32>((static_cast<s64>(final_sample) * delta) >> 15);
if (positive) {
output[i] += final_sample;
} else {
output[i] -= final_sample;
}
}
if (positive) {
return final_sample;
} else {
return -final_sample;
}
}
} // namespace
CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_params,
VoiceContext& voice_context, MixContext& mix_context,
SplitterContext& splitter_context, EffectContext& effect_context,
Core::Memory::Memory& memory)
: worker_params(worker_params), voice_context(voice_context), mix_context(mix_context),
splitter_context(splitter_context), effect_context(effect_context), memory(memory),
mix_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) *
worker_params.sample_count),
sample_buffer(MIX_BUFFER_SIZE),
depop_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) *
worker_params.sample_count) {}
CommandGenerator::~CommandGenerator() = default;
void CommandGenerator::ClearMixBuffers() {
std::fill(mix_buffer.begin(), mix_buffer.end(), 0);
std::fill(sample_buffer.begin(), sample_buffer.end(), 0);
// std::fill(depop_buffer.begin(), depop_buffer.end(), 0);
}
void CommandGenerator::GenerateVoiceCommands() {
if (dumping_frame) {
LOG_DEBUG(Audio, "(DSP_TRACE) GenerateVoiceCommands");
}
// Grab all our voices
const auto voice_count = voice_context.GetVoiceCount();
for (std::size_t i = 0; i < voice_count; i++) {
auto& voice_info = voice_context.GetSortedInfo(i);
// Update voices and check if we should queue them
if (voice_info.ShouldSkip() || !voice_info.UpdateForCommandGeneration(voice_context)) {
continue;
}
// Queue our voice
GenerateVoiceCommand(voice_info);
}
// Update our splitters
splitter_context.UpdateInternalState();
}
void CommandGenerator::GenerateVoiceCommand(ServerVoiceInfo& voice_info) {
auto& in_params = voice_info.GetInParams();
const auto channel_count = in_params.channel_count;
for (s32 channel = 0; channel < channel_count; channel++) {
const auto resource_id = in_params.voice_channel_resource_id[channel];
auto& dsp_state = voice_context.GetDspSharedState(resource_id);
auto& channel_resource = voice_context.GetChannelResource(resource_id);
// Decode our samples for our channel
GenerateDataSourceCommand(voice_info, dsp_state, channel);
if (in_params.should_depop) {
in_params.last_volume = 0.0f;
} else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER ||
in_params.mix_id != AudioCommon::NO_MIX) {
// Apply a biquad filter if needed
GenerateBiquadFilterCommandForVoice(voice_info, dsp_state,
worker_params.mix_buffer_count, channel);
// Base voice volume ramping
GenerateVolumeRampCommand(in_params.last_volume, in_params.volume, channel,
in_params.node_id);
in_params.last_volume = in_params.volume;
if (in_params.mix_id != AudioCommon::NO_MIX) {
// If we're using a mix id
auto& mix_info = mix_context.GetInfo(in_params.mix_id);
const auto& dest_mix_params = mix_info.GetInParams();
// Voice Mixing
GenerateVoiceMixCommand(
channel_resource.GetCurrentMixVolume(), channel_resource.GetLastMixVolume(),
dsp_state, dest_mix_params.buffer_offset, dest_mix_params.buffer_count,
worker_params.mix_buffer_count + channel, in_params.node_id);
// Update last mix volumes
channel_resource.UpdateLastMixVolumes();
} else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) {
s32 base = channel;
while (auto* destination_data =
GetDestinationData(in_params.splitter_info_id, base)) {
base += channel_count;
if (!destination_data->IsConfigured()) {
continue;
}
if (destination_data->GetMixId() >= static_cast<int>(mix_context.GetCount())) {
continue;
}
const auto& mix_info = mix_context.GetInfo(destination_data->GetMixId());
const auto& dest_mix_params = mix_info.GetInParams();
GenerateVoiceMixCommand(
destination_data->CurrentMixVolumes(), destination_data->LastMixVolumes(),
dsp_state, dest_mix_params.buffer_offset, dest_mix_params.buffer_count,
worker_params.mix_buffer_count + channel, in_params.node_id);
destination_data->MarkDirty();
}
}
// Update biquad filter enabled states
for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) {
in_params.was_biquad_filter_enabled[i] = in_params.biquad_filter[i].enabled;
}
}
}
}
void CommandGenerator::GenerateSubMixCommands() {
const auto mix_count = mix_context.GetCount();
for (std::size_t i = 0; i < mix_count; i++) {
auto& mix_info = mix_context.GetSortedInfo(i);
const auto& in_params = mix_info.GetInParams();
if (!in_params.in_use || in_params.mix_id == AudioCommon::FINAL_MIX) {
continue;
}
GenerateSubMixCommand(mix_info);
}
}
void CommandGenerator::GenerateFinalMixCommands() {
GenerateFinalMixCommand();
}
void CommandGenerator::PreCommand() {
if (!dumping_frame) {
return;
}
for (std::size_t i = 0; i < splitter_context.GetInfoCount(); i++) {
const auto& base = splitter_context.GetInfo(i);
std::string graph = fmt::format("b[{}]", i);
const auto* head = base.GetHead();
while (head != nullptr) {
graph += fmt::format("->{}", head->GetMixId());
head = head->GetNextDestination();
}
LOG_DEBUG(Audio, "(DSP_TRACE) SplitterGraph splitter_info={}, {}", i, graph);
}
}
void CommandGenerator::PostCommand() {
if (!dumping_frame) {
return;
}
dumping_frame = false;
}
void CommandGenerator::GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
s32 channel) {
const auto& in_params = voice_info.GetInParams();
const auto depop = in_params.should_depop;
if (depop) {
if (in_params.mix_id != AudioCommon::NO_MIX) {
auto& mix_info = mix_context.GetInfo(in_params.mix_id);
const auto& mix_in = mix_info.GetInParams();
GenerateDepopPrepareCommand(dsp_state, mix_in.buffer_count, mix_in.buffer_offset);
} else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) {
s32 index{};
while (const auto* destination =
GetDestinationData(in_params.splitter_info_id, index++)) {
if (!destination->IsConfigured()) {
continue;
}
auto& mix_info = mix_context.GetInfo(destination->GetMixId());
const auto& mix_in = mix_info.GetInParams();
GenerateDepopPrepareCommand(dsp_state, mix_in.buffer_count, mix_in.buffer_offset);
}
}
} else {
switch (in_params.sample_format) {
case SampleFormat::Pcm16:
DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(channel), dsp_state, channel,
worker_params.sample_rate, worker_params.sample_count,
in_params.node_id);
break;
case SampleFormat::Adpcm:
ASSERT(channel == 0 && in_params.channel_count == 1);
DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(0), dsp_state, 0,
worker_params.sample_rate, worker_params.sample_count,
in_params.node_id);
break;
default:
UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format);
}
}
}
void CommandGenerator::GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voice_info,
VoiceState& dsp_state,
s32 mix_buffer_count, s32 channel) {
for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) {
const auto& in_params = voice_info.GetInParams();
auto& biquad_filter = in_params.biquad_filter[i];
// Check if biquad filter is actually used
if (!biquad_filter.enabled) {
continue;
}
// Reinitialize our biquad filter state if it was enabled previously
if (!in_params.was_biquad_filter_enabled[i]) {
dsp_state.biquad_filter_state.fill(0);
}
// Generate biquad filter
// GenerateBiquadFilterCommand(mix_buffer_count, biquad_filter,
// dsp_state.biquad_filter_state,
// mix_buffer_count + channel, mix_buffer_count +
// channel, worker_params.sample_count,
// voice_info.GetInParams().node_id);
}
}
void AudioCore::CommandGenerator::GenerateBiquadFilterCommand(
s32 mix_buffer, const BiquadFilterParameter& params, std::array<s64, 2>& state,
std::size_t input_offset, std::size_t output_offset, s32 sample_count, s32 node_id) {
if (dumping_frame) {
LOG_DEBUG(Audio,
"(DSP_TRACE) GenerateBiquadFilterCommand node_id={}, "
"input_mix_buffer={}, output_mix_buffer={}",
node_id, input_offset, output_offset);
}
const auto* input = GetMixBuffer(input_offset);
auto* output = GetMixBuffer(output_offset);
// Biquad filter parameters
const auto [n0, n1, n2] = params.numerator;
const auto [d0, d1] = params.denominator;
// Biquad filter states
auto [s0, s1] = state;
constexpr s64 int32_min = std::numeric_limits<s32>::min();
constexpr s64 int32_max = std::numeric_limits<s32>::max();
for (int i = 0; i < sample_count; ++i) {
const auto sample = static_cast<s64>(input[i]);
const auto f = (sample * n0 + s0 + 0x4000) >> 15;
const auto y = std::clamp(f, int32_min, int32_max);
s0 = sample * n1 + y * d0 + s1;
s1 = sample * n2 + y * d1;
output[i] = static_cast<s32>(y);
}
state = {s0, s1};
}
void CommandGenerator::GenerateDepopPrepareCommand(VoiceState& dsp_state,
std::size_t mix_buffer_count,
std::size_t mix_buffer_offset) {
for (std::size_t i = 0; i < mix_buffer_count; i++) {
auto& sample = dsp_state.previous_samples[i];
if (sample != 0) {
depop_buffer[mix_buffer_offset + i] += sample;
sample = 0;
}
}
}
void CommandGenerator::GenerateDepopForMixBuffersCommand(std::size_t mix_buffer_count,
std::size_t mix_buffer_offset,
s32 sample_rate) {
const std::size_t end_offset =
std::min(mix_buffer_offset + mix_buffer_count, GetTotalMixBufferCount());
const s32 delta = sample_rate == 48000 ? 0x7B29 : 0x78CB;
for (std::size_t i = mix_buffer_offset; i < end_offset; i++) {
if (depop_buffer[i] == 0) {
continue;
}
depop_buffer[i] =
ApplyMixDepop(GetMixBuffer(i), depop_buffer[i], delta, worker_params.sample_count);
}
}
void CommandGenerator::GenerateEffectCommand(ServerMixInfo& mix_info) {
const std::size_t effect_count = effect_context.GetCount();
const auto buffer_offset = mix_info.GetInParams().buffer_offset;
for (std::size_t i = 0; i < effect_count; i++) {
const auto index = mix_info.GetEffectOrder(i);
if (index == AudioCommon::NO_EFFECT_ORDER) {
break;
}
auto* info = effect_context.GetInfo(index);
const auto type = info->GetType();
// TODO(ogniK): Finish remaining effects
switch (type) {
case EffectType::Aux:
GenerateAuxCommand(buffer_offset, info, info->IsEnabled());
break;
case EffectType::I3dl2Reverb:
GenerateI3dl2ReverbEffectCommand(buffer_offset, info, info->IsEnabled());
break;
case EffectType::BiquadFilter:
GenerateBiquadFilterEffectCommand(buffer_offset, info, info->IsEnabled());
break;
default:
break;
}
info->UpdateForCommandGeneration();
}
}
void CommandGenerator::GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info,
bool enabled) {
if (!enabled) {
return;
}
const auto& params = dynamic_cast<EffectI3dl2Reverb*>(info)->GetParams();
const auto channel_count = params.channel_count;
for (s32 i = 0; i < channel_count; i++) {
// TODO(ogniK): Actually implement reverb
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]);
ApplyMix<1>(output, input, 32768, worker_params.sample_count);
}
}
}
void CommandGenerator::GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset, EffectBase* info,
bool enabled) {
if (!enabled) {
return;
}
const auto& params = dynamic_cast<EffectBiquadFilter*>(info)->GetParams();
const auto channel_count = params.channel_count;
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]);
ApplyMix<1>(output, input, 32768, worker_params.sample_count);
}
}
}
void CommandGenerator::GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled) {
auto* aux = dynamic_cast<EffectAuxInfo*>(info);
const auto& params = aux->GetParams();
if (aux->GetSendBuffer() != 0 && aux->GetRecvBuffer() != 0) {
const auto max_channels = params.count;
u32 offset{};
for (u32 channel = 0; channel < max_channels; channel++) {
u32 write_count = 0;
if (channel == (max_channels - 1)) {
write_count = offset + worker_params.sample_count;
}
const auto input_index = params.input_mix_buffers[channel] + mix_buffer_offset;
const auto output_index = params.output_mix_buffers[channel] + mix_buffer_offset;
if (enabled) {
AuxInfoDSP send_info{};
AuxInfoDSP recv_info{};
memory.ReadBlock(aux->GetSendInfo(), &send_info, sizeof(AuxInfoDSP));
memory.ReadBlock(aux->GetRecvInfo(), &recv_info, sizeof(AuxInfoDSP));
WriteAuxBuffer(send_info, aux->GetSendBuffer(), params.sample_count,
GetMixBuffer(input_index), worker_params.sample_count, offset,
write_count);
memory.WriteBlock(aux->GetSendInfo(), &send_info, sizeof(AuxInfoDSP));
const auto samples_read = ReadAuxBuffer(
recv_info, aux->GetRecvBuffer(), params.sample_count,
GetMixBuffer(output_index), worker_params.sample_count, offset, write_count);
memory.WriteBlock(aux->GetRecvInfo(), &recv_info, sizeof(AuxInfoDSP));
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);
}
} 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),
worker_params.sample_count * sizeof(s32));
}
}
offset += worker_params.sample_count;
}
}
}
ServerSplitterDestinationData* CommandGenerator::GetDestinationData(s32 splitter_id, s32 index) {
if (splitter_id == AudioCommon::NO_SPLITTER) {
return nullptr;
}
return splitter_context.GetDestinationData(splitter_id, index);
}
s32 CommandGenerator::WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples,
const s32* data, u32 sample_count, u32 write_offset,
u32 write_count) {
if (max_samples == 0) {
return 0;
}
u32 offset = dsp_info.write_offset + write_offset;
if (send_buffer == 0 || offset > max_samples) {
return 0;
}
std::size_t 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));
offset = (offset + samples_to_grab) % max_samples;
remaining -= samples_to_grab;
data_offset += samples_to_grab;
}
if (write_count != 0) {
dsp_info.write_offset = (dsp_info.write_offset + write_count) % max_samples;
}
return sample_count;
}
s32 CommandGenerator::ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples,
s32* out_data, u32 sample_count, u32 read_offset,
u32 read_count) {
if (max_samples == 0) {
return 0;
}
u32 offset = recv_info.read_offset + read_offset;
if (recv_buffer == 0 || offset > max_samples) {
return 0;
}
u32 remaining = sample_count;
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;
offset = (offset + samples_to_grab) % max_samples;
remaining -= samples_to_grab;
}
if (read_count != 0) {
recv_info.read_offset = (recv_info.read_offset + read_count) % max_samples;
}
return sample_count;
}
void CommandGenerator::GenerateVolumeRampCommand(float last_volume, float current_volume,
s32 channel, s32 node_id) {
const auto last = static_cast<s32>(last_volume * 32768.0f);
const auto current = static_cast<s32>(current_volume * 32768.0f);
const auto delta = static_cast<s32>((static_cast<float>(current) - static_cast<float>(last)) /
static_cast<float>(worker_params.sample_count));
if (dumping_frame) {
LOG_DEBUG(Audio,
"(DSP_TRACE) GenerateVolumeRampCommand node_id={}, input={}, output={}, "
"last_volume={}, current_volume={}",
node_id, GetMixChannelBufferOffset(channel), GetMixChannelBufferOffset(channel),
last_volume, current_volume);
}
// Apply generic gain on samples
ApplyGain(GetChannelMixBuffer(channel), GetChannelMixBuffer(channel), last, delta,
worker_params.sample_count);
}
void CommandGenerator::GenerateVoiceMixCommand(const MixVolumeBuffer& mix_volumes,
const MixVolumeBuffer& last_mix_volumes,
VoiceState& dsp_state, s32 mix_buffer_offset,
s32 mix_buffer_count, s32 voice_index, s32 node_id) {
// Loop all our mix buffers
for (s32 i = 0; i < mix_buffer_count; i++) {
if (last_mix_volumes[i] != 0.0f || mix_volumes[i] != 0.0f) {
const auto delta = static_cast<float>((mix_volumes[i] - last_mix_volumes[i])) /
static_cast<float>(worker_params.sample_count);
if (dumping_frame) {
LOG_DEBUG(Audio,
"(DSP_TRACE) GenerateVoiceMixCommand node_id={}, input={}, "
"output={}, last_volume={}, current_volume={}",
node_id, voice_index, mix_buffer_offset + i, last_mix_volumes[i],
mix_volumes[i]);
}
dsp_state.previous_samples[i] =
ApplyMixRamp(GetMixBuffer(mix_buffer_offset + i), GetMixBuffer(voice_index),
last_mix_volumes[i], delta, worker_params.sample_count);
} else {
dsp_state.previous_samples[i] = 0;
}
}
}
void CommandGenerator::GenerateSubMixCommand(ServerMixInfo& mix_info) {
if (dumping_frame) {
LOG_DEBUG(Audio, "(DSP_TRACE) GenerateSubMixCommand");
}
const auto& in_params = mix_info.GetInParams();
GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset,
in_params.sample_rate);
GenerateEffectCommand(mix_info);
GenerateMixCommands(mix_info);
}
void CommandGenerator::GenerateMixCommands(ServerMixInfo& mix_info) {
if (!mix_info.HasAnyConnection()) {
return;
}
const auto& in_params = mix_info.GetInParams();
if (in_params.dest_mix_id != AudioCommon::NO_MIX) {
const auto& dest_mix = mix_context.GetInfo(in_params.dest_mix_id);
const auto& dest_in_params = dest_mix.GetInParams();
const auto buffer_count = in_params.buffer_count;
for (s32 i = 0; i < buffer_count; i++) {
for (s32 j = 0; j < dest_in_params.buffer_count; j++) {
const auto mixed_volume = in_params.volume * in_params.mix_volume[i][j];
if (mixed_volume != 0.0f) {
GenerateMixCommand(dest_in_params.buffer_offset + j,
in_params.buffer_offset + i, mixed_volume,
in_params.node_id);
}
}
}
} else if (in_params.splitter_id != AudioCommon::NO_SPLITTER) {
s32 base{};
while (const auto* destination_data = GetDestinationData(in_params.splitter_id, base++)) {
if (!destination_data->IsConfigured()) {
continue;
}
const auto& dest_mix = mix_context.GetInfo(destination_data->GetMixId());
const auto& dest_in_params = dest_mix.GetInParams();
const auto mix_index = (base - 1) % in_params.buffer_count + in_params.buffer_offset;
for (std::size_t i = 0; i < static_cast<std::size_t>(dest_in_params.buffer_count);
i++) {
const auto mixed_volume = in_params.volume * destination_data->GetMixVolume(i);
if (mixed_volume != 0.0f) {
GenerateMixCommand(dest_in_params.buffer_offset + i, mix_index, mixed_volume,
in_params.node_id);
}
}
}
}
}
void CommandGenerator::GenerateMixCommand(std::size_t output_offset, std::size_t input_offset,
float volume, s32 node_id) {
if (dumping_frame) {
LOG_DEBUG(Audio,
"(DSP_TRACE) GenerateMixCommand node_id={}, input={}, output={}, volume={}",
node_id, input_offset, output_offset, volume);
}
auto* output = GetMixBuffer(output_offset);
const auto* input = GetMixBuffer(input_offset);
const s32 gain = static_cast<s32>(volume * 32768.0f);
// Mix with loop unrolling
if (worker_params.sample_count % 4 == 0) {
ApplyMix<4>(output, input, gain, worker_params.sample_count);
} else if (worker_params.sample_count % 2 == 0) {
ApplyMix<2>(output, input, gain, worker_params.sample_count);
} else {
ApplyMix<1>(output, input, gain, worker_params.sample_count);
}
}
void CommandGenerator::GenerateFinalMixCommand() {
if (dumping_frame) {
LOG_DEBUG(Audio, "(DSP_TRACE) GenerateFinalMixCommand");
}
auto& mix_info = mix_context.GetFinalMixInfo();
const auto& in_params = mix_info.GetInParams();
GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset,
in_params.sample_rate);
GenerateEffectCommand(mix_info);
for (s32 i = 0; i < in_params.buffer_count; i++) {
const s32 gain = static_cast<s32>(in_params.volume * 32768.0f);
if (dumping_frame) {
LOG_DEBUG(
Audio,
"(DSP_TRACE) ApplyGainWithoutDelta node_id={}, input={}, output={}, volume={}",
in_params.node_id, in_params.buffer_offset + i, in_params.buffer_offset + i,
in_params.volume);
}
ApplyGainWithoutDelta(GetMixBuffer(in_params.buffer_offset + i),
GetMixBuffer(in_params.buffer_offset + i), gain,
worker_params.sample_count);
}
}
s32 CommandGenerator::DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
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) {
return 0;
}
if (wave_buffer.buffer_size == 0) {
return 0;
}
if (wave_buffer.end_sample_offset < wave_buffer.start_sample_offset) {
return 0;
}
const auto samples_remaining =
(wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) - dsp_state.offset;
const auto start_offset =
((wave_buffer.start_sample_offset + dsp_state.offset) * in_params.channel_count) *
sizeof(s16);
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));
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];
}
}
return samples_processed;
}
s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
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) {
return 0;
}
if (wave_buffer.buffer_size == 0) {
return 0;
}
if (wave_buffer.end_sample_offset < wave_buffer.start_sample_offset) {
return 0;
}
static constexpr std::array<int, 16> SIGNED_NIBBLES{
0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1,
};
constexpr std::size_t FRAME_LEN = 8;
constexpr std::size_t NIBBLES_PER_SAMPLE = 16;
constexpr std::size_t SAMPLES_PER_FRAME = 14;
auto frame_header = dsp_state.context.header;
s32 idx = (frame_header >> 4) & 0xf;
s32 scale = frame_header & 0xf;
s16 yn1 = dsp_state.context.yn1;
s16 yn2 = dsp_state.context.yn2;
Codec::ADPCM_Coeff coeffs;
memory.ReadBlock(in_params.additional_params_address, coeffs.data(),
sizeof(Codec::ADPCM_Coeff));
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_processed = std::min(sample_count, samples_remaining);
const auto sample_pos = wave_buffer.start_sample_offset + dsp_state.offset;
const auto samples_remaining_in_frame = sample_pos % SAMPLES_PER_FRAME;
auto position_in_frame = ((sample_pos / SAMPLES_PER_FRAME) * NIBBLES_PER_SAMPLE) +
samples_remaining_in_frame + (samples_remaining_in_frame != 0 ? 2 : 0);
const auto decode_sample = [&](const int nibble) -> s16 {
const int xn = nibble * (1 << scale);
// We first transform everything into 11 bit fixed point, perform the second order
// digital filter, then transform back.
// 0x400 == 0.5 in 11 bit fixed point.
// Filter: y[n] = x[n] + 0.5 + c1 * y[n-1] + c2 * y[n-2]
int val = ((xn << 11) + 0x400 + coef1 * yn1 + coef2 * yn2) >> 11;
// Clamp to output range.
val = std::clamp<s32>(val, -32768, 32767);
// Advance output feedback.
yn2 = yn1;
yn1 = static_cast<s16>(val);
return yn1;
};
std::size_t buffer_offset{};
std::vector<u8> buffer(
std::max((samples_processed / FRAME_LEN) * SAMPLES_PER_FRAME, FRAME_LEN));
memory.ReadBlock(wave_buffer.buffer_address + (position_in_frame / 2), buffer.data(),
buffer.size());
std::size_t cur_mix_offset = mix_offset;
auto remaining_samples = samples_processed;
while (remaining_samples > 0) {
if (position_in_frame % NIBBLES_PER_SAMPLE == 0) {
// Read header
frame_header = buffer[buffer_offset++];
idx = (frame_header >> 4) & 0xf;
scale = frame_header & 0xf;
coef1 = coeffs[idx * 2];
coef2 = coeffs[idx * 2 + 1];
position_in_frame += 2;
// Decode entire frame
if (remaining_samples >= static_cast<int>(SAMPLES_PER_FRAME)) {
for (std::size_t i = 0; i < SAMPLES_PER_FRAME / 2; i++) {
// Sample 1
const s32 s0 = SIGNED_NIBBLES[buffer[buffer_offset] >> 4];
const s32 s1 = SIGNED_NIBBLES[buffer[buffer_offset++] & 0xf];
const s16 sample_1 = decode_sample(s0);
const s16 sample_2 = decode_sample(s1);
sample_buffer[cur_mix_offset++] = sample_1;
sample_buffer[cur_mix_offset++] = sample_2;
}
remaining_samples -= static_cast<int>(SAMPLES_PER_FRAME);
position_in_frame += SAMPLES_PER_FRAME;
continue;
}
}
// Decode mid frame
s32 current_nibble = buffer[buffer_offset];
if (position_in_frame++ & 0x1) {
current_nibble &= 0xf;
buffer_offset++;
} else {
current_nibble >>= 4;
}
const s16 sample = decode_sample(SIGNED_NIBBLES[current_nibble]);
sample_buffer[cur_mix_offset++] = sample;
remaining_samples--;
}
dsp_state.context.header = frame_header;
dsp_state.context.yn1 = yn1;
dsp_state.context.yn2 = yn2;
return samples_processed;
}
s32* CommandGenerator::GetMixBuffer(std::size_t index) {
return mix_buffer.data() + (index * worker_params.sample_count);
}
const s32* CommandGenerator::GetMixBuffer(std::size_t index) const {
return mix_buffer.data() + (index * worker_params.sample_count);
}
std::size_t CommandGenerator::GetMixChannelBufferOffset(s32 channel) const {
return worker_params.mix_buffer_count + channel;
}
std::size_t CommandGenerator::GetTotalMixBufferCount() const {
return worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT;
}
s32* CommandGenerator::GetChannelMixBuffer(s32 channel) {
return GetMixBuffer(worker_params.mix_buffer_count + channel);
}
const s32* CommandGenerator::GetChannelMixBuffer(s32 channel) const {
return GetMixBuffer(worker_params.mix_buffer_count + channel);
}
void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* output,
VoiceState& dsp_state, s32 channel,
s32 target_sample_rate, s32 sample_count,
s32 node_id) {
const auto& in_params = voice_info.GetInParams();
if (dumping_frame) {
LOG_DEBUG(Audio,
"(DSP_TRACE) DecodeFromWaveBuffers, node_id={}, channel={}, "
"format={}, sample_count={}, sample_rate={}, mix_id={}, splitter_id={}",
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; });
const auto resample_rate = static_cast<s32>(
static_cast<float>(in_params.sample_rate) / static_cast<float>(target_sample_rate) *
static_cast<float>(static_cast<s32>(in_params.pitch * 32768.0f)));
if (dsp_state.fraction + sample_count * resample_rate >
static_cast<s32>(SCALED_MIX_BUFFER_SIZE - 4ULL)) {
return;
}
auto min_required_samples =
std::min(static_cast<s32>(SCALED_MIX_BUFFER_SIZE) - dsp_state.fraction, resample_rate);
if (min_required_samples >= sample_count) {
min_required_samples = sample_count;
}
std::size_t temp_mix_offset{};
bool is_buffer_completed{false};
auto samples_remaining = sample_count;
while (samples_remaining > 0 && !is_buffer_completed) {
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;
if (!in_params.behavior_flags.is_pitch_and_src_skipped) {
// Append sample histtory for resampler
for (std::size_t i = 0; i < AudioCommon::MAX_SAMPLE_HISTORY; i++) {
sample_buffer[temp_mix_offset + i] = dsp_state.sample_history[i];
}
temp_mix_offset += 4;
}
s32 samples_read{};
while (samples_read < samples_to_read) {
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
}
s32 samples_decoded{0};
switch (in_params.sample_format) {
case SampleFormat::Pcm16:
samples_decoded = DecodePcm16(voice_info, dsp_state, 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);
break;
default:
UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format);
}
temp_mix_offset += samples_decoded;
samples_read += samples_decoded;
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) ||
samples_decoded == 0) {
// Reset our sample offset
dsp_state.offset = 0;
if (wave_buffer.is_looping) {
if (samples_decoded == 0) {
// End of our buffer
is_buffer_completed = true;
break;
}
if (in_params.behavior_flags.is_played_samples_reset_at_loop_point.Value()) {
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;
}
}
}
}
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));
} 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);
// 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;
}
}
} // namespace AudioCore

View File

@@ -0,0 +1,102 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include "audio_core/common.h"
#include "audio_core/voice_context.h"
#include "common/common_types.h"
namespace Core::Memory {
class Memory;
}
namespace AudioCore {
class MixContext;
class SplitterContext;
class ServerSplitterDestinationData;
class ServerMixInfo;
class EffectContext;
class EffectBase;
struct AuxInfoDSP;
using MixVolumeBuffer = std::array<float, AudioCommon::MAX_MIX_BUFFERS>;
class CommandGenerator {
public:
explicit CommandGenerator(AudioCommon::AudioRendererParameter& worker_params,
VoiceContext& voice_context, MixContext& mix_context,
SplitterContext& splitter_context, EffectContext& effect_context,
Core::Memory::Memory& memory);
~CommandGenerator();
void ClearMixBuffers();
void GenerateVoiceCommands();
void GenerateVoiceCommand(ServerVoiceInfo& voice_info);
void GenerateSubMixCommands();
void GenerateFinalMixCommands();
void PreCommand();
void PostCommand();
s32* GetChannelMixBuffer(s32 channel);
const s32* GetChannelMixBuffer(s32 channel) const;
s32* GetMixBuffer(std::size_t index);
const s32* GetMixBuffer(std::size_t index) const;
std::size_t GetMixChannelBufferOffset(s32 channel) const;
std::size_t GetTotalMixBufferCount() const;
private:
void GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 channel);
void GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
s32 mix_buffer_count, s32 channel);
void GenerateVolumeRampCommand(float last_volume, float current_volume, s32 channel,
s32 node_id);
void GenerateVoiceMixCommand(const MixVolumeBuffer& mix_volumes,
const MixVolumeBuffer& last_mix_volumes, VoiceState& dsp_state,
s32 mix_buffer_offset, s32 mix_buffer_count, s32 voice_index,
s32 node_id);
void GenerateSubMixCommand(ServerMixInfo& mix_info);
void GenerateMixCommands(ServerMixInfo& mix_info);
void GenerateMixCommand(std::size_t output_offset, std::size_t input_offset, float volume,
s32 node_id);
void GenerateFinalMixCommand();
void GenerateBiquadFilterCommand(s32 mix_buffer, const BiquadFilterParameter& params,
std::array<s64, 2>& state, std::size_t input_offset,
std::size_t output_offset, s32 sample_count, s32 node_id);
void GenerateDepopPrepareCommand(VoiceState& dsp_state, std::size_t mix_buffer_count,
std::size_t mix_buffer_offset);
void GenerateDepopForMixBuffersCommand(std::size_t mix_buffer_count,
std::size_t mix_buffer_offset, s32 sample_rate);
void GenerateEffectCommand(ServerMixInfo& mix_info);
void GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled);
void GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled);
void GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled);
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);
// 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);
AudioCommon::AudioRendererParameter& worker_params;
VoiceContext& voice_context;
MixContext& mix_context;
SplitterContext& splitter_context;
EffectContext& effect_context;
Core::Memory::Memory& memory;
std::vector<s32> mix_buffer{};
std::vector<s32> sample_buffer{};
std::vector<s32> depop_buffer{};
bool dumping_frame{false};
};
} // namespace AudioCore

View File

@@ -3,18 +3,36 @@
// Refer to the license.txt file included.
#pragma once
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "core/hle/result.h"
namespace AudioCore {
namespace AudioCommon {
namespace Audren {
constexpr ResultCode ERR_INVALID_PARAMETERS{ErrorModule::Audio, 41};
}
constexpr ResultCode ERR_SPLITTER_SORT_FAILED{ErrorModule::Audio, 43};
} // namespace Audren
constexpr u32_le CURRENT_PROCESS_REVISION = Common::MakeMagic('R', 'E', 'V', '8');
constexpr std::size_t MAX_MIX_BUFFERS = 24;
constexpr std::size_t MAX_BIQUAD_FILTERS = 2;
constexpr std::size_t MAX_CHANNEL_COUNT = 6;
constexpr std::size_t MAX_WAVE_BUFFERS = 4;
constexpr std::size_t MAX_SAMPLE_HISTORY = 4;
constexpr u32 STREAM_SAMPLE_RATE = 48000;
constexpr u32 STREAM_NUM_CHANNELS = 6;
constexpr s32 NO_SPLITTER = -1;
constexpr s32 NO_MIX = 0x7fffffff;
constexpr s32 NO_FINAL_MIX = std::numeric_limits<s32>::min();
constexpr s32 FINAL_MIX = 0;
constexpr s32 NO_EFFECT_ORDER = -1;
constexpr std::size_t TEMP_MIX_BASE_SIZE = 0x3f00; // TODO(ogniK): Work out this constant
// Any size checks seem to take the sample history into account
// and our const ends up being 0x3f04, the 4 bytes are most
// likely the sample history
constexpr std::size_t TOTAL_TEMP_MIX_SIZE = TEMP_MIX_BASE_SIZE + AudioCommon::MAX_SAMPLE_HISTORY;
static constexpr u32 VersionFromRevision(u32_le rev) {
// "REV7" -> 7
@@ -45,4 +63,46 @@ static constexpr bool CanConsumeBuffer(std::size_t size, std::size_t offset, std
return true;
}
} // namespace AudioCore
struct UpdateDataSizes {
u32_le behavior{};
u32_le memory_pool{};
u32_le voice{};
u32_le voice_channel_resource{};
u32_le effect{};
u32_le mixer{};
u32_le sink{};
u32_le performance{};
u32_le splitter{};
u32_le render_info{};
INSERT_PADDING_WORDS(4);
};
static_assert(sizeof(UpdateDataSizes) == 0x38, "UpdateDataSizes is an invalid size");
struct UpdateDataHeader {
u32_le revision{};
UpdateDataSizes size{};
u32_le total_size{};
};
static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader is an invalid size");
struct AudioRendererParameter {
u32_le sample_rate;
u32_le sample_count;
u32_le mix_buffer_count;
u32_le submix_count;
u32_le voice_count;
u32_le sink_count;
u32_le effect_count;
u32_le performance_frame_count;
u8 is_voice_drop_enabled;
u8 unknown_21;
u8 unknown_22;
u8 execution_mode;
u32_le splitter_count;
u32_le num_splitter_send_channels;
u32_le unknown_30;
u32_le revision;
};
static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size");
} // namespace AudioCommon

View File

@@ -23,14 +23,24 @@ class CubebSinkStream final : public SinkStream {
public:
CubebSinkStream(cubeb* ctx, u32 sample_rate, u32 num_channels_, cubeb_devid output_device,
const std::string& name)
: ctx{ctx}, num_channels{std::min(num_channels_, 2u)}, time_stretch{sample_rate,
: ctx{ctx}, num_channels{std::min(num_channels_, 6u)}, time_stretch{sample_rate,
num_channels} {
cubeb_stream_params params{};
params.rate = sample_rate;
params.channels = num_channels;
params.format = CUBEB_SAMPLE_S16NE;
params.layout = num_channels == 1 ? CUBEB_LAYOUT_MONO : CUBEB_LAYOUT_STEREO;
switch (num_channels) {
case 1:
params.layout = CUBEB_LAYOUT_MONO;
break;
case 2:
params.layout = CUBEB_LAYOUT_STEREO;
break;
case 6:
params.layout = CUBEB_LAYOUT_3F2_LFE;
break;
}
u32 minimum_latency{};
if (cubeb_get_min_latency(ctx, &params, &minimum_latency) != CUBEB_OK) {
@@ -83,8 +93,10 @@ public:
constexpr s32 clev{707}; // center mixing level coefficient
constexpr s32 slev{707}; // surround mixing level coefficient
buf.push_back(left + (clev * center / 1000) + (slev * surround_left / 1000));
buf.push_back(right + (clev * center / 1000) + (slev * surround_right / 1000));
buf.push_back(static_cast<s16>(left + (clev * center / 1000) +
(slev * surround_left / 1000)));
buf.push_back(static_cast<s16>(right + (clev * center / 1000) +
(slev * surround_right / 1000)));
}
queue.Push(buf);
return;
@@ -182,8 +194,8 @@ SinkStream& CubebSink::AcquireSinkStream(u32 sample_rate, u32 num_channels,
long CubebSinkStream::DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer,
void* output_buffer, long num_frames) {
CubebSinkStream* impl = static_cast<CubebSinkStream*>(user_data);
u8* buffer = reinterpret_cast<u8*>(output_buffer);
auto* impl = static_cast<CubebSinkStream*>(user_data);
auto* buffer = static_cast<u8*>(output_buffer);
if (!impl) {
return {};
@@ -193,6 +205,7 @@ long CubebSinkStream::DataCallback(cubeb_stream* stream, void* user_data, const
const std::size_t samples_to_write = num_channels * num_frames;
std::size_t samples_written;
/*
if (Settings::values.enable_audio_stretching.GetValue()) {
const std::vector<s16> in{impl->queue.Pop()};
const std::size_t num_in{in.size() / num_channels};
@@ -207,7 +220,8 @@ long CubebSinkStream::DataCallback(cubeb_stream* stream, void* user_data, const
}
} else {
samples_written = impl->queue.Pop(buffer, samples_to_write);
}
}*/
samples_written = impl->queue.Pop(buffer, samples_to_write);
if (samples_written >= num_channels) {
std::memcpy(&impl->last_frame[0], buffer + (samples_written - num_channels) * sizeof(s16),

View File

@@ -0,0 +1,299 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include "audio_core/effect_context.h"
namespace AudioCore {
namespace {
bool ValidChannelCountForEffect(s32 channel_count) {
return channel_count == 1 || channel_count == 2 || channel_count == 4 || channel_count == 6;
}
} // namespace
EffectContext::EffectContext(std::size_t effect_count) : effect_count(effect_count) {
effects.reserve(effect_count);
std::generate_n(std::back_inserter(effects), effect_count,
[] { return std::make_unique<EffectStubbed>(); });
}
EffectContext::~EffectContext() = default;
std::size_t EffectContext::GetCount() const {
return effect_count;
}
EffectBase* EffectContext::GetInfo(std::size_t i) {
return effects.at(i).get();
}
EffectBase* EffectContext::RetargetEffect(std::size_t i, EffectType effect) {
switch (effect) {
case EffectType::Invalid:
effects[i] = std::make_unique<EffectStubbed>();
break;
case EffectType::BufferMixer:
effects[i] = std::make_unique<EffectBufferMixer>();
break;
case EffectType::Aux:
effects[i] = std::make_unique<EffectAuxInfo>();
break;
case EffectType::Delay:
effects[i] = std::make_unique<EffectDelay>();
break;
case EffectType::Reverb:
effects[i] = std::make_unique<EffectReverb>();
break;
case EffectType::I3dl2Reverb:
effects[i] = std::make_unique<EffectI3dl2Reverb>();
break;
case EffectType::BiquadFilter:
effects[i] = std::make_unique<EffectBiquadFilter>();
break;
default:
UNREACHABLE_MSG("Unimplemented effect {}", effect);
effects[i] = std::make_unique<EffectStubbed>();
}
return GetInfo(i);
}
const EffectBase* EffectContext::GetInfo(std::size_t i) const {
return effects.at(i).get();
}
EffectStubbed::EffectStubbed() : EffectBase::EffectBase(EffectType::Invalid) {}
EffectStubbed::~EffectStubbed() = default;
void EffectStubbed::Update(EffectInfo::InParams& in_params) {}
void EffectStubbed::UpdateForCommandGeneration() {}
EffectBase::EffectBase(EffectType effect_type) : effect_type(effect_type) {}
EffectBase::~EffectBase() = default;
UsageState EffectBase::GetUsage() const {
return usage;
}
EffectType EffectBase::GetType() const {
return effect_type;
}
bool EffectBase::IsEnabled() const {
return enabled;
}
s32 EffectBase::GetMixID() const {
return mix_id;
}
s32 EffectBase::GetProcessingOrder() const {
return processing_order;
}
EffectI3dl2Reverb::EffectI3dl2Reverb() : EffectGeneric::EffectGeneric(EffectType::I3dl2Reverb) {}
EffectI3dl2Reverb::~EffectI3dl2Reverb() = default;
void EffectI3dl2Reverb::Update(EffectInfo::InParams& in_params) {
auto& internal_params = GetParams();
const auto* reverb_params = reinterpret_cast<I3dl2ReverbParams*>(in_params.raw.data());
if (!ValidChannelCountForEffect(reverb_params->max_channels)) {
UNREACHABLE_MSG("Invalid reverb max channel count {}", reverb_params->max_channels);
return;
}
const auto last_status = internal_params.status;
mix_id = in_params.mix_id;
processing_order = in_params.processing_order;
internal_params = *reverb_params;
if (!ValidChannelCountForEffect(reverb_params->channel_count)) {
internal_params.channel_count = internal_params.max_channels;
}
enabled = in_params.is_enabled;
if (last_status != ParameterStatus::Updated) {
internal_params.status = last_status;
}
if (in_params.is_new || skipped) {
usage = UsageState::Initialized;
internal_params.status = ParameterStatus::Initialized;
skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
}
}
void EffectI3dl2Reverb::UpdateForCommandGeneration() {
if (enabled) {
usage = UsageState::Running;
} else {
usage = UsageState::Stopped;
}
GetParams().status = ParameterStatus::Updated;
}
EffectBiquadFilter::EffectBiquadFilter() : EffectGeneric::EffectGeneric(EffectType::BiquadFilter) {}
EffectBiquadFilter::~EffectBiquadFilter() = default;
void EffectBiquadFilter::Update(EffectInfo::InParams& in_params) {
auto& internal_params = GetParams();
const auto* biquad_params = reinterpret_cast<BiquadFilterParams*>(in_params.raw.data());
mix_id = in_params.mix_id;
processing_order = in_params.processing_order;
internal_params = *biquad_params;
enabled = in_params.is_enabled;
}
void EffectBiquadFilter::UpdateForCommandGeneration() {
if (enabled) {
usage = UsageState::Running;
} else {
usage = UsageState::Stopped;
}
GetParams().status = ParameterStatus::Updated;
}
EffectAuxInfo::EffectAuxInfo() : EffectGeneric::EffectGeneric(EffectType::Aux) {}
EffectAuxInfo::~EffectAuxInfo() = default;
void EffectAuxInfo::Update(EffectInfo::InParams& in_params) {
const auto* aux_params = reinterpret_cast<AuxInfo*>(in_params.raw.data());
mix_id = in_params.mix_id;
processing_order = in_params.processing_order;
GetParams() = *aux_params;
enabled = in_params.is_enabled;
if (in_params.is_new || skipped) {
skipped = aux_params->send_buffer_info == 0 || aux_params->return_buffer_info == 0;
if (skipped) {
return;
}
// There's two AuxInfos which are an identical size, the first one is managed by the cpu,
// the second is managed by the dsp. All we care about is managing the DSP one
send_info = aux_params->send_buffer_info + sizeof(AuxInfoDSP);
send_buffer = aux_params->send_buffer_info + (sizeof(AuxInfoDSP) * 2);
recv_info = aux_params->return_buffer_info + sizeof(AuxInfoDSP);
recv_buffer = aux_params->return_buffer_info + (sizeof(AuxInfoDSP) * 2);
}
}
void EffectAuxInfo::UpdateForCommandGeneration() {
if (enabled) {
usage = UsageState::Running;
} else {
usage = UsageState::Stopped;
}
}
VAddr EffectAuxInfo::GetSendInfo() const {
return send_info;
}
VAddr EffectAuxInfo::GetSendBuffer() const {
return send_buffer;
}
VAddr EffectAuxInfo::GetRecvInfo() const {
return recv_info;
}
VAddr EffectAuxInfo::GetRecvBuffer() const {
return recv_buffer;
}
EffectDelay::EffectDelay() : EffectGeneric::EffectGeneric(EffectType::Delay) {}
EffectDelay::~EffectDelay() = default;
void EffectDelay::Update(EffectInfo::InParams& in_params) {
const auto* delay_params = reinterpret_cast<DelayParams*>(in_params.raw.data());
auto& internal_params = GetParams();
if (!ValidChannelCountForEffect(delay_params->max_channels)) {
return;
}
const auto last_status = internal_params.status;
mix_id = in_params.mix_id;
processing_order = in_params.processing_order;
internal_params = *delay_params;
if (!ValidChannelCountForEffect(delay_params->channels)) {
internal_params.channels = internal_params.max_channels;
}
enabled = in_params.is_enabled;
if (last_status != ParameterStatus::Updated) {
internal_params.status = last_status;
}
if (in_params.is_new || skipped) {
usage = UsageState::Initialized;
internal_params.status = ParameterStatus::Initialized;
skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
}
}
void EffectDelay::UpdateForCommandGeneration() {
if (enabled) {
usage = UsageState::Running;
} else {
usage = UsageState::Stopped;
}
GetParams().status = ParameterStatus::Updated;
}
EffectBufferMixer::EffectBufferMixer() : EffectGeneric::EffectGeneric(EffectType::BufferMixer) {}
EffectBufferMixer::~EffectBufferMixer() = default;
void EffectBufferMixer::Update(EffectInfo::InParams& in_params) {
mix_id = in_params.mix_id;
processing_order = in_params.processing_order;
GetParams() = *reinterpret_cast<BufferMixerParams*>(in_params.raw.data());
enabled = in_params.is_enabled;
}
void EffectBufferMixer::UpdateForCommandGeneration() {
if (enabled) {
usage = UsageState::Running;
} else {
usage = UsageState::Stopped;
}
}
EffectReverb::EffectReverb() : EffectGeneric::EffectGeneric(EffectType::Reverb) {}
EffectReverb::~EffectReverb() = default;
void EffectReverb::Update(EffectInfo::InParams& in_params) {
const auto* reverb_params = reinterpret_cast<ReverbParams*>(in_params.raw.data());
auto& internal_params = GetParams();
if (!ValidChannelCountForEffect(reverb_params->max_channels)) {
return;
}
const auto last_status = internal_params.status;
mix_id = in_params.mix_id;
processing_order = in_params.processing_order;
internal_params = *reverb_params;
if (!ValidChannelCountForEffect(reverb_params->channels)) {
internal_params.channels = internal_params.max_channels;
}
enabled = in_params.is_enabled;
if (last_status != ParameterStatus::Updated) {
internal_params.status = last_status;
}
if (in_params.is_new || skipped) {
usage = UsageState::Initialized;
internal_params.status = ParameterStatus::Initialized;
skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
}
}
void EffectReverb::UpdateForCommandGeneration() {
if (enabled) {
usage = UsageState::Running;
} else {
usage = UsageState::Stopped;
}
GetParams().status = ParameterStatus::Updated;
}
} // namespace AudioCore

View File

@@ -0,0 +1,321 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <memory>
#include <vector>
#include "audio_core/common.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
namespace AudioCore {
enum class EffectType : u8 {
Invalid = 0,
BufferMixer = 1,
Aux = 2,
Delay = 3,
Reverb = 4,
I3dl2Reverb = 5,
BiquadFilter = 6,
};
enum class UsageStatus : u8 {
Invalid = 0,
New = 1,
Initialized = 2,
Used = 3,
Removed = 4,
};
enum class UsageState {
Invalid = 0,
Initialized = 1,
Running = 2,
Stopped = 3,
};
enum class ParameterStatus : u8 {
Initialized = 0,
Updating = 1,
Updated = 2,
};
struct BufferMixerParams {
std::array<s8, AudioCommon::MAX_MIX_BUFFERS> input{};
std::array<s8, AudioCommon::MAX_MIX_BUFFERS> output{};
std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> volume{};
s32_le count{};
};
static_assert(sizeof(BufferMixerParams) == 0x94, "BufferMixerParams is an invalid size");
struct AuxInfoDSP {
u32_le read_offset{};
u32_le write_offset{};
u32_le remaining{};
INSERT_PADDING_WORDS(13);
};
static_assert(sizeof(AuxInfoDSP) == 0x40, "AuxInfoDSP is an invalid size");
struct AuxInfo {
std::array<s8, AudioCommon::MAX_MIX_BUFFERS> input_mix_buffers{};
std::array<s8, AudioCommon::MAX_MIX_BUFFERS> output_mix_buffers{};
u32_le count{};
s32_le sample_rate{};
s32_le sample_count{};
s32_le mix_buffer_count{};
u64_le send_buffer_info{};
u64_le send_buffer_base{};
u64_le return_buffer_info{};
u64_le return_buffer_base{};
};
static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size");
struct I3dl2ReverbParams {
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
u16_le max_channels{};
u16_le channel_count{};
INSERT_PADDING_BYTES(1);
u32_le sample_rate{};
f32 room_hf{};
f32 hf_reference{};
f32 decay_time{};
f32 hf_decay_ratio{};
f32 room{};
f32 reflection{};
f32 reverb{};
f32 diffusion{};
f32 reflection_delay{};
f32 reverb_delay{};
f32 density{};
f32 dry_gain{};
ParameterStatus status{};
INSERT_PADDING_BYTES(3);
};
static_assert(sizeof(I3dl2ReverbParams) == 0x4c, "I3dl2ReverbParams is an invalid size");
struct BiquadFilterParams {
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
std::array<s16_le, 3> numerator;
std::array<s16_le, 2> denominator;
s8 channel_count{};
ParameterStatus status{};
};
static_assert(sizeof(BiquadFilterParams) == 0x18, "BiquadFilterParams is an invalid size");
struct DelayParams {
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
u16_le max_channels{};
u16_le channels{};
s32_le max_delay{};
s32_le delay{};
s32_le sample_rate{};
s32_le gain{};
s32_le feedback_gain{};
s32_le out_gain{};
s32_le dry_gain{};
s32_le channel_spread{};
s32_le low_pass{};
ParameterStatus status{};
INSERT_PADDING_BYTES(3);
};
static_assert(sizeof(DelayParams) == 0x38, "DelayParams is an invalid size");
struct ReverbParams {
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
u16_le max_channels{};
u16_le channels{};
s32_le sample_rate{};
s32_le mode0{};
s32_le mode0_gain{};
s32_le pre_delay{};
s32_le mode1{};
s32_le mode1_gain{};
s32_le decay{};
s32_le hf_decay_ratio{};
s32_le coloration{};
s32_le reverb_gain{};
s32_le out_gain{};
s32_le dry_gain{};
ParameterStatus status{};
INSERT_PADDING_BYTES(3);
};
static_assert(sizeof(ReverbParams) == 0x44, "ReverbParams is an invalid size");
class EffectInfo {
public:
struct InParams {
EffectType type{};
u8 is_new{};
u8 is_enabled{};
INSERT_PADDING_BYTES(1);
s32_le mix_id{};
u64_le buffer_address{};
u64_le buffer_size{};
s32_le processing_order{};
INSERT_PADDING_BYTES(4);
union {
std::array<u8, 0xa0> raw;
};
};
static_assert(sizeof(InParams) == 0xc0, "InParams is an invalid size");
struct OutParams {
UsageStatus status{};
INSERT_PADDING_BYTES(15);
};
static_assert(sizeof(OutParams) == 0x10, "OutParams is an invalid size");
};
struct AuxAddress {
VAddr send_dsp_info{};
VAddr send_buffer_base{};
VAddr return_dsp_info{};
VAddr return_buffer_base{};
};
class EffectBase {
public:
explicit EffectBase(EffectType effect_type);
virtual ~EffectBase();
virtual void Update(EffectInfo::InParams& in_params) = 0;
virtual void UpdateForCommandGeneration() = 0;
UsageState GetUsage() const;
EffectType GetType() const;
bool IsEnabled() const;
s32 GetMixID() const;
s32 GetProcessingOrder() const;
protected:
UsageState usage{UsageState::Invalid};
EffectType effect_type{};
s32 mix_id{};
s32 processing_order{};
bool enabled = false;
};
template <typename T>
class EffectGeneric : public EffectBase {
public:
explicit EffectGeneric(EffectType effect_type) : EffectBase(effect_type) {}
T& GetParams() {
return internal_params;
}
const I3dl2ReverbParams& GetParams() const {
return internal_params;
}
private:
T internal_params{};
};
class EffectStubbed : public EffectBase {
public:
explicit EffectStubbed();
~EffectStubbed() override;
void Update(EffectInfo::InParams& in_params) override;
void UpdateForCommandGeneration() override;
};
class EffectI3dl2Reverb : public EffectGeneric<I3dl2ReverbParams> {
public:
explicit EffectI3dl2Reverb();
~EffectI3dl2Reverb() override;
void Update(EffectInfo::InParams& in_params) override;
void UpdateForCommandGeneration() override;
private:
bool skipped = false;
};
class EffectBiquadFilter : public EffectGeneric<BiquadFilterParams> {
public:
explicit EffectBiquadFilter();
~EffectBiquadFilter() override;
void Update(EffectInfo::InParams& in_params) override;
void UpdateForCommandGeneration() override;
};
class EffectAuxInfo : public EffectGeneric<AuxInfo> {
public:
explicit EffectAuxInfo();
~EffectAuxInfo() override;
void Update(EffectInfo::InParams& in_params) override;
void UpdateForCommandGeneration() override;
VAddr GetSendInfo() const;
VAddr GetSendBuffer() const;
VAddr GetRecvInfo() const;
VAddr GetRecvBuffer() const;
private:
VAddr send_info{};
VAddr send_buffer{};
VAddr recv_info{};
VAddr recv_buffer{};
bool skipped = false;
AuxAddress addresses{};
};
class EffectDelay : public EffectGeneric<DelayParams> {
public:
explicit EffectDelay();
~EffectDelay() override;
void Update(EffectInfo::InParams& in_params) override;
void UpdateForCommandGeneration() override;
private:
bool skipped = false;
};
class EffectBufferMixer : public EffectGeneric<BufferMixerParams> {
public:
explicit EffectBufferMixer();
~EffectBufferMixer() override;
void Update(EffectInfo::InParams& in_params) override;
void UpdateForCommandGeneration() override;
};
class EffectReverb : public EffectGeneric<ReverbParams> {
public:
explicit EffectReverb();
~EffectReverb() override;
void Update(EffectInfo::InParams& in_params) override;
void UpdateForCommandGeneration() override;
private:
bool skipped = false;
};
class EffectContext {
public:
explicit EffectContext(std::size_t effect_count);
~EffectContext();
std::size_t GetCount() const;
EffectBase* GetInfo(std::size_t i);
EffectBase* RetargetEffect(std::size_t i, EffectType effect);
const EffectBase* GetInfo(std::size_t i) const;
private:
std::size_t effect_count{};
std::vector<std::unique_ptr<EffectBase>> effects;
};
} // namespace AudioCore

View File

@@ -0,0 +1,516 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "audio_core/behavior_info.h"
#include "audio_core/effect_context.h"
#include "audio_core/info_updater.h"
#include "audio_core/memory_pool.h"
#include "audio_core/mix_context.h"
#include "audio_core/sink_context.h"
#include "audio_core/splitter_context.h"
#include "audio_core/voice_context.h"
#include "common/logging/log.h"
namespace AudioCore {
InfoUpdater::InfoUpdater(const std::vector<u8>& in_params, std::vector<u8>& out_params,
BehaviorInfo& behavior_info)
: in_params(in_params), out_params(out_params), behavior_info(behavior_info) {
ASSERT(
AudioCommon::CanConsumeBuffer(in_params.size(), 0, sizeof(AudioCommon::UpdateDataHeader)));
std::memcpy(&input_header, in_params.data(), sizeof(AudioCommon::UpdateDataHeader));
output_header.total_size = sizeof(AudioCommon::UpdateDataHeader);
}
InfoUpdater::~InfoUpdater() = default;
bool InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& in_behavior_info) {
if (input_header.size.behavior != sizeof(BehaviorInfo::InParams)) {
LOG_ERROR(Audio, "Behavior info is an invalid size, expecting 0x{:X} but got 0x{:X}",
sizeof(BehaviorInfo::InParams), input_header.size.behavior);
return false;
}
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset,
sizeof(BehaviorInfo::InParams))) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
BehaviorInfo::InParams behavior_in{};
std::memcpy(&behavior_in, in_params.data() + input_offset, sizeof(BehaviorInfo::InParams));
input_offset += sizeof(BehaviorInfo::InParams);
// Make sure it's an audio revision we can actually support
if (!AudioCommon::IsValidRevision(behavior_in.revision)) {
LOG_ERROR(Audio, "Invalid input revision, revision=0x{:08X}", behavior_in.revision);
return false;
}
// Make sure that our behavior info revision matches the input
if (in_behavior_info.GetUserRevision() != behavior_in.revision) {
LOG_ERROR(Audio,
"User revision differs from input revision, expecting 0x{:08X} but got 0x{:08X}",
in_behavior_info.GetUserRevision(), behavior_in.revision);
return false;
}
// Update behavior info flags
in_behavior_info.ClearError();
in_behavior_info.UpdateFlags(behavior_in.flags);
return true;
}
bool InfoUpdater::UpdateMemoryPools(std::vector<ServerMemoryPoolInfo>& memory_pool_info) {
const auto memory_pool_count = memory_pool_info.size();
const auto total_memory_pool_in = sizeof(ServerMemoryPoolInfo::InParams) * memory_pool_count;
const auto total_memory_pool_out = sizeof(ServerMemoryPoolInfo::OutParams) * memory_pool_count;
if (input_header.size.memory_pool != total_memory_pool_in) {
LOG_ERROR(Audio, "Memory pools are an invalid size, expecting 0x{:X} but got 0x{:X}",
total_memory_pool_in, input_header.size.memory_pool);
return false;
}
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_memory_pool_in)) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
std::vector<ServerMemoryPoolInfo::InParams> mempool_in(memory_pool_count);
std::vector<ServerMemoryPoolInfo::OutParams> mempool_out(memory_pool_count);
std::memcpy(mempool_in.data(), in_params.data() + input_offset, total_memory_pool_in);
input_offset += total_memory_pool_in;
// Update our memory pools
for (std::size_t i = 0; i < memory_pool_count; i++) {
if (!memory_pool_info[i].Update(mempool_in[i], mempool_out[i])) {
LOG_ERROR(Audio, "Failed to update memory pool {}!", i);
return false;
}
}
if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset,
sizeof(BehaviorInfo::InParams))) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
std::memcpy(out_params.data() + output_offset, mempool_out.data(), total_memory_pool_out);
output_offset += total_memory_pool_out;
output_header.size.memory_pool = static_cast<u32>(total_memory_pool_out);
return true;
}
bool InfoUpdater::UpdateVoiceChannelResources(VoiceContext& voice_context) {
const auto voice_count = voice_context.GetVoiceCount();
const auto voice_size = voice_count * sizeof(VoiceChannelResource::InParams);
std::vector<VoiceChannelResource::InParams> resources_in(voice_count);
if (input_header.size.voice_channel_resource != voice_size) {
LOG_ERROR(Audio, "VoiceChannelResource is an invalid size, expecting 0x{:X} but got 0x{:X}",
voice_size, input_header.size.voice_channel_resource);
return false;
}
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, voice_size)) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
std::memcpy(resources_in.data(), in_params.data() + input_offset, voice_size);
input_offset += voice_size;
// Update our channel resources
for (std::size_t i = 0; i < voice_count; i++) {
// Grab our channel resource
auto& resource = voice_context.GetChannelResource(i);
resource.Update(resources_in[i]);
}
return true;
}
bool InfoUpdater::UpdateVoices(VoiceContext& voice_context,
std::vector<ServerMemoryPoolInfo>& memory_pool_info,
VAddr audio_codec_dsp_addr) {
const auto voice_count = voice_context.GetVoiceCount();
std::vector<VoiceInfo::InParams> voice_in(voice_count);
std::vector<VoiceInfo::OutParams> voice_out(voice_count);
const auto voice_in_size = voice_count * sizeof(VoiceInfo::InParams);
const auto voice_out_size = voice_count * sizeof(VoiceInfo::OutParams);
if (input_header.size.voice != voice_in_size) {
LOG_ERROR(Audio, "Voices are an invalid size, expecting 0x{:X} but got 0x{:X}",
voice_in_size, input_header.size.voice);
return false;
}
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, voice_in_size)) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
std::memcpy(voice_in.data(), in_params.data() + input_offset, voice_in_size);
input_offset += voice_in_size;
// Set all voices to not be in use
for (std::size_t i = 0; i < voice_count; i++) {
voice_context.GetInfo(i).GetInParams().in_use = false;
}
// Update our voices
for (std::size_t i = 0; i < voice_count; i++) {
auto& in_params = voice_in[i];
const auto channel_count = static_cast<std::size_t>(in_params.channel_count);
// Skip if it's not currently in use
if (!in_params.is_in_use) {
continue;
}
// Voice states for each channel
std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT> voice_states{};
ASSERT(static_cast<std::size_t>(in_params.id) < voice_count);
// Grab our current voice info
auto& voice_info = voice_context.GetInfo(static_cast<std::size_t>(in_params.id));
ASSERT(channel_count <= AudioCommon::MAX_CHANNEL_COUNT);
// Get all our channel voice states
for (std::size_t channel = 0; channel < channel_count; channel++) {
voice_states[channel] =
&voice_context.GetState(in_params.voice_channel_resource_ids[channel]);
}
if (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++) {
std::memset(voice_states[channel], 0, sizeof(VoiceState));
}
}
// Update our voice
voice_info.UpdateParameters(in_params, behavior_info);
// TODO(ogniK): Handle mapping errors with behavior info based on in params response
// Update our wave buffers
voice_info.UpdateWaveBuffers(in_params, voice_states, behavior_info);
voice_info.WriteOutStatus(voice_out[i], in_params, voice_states);
}
if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, voice_out_size)) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
std::memcpy(out_params.data() + output_offset, voice_out.data(), voice_out_size);
output_offset += voice_out_size;
output_header.size.voice = static_cast<u32>(voice_out_size);
return true;
}
bool InfoUpdater::UpdateEffects(EffectContext& effect_context, bool is_active) {
const auto effect_count = effect_context.GetCount();
std::vector<EffectInfo::InParams> effect_in(effect_count);
std::vector<EffectInfo::OutParams> effect_out(effect_count);
const auto total_effect_in = effect_count * sizeof(EffectInfo::InParams);
const auto total_effect_out = effect_count * sizeof(EffectInfo::OutParams);
if (input_header.size.effect != total_effect_in) {
LOG_ERROR(Audio, "Effects are an invalid size, expecting 0x{:X} but got 0x{:X}",
total_effect_in, input_header.size.effect);
return false;
}
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_effect_in)) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
std::memcpy(effect_in.data(), in_params.data() + input_offset, total_effect_in);
input_offset += total_effect_in;
// Update effects
for (std::size_t i = 0; i < effect_count; i++) {
auto* info = effect_context.GetInfo(i);
if (effect_in[i].type != info->GetType()) {
info = effect_context.RetargetEffect(i, effect_in[i].type);
}
info->Update(effect_in[i]);
if ((!is_active && info->GetUsage() != UsageState::Initialized) ||
info->GetUsage() == UsageState::Stopped) {
effect_out[i].status = UsageStatus::Removed;
} else {
effect_out[i].status = UsageStatus::Used;
}
}
if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_effect_out)) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
std::memcpy(out_params.data() + output_offset, effect_out.data(), total_effect_out);
output_offset += total_effect_out;
output_header.size.effect = static_cast<u32>(total_effect_out);
return true;
}
bool InfoUpdater::UpdateSplitterInfo(SplitterContext& splitter_context) {
std::size_t start_offset = input_offset;
std::size_t bytes_read{};
// Update splitter context
if (!splitter_context.Update(in_params, input_offset, bytes_read)) {
LOG_ERROR(Audio, "Failed to update splitter context!");
return false;
}
const auto consumed = input_offset - start_offset;
if (input_header.size.splitter != consumed) {
LOG_ERROR(Audio, "Splitters is an invalid size, expecting 0x{:X} but got 0x{:X}",
bytes_read, input_header.size.splitter);
return false;
}
return true;
}
ResultCode InfoUpdater::UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count,
SplitterContext& splitter_context,
EffectContext& effect_context) {
std::vector<MixInfo::InParams> mix_in_params;
if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
// If we're not dirty, get ALL mix in parameters
const auto context_mix_count = mix_context.GetCount();
const auto total_mix_in = context_mix_count * sizeof(MixInfo::InParams);
if (input_header.size.mixer != total_mix_in) {
LOG_ERROR(Audio, "Mixer is an invalid size, expecting 0x{:X} but got 0x{:X}",
total_mix_in, input_header.size.mixer);
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_mix_in)) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
mix_in_params.resize(context_mix_count);
std::memcpy(mix_in_params.data(), in_params.data() + input_offset, total_mix_in);
input_offset += total_mix_in;
} else {
// Only update the "dirty" mixes
MixInfo::DirtyHeader dirty_header{};
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset,
sizeof(MixInfo::DirtyHeader))) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
std::memcpy(&dirty_header, in_params.data() + input_offset, sizeof(MixInfo::DirtyHeader));
input_offset += sizeof(MixInfo::DirtyHeader);
const auto total_mix_in =
dirty_header.mixer_count * sizeof(MixInfo::InParams) + sizeof(MixInfo::DirtyHeader);
if (input_header.size.mixer != total_mix_in) {
LOG_ERROR(Audio, "Mixer is an invalid size, expecting 0x{:X} but got 0x{:X}",
total_mix_in, input_header.size.mixer);
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
if (dirty_header.mixer_count != 0) {
mix_in_params.resize(dirty_header.mixer_count);
std::memcpy(mix_in_params.data(), in_params.data() + input_offset,
mix_in_params.size() * sizeof(MixInfo::InParams));
input_offset += mix_in_params.size() * sizeof(MixInfo::InParams);
}
}
// Get our total input count
const auto mix_count = mix_in_params.size();
if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
// Only verify our buffer count if we're not dirty
std::size_t total_buffer_count{};
for (std::size_t i = 0; i < mix_count; i++) {
const auto& in = mix_in_params[i];
total_buffer_count += in.buffer_count;
if (static_cast<std::size_t>(in.dest_mix_id) > mix_count &&
in.dest_mix_id != AudioCommon::NO_MIX && in.mix_id != AudioCommon::FINAL_MIX) {
LOG_ERROR(
Audio,
"Invalid mix destination, mix_id={:X}, dest_mix_id={:X}, mix_buffer_count={:X}",
in.mix_id, in.dest_mix_id, mix_buffer_count);
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
}
if (total_buffer_count > mix_buffer_count) {
LOG_ERROR(Audio,
"Too many mix buffers used! mix_buffer_count={:X}, requesting_buffers={:X}",
mix_buffer_count, total_buffer_count);
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
}
if (mix_buffer_count == 0) {
LOG_ERROR(Audio, "No mix buffers!");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
bool should_sort = false;
for (std::size_t i = 0; i < mix_count; i++) {
const auto& mix_in = mix_in_params[i];
std::size_t target_mix{};
if (behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
target_mix = mix_in.mix_id;
} else {
// Non dirty supported games just use i instead of the actual mix_id
target_mix = i;
}
auto& mix_info = mix_context.GetInfo(target_mix);
auto& mix_info_params = mix_info.GetInParams();
if (mix_info_params.in_use != mix_in.in_use) {
mix_info_params.in_use = mix_in.in_use;
mix_info.ResetEffectProcessingOrder();
should_sort = true;
}
if (mix_in.in_use) {
should_sort |= mix_info.Update(mix_context.GetEdgeMatrix(), mix_in, behavior_info,
splitter_context, effect_context);
}
}
if (should_sort && behavior_info.IsSplitterSupported()) {
// Sort our splitter data
if (!mix_context.TsortInfo(splitter_context)) {
return AudioCommon::Audren::ERR_SPLITTER_SORT_FAILED;
}
}
// TODO(ogniK): Sort when splitter is suppoorted
return RESULT_SUCCESS;
}
bool InfoUpdater::UpdateSinks(SinkContext& sink_context) {
const auto sink_count = sink_context.GetCount();
std::vector<SinkInfo::InParams> sink_in_params(sink_count);
const auto total_sink_in = sink_count * sizeof(SinkInfo::InParams);
if (input_header.size.sink != total_sink_in) {
LOG_ERROR(Audio, "Sinks are an invalid size, expecting 0x{:X} but got 0x{:X}",
total_sink_in, input_header.size.effect);
return false;
}
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_sink_in)) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
std::memcpy(sink_in_params.data(), in_params.data() + input_offset, total_sink_in);
input_offset += total_sink_in;
// TODO(ogniK): Properly update sinks
if (!sink_in_params.empty()) {
sink_context.UpdateMainSink(sink_in_params[0]);
}
output_header.size.sink = static_cast<u32>(0x20 * sink_count);
output_offset += 0x20 * sink_count;
return true;
}
bool InfoUpdater::UpdatePerformanceBuffer() {
output_header.size.performance = 0x10;
output_offset += 0x10;
return true;
}
bool InfoUpdater::UpdateErrorInfo(BehaviorInfo& in_behavior_info) {
const auto total_beahvior_info_out = sizeof(BehaviorInfo::OutParams);
if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_beahvior_info_out)) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
BehaviorInfo::OutParams behavior_info_out{};
behavior_info.CopyErrorInfo(behavior_info_out);
std::memcpy(out_params.data() + output_offset, &behavior_info_out, total_beahvior_info_out);
output_offset += total_beahvior_info_out;
output_header.size.behavior = total_beahvior_info_out;
return true;
}
struct RendererInfo {
u64_le elasped_frame_count{};
INSERT_PADDING_WORDS(2);
};
static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size");
bool InfoUpdater::UpdateRendererInfo(std::size_t elapsed_frame_count) {
const auto total_renderer_info_out = sizeof(RendererInfo);
if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_renderer_info_out)) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
RendererInfo out{};
out.elasped_frame_count = elapsed_frame_count;
std::memcpy(out_params.data() + output_offset, &out, total_renderer_info_out);
output_offset += total_renderer_info_out;
output_header.size.render_info = total_renderer_info_out;
return true;
}
bool InfoUpdater::CheckConsumedSize() const {
if (output_offset != out_params.size()) {
LOG_ERROR(Audio, "Output is not consumed! Consumed {}, but requires {}. {} bytes remaining",
output_offset, out_params.size(), out_params.size() - output_offset);
return false;
}
/*if (input_offset != in_params.size()) {
LOG_ERROR(Audio, "Input is not consumed!");
return false;
}*/
return true;
}
bool InfoUpdater::WriteOutputHeader() {
if (!AudioCommon::CanConsumeBuffer(out_params.size(), 0,
sizeof(AudioCommon::UpdateDataHeader))) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
output_header.revision = AudioCommon::CURRENT_PROCESS_REVISION;
const auto& sz = output_header.size;
output_header.total_size += sz.behavior + sz.memory_pool + sz.voice +
sz.voice_channel_resource + sz.effect + sz.mixer + sz.sink +
sz.performance + sz.splitter + sz.render_info;
std::memcpy(out_params.data(), &output_header, sizeof(AudioCommon::UpdateDataHeader));
return true;
}
} // namespace AudioCore

View File

@@ -0,0 +1,58 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <vector>
#include "audio_core/common.h"
#include "common/common_types.h"
namespace AudioCore {
class BehaviorInfo;
class ServerMemoryPoolInfo;
class VoiceContext;
class EffectContext;
class MixContext;
class SinkContext;
class SplitterContext;
class InfoUpdater {
public:
// TODO(ogniK): Pass process handle when we support it
InfoUpdater(const std::vector<u8>& in_params, std::vector<u8>& out_params,
BehaviorInfo& behavior_info);
~InfoUpdater();
bool UpdateBehaviorInfo(BehaviorInfo& in_behavior_info);
bool UpdateMemoryPools(std::vector<ServerMemoryPoolInfo>& memory_pool_info);
bool UpdateVoiceChannelResources(VoiceContext& voice_context);
bool UpdateVoices(VoiceContext& voice_context,
std::vector<ServerMemoryPoolInfo>& memory_pool_info,
VAddr audio_codec_dsp_addr);
bool UpdateEffects(EffectContext& effect_context, bool is_active);
bool UpdateSplitterInfo(SplitterContext& splitter_context);
ResultCode UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count,
SplitterContext& splitter_context, EffectContext& effect_context);
bool UpdateSinks(SinkContext& sink_context);
bool UpdatePerformanceBuffer();
bool UpdateErrorInfo(BehaviorInfo& in_behavior_info);
bool UpdateRendererInfo(std::size_t elapsed_frame_count);
bool CheckConsumedSize() const;
bool WriteOutputHeader();
private:
const std::vector<u8>& in_params;
std::vector<u8>& out_params;
BehaviorInfo& behavior_info;
AudioCommon::UpdateDataHeader input_header{};
AudioCommon::UpdateDataHeader output_header{};
std::size_t input_offset{sizeof(AudioCommon::UpdateDataHeader)};
std::size_t output_offset{sizeof(AudioCommon::UpdateDataHeader)};
};
} // namespace AudioCore

View File

@@ -0,0 +1,62 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "audio_core/memory_pool.h"
#include "common/logging/log.h"
namespace AudioCore {
ServerMemoryPoolInfo::ServerMemoryPoolInfo() = default;
ServerMemoryPoolInfo::~ServerMemoryPoolInfo() = default;
bool ServerMemoryPoolInfo::Update(const ServerMemoryPoolInfo::InParams& in_params,
ServerMemoryPoolInfo::OutParams& out_params) {
// Our state does not need to be changed
if (in_params.state != ServerMemoryPoolInfo::State::RequestAttach &&
in_params.state != ServerMemoryPoolInfo::State::RequestDetach) {
return true;
}
// Address or size is null
if (in_params.address == 0 || in_params.size == 0) {
LOG_ERROR(Audio, "Memory pool address or size is zero! address={:X}, size={:X}",
in_params.address, in_params.size);
return false;
}
// Address or size is not aligned
if ((in_params.address % 0x1000) != 0 || (in_params.size % 0x1000) != 0) {
LOG_ERROR(Audio, "Memory pool address or size is not aligned! address={:X}, size={:X}",
in_params.address, in_params.size);
return false;
}
if (in_params.state == ServerMemoryPoolInfo::State::RequestAttach) {
cpu_address = in_params.address;
size = in_params.size;
used = true;
out_params.state = ServerMemoryPoolInfo::State::Attached;
} else {
// Unexpected address
if (cpu_address != in_params.address) {
LOG_ERROR(Audio, "Memory pool address differs! Expecting {:X} but address is {:X}",
cpu_address, in_params.address);
return false;
}
if (size != in_params.size) {
LOG_ERROR(Audio, "Memory pool size differs! Expecting {:X} but size is {:X}", size,
in_params.size);
return false;
}
cpu_address = 0;
size = 0;
used = false;
out_params.state = ServerMemoryPoolInfo::State::Detached;
}
return true;
}
} // namespace AudioCore

View File

@@ -0,0 +1,53 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
namespace AudioCore {
class ServerMemoryPoolInfo {
public:
ServerMemoryPoolInfo();
~ServerMemoryPoolInfo();
enum class State : u32_le {
Invalid = 0x0,
Aquired = 0x1,
RequestDetach = 0x2,
Detached = 0x3,
RequestAttach = 0x4,
Attached = 0x5,
Released = 0x6,
};
struct InParams {
u64_le address{};
u64_le size{};
ServerMemoryPoolInfo::State state{};
INSERT_PADDING_WORDS(3);
};
static_assert(sizeof(ServerMemoryPoolInfo::InParams) == 0x20, "InParams are an invalid size");
struct OutParams {
ServerMemoryPoolInfo::State state{};
INSERT_PADDING_WORDS(3);
};
static_assert(sizeof(ServerMemoryPoolInfo::OutParams) == 0x10, "OutParams are an invalid size");
bool Update(const ServerMemoryPoolInfo::InParams& in_params,
ServerMemoryPoolInfo::OutParams& out_params);
private:
// There's another entry here which is the DSP address, however since we're not talking to the
// DSP we can just use the same address provided by the guest without needing to remap
u64_le cpu_address{};
u64_le size{};
bool used{};
};
} // namespace AudioCore

View File

@@ -0,0 +1,296 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "audio_core/behavior_info.h"
#include "audio_core/common.h"
#include "audio_core/effect_context.h"
#include "audio_core/mix_context.h"
#include "audio_core/splitter_context.h"
namespace AudioCore {
MixContext::MixContext() = default;
MixContext::~MixContext() = default;
void MixContext::Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count,
std::size_t effect_count) {
info_count = mix_count;
infos.resize(info_count);
auto& final_mix = GetInfo(AudioCommon::FINAL_MIX);
final_mix.GetInParams().mix_id = AudioCommon::FINAL_MIX;
sorted_info.reserve(infos.size());
for (auto& info : infos) {
sorted_info.push_back(&info);
}
for (auto& info : infos) {
info.SetEffectCount(effect_count);
}
// Only initialize our edge matrix and node states if splitters are supported
if (behavior_info.IsSplitterSupported()) {
node_states.Initialize(mix_count);
edge_matrix.Initialize(mix_count);
}
}
void MixContext::UpdateDistancesFromFinalMix() {
// Set all distances to be invalid
for (std::size_t i = 0; i < info_count; i++) {
GetInfo(i).GetInParams().final_mix_distance = AudioCommon::NO_FINAL_MIX;
}
for (std::size_t i = 0; i < info_count; i++) {
auto& info = GetInfo(i);
auto& in_params = info.GetInParams();
// Populate our sorted info
sorted_info[i] = &info;
if (!in_params.in_use) {
continue;
}
auto mix_id = in_params.mix_id;
// Needs to be referenced out of scope
s32 distance_to_final_mix{AudioCommon::FINAL_MIX};
for (; distance_to_final_mix < static_cast<s32>(info_count); distance_to_final_mix++) {
if (mix_id == AudioCommon::FINAL_MIX) {
// If we're at the final mix, we're done
break;
} else if (mix_id == AudioCommon::NO_MIX) {
// If we have no more mix ids, we're done
distance_to_final_mix = AudioCommon::NO_FINAL_MIX;
break;
} else {
const auto& dest_mix = GetInfo(mix_id);
const auto dest_mix_distance = dest_mix.GetInParams().final_mix_distance;
if (dest_mix_distance == AudioCommon::NO_FINAL_MIX) {
// If our current mix isn't pointing to a final mix, follow through
mix_id = dest_mix.GetInParams().dest_mix_id;
} else {
// Our current mix + 1 = final distance
distance_to_final_mix = dest_mix_distance + 1;
break;
}
}
}
// If we're out of range for our distance, mark it as no final mix
if (distance_to_final_mix >= static_cast<s32>(info_count)) {
distance_to_final_mix = AudioCommon::NO_FINAL_MIX;
}
in_params.final_mix_distance = distance_to_final_mix;
}
}
void MixContext::CalcMixBufferOffset() {
s32 offset{};
for (std::size_t i = 0; i < info_count; i++) {
auto& info = GetSortedInfo(i);
auto& in_params = info.GetInParams();
if (in_params.in_use) {
// Only update if in use
in_params.buffer_offset = offset;
offset += in_params.buffer_count;
}
}
}
void MixContext::SortInfo() {
// Get the distance to the final mix
UpdateDistancesFromFinalMix();
// Sort based on the distance to the final mix
std::sort(sorted_info.begin(), sorted_info.end(),
[](const ServerMixInfo* lhs, const ServerMixInfo* rhs) {
return lhs->GetInParams().final_mix_distance >
rhs->GetInParams().final_mix_distance;
});
// Calculate the mix buffer offset
CalcMixBufferOffset();
}
bool MixContext::TsortInfo(SplitterContext& splitter_context) {
// If we're not using mixes, just calculate the mix buffer offset
if (!splitter_context.UsingSplitter()) {
CalcMixBufferOffset();
return true;
}
// Sort our node states
if (!node_states.Tsort(edge_matrix)) {
return false;
}
// Get our sorted list
const auto sorted_list = node_states.GetIndexList();
std::size_t info_id{};
for (auto itr = sorted_list.rbegin(); itr != sorted_list.rend(); ++itr) {
// Set our sorted info
sorted_info[info_id++] = &GetInfo(*itr);
}
// Calculate the mix buffer offset
CalcMixBufferOffset();
return true;
}
std::size_t MixContext::GetCount() const {
return info_count;
}
ServerMixInfo& MixContext::GetInfo(std::size_t i) {
ASSERT(i < info_count);
return infos.at(i);
}
const ServerMixInfo& MixContext::GetInfo(std::size_t i) const {
ASSERT(i < info_count);
return infos.at(i);
}
ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) {
ASSERT(i < info_count);
return *sorted_info.at(i);
}
const ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) const {
ASSERT(i < info_count);
return *sorted_info.at(i);
}
ServerMixInfo& MixContext::GetFinalMixInfo() {
return infos.at(AudioCommon::FINAL_MIX);
}
const ServerMixInfo& MixContext::GetFinalMixInfo() const {
return infos.at(AudioCommon::FINAL_MIX);
}
EdgeMatrix& MixContext::GetEdgeMatrix() {
return edge_matrix;
}
const EdgeMatrix& MixContext::GetEdgeMatrix() const {
return edge_matrix;
}
ServerMixInfo::ServerMixInfo() {
Cleanup();
}
ServerMixInfo::~ServerMixInfo() = default;
const ServerMixInfo::InParams& ServerMixInfo::GetInParams() const {
return in_params;
}
ServerMixInfo::InParams& ServerMixInfo::GetInParams() {
return in_params;
}
bool ServerMixInfo::Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
BehaviorInfo& behavior_info, SplitterContext& splitter_context,
EffectContext& effect_context) {
in_params.volume = mix_in.volume;
in_params.sample_rate = mix_in.sample_rate;
in_params.buffer_count = mix_in.buffer_count;
in_params.in_use = mix_in.in_use;
in_params.mix_id = mix_in.mix_id;
in_params.node_id = mix_in.node_id;
for (std::size_t i = 0; i < mix_in.mix_volume.size(); i++) {
std::copy(mix_in.mix_volume[i].begin(), mix_in.mix_volume[i].end(),
in_params.mix_volume[i].begin());
}
bool require_sort = false;
if (behavior_info.IsSplitterSupported()) {
require_sort = UpdateConnection(edge_matrix, mix_in, splitter_context);
} else {
in_params.dest_mix_id = mix_in.dest_mix_id;
in_params.splitter_id = AudioCommon::NO_SPLITTER;
}
ResetEffectProcessingOrder();
const auto effect_count = effect_context.GetCount();
for (std::size_t i = 0; i < effect_count; i++) {
auto* effect_info = effect_context.GetInfo(i);
if (effect_info->GetMixID() == in_params.mix_id) {
effect_processing_order[effect_info->GetProcessingOrder()] = static_cast<s32>(i);
}
}
// TODO(ogniK): Update effect processing order
return require_sort;
}
bool ServerMixInfo::HasAnyConnection() const {
return in_params.splitter_id != AudioCommon::NO_SPLITTER ||
in_params.mix_id != AudioCommon::NO_MIX;
}
void ServerMixInfo::Cleanup() {
in_params.volume = 0.0f;
in_params.sample_rate = 0;
in_params.buffer_count = 0;
in_params.in_use = false;
in_params.mix_id = AudioCommon::NO_MIX;
in_params.node_id = 0;
in_params.buffer_offset = 0;
in_params.dest_mix_id = AudioCommon::NO_MIX;
in_params.splitter_id = AudioCommon::NO_SPLITTER;
std::memset(in_params.mix_volume.data(), 0, sizeof(float) * in_params.mix_volume.size());
}
void ServerMixInfo::SetEffectCount(std::size_t count) {
effect_processing_order.resize(count);
ResetEffectProcessingOrder();
}
void ServerMixInfo::ResetEffectProcessingOrder() {
for (auto& order : effect_processing_order) {
order = AudioCommon::NO_EFFECT_ORDER;
}
}
s32 ServerMixInfo::GetEffectOrder(std::size_t i) const {
return effect_processing_order.at(i);
}
bool ServerMixInfo::UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
SplitterContext& splitter_context) {
// Mixes are identical
if (in_params.dest_mix_id == mix_in.dest_mix_id &&
in_params.splitter_id == mix_in.splitter_id &&
((in_params.splitter_id == AudioCommon::NO_SPLITTER) ||
!splitter_context.GetInfo(in_params.splitter_id).HasNewConnection())) {
return false;
}
// Remove current edges for mix id
edge_matrix.RemoveEdges(in_params.mix_id);
if (mix_in.dest_mix_id != AudioCommon::NO_MIX) {
// If we have a valid destination mix id, set our edge matrix
edge_matrix.Connect(in_params.mix_id, mix_in.dest_mix_id);
} else if (mix_in.splitter_id != AudioCommon::NO_SPLITTER) {
// Recurse our splitter linked and set our edges
auto& splitter_info = splitter_context.GetInfo(mix_in.splitter_id);
const auto length = splitter_info.GetLength();
for (s32 i = 0; i < length; i++) {
const auto* splitter_destination =
splitter_context.GetDestinationData(mix_in.splitter_id, i);
if (splitter_destination == nullptr) {
continue;
}
if (splitter_destination->ValidMixId()) {
edge_matrix.Connect(in_params.mix_id, splitter_destination->GetMixId());
}
}
}
in_params.dest_mix_id = mix_in.dest_mix_id;
in_params.splitter_id = mix_in.splitter_id;
return true;
}
} // namespace AudioCore

View File

@@ -0,0 +1,114 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <vector>
#include "audio_core/common.h"
#include "audio_core/splitter_context.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
namespace AudioCore {
class BehaviorInfo;
class EffectContext;
class MixInfo {
public:
struct DirtyHeader {
u32_le magic{};
u32_le mixer_count{};
INSERT_PADDING_BYTES(0x18);
};
static_assert(sizeof(DirtyHeader) == 0x20, "MixInfo::DirtyHeader is an invalid size");
struct InParams {
float_le volume{};
s32_le sample_rate{};
s32_le buffer_count{};
bool in_use{};
INSERT_PADDING_BYTES(3);
s32_le mix_id{};
s32_le effect_count{};
u32_le node_id{};
INSERT_PADDING_WORDS(2);
std::array<std::array<float_le, AudioCommon::MAX_MIX_BUFFERS>, AudioCommon::MAX_MIX_BUFFERS>
mix_volume{};
s32_le dest_mix_id{};
s32_le splitter_id{};
INSERT_PADDING_WORDS(1);
};
static_assert(sizeof(MixInfo::InParams) == 0x930, "MixInfo::InParams is an invalid size");
};
class ServerMixInfo {
public:
struct InParams {
float volume{};
s32 sample_rate{};
s32 buffer_count{};
bool in_use{};
s32 mix_id{};
u32 node_id{};
std::array<std::array<float_le, AudioCommon::MAX_MIX_BUFFERS>, AudioCommon::MAX_MIX_BUFFERS>
mix_volume{};
s32 dest_mix_id{};
s32 splitter_id{};
s32 buffer_offset{};
s32 final_mix_distance{};
};
ServerMixInfo();
~ServerMixInfo();
const ServerMixInfo::InParams& GetInParams() const;
ServerMixInfo::InParams& GetInParams();
bool Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
BehaviorInfo& behavior_info, SplitterContext& splitter_context,
EffectContext& effect_context);
bool HasAnyConnection() const;
void Cleanup();
void SetEffectCount(std::size_t count);
void ResetEffectProcessingOrder();
s32 GetEffectOrder(std::size_t i) const;
private:
std::vector<s32> effect_processing_order;
InParams in_params{};
bool UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
SplitterContext& splitter_context);
};
class MixContext {
public:
MixContext();
~MixContext();
void Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count,
std::size_t effect_count);
void SortInfo();
bool TsortInfo(SplitterContext& splitter_context);
std::size_t GetCount() const;
ServerMixInfo& GetInfo(std::size_t i);
const ServerMixInfo& GetInfo(std::size_t i) const;
ServerMixInfo& GetSortedInfo(std::size_t i);
const ServerMixInfo& GetSortedInfo(std::size_t i) const;
ServerMixInfo& GetFinalMixInfo();
const ServerMixInfo& GetFinalMixInfo() const;
EdgeMatrix& GetEdgeMatrix();
const EdgeMatrix& GetEdgeMatrix() const;
private:
void CalcMixBufferOffset();
void UpdateDistancesFromFinalMix();
NodeStates node_states{};
EdgeMatrix edge_matrix{};
std::size_t info_count{};
std::vector<ServerMixInfo> infos{};
std::vector<ServerMixInfo*> sorted_info{};
};
} // namespace AudioCore

View File

@@ -0,0 +1,31 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "audio_core/sink_context.h"
namespace AudioCore {
SinkContext::SinkContext(std::size_t sink_count) : sink_count(sink_count) {}
SinkContext::~SinkContext() = default;
std::size_t SinkContext::GetCount() const {
return sink_count;
}
void SinkContext::UpdateMainSink(SinkInfo::InParams& in) {
in_use = in.in_use;
use_count = in.device.input_count;
std::memcpy(buffers.data(), in.device.input.data(), AudioCommon::MAX_CHANNEL_COUNT);
}
bool SinkContext::InUse() const {
return in_use;
}
std::vector<u8> SinkContext::OutputBuffers() const {
std::vector<u8> buffer_ret(use_count);
std::memcpy(buffer_ret.data(), buffers.data(), use_count);
return buffer_ret;
}
} // namespace AudioCore

View File

@@ -0,0 +1,89 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "audio_core/common.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
namespace AudioCore {
enum class SinkTypes : u8 {
Invalid = 0,
Device = 1,
Circular = 2,
};
enum class SinkSampleFormat : u32_le {
None = 0,
Pcm8 = 1,
Pcm16 = 2,
Pcm24 = 3,
Pcm32 = 4,
PcmFloat = 5,
Adpcm = 6,
};
class SinkInfo {
public:
struct CircularBufferIn {
u64_le address;
u32_le size;
u32_le input_count;
u32_le sample_count;
u32_le previous_position;
SinkSampleFormat sample_format;
std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> input;
bool in_use;
INSERT_UNION_PADDING_BYTES(5);
};
static_assert(sizeof(SinkInfo::CircularBufferIn) == 0x28,
"SinkInfo::CircularBufferIn is in invalid size");
struct DeviceIn {
std::array<u8, 255> device_name;
INSERT_UNION_PADDING_BYTES(1);
s32_le input_count;
std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> input;
INSERT_UNION_PADDING_BYTES(1);
bool down_matrix_enabled;
std::array<float_le, 4> down_matrix_coef;
};
static_assert(sizeof(SinkInfo::DeviceIn) == 0x11c, "SinkInfo::DeviceIn is an invalid size");
struct InParams {
SinkTypes type{};
bool in_use{};
INSERT_PADDING_BYTES(2);
u32_le node_id{};
INSERT_PADDING_WORDS(6);
union {
// std::array<u8, 0x120> raw{};
SinkInfo::DeviceIn device;
SinkInfo::CircularBufferIn circular_buffer;
};
};
static_assert(sizeof(SinkInfo::InParams) == 0x140, "SinkInfo::InParams are an invalid size!");
};
class SinkContext {
public:
explicit SinkContext(std::size_t sink_count);
~SinkContext();
std::size_t GetCount() const;
void UpdateMainSink(SinkInfo::InParams& in);
bool InUse() const;
std::vector<u8> OutputBuffers() const;
private:
bool in_use{false};
s32 use_count{};
std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> buffers{};
std::size_t sink_count{};
};
} // namespace AudioCore

View File

@@ -0,0 +1,617 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "audio_core/behavior_info.h"
#include "audio_core/splitter_context.h"
#include "common/alignment.h"
#include "common/assert.h"
#include "common/logging/log.h"
namespace AudioCore {
ServerSplitterDestinationData::ServerSplitterDestinationData(s32 id) : id(id) {}
ServerSplitterDestinationData::~ServerSplitterDestinationData() = default;
void ServerSplitterDestinationData::Update(SplitterInfo::InDestinationParams& header) {
// Log error as these are not actually failure states
if (header.magic != SplitterMagic::DataHeader) {
LOG_ERROR(Audio, "Splitter destination header is invalid!");
return;
}
// Incorrect splitter id
if (header.splitter_id != id) {
LOG_ERROR(Audio, "Splitter destination ids do not match!");
return;
}
mix_id = header.mix_id;
// Copy our mix volumes
std::copy(header.mix_volumes.begin(), header.mix_volumes.end(), current_mix_volumes.begin());
if (!in_use && header.in_use) {
// Update mix volumes
std::copy(current_mix_volumes.begin(), current_mix_volumes.end(), last_mix_volumes.begin());
needs_update = false;
}
in_use = header.in_use;
}
ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() {
return next;
}
const ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() const {
return next;
}
void ServerSplitterDestinationData::SetNextDestination(ServerSplitterDestinationData* dest) {
next = dest;
}
bool ServerSplitterDestinationData::ValidMixId() const {
return GetMixId() != AudioCommon::NO_MIX;
}
s32 ServerSplitterDestinationData::GetMixId() const {
return mix_id;
}
bool ServerSplitterDestinationData::IsConfigured() const {
return in_use && ValidMixId();
}
float ServerSplitterDestinationData::GetMixVolume(std::size_t i) const {
ASSERT(i < AudioCommon::MAX_MIX_BUFFERS);
return current_mix_volumes.at(i);
}
const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
ServerSplitterDestinationData::CurrentMixVolumes() const {
return current_mix_volumes;
}
const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
ServerSplitterDestinationData::LastMixVolumes() const {
return last_mix_volumes;
}
void ServerSplitterDestinationData::MarkDirty() {
needs_update = true;
}
void ServerSplitterDestinationData::UpdateInternalState() {
if (in_use && needs_update) {
std::copy(current_mix_volumes.begin(), current_mix_volumes.end(), last_mix_volumes.begin());
}
needs_update = false;
}
ServerSplitterInfo::ServerSplitterInfo(s32 id) : id(id) {}
ServerSplitterInfo::~ServerSplitterInfo() = default;
void ServerSplitterInfo::InitializeInfos() {
send_length = 0;
head = nullptr;
new_connection = true;
}
void ServerSplitterInfo::ClearNewConnectionFlag() {
new_connection = false;
}
std::size_t ServerSplitterInfo::Update(SplitterInfo::InInfoPrams& header) {
if (header.send_id != id) {
return 0;
}
sample_rate = header.sample_rate;
new_connection = true;
// We need to update the size here due to the splitter bug being present and providing an
// incorrect size. We're suppose to also update the header here but we just ignore and continue
return (sizeof(s32_le) * (header.length - 1)) + (sizeof(s32_le) * 3);
}
ServerSplitterDestinationData* ServerSplitterInfo::GetHead() {
return head;
}
const ServerSplitterDestinationData* ServerSplitterInfo::GetHead() const {
return head;
}
ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) {
auto current_head = head;
for (std::size_t i = 0; i < depth; i++) {
if (current_head == nullptr) {
return nullptr;
}
current_head = current_head->GetNextDestination();
}
return current_head;
}
const ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) const {
auto current_head = head;
for (std::size_t i = 0; i < depth; i++) {
if (current_head == nullptr) {
return nullptr;
}
current_head = current_head->GetNextDestination();
}
return current_head;
}
bool ServerSplitterInfo::HasNewConnection() const {
return new_connection;
}
s32 ServerSplitterInfo::GetLength() const {
return send_length;
}
void ServerSplitterInfo::SetHead(ServerSplitterDestinationData* new_head) {
head = new_head;
}
void ServerSplitterInfo::SetHeadDepth(s32 length) {
send_length = length;
}
SplitterContext::SplitterContext() = default;
SplitterContext::~SplitterContext() = default;
void SplitterContext::Initialize(BehaviorInfo& behavior_info, std::size_t _info_count,
std::size_t _data_count) {
if (!behavior_info.IsSplitterSupported() || _data_count == 0 || _info_count == 0) {
Setup(0, 0, false);
return;
}
// Only initialize if we're using splitters
Setup(_info_count, _data_count, behavior_info.IsSplitterBugFixed());
}
bool SplitterContext::Update(const std::vector<u8>& input, std::size_t& input_offset,
std::size_t& bytes_read) {
const auto UpdateOffsets = [&](std::size_t read) {
input_offset += read;
bytes_read += read;
};
if (info_count == 0 || data_count == 0) {
bytes_read = 0;
return true;
}
if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
sizeof(SplitterInfo::InHeader))) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
SplitterInfo::InHeader header{};
std::memcpy(&header, input.data() + input_offset, sizeof(SplitterInfo::InHeader));
UpdateOffsets(sizeof(SplitterInfo::InHeader));
if (header.magic != SplitterMagic::SplitterHeader) {
LOG_ERROR(Audio, "Invalid header magic! Expecting {:X} but got {:X}",
SplitterMagic::SplitterHeader, header.magic);
return false;
}
// Clear all connections
for (auto& info : infos) {
info.ClearNewConnectionFlag();
}
UpdateInfo(input, input_offset, bytes_read, header.info_count);
UpdateData(input, input_offset, bytes_read, header.data_count);
const auto aligned_bytes_read = Common::AlignUp(bytes_read, 16);
input_offset += aligned_bytes_read - bytes_read;
bytes_read = aligned_bytes_read;
return true;
}
bool SplitterContext::UsingSplitter() const {
return info_count > 0 && data_count > 0;
}
ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) {
ASSERT(i < info_count);
return infos.at(i);
}
const ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) const {
ASSERT(i < info_count);
return infos.at(i);
}
ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) {
ASSERT(i < data_count);
return datas.at(i);
}
const ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) const {
ASSERT(i < data_count);
return datas.at(i);
}
ServerSplitterDestinationData* SplitterContext::GetDestinationData(std::size_t info,
std::size_t data) {
ASSERT(info < info_count);
auto& cur_info = GetInfo(info);
return cur_info.GetData(data);
}
const ServerSplitterDestinationData* SplitterContext::GetDestinationData(std::size_t info,
std::size_t data) const {
ASSERT(info < info_count);
auto& cur_info = GetInfo(info);
return cur_info.GetData(data);
}
void SplitterContext::UpdateInternalState() {
if (data_count == 0) {
return;
}
for (auto& data : datas) {
data.UpdateInternalState();
}
}
std::size_t SplitterContext::GetInfoCount() const {
return info_count;
}
std::size_t SplitterContext::GetDataCount() const {
return data_count;
}
void SplitterContext::Setup(std::size_t _info_count, std::size_t _data_count,
bool is_splitter_bug_fixed) {
info_count = _info_count;
data_count = _data_count;
for (std::size_t i = 0; i < info_count; i++) {
auto& splitter = infos.emplace_back(static_cast<s32>(i));
splitter.InitializeInfos();
}
for (std::size_t i = 0; i < data_count; i++) {
datas.emplace_back(static_cast<s32>(i));
}
bug_fixed = is_splitter_bug_fixed;
}
bool SplitterContext::UpdateInfo(const std::vector<u8>& input, std::size_t& input_offset,
std::size_t& bytes_read, s32 in_splitter_count) {
const auto UpdateOffsets = [&](std::size_t read) {
input_offset += read;
bytes_read += read;
};
for (s32 i = 0; i < in_splitter_count; i++) {
if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
sizeof(SplitterInfo::InInfoPrams))) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
SplitterInfo::InInfoPrams header{};
std::memcpy(&header, input.data() + input_offset, sizeof(SplitterInfo::InInfoPrams));
// Logged as warning as these don't actually cause a bailout for some reason
if (header.magic != SplitterMagic::InfoHeader) {
LOG_ERROR(Audio, "Bad splitter data header");
break;
}
if (header.send_id < 0 || static_cast<std::size_t>(header.send_id) > info_count) {
LOG_ERROR(Audio, "Bad splitter data id");
break;
}
UpdateOffsets(sizeof(SplitterInfo::InInfoPrams));
auto& info = GetInfo(header.send_id);
if (!RecomposeDestination(info, header, input, input_offset)) {
LOG_ERROR(Audio, "Failed to recompose destination for splitter!");
return false;
}
const std::size_t read = info.Update(header);
bytes_read += read;
input_offset += read;
}
return true;
}
bool SplitterContext::UpdateData(const std::vector<u8>& input, std::size_t& input_offset,
std::size_t& bytes_read, s32 in_data_count) {
const auto UpdateOffsets = [&](std::size_t read) {
input_offset += read;
bytes_read += read;
};
for (s32 i = 0; i < in_data_count; i++) {
if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
sizeof(SplitterInfo::InDestinationParams))) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
SplitterInfo::InDestinationParams header{};
std::memcpy(&header, input.data() + input_offset,
sizeof(SplitterInfo::InDestinationParams));
UpdateOffsets(sizeof(SplitterInfo::InDestinationParams));
// Logged as warning as these don't actually cause a bailout for some reason
if (header.magic != SplitterMagic::DataHeader) {
LOG_ERROR(Audio, "Bad splitter data header");
break;
}
if (header.splitter_id < 0 || static_cast<std::size_t>(header.splitter_id) > data_count) {
LOG_ERROR(Audio, "Bad splitter data id");
break;
}
GetData(header.splitter_id).Update(header);
}
return true;
}
bool SplitterContext::RecomposeDestination(ServerSplitterInfo& info,
SplitterInfo::InInfoPrams& header,
const std::vector<u8>& input,
const std::size_t& input_offset) {
// Clear our current destinations
auto* current_head = info.GetHead();
while (current_head != nullptr) {
auto next_head = current_head->GetNextDestination();
current_head->SetNextDestination(nullptr);
current_head = next_head;
}
info.SetHead(nullptr);
s32 size = header.length;
// If the splitter bug is present, calculate fixed size
if (!bug_fixed) {
if (info_count > 0) {
const auto factor = data_count / info_count;
size = std::min(header.length, static_cast<s32>(factor));
} else {
size = 0;
}
}
if (size < 1) {
LOG_ERROR(Audio, "Invalid splitter info size! size={:X}", size);
return true;
}
auto* start_head = &GetData(header.resource_id_base);
current_head = start_head;
std::vector<s32_le> resource_ids(size - 1);
if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
resource_ids.size() * sizeof(s32_le))) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
std::memcpy(resource_ids.data(), input.data() + input_offset,
resource_ids.size() * sizeof(s32_le));
for (auto resource_id : resource_ids) {
auto* head = &GetData(resource_id);
current_head->SetNextDestination(head);
current_head = head;
}
info.SetHead(start_head);
info.SetHeadDepth(size);
return true;
}
NodeStates::NodeStates() = default;
NodeStates::~NodeStates() = default;
void NodeStates::Initialize(std::size_t node_count_) {
// Setup our work parameters
node_count = node_count_;
was_node_found.resize(node_count);
was_node_completed.resize(node_count);
index_list.resize(node_count);
index_stack.Reset(node_count * node_count);
}
bool NodeStates::Tsort(EdgeMatrix& edge_matrix) {
return DepthFirstSearch(edge_matrix);
}
std::size_t NodeStates::GetIndexPos() const {
return index_pos;
}
const std::vector<s32>& NodeStates::GetIndexList() const {
return index_list;
}
void NodeStates::PushTsortResult(s32 index) {
ASSERT(index < static_cast<s32>(node_count));
index_list[index_pos++] = index;
}
bool NodeStates::DepthFirstSearch(EdgeMatrix& edge_matrix) {
ResetState();
for (std::size_t i = 0; i < node_count; i++) {
const auto node_id = static_cast<s32>(i);
// If we don't have a state, send to our index stack for work
if (GetState(i) == NodeStates::State::NoState) {
index_stack.push(node_id);
}
// While we have work to do in our stack
while (index_stack.Count() > 0) {
// Get the current node
const auto current_stack_index = index_stack.top();
// Check if we've seen the node yet
const auto index_state = GetState(current_stack_index);
if (index_state == NodeStates::State::NoState) {
// Mark the node as seen
UpdateState(NodeStates::State::InFound, current_stack_index);
} else if (index_state == NodeStates::State::InFound) {
// We've seen this node before, mark it as completed
UpdateState(NodeStates::State::InCompleted, current_stack_index);
// Update our index list
PushTsortResult(current_stack_index);
// Pop the stack
index_stack.pop();
continue;
} else if (index_state == NodeStates::State::InCompleted) {
// If our node is already sorted, clear it
index_stack.pop();
continue;
}
const auto node_count = edge_matrix.GetNodeCount();
for (s32 j = 0; j < static_cast<s32>(node_count); j++) {
// Check if our node is connected to our edge matrix
if (!edge_matrix.Connected(current_stack_index, j)) {
continue;
}
// Check if our node exists
const auto node_state = GetState(j);
if (node_state == NodeStates::State::NoState) {
// Add more work
index_stack.push(j);
} else if (node_state == NodeStates::State::InFound) {
UNREACHABLE_MSG("Node start marked as found");
ResetState();
return false;
}
}
}
}
return true;
}
void NodeStates::ResetState() {
// Reset to the start of our index stack
index_pos = 0;
for (std::size_t i = 0; i < node_count; i++) {
// Mark all nodes as not found
was_node_found[i] = false;
// Mark all nodes as uncompleted
was_node_completed[i] = false;
// Mark all indexes as invalid
index_list[i] = -1;
}
}
void NodeStates::UpdateState(NodeStates::State state, std::size_t i) {
switch (state) {
case NodeStates::State::NoState:
was_node_found[i] = false;
was_node_completed[i] = false;
break;
case NodeStates::State::InFound:
was_node_found[i] = true;
was_node_completed[i] = false;
break;
case NodeStates::State::InCompleted:
was_node_found[i] = false;
was_node_completed[i] = true;
break;
}
}
NodeStates::State NodeStates::GetState(std::size_t i) {
ASSERT(i < node_count);
if (was_node_found[i]) {
// If our node exists in our found list
return NodeStates::State::InFound;
} else if (was_node_completed[i]) {
// If node is in the completed list
return NodeStates::State::InCompleted;
} else {
// If in neither
return NodeStates::State::NoState;
}
}
NodeStates::Stack::Stack() = default;
NodeStates::Stack::~Stack() = default;
void NodeStates::Stack::Reset(std::size_t size) {
// Mark our stack as empty
stack.resize(size);
stack_size = size;
stack_pos = 0;
std::fill(stack.begin(), stack.end(), 0);
}
void NodeStates::Stack::push(s32 val) {
ASSERT(stack_pos < stack_size);
stack[stack_pos++] = val;
}
std::size_t NodeStates::Stack::Count() const {
return stack_pos;
}
s32 NodeStates::Stack::top() const {
ASSERT(stack_pos > 0);
return stack[stack_pos - 1];
}
s32 NodeStates::Stack::pop() {
ASSERT(stack_pos > 0);
stack_pos--;
return stack[stack_pos];
}
EdgeMatrix::EdgeMatrix() = default;
EdgeMatrix::~EdgeMatrix() = default;
void EdgeMatrix::Initialize(std::size_t _node_count) {
node_count = _node_count;
edge_matrix.resize(node_count * node_count);
}
bool EdgeMatrix::Connected(s32 a, s32 b) {
return GetState(a, b);
}
void EdgeMatrix::Connect(s32 a, s32 b) {
SetState(a, b, true);
}
void EdgeMatrix::Disconnect(s32 a, s32 b) {
SetState(a, b, false);
}
void EdgeMatrix::RemoveEdges(s32 edge) {
for (std::size_t i = 0; i < node_count; i++) {
SetState(edge, static_cast<s32>(i), false);
}
}
std::size_t EdgeMatrix::GetNodeCount() const {
return node_count;
}
void EdgeMatrix::SetState(s32 a, s32 b, bool state) {
ASSERT(InRange(a, b));
edge_matrix.at(a * node_count + b) = state;
}
bool EdgeMatrix::GetState(s32 a, s32 b) {
ASSERT(InRange(a, b));
return edge_matrix.at(a * node_count + b);
}
bool EdgeMatrix::InRange(s32 a, s32 b) const {
const std::size_t pos = a * node_count + b;
return pos < (node_count * node_count);
}
} // namespace AudioCore

View File

@@ -0,0 +1,221 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <stack>
#include <vector>
#include "audio_core/common.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
namespace AudioCore {
class BehaviorInfo;
class EdgeMatrix {
public:
EdgeMatrix();
~EdgeMatrix();
void Initialize(std::size_t _node_count);
bool Connected(s32 a, s32 b);
void Connect(s32 a, s32 b);
void Disconnect(s32 a, s32 b);
void RemoveEdges(s32 edge);
std::size_t GetNodeCount() const;
private:
void SetState(s32 a, s32 b, bool state);
bool GetState(s32 a, s32 b);
bool InRange(s32 a, s32 b) const;
std::vector<bool> edge_matrix{};
std::size_t node_count{};
};
class NodeStates {
public:
enum class State {
NoState = 0,
InFound = 1,
InCompleted = 2,
};
// Looks to be a fixed size stack. Placed within the NodeStates class based on symbols
class Stack {
public:
Stack();
~Stack();
void Reset(std::size_t size);
void push(s32 val);
std::size_t Count() const;
s32 top() const;
s32 pop();
private:
std::vector<s32> stack{};
std::size_t stack_size{};
std::size_t stack_pos{};
};
NodeStates();
~NodeStates();
void Initialize(std::size_t _node_count);
bool Tsort(EdgeMatrix& edge_matrix);
std::size_t GetIndexPos() const;
const std::vector<s32>& GetIndexList() const;
private:
void PushTsortResult(s32 index);
bool DepthFirstSearch(EdgeMatrix& edge_matrix);
void ResetState();
void UpdateState(NodeStates::State state, std::size_t i);
NodeStates::State GetState(std::size_t i);
std::size_t node_count{};
std::vector<bool> was_node_found{};
std::vector<bool> was_node_completed{};
std::size_t index_pos{};
std::vector<s32> index_list{};
NodeStates::Stack index_stack{};
};
enum class SplitterMagic : u32_le {
SplitterHeader = Common::MakeMagic('S', 'N', 'D', 'H'),
DataHeader = Common::MakeMagic('S', 'N', 'D', 'D'),
InfoHeader = Common::MakeMagic('S', 'N', 'D', 'I'),
};
class SplitterInfo {
public:
struct InHeader {
SplitterMagic magic{};
s32_le info_count{};
s32_le data_count{};
INSERT_PADDING_WORDS(5);
};
static_assert(sizeof(SplitterInfo::InHeader) == 0x20,
"SplitterInfo::InHeader is an invalid size");
struct InInfoPrams {
SplitterMagic magic{};
s32_le send_id{};
s32_le sample_rate{};
s32_le length{};
s32_le resource_id_base{};
};
static_assert(sizeof(SplitterInfo::InInfoPrams) == 0x14,
"SplitterInfo::InInfoPrams is an invalid size");
struct InDestinationParams {
SplitterMagic magic{};
s32_le splitter_id{};
std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> mix_volumes{};
s32_le mix_id{};
bool in_use{};
INSERT_PADDING_BYTES(3);
};
static_assert(sizeof(SplitterInfo::InDestinationParams) == 0x70,
"SplitterInfo::InDestinationParams is an invalid size");
};
class ServerSplitterDestinationData {
public:
explicit ServerSplitterDestinationData(s32 id);
~ServerSplitterDestinationData();
void Update(SplitterInfo::InDestinationParams& header);
ServerSplitterDestinationData* GetNextDestination();
const ServerSplitterDestinationData* GetNextDestination() const;
void SetNextDestination(ServerSplitterDestinationData* dest);
bool ValidMixId() const;
s32 GetMixId() const;
bool IsConfigured() const;
float GetMixVolume(std::size_t i) const;
const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& CurrentMixVolumes() const;
const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& LastMixVolumes() const;
void MarkDirty();
void UpdateInternalState();
private:
bool needs_update{};
bool in_use{};
s32 id{};
s32 mix_id{};
std::array<float, AudioCommon::MAX_MIX_BUFFERS> current_mix_volumes{};
std::array<float, AudioCommon::MAX_MIX_BUFFERS> last_mix_volumes{};
ServerSplitterDestinationData* next = nullptr;
};
class ServerSplitterInfo {
public:
explicit ServerSplitterInfo(s32 id);
~ServerSplitterInfo();
void InitializeInfos();
void ClearNewConnectionFlag();
std::size_t Update(SplitterInfo::InInfoPrams& header);
ServerSplitterDestinationData* GetHead();
const ServerSplitterDestinationData* GetHead() const;
ServerSplitterDestinationData* GetData(std::size_t depth);
const ServerSplitterDestinationData* GetData(std::size_t depth) const;
bool HasNewConnection() const;
s32 GetLength() const;
void SetHead(ServerSplitterDestinationData* new_head);
void SetHeadDepth(s32 length);
private:
s32 sample_rate{};
s32 id{};
s32 send_length{};
ServerSplitterDestinationData* head = nullptr;
bool new_connection{};
};
class SplitterContext {
public:
SplitterContext();
~SplitterContext();
void Initialize(BehaviorInfo& behavior_info, std::size_t splitter_count,
std::size_t data_count);
bool Update(const std::vector<u8>& input, std::size_t& input_offset, std::size_t& bytes_read);
bool UsingSplitter() const;
ServerSplitterInfo& GetInfo(std::size_t i);
const ServerSplitterInfo& GetInfo(std::size_t i) const;
ServerSplitterDestinationData& GetData(std::size_t i);
const ServerSplitterDestinationData& GetData(std::size_t i) const;
ServerSplitterDestinationData* GetDestinationData(std::size_t info, std::size_t data);
const ServerSplitterDestinationData* GetDestinationData(std::size_t info,
std::size_t data) const;
void UpdateInternalState();
std::size_t GetInfoCount() const;
std::size_t GetDataCount() const;
private:
void Setup(std::size_t info_count, std::size_t data_count, bool is_splitter_bug_fixed);
bool UpdateInfo(const std::vector<u8>& input, std::size_t& input_offset,
std::size_t& bytes_read, s32 in_splitter_count);
bool UpdateData(const std::vector<u8>& input, std::size_t& input_offset,
std::size_t& bytes_read, s32 in_data_count);
bool RecomposeDestination(ServerSplitterInfo& info, SplitterInfo::InInfoPrams& header,
const std::vector<u8>& input, const std::size_t& input_offset);
std::vector<ServerSplitterInfo> infos{};
std::vector<ServerSplitterDestinationData> datas{};
std::size_t info_count{};
std::size_t data_count{};
bool bug_fixed{};
};
} // namespace AudioCore

View File

@@ -12,7 +12,6 @@
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/core_timing.h"
#include "core/core_timing_util.h"
#include "core/settings.h"
namespace AudioCore {
@@ -104,11 +103,7 @@ void Stream::PlayNextBuffer(std::chrono::nanoseconds ns_late) {
sink_stream.EnqueueSamples(GetNumChannels(), active_buffer->GetSamples());
const auto time_stretch_delta = Settings::values.enable_audio_stretching.GetValue()
? std::chrono::nanoseconds::zero()
: ns_late;
const auto future_time = GetBufferReleaseNS(*active_buffer) - time_stretch_delta;
core_timing.ScheduleEvent(future_time, release_event, {});
core_timing.ScheduleEvent(GetBufferReleaseNS(*active_buffer) - ns_late, release_event, {});
}
void Stream::ReleaseActiveBuffer(std::chrono::nanoseconds ns_late) {

View File

@@ -0,0 +1,529 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "audio_core/behavior_info.h"
#include "audio_core/voice_context.h"
#include "core/memory.h"
namespace AudioCore {
ServerVoiceChannelResource::ServerVoiceChannelResource(s32 id) : id(id) {}
ServerVoiceChannelResource::~ServerVoiceChannelResource() = default;
bool ServerVoiceChannelResource::InUse() const {
return in_use;
}
float ServerVoiceChannelResource::GetCurrentMixVolumeAt(std::size_t i) const {
ASSERT(i < AudioCommon::MAX_MIX_BUFFERS);
return mix_volume.at(i);
}
float ServerVoiceChannelResource::GetLastMixVolumeAt(std::size_t i) const {
ASSERT(i < AudioCommon::MAX_MIX_BUFFERS);
return last_mix_volume.at(i);
}
void ServerVoiceChannelResource::Update(VoiceChannelResource::InParams& in_params) {
in_use = in_params.in_use;
// Update our mix volumes only if it's in use
if (in_params.in_use) {
mix_volume = in_params.mix_volume;
}
}
void ServerVoiceChannelResource::UpdateLastMixVolumes() {
last_mix_volume = mix_volume;
}
const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
ServerVoiceChannelResource::GetCurrentMixVolume() const {
return mix_volume;
}
const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
ServerVoiceChannelResource::GetLastMixVolume() const {
return last_mix_volume;
}
ServerVoiceInfo::ServerVoiceInfo() {
Initialize();
}
ServerVoiceInfo::~ServerVoiceInfo() = default;
void ServerVoiceInfo::Initialize() {
in_params.in_use = false;
in_params.node_id = 0;
in_params.id = 0;
in_params.current_playstate = ServerPlayState::Stop;
in_params.priority = 255;
in_params.sample_rate = 0;
in_params.sample_format = SampleFormat::Invalid;
in_params.channel_count = 0;
in_params.pitch = 0.0f;
in_params.volume = 0.0f;
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.mix_id = AudioCommon::NO_MIX;
in_params.splitter_info_id = AudioCommon::NO_SPLITTER;
in_params.additional_params_address = 0;
in_params.additional_params_size = 0;
in_params.is_new = false;
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.wave_buffer_flush_request_count = 0;
in_params.was_biquad_filter_enabled.fill(false);
for (auto& wave_buffer : in_params.wave_buffer) {
wave_buffer.start_sample_offset = 0;
wave_buffer.end_sample_offset = 0;
wave_buffer.is_looping = false;
wave_buffer.end_of_stream = false;
wave_buffer.buffer_address = 0;
wave_buffer.buffer_size = 0;
wave_buffer.context_address = 0;
wave_buffer.context_size = 0;
wave_buffer.sent_to_dsp = true;
}
stored_samples.clear();
}
void ServerVoiceInfo::UpdateParameters(const VoiceInfo::InParams& voice_in,
BehaviorInfo& behavior_info) {
in_params.in_use = voice_in.is_in_use;
in_params.id = voice_in.id;
in_params.node_id = voice_in.node_id;
in_params.last_playstate = in_params.current_playstate;
switch (voice_in.play_state) {
case PlayState::Paused:
in_params.current_playstate = ServerPlayState::Paused;
break;
case PlayState::Stopped:
if (in_params.current_playstate != ServerPlayState::Stop) {
in_params.current_playstate = ServerPlayState::RequestStop;
}
break;
case PlayState::Started:
in_params.current_playstate = ServerPlayState::Play;
break;
default:
UNREACHABLE_MSG("Unknown playstate {}", voice_in.play_state);
break;
}
in_params.priority = voice_in.priority;
in_params.sorting_order = voice_in.sorting_order;
in_params.sample_rate = voice_in.sample_rate;
in_params.sample_format = voice_in.sample_format;
in_params.channel_count = voice_in.channel_count;
in_params.pitch = voice_in.pitch;
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;
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;
in_params.wave_buffer_flush_request_count =
static_cast<u8>(in_request_count + voice_request_count);
}
in_params.mix_id = voice_in.mix_id;
if (behavior_info.IsSplitterSupported()) {
in_params.splitter_info_id = voice_in.splitter_info_id;
} else {
in_params.splitter_info_id = AudioCommon::NO_SPLITTER;
}
std::memcpy(in_params.voice_channel_resource_id.data(),
voice_in.voice_channel_resource_ids.data(),
sizeof(s32) * in_params.voice_channel_resource_id.size());
if (behavior_info.IsVoicePlayedSampleCountResetAtLoopPointSupported()) {
in_params.behavior_flags.is_played_samples_reset_at_loop_point =
voice_in.behavior_flags.is_played_samples_reset_at_loop_point;
} else {
in_params.behavior_flags.is_played_samples_reset_at_loop_point.Assign(0);
}
if (behavior_info.IsVoicePitchAndSrcSkippedSupported()) {
in_params.behavior_flags.is_pitch_and_src_skipped =
voice_in.behavior_flags.is_pitch_and_src_skipped;
} else {
in_params.behavior_flags.is_pitch_and_src_skipped.Assign(0);
}
if (voice_in.is_voice_drop_flag_clear_requested) {
in_params.voice_drop_flag = false;
}
if (in_params.additional_params_address != voice_in.additional_params_address ||
in_params.additional_params_size != voice_in.additional_params_size) {
in_params.additional_params_address = voice_in.additional_params_address;
in_params.additional_params_size = voice_in.additional_params_size;
// TODO(ogniK): Reattach buffer, do we actually need to? Maybe just signal to the DSP that
// our context is new
}
}
void ServerVoiceInfo::UpdateWaveBuffers(
const VoiceInfo::InParams& voice_in,
std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states,
BehaviorInfo& behavior_info) {
if (voice_in.is_new) {
// Initialize our wave buffers
for (auto& wave_buffer : in_params.wave_buffer) {
wave_buffer.start_sample_offset = 0;
wave_buffer.end_sample_offset = 0;
wave_buffer.is_looping = false;
wave_buffer.end_of_stream = false;
wave_buffer.buffer_address = 0;
wave_buffer.buffer_size = 0;
wave_buffer.context_address = 0;
wave_buffer.context_size = 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;
}
}
}
// Update our wave buffers
for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) {
// Assume that we have at least 1 channel voice state
const auto have_valid_wave_buffer = voice_states[0]->is_wave_buffer_valid[i];
UpdateWaveBuffer(in_params.wave_buffer[i], voice_in.wave_buffer[i], in_params.sample_format,
have_valid_wave_buffer, behavior_info);
}
}
void ServerVoiceInfo::UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer,
const WaveBuffer& in_wave_buffer, SampleFormat sample_format,
bool is_buffer_valid, BehaviorInfo& behavior_info) {
if (!is_buffer_valid && out_wavebuffer.sent_to_dsp) {
out_wavebuffer.buffer_address = 0;
out_wavebuffer.buffer_size = 0;
}
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))) {
// TODO(ogniK): Write error info
return;
}
}
// TODO(ogniK): ADPCM Size error
out_wavebuffer.sent_to_dsp = false;
out_wavebuffer.start_sample_offset = in_wave_buffer.start_sample_offset;
out_wavebuffer.end_sample_offset = in_wave_buffer.end_sample_offset;
out_wavebuffer.is_looping = in_wave_buffer.is_looping;
out_wavebuffer.end_of_stream = in_wave_buffer.end_of_stream;
out_wavebuffer.buffer_address = in_wave_buffer.buffer_address;
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;
in_params.buffer_mapped =
in_wave_buffer.buffer_address != 0 && in_wave_buffer.buffer_size != 0;
// TODO(ogniK): Pool mapper attachment
// TODO(ogniK): IsAdpcmLoopContextBugFixed
}
}
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) {
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 ServerVoiceInfo::InParams& ServerVoiceInfo::GetInParams() const {
return in_params;
}
ServerVoiceInfo::InParams& ServerVoiceInfo::GetInParams() {
return in_params;
}
const ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() const {
return out_params;
}
ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() {
return out_params;
}
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;
}
bool ServerVoiceInfo::UpdateForCommandGeneration(VoiceContext& voice_context) {
std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT> dsp_voice_states{};
if (in_params.is_new) {
ResetResources(voice_context);
in_params.last_volume = in_params.volume;
in_params.is_new = false;
}
const s32 channel_count = in_params.channel_count;
for (s32 i = 0; i < channel_count; i++) {
const auto channel_resource = in_params.voice_channel_resource_id[i];
dsp_voice_states[i] =
&voice_context.GetDspSharedState(static_cast<std::size_t>(channel_resource));
}
return UpdateParametersForCommandGeneration(dsp_voice_states);
}
void ServerVoiceInfo::ResetResources(VoiceContext& voice_context) {
const s32 channel_count = in_params.channel_count;
for (s32 i = 0; i < channel_count; i++) {
const auto channel_resource = in_params.voice_channel_resource_id[i];
auto& dsp_state =
voice_context.GetDspSharedState(static_cast<std::size_t>(channel_resource));
dsp_state = {};
voice_context.GetChannelResource(static_cast<std::size_t>(channel_resource))
.UpdateLastMixVolumes();
}
}
bool ServerVoiceInfo::UpdateParametersForCommandGeneration(
std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states) {
const s32 channel_count = in_params.channel_count;
if (in_params.wave_buffer_flush_request_count > 0) {
FlushWaveBuffers(in_params.wave_buffer_flush_request_count, dsp_voice_states,
channel_count);
in_params.wave_buffer_flush_request_count = 0;
}
switch (in_params.current_playstate) {
case ServerPlayState::Play: {
for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) {
if (!in_params.wave_buffer[i].sent_to_dsp) {
for (s32 channel = 0; channel < channel_count; channel++) {
dsp_voice_states[channel]->is_wave_buffer_valid[i] = true;
}
in_params.wave_buffer[i].sent_to_dsp = true;
}
}
in_params.should_depop = false;
return HasValidWaveBuffer(dsp_voice_states[0]);
}
case ServerPlayState::Paused:
case ServerPlayState::Stop: {
in_params.should_depop = in_params.last_playstate == ServerPlayState::Play;
return in_params.should_depop;
}
case ServerPlayState::RequestStop: {
for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) {
in_params.wave_buffer[i].sent_to_dsp = true;
for (s32 channel = 0; channel < channel_count; channel++) {
auto* dsp_state = dsp_voice_states[channel];
if (dsp_state->is_wave_buffer_valid[i]) {
dsp_state->wave_buffer_index =
(dsp_state->wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
dsp_state->wave_buffer_consumed++;
}
dsp_state->is_wave_buffer_valid[i] = false;
}
}
for (s32 channel = 0; channel < channel_count; channel++) {
auto* dsp_state = dsp_voice_states[channel];
dsp_state->offset = 0;
dsp_state->played_sample_count = 0;
dsp_state->fraction = 0;
dsp_state->sample_history.fill(0);
dsp_state->context = {};
}
in_params.current_playstate = ServerPlayState::Stop;
in_params.should_depop = in_params.last_playstate == ServerPlayState::Play;
return in_params.should_depop;
}
default:
UNREACHABLE_MSG("Invalid playstate {}", in_params.current_playstate);
}
return false;
}
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;
for (u8 i = 0; i < flush_count; i++) {
in_params.wave_buffer[wave_head].sent_to_dsp = true;
for (s32 channel = 0; channel < channel_count; channel++) {
auto* dsp_state = dsp_voice_states[channel];
dsp_state->wave_buffer_consumed++;
dsp_state->is_wave_buffer_valid[wave_head] = false;
dsp_state->wave_buffer_index =
(dsp_state->wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
}
wave_head = (wave_head + 1) % AudioCommon::MAX_WAVE_BUFFERS;
}
}
bool ServerVoiceInfo::HasValidWaveBuffer(const VoiceState* state) const {
const auto& valid_wb = state->is_wave_buffer_valid;
return std::find(valid_wb.begin(), valid_wb.end(), true) != valid_wb.end();
}
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));
sorted_voice_info.push_back(&voice_info.emplace_back());
voice_states.emplace_back();
dsp_voice_states.emplace_back();
}
}
VoiceContext::~VoiceContext() {
sorted_voice_info.clear();
}
std::size_t VoiceContext::GetVoiceCount() const {
return voice_count;
}
ServerVoiceChannelResource& VoiceContext::GetChannelResource(std::size_t i) {
ASSERT(i < voice_count);
return voice_channel_resources.at(i);
}
const ServerVoiceChannelResource& VoiceContext::GetChannelResource(std::size_t i) const {
ASSERT(i < voice_count);
return voice_channel_resources.at(i);
}
VoiceState& VoiceContext::GetState(std::size_t i) {
ASSERT(i < voice_count);
return voice_states.at(i);
}
const VoiceState& VoiceContext::GetState(std::size_t i) const {
ASSERT(i < voice_count);
return voice_states.at(i);
}
VoiceState& VoiceContext::GetDspSharedState(std::size_t i) {
ASSERT(i < voice_count);
return dsp_voice_states.at(i);
}
const VoiceState& VoiceContext::GetDspSharedState(std::size_t i) const {
ASSERT(i < voice_count);
return dsp_voice_states.at(i);
}
ServerVoiceInfo& VoiceContext::GetInfo(std::size_t i) {
ASSERT(i < voice_count);
return voice_info.at(i);
}
const ServerVoiceInfo& VoiceContext::GetInfo(std::size_t i) const {
ASSERT(i < voice_count);
return voice_info.at(i);
}
ServerVoiceInfo& VoiceContext::GetSortedInfo(std::size_t i) {
ASSERT(i < voice_count);
return *sorted_voice_info.at(i);
}
const ServerVoiceInfo& VoiceContext::GetSortedInfo(std::size_t i) const {
ASSERT(i < voice_count);
return *sorted_voice_info.at(i);
}
s32 VoiceContext::DecodePcm16(s32* output_buffer, ServerWaveBuffer* wave_buffer, s32 channel,
s32 channel_count, s32 buffer_offset, s32 sample_count,
Core::Memory::Memory& memory) {
if (wave_buffer->buffer_address == 0) {
return 0;
}
if (wave_buffer->buffer_size == 0) {
return 0;
}
if (wave_buffer->end_sample_offset < wave_buffer->start_sample_offset) {
return 0;
}
const auto samples_remaining =
(wave_buffer->end_sample_offset - wave_buffer->start_sample_offset) - buffer_offset;
const auto start_offset = (wave_buffer->start_sample_offset + buffer_offset) * channel_count;
const auto buffer_pos = wave_buffer->buffer_address + start_offset;
s16* buffer_data = reinterpret_cast<s16*>(memory.GetPointer(buffer_pos));
const auto samples_processed = std::min(sample_count, samples_remaining);
// Fast path
if (channel_count == 1) {
for (std::ptrdiff_t i = 0; i < samples_processed; i++) {
output_buffer[i] = buffer_data[i];
}
} else {
for (std::ptrdiff_t i = 0; i < samples_processed; i++) {
output_buffer[i] = buffer_data[i * channel_count + channel];
}
}
return samples_processed;
}
void VoiceContext::SortInfo() {
for (std::size_t i = 0; i < voice_count; i++) {
sorted_voice_info[i] = &voice_info[i];
}
std::sort(sorted_voice_info.begin(), sorted_voice_info.end(),
[](const ServerVoiceInfo* lhs, const ServerVoiceInfo* rhs) {
const auto& lhs_in = lhs->GetInParams();
const auto& rhs_in = rhs->GetInParams();
// Sort by priority
if (lhs_in.priority != rhs_in.priority) {
return lhs_in.priority > rhs_in.priority;
} else {
// If the priorities match, sort by sorting order
return lhs_in.sorting_order > rhs_in.sorting_order;
}
});
}
void VoiceContext::UpdateStateByDspShared() {
voice_states = dsp_voice_states;
}
} // namespace AudioCore

View File

@@ -0,0 +1,296 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include "audio_core/algorithm/interpolate.h"
#include "audio_core/codec.h"
#include "audio_core/common.h"
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
namespace Core::Memory {
class Memory;
}
namespace AudioCore {
class BehaviorInfo;
class VoiceContext;
enum class SampleFormat : u8 {
Invalid = 0,
Pcm8 = 1,
Pcm16 = 2,
Pcm24 = 3,
Pcm32 = 4,
PcmFloat = 5,
Adpcm = 6,
};
enum class PlayState : u8 {
Started = 0,
Stopped = 1,
Paused = 2,
};
enum class ServerPlayState {
Play = 0,
Stop = 1,
RequestStop = 2,
Paused = 3,
};
struct BiquadFilterParameter {
bool enabled{};
INSERT_PADDING_BYTES(1);
std::array<s16, 3> numerator{};
std::array<s16, 2> denominator{};
};
static_assert(sizeof(BiquadFilterParameter) == 0xc, "BiquadFilterParameter is an invalid size");
struct WaveBuffer {
u64_le buffer_address{};
u64_le buffer_size{};
s32_le start_sample_offset{};
s32_le end_sample_offset{};
u8 is_looping{};
u8 end_of_stream{};
u8 sent_to_server{};
INSERT_PADDING_BYTES(5);
u64 context_address{};
u64 context_size{};
INSERT_PADDING_BYTES(8);
};
static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer is an invalid size");
struct ServerWaveBuffer {
VAddr buffer_address{};
std::size_t buffer_size{};
s32 start_sample_offset{};
s32 end_sample_offset{};
bool is_looping{};
bool end_of_stream{};
VAddr context_address{};
std::size_t context_size{};
bool sent_to_dsp{true};
};
struct BehaviorFlags {
BitField<0, 1, u16> is_played_samples_reset_at_loop_point;
BitField<1, 1, u16> is_pitch_and_src_skipped;
};
static_assert(sizeof(BehaviorFlags) == 0x4, "BehaviorFlags is an invalid size");
struct ADPCMContext {
u16 header{};
s16 yn1{};
s16 yn2{};
};
static_assert(sizeof(ADPCMContext) == 0x6, "ADPCMContext is an invalid size");
struct VoiceState {
s64 played_sample_count{};
s32 offset{};
s32 wave_buffer_index{};
std::array<bool, AudioCommon::MAX_WAVE_BUFFERS> is_wave_buffer_valid{};
s32 wave_buffer_consumed{};
std::array<s32, AudioCommon::MAX_SAMPLE_HISTORY> sample_history{};
s32 fraction{};
VAddr context_address{};
Codec::ADPCM_Coeff coeff{};
ADPCMContext context{};
std::array<s64, 2> biquad_filter_state{};
std::array<s32, AudioCommon::MAX_MIX_BUFFERS> previous_samples{};
u32 external_context_size{};
bool is_external_context_used{};
bool voice_dropped{};
};
class VoiceChannelResource {
public:
struct InParams {
s32_le id{};
std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> mix_volume{};
bool in_use{};
INSERT_PADDING_BYTES(11);
};
static_assert(sizeof(VoiceChannelResource::InParams) == 0x70, "InParams is an invalid size");
};
class ServerVoiceChannelResource {
public:
explicit ServerVoiceChannelResource(s32 id);
~ServerVoiceChannelResource();
bool InUse() const;
float GetCurrentMixVolumeAt(std::size_t i) const;
float GetLastMixVolumeAt(std::size_t i) const;
void Update(VoiceChannelResource::InParams& in_params);
void UpdateLastMixVolumes();
const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& GetCurrentMixVolume() const;
const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& GetLastMixVolume() const;
private:
s32 id{};
std::array<float, AudioCommon::MAX_MIX_BUFFERS> mix_volume{};
std::array<float, AudioCommon::MAX_MIX_BUFFERS> last_mix_volume{};
bool in_use{};
};
class VoiceInfo {
public:
struct InParams {
s32_le id{};
u32_le node_id{};
u8 is_new{};
u8 is_in_use{};
PlayState play_state{};
SampleFormat sample_format{};
s32_le sample_rate{};
s32_le priority{};
s32_le sorting_order{};
s32_le channel_count{};
float_le pitch{};
float_le volume{};
std::array<BiquadFilterParameter, 2> biquad_filter{};
s32_le wave_buffer_count{};
s16_le wave_buffer_head{};
INSERT_PADDING_BYTES(6);
u64_le additional_params_address{};
u64_le additional_params_size{};
s32_le mix_id{};
s32_le splitter_info_id{};
std::array<WaveBuffer, 4> wave_buffer{};
std::array<u32_le, 6> voice_channel_resource_ids{};
// TODO(ogniK): Remaining flags
u8 is_voice_drop_flag_clear_requested{};
u8 wave_buffer_flush_request_count{};
INSERT_PADDING_BYTES(2);
BehaviorFlags behavior_flags{};
INSERT_PADDING_BYTES(16);
};
static_assert(sizeof(VoiceInfo::InParams) == 0x170, "InParams is an invalid size");
struct OutParams {
u64_le played_sample_count{};
u32_le wave_buffer_consumed{};
u8 voice_dropped{};
INSERT_PADDING_BYTES(3);
};
static_assert(sizeof(VoiceInfo::OutParams) == 0x10, "OutParams is an invalid size");
};
class ServerVoiceInfo {
public:
struct InParams {
bool in_use{};
bool is_new{};
bool should_depop{};
SampleFormat sample_format{};
s32 sample_rate{};
s32 channel_count{};
s32 id{};
s32 node_id{};
s32 mix_id{};
ServerPlayState current_playstate{};
ServerPlayState last_playstate{};
s32 priority{};
s32 sorting_order{};
float pitch{};
float volume{};
float last_volume{};
std::array<BiquadFilterParameter, AudioCommon::MAX_BIQUAD_FILTERS> biquad_filter{};
s32 wave_buffer_count{};
s16 wave_bufffer_head{};
INSERT_PADDING_BYTES(2);
BehaviorFlags behavior_flags{};
VAddr additional_params_address{};
std::size_t additional_params_size{};
std::array<ServerWaveBuffer, AudioCommon::MAX_WAVE_BUFFERS> wave_buffer{};
std::array<s32, AudioCommon::MAX_CHANNEL_COUNT> voice_channel_resource_id{};
s32 splitter_info_id{};
u8 wave_buffer_flush_request_count{};
bool voice_drop_flag{};
bool buffer_mapped{};
std::array<bool, AudioCommon::MAX_BIQUAD_FILTERS> was_biquad_filter_enabled{};
};
struct OutParams {
s64 played_sample_count{};
s32 wave_buffer_consumed{};
};
ServerVoiceInfo();
~ServerVoiceInfo();
void Initialize();
void UpdateParameters(const VoiceInfo::InParams& voice_in, BehaviorInfo& behavior_info);
void UpdateWaveBuffers(const VoiceInfo::InParams& voice_in,
std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states,
BehaviorInfo& behavior_info);
void UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer, const WaveBuffer& in_wave_buffer,
SampleFormat sample_format, bool is_buffer_valid,
BehaviorInfo& behavior_info);
void WriteOutStatus(VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in,
std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states);
const InParams& GetInParams() const;
InParams& GetInParams();
const OutParams& GetOutParams() const;
OutParams& GetOutParams();
bool ShouldSkip() const;
bool UpdateForCommandGeneration(VoiceContext& voice_context);
void ResetResources(VoiceContext& voice_context);
bool UpdateParametersForCommandGeneration(
std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states);
void FlushWaveBuffers(u8 flush_count,
std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states,
s32 channel_count);
private:
std::vector<s16> stored_samples;
InParams in_params{};
OutParams out_params{};
bool HasValidWaveBuffer(const VoiceState* state) const;
};
class VoiceContext {
public:
VoiceContext(std::size_t voice_count);
~VoiceContext();
std::size_t GetVoiceCount() const;
ServerVoiceChannelResource& GetChannelResource(std::size_t i);
const ServerVoiceChannelResource& GetChannelResource(std::size_t i) const;
VoiceState& GetState(std::size_t i);
const VoiceState& GetState(std::size_t i) const;
VoiceState& GetDspSharedState(std::size_t i);
const VoiceState& GetDspSharedState(std::size_t i) const;
ServerVoiceInfo& GetInfo(std::size_t i);
const ServerVoiceInfo& GetInfo(std::size_t i) const;
ServerVoiceInfo& GetSortedInfo(std::size_t i);
const ServerVoiceInfo& GetSortedInfo(std::size_t i) const;
s32 DecodePcm16(s32* output_buffer, ServerWaveBuffer* wave_buffer, s32 channel,
s32 channel_count, s32 buffer_offset, s32 sample_count,
Core::Memory::Memory& memory);
void SortInfo();
void UpdateStateByDspShared();
private:
std::size_t voice_count{};
std::vector<ServerVoiceChannelResource> voice_channel_resources{};
std::vector<VoiceState> voice_states{};
std::vector<VoiceState> dsp_voice_states{};
std::vector<ServerVoiceInfo> voice_info{};
std::vector<ServerVoiceInfo*> sorted_voice_info{};
};
} // namespace AudioCore

View File

@@ -102,6 +102,7 @@ add_library(common STATIC
atomic_ops.h
detached_tasks.cpp
detached_tasks.h
bit_cast.h
bit_field.h
bit_util.h
cityhash.cpp
@@ -150,6 +151,8 @@ add_library(common STATIC
scope_exit.h
spin_lock.cpp
spin_lock.h
stream.cpp
stream.h
string_util.cpp
string_util.h
swap.h
@@ -188,8 +191,29 @@ if(ARCHITECTURE_x86_64)
)
endif()
if (MSVC)
target_compile_definitions(common PRIVATE
# The standard library doesn't provide any replacement for codecvt yet
# so we can disable this deprecation warning for the time being.
_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING
)
target_compile_options(common PRIVATE
/W4
/WX
)
else()
target_compile_options(common PRIVATE
-Werror
)
endif()
create_target_directory_groups(common)
find_package(Boost 1.71 COMPONENTS context headers REQUIRED)
target_link_libraries(common PUBLIC ${Boost_LIBRARIES} fmt::fmt microprofile)
target_link_libraries(common PRIVATE lz4::lz4 zstd::zstd xbyak)
target_link_libraries(common PRIVATE lz4::lz4 xbyak)
if (MSVC)
target_link_libraries(common PRIVATE zstd::zstd)
else()
target_link_libraries(common PRIVATE zstd)
endif()

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