Compare commits

...

117 Commits

Author SHA1 Message Date
Levi Behunin
77e327dd1a Clean-up
Numerize names, remove unneeded properties and spacer.
2021-09-20 20:02:54 -06:00
Levi Behunin
ad85689417 Tas configure ui nits
Text looked cramped on my pc (Ubuntu 21.04). Re-flowed text as well for nicer read.
2021-09-18 19:41:59 -06:00
bunnei
6e376c27a3 Merge pull request #6485 from MonsterDruide1/tas
input_common: TAS with script playback & recording
2021-09-18 16:30:06 -07:00
german77
75d8ec1e9f UI: Relocate tas menu and add brief description 2021-09-18 23:23:03 +02:00
german77
5401cf6eb5 input_common/tas: new update method 2021-09-18 23:22:57 +02:00
german77
33a1d790e8 input_common/tas: Document the main class 2021-09-18 23:22:48 +02:00
german77
e6c4bf52f0 input_common/tas: Add swap controller 2021-09-18 23:22:42 +02:00
german77
9bb6580d89 input_common/tas: overwrite file dialog 2021-09-18 23:22:42 +02:00
MonsterDruide1
f078b15565 input_common/tas: Fallback to simple update 2021-09-18 23:22:30 +02:00
german77
c01a872c8e config: Move TAS options to it's own menu 2021-09-18 23:22:30 +02:00
MonsterDruide1
4297d2fea2 core: Hacky TAS syncing & load pausing
To keep the TAS inputs synced to the game speed even through lag spikes and loading zones, deeper access is required.

First, the `TAS::UpdateThread` has to be executed exactly once per frame. This is done by connecting it to the service method the game calls to pass parameters to the GPU: `Service::VI::QueueBuffer`.

Second, the loading time of new subareas and/or kingdoms (SMO) can vary. To counteract that, the `CPU_BOOST_MODE` can be detected: In the `APM`-interface, the call to enabling/disabling the boost mode can be caught and forwarded to the TASing system, which can pause the script execution if neccessary and enabled in the settings.
2021-09-18 23:22:20 +02:00
MonsterDruide1
3a7b37238b main: TAS Playback state label
During script playback/recording, the user has to see what happens currently. For that, a new label has been added to the bottom-left corner, always displaying the current state of the TASing system.
2021-09-18 23:22:12 +02:00
MonsterDruide1
f25d6ebc45 settings: File selector & other settings
First of all, TASing requires a script to play back. The user can select the parent directory at `System -> Filesystem`, next to an option to pause TAS during loads: This requires a "hacky" setup deeper in the code and will be added in the last commit.

Also, Hotkeys are being introduced: CTRL+F5 for playback start/stop, CTRL+F6 for re-reading the script and CTRL+F7 for recording a new script.
2021-09-18 23:22:11 +02:00
MonsterDruide1
b42c3ce21d input_common/tas: Base playback & recording system
The base playback system supports up to 8 controllers (specified by `PLAYER_NUMBER` in `tas_input.h`), which all change their inputs simulataneously when `TAS::UpdateThread` is called.

The recording system uses the controller debugger to read the state of the first controller and forwards that data to the TASing system for recording. Currently, this process sadly is not frame-perfect and pixel-accurate.

Co-authored-by: Naii-the-Baf <sfabian200@gmail.com>
Co-authored-by: Narr-the-Reg <juangerman-13@hotmail.com>
2021-09-18 23:22:00 +02:00
bunnei
35f46fc079 Merge pull request #7020 from Moonlacer/remove_audio_stretching
Remove audio stretching
2021-09-18 11:18:24 -07:00
bunnei
6d7801deb7 Merge pull request #6950 from german77/multiplay
input_common: Add advanced setting for 8 player support
2021-09-17 17:25:51 -07:00
bunnei
d4ee94165f Merge pull request #7015 from german77/NotGoodForTerra
ngct: Stub Match
2021-09-17 10:58:55 -07:00
bunnei
ff54d9615f Merge pull request #7011 from ameerj/vk-validation-0x0
vulkan_debug_callback: Ignore InvalidCommandBuffer-VkDescriptorSet errors
2021-09-16 17:17:04 -07:00
Fernando S
a606b1448b Merge pull request #7027 from ameerj/sorry-amd
vulkan_device: Reorder Float16Int8 declaration
2021-09-16 07:05:58 +02:00
ameerj
e3c153efa4 vulkan_device: Reorder Float16Int8 declaration
This variable was going out of scope before its usage in the vulkan device creation, causing a crash on very specific drivers.
2021-09-16 00:54:24 -04:00
ameerj
5601e1cb00 Revert "Merge pull request #7006 from FernandoS27/a-motherfucking-driver"
This reverts commit 62e88d0e74, reversing
changes made to edf3da346f.
2021-09-16 00:51:22 -04:00
bunnei
f6d5444293 Merge pull request #7010 from Morph1984/fs-timestamp
vfs: Partially implement GetFileTimeStampRaw
2021-09-15 20:13:26 -07:00
Moonlacer
371feaa635 fix_clang_error 2021-09-15 20:20:45 -05:00
Moonlacer
09ab819040 fix_accidental_deletion 2021-09-15 19:59:10 -05:00
Moonlacer
44135b011f remove-audio-stretching-setting 2021-09-15 19:52:43 -05:00
Ameer J
e4d9814ec4 Merge pull request #7014 from Morph1984/log-pipeline-count
renderers: Log total pipeline count
2021-09-15 19:44:08 -04:00
Narr the Reg
b34b3efbb2 ngct: Stub Match
Needed for  Cruis'n Blast
2021-09-15 00:17:31 -05:00
Morph
2df2b3719a renderers: Log total pipeline count 2021-09-14 13:18:26 -04:00
Morph
8d63ebcb64 vfs: Partially implement GetFileTimeStampRaw
Gets rid of homebrew warnings using this func
2021-09-14 08:48:01 -04:00
bunnei
081ccc6441 Merge pull request #7009 from ameerj/main_process_cleanup
core: Destroy main_process during shutdown
2021-09-13 23:32:43 -07:00
ameerj
67f7a6c398 core: Destroy main_process during shutdown
The main_process was never being cleaned up, causing a noticeable memory leak after subsequent launches. This change cleans up the memory during Core Shutdown, mitigating the leak.
2021-09-13 23:44:52 -04:00
ameerj
db1c4b125f vulkan_debug_callback: Ignore InvalidCommandBuffer-VkDescriptorSet errors
This validation error is spammed on some titles, asserting that VkDescriptorSet 0x0[] was destroyed.

This is likely a validation layer bug when using VK_KHR_push_descriptor, which can avoid using traditional VkDescriptorSet. It should be safe to ignore for now.
2021-09-13 23:08:59 -04:00
Morph
d86a9b9a4b Merge pull request #6943 from FernandoS27/omae-wa-mou-shindeiru
Vulkan: Disable VK_EXT_SAMPLER_FILTER_MINMAX in GCN AMD
2021-09-13 17:33:15 -04:00
Fernando Sahmkow
7a712da2b3 Vulkan: Disable VK_EXT_SAMPLER_FILTER_MINMAX in GCN AMD since it's broken. 2021-09-13 23:29:57 +02:00
Morph
62e88d0e74 Merge pull request #7006 from FernandoS27/a-motherfucking-driver
Vulkan: Blacklist Int8Float16 Extension on AMD on driver 21.9.1
2021-09-13 17:25:56 -04:00
Mai M
edf3da346f Merge pull request #7005 from Morph1984/enum-bitwise-shift-ops
common_funcs: Add enum flag bitwise shift operator overloads
2021-09-13 17:21:22 -04:00
Morph
fde9b84b21 Merge pull request #6944 from FernandoS27/dear-drunk-me
Vulkan/Descriptors: Increase sets per pool on AMD propietary driver.
2021-09-13 17:20:07 -04:00
Fernando Sahmkow
e7c8a0bb23 Vulkan: Blacklist Int8Float16 Extension on AMD on driver 21.9.1 2021-09-13 23:17:37 +02:00
Fernando S
1bb28dfe2c Merge pull request #7001 from ameerj/wario-fix
vk_rasterizer: Fix dynamic StencilOp updating when two faces are enabled
2021-09-13 23:16:59 +02:00
Fernando Sahmkow
e7ca37b1e5 Vulkan/Descriptors: Increase sets per pool on AMFD propietary driver. 2021-09-13 23:09:18 +02:00
Morph
3512cae623 common_funcs: Add enum flag bitwise shift operator overloads
This adds bitwise shift operator overloads (<<, >>, <<=, >>=) in the macro DECLARE_ENUM_FLAG_OPERATORS(type)
2021-09-13 16:01:20 -04:00
Ameer J
d180fd7c36 Merge pull request #7000 from Morph1984/create-dir-comment
FS: Mark recursive CreateDirectory as inaccurate and temporary
2021-09-12 21:06:52 -04:00
Mai M
e4318d2207 Merge pull request #7002 from ameerj/vk-state-unused
vk_state_tracker: Remove unused function
2021-09-12 17:31:56 -04:00
ameerj
678f73069f vk_rasterizer: Fix dynamic StencilOp updating when two faces are enabled
This function was incorrectly using the stencil_two_side_enable register when dynamically updating the StencilOp.
2021-09-12 16:19:12 -04:00
ameerj
8e289ade15 vk_state_tracker: Remove unused function 2021-09-12 15:28:24 -04:00
Morph
727f607e00 FS: Mark recursive CreateDirectory as inaccurate and temporary 2021-09-12 14:06:01 -04:00
Morph
9248442bb2 Merge pull request #6948 from ameerj/amd-warp-fix
shaders: Fix warp instructions on 64-thread warp devices
2021-09-12 13:53:29 -04:00
Morph
4ab549e62a Merge pull request #6975 from ogniK5377/acc-async-ctx
account: EnsureTokenIdCacheAsync
2021-09-12 12:03:10 -04:00
Morph
f0f416e85c Merge pull request #6974 from ogniK5377/fs-recursive-createdir
FS: Recursively create directories for CreateDirectory
2021-09-12 12:02:39 -04:00
Morph
9907302465 Merge pull request #6997 from ameerj/stop-emulation-confirmation
main: Apply confirm exit setting in exit locked scenarios
2021-09-12 12:01:57 -04:00
Morph
3428232bca Merge pull request #6992 from german77/brains
hid/am: Stub SetTouchScreenConfiguration and implement GetNotificationStorageChannelEvent
2021-09-12 12:01:43 -04:00
Morph
74030eb427 Merge pull request #6987 from Morph1984/common-error
common: Move error handling functions out of common_funcs
2021-09-12 12:01:23 -04:00
Morph
47b6f522bd Merge pull request #6986 from Morph1984/version-update
api_version: Update and add AtmosphereTargetFirmware
2021-09-12 12:01:11 -04:00
ameerj
188cf1aed2 main: Apply confirm exit setting in exit locked scenarios
Some titles set an exit lock through HLE, which prompts an exit confirmation when stopping emulation if the system is locked.
This change allows bypassing this confirmation if the setting to confirm exits has been disabled by the user.
2021-09-12 00:31:32 -04:00
Morph
e67463df24 shader_environment: Add missing <algorithm> include 2021-09-11 17:19:16 -04:00
Morph
63b4c8f9f7 vk_descriptor_pool: Add missing <algorithm> include 2021-09-11 17:19:16 -04:00
Morph
76abf55f25 slot_vector: Add missing <algorithm> include 2021-09-11 17:19:15 -04:00
Morph
554c46d186 video_core/memory_manager: Add missing <algorithm> include 2021-09-11 17:19:15 -04:00
Morph
6f307f1521 kernel: Add missing <functional> include 2021-09-11 17:19:15 -04:00
Morph
4a6a73e887 file_sys/kernel_executable: Add missing <string> include 2021-09-11 17:19:14 -04:00
Morph
ae028ddf22 codec: Add missing <string_view> include 2021-09-11 17:19:14 -04:00
Morph
eb1e3f19bb common_funcs: Replace <algorithm> with <iterator> 2021-09-11 17:19:14 -04:00
Morph
290afc00d3 common: Move error handling to error.cpp/h
This allows us to avoid implicitly including <string> every time common_funcs.h is included.
2021-09-11 17:19:14 -04:00
Fernando S
be4e192903 Merge pull request #6846 from ameerj/nvdec-gpu-decode
nvdec: Add GPU video decoding for all capable drivers and platforms
2021-09-11 23:11:32 +02:00
Fernando S
82c867164b Merge pull request #6901 from ameerj/vk-clear-bits
vk_rasterizer: Only clear depth/stencil buffers when specified in attachment aspect mask
2021-09-11 22:36:22 +02:00
Fernando S
ec6490f5ad Merge pull request #6941 from ameerj/swapchain-srgb
vk_swapchain: Prefer linear swapchain format when presenting sRGB images
2021-09-11 22:36:03 +02:00
Fernando S
472aad69db Merge pull request #6953 from ameerj/anv-semaphore
renderer_vulkan: Wait on present semaphore at queue submit
2021-09-11 22:35:52 +02:00
Fernando S
55854c807d Merge pull request #6981 from ameerj/nvflinger-hb-format
nvflinger: Use external surface format for framebuffer creation
2021-09-11 22:35:25 +02:00
german77
9bddcdac69 am: Implement GetNotificationStorageChannelEvent 2021-09-10 12:24:50 -05:00
german77
a7bbd37f81 hid: Stub SetTouchScreenConfiguration 2021-09-10 12:24:28 -05:00
german77
5798537ce4 input_common: Enable steam controllers and 8 player support 2021-09-10 00:58:12 -05:00
Morph
c9710f6c78 api_version: Update and add AtmosphereTargetFirmware 2021-09-10 01:10:47 -04:00
bunnei
7e9163779d Merge pull request #6962 from vonchenplus/spirv_support_legacy_attribute
renderer_vulkan: Spirv support glsl  legacy attribute
2021-09-08 14:04:44 -07:00
Chloe
005b0e68db Addressed issues
Co-authored-by: Mai M. <mathew1800@gmail.com>
2021-09-09 03:00:08 +10:00
Chloe Marcec
543081e4a1 Mark is_complete as atomic 2021-09-09 00:10:52 +10:00
Chloe Marcec
89958e27aa Addressed issues 2021-09-09 00:09:04 +10:00
Fernando S
6b16f7807e Merge pull request #6980 from vonchenplus/fix_blend_equation_error
Fix blend equation enum error
2021-09-08 11:50:26 +02:00
Ameer J
eb1ba45c39 Merge pull request #6971 from bunnei/buffer-queue-kevent
core: hle: service: buffer_queue: Improve management of KEvent.
2021-09-08 00:34:36 -04:00
Feng Chen
b1e655f898 Detail adjustment 2021-09-08 10:30:00 +08:00
Feng Chen
bbc1800c1b Detail adjustment 2021-09-08 09:53:10 +08:00
Feng Chen
e5ca733722 Re-implement get unused location 2021-09-07 13:22:52 +08:00
Feng Chen
9cdf2383e9 Move attribute related definitions to spirv anonymous namespace 2021-09-07 12:34:35 +08:00
ameerj
9e2bf49677 nvflinger: Use external surface format for framebuffer creation
The format member the IGBPBuffer may not always specify the correct desired format. Using the external format member ensures a valid format is provided when creating the framebuffer.

Fixes homebrew using the wrong framebuffer format.
2021-09-06 23:14:31 -04:00
Ameer J
ab73787d8f Merge pull request #6977 from Moonlacer/master
Second part of Golden's PR #6976
2021-09-06 22:58:23 -04:00
Ameer J
743428e025 Merge pull request #6976 from goldenx86/patch-2
Rename all shader cache strings to pipeline cache
2021-09-06 22:58:03 -04:00
Feng Chen
0292374807 Fix blend equation enum error 2021-09-07 10:12:09 +08:00
Moonlacer
bdd153bc0d Second part of Golden's PR 2021-09-06 15:25:40 -05:00
Matías Locatti
296fa4e06e Rename all shader cache references to pipeline cache
After Hades, both OpenGL and Vulkan use a pipeline cache instead of single stages of the graphics pipeline. Renamed the Remove menu entries to match.
2021-09-06 15:53:04 -03:00
Chloe Marcec
9141816b10 address name shadowing with system 2021-09-06 22:13:51 +10:00
Chloe Marcec
4e2aa50cef account: EnsureTokenIdCacheAsync
Closes #2547, #6946
2021-09-06 21:16:21 +10:00
bunnei
51ccc29cdd Merge pull request #6965 from bunnei/cpu_manager_jthread
core: cpu_manager: Use jthread.
2021-09-06 03:49:14 -07:00
Chloe Marcec
0b891c9245 FS: Recursively create directories for CreateDirectory
Originally we only created the parent directory, this caused issues for creating directories which also contained subdirectories, eg `/Folder1/Folder2`

This allows the ultimate mod manager homebrew to at least boot
2021-09-06 19:35:55 +10:00
Feng Chen
1de9e4e121 Dynamic get unused location 2021-09-06 10:46:03 +08:00
Feng Chen
d994466a08 Implement intput and output fixed fnc textures 2021-09-06 10:36:45 +08:00
bunnei
e05bfd2f54 core: hle: service: buffer_queue: Improve management of KEvent. 2021-09-04 22:25:46 -07:00
bunnei
d9ce179ec2 Merge pull request #6968 from bunnei/nvflinger-event
core: hle: service: nvflinger/vi: Improve management of KEvent.
2021-09-04 22:25:20 -07:00
bunnei
fb3e9314b9 core: hle: service: nvflinger/vi: Improve management of KEvent. 2021-09-03 21:53:00 -07:00
bunnei
25a97e0139 core: cpu_manager: Use jthread. 2021-09-03 19:05:41 -07:00
Feng Chen
a7bbaa4897 Rename parameters 2021-09-03 23:52:20 +08:00
Feng Chen
cf26f375ff Fix create GraphicsPipelines crash 2021-09-03 22:55:53 +08:00
Feng Chen
1e2a89d306 Add input/output location 2021-09-02 23:34:51 +08:00
ameerj
d956fb3c7c emit_glsl_warp: Fix shuffle ops for 64-thread warp sizes 2021-08-31 16:11:25 -04:00
ameerj
5b45dfe971 emit_glsl_warp: Fix ballot related ops for 64-thread warp sizes 2021-08-31 16:11:25 -04:00
ameerj
a5d9dcf3d9 emit_spirv_warp: Fix shuffle ops for 64-thread warp sizes 2021-08-31 13:40:39 -04:00
ameerj
95213270ef emit_spirv_warp: Fix ballot related ops for 64-thread warp sizes 2021-08-31 13:40:12 -04:00
Feng Chen
73b11f390e Add colorfront and txtcoord support 2021-09-01 00:07:25 +08:00
ameerj
27f8f3333f vulkan_device: Enable VK_KHR_swapchain_mutable_format if available
Silences validation errors when creating sRGB image views of linear swapchain images
2021-08-29 02:03:36 -04:00
ameerj
3c65c8580f vk_swapchain: Prefer linear swapchain format when presenting sRGB images
Fixes broken sRGB when presenting from a secondary GPU.
2021-08-29 02:03:35 -04:00
ameerj
e0397f00d0 vk_rasterizer: Only clear depth and stencil buffers when set in attachment aspect mask
Silences validation errors for clearing the depth/stencil buffers of framebuffer attachments that were not specified to have depth/stencil usage.
2021-08-21 02:37:15 -04:00
ameerj
b384129c63 h264: Lower max_num_ref_frames
GPU decoding seems to be more picky when it comes to the maximum number of reference frames.
2021-08-16 14:40:53 -04:00
ameerj
cd016d3cb5 configure_graphics: Add GPU nvdec decoding as an option
Some system configurations may see visual regressions or lower performance using GPU decoding compared to CPU decoding. This setting provides the option for users to specify their decoding preference.

Co-Authored-By: yzct12345 <87620833+yzct12345@users.noreply.github.com>
2021-08-16 14:40:53 -04:00
ameerj
a832aa699f codec: Improve libav memory alloc and cleanup 2021-08-16 14:40:53 -04:00
ameerj
bc3efb79cc codec: Fallback to CPU decoding if no compatible GPU format is found 2021-08-16 14:40:53 -04:00
lat9nq
92bc51b66a cmake: Add VDPAU and NVDEC support to FFmpeg
Adds {h264_,vp9_}{nvdec,vdpau} hwaccels.
2021-08-16 14:40:52 -04:00
ameerj
356e10898f codec: Replace deprecated av_init_packet usage 2021-08-12 01:28:01 -04:00
ameerj
0be4e402e2 cmake: Always find LIBVA, update windows FFmpeg version
Allows the use of VAAPI gpu decoders on system installed ffmpeg as well.
2021-08-12 01:28:01 -04:00
ameerj
659039ca6d nvdec: Implement GPU accelerated decoding for all platforms
Supplements the VAAPI intel gpu decoder by implementing the D3D11VA decoder for Windows, and CUVID/VDPAU for Nvidia and AMD on drivers linux respectively.
2021-08-12 01:28:01 -04:00
108 changed files with 2579 additions and 417 deletions

View File

@@ -518,6 +518,10 @@ set(FFmpeg_COMPONENTS
avutil
swscale)
if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
Include(FindPkgConfig REQUIRED)
pkg_check_modules(LIBVA libva)
endif()
if (NOT YUZU_USE_BUNDLED_FFMPEG)
# Use system installed FFmpeg
find_package(FFmpeg QUIET COMPONENTS ${FFmpeg_COMPONENTS})
@@ -540,6 +544,9 @@ endif()
if (YUZU_USE_BUNDLED_FFMPEG)
if (NOT WIN32)
# TODO(lat9nq): Move this to externals/ffmpeg/CMakeLists.txt (and move externals/ffmpeg to
# externals/ffmpeg/ffmpeg)
# Build FFmpeg from externals
message(STATUS "Using FFmpeg from externals")
@@ -579,20 +586,23 @@ if (YUZU_USE_BUNDLED_FFMPEG)
CACHE PATH "Paths to FFmpeg libraries" FORCE)
endforeach()
set(FFmpeg_INCLUDE_DIR
"${FFmpeg_PREFIX};${FFmpeg_BUILD_DIR}"
CACHE PATH "Path to FFmpeg headers" FORCE)
Include(FindPkgConfig REQUIRED)
pkg_check_modules(LIBVA libva)
pkg_check_modules(CUDA cuda)
pkg_check_modules(FFNVCODEC ffnvcodec)
pkg_check_modules(VDPAU vdpau)
set(FFmpeg_HWACCEL_LIBRARIES)
set(FFmpeg_HWACCEL_FLAGS)
set(FFmpeg_HWACCEL_INCLUDE_DIRS)
set(FFmpeg_HWACCEL_LDFLAGS)
if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
Include(FindPkgConfig REQUIRED)
pkg_check_modules(LIBVA libva)
endif()
if(LIBVA_FOUND)
pkg_check_modules(LIBDRM libdrm REQUIRED)
find_package(X11 REQUIRED)
pkg_check_modules(LIBVA-DRM libva-drm REQUIRED)
pkg_check_modules(LIBVA-X11 libva-x11 REQUIRED)
set(FFmpeg_LIBVA_LIBRARIES
list(APPEND FFmpeg_HWACCEL_LIBRARIES
${LIBDRM_LIBRARIES}
${X11_LIBRARIES}
${LIBVA-DRM_LIBRARIES}
@@ -602,11 +612,56 @@ if (YUZU_USE_BUNDLED_FFMPEG)
--enable-hwaccel=h264_vaapi
--enable-hwaccel=vp9_vaapi
--enable-libdrm)
list(APPEND FFmpeg_HWACCEL_INCLUDE_DIRS
${LIBDRM_INCLUDE_DIRS}
${X11_INCLUDE_DIRS}
${LIBVA-DRM_INCLUDE_DIRS}
${LIBVA-X11_INCLUDE_DIRS}
${LIBVA_INCLUDE_DIRS}
)
message(STATUS "VA-API found")
else()
set(FFmpeg_HWACCEL_FLAGS --disable-vaapi)
endif()
if (FFNVCODEC_FOUND AND CUDA_FOUND)
list(APPEND FFmpeg_HWACCEL_FLAGS
--enable-cuvid
--enable-ffnvcodec
--enable-nvdec
--enable-hwaccel=h264_nvdec
--enable-hwaccel=vp9_nvdec
--extra-cflags=-I${CUDA_INCLUDE_DIRS}
)
list(APPEND FFmpeg_HWACCEL_LIBRARIES
${FFNVCODEC_LIBRARIES}
${CUDA_LIBRARIES}
)
list(APPEND FFmpeg_HWACCEL_INCLUDE_DIRS
${FFNVCODEC_INCLUDE_DIRS}
${CUDA_INCLUDE_DIRS}
)
list(APPEND FFmpeg_HWACCEL_LDFLAGS
${FFNVCODEC_LDFLAGS}
${CUDA_LDFLAGS}
)
message(STATUS "ffnvcodec libraries version ${FFNVCODEC_VERSION} found")
endif()
if (VDPAU_FOUND)
list(APPEND FFmpeg_HWACCEL_FLAGS
--enable-vdpau
--enable-hwaccel=h264_vdpau
--enable-hwaccel=vp9_vdpau
)
list(APPEND FFmpeg_HWACCEL_LIBRARIES ${VDPAU_LIBRARIES})
list(APPEND FFmpeg_HWACCEL_INCLUDE_DIRS ${VDPAU_INCLUDE_DIRS})
list(APPEND FFmpeg_HWACCEL_LDFLAGS ${VDPAU_LDFLAGS})
message(STATUS "vdpau libraries version ${VDPAU_VERSION} found")
else()
list(APPEND FFmpeg_HWACCEL_FLAGS --disable-vdpau)
endif()
# `configure` parameters builds only exactly what yuzu needs from FFmpeg
# `--disable-vdpau` is needed to avoid linking issues
add_custom_command(
@@ -624,7 +679,6 @@ if (YUZU_USE_BUNDLED_FFMPEG)
--disable-network
--disable-postproc
--disable-swresample
--disable-vdpau
--enable-decoder=h264
--enable-decoder=vp9
--cc="${CMAKE_C_COMPILER}"
@@ -653,15 +707,26 @@ if (YUZU_USE_BUNDLED_FFMPEG)
${FFmpeg_BUILD_DIR}
)
set(FFmpeg_INCLUDE_DIR
"${FFmpeg_PREFIX};${FFmpeg_BUILD_DIR};${FFmpeg_HWACCEL_INCLUDE_DIRS}"
CACHE PATH "Path to FFmpeg headers" FORCE)
set(FFmpeg_LDFLAGS
"${FFmpeg_HWACCEL_LDFLAGS}"
CACHE STRING "FFmpeg linker flags" FORCE)
# ALL makes this custom target build every time
# but it won't actually build if the DEPENDS parameter is up to date
add_custom_target(ffmpeg-configure ALL DEPENDS ${FFmpeg_MAKEFILE})
add_custom_target(ffmpeg-build ALL DEPENDS ${FFmpeg_BUILD_LIBRARIES} ffmpeg-configure)
link_libraries(${FFmpeg_LIBVA_LIBRARIES})
set(FFmpeg_LIBRARIES ${FFmpeg_LIBVA_LIBRARIES} ${FFmpeg_BUILD_LIBRARIES}
set(FFmpeg_LIBRARIES ${FFmpeg_BUILD_LIBRARIES} ${FFmpeg_HWACCEL_LIBRARIES}
CACHE PATH "Paths to FFmpeg libraries" FORCE)
unset(FFmpeg_BUILD_LIBRARIES)
unset(FFmpeg_LIBVA_LIBRARIES)
unset(FFmpeg_HWACCEL_FLAGS)
unset(FFmpeg_HWACCEL_INCLUDE_DIRS)
unset(FFmpeg_HWACCEL_LDFLAGS)
unset(FFmpeg_HWACCEL_LIBRARIES)
if (FFmpeg_FOUND)
message(STATUS "Found FFmpeg version ${FFmpeg_VERSION}")
@@ -670,12 +735,13 @@ if (YUZU_USE_BUNDLED_FFMPEG)
endif()
else() # WIN32
# Use yuzu FFmpeg binaries
set(FFmpeg_EXT_NAME "ffmpeg-4.3.1")
set(FFmpeg_EXT_NAME "ffmpeg-4.4")
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 directory" FORCE)
set(FFmpeg_LDFLAGS "" CACHE STRING "FFmpeg linker flags" FORCE)
set(FFmpeg_DLL_DIR "${FFmpeg_PATH}/bin" CACHE PATH "Path to FFmpeg dll's" FORCE)
set(FFmpeg_LIBRARIES
${FFmpeg_LIBRARY_DIR}/swscale.lib

View File

@@ -53,6 +53,8 @@ add_library(common STATIC
div_ceil.h
dynamic_library.cpp
dynamic_library.h
error.cpp
error.h
fiber.cpp
fiber.h
fs/file.cpp
@@ -88,7 +90,6 @@ add_library(common STATIC
microprofile.cpp
microprofile.h
microprofileui.h
misc.cpp
nvidia_flags.cpp
nvidia_flags.h
page_table.cpp

View File

@@ -4,9 +4,8 @@
#pragma once
#include <algorithm>
#include <array>
#include <string>
#include <iterator>
#if !defined(ARCHITECTURE_x86_64)
#include <cstdlib> // for exit
@@ -49,16 +48,6 @@ __declspec(dllimport) void __stdcall DebugBreak(void);
#endif // _MSC_VER ndef
// Generic function to get last error message.
// Call directly after the command or use the error num.
// This function might change the error code.
// Defined in misc.cpp.
[[nodiscard]] std::string GetLastErrorMsg();
// Like GetLastErrorMsg(), but passing an explicit error code.
// Defined in misc.cpp.
[[nodiscard]] std::string NativeErrorToString(int e);
#define DECLARE_ENUM_FLAG_OPERATORS(type) \
[[nodiscard]] constexpr type operator|(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
@@ -72,6 +61,14 @@ __declspec(dllimport) void __stdcall DebugBreak(void);
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) ^ static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator<<(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) << static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator>>(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) >> static_cast<T>(b)); \
} \
constexpr type& operator|=(type& a, type b) noexcept { \
a = a | b; \
return a; \
@@ -84,6 +81,14 @@ __declspec(dllimport) void __stdcall DebugBreak(void);
a = a ^ b; \
return a; \
} \
constexpr type& operator<<=(type& a, type b) noexcept { \
a = a << b; \
return a; \
} \
constexpr type& operator>>=(type& a, type b) noexcept { \
a = a >> b; \
return a; \
} \
[[nodiscard]] constexpr type operator~(type key) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(~static_cast<T>(key)); \

View File

@@ -10,7 +10,9 @@
#include <cstring>
#endif
#include "common/common_funcs.h"
#include "common/error.h"
namespace Common {
std::string NativeErrorToString(int e) {
#ifdef _WIN32
@@ -50,3 +52,5 @@ std::string GetLastErrorMsg() {
return NativeErrorToString(errno);
#endif
}
} // namespace Common

21
src/common/error.h Normal file
View File

@@ -0,0 +1,21 @@
// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <string>
namespace Common {
// Generic function to get last error message.
// Call directly after the command or use the error num.
// This function might change the error code.
// Defined in error.cpp.
[[nodiscard]] std::string GetLastErrorMsg();
// Like GetLastErrorMsg(), but passing an explicit error code.
// Defined in error.cpp.
[[nodiscard]] std::string NativeErrorToString(int e);
} // namespace Common

View File

@@ -21,6 +21,7 @@
#define SCREENSHOTS_DIR "screenshots"
#define SDMC_DIR "sdmc"
#define SHADER_DIR "shader"
#define TAS_DIR "tas"
// yuzu-specific files

View File

@@ -116,6 +116,7 @@ private:
GenerateYuzuPath(YuzuPath::ScreenshotsDir, yuzu_path / SCREENSHOTS_DIR);
GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR);
GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR);
GenerateYuzuPath(YuzuPath::TASDir, yuzu_path / TAS_DIR);
}
~PathManagerImpl() = default;

View File

@@ -23,6 +23,7 @@ enum class YuzuPath {
ScreenshotsDir, // Where yuzu screenshots are stored.
SDMCDir, // Where the emulated SDMC is stored.
ShaderDir, // Where shaders are stored.
TASDir, // Where TAS scripts are stored.
};
/**

View File

@@ -54,14 +54,13 @@ void LogSettings() {
log_setting("Renderer_GPUAccuracyLevel", values.gpu_accuracy.GetValue());
log_setting("Renderer_UseAsynchronousGpuEmulation",
values.use_asynchronous_gpu_emulation.GetValue());
log_setting("Renderer_UseNvdecEmulation", values.use_nvdec_emulation.GetValue());
log_setting("Renderer_NvdecEmulation", values.nvdec_emulation.GetValue());
log_setting("Renderer_AccelerateASTC", values.accelerate_astc.GetValue());
log_setting("Renderer_UseVsync", values.use_vsync.GetValue());
log_setting("Renderer_ShaderBackend", values.shader_backend.GetValue());
log_setting("Renderer_UseAsynchronousShaders", values.use_asynchronous_shaders.GetValue());
log_setting("Renderer_AnisotropicFilteringLevel", values.max_anisotropy.GetValue());
log_setting("Audio_OutputEngine", values.sink_id.GetValue());
log_setting("Audio_EnableAudioStretching", values.enable_audio_stretching.GetValue());
log_setting("Audio_OutputDevice", values.audio_device_id.GetValue());
log_setting("DataStorage_UseVirtualSd", values.use_virtual_sd.GetValue());
log_path("DataStorage_CacheDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir));
@@ -72,6 +71,9 @@ void LogSettings() {
log_setting("Debugging_ProgramArgs", values.program_args.GetValue());
log_setting("Services_BCATBackend", values.bcat_backend.GetValue());
log_setting("Services_BCATBoxcatLocal", values.bcat_boxcat_local.GetValue());
log_setting("Input_EnableMotion", values.motion_enabled.GetValue());
log_setting("Input_EnableVibration", values.vibration_enabled.GetValue());
log_setting("Input_EnableRawInput", values.enable_raw_input.GetValue());
}
bool IsConfiguringGlobal() {
@@ -112,7 +114,6 @@ void RestoreGlobalState(bool is_powered_on) {
}
// Audio
values.enable_audio_stretching.SetGlobal(true);
values.volume.SetGlobal(true);
// Core
@@ -136,7 +137,7 @@ void RestoreGlobalState(bool is_powered_on) {
values.use_disk_shader_cache.SetGlobal(true);
values.gpu_accuracy.SetGlobal(true);
values.use_asynchronous_gpu_emulation.SetGlobal(true);
values.use_nvdec_emulation.SetGlobal(true);
values.nvdec_emulation.SetGlobal(true);
values.accelerate_astc.SetGlobal(true);
values.use_vsync.SetGlobal(true);
values.shader_backend.SetGlobal(true);

View File

@@ -16,7 +16,6 @@
#include "common/common_types.h"
#include "common/settings_input.h"
#include "input_common/udp/client.h"
namespace Settings {
@@ -48,6 +47,12 @@ enum class FullscreenMode : u32 {
Exclusive = 1,
};
enum class NvdecEmulation : u32 {
Off = 0,
CPU = 1,
GPU = 2,
};
/** The BasicSetting class is a simple resource manager. It defines a label and default value
* alongside the actual value of the setting for simpler and less-error prone use with frontend
* configurations. Setting a default value and label is required, though subclasses may deviate from
@@ -409,7 +414,6 @@ struct Values {
BasicSetting<std::string> audio_device_id{"auto", "output_device"};
BasicSetting<std::string> sink_id{"auto", "output_engine"};
BasicSetting<bool> audio_muted{false, "audio_muted"};
Setting<bool> enable_audio_stretching{true, "enable_audio_stretching"};
RangedSetting<u8> volume{100, 0, 100, "volume"};
// Core
@@ -466,7 +470,7 @@ struct Values {
RangedSetting<GPUAccuracy> gpu_accuracy{GPUAccuracy::High, GPUAccuracy::Normal,
GPUAccuracy::Extreme, "gpu_accuracy"};
Setting<bool> use_asynchronous_gpu_emulation{true, "use_asynchronous_gpu_emulation"};
Setting<bool> use_nvdec_emulation{true, "use_nvdec_emulation"};
Setting<NvdecEmulation> nvdec_emulation{NvdecEmulation::GPU, "nvdec_emulation"};
Setting<bool> accelerate_astc{true, "accelerate_astc"};
Setting<bool> use_vsync{true, "use_vsync"};
BasicRangedSetting<u16> fps_cap{1000, 1, 1000, "fps_cap"};
@@ -498,14 +502,20 @@ struct Values {
Setting<bool> use_docked_mode{true, "use_docked_mode"};
BasicSetting<bool> enable_raw_input{false, "enable_raw_input"};
Setting<bool> vibration_enabled{true, "vibration_enabled"};
Setting<bool> enable_accurate_vibrations{false, "enable_accurate_vibrations"};
Setting<bool> motion_enabled{true, "motion_enabled"};
BasicSetting<std::string> motion_device{"engine:motion_emu,update_period:100,sensitivity:0.01",
"motion_device"};
BasicSetting<std::string> udp_input_servers{InputCommon::CemuhookUDP::DEFAULT_SRV,
"udp_input_servers"};
BasicSetting<std::string> udp_input_servers{"127.0.0.1:26760", "udp_input_servers"};
BasicSetting<bool> pause_tas_on_load{true, "pause_tas_on_load"};
BasicSetting<bool> tas_enable{false, "tas_enable"};
BasicSetting<bool> tas_loop{false, "tas_loop"};
BasicSetting<bool> tas_swap_controllers{true, "tas_swap_controllers"};
BasicSetting<bool> mouse_panning{false, "mouse_panning"};
BasicRangedSetting<u8> mouse_panning_sensitivity{10, 1, 100, "mouse_panning_sensitivity"};

View File

@@ -2,7 +2,9 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/common_funcs.h"
#include <string>
#include "common/error.h"
#include "common/logging/log.h"
#include "common/thread.h"
#ifdef __APPLE__
@@ -21,8 +23,6 @@
#include <unistd.h>
#endif
#include <string>
#ifdef __FreeBSD__
#define cpu_set_t cpuset_t
#endif

View File

@@ -263,6 +263,8 @@ add_library(core STATIC
hle/service/acc/acc_u0.h
hle/service/acc/acc_u1.cpp
hle/service/acc/acc_u1.h
hle/service/acc/async_context.cpp
hle/service/acc/async_context.h
hle/service/acc/errors.h
hle/service/acc/profile_manager.cpp
hle/service/acc/profile_manager.h

View File

@@ -83,6 +83,12 @@ FileSys::StorageId GetStorageIdForFrontendSlot(
}
}
void KProcessDeleter(Kernel::KProcess* process) {
process->Destroy();
}
using KProcessPtr = std::unique_ptr<Kernel::KProcess, decltype(&KProcessDeleter)>;
} // Anonymous namespace
FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
@@ -233,8 +239,8 @@ struct System::Impl {
}
telemetry_session->AddInitialInfo(*app_loader, fs_controller, *content_provider);
auto main_process = Kernel::KProcess::Create(system.Kernel());
ASSERT(Kernel::KProcess::Initialize(main_process, system, "main",
main_process = KProcessPtr{Kernel::KProcess::Create(system.Kernel()), KProcessDeleter};
ASSERT(Kernel::KProcess::Initialize(main_process.get(), system, "main",
Kernel::KProcess::ProcessType::Userland)
.IsSuccess());
main_process->Open();
@@ -247,7 +253,7 @@ struct System::Impl {
static_cast<u32>(load_result));
}
AddGlueRegistrationForProcess(*app_loader, *main_process);
kernel.MakeCurrentProcess(main_process);
kernel.MakeCurrentProcess(main_process.get());
kernel.InitializeCores();
// Initialize cheat engine
@@ -316,6 +322,8 @@ struct System::Impl {
kernel.Shutdown();
memory.Reset();
applet_manager.ClearAll();
// TODO: The main process should be freed based on KAutoObject ref counting.
main_process.reset();
LOG_DEBUG(Core, "Shutdown OK");
}
@@ -374,6 +382,7 @@ struct System::Impl {
std::unique_ptr<Tegra::GPU> gpu_core;
std::unique_ptr<Hardware::InterruptManager> interrupt_manager;
std::unique_ptr<Core::DeviceMemory> device_memory;
KProcessPtr main_process{nullptr, KProcessDeleter};
Core::Memory::Memory memory;
CpuManager cpu_manager;
std::atomic_bool is_powered_on{};

View File

@@ -21,34 +21,25 @@ namespace Core {
CpuManager::CpuManager(System& system_) : system{system_} {}
CpuManager::~CpuManager() = default;
void CpuManager::ThreadStart(CpuManager& cpu_manager, std::size_t core) {
cpu_manager.RunThread(core);
void CpuManager::ThreadStart(std::stop_token stop_token, CpuManager& cpu_manager,
std::size_t core) {
cpu_manager.RunThread(stop_token, core);
}
void CpuManager::Initialize() {
running_mode = true;
if (is_multicore) {
for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
core_data[core].host_thread =
std::make_unique<std::thread>(ThreadStart, std::ref(*this), core);
core_data[core].host_thread = std::jthread(ThreadStart, std::ref(*this), core);
}
} else {
core_data[0].host_thread = std::make_unique<std::thread>(ThreadStart, std::ref(*this), 0);
core_data[0].host_thread = std::jthread(ThreadStart, std::ref(*this), 0);
}
}
void CpuManager::Shutdown() {
running_mode = false;
Pause(false);
if (is_multicore) {
for (auto& data : core_data) {
data.host_thread->join();
data.host_thread.reset();
}
} else {
core_data[0].host_thread->join();
core_data[0].host_thread.reset();
}
}
std::function<void(void*)> CpuManager::GetGuestThreadStartFunc() {
@@ -317,7 +308,7 @@ void CpuManager::Pause(bool paused) {
}
}
void CpuManager::RunThread(std::size_t core) {
void CpuManager::RunThread(std::stop_token stop_token, std::size_t core) {
/// Initialization
system.RegisterCoreThread(core);
std::string name;
@@ -361,6 +352,10 @@ void CpuManager::RunThread(std::size_t core) {
return;
}
if (stop_token.stop_requested()) {
break;
}
auto current_thread = system.Kernel().CurrentScheduler()->GetCurrentThread();
data.is_running = true;
Common::Fiber::YieldTo(data.host_context, *current_thread->GetHostContext());

View File

@@ -78,9 +78,9 @@ private:
void SingleCoreRunSuspendThread();
void SingleCorePause(bool paused);
static void ThreadStart(CpuManager& cpu_manager, std::size_t core);
static void ThreadStart(std::stop_token stop_token, CpuManager& cpu_manager, std::size_t core);
void RunThread(std::size_t core);
void RunThread(std::stop_token stop_token, std::size_t core);
struct CoreData {
std::shared_ptr<Common::Fiber> host_context;
@@ -89,7 +89,7 @@ private:
std::atomic<bool> is_running;
std::atomic<bool> is_paused;
std::atomic<bool> initialized;
std::unique_ptr<std::thread> host_thread;
std::jthread host_thread;
};
std::atomic<bool> running_mode{};

View File

@@ -5,6 +5,7 @@
#pragma once
#include <array>
#include <string>
#include <vector>
#include "common/common_funcs.h"

View File

@@ -273,6 +273,10 @@ VirtualFile VfsDirectory::GetFile(std::string_view name) const {
return iter == files.end() ? nullptr : *iter;
}
FileTimeStampRaw VfsDirectory::GetFileTimeStamp([[maybe_unused]] std::string_view path) const {
return {};
}
VirtualDir VfsDirectory::GetSubdirectory(std::string_view name) const {
const auto& subs = GetSubdirectories();
const auto iter = std::find_if(subs.begin(), subs.end(),

View File

@@ -199,6 +199,9 @@ public:
// file with name.
virtual VirtualFile GetFile(std::string_view name) const;
// Returns a struct containing the file's timestamp.
virtual FileTimeStampRaw GetFileTimeStamp(std::string_view path) const;
// Returns a vector containing all of the subdirectories in this directory.
virtual std::vector<VirtualDir> GetSubdirectories() const = 0;
// Returns the directory with name matching name. Returns nullptr if directory dosen't have a

View File

@@ -13,6 +13,13 @@
#include "common/logging/log.h"
#include "core/file_sys/vfs_real.h"
// For FileTimeStampRaw
#include <sys/stat.h>
#ifdef _MSC_VER
#define stat _stat64
#endif
namespace FileSys {
namespace FS = Common::FS;
@@ -392,6 +399,28 @@ std::vector<VirtualFile> RealVfsDirectory::GetFiles() const {
return IterateEntries<RealVfsFile, VfsFile>();
}
FileTimeStampRaw RealVfsDirectory::GetFileTimeStamp(std::string_view path_) const {
const auto full_path = FS::SanitizePath(path + '/' + std::string(path_));
const auto fs_path = std::filesystem::path{FS::ToU8String(full_path)};
struct stat file_status;
#ifdef _WIN32
const auto stat_result = _wstat64(fs_path.c_str(), &file_status);
#else
const auto stat_result = stat(fs_path.c_str(), &file_status);
#endif
if (stat_result != 0) {
return {};
}
return {
.created{static_cast<u64>(file_status.st_ctime)},
.accessed{static_cast<u64>(file_status.st_atime)},
.modified{static_cast<u64>(file_status.st_mtime)},
};
}
std::vector<VirtualDir> RealVfsDirectory::GetSubdirectories() const {
return IterateEntries<RealVfsDirectory, VfsDirectory>();
}

View File

@@ -86,6 +86,7 @@ public:
VirtualDir CreateDirectoryRelative(std::string_view relative_path) override;
bool DeleteSubdirectoryRecursive(std::string_view name) override;
std::vector<VirtualFile> GetFiles() const override;
FileTimeStampRaw GetFileTimeStamp(std::string_view path) const override;
std::vector<VirtualDir> GetSubdirectories() const override;
bool IsWritable() const override;
bool IsReadable() const override;

View File

@@ -6,6 +6,8 @@
#include <memory>
#include "common/common_types.h"
namespace FileSys {
class VfsDirectory;
@@ -18,4 +20,11 @@ using VirtualDir = std::shared_ptr<VfsDirectory>;
using VirtualFile = std::shared_ptr<VfsFile>;
using VirtualFilesystem = std::shared_ptr<VfsFilesystem>;
struct FileTimeStampRaw {
u64 created{};
u64 accessed{};
u64 modified{};
u64 padding{};
};
} // namespace FileSys

View File

@@ -28,13 +28,20 @@ constexpr char DISPLAY_TITLE[] = "NintendoSDK Firmware for NX 12.1.0-1.0";
// Atmosphere version constants.
constexpr u8 ATMOSPHERE_RELEASE_VERSION_MAJOR = 0;
constexpr u8 ATMOSPHERE_RELEASE_VERSION_MINOR = 19;
constexpr u8 ATMOSPHERE_RELEASE_VERSION_MICRO = 5;
constexpr u8 ATMOSPHERE_RELEASE_VERSION_MAJOR = 1;
constexpr u8 ATMOSPHERE_RELEASE_VERSION_MINOR = 0;
constexpr u8 ATMOSPHERE_RELEASE_VERSION_MICRO = 0;
constexpr u32 AtmosphereTargetFirmwareWithRevision(u8 major, u8 minor, u8 micro, u8 rev) {
return u32{major} << 24 | u32{minor} << 16 | u32{micro} << 8 | u32{rev};
}
constexpr u32 AtmosphereTargetFirmware(u8 major, u8 minor, u8 micro) {
return AtmosphereTargetFirmwareWithRevision(major, minor, micro, 0);
}
constexpr u32 GetTargetFirmware() {
return u32{HOS_VERSION_MAJOR} << 24 | u32{HOS_VERSION_MINOR} << 16 |
u32{HOS_VERSION_MICRO} << 8 | 0U;
return AtmosphereTargetFirmware(HOS_VERSION_MAJOR, HOS_VERSION_MINOR, HOS_VERSION_MICRO);
}
} // namespace HLE::ApiVersion

View File

@@ -5,6 +5,7 @@
#pragma once
#include <array>
#include <functional>
#include <memory>
#include <string>
#include <unordered_map>

View File

@@ -23,6 +23,7 @@
#include "core/hle/service/acc/acc_su.h"
#include "core/hle/service/acc/acc_u0.h"
#include "core/hle/service/acc/acc_u1.h"
#include "core/hle/service/acc/async_context.h"
#include "core/hle/service/acc/errors.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/glue/arp.h"
@@ -454,22 +455,6 @@ public:
: IProfileCommon{system_, "IProfileEditor", true, user_id_, profile_manager_} {}
};
class IAsyncContext final : public ServiceFramework<IAsyncContext> {
public:
explicit IAsyncContext(Core::System& system_) : ServiceFramework{system_, "IAsyncContext"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetSystemEvent"},
{1, nullptr, "Cancel"},
{2, nullptr, "HasDone"},
{3, nullptr, "GetResult"},
};
// clang-format on
RegisterHandlers(functions);
}
};
class ISessionObject final : public ServiceFramework<ISessionObject> {
public:
explicit ISessionObject(Core::System& system_, Common::UUID)
@@ -504,16 +489,44 @@ public:
}
};
class EnsureTokenIdCacheAsyncInterface final : public IAsyncContext {
public:
explicit EnsureTokenIdCacheAsyncInterface(Core::System& system_) : IAsyncContext{system_} {
MarkComplete();
}
~EnsureTokenIdCacheAsyncInterface() = default;
void LoadIdTokenCache(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_ACC, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
protected:
bool IsComplete() const override {
return true;
}
void Cancel() override {}
ResultCode GetResult() const override {
return ResultSuccess;
}
};
class IManagerForApplication final : public ServiceFramework<IManagerForApplication> {
public:
explicit IManagerForApplication(Core::System& system_, Common::UUID user_id_)
: ServiceFramework{system_, "IManagerForApplication"}, user_id{user_id_} {
: ServiceFramework{system_, "IManagerForApplication"},
ensure_token_id{std::make_shared<EnsureTokenIdCacheAsyncInterface>(system)},
user_id{user_id_} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IManagerForApplication::CheckAvailability, "CheckAvailability"},
{1, &IManagerForApplication::GetAccountId, "GetAccountId"},
{2, nullptr, "EnsureIdTokenCacheAsync"},
{3, nullptr, "LoadIdTokenCache"},
{2, &IManagerForApplication::EnsureIdTokenCacheAsync, "EnsureIdTokenCacheAsync"},
{3, &IManagerForApplication::LoadIdTokenCache, "LoadIdTokenCache"},
{130, &IManagerForApplication::GetNintendoAccountUserResourceCacheForApplication, "GetNintendoAccountUserResourceCacheForApplication"},
{150, nullptr, "CreateAuthorizationRequest"},
{160, &IManagerForApplication::StoreOpenContext, "StoreOpenContext"},
@@ -540,6 +553,20 @@ private:
rb.PushRaw<u64>(user_id.GetNintendoID());
}
void EnsureIdTokenCacheAsync(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_ACC, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
rb.PushIpcInterface(ensure_token_id);
}
void LoadIdTokenCache(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_ACC, "(STUBBED) called");
ensure_token_id->LoadIdTokenCache(ctx);
}
void GetNintendoAccountUserResourceCacheForApplication(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_ACC, "(STUBBED) called");
@@ -562,6 +589,7 @@ private:
rb.Push(ResultSuccess);
}
std::shared_ptr<EnsureTokenIdCacheAsyncInterface> ensure_token_id{};
Common::UUID user_id{Common::INVALID_UUID};
};

View File

@@ -0,0 +1,68 @@
// Copyright 2021 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/acc/async_context.h"
namespace Service::Account {
IAsyncContext::IAsyncContext(Core::System& system_)
: ServiceFramework{system_, "IAsyncContext"}, compeletion_event{system_.Kernel()} {
Kernel::KAutoObject::Create(std::addressof(compeletion_event));
compeletion_event.Initialize("IAsyncContext:CompletionEvent");
// clang-format off
static const FunctionInfo functions[] = {
{0, &IAsyncContext::GetSystemEvent, "GetSystemEvent"},
{1, &IAsyncContext::Cancel, "Cancel"},
{2, &IAsyncContext::HasDone, "HasDone"},
{3, &IAsyncContext::GetResult, "GetResult"},
};
// clang-format on
RegisterHandlers(functions);
}
void IAsyncContext::GetSystemEvent(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_ACC, "called");
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(ResultSuccess);
rb.PushCopyObjects(compeletion_event.GetReadableEvent());
}
void IAsyncContext::Cancel(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_ACC, "called");
Cancel();
MarkComplete();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void IAsyncContext::HasDone(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_ACC, "called");
is_complete.store(IsComplete());
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push(is_complete.load());
}
void IAsyncContext::GetResult(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_ACC, "called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(GetResult());
}
void IAsyncContext::MarkComplete() {
is_complete.store(true);
compeletion_event.GetWritableEvent().Signal();
}
} // namespace Service::Account

View File

@@ -0,0 +1,37 @@
// Copyright 2021 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <atomic>
#include "core/hle/kernel/k_event.h"
#include "core/hle/service/service.h"
namespace Core {
class System;
}
namespace Service::Account {
class IAsyncContext : public ServiceFramework<IAsyncContext> {
public:
explicit IAsyncContext(Core::System& system_);
void GetSystemEvent(Kernel::HLERequestContext& ctx);
void Cancel(Kernel::HLERequestContext& ctx);
void HasDone(Kernel::HLERequestContext& ctx);
void GetResult(Kernel::HLERequestContext& ctx);
protected:
virtual bool IsComplete() const = 0;
virtual void Cancel() = 0;
virtual ResultCode GetResult() const = 0;
void MarkComplete();
std::atomic<bool> is_complete{false};
Kernel::KEvent compeletion_event;
};
} // namespace Service::Account

View File

@@ -1270,7 +1270,8 @@ void ILibraryAppletCreator::CreateHandleStorage(Kernel::HLERequestContext& ctx)
IApplicationFunctions::IApplicationFunctions(Core::System& system_)
: ServiceFramework{system_, "IApplicationFunctions"}, gpu_error_detected_event{system.Kernel()},
friend_invitation_storage_channel_event{system.Kernel()},
health_warning_disappeared_system_event{system.Kernel()} {
notification_storage_channel_event{system.Kernel()}, health_warning_disappeared_system_event{
system.Kernel()} {
// clang-format off
static const FunctionInfo functions[] = {
{1, &IApplicationFunctions::PopLaunchParameter, "PopLaunchParameter"},
@@ -1322,7 +1323,7 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_)
{131, nullptr, "SetDelayTimeToAbortOnGpuError"},
{140, &IApplicationFunctions::GetFriendInvitationStorageChannelEvent, "GetFriendInvitationStorageChannelEvent"},
{141, &IApplicationFunctions::TryPopFromFriendInvitationStorageChannel, "TryPopFromFriendInvitationStorageChannel"},
{150, nullptr, "GetNotificationStorageChannelEvent"},
{150, &IApplicationFunctions::GetNotificationStorageChannelEvent, "GetNotificationStorageChannelEvent"},
{151, nullptr, "TryPopFromNotificationStorageChannel"},
{160, &IApplicationFunctions::GetHealthWarningDisappearedSystemEvent, "GetHealthWarningDisappearedSystemEvent"},
{170, nullptr, "SetHdcpAuthenticationActivated"},
@@ -1340,11 +1341,14 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_)
Kernel::KAutoObject::Create(std::addressof(gpu_error_detected_event));
Kernel::KAutoObject::Create(std::addressof(friend_invitation_storage_channel_event));
Kernel::KAutoObject::Create(std::addressof(notification_storage_channel_event));
Kernel::KAutoObject::Create(std::addressof(health_warning_disappeared_system_event));
gpu_error_detected_event.Initialize("IApplicationFunctions:GpuErrorDetectedSystemEvent");
friend_invitation_storage_channel_event.Initialize(
"IApplicationFunctions:FriendInvitationStorageChannelEvent");
notification_storage_channel_event.Initialize(
"IApplicationFunctions:NotificationStorageChannelEvent");
health_warning_disappeared_system_event.Initialize(
"IApplicationFunctions:HealthWarningDisappearedSystemEvent");
}
@@ -1762,6 +1766,14 @@ void IApplicationFunctions::TryPopFromFriendInvitationStorageChannel(
rb.Push(ERR_NO_DATA_IN_CHANNEL);
}
void IApplicationFunctions::GetNotificationStorageChannelEvent(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(ResultSuccess);
rb.PushCopyObjects(notification_storage_channel_event.GetReadableEvent());
}
void IApplicationFunctions::GetHealthWarningDisappearedSystemEvent(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");

View File

@@ -295,6 +295,7 @@ private:
void GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx);
void GetFriendInvitationStorageChannelEvent(Kernel::HLERequestContext& ctx);
void TryPopFromFriendInvitationStorageChannel(Kernel::HLERequestContext& ctx);
void GetNotificationStorageChannelEvent(Kernel::HLERequestContext& ctx);
void GetHealthWarningDisappearedSystemEvent(Kernel::HLERequestContext& ctx);
bool launch_popped_application_specific = false;
@@ -302,6 +303,7 @@ private:
s32 previous_program_index{-1};
Kernel::KEvent gpu_error_detected_event;
Kernel::KEvent friend_invitation_storage_channel_event;
Kernel::KEvent notification_storage_channel_event;
Kernel::KEvent health_warning_disappeared_system_event;
};

View File

@@ -97,14 +97,24 @@ ResultCode VfsDirectoryServiceWrapper::DeleteFile(const std::string& path_) cons
ResultCode VfsDirectoryServiceWrapper::CreateDirectory(const std::string& path_) const {
std::string path(Common::FS::SanitizePath(path_));
auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
if (dir == nullptr || Common::FS::GetFilename(Common::FS::GetParentPath(path)).empty()) {
dir = backing;
}
auto new_dir = dir->CreateSubdirectory(Common::FS::GetFilename(path));
if (new_dir == nullptr) {
// TODO(DarkLordZach): Find a better error code for this
return ResultUnknown;
// NOTE: This is inaccurate behavior. CreateDirectory is not recursive.
// CreateDirectory should return PathNotFound if the parent directory does not exist.
// This is here temporarily in order to have UMM "work" in the meantime.
// TODO (Morph): Remove this when a hardware test verifies the correct behavior.
const auto components = Common::FS::SplitPathComponents(path);
std::string relative_path;
for (const auto& component : components) {
// Skip empty path components
if (component.empty()) {
continue;
}
relative_path = Common::FS::SanitizePath(relative_path + '/' + component);
auto new_dir = backing->CreateSubdirectory(relative_path);
if (new_dir == nullptr) {
// TODO(DarkLordZach): Find a better error code for this
return ResultUnknown;
}
}
return ResultSuccess;
}
@@ -251,6 +261,18 @@ ResultVal<FileSys::EntryType> VfsDirectoryServiceWrapper::GetEntryType(
return FileSys::ERROR_PATH_NOT_FOUND;
}
ResultVal<FileSys::FileTimeStampRaw> VfsDirectoryServiceWrapper::GetFileTimeStampRaw(
const std::string& path) const {
auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
if (dir == nullptr) {
return FileSys::ERROR_PATH_NOT_FOUND;
}
if (GetEntryType(path).Failed()) {
return FileSys::ERROR_PATH_NOT_FOUND;
}
return MakeResult(dir->GetFileTimeStamp(Common::FS::GetFilename(path)));
}
FileSystemController::FileSystemController(Core::System& system_) : system{system_} {}
FileSystemController::~FileSystemController() = default;

View File

@@ -240,6 +240,12 @@ public:
*/
ResultVal<FileSys::EntryType> GetEntryType(const std::string& path) const;
/**
* Get the timestamp of the specified path
* @return The timestamp of the specified path or error code
*/
ResultVal<FileSys::FileTimeStampRaw> GetFileTimeStampRaw(const std::string& path) const;
private:
FileSys::VirtualDir backing;
};

View File

@@ -326,7 +326,7 @@ public:
{11, &IFileSystem::GetFreeSpaceSize, "GetFreeSpaceSize"},
{12, &IFileSystem::GetTotalSpaceSize, "GetTotalSpaceSize"},
{13, &IFileSystem::CleanDirectoryRecursively, "CleanDirectoryRecursively"},
{14, nullptr, "GetFileTimeStampRaw"},
{14, &IFileSystem::GetFileTimeStampRaw, "GetFileTimeStampRaw"},
{15, nullptr, "QueryEntry"},
};
RegisterHandlers(functions);
@@ -501,6 +501,24 @@ public:
rb.Push(size.get_total_size());
}
void GetFileTimeStampRaw(Kernel::HLERequestContext& ctx) {
const auto file_buffer = ctx.ReadBuffer();
const std::string name = Common::StringFromBuffer(file_buffer);
LOG_WARNING(Service_FS, "(Partial Implementation) called. file={}", name);
auto result = backend.GetFileTimeStampRaw(name);
if (result.Failed()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result.Code());
return;
}
IPC::ResponseBuilder rb{ctx, 10};
rb.Push(ResultSuccess);
rb.PushRaw(*result);
}
private:
VfsDirectoryServiceWrapper backend;
SizeGetter size;

View File

@@ -15,6 +15,20 @@
namespace Service::HID {
class Controller_Touchscreen final : public ControllerBase {
public:
enum class TouchScreenModeForNx : u8 {
UseSystemSetting,
Finger,
Heat2,
};
struct TouchScreenConfigurationForNx {
TouchScreenModeForNx mode;
INSERT_PADDING_BYTES_NOINIT(0x7);
INSERT_PADDING_BYTES_NOINIT(0xF); // Reserved
};
static_assert(sizeof(TouchScreenConfigurationForNx) == 0x17,
"TouchScreenConfigurationForNx is an invalid size");
explicit Controller_Touchscreen(Core::System& system_);
~Controller_Touchscreen() override;

View File

@@ -331,7 +331,7 @@ Hid::Hid(Core::System& system_)
{529, nullptr, "SetDisallowedPalmaConnection"},
{1000, &Hid::SetNpadCommunicationMode, "SetNpadCommunicationMode"},
{1001, &Hid::GetNpadCommunicationMode, "GetNpadCommunicationMode"},
{1002, nullptr, "SetTouchScreenConfiguration"},
{1002, &Hid::SetTouchScreenConfiguration, "SetTouchScreenConfiguration"},
{1003, nullptr, "IsFirmwareUpdateNeededForNotification"},
{2000, nullptr, "ActivateDigitizer"},
};
@@ -1631,6 +1631,18 @@ void Hid::GetNpadCommunicationMode(Kernel::HLERequestContext& ctx) {
.GetNpadCommunicationMode());
}
void Hid::SetTouchScreenConfiguration(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto touchscreen_mode{rp.PopRaw<Controller_Touchscreen::TouchScreenConfigurationForNx>()};
const auto applet_resource_user_id{rp.Pop<u64>()};
LOG_WARNING(Service_HID, "(STUBBED) called, touchscreen_mode={}, applet_resource_user_id={}",
touchscreen_mode.mode, applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
class HidDbg final : public ServiceFramework<HidDbg> {
public:
explicit HidDbg(Core::System& system_) : ServiceFramework{system_, "hid:dbg"} {

View File

@@ -159,6 +159,7 @@ private:
void SetPalmaBoostMode(Kernel::HLERequestContext& ctx);
void SetNpadCommunicationMode(Kernel::HLERequestContext& ctx);
void GetNpadCommunicationMode(Kernel::HLERequestContext& ctx);
void SetTouchScreenConfiguration(Kernel::HLERequestContext& ctx);
enum class VibrationDeviceType : u32 {
Unknown = 0,

View File

@@ -15,7 +15,7 @@ public:
explicit IService(Core::System& system_) : ServiceFramework{system_, "ngct:u"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Match"},
{0, &IService::Match, "Match"},
{1, &IService::Filter, "Filter"},
};
// clang-format on
@@ -24,6 +24,19 @@ public:
}
private:
void Match(Kernel::HLERequestContext& ctx) {
const auto buffer = ctx.ReadBuffer();
const auto text = Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(buffer.data()), buffer.size());
LOG_WARNING(Service_NGCT, "(STUBBED) called, text={}", text);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
// Return false since we don't censor anything
rb.Push(false);
}
void Filter(Kernel::HLERequestContext& ctx) {
const auto buffer = ctx.ReadBuffer();
const auto text = Common::StringFromFixedZeroTerminatedBuffer(

View File

@@ -42,15 +42,14 @@ void nvdisp_disp0::OnClose(DeviceFD fd) {}
void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u32 height,
u32 stride, NVFlinger::BufferQueue::BufferTransformFlags transform,
const Common::Rectangle<int>& crop_rect) {
VAddr addr = nvmap_dev->GetObjectAddress(buffer_handle);
const VAddr addr = nvmap_dev->GetObjectAddress(buffer_handle);
LOG_TRACE(Service,
"Drawing from address {:X} offset {:08X} Width {} Height {} Stride {} Format {}",
addr, offset, width, height, stride, format);
using PixelFormat = Tegra::FramebufferConfig::PixelFormat;
const Tegra::FramebufferConfig framebuffer{
addr, offset, width, height, stride, static_cast<PixelFormat>(format),
transform, crop_rect};
const auto pixel_format = static_cast<Tegra::FramebufferConfig::PixelFormat>(format);
const Tegra::FramebufferConfig framebuffer{addr, offset, width, height,
stride, pixel_format, transform, crop_rect};
system.GetPerfStats().EndSystemFrame();
system.GPU().SwapBuffers(&framebuffer);

View File

@@ -9,17 +9,20 @@
#include "core/core.h"
#include "core/hle/kernel/k_writable_event.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/nvflinger/buffer_queue.h"
namespace Service::NVFlinger {
BufferQueue::BufferQueue(Kernel::KernelCore& kernel, u32 id_, u64 layer_id_)
: id(id_), layer_id(layer_id_), buffer_wait_event{kernel} {
Kernel::KAutoObject::Create(std::addressof(buffer_wait_event));
buffer_wait_event.Initialize("BufferQueue:WaitEvent");
BufferQueue::BufferQueue(Kernel::KernelCore& kernel, u32 id_, u64 layer_id_,
KernelHelpers::ServiceContext& service_context_)
: id(id_), layer_id(layer_id_), service_context{service_context_} {
buffer_wait_event = service_context.CreateEvent("BufferQueue:WaitEvent");
}
BufferQueue::~BufferQueue() = default;
BufferQueue::~BufferQueue() {
service_context.CloseEvent(buffer_wait_event);
}
void BufferQueue::SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer) {
ASSERT(slot < buffer_slots);
@@ -41,7 +44,7 @@ void BufferQueue::SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer)
.multi_fence = {},
};
buffer_wait_event.GetWritableEvent().Signal();
buffer_wait_event->GetWritableEvent().Signal();
}
std::optional<std::pair<u32, Service::Nvidia::MultiFence*>> BufferQueue::DequeueBuffer(u32 width,
@@ -119,7 +122,7 @@ void BufferQueue::CancelBuffer(u32 slot, const Service::Nvidia::MultiFence& mult
}
free_buffers_condition.notify_one();
buffer_wait_event.GetWritableEvent().Signal();
buffer_wait_event->GetWritableEvent().Signal();
}
std::optional<std::reference_wrapper<const BufferQueue::Buffer>> BufferQueue::AcquireBuffer() {
@@ -154,7 +157,7 @@ void BufferQueue::ReleaseBuffer(u32 slot) {
}
free_buffers_condition.notify_one();
buffer_wait_event.GetWritableEvent().Signal();
buffer_wait_event->GetWritableEvent().Signal();
}
void BufferQueue::Connect() {
@@ -169,7 +172,7 @@ void BufferQueue::Disconnect() {
std::unique_lock lock{queue_sequence_mutex};
queue_sequence.clear();
}
buffer_wait_event.GetWritableEvent().Signal();
buffer_wait_event->GetWritableEvent().Signal();
is_connect = false;
free_buffers_condition.notify_one();
}
@@ -189,11 +192,11 @@ u32 BufferQueue::Query(QueryType type) {
}
Kernel::KWritableEvent& BufferQueue::GetWritableBufferWaitEvent() {
return buffer_wait_event.GetWritableEvent();
return buffer_wait_event->GetWritableEvent();
}
Kernel::KReadableEvent& BufferQueue::GetBufferWaitEvent() {
return buffer_wait_event.GetReadableEvent();
return buffer_wait_event->GetReadableEvent();
}
} // namespace Service::NVFlinger

View File

@@ -24,6 +24,10 @@ class KReadableEvent;
class KWritableEvent;
} // namespace Kernel
namespace Service::KernelHelpers {
class ServiceContext;
} // namespace Service::KernelHelpers
namespace Service::NVFlinger {
constexpr u32 buffer_slots = 0x40;
@@ -38,7 +42,9 @@ struct IGBPBuffer {
u32_le index;
INSERT_PADDING_WORDS(3);
u32_le gpu_buffer_id;
INSERT_PADDING_WORDS(17);
INSERT_PADDING_WORDS(6);
u32_le external_format;
INSERT_PADDING_WORDS(10);
u32_le nvmap_handle;
u32_le offset;
INSERT_PADDING_WORDS(60);
@@ -54,7 +60,8 @@ public:
NativeWindowFormat = 2,
};
explicit BufferQueue(Kernel::KernelCore& kernel, u32 id_, u64 layer_id_);
explicit BufferQueue(Kernel::KernelCore& kernel, u32 id_, u64 layer_id_,
KernelHelpers::ServiceContext& service_context_);
~BufferQueue();
enum class BufferTransformFlags : u32 {
@@ -130,12 +137,14 @@ private:
std::list<u32> free_buffers;
std::array<Buffer, buffer_slots> buffers;
std::list<u32> queue_sequence;
Kernel::KEvent buffer_wait_event;
Kernel::KEvent* buffer_wait_event{};
std::mutex free_buffers_mutex;
std::condition_variable free_buffers_condition;
std::mutex queue_sequence_mutex;
KernelHelpers::ServiceContext& service_context;
};
} // namespace Service::NVFlinger

View File

@@ -61,12 +61,13 @@ void NVFlinger::SplitVSync() {
}
}
NVFlinger::NVFlinger(Core::System& system_) : system(system_) {
displays.emplace_back(0, "Default", system);
displays.emplace_back(1, "External", system);
displays.emplace_back(2, "Edid", system);
displays.emplace_back(3, "Internal", system);
displays.emplace_back(4, "Null", system);
NVFlinger::NVFlinger(Core::System& system_)
: system(system_), service_context(system_, "nvflinger") {
displays.emplace_back(0, "Default", service_context, system);
displays.emplace_back(1, "External", service_context, system);
displays.emplace_back(2, "Edid", service_context, system);
displays.emplace_back(3, "Internal", service_context, system);
displays.emplace_back(4, "Null", service_context, system);
guard = std::make_shared<std::mutex>();
// Schedule the screen composition events
@@ -146,7 +147,7 @@ std::optional<u64> NVFlinger::CreateLayer(u64 display_id) {
void NVFlinger::CreateLayerAtId(VI::Display& display, u64 layer_id) {
const u32 buffer_queue_id = next_buffer_queue_id++;
buffer_queues.emplace_back(
std::make_unique<BufferQueue>(system.Kernel(), buffer_queue_id, layer_id));
std::make_unique<BufferQueue>(system.Kernel(), buffer_queue_id, layer_id, service_context));
display.CreateLayer(layer_id, *buffer_queues.back());
}
@@ -297,7 +298,7 @@ void NVFlinger::Compose() {
auto nvdisp = nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>("/dev/nvdisp_disp0");
ASSERT(nvdisp);
nvdisp->flip(igbp_buffer.gpu_buffer_id, igbp_buffer.offset, igbp_buffer.format,
nvdisp->flip(igbp_buffer.gpu_buffer_id, igbp_buffer.offset, igbp_buffer.external_format,
igbp_buffer.width, igbp_buffer.height, igbp_buffer.stride,
buffer->get().transform, buffer->get().crop_rect);

View File

@@ -15,6 +15,7 @@
#include <vector>
#include "common/common_types.h"
#include "core/hle/service/kernel_helpers.h"
namespace Common {
class Event;
@@ -135,6 +136,8 @@ private:
std::unique_ptr<std::thread> vsync_thread;
std::unique_ptr<Common::Event> wait_event;
std::atomic<bool> is_running{};
KernelHelpers::ServiceContext service_context;
};
} // namespace Service::NVFlinger

View File

@@ -12,18 +12,21 @@
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/kernel/k_writable_event.h"
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/vi/display/vi_display.h"
#include "core/hle/service/vi/layer/vi_layer.h"
namespace Service::VI {
Display::Display(u64 id, std::string name_, Core::System& system)
: display_id{id}, name{std::move(name_)}, vsync_event{system.Kernel()} {
Kernel::KAutoObject::Create(std::addressof(vsync_event));
vsync_event.Initialize(fmt::format("Display VSync Event {}", id));
Display::Display(u64 id, std::string name_, KernelHelpers::ServiceContext& service_context_,
Core::System& system_)
: display_id{id}, name{std::move(name_)}, service_context{service_context_} {
vsync_event = service_context.CreateEvent(fmt::format("Display VSync Event {}", id));
}
Display::~Display() = default;
Display::~Display() {
service_context.CloseEvent(vsync_event);
}
Layer& Display::GetLayer(std::size_t index) {
return *layers.at(index);
@@ -34,11 +37,11 @@ const Layer& Display::GetLayer(std::size_t index) const {
}
Kernel::KReadableEvent& Display::GetVSyncEvent() {
return vsync_event.GetReadableEvent();
return vsync_event->GetReadableEvent();
}
void Display::SignalVSyncEvent() {
vsync_event.GetWritableEvent().Signal();
vsync_event->GetWritableEvent().Signal();
}
void Display::CreateLayer(u64 layer_id, NVFlinger::BufferQueue& buffer_queue) {

View File

@@ -18,6 +18,9 @@ class KEvent;
namespace Service::NVFlinger {
class BufferQueue;
}
namespace Service::KernelHelpers {
class ServiceContext;
} // namespace Service::KernelHelpers
namespace Service::VI {
@@ -31,10 +34,13 @@ class Display {
public:
/// Constructs a display with a given unique ID and name.
///
/// @param id The unique ID for this display.
/// @param id The unique ID for this display.
/// @param service_context_ The ServiceContext for the owning service.
/// @param name_ The name for this display.
/// @param system_ The global system instance.
///
Display(u64 id, std::string name_, Core::System& system);
Display(u64 id, std::string name_, KernelHelpers::ServiceContext& service_context_,
Core::System& system_);
~Display();
/// Gets the unique ID assigned to this display.
@@ -98,9 +104,10 @@ public:
private:
u64 display_id;
std::string name;
KernelHelpers::ServiceContext& service_context;
std::vector<std::shared_ptr<Layer>> layers;
Kernel::KEvent vsync_event;
Kernel::KEvent* vsync_event{};
};
} // namespace Service::VI

View File

@@ -7,7 +7,8 @@
#include <limits>
#include <utility>
#include <vector>
#include "common/common_funcs.h"
#include "common/error.h"
#ifdef _WIN32
#include <winsock2.h>
@@ -223,7 +224,7 @@ Errno GetAndLogLastError() {
if (err == Errno::AGAIN) {
return err;
}
LOG_ERROR(Network, "Socket operation error: {}", NativeErrorToString(e));
LOG_ERROR(Network, "Socket operation error: {}", Common::NativeErrorToString(e));
return err;
}

View File

@@ -72,6 +72,18 @@ static const char* TranslateGPUAccuracyLevel(Settings::GPUAccuracy backend) {
return "Unknown";
}
static const char* TranslateNvdecEmulation(Settings::NvdecEmulation backend) {
switch (backend) {
case Settings::NvdecEmulation::Off:
return "Off";
case Settings::NvdecEmulation::CPU:
return "CPU";
case Settings::NvdecEmulation::GPU:
return "GPU";
}
return "Unknown";
}
u64 GetTelemetryId() {
u64 telemetry_id{};
const auto filename = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir) / "telemetry_id";
@@ -214,8 +226,6 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader,
// Log user configuration information
constexpr auto field_type = Telemetry::FieldType::UserConfig;
AddField(field_type, "Audio_SinkId", Settings::values.sink_id.GetValue());
AddField(field_type, "Audio_EnableAudioStretching",
Settings::values.enable_audio_stretching.GetValue());
AddField(field_type, "Core_UseMultiCore", Settings::values.use_multi_core.GetValue());
AddField(field_type, "Renderer_Backend",
TranslateRenderer(Settings::values.renderer_backend.GetValue()));
@@ -229,8 +239,8 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader,
TranslateGPUAccuracyLevel(Settings::values.gpu_accuracy.GetValue()));
AddField(field_type, "Renderer_UseAsynchronousGpuEmulation",
Settings::values.use_asynchronous_gpu_emulation.GetValue());
AddField(field_type, "Renderer_UseNvdecEmulation",
Settings::values.use_nvdec_emulation.GetValue());
AddField(field_type, "Renderer_NvdecEmulation",
TranslateNvdecEmulation(Settings::values.nvdec_emulation.GetValue()));
AddField(field_type, "Renderer_AccelerateASTC", Settings::values.accelerate_astc.GetValue());
AddField(field_type, "Renderer_UseVsync", Settings::values.use_vsync.GetValue());
AddField(field_type, "Renderer_ShaderBackend",

View File

@@ -21,6 +21,10 @@ add_library(input_common STATIC
mouse/mouse_poller.h
sdl/sdl.cpp
sdl/sdl.h
tas/tas_input.cpp
tas/tas_input.h
tas/tas_poller.cpp
tas/tas_poller.h
udp/client.cpp
udp/client.h
udp/protocol.cpp

View File

@@ -5,6 +5,7 @@
#include <memory>
#include <thread>
#include "common/param_package.h"
#include "common/settings.h"
#include "input_common/analog_from_button.h"
#include "input_common/gcadapter/gc_adapter.h"
#include "input_common/gcadapter/gc_poller.h"
@@ -13,6 +14,8 @@
#include "input_common/motion_from_button.h"
#include "input_common/mouse/mouse_input.h"
#include "input_common/mouse/mouse_poller.h"
#include "input_common/tas/tas_input.h"
#include "input_common/tas/tas_poller.h"
#include "input_common/touch_from_button.h"
#include "input_common/udp/client.h"
#include "input_common/udp/udp.h"
@@ -60,6 +63,12 @@ struct InputSubsystem::Impl {
Input::RegisterFactory<Input::MotionDevice>("mouse", mousemotion);
mousetouch = std::make_shared<MouseTouchFactory>(mouse);
Input::RegisterFactory<Input::TouchDevice>("mouse", mousetouch);
tas = std::make_shared<TasInput::Tas>();
tasbuttons = std::make_shared<TasButtonFactory>(tas);
Input::RegisterFactory<Input::ButtonDevice>("tas", tasbuttons);
tasanalog = std::make_shared<TasAnalogFactory>(tas);
Input::RegisterFactory<Input::AnalogDevice>("tas", tasanalog);
}
void Shutdown() {
@@ -94,6 +103,12 @@ struct InputSubsystem::Impl {
mouseanalog.reset();
mousemotion.reset();
mousetouch.reset();
Input::UnregisterFactory<Input::ButtonDevice>("tas");
Input::UnregisterFactory<Input::AnalogDevice>("tas");
tasbuttons.reset();
tasanalog.reset();
}
[[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const {
@@ -101,6 +116,10 @@ struct InputSubsystem::Impl {
Common::ParamPackage{{"display", "Any"}, {"class", "any"}},
Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "keyboard"}},
};
if (Settings::values.tas_enable) {
devices.emplace_back(
Common::ParamPackage{{"display", "TAS Controller"}, {"class", "tas"}});
}
#ifdef HAVE_SDL2
auto sdl_devices = sdl->GetInputDevices();
devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end());
@@ -120,6 +139,9 @@ struct InputSubsystem::Impl {
if (params.Get("class", "") == "gcpad") {
return gcadapter->GetAnalogMappingForDevice(params);
}
if (params.Get("class", "") == "tas") {
return tas->GetAnalogMappingForDevice(params);
}
#ifdef HAVE_SDL2
if (params.Get("class", "") == "sdl") {
return sdl->GetAnalogMappingForDevice(params);
@@ -136,6 +158,9 @@ struct InputSubsystem::Impl {
if (params.Get("class", "") == "gcpad") {
return gcadapter->GetButtonMappingForDevice(params);
}
if (params.Get("class", "") == "tas") {
return tas->GetButtonMappingForDevice(params);
}
#ifdef HAVE_SDL2
if (params.Get("class", "") == "sdl") {
return sdl->GetButtonMappingForDevice(params);
@@ -174,9 +199,12 @@ struct InputSubsystem::Impl {
std::shared_ptr<MouseAnalogFactory> mouseanalog;
std::shared_ptr<MouseMotionFactory> mousemotion;
std::shared_ptr<MouseTouchFactory> mousetouch;
std::shared_ptr<TasButtonFactory> tasbuttons;
std::shared_ptr<TasAnalogFactory> tasanalog;
std::shared_ptr<CemuhookUDP::Client> udp;
std::shared_ptr<GCAdapter::Adapter> gcadapter;
std::shared_ptr<MouseInput::Mouse> mouse;
std::shared_ptr<TasInput::Tas> tas;
};
InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {}
@@ -207,6 +235,14 @@ const MouseInput::Mouse* InputSubsystem::GetMouse() const {
return impl->mouse.get();
}
TasInput::Tas* InputSubsystem::GetTas() {
return impl->tas.get();
}
const TasInput::Tas* InputSubsystem::GetTas() const {
return impl->tas.get();
}
std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const {
return impl->GetInputDevices();
}
@@ -287,6 +323,22 @@ const MouseTouchFactory* InputSubsystem::GetMouseTouch() const {
return impl->mousetouch.get();
}
TasButtonFactory* InputSubsystem::GetTasButtons() {
return impl->tasbuttons.get();
}
const TasButtonFactory* InputSubsystem::GetTasButtons() const {
return impl->tasbuttons.get();
}
TasAnalogFactory* InputSubsystem::GetTasAnalogs() {
return impl->tasanalog.get();
}
const TasAnalogFactory* InputSubsystem::GetTasAnalogs() const {
return impl->tasanalog.get();
}
void InputSubsystem::ReloadInputDevices() {
if (!impl->udp) {
return;

View File

@@ -29,6 +29,10 @@ namespace MouseInput {
class Mouse;
}
namespace TasInput {
class Tas;
}
namespace InputCommon {
namespace Polling {
@@ -64,6 +68,8 @@ class MouseButtonFactory;
class MouseAnalogFactory;
class MouseMotionFactory;
class MouseTouchFactory;
class TasButtonFactory;
class TasAnalogFactory;
class Keyboard;
/**
@@ -103,6 +109,11 @@ public:
/// Retrieves the underlying mouse device.
[[nodiscard]] const MouseInput::Mouse* GetMouse() const;
/// Retrieves the underlying tas device.
[[nodiscard]] TasInput::Tas* GetTas();
/// Retrieves the underlying tas device.
[[nodiscard]] const TasInput::Tas* GetTas() const;
/**
* Returns all available input devices that this Factory can create a new device with.
* Each returned ParamPackage should have a `display` field used for display, a class field for
@@ -144,30 +155,42 @@ public:
/// Retrieves the underlying udp touch handler.
[[nodiscard]] const UDPTouchFactory* GetUDPTouch() const;
/// Retrieves the underlying GameCube button handler.
/// Retrieves the underlying mouse button handler.
[[nodiscard]] MouseButtonFactory* GetMouseButtons();
/// Retrieves the underlying GameCube button handler.
/// Retrieves the underlying mouse button handler.
[[nodiscard]] const MouseButtonFactory* GetMouseButtons() const;
/// Retrieves the underlying udp touch handler.
/// Retrieves the underlying mouse analog handler.
[[nodiscard]] MouseAnalogFactory* GetMouseAnalogs();
/// Retrieves the underlying udp touch handler.
/// Retrieves the underlying mouse analog handler.
[[nodiscard]] const MouseAnalogFactory* GetMouseAnalogs() const;
/// Retrieves the underlying udp motion handler.
/// Retrieves the underlying mouse motion handler.
[[nodiscard]] MouseMotionFactory* GetMouseMotions();
/// Retrieves the underlying udp motion handler.
/// Retrieves the underlying mouse motion handler.
[[nodiscard]] const MouseMotionFactory* GetMouseMotions() const;
/// Retrieves the underlying udp touch handler.
/// Retrieves the underlying mouse touch handler.
[[nodiscard]] MouseTouchFactory* GetMouseTouch();
/// Retrieves the underlying udp touch handler.
/// Retrieves the underlying mouse touch handler.
[[nodiscard]] const MouseTouchFactory* GetMouseTouch() const;
/// Retrieves the underlying tas button handler.
[[nodiscard]] TasButtonFactory* GetTasButtons();
/// Retrieves the underlying tas button handler.
[[nodiscard]] const TasButtonFactory* GetTasButtons() const;
/// Retrieves the underlying tas analogs handler.
[[nodiscard]] TasAnalogFactory* GetTasAnalogs();
/// Retrieves the underlying tas analogs handler.
[[nodiscard]] const TasAnalogFactory* GetTasAnalogs() const;
/// Reloads the input devices
void ReloadInputDevices();

View File

@@ -21,7 +21,7 @@
#include "common/logging/log.h"
#include "common/math_util.h"
#include "common/param_package.h"
#include "common/settings_input.h"
#include "common/settings.h"
#include "common/threadsafe_queue.h"
#include "core/frontend/input.h"
#include "input_common/motion_input.h"
@@ -889,8 +889,10 @@ SDLState::SDLState() {
RegisterFactory<VibrationDevice>("sdl", vibration_factory);
RegisterFactory<MotionDevice>("sdl", motion_factory);
// Disable raw input. When enabled this setting causes SDL to die when a web applet opens
SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, "0");
if (!Settings::values.enable_raw_input) {
// Disable raw input. When enabled this setting causes SDL to die when a web applet opens
SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, "0");
}
// Enable HIDAPI rumble. This prevents SDL from disabling motion on PS4 and PS5 controllers
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
@@ -898,10 +900,10 @@ SDLState::SDLState() {
// Tell SDL2 to use the hidapi driver. This will allow joycons to be detected as a
// GameController and not a generic one
SDL_SetHint("SDL_JOYSTICK_HIDAPI_JOY_CONS", "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1");
// Turn off Pro controller home led
SDL_SetHint("SDL_JOYSTICK_HIDAPI_SWITCH_HOME_LED", "0");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_HOME_LED, "0");
// If the frontend is going to manage the event loop, then we don't start one here
start_thread = SDL_WasInit(SDL_INIT_JOYSTICK) == 0;

View File

@@ -0,0 +1,455 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <cstring>
#include <regex>
#include "common/fs/file.h"
#include "common/fs/fs_types.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
#include "common/settings.h"
#include "input_common/tas/tas_input.h"
namespace TasInput {
// Supported keywords and buttons from a TAS file
constexpr std::array<std::pair<std::string_view, TasButton>, 20> text_to_tas_button = {
std::pair{"KEY_A", TasButton::BUTTON_A},
{"KEY_B", TasButton::BUTTON_B},
{"KEY_X", TasButton::BUTTON_X},
{"KEY_Y", TasButton::BUTTON_Y},
{"KEY_LSTICK", TasButton::STICK_L},
{"KEY_RSTICK", TasButton::STICK_R},
{"KEY_L", TasButton::TRIGGER_L},
{"KEY_R", TasButton::TRIGGER_R},
{"KEY_PLUS", TasButton::BUTTON_PLUS},
{"KEY_MINUS", TasButton::BUTTON_MINUS},
{"KEY_DLEFT", TasButton::BUTTON_LEFT},
{"KEY_DUP", TasButton::BUTTON_UP},
{"KEY_DRIGHT", TasButton::BUTTON_RIGHT},
{"KEY_DDOWN", TasButton::BUTTON_DOWN},
{"KEY_SL", TasButton::BUTTON_SL},
{"KEY_SR", TasButton::BUTTON_SR},
{"KEY_CAPTURE", TasButton::BUTTON_CAPTURE},
{"KEY_HOME", TasButton::BUTTON_HOME},
{"KEY_ZL", TasButton::TRIGGER_ZL},
{"KEY_ZR", TasButton::TRIGGER_ZR},
};
Tas::Tas() {
if (!Settings::values.tas_enable) {
needs_reset = true;
return;
}
LoadTasFiles();
}
Tas::~Tas() {
Stop();
};
void Tas::LoadTasFiles() {
script_length = 0;
for (size_t i = 0; i < commands.size(); i++) {
LoadTasFile(i);
if (commands[i].size() > script_length) {
script_length = commands[i].size();
}
}
}
void Tas::LoadTasFile(size_t player_index) {
if (!commands[player_index].empty()) {
commands[player_index].clear();
}
std::string file =
Common::FS::ReadStringFromFile(Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) /
fmt::format("script0-{}.txt", player_index + 1),
Common::FS::FileType::BinaryFile);
std::stringstream command_line(file);
std::string line;
int frame_no = 0;
while (std::getline(command_line, line, '\n')) {
if (line.empty()) {
continue;
}
LOG_DEBUG(Input, "Loading line: {}", line);
std::smatch m;
std::stringstream linestream(line);
std::string segment;
std::vector<std::string> seglist;
while (std::getline(linestream, segment, ' ')) {
seglist.push_back(segment);
}
if (seglist.size() < 4) {
continue;
}
while (frame_no < std::stoi(seglist.at(0))) {
commands[player_index].push_back({});
frame_no++;
}
TASCommand command = {
.buttons = ReadCommandButtons(seglist.at(1)),
.l_axis = ReadCommandAxis(seglist.at(2)),
.r_axis = ReadCommandAxis(seglist.at(3)),
};
commands[player_index].push_back(command);
frame_no++;
}
LOG_INFO(Input, "TAS file loaded! {} frames", frame_no);
}
void Tas::WriteTasFile(std::u8string file_name) {
std::string output_text;
for (size_t frame = 0; frame < record_commands.size(); frame++) {
if (!output_text.empty()) {
output_text += "\n";
}
const TASCommand& line = record_commands[frame];
output_text += std::to_string(frame) + " " + WriteCommandButtons(line.buttons) + " " +
WriteCommandAxis(line.l_axis) + " " + WriteCommandAxis(line.r_axis);
}
const auto bytes_written = Common::FS::WriteStringToFile(
Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) / file_name,
Common::FS::FileType::TextFile, output_text);
if (bytes_written == output_text.size()) {
LOG_INFO(Input, "TAS file written to file!");
} else {
LOG_ERROR(Input, "Writing the TAS-file has failed! {} / {} bytes written", bytes_written,
output_text.size());
}
}
std::pair<float, float> Tas::FlipAxisY(std::pair<float, float> old) {
auto [x, y] = old;
return {x, -y};
}
void Tas::RecordInput(u32 buttons, const std::array<std::pair<float, float>, 2>& axes) {
last_input = {buttons, FlipAxisY(axes[0]), FlipAxisY(axes[1])};
}
std::tuple<TasState, size_t, size_t> Tas::GetStatus() const {
TasState state;
if (is_recording) {
return {TasState::Recording, 0, record_commands.size()};
}
if (is_running) {
state = TasState::Running;
} else {
state = TasState::Stopped;
}
return {state, current_command, script_length};
}
std::string Tas::DebugButtons(u32 buttons) const {
return fmt::format("{{ {} }}", TasInput::Tas::ButtonsToString(buttons));
}
std::string Tas::DebugJoystick(float x, float y) const {
return fmt::format("[ {} , {} ]", std::to_string(x), std::to_string(y));
}
std::string Tas::DebugInput(const TasData& data) const {
return fmt::format("{{ {} , {} , {} }}", DebugButtons(data.buttons),
DebugJoystick(data.axis[0], data.axis[1]),
DebugJoystick(data.axis[2], data.axis[3]));
}
std::string Tas::DebugInputs(const std::array<TasData, PLAYER_NUMBER>& arr) const {
std::string returns = "[ ";
for (size_t i = 0; i < arr.size(); i++) {
returns += DebugInput(arr[i]);
if (i != arr.size() - 1) {
returns += " , ";
}
}
return returns + "]";
}
std::string Tas::ButtonsToString(u32 button) const {
std::string returns;
for (auto [text_button, tas_button] : text_to_tas_button) {
if ((button & static_cast<u32>(tas_button)) != 0)
returns += fmt::format(", {}", text_button.substr(4));
}
return returns.empty() ? "" : returns.substr(2);
}
void Tas::UpdateThread() {
if (!Settings::values.tas_enable) {
if (is_running) {
Stop();
}
return;
}
if (is_recording) {
record_commands.push_back(last_input);
}
if (needs_reset) {
current_command = 0;
needs_reset = false;
LoadTasFiles();
LOG_DEBUG(Input, "tas_reset done");
}
if (!is_running) {
tas_data.fill({});
return;
}
if (current_command < script_length) {
LOG_DEBUG(Input, "Playing TAS {}/{}", current_command, script_length);
size_t frame = current_command++;
for (size_t i = 0; i < commands.size(); i++) {
if (frame < commands[i].size()) {
TASCommand command = commands[i][frame];
tas_data[i].buttons = command.buttons;
auto [l_axis_x, l_axis_y] = command.l_axis;
tas_data[i].axis[0] = l_axis_x;
tas_data[i].axis[1] = l_axis_y;
auto [r_axis_x, r_axis_y] = command.r_axis;
tas_data[i].axis[2] = r_axis_x;
tas_data[i].axis[3] = r_axis_y;
} else {
tas_data[i] = {};
}
}
} else {
is_running = Settings::values.tas_loop.GetValue();
current_command = 0;
tas_data.fill({});
if (!is_running) {
SwapToStoredController();
}
}
LOG_DEBUG(Input, "TAS inputs: {}", DebugInputs(tas_data));
}
TasAnalog Tas::ReadCommandAxis(const std::string& line) const {
std::stringstream linestream(line);
std::string segment;
std::vector<std::string> seglist;
while (std::getline(linestream, segment, ';')) {
seglist.push_back(segment);
}
const float x = std::stof(seglist.at(0)) / 32767.0f;
const float y = std::stof(seglist.at(1)) / 32767.0f;
return {x, y};
}
u32 Tas::ReadCommandButtons(const std::string& data) const {
std::stringstream button_text(data);
std::string line;
u32 buttons = 0;
while (std::getline(button_text, line, ';')) {
for (auto [text, tas_button] : text_to_tas_button) {
if (text == line) {
buttons |= static_cast<u32>(tas_button);
break;
}
}
}
return buttons;
}
std::string Tas::WriteCommandAxis(TasAnalog data) const {
auto [x, y] = data;
std::string line;
line += std::to_string(static_cast<int>(x * 32767));
line += ";";
line += std::to_string(static_cast<int>(y * 32767));
return line;
}
std::string Tas::WriteCommandButtons(u32 data) const {
if (data == 0) {
return "NONE";
}
std::string line;
u32 index = 0;
while (data > 0) {
if ((data & 1) == 1) {
for (auto [text, tas_button] : text_to_tas_button) {
if (tas_button == static_cast<TasButton>(1 << index)) {
if (line.size() > 0) {
line += ";";
}
line += text;
break;
}
}
}
index++;
data >>= 1;
}
return line;
}
void Tas::StartStop() {
if (!Settings::values.tas_enable) {
return;
}
if (is_running) {
Stop();
} else {
is_running = true;
SwapToTasController();
}
}
void Tas::Stop() {
is_running = false;
SwapToStoredController();
}
void Tas::SwapToTasController() {
if (!Settings::values.tas_swap_controllers) {
return;
}
auto& players = Settings::values.players.GetValue();
for (std::size_t index = 0; index < players.size(); index++) {
auto& player = players[index];
player_mappings[index] = player;
// Only swap active controllers
if (!player.connected) {
continue;
}
Common::ParamPackage tas_param;
tas_param.Set("pad", static_cast<u8>(index));
auto button_mapping = GetButtonMappingForDevice(tas_param);
auto analog_mapping = GetAnalogMappingForDevice(tas_param);
auto& buttons = player.buttons;
auto& analogs = player.analogs;
for (std::size_t i = 0; i < buttons.size(); ++i) {
buttons[i] = button_mapping[static_cast<Settings::NativeButton::Values>(i)].Serialize();
}
for (std::size_t i = 0; i < analogs.size(); ++i) {
analogs[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)].Serialize();
}
}
is_old_input_saved = true;
Settings::values.is_device_reload_pending.store(true);
}
void Tas::SwapToStoredController() {
if (!is_old_input_saved) {
return;
}
auto& players = Settings::values.players.GetValue();
for (std::size_t index = 0; index < players.size(); index++) {
players[index] = player_mappings[index];
}
is_old_input_saved = false;
Settings::values.is_device_reload_pending.store(true);
}
void Tas::Reset() {
if (!Settings::values.tas_enable) {
return;
}
needs_reset = true;
}
bool Tas::Record() {
if (!Settings::values.tas_enable) {
return true;
}
is_recording = !is_recording;
return is_recording;
}
void Tas::SaveRecording(bool overwrite_file) {
if (is_recording) {
return;
}
if (record_commands.empty()) {
return;
}
WriteTasFile(u8"record.txt");
if (overwrite_file) {
WriteTasFile(u8"script0-1.txt");
}
needs_reset = true;
record_commands.clear();
}
InputCommon::ButtonMapping Tas::GetButtonMappingForDevice(
const Common::ParamPackage& params) const {
// This list is missing ZL/ZR since those are not considered buttons.
// We will add those afterwards
// This list also excludes any button that can't be really mapped
static constexpr std::array<std::pair<Settings::NativeButton::Values, TasButton>, 20>
switch_to_tas_button = {
std::pair{Settings::NativeButton::A, TasButton::BUTTON_A},
{Settings::NativeButton::B, TasButton::BUTTON_B},
{Settings::NativeButton::X, TasButton::BUTTON_X},
{Settings::NativeButton::Y, TasButton::BUTTON_Y},
{Settings::NativeButton::LStick, TasButton::STICK_L},
{Settings::NativeButton::RStick, TasButton::STICK_R},
{Settings::NativeButton::L, TasButton::TRIGGER_L},
{Settings::NativeButton::R, TasButton::TRIGGER_R},
{Settings::NativeButton::Plus, TasButton::BUTTON_PLUS},
{Settings::NativeButton::Minus, TasButton::BUTTON_MINUS},
{Settings::NativeButton::DLeft, TasButton::BUTTON_LEFT},
{Settings::NativeButton::DUp, TasButton::BUTTON_UP},
{Settings::NativeButton::DRight, TasButton::BUTTON_RIGHT},
{Settings::NativeButton::DDown, TasButton::BUTTON_DOWN},
{Settings::NativeButton::SL, TasButton::BUTTON_SL},
{Settings::NativeButton::SR, TasButton::BUTTON_SR},
{Settings::NativeButton::Screenshot, TasButton::BUTTON_CAPTURE},
{Settings::NativeButton::Home, TasButton::BUTTON_HOME},
{Settings::NativeButton::ZL, TasButton::TRIGGER_ZL},
{Settings::NativeButton::ZR, TasButton::TRIGGER_ZR},
};
InputCommon::ButtonMapping mapping{};
for (const auto& [switch_button, tas_button] : switch_to_tas_button) {
Common::ParamPackage button_params({{"engine", "tas"}});
button_params.Set("pad", params.Get("pad", 0));
button_params.Set("button", static_cast<int>(tas_button));
mapping.insert_or_assign(switch_button, std::move(button_params));
}
return mapping;
}
InputCommon::AnalogMapping Tas::GetAnalogMappingForDevice(
const Common::ParamPackage& params) const {
InputCommon::AnalogMapping mapping = {};
Common::ParamPackage left_analog_params;
left_analog_params.Set("engine", "tas");
left_analog_params.Set("pad", params.Get("pad", 0));
left_analog_params.Set("axis_x", static_cast<int>(TasAxes::StickX));
left_analog_params.Set("axis_y", static_cast<int>(TasAxes::StickY));
mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
Common::ParamPackage right_analog_params;
right_analog_params.Set("engine", "tas");
right_analog_params.Set("pad", params.Get("pad", 0));
right_analog_params.Set("axis_x", static_cast<int>(TasAxes::SubstickX));
right_analog_params.Set("axis_y", static_cast<int>(TasAxes::SubstickY));
mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
return mapping;
}
const TasData& Tas::GetTasState(std::size_t pad) const {
return tas_data[pad];
}
} // namespace TasInput

View File

@@ -0,0 +1,237 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include "common/common_types.h"
#include "common/settings_input.h"
#include "core/frontend/input.h"
#include "input_common/main.h"
/*
To play back TAS scripts on Yuzu, select the folder with scripts in the configuration menu below
Tools -> Configure TAS. The file itself has normal text format and has to be called script0-1.txt
for controller 1, script0-2.txt for controller 2 and so forth (with max. 8 players).
A script file has the same format as TAS-nx uses, so final files will look like this:
1 KEY_B 0;0 0;0
6 KEY_ZL 0;0 0;0
41 KEY_ZL;KEY_Y 0;0 0;0
43 KEY_X;KEY_A 32767;0 0;0
44 KEY_A 32767;0 0;0
45 KEY_A 32767;0 0;0
46 KEY_A 32767;0 0;0
47 KEY_A 32767;0 0;0
After placing the file at the correct location, it can be read into Yuzu with the (default) hotkey
CTRL+F6 (refresh). In the bottom left corner, it will display the amount of frames the script file
has. Playback can be started or stopped using CTRL+F5.
However, for playback to actually work, the correct input device has to be selected: In the Controls
menu, select TAS from the device list for the controller that the script should be played on.
Recording a new script file is really simple: Just make sure that the proper device (not TAS) is
connected on P1, and press CTRL+F7 to start recording. When done, just press the same keystroke
again (CTRL+F7). The new script will be saved at the location previously selected, as the filename
record.txt.
For debugging purposes, the common controller debugger can be used (View -> Debugging -> Controller
P1).
*/
namespace TasInput {
constexpr size_t PLAYER_NUMBER = 8;
using TasAnalog = std::pair<float, float>;
enum class TasState {
Running,
Recording,
Stopped,
};
enum class TasButton : u32 {
BUTTON_A = 1U << 0,
BUTTON_B = 1U << 1,
BUTTON_X = 1U << 2,
BUTTON_Y = 1U << 3,
STICK_L = 1U << 4,
STICK_R = 1U << 5,
TRIGGER_L = 1U << 6,
TRIGGER_R = 1U << 7,
TRIGGER_ZL = 1U << 8,
TRIGGER_ZR = 1U << 9,
BUTTON_PLUS = 1U << 10,
BUTTON_MINUS = 1U << 11,
BUTTON_LEFT = 1U << 12,
BUTTON_UP = 1U << 13,
BUTTON_RIGHT = 1U << 14,
BUTTON_DOWN = 1U << 15,
BUTTON_SL = 1U << 16,
BUTTON_SR = 1U << 17,
BUTTON_HOME = 1U << 18,
BUTTON_CAPTURE = 1U << 19,
};
enum class TasAxes : u8 {
StickX,
StickY,
SubstickX,
SubstickY,
Undefined,
};
struct TasData {
u32 buttons{};
std::array<float, 4> axis{};
};
class Tas {
public:
Tas();
~Tas();
// Changes the input status that will be stored in each frame
void RecordInput(u32 buttons, const std::array<std::pair<float, float>, 2>& axes);
// Main loop that records or executes input
void UpdateThread();
// Sets the flag to start or stop the TAS command excecution and swaps controllers profiles
void StartStop();
// Stop the TAS and reverts any controller profile
void Stop();
// Sets the flag to reload the file and start from the begining in the next update
void Reset();
/**
* Sets the flag to enable or disable recording of inputs
* @return Returns true if the current recording status is enabled
*/
bool Record();
// Saves contents of record_commands on a file if overwrite is enabled player 1 will be
// overwritten with the recorded commands
void SaveRecording(bool overwrite_file);
/**
* Returns the current status values of TAS playback/recording
* @return Tuple of
* TasState indicating the current state out of Running, Recording or Stopped ;
* Current playback progress or amount of frames (so far) for Recording ;
* Total length of script file currently loaded or amount of frames (so far) for Recording
*/
std::tuple<TasState, size_t, size_t> GetStatus() const;
// Retuns an array of the default button mappings
InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const;
// Retuns an array of the default analog mappings
InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const;
[[nodiscard]] const TasData& GetTasState(std::size_t pad) const;
private:
struct TASCommand {
u32 buttons{};
TasAnalog l_axis{};
TasAnalog r_axis{};
};
// Loads TAS files from all players
void LoadTasFiles();
// Loads TAS file from the specified player
void LoadTasFile(size_t player_index);
// Writes a TAS file from the recorded commands
void WriteTasFile(std::u8string file_name);
/**
* Parses a string containing the axis values with the following format "x;y"
* X and Y have a range from -32767 to 32767
* @return Returns a TAS analog object with axis values with range from -1.0 to 1.0
*/
TasAnalog ReadCommandAxis(const std::string& line) const;
/**
* Parses a string containing the button values with the following format "a;b;c;d..."
* Each button is represented by it's text format specified in text_to_tas_button array
* @return Returns a u32 with each bit representing the status of a button
*/
u32 ReadCommandButtons(const std::string& line) const;
/**
* Converts an u32 containing the button status into the text equivalent
* @return Returns a string with the name of the buttons to be written to the file
*/
std::string WriteCommandButtons(u32 data) const;
/**
* Converts an TAS analog object containing the axis status into the text equivalent
* @return Returns a string with the value of the axis to be written to the file
*/
std::string WriteCommandAxis(TasAnalog data) const;
// Inverts the Y axis polarity
std::pair<float, float> FlipAxisY(std::pair<float, float> old);
/**
* Converts an u32 containing the button status into the text equivalent
* @return Returns a string with the name of the buttons to be printed on console
*/
std::string DebugButtons(u32 buttons) const;
/**
* Converts an TAS analog object containing the axis status into the text equivalent
* @return Returns a string with the value of the axis to be printed on console
*/
std::string DebugJoystick(float x, float y) const;
/**
* Converts the given TAS status into the text equivalent
* @return Returns a string with the value of the TAS status to be printed on console
*/
std::string DebugInput(const TasData& data) const;
/**
* Converts the given TAS status of multiple players into the text equivalent
* @return Returns a string with the value of the status of all TAS players to be printed on
* console
*/
std::string DebugInputs(const std::array<TasData, PLAYER_NUMBER>& arr) const;
/**
* Converts an u32 containing the button status into the text equivalent
* @return Returns a string with the name of the buttons
*/
std::string ButtonsToString(u32 button) const;
// Stores current controller configuration and sets a TAS controller for every active controller
// to the current config
void SwapToTasController();
// Sets the stored controller configuration to the current config
void SwapToStoredController();
size_t script_length{0};
std::array<TasData, PLAYER_NUMBER> tas_data;
bool is_old_input_saved{false};
bool is_recording{false};
bool is_running{false};
bool needs_reset{false};
std::array<std::vector<TASCommand>, PLAYER_NUMBER> commands{};
std::vector<TASCommand> record_commands{};
size_t current_command{0};
TASCommand last_input{}; // only used for recording
// Old settings for swapping controllers
std::array<Settings::PlayerInput, 10> player_mappings;
};
} // namespace TasInput

View File

@@ -0,0 +1,101 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <mutex>
#include <utility>
#include "common/settings.h"
#include "common/threadsafe_queue.h"
#include "input_common/tas/tas_input.h"
#include "input_common/tas/tas_poller.h"
namespace InputCommon {
class TasButton final : public Input::ButtonDevice {
public:
explicit TasButton(u32 button_, u32 pad_, const TasInput::Tas* tas_input_)
: button(button_), pad(pad_), tas_input(tas_input_) {}
bool GetStatus() const override {
return (tas_input->GetTasState(pad).buttons & button) != 0;
}
private:
const u32 button;
const u32 pad;
const TasInput::Tas* tas_input;
};
TasButtonFactory::TasButtonFactory(std::shared_ptr<TasInput::Tas> tas_input_)
: tas_input(std::move(tas_input_)) {}
std::unique_ptr<Input::ButtonDevice> TasButtonFactory::Create(const Common::ParamPackage& params) {
const auto button_id = params.Get("button", 0);
const auto pad = params.Get("pad", 0);
return std::make_unique<TasButton>(button_id, pad, tas_input.get());
}
class TasAnalog final : public Input::AnalogDevice {
public:
explicit TasAnalog(u32 pad_, u32 axis_x_, u32 axis_y_, const TasInput::Tas* tas_input_)
: pad(pad_), axis_x(axis_x_), axis_y(axis_y_), tas_input(tas_input_) {}
float GetAxis(u32 axis) const {
std::lock_guard lock{mutex};
return tas_input->GetTasState(pad).axis.at(axis);
}
std::pair<float, float> GetAnalog(u32 analog_axis_x, u32 analog_axis_y) const {
float x = GetAxis(analog_axis_x);
float y = GetAxis(analog_axis_y);
// Make sure the coordinates are in the unit circle,
// otherwise normalize it.
float r = x * x + y * y;
if (r > 1.0f) {
r = std::sqrt(r);
x /= r;
y /= r;
}
return {x, y};
}
std::tuple<float, float> GetStatus() const override {
return GetAnalog(axis_x, axis_y);
}
Input::AnalogProperties GetAnalogProperties() const override {
return {0.0f, 1.0f, 0.5f};
}
private:
const u32 pad;
const u32 axis_x;
const u32 axis_y;
const TasInput::Tas* tas_input;
mutable std::mutex mutex;
};
/// An analog device factory that creates analog devices from GC Adapter
TasAnalogFactory::TasAnalogFactory(std::shared_ptr<TasInput::Tas> tas_input_)
: tas_input(std::move(tas_input_)) {}
/**
* Creates analog device from joystick axes
* @param params contains parameters for creating the device:
* - "port": the nth gcpad on the adapter
* - "axis_x": the index of the axis to be bind as x-axis
* - "axis_y": the index of the axis to be bind as y-axis
*/
std::unique_ptr<Input::AnalogDevice> TasAnalogFactory::Create(const Common::ParamPackage& params) {
const auto pad = static_cast<u32>(params.Get("pad", 0));
const auto axis_x = static_cast<u32>(params.Get("axis_x", 0));
const auto axis_y = static_cast<u32>(params.Get("axis_y", 1));
return std::make_unique<TasAnalog>(pad, axis_x, axis_y, tas_input.get());
}
} // namespace InputCommon

View File

@@ -0,0 +1,43 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include "core/frontend/input.h"
#include "input_common/tas/tas_input.h"
namespace InputCommon {
/**
* A button device factory representing a tas bot. It receives tas events and forward them
* to all button devices it created.
*/
class TasButtonFactory final : public Input::Factory<Input::ButtonDevice> {
public:
explicit TasButtonFactory(std::shared_ptr<TasInput::Tas> tas_input_);
/**
* Creates a button device from a button press
* @param params contains parameters for creating the device:
* - "code": the code of the key to bind with the button
*/
std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override;
private:
std::shared_ptr<TasInput::Tas> tas_input;
};
/// An analog device factory that creates analog devices from tas
class TasAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
public:
explicit TasAnalogFactory(std::shared_ptr<TasInput::Tas> tas_input_);
std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override;
private:
std::shared_ptr<TasInput::Tas> tas_input;
};
} // namespace InputCommon

View File

@@ -21,8 +21,6 @@
namespace InputCommon::CemuhookUDP {
constexpr char DEFAULT_SRV[] = "127.0.0.1:26760";
class Socket;
namespace Response {

View File

@@ -11,6 +11,8 @@
namespace Shader::Backend::GLSL {
namespace {
constexpr char THREAD_ID[]{"gl_SubGroupInvocationARB"};
void SetInBoundsFlag(EmitContext& ctx, IR::Inst& inst) {
IR::Inst* const in_bounds{inst.GetAssociatedPseudoOperation(IR::Opcode::GetInBoundsFromOp)};
if (!in_bounds) {
@@ -43,84 +45,100 @@ void UseShuffleNv(EmitContext& ctx, IR::Inst& inst, std::string_view shfl_op,
ctx.AddU32("{}={}({},{},{},shfl_in_bounds);", inst, shfl_op, value, index, width);
SetInBoundsFlag(ctx, inst);
}
std::string_view BallotIndex(EmitContext& ctx) {
if (!ctx.profile.warp_size_potentially_larger_than_guest) {
return ".x";
}
return "[gl_SubGroupInvocationARB>>5]";
}
std::string GetMask(EmitContext& ctx, std::string_view mask) {
const auto ballot_index{BallotIndex(ctx)};
return fmt::format("uint(uvec2({}){})", mask, ballot_index);
}
} // Anonymous namespace
void EmitLaneId(EmitContext& ctx, IR::Inst& inst) {
ctx.AddU32("{}=gl_SubGroupInvocationARB&31u;", inst);
ctx.AddU32("{}={}&31u;", inst, THREAD_ID);
}
void EmitVoteAll(EmitContext& ctx, IR::Inst& inst, std::string_view pred) {
if (!ctx.profile.warp_size_potentially_larger_than_guest) {
ctx.AddU1("{}=allInvocationsEqualARB({});", inst, pred);
} else {
const auto active_mask{fmt::format("uvec2(ballotARB(true))[gl_SubGroupInvocationARB]")};
const auto ballot{fmt::format("uvec2(ballotARB({}))[gl_SubGroupInvocationARB]", pred)};
ctx.AddU1("{}=({}&{})=={};", inst, ballot, active_mask, active_mask);
return;
}
const auto ballot_index{BallotIndex(ctx)};
const auto active_mask{fmt::format("uvec2(ballotARB(true)){}", ballot_index)};
const auto ballot{fmt::format("uvec2(ballotARB({})){}", pred, ballot_index)};
ctx.AddU1("{}=({}&{})=={};", inst, ballot, active_mask, active_mask);
}
void EmitVoteAny(EmitContext& ctx, IR::Inst& inst, std::string_view pred) {
if (!ctx.profile.warp_size_potentially_larger_than_guest) {
ctx.AddU1("{}=anyInvocationARB({});", inst, pred);
} else {
const auto active_mask{fmt::format("uvec2(ballotARB(true))[gl_SubGroupInvocationARB]")};
const auto ballot{fmt::format("uvec2(ballotARB({}))[gl_SubGroupInvocationARB]", pred)};
ctx.AddU1("{}=({}&{})!=0u;", inst, ballot, active_mask, active_mask);
return;
}
const auto ballot_index{BallotIndex(ctx)};
const auto active_mask{fmt::format("uvec2(ballotARB(true)){}", ballot_index)};
const auto ballot{fmt::format("uvec2(ballotARB({})){}", pred, ballot_index)};
ctx.AddU1("{}=({}&{})!=0u;", inst, ballot, active_mask, active_mask);
}
void EmitVoteEqual(EmitContext& ctx, IR::Inst& inst, std::string_view pred) {
if (!ctx.profile.warp_size_potentially_larger_than_guest) {
ctx.AddU1("{}=allInvocationsEqualARB({});", inst, pred);
} else {
const auto active_mask{fmt::format("uvec2(ballotARB(true))[gl_SubGroupInvocationARB]")};
const auto ballot{fmt::format("uvec2(ballotARB({}))[gl_SubGroupInvocationARB]", pred)};
const auto value{fmt::format("({}^{})", ballot, active_mask)};
ctx.AddU1("{}=({}==0)||({}=={});", inst, value, value, active_mask);
return;
}
const auto ballot_index{BallotIndex(ctx)};
const auto active_mask{fmt::format("uvec2(ballotARB(true)){}", ballot_index)};
const auto ballot{fmt::format("uvec2(ballotARB({})){}", pred, ballot_index)};
const auto value{fmt::format("({}^{})", ballot, active_mask)};
ctx.AddU1("{}=({}==0)||({}=={});", inst, value, value, active_mask);
}
void EmitSubgroupBallot(EmitContext& ctx, IR::Inst& inst, std::string_view pred) {
if (!ctx.profile.warp_size_potentially_larger_than_guest) {
ctx.AddU32("{}=uvec2(ballotARB({})).x;", inst, pred);
} else {
ctx.AddU32("{}=uvec2(ballotARB({}))[gl_SubGroupInvocationARB];", inst, pred);
}
const auto ballot_index{BallotIndex(ctx)};
ctx.AddU32("{}=uvec2(ballotARB({})){};", inst, pred, ballot_index);
}
void EmitSubgroupEqMask(EmitContext& ctx, IR::Inst& inst) {
ctx.AddU32("{}=uint(gl_SubGroupEqMaskARB.x);", inst);
ctx.AddU32("{}={};", inst, GetMask(ctx, "gl_SubGroupEqMaskARB"));
}
void EmitSubgroupLtMask(EmitContext& ctx, IR::Inst& inst) {
ctx.AddU32("{}=uint(gl_SubGroupLtMaskARB.x);", inst);
ctx.AddU32("{}={};", inst, GetMask(ctx, "gl_SubGroupLtMaskARB"));
}
void EmitSubgroupLeMask(EmitContext& ctx, IR::Inst& inst) {
ctx.AddU32("{}=uint(gl_SubGroupLeMaskARB.x);", inst);
ctx.AddU32("{}={};", inst, GetMask(ctx, "gl_SubGroupLeMaskARB"));
}
void EmitSubgroupGtMask(EmitContext& ctx, IR::Inst& inst) {
ctx.AddU32("{}=uint(gl_SubGroupGtMaskARB.x);", inst);
ctx.AddU32("{}={};", inst, GetMask(ctx, "gl_SubGroupGtMaskARB"));
}
void EmitSubgroupGeMask(EmitContext& ctx, IR::Inst& inst) {
ctx.AddU32("{}=uint(gl_SubGroupGeMaskARB.x);", inst);
ctx.AddU32("{}={};", inst, GetMask(ctx, "gl_SubGroupGeMaskARB"));
}
void EmitShuffleIndex(EmitContext& ctx, IR::Inst& inst, std::string_view value,
std::string_view index, std::string_view clamp,
std::string_view segmentation_mask) {
std::string_view index, std::string_view clamp, std::string_view seg_mask) {
if (ctx.profile.support_gl_warp_intrinsics) {
UseShuffleNv(ctx, inst, "shuffleNV", value, index, clamp, segmentation_mask);
UseShuffleNv(ctx, inst, "shuffleNV", value, index, clamp, seg_mask);
return;
}
const auto not_seg_mask{fmt::format("(~{})", segmentation_mask)};
const auto thread_id{"gl_SubGroupInvocationARB"};
const auto min_thread_id{ComputeMinThreadId(thread_id, segmentation_mask)};
const auto max_thread_id{ComputeMaxThreadId(min_thread_id, clamp, not_seg_mask)};
const bool big_warp{ctx.profile.warp_size_potentially_larger_than_guest};
const auto is_upper_partition{"int(gl_SubGroupInvocationARB)>=32"};
const auto upper_index{fmt::format("{}?{}+32:{}", is_upper_partition, index, index)};
const auto upper_clamp{fmt::format("{}?{}+32:{}", is_upper_partition, clamp, clamp)};
const auto lhs{fmt::format("({}&{})", index, not_seg_mask)};
const auto not_seg_mask{fmt::format("(~{})", seg_mask)};
const auto min_thread_id{ComputeMinThreadId(THREAD_ID, seg_mask)};
const auto max_thread_id{
ComputeMaxThreadId(min_thread_id, big_warp ? upper_clamp : clamp, not_seg_mask)};
const auto lhs{fmt::format("({}&{})", big_warp ? upper_index : index, not_seg_mask)};
const auto src_thread_id{fmt::format("({})|({})", lhs, min_thread_id)};
ctx.Add("shfl_in_bounds=int({})<=int({});", src_thread_id, max_thread_id);
SetInBoundsFlag(ctx, inst);
@@ -128,29 +146,34 @@ void EmitShuffleIndex(EmitContext& ctx, IR::Inst& inst, std::string_view value,
}
void EmitShuffleUp(EmitContext& ctx, IR::Inst& inst, std::string_view value, std::string_view index,
std::string_view clamp, std::string_view segmentation_mask) {
std::string_view clamp, std::string_view seg_mask) {
if (ctx.profile.support_gl_warp_intrinsics) {
UseShuffleNv(ctx, inst, "shuffleUpNV", value, index, clamp, segmentation_mask);
UseShuffleNv(ctx, inst, "shuffleUpNV", value, index, clamp, seg_mask);
return;
}
const auto thread_id{"gl_SubGroupInvocationARB"};
const auto max_thread_id{GetMaxThreadId(thread_id, clamp, segmentation_mask)};
const auto src_thread_id{fmt::format("({}-{})", thread_id, index)};
const bool big_warp{ctx.profile.warp_size_potentially_larger_than_guest};
const auto is_upper_partition{"int(gl_SubGroupInvocationARB)>=32"};
const auto upper_clamp{fmt::format("{}?{}+32:{}", is_upper_partition, clamp, clamp)};
const auto max_thread_id{GetMaxThreadId(THREAD_ID, big_warp ? upper_clamp : clamp, seg_mask)};
const auto src_thread_id{fmt::format("({}-{})", THREAD_ID, index)};
ctx.Add("shfl_in_bounds=int({})>=int({});", src_thread_id, max_thread_id);
SetInBoundsFlag(ctx, inst);
ctx.AddU32("{}=shfl_in_bounds?readInvocationARB({},{}):{};", inst, value, src_thread_id, value);
}
void EmitShuffleDown(EmitContext& ctx, IR::Inst& inst, std::string_view value,
std::string_view index, std::string_view clamp,
std::string_view segmentation_mask) {
std::string_view index, std::string_view clamp, std::string_view seg_mask) {
if (ctx.profile.support_gl_warp_intrinsics) {
UseShuffleNv(ctx, inst, "shuffleDownNV", value, index, clamp, segmentation_mask);
UseShuffleNv(ctx, inst, "shuffleDownNV", value, index, clamp, seg_mask);
return;
}
const auto thread_id{"gl_SubGroupInvocationARB"};
const auto max_thread_id{GetMaxThreadId(thread_id, clamp, segmentation_mask)};
const auto src_thread_id{fmt::format("({}+{})", thread_id, index)};
const bool big_warp{ctx.profile.warp_size_potentially_larger_than_guest};
const auto is_upper_partition{"int(gl_SubGroupInvocationARB)>=32"};
const auto upper_clamp{fmt::format("{}?{}+32:{}", is_upper_partition, clamp, clamp)};
const auto max_thread_id{GetMaxThreadId(THREAD_ID, big_warp ? upper_clamp : clamp, seg_mask)};
const auto src_thread_id{fmt::format("({}+{})", THREAD_ID, index)};
ctx.Add("shfl_in_bounds=int({})<=int({});", src_thread_id, max_thread_id);
SetInBoundsFlag(ctx, inst);
ctx.AddU32("{}=shfl_in_bounds?readInvocationARB({},{}):{};", inst, value, src_thread_id, value);
@@ -158,14 +181,17 @@ void EmitShuffleDown(EmitContext& ctx, IR::Inst& inst, std::string_view value,
void EmitShuffleButterfly(EmitContext& ctx, IR::Inst& inst, std::string_view value,
std::string_view index, std::string_view clamp,
std::string_view segmentation_mask) {
std::string_view seg_mask) {
if (ctx.profile.support_gl_warp_intrinsics) {
UseShuffleNv(ctx, inst, "shuffleXorNV", value, index, clamp, segmentation_mask);
UseShuffleNv(ctx, inst, "shuffleXorNV", value, index, clamp, seg_mask);
return;
}
const auto thread_id{"gl_SubGroupInvocationARB"};
const auto max_thread_id{GetMaxThreadId(thread_id, clamp, segmentation_mask)};
const auto src_thread_id{fmt::format("({}^{})", thread_id, index)};
const bool big_warp{ctx.profile.warp_size_potentially_larger_than_guest};
const auto is_upper_partition{"int(gl_SubGroupInvocationARB)>=32"};
const auto upper_clamp{fmt::format("{}?{}+32:{}", is_upper_partition, clamp, clamp)};
const auto max_thread_id{GetMaxThreadId(THREAD_ID, big_warp ? upper_clamp : clamp, seg_mask)};
const auto src_thread_id{fmt::format("({}^{})", THREAD_ID, index)};
ctx.Add("shfl_in_bounds=int({})<=int({});", src_thread_id, max_thread_id);
SetInBoundsFlag(ctx, inst);
ctx.AddU32("{}=shfl_in_bounds?readInvocationARB({},{}):{};", inst, value, src_thread_id, value);

View File

@@ -15,6 +15,8 @@
namespace Shader::Backend::SPIRV {
namespace {
constexpr size_t NUM_FIXEDFNCTEXTURE = 10;
enum class Operation {
Increment,
Decrement,
@@ -427,6 +429,16 @@ Id DescType(EmitContext& ctx, Id sampled_type, Id pointer_type, u32 count) {
return pointer_type;
}
}
size_t FindNextUnusedLocation(const std::bitset<IR::NUM_GENERICS>& used_locations,
size_t start_offset) {
for (size_t location = start_offset; location < used_locations.size(); ++location) {
if (!used_locations.test(location)) {
return location;
}
}
throw RuntimeError("Unable to get an unused location for legacy attribute");
}
} // Anonymous namespace
void VectorTypes::Define(Sirit::Module& sirit_ctx, Id base_type, std::string_view name) {
@@ -1227,6 +1239,7 @@ void EmitContext::DefineInputs(const IR::Program& program) {
loads[IR::Attribute::TessellationEvaluationPointV]) {
tess_coord = DefineInput(*this, F32[3], false, spv::BuiltIn::TessCoord);
}
std::bitset<IR::NUM_GENERICS> used_locations{};
for (size_t index = 0; index < IR::NUM_GENERICS; ++index) {
const AttributeType input_type{runtime_info.generic_input_types[index]};
if (!runtime_info.previous_stage_stores.Generic(index)) {
@@ -1238,6 +1251,7 @@ void EmitContext::DefineInputs(const IR::Program& program) {
if (input_type == AttributeType::Disabled) {
continue;
}
used_locations.set(index);
const Id type{GetAttributeType(*this, input_type)};
const Id id{DefineInput(*this, type, true)};
Decorate(id, spv::Decoration::Location, static_cast<u32>(index));
@@ -1263,6 +1277,26 @@ void EmitContext::DefineInputs(const IR::Program& program) {
break;
}
}
size_t previous_unused_location = 0;
if (loads.AnyComponent(IR::Attribute::ColorFrontDiffuseR)) {
const size_t location = FindNextUnusedLocation(used_locations, previous_unused_location);
previous_unused_location = location;
used_locations.set(location);
const Id id{DefineInput(*this, F32[4], true)};
Decorate(id, spv::Decoration::Location, location);
input_front_color = id;
}
for (size_t index = 0; index < NUM_FIXEDFNCTEXTURE; ++index) {
if (loads.AnyComponent(IR::Attribute::FixedFncTexture0S + index * 4)) {
const size_t location =
FindNextUnusedLocation(used_locations, previous_unused_location);
previous_unused_location = location;
used_locations.set(location);
const Id id{DefineInput(*this, F32[4], true)};
Decorate(id, spv::Decoration::Location, location);
input_fixed_fnc_textures[index] = id;
}
}
if (stage == Stage::TessellationEval) {
for (size_t index = 0; index < info.uses_patches.size(); ++index) {
if (!info.uses_patches[index]) {
@@ -1313,9 +1347,31 @@ void EmitContext::DefineOutputs(const IR::Program& program) {
viewport_mask = DefineOutput(*this, TypeArray(U32[1], Const(1u)), std::nullopt,
spv::BuiltIn::ViewportMaskNV);
}
std::bitset<IR::NUM_GENERICS> used_locations{};
for (size_t index = 0; index < IR::NUM_GENERICS; ++index) {
if (info.stores.Generic(index)) {
DefineGenericOutput(*this, index, invocations);
used_locations.set(index);
}
}
size_t previous_unused_location = 0;
if (info.stores.AnyComponent(IR::Attribute::ColorFrontDiffuseR)) {
const size_t location = FindNextUnusedLocation(used_locations, previous_unused_location);
previous_unused_location = location;
used_locations.set(location);
const Id id{DefineOutput(*this, F32[4], invocations)};
Decorate(id, spv::Decoration::Location, static_cast<u32>(location));
output_front_color = id;
}
for (size_t index = 0; index < NUM_FIXEDFNCTEXTURE; ++index) {
if (info.stores.AnyComponent(IR::Attribute::FixedFncTexture0S + index * 4)) {
const size_t location =
FindNextUnusedLocation(used_locations, previous_unused_location);
previous_unused_location = location;
used_locations.set(location);
const Id id{DefineOutput(*this, F32[4], invocations)};
Decorate(id, spv::Decoration::Location, location);
output_fixed_fnc_textures[index] = id;
}
}
switch (stage) {

View File

@@ -268,10 +268,14 @@ public:
Id write_global_func_u32x4{};
Id input_position{};
Id input_front_color{};
std::array<Id, 10> input_fixed_fnc_textures{};
std::array<Id, 32> input_generics{};
Id output_point_size{};
Id output_position{};
Id output_front_color{};
std::array<Id, 10> output_fixed_fnc_textures{};
std::array<std::array<GenericElementInfo, 4>, 32> output_generics{};
Id output_tess_level_outer{};

View File

@@ -43,6 +43,25 @@ Id AttrPointer(EmitContext& ctx, Id pointer_type, Id vertex, Id base, Args&&...
}
}
bool IsFixedFncTexture(IR::Attribute attribute) {
return attribute >= IR::Attribute::FixedFncTexture0S &&
attribute <= IR::Attribute::FixedFncTexture9Q;
}
u32 FixedFncTextureAttributeIndex(IR::Attribute attribute) {
if (!IsFixedFncTexture(attribute)) {
throw InvalidArgument("Attribute {} is not a FixedFncTexture", attribute);
}
return (static_cast<u32>(attribute) - static_cast<u32>(IR::Attribute::FixedFncTexture0S)) / 4u;
}
u32 FixedFncTextureAttributeElement(IR::Attribute attribute) {
if (!IsFixedFncTexture(attribute)) {
throw InvalidArgument("Attribute {} is not a FixedFncTexture", attribute);
}
return static_cast<u32>(attribute) % 4u;
}
template <typename... Args>
Id OutputAccessChain(EmitContext& ctx, Id result_type, Id base, Args&&... args) {
if (ctx.stage == Stage::TessellationControl) {
@@ -74,6 +93,13 @@ std::optional<OutAttr> OutputAttrPointer(EmitContext& ctx, IR::Attribute attr) {
return OutputAccessChain(ctx, ctx.output_f32, info.id, index_id);
}
}
if (IsFixedFncTexture(attr)) {
const u32 index{FixedFncTextureAttributeIndex(attr)};
const u32 element{FixedFncTextureAttributeElement(attr)};
const Id element_id{ctx.Const(element)};
return OutputAccessChain(ctx, ctx.output_f32, ctx.output_fixed_fnc_textures[index],
element_id);
}
switch (attr) {
case IR::Attribute::PointSize:
return ctx.output_point_size;
@@ -85,6 +111,14 @@ std::optional<OutAttr> OutputAttrPointer(EmitContext& ctx, IR::Attribute attr) {
const Id element_id{ctx.Const(element)};
return OutputAccessChain(ctx, ctx.output_f32, ctx.output_position, element_id);
}
case IR::Attribute::ColorFrontDiffuseR:
case IR::Attribute::ColorFrontDiffuseG:
case IR::Attribute::ColorFrontDiffuseB:
case IR::Attribute::ColorFrontDiffuseA: {
const u32 element{static_cast<u32>(attr) % 4};
const Id element_id{ctx.Const(element)};
return OutputAccessChain(ctx, ctx.output_f32, ctx.output_front_color, element_id);
}
case IR::Attribute::ClipDistance0:
case IR::Attribute::ClipDistance1:
case IR::Attribute::ClipDistance2:
@@ -307,6 +341,12 @@ Id EmitGetAttribute(EmitContext& ctx, IR::Attribute attr, Id vertex) {
const Id value{ctx.OpLoad(type->id, pointer)};
return type->needs_cast ? ctx.OpBitcast(ctx.F32[1], value) : value;
}
if (IsFixedFncTexture(attr)) {
const u32 index{FixedFncTextureAttributeIndex(attr)};
const Id attr_id{ctx.input_fixed_fnc_textures[index]};
const Id attr_ptr{AttrPointer(ctx, ctx.input_f32, vertex, attr_id, ctx.Const(element))};
return ctx.OpLoad(ctx.F32[1], attr_ptr);
}
switch (attr) {
case IR::Attribute::PrimitiveId:
return ctx.OpBitcast(ctx.F32[1], ctx.OpLoad(ctx.U32[1], ctx.primitive_id));
@@ -316,6 +356,13 @@ Id EmitGetAttribute(EmitContext& ctx, IR::Attribute attr, Id vertex) {
case IR::Attribute::PositionW:
return ctx.OpLoad(ctx.F32[1], AttrPointer(ctx, ctx.input_f32, vertex, ctx.input_position,
ctx.Const(element)));
case IR::Attribute::ColorFrontDiffuseR:
case IR::Attribute::ColorFrontDiffuseG:
case IR::Attribute::ColorFrontDiffuseB:
case IR::Attribute::ColorFrontDiffuseA: {
return ctx.OpLoad(ctx.F32[1], AttrPointer(ctx, ctx.input_f32, vertex, ctx.input_front_color,
ctx.Const(element)));
}
case IR::Attribute::InstanceId:
if (ctx.profile.support_vertex_instance_id) {
return ctx.OpBitcast(ctx.F32[1], ctx.OpLoad(ctx.U32[1], ctx.instance_id));

View File

@@ -7,8 +7,13 @@
namespace Shader::Backend::SPIRV {
namespace {
Id GetThreadId(EmitContext& ctx) {
return ctx.OpLoad(ctx.U32[1], ctx.subgroup_local_invocation_id);
}
Id WarpExtract(EmitContext& ctx, Id value) {
const Id local_index{ctx.OpLoad(ctx.U32[1], ctx.subgroup_local_invocation_id)};
const Id thread_id{GetThreadId(ctx)};
const Id local_index{ctx.OpShiftRightArithmetic(ctx.U32[1], thread_id, ctx.Const(5U))};
return ctx.OpVectorExtractDynamic(ctx.U32[1], value, local_index);
}
@@ -48,10 +53,17 @@ Id SelectValue(EmitContext& ctx, Id in_range, Id value, Id src_thread_id) {
return ctx.OpSelect(ctx.U32[1], in_range,
ctx.OpSubgroupReadInvocationKHR(ctx.U32[1], value, src_thread_id), value);
}
Id GetUpperClamp(EmitContext& ctx, Id invocation_id, Id clamp) {
const Id thirty_two{ctx.Const(32u)};
const Id is_upper_partition{ctx.OpSGreaterThanEqual(ctx.U1, invocation_id, thirty_two)};
const Id upper_clamp{ctx.OpIAdd(ctx.U32[1], thirty_two, clamp)};
return ctx.OpSelect(ctx.U32[1], is_upper_partition, upper_clamp, clamp);
}
} // Anonymous namespace
Id EmitLaneId(EmitContext& ctx) {
const Id id{ctx.OpLoad(ctx.U32[1], ctx.subgroup_local_invocation_id)};
const Id id{GetThreadId(ctx)};
if (!ctx.profile.warp_size_potentially_larger_than_guest) {
return id;
}
@@ -123,7 +135,15 @@ Id EmitSubgroupGeMask(EmitContext& ctx) {
Id EmitShuffleIndex(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
Id segmentation_mask) {
const Id not_seg_mask{ctx.OpNot(ctx.U32[1], segmentation_mask)};
const Id thread_id{ctx.OpLoad(ctx.U32[1], ctx.subgroup_local_invocation_id)};
const Id thread_id{GetThreadId(ctx)};
if (ctx.profile.warp_size_potentially_larger_than_guest) {
const Id thirty_two{ctx.Const(32u)};
const Id is_upper_partition{ctx.OpSGreaterThanEqual(ctx.U1, thread_id, thirty_two)};
const Id upper_index{ctx.OpIAdd(ctx.U32[1], thirty_two, index)};
const Id upper_clamp{ctx.OpIAdd(ctx.U32[1], thirty_two, clamp)};
index = ctx.OpSelect(ctx.U32[1], is_upper_partition, upper_index, index);
clamp = ctx.OpSelect(ctx.U32[1], is_upper_partition, upper_clamp, clamp);
}
const Id min_thread_id{ComputeMinThreadId(ctx, thread_id, segmentation_mask)};
const Id max_thread_id{ComputeMaxThreadId(ctx, min_thread_id, clamp, not_seg_mask)};
@@ -137,7 +157,10 @@ Id EmitShuffleIndex(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id cla
Id EmitShuffleUp(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
Id segmentation_mask) {
const Id thread_id{ctx.OpLoad(ctx.U32[1], ctx.subgroup_local_invocation_id)};
const Id thread_id{GetThreadId(ctx)};
if (ctx.profile.warp_size_potentially_larger_than_guest) {
clamp = GetUpperClamp(ctx, thread_id, clamp);
}
const Id max_thread_id{GetMaxThreadId(ctx, thread_id, clamp, segmentation_mask)};
const Id src_thread_id{ctx.OpISub(ctx.U32[1], thread_id, index)};
const Id in_range{ctx.OpSGreaterThanEqual(ctx.U1, src_thread_id, max_thread_id)};
@@ -148,7 +171,10 @@ Id EmitShuffleUp(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
Id EmitShuffleDown(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
Id segmentation_mask) {
const Id thread_id{ctx.OpLoad(ctx.U32[1], ctx.subgroup_local_invocation_id)};
const Id thread_id{GetThreadId(ctx)};
if (ctx.profile.warp_size_potentially_larger_than_guest) {
clamp = GetUpperClamp(ctx, thread_id, clamp);
}
const Id max_thread_id{GetMaxThreadId(ctx, thread_id, clamp, segmentation_mask)};
const Id src_thread_id{ctx.OpIAdd(ctx.U32[1], thread_id, index)};
const Id in_range{ctx.OpSLessThanEqual(ctx.U1, src_thread_id, max_thread_id)};
@@ -159,7 +185,10 @@ Id EmitShuffleDown(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clam
Id EmitShuffleButterfly(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
Id segmentation_mask) {
const Id thread_id{ctx.OpLoad(ctx.U32[1], ctx.subgroup_local_invocation_id)};
const Id thread_id{GetThreadId(ctx)};
if (ctx.profile.warp_size_potentially_larger_than_guest) {
clamp = GetUpperClamp(ctx, thread_id, clamp);
}
const Id max_thread_id{GetMaxThreadId(ctx, thread_id, clamp, segmentation_mask)};
const Id src_thread_id{ctx.OpBitwiseXor(ctx.U32[1], thread_id, index)};
const Id in_range{ctx.OpSLessThanEqual(ctx.U1, src_thread_id, max_thread_id)};

View File

@@ -231,6 +231,7 @@ endif()
target_include_directories(video_core PRIVATE ${FFmpeg_INCLUDE_DIR})
target_link_libraries(video_core PRIVATE ${FFmpeg_LIBRARIES})
target_link_options(video_core PRIVATE ${FFmpeg_LDFLAGS})
add_dependencies(video_core host_shaders)
target_include_directories(video_core PRIVATE ${HOST_SHADERS_INCLUDE})

View File

@@ -5,6 +5,7 @@
#include <fstream>
#include <vector>
#include "common/assert.h"
#include "common/settings.h"
#include "video_core/command_classes/codecs/codec.h"
#include "video_core/command_classes/codecs/h264.h"
#include "video_core/command_classes/codecs/vp9.h"
@@ -16,44 +17,28 @@ extern "C" {
}
namespace Tegra {
#if defined(LIBVA_FOUND)
// Hardware acceleration code from FFmpeg/doc/examples/hw_decode.c originally under MIT license
namespace {
constexpr std::array<const char*, 2> VAAPI_DRIVERS = {
"i915",
"amdgpu",
};
constexpr AVPixelFormat PREFERRED_GPU_FMT = AV_PIX_FMT_NV12;
constexpr AVPixelFormat PREFERRED_CPU_FMT = AV_PIX_FMT_YUV420P;
AVPixelFormat GetHwFormat(AVCodecContext*, const AVPixelFormat* pix_fmts) {
void AVPacketDeleter(AVPacket* ptr) {
av_packet_free(&ptr);
}
using AVPacketPtr = std::unique_ptr<AVPacket, decltype(&AVPacketDeleter)>;
AVPixelFormat GetGpuFormat(AVCodecContext* av_codec_ctx, const AVPixelFormat* pix_fmts) {
for (const AVPixelFormat* p = pix_fmts; *p != AV_PIX_FMT_NONE; ++p) {
if (*p == AV_PIX_FMT_VAAPI) {
return AV_PIX_FMT_VAAPI;
if (*p == av_codec_ctx->pix_fmt) {
return av_codec_ctx->pix_fmt;
}
}
LOG_INFO(Service_NVDRV, "Could not find compatible GPU AV format, falling back to CPU");
return *pix_fmts;
}
bool CreateVaapiHwdevice(AVBufferRef** av_hw_device) {
AVDictionary* hwdevice_options = nullptr;
av_dict_set(&hwdevice_options, "connection_type", "drm", 0);
for (const auto& driver : VAAPI_DRIVERS) {
av_dict_set(&hwdevice_options, "kernel_driver", driver, 0);
const int hwdevice_error = av_hwdevice_ctx_create(av_hw_device, AV_HWDEVICE_TYPE_VAAPI,
nullptr, hwdevice_options, 0);
if (hwdevice_error >= 0) {
LOG_INFO(Service_NVDRV, "Using VA-API with {}", driver);
av_dict_free(&hwdevice_options);
return true;
}
LOG_DEBUG(Service_NVDRV, "VA-API av_hwdevice_ctx_create failed {}", hwdevice_error);
}
LOG_DEBUG(Service_NVDRV, "VA-API av_hwdevice_ctx_create failed for all drivers");
av_dict_free(&hwdevice_options);
return false;
av_buffer_unref(&av_codec_ctx->hw_device_ctx);
av_codec_ctx->pix_fmt = PREFERRED_CPU_FMT;
return PREFERRED_CPU_FMT;
}
} // namespace
#endif
void AVFrameDeleter(AVFrame* ptr) {
av_frame_free(&ptr);
@@ -68,56 +53,110 @@ Codec::~Codec() {
return;
}
// Free libav memory
avcodec_send_packet(av_codec_ctx, nullptr);
AVFrame* av_frame = av_frame_alloc();
avcodec_receive_frame(av_codec_ctx, av_frame);
avcodec_flush_buffers(av_codec_ctx);
av_frame_free(&av_frame);
avcodec_close(av_codec_ctx);
av_buffer_unref(&av_hw_device);
avcodec_free_context(&av_codec_ctx);
av_buffer_unref(&av_gpu_decoder);
}
void Codec::InitializeHwdec() {
// Prioritize integrated GPU to mitigate bandwidth bottlenecks
bool Codec::CreateGpuAvDevice() {
#if defined(LIBVA_FOUND)
if (CreateVaapiHwdevice(&av_hw_device)) {
const auto hw_device_ctx = av_buffer_ref(av_hw_device);
ASSERT_MSG(hw_device_ctx, "av_buffer_ref failed");
av_codec_ctx->hw_device_ctx = hw_device_ctx;
av_codec_ctx->get_format = GetHwFormat;
static constexpr std::array<const char*, 3> VAAPI_DRIVERS = {
"i915",
"iHD",
"amdgpu",
};
AVDictionary* hwdevice_options = nullptr;
av_dict_set(&hwdevice_options, "connection_type", "drm", 0);
for (const auto& driver : VAAPI_DRIVERS) {
av_dict_set(&hwdevice_options, "kernel_driver", driver, 0);
const int hwdevice_error = av_hwdevice_ctx_create(&av_gpu_decoder, AV_HWDEVICE_TYPE_VAAPI,
nullptr, hwdevice_options, 0);
if (hwdevice_error >= 0) {
LOG_INFO(Service_NVDRV, "Using VA-API with {}", driver);
av_dict_free(&hwdevice_options);
av_codec_ctx->pix_fmt = AV_PIX_FMT_VAAPI;
return true;
}
LOG_DEBUG(Service_NVDRV, "VA-API av_hwdevice_ctx_create failed {}", hwdevice_error);
}
LOG_DEBUG(Service_NVDRV, "VA-API av_hwdevice_ctx_create failed for all drivers");
av_dict_free(&hwdevice_options);
#endif
static constexpr auto HW_CONFIG_METHOD = AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX;
static constexpr std::array GPU_DECODER_TYPES{
AV_HWDEVICE_TYPE_CUDA,
#ifdef _WIN32
AV_HWDEVICE_TYPE_D3D11VA,
#else
AV_HWDEVICE_TYPE_VDPAU,
#endif
};
for (const auto& type : GPU_DECODER_TYPES) {
const int hwdevice_res = av_hwdevice_ctx_create(&av_gpu_decoder, type, nullptr, nullptr, 0);
if (hwdevice_res < 0) {
LOG_DEBUG(Service_NVDRV, "{} av_hwdevice_ctx_create failed {}",
av_hwdevice_get_type_name(type), hwdevice_res);
continue;
}
for (int i = 0;; i++) {
const AVCodecHWConfig* config = avcodec_get_hw_config(av_codec, i);
if (!config) {
LOG_DEBUG(Service_NVDRV, "{} decoder does not support device type {}.",
av_codec->name, av_hwdevice_get_type_name(type));
break;
}
if (config->methods & HW_CONFIG_METHOD && config->device_type == type) {
av_codec_ctx->pix_fmt = config->pix_fmt;
LOG_INFO(Service_NVDRV, "Using {} GPU decoder", av_hwdevice_get_type_name(type));
return true;
}
}
}
return false;
}
void Codec::InitializeAvCodecContext() {
av_codec_ctx = avcodec_alloc_context3(av_codec);
av_opt_set(av_codec_ctx->priv_data, "tune", "zerolatency", 0);
}
void Codec::InitializeGpuDecoder() {
if (!CreateGpuAvDevice()) {
av_buffer_unref(&av_gpu_decoder);
return;
}
#endif
// TODO more GPU accelerated decoders
auto* hw_device_ctx = av_buffer_ref(av_gpu_decoder);
ASSERT_MSG(hw_device_ctx, "av_buffer_ref failed");
av_codec_ctx->hw_device_ctx = hw_device_ctx;
av_codec_ctx->get_format = GetGpuFormat;
}
void Codec::Initialize() {
AVCodecID codec;
switch (current_codec) {
case NvdecCommon::VideoCodec::H264:
codec = AV_CODEC_ID_H264;
break;
case NvdecCommon::VideoCodec::Vp9:
codec = AV_CODEC_ID_VP9;
break;
default:
UNIMPLEMENTED_MSG("Unknown codec {}", current_codec);
const AVCodecID codec = [&] {
switch (current_codec) {
case NvdecCommon::VideoCodec::H264:
return AV_CODEC_ID_H264;
case NvdecCommon::VideoCodec::Vp9:
return AV_CODEC_ID_VP9;
default:
UNIMPLEMENTED_MSG("Unknown codec {}", current_codec);
return AV_CODEC_ID_NONE;
}
}();
av_codec = avcodec_find_decoder(codec);
InitializeAvCodecContext();
if (Settings::values.nvdec_emulation.GetValue() == Settings::NvdecEmulation::GPU) {
InitializeGpuDecoder();
}
if (const int res = avcodec_open2(av_codec_ctx, av_codec, nullptr); res < 0) {
LOG_ERROR(Service_NVDRV, "avcodec_open2() Failed with result {}", res);
avcodec_free_context(&av_codec_ctx);
av_buffer_unref(&av_gpu_decoder);
return;
}
av_codec = avcodec_find_decoder(codec);
av_codec_ctx = avcodec_alloc_context3(av_codec);
av_opt_set(av_codec_ctx->priv_data, "tune", "zerolatency", 0);
InitializeHwdec();
if (!av_codec_ctx->hw_device_ctx) {
LOG_INFO(Service_NVDRV, "Using FFmpeg software decoding");
}
const auto av_error = avcodec_open2(av_codec_ctx, av_codec, nullptr);
if (av_error < 0) {
LOG_ERROR(Service_NVDRV, "avcodec_open2() Failed.");
avcodec_close(av_codec_ctx);
av_buffer_unref(&av_hw_device);
return;
}
initialized = true;
}
@@ -133,6 +172,9 @@ void Codec::Decode() {
if (is_first_frame) {
Initialize();
}
if (!initialized) {
return;
}
bool vp9_hidden_frame = false;
std::vector<u8> frame_data;
if (current_codec == NvdecCommon::VideoCodec::H264) {
@@ -141,50 +183,48 @@ void Codec::Decode() {
frame_data = vp9_decoder->ComposeFrameHeader(state);
vp9_hidden_frame = vp9_decoder->WasFrameHidden();
}
AVPacket packet{};
av_init_packet(&packet);
packet.data = frame_data.data();
packet.size = static_cast<s32>(frame_data.size());
if (const int ret = avcodec_send_packet(av_codec_ctx, &packet); ret) {
LOG_DEBUG(Service_NVDRV, "avcodec_send_packet error {}", ret);
AVPacketPtr packet{av_packet_alloc(), AVPacketDeleter};
if (!packet) {
LOG_ERROR(Service_NVDRV, "av_packet_alloc failed");
return;
}
packet->data = frame_data.data();
packet->size = static_cast<s32>(frame_data.size());
if (const int res = avcodec_send_packet(av_codec_ctx, packet.get()); res != 0) {
LOG_DEBUG(Service_NVDRV, "avcodec_send_packet error {}", res);
return;
}
// Only receive/store visible frames
if (vp9_hidden_frame) {
return;
}
AVFrame* hw_frame = av_frame_alloc();
AVFrame* sw_frame = hw_frame;
ASSERT_MSG(hw_frame, "av_frame_alloc hw_frame failed");
if (const int ret = avcodec_receive_frame(av_codec_ctx, hw_frame); ret) {
AVFramePtr initial_frame{av_frame_alloc(), AVFrameDeleter};
AVFramePtr final_frame{nullptr, AVFrameDeleter};
ASSERT_MSG(initial_frame, "av_frame_alloc initial_frame failed");
if (const int ret = avcodec_receive_frame(av_codec_ctx, initial_frame.get()); ret) {
LOG_DEBUG(Service_NVDRV, "avcodec_receive_frame error {}", ret);
av_frame_free(&hw_frame);
return;
}
if (!hw_frame->width || !hw_frame->height) {
if (initial_frame->width == 0 || initial_frame->height == 0) {
LOG_WARNING(Service_NVDRV, "Zero width or height in frame");
av_frame_free(&hw_frame);
return;
}
#if defined(LIBVA_FOUND)
// Hardware acceleration code from FFmpeg/doc/examples/hw_decode.c under MIT license
if (hw_frame->format == AV_PIX_FMT_VAAPI) {
sw_frame = av_frame_alloc();
ASSERT_MSG(sw_frame, "av_frame_alloc sw_frame failed");
if (av_codec_ctx->hw_device_ctx) {
final_frame = AVFramePtr{av_frame_alloc(), AVFrameDeleter};
ASSERT_MSG(final_frame, "av_frame_alloc final_frame failed");
// Can't use AV_PIX_FMT_YUV420P and share code with software decoding in vic.cpp
// because Intel drivers crash unless using AV_PIX_FMT_NV12
sw_frame->format = AV_PIX_FMT_NV12;
const int transfer_data_ret = av_hwframe_transfer_data(sw_frame, hw_frame, 0);
ASSERT_MSG(!transfer_data_ret, "av_hwframe_transfer_data error {}", transfer_data_ret);
av_frame_free(&hw_frame);
final_frame->format = PREFERRED_GPU_FMT;
const int ret = av_hwframe_transfer_data(final_frame.get(), initial_frame.get(), 0);
ASSERT_MSG(!ret, "av_hwframe_transfer_data error {}", ret);
} else {
final_frame = std::move(initial_frame);
}
#endif
if (sw_frame->format != AV_PIX_FMT_YUV420P && sw_frame->format != AV_PIX_FMT_NV12) {
UNIMPLEMENTED_MSG("Unexpected video format from host graphics: {}", sw_frame->format);
av_frame_free(&sw_frame);
if (final_frame->format != PREFERRED_CPU_FMT && final_frame->format != PREFERRED_GPU_FMT) {
UNIMPLEMENTED_MSG("Unexpected video format: {}", final_frame->format);
return;
}
av_frames.push(AVFramePtr{sw_frame, AVFrameDeleter});
av_frames.push(std::move(final_frame));
if (av_frames.size() > 10) {
LOG_TRACE(Service_NVDRV, "av_frames.push overflow dropped frame");
av_frames.pop();

View File

@@ -5,6 +5,7 @@
#pragma once
#include <memory>
#include <string_view>
#include <queue>
#include "common/common_types.h"
#include "video_core/command_classes/nvdec_common.h"
@@ -50,18 +51,23 @@ public:
/// Returns the value of current_codec
[[nodiscard]] NvdecCommon::VideoCodec GetCurrentCodec() const;
/// Return name of the current codec
[[nodiscard]] std::string_view GetCurrentCodecName() const;
private:
void InitializeHwdec();
void InitializeAvCodecContext();
void InitializeGpuDecoder();
bool CreateGpuAvDevice();
bool initialized{};
NvdecCommon::VideoCodec current_codec{NvdecCommon::VideoCodec::None};
AVCodec* av_codec{nullptr};
AVBufferRef* av_hw_device{nullptr};
AVCodecContext* av_codec_ctx{nullptr};
AVBufferRef* av_gpu_decoder{nullptr};
GPU& gpu;
const NvdecCommon::NvdecRegisters& state;

View File

@@ -95,7 +95,8 @@ const std::vector<u8>& H264::ComposeFrameHeader(const NvdecCommon::NvdecRegister
const s32 pic_height = context.h264_parameter_set.frame_height_in_map_units /
(context.h264_parameter_set.frame_mbs_only_flag ? 1 : 2);
writer.WriteUe(16);
// TODO (ameerj): Where do we get this number, it seems to be particular for each stream
writer.WriteUe(6); // Max number of reference frames
writer.WriteBit(false);
writer.WriteUe(context.h264_parameter_set.pic_width_in_mbs - 1);
writer.WriteUe(pic_height - 1);

View File

@@ -475,10 +475,10 @@ public:
// These values are used by Nouveau and some games.
AddGL = 0x8006,
SubtractGL = 0x8007,
ReverseSubtractGL = 0x8008,
MinGL = 0x800a,
MaxGL = 0x800b
MinGL = 0x8007,
MaxGL = 0x8008,
SubtractGL = 0x800a,
ReverseSubtractGL = 0x800b
};
enum class Factor : u32 {

View File

@@ -2,6 +2,8 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include "common/alignment.h"
#include "common/assert.h"
#include "common/logging/log.h"

View File

@@ -293,6 +293,8 @@ void ShaderCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading,
}};
LoadPipelines(stop_loading, shader_cache_filename, CACHE_VERSION, load_compute, load_graphics);
LOG_INFO(Render_OpenGL, "Total Pipeline Count: {}", state.total);
std::unique_lock lock{state.mutex};
callback(VideoCore::LoadCallbackStage::Build, 0, state.total);
state.has_loaded = true;

View File

@@ -358,7 +358,7 @@ void VKBlitScreen::CreateDescriptorPool() {
void VKBlitScreen::CreateRenderPass() {
const VkAttachmentDescription color_attachment{
.flags = 0,
.format = swapchain.GetImageFormat(),
.format = swapchain.GetImageViewFormat(),
.samples = VK_SAMPLE_COUNT_1_BIT,
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,

View File

@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <mutex>
#include <span>
#include <vector>
@@ -18,7 +19,6 @@ namespace Vulkan {
// Prefer small grow rates to avoid saturating the descriptor pool with barely used pipelines
constexpr size_t SETS_GROW_RATE = 16;
constexpr s32 SCORE_THRESHOLD = 3;
constexpr u32 SETS_PER_POOL = 64;
struct DescriptorBank {
DescriptorBankInfo info;
@@ -58,11 +58,12 @@ static DescriptorBankInfo MakeBankInfo(std::span<const Shader::Info> infos) {
static void AllocatePool(const Device& device, DescriptorBank& bank) {
std::array<VkDescriptorPoolSize, 6> pool_sizes;
size_t pool_cursor{};
const u32 sets_per_pool = device.GetSetsPerPool();
const auto add = [&](VkDescriptorType type, u32 count) {
if (count > 0) {
pool_sizes[pool_cursor++] = {
.type = type,
.descriptorCount = count * SETS_PER_POOL,
.descriptorCount = count * sets_per_pool,
};
}
};
@@ -77,7 +78,7 @@ static void AllocatePool(const Device& device, DescriptorBank& bank) {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
.pNext = nullptr,
.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
.maxSets = SETS_PER_POOL,
.maxSets = sets_per_pool,
.poolSizeCount = static_cast<u32>(pool_cursor),
.pPoolSizes = std::data(pool_sizes),
}));

View File

@@ -447,6 +447,8 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading
VideoCommon::LoadPipelines(stop_loading, pipeline_cache_filename, CACHE_VERSION, load_compute,
load_graphics);
LOG_INFO(Render_Vulkan, "Total Pipeline Count: {}", state.total);
std::unique_lock lock{state.mutex};
callback(VideoCore::LoadCallbackStage::Build, 0, state.total);
state.has_loaded = true;

View File

@@ -228,9 +228,7 @@ void RasterizerVulkan::Clear() {
};
const u32 color_attachment = regs.clear_buffers.RT;
const auto attachment_aspect_mask = framebuffer->ImageRanges()[color_attachment].aspectMask;
const bool is_color_rt = (attachment_aspect_mask & VK_IMAGE_ASPECT_COLOR_BIT) != 0;
if (use_color && is_color_rt) {
if (use_color && framebuffer->HasAspectColorBit(color_attachment)) {
VkClearValue clear_value;
std::memcpy(clear_value.color.float32, regs.clear_color, sizeof(regs.clear_color));
@@ -248,12 +246,15 @@ void RasterizerVulkan::Clear() {
return;
}
VkImageAspectFlags aspect_flags = 0;
if (use_depth) {
if (use_depth && framebuffer->HasAspectDepthBit()) {
aspect_flags |= VK_IMAGE_ASPECT_DEPTH_BIT;
}
if (use_stencil) {
if (use_stencil && framebuffer->HasAspectStencilBit()) {
aspect_flags |= VK_IMAGE_ASPECT_STENCIL_BIT;
}
if (aspect_flags == 0) {
return;
}
scheduler.Record([clear_depth = regs.clear_depth, clear_stencil = regs.clear_stencil,
clear_rect, aspect_flags](vk::CommandBuffer cmdbuf) {
VkClearAttachment attachment;
@@ -764,12 +765,7 @@ void RasterizerVulkan::UpdateStencilOp(Tegra::Engines::Maxwell3D::Regs& regs) {
const Maxwell::StencilOp zpass = regs.stencil_front_op_zpass;
const Maxwell::ComparisonOp compare = regs.stencil_front_func_func;
if (regs.stencil_two_side_enable) {
scheduler.Record([fail, zfail, zpass, compare](vk::CommandBuffer cmdbuf) {
cmdbuf.SetStencilOpEXT(VK_STENCIL_FACE_FRONT_AND_BACK, MaxwellToVK::StencilOp(fail),
MaxwellToVK::StencilOp(zpass), MaxwellToVK::StencilOp(zfail),
MaxwellToVK::ComparisonOp(compare));
});
} else {
// Separate stencil op per face
const Maxwell::StencilOp back_fail = regs.stencil_back_op_fail;
const Maxwell::StencilOp back_zfail = regs.stencil_back_op_zfail;
const Maxwell::StencilOp back_zpass = regs.stencil_back_op_zpass;
@@ -784,6 +780,13 @@ void RasterizerVulkan::UpdateStencilOp(Tegra::Engines::Maxwell3D::Regs& regs) {
MaxwellToVK::StencilOp(back_zfail),
MaxwellToVK::ComparisonOp(back_compare));
});
} else {
// Front face defines the stencil op of both faces
scheduler.Record([fail, zfail, zpass, compare](vk::CommandBuffer cmdbuf) {
cmdbuf.SetStencilOpEXT(VK_STENCIL_FACE_FRONT_AND_BACK, MaxwellToVK::StencilOp(fail),
MaxwellToVK::StencilOp(zpass), MaxwellToVK::StencilOp(zfail),
MaxwellToVK::ComparisonOp(compare));
});
}
}

View File

@@ -110,10 +110,6 @@ public:
return Exchange(Dirty::DepthTestEnable, false);
}
bool TouchDepthBoundsEnable() {
return Exchange(Dirty::DepthBoundsEnable, false);
}
bool TouchDepthWriteEnable() {
return Exchange(Dirty::DepthWriteEnable, false);
}

View File

@@ -20,16 +20,15 @@ namespace Vulkan {
namespace {
VkSurfaceFormatKHR ChooseSwapSurfaceFormat(vk::Span<VkSurfaceFormatKHR> formats, bool srgb) {
VkSurfaceFormatKHR ChooseSwapSurfaceFormat(vk::Span<VkSurfaceFormatKHR> formats) {
if (formats.size() == 1 && formats[0].format == VK_FORMAT_UNDEFINED) {
VkSurfaceFormatKHR format;
format.format = VK_FORMAT_B8G8R8A8_UNORM;
format.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
return format;
}
const auto& found = std::find_if(formats.begin(), formats.end(), [srgb](const auto& format) {
const auto request_format = srgb ? VK_FORMAT_B8G8R8A8_SRGB : VK_FORMAT_B8G8R8A8_UNORM;
return format.format == request_format &&
const auto& found = std::find_if(formats.begin(), formats.end(), [](const auto& format) {
return format.format == VK_FORMAT_B8G8R8A8_UNORM &&
format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
});
return found != formats.end() ? *found : formats[0];
@@ -143,7 +142,7 @@ void VKSwapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities,
const auto formats{physical_device.GetSurfaceFormatsKHR(surface)};
const auto present_modes{physical_device.GetSurfacePresentModesKHR(surface)};
const VkSurfaceFormatKHR surface_format{ChooseSwapSurfaceFormat(formats, srgb)};
const VkSurfaceFormatKHR surface_format{ChooseSwapSurfaceFormat(formats)};
const VkPresentModeKHR present_mode{ChooseSwapPresentMode(present_modes)};
u32 requested_image_count{capabilities.minImageCount + 1};
@@ -178,6 +177,17 @@ void VKSwapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities,
swapchain_ci.queueFamilyIndexCount = static_cast<u32>(queue_indices.size());
swapchain_ci.pQueueFamilyIndices = queue_indices.data();
}
static constexpr std::array view_formats{VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_B8G8R8A8_SRGB};
VkImageFormatListCreateInfo format_list{
.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO_KHR,
.pNext = nullptr,
.viewFormatCount = static_cast<u32>(view_formats.size()),
.pViewFormats = view_formats.data(),
};
if (device.IsKhrSwapchainMutableFormatEnabled()) {
format_list.pNext = std::exchange(swapchain_ci.pNext, &format_list);
swapchain_ci.flags |= VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR;
}
// Request the size again to reduce the possibility of a TOCTOU race condition.
const auto updated_capabilities = physical_device.GetSurfaceCapabilitiesKHR(surface);
swapchain_ci.imageExtent = ChooseSwapExtent(updated_capabilities, width, height);
@@ -189,7 +199,7 @@ void VKSwapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities,
images = swapchain.GetImages();
image_count = static_cast<u32>(images.size());
image_format = surface_format.format;
image_view_format = srgb ? VK_FORMAT_B8G8R8A8_SRGB : VK_FORMAT_B8G8R8A8_UNORM;
}
void VKSwapchain::CreateSemaphores() {
@@ -205,7 +215,7 @@ void VKSwapchain::CreateImageViews() {
.flags = 0,
.image = {},
.viewType = VK_IMAGE_VIEW_TYPE_2D,
.format = image_format,
.format = image_view_format,
.components =
{
.r = VK_COMPONENT_SWIZZLE_IDENTITY,

View File

@@ -68,8 +68,8 @@ public:
return *image_views[index];
}
VkFormat GetImageFormat() const {
return image_format;
VkFormat GetImageViewFormat() const {
return image_view_format;
}
VkSemaphore CurrentPresentSemaphore() const {
@@ -100,7 +100,7 @@ private:
u32 image_index{};
u32 frame_index{};
VkFormat image_format{};
VkFormat image_view_format{};
VkExtent2D extent{};
bool current_srgb{};

View File

@@ -1186,9 +1186,12 @@ Framebuffer::Framebuffer(TextureCacheRuntime& runtime, std::span<ImageView*, NUM
renderpass_key.depth_format = depth_buffer->format;
num_layers = std::max(num_layers, depth_buffer->range.extent.layers);
images[num_images] = depth_buffer->ImageHandle();
image_ranges[num_images] = MakeSubresourceRange(depth_buffer);
const VkImageSubresourceRange subresource_range = MakeSubresourceRange(depth_buffer);
image_ranges[num_images] = subresource_range;
samples = depth_buffer->Samples();
++num_images;
has_depth = (subresource_range.aspectMask & VK_IMAGE_ASPECT_DEPTH_BIT) != 0;
has_stencil = (subresource_range.aspectMask & VK_IMAGE_ASPECT_STENCIL_BIT) != 0;
} else {
renderpass_key.depth_format = PixelFormat::Invalid;
}

View File

@@ -232,6 +232,18 @@ public:
return image_ranges;
}
[[nodiscard]] bool HasAspectColorBit(size_t index) const noexcept {
return (image_ranges.at(index).aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) != 0;
}
[[nodiscard]] bool HasAspectDepthBit() const noexcept {
return has_depth;
}
[[nodiscard]] bool HasAspectStencilBit() const noexcept {
return has_stencil;
}
private:
vk::Framebuffer framebuffer;
VkRenderPass renderpass{};
@@ -241,6 +253,8 @@ private:
u32 num_images = 0;
std::array<VkImage, 9> images{};
std::array<VkImageSubresourceRange, 9> image_ranges{};
bool has_depth{};
bool has_stencil{};
};
struct TextureCacheParams {

View File

@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <memory>

View File

@@ -4,6 +4,7 @@
#pragma once
#include <algorithm>
#include <array>
#include <bit>
#include <concepts>

View File

@@ -37,7 +37,8 @@ std::unique_ptr<VideoCore::RendererBase> CreateRenderer(
namespace VideoCore {
std::unique_ptr<Tegra::GPU> CreateGPU(Core::Frontend::EmuWindow& emu_window, Core::System& system) {
const bool use_nvdec = Settings::values.use_nvdec_emulation.GetValue();
const auto nvdec_value = Settings::values.nvdec_emulation.GetValue();
const bool use_nvdec = nvdec_value != Settings::NvdecEmulation::Off;
const bool use_async = Settings::values.use_asynchronous_gpu_emulation.GetValue();
auto gpu = std::make_unique<Tegra::GPU>(system, use_async, use_nvdec);
auto context = emu_window.CreateSharedContext();

View File

@@ -16,6 +16,7 @@ VkBool32 Callback(VkDebugUtilsMessageSeverityFlagBitsEXT severity,
switch (static_cast<u32>(data->messageIdNumber)) {
case 0x682a878au: // VUID-vkCmdBindVertexBuffers2EXT-pBuffers-parameter
case 0x99fb7dfdu: // UNASSIGNED-RequiredParameter (vkCmdBindVertexBuffers2EXT pBuffers[0])
case 0xe8616bf2u: // Bound VkDescriptorSet 0x0[] was destroyed. Likely push_descriptor related
return VK_FALSE;
default:
break;

View File

@@ -368,8 +368,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
};
SetNext(next, demote);
VkPhysicalDeviceFloat16Int8FeaturesKHR float16_int8;
if (is_int8_supported || is_float16_supported) {
VkPhysicalDeviceFloat16Int8FeaturesKHR float16_int8{
float16_int8 = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FLOAT16_INT8_FEATURES_KHR,
.pNext = nullptr,
.shaderFloat16 = is_float16_supported,
@@ -587,6 +588,26 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
ext_extended_dynamic_state = false;
}
}
sets_per_pool = 64;
if (driver_id == VK_DRIVER_ID_AMD_PROPRIETARY || driver_id == VK_DRIVER_ID_AMD_OPEN_SOURCE) {
// AMD drivers need a higher amount of Sets per Pool in certain circunstances like in XC2.
sets_per_pool = 96;
}
const bool is_amd = driver_id == VK_DRIVER_ID_AMD_PROPRIETARY ||
driver_id == VK_DRIVER_ID_MESA_RADV ||
driver_id == VK_DRIVER_ID_AMD_OPEN_SOURCE;
if (ext_sampler_filter_minmax && is_amd) {
// Disable ext_sampler_filter_minmax on AMD GCN4 and lower as it is broken.
if (!is_float16_supported) {
LOG_WARNING(
Render_Vulkan,
"Blacklisting AMD GCN4 and lower for VK_EXT_SAMPLER_FILTER_MINMAX_EXTENSION_NAME");
ext_sampler_filter_minmax = false;
}
}
if (ext_vertex_input_dynamic_state && driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS) {
LOG_WARNING(Render_Vulkan, "Blacklisting Intel for VK_EXT_vertex_input_dynamic_state");
ext_vertex_input_dynamic_state = false;
@@ -839,6 +860,8 @@ std::vector<const char*> Device::LoadExtensions(bool requires_surface) {
bool has_khr_shader_float16_int8{};
bool has_khr_workgroup_memory_explicit_layout{};
bool has_khr_pipeline_executable_properties{};
bool has_khr_image_format_list{};
bool has_khr_swapchain_mutable_format{};
bool has_ext_subgroup_size_control{};
bool has_ext_transform_feedback{};
bool has_ext_custom_border_color{};
@@ -888,6 +911,9 @@ std::vector<const char*> Device::LoadExtensions(bool requires_surface) {
test(has_ext_shader_atomic_int64, VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME, false);
test(has_khr_workgroup_memory_explicit_layout,
VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME, false);
test(has_khr_image_format_list, VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME, false);
test(has_khr_swapchain_mutable_format, VK_KHR_SWAPCHAIN_MUTABLE_FORMAT_EXTENSION_NAME,
false);
test(has_ext_line_rasterization, VK_EXT_LINE_RASTERIZATION_EXTENSION_NAME, false);
if (Settings::values.enable_nsight_aftermath) {
test(nv_device_diagnostics_config, VK_NV_DEVICE_DIAGNOSTICS_CONFIG_EXTENSION_NAME,
@@ -1066,6 +1092,11 @@ std::vector<const char*> Device::LoadExtensions(bool requires_surface) {
khr_pipeline_executable_properties = true;
}
}
if (has_khr_image_format_list && has_khr_swapchain_mutable_format) {
extensions.push_back(VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME);
extensions.push_back(VK_KHR_SWAPCHAIN_MUTABLE_FORMAT_EXTENSION_NAME);
khr_swapchain_mutable_format = true;
}
if (khr_push_descriptor) {
VkPhysicalDevicePushDescriptorPropertiesKHR push_descriptor;
push_descriptor.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PUSH_DESCRIPTOR_PROPERTIES_KHR;

View File

@@ -224,6 +224,11 @@ public:
return khr_pipeline_executable_properties;
}
/// Returns true if VK_KHR_swapchain_mutable_format is enabled.
bool IsKhrSwapchainMutableFormatEnabled() const {
return khr_swapchain_mutable_format;
}
/// Returns true if the device supports VK_KHR_workgroup_memory_explicit_layout.
bool IsKhrWorkgroupMemoryExplicitLayoutSupported() const {
return khr_workgroup_memory_explicit_layout;
@@ -318,6 +323,10 @@ public:
return device_access_memory;
}
u32 GetSetsPerPool() const {
return sets_per_pool;
}
private:
/// Checks if the physical device is suitable.
void CheckSuitability(bool requires_swapchain) const;
@@ -371,6 +380,7 @@ private:
VkShaderStageFlags guest_warp_stages{}; ///< Stages where the guest warp size can be forced.
u64 device_access_memory{}; ///< Total size of device local memory in bytes.
u32 max_push_descriptors{}; ///< Maximum number of push descriptors
u32 sets_per_pool{}; ///< Sets per Description Pool
bool is_optimal_astc_supported{}; ///< Support for native ASTC.
bool is_float16_supported{}; ///< Support for float16 arithmetic.
bool is_int8_supported{}; ///< Support for int8 arithmetic.
@@ -390,6 +400,7 @@ private:
bool khr_workgroup_memory_explicit_layout{}; ///< Support for explicit workgroup layouts.
bool khr_push_descriptor{}; ///< Support for VK_KHR_push_descritor.
bool khr_pipeline_executable_properties{}; ///< Support for executable properties.
bool khr_swapchain_mutable_format{}; ///< Support for VK_KHR_swapchain_mutable_format.
bool ext_index_type_uint8{}; ///< Support for VK_EXT_index_type_uint8.
bool ext_sampler_filter_minmax{}; ///< Support for VK_EXT_sampler_filter_minmax.
bool ext_depth_range_unrestricted{}; ///< Support for VK_EXT_depth_range_unrestricted.

View File

@@ -108,6 +108,9 @@ add_executable(yuzu
configuration/configure_system.cpp
configuration/configure_system.h
configuration/configure_system.ui
configuration/configure_tas.cpp
configuration/configure_tas.h
configuration/configure_tas.ui
configuration/configure_touch_from_button.cpp
configuration/configure_touch_from_button.h
configuration/configure_touch_from_button.ui

View File

@@ -36,6 +36,7 @@
#include "input_common/keyboard.h"
#include "input_common/main.h"
#include "input_common/mouse/mouse_input.h"
#include "input_common/tas/tas_input.h"
#include "video_core/renderer_base.h"
#include "video_core/video_core.h"
#include "yuzu/bootmanager.h"
@@ -312,6 +313,7 @@ GRenderWindow::~GRenderWindow() {
}
void GRenderWindow::OnFrameDisplayed() {
input_subsystem->GetTas()->UpdateThread();
if (!first_frame) {
first_frame = true;
emit FirstFrameDisplayed();

View File

@@ -221,7 +221,7 @@ const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> Config::default
// This must be in alphabetical order according to action name as it must have the same order as
// UISetting::values.shortcuts, which is alphabetically ordered.
// clang-format off
const std::array<UISettings::Shortcut, 18> Config::default_hotkeys{{
const std::array<UISettings::Shortcut, 21> Config::default_hotkeys{{
{QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::WidgetWithChildrenShortcut}},
{QStringLiteral("Change Docked Mode"), QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::ApplicationShortcut}},
{QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}},
@@ -235,6 +235,9 @@ const std::array<UISettings::Shortcut, 18> Config::default_hotkeys{{
{QStringLiteral("Mute Audio"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}},
{QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}},
{QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}},
{QStringLiteral("TAS Start/Stop"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F5"), Qt::ApplicationShortcut}},
{QStringLiteral("TAS Reset"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F6"), Qt::ApplicationShortcut}},
{QStringLiteral("TAS Record"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F7"), Qt::ApplicationShortcut}},
{QStringLiteral("Toggle Filter Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), Qt::WindowShortcut}},
{QStringLiteral("Toggle Framerate Limit"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+U"), Qt::ApplicationShortcut}},
{QStringLiteral("Toggle Mouse Panning"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F9"), Qt::ApplicationShortcut}},
@@ -542,7 +545,6 @@ void Config::ReadAudioValues() {
ReadBasicSetting(Settings::values.audio_device_id);
ReadBasicSetting(Settings::values.sink_id);
}
ReadGlobalSetting(Settings::values.enable_audio_stretching);
ReadGlobalSetting(Settings::values.volume);
qt_config->endGroup();
@@ -560,10 +562,16 @@ void Config::ReadControlValues() {
ReadTouchscreenValues();
ReadMotionTouchValues();
ReadBasicSetting(Settings::values.enable_raw_input);
ReadBasicSetting(Settings::values.emulate_analog_keyboard);
Settings::values.mouse_panning = false;
ReadBasicSetting(Settings::values.mouse_panning_sensitivity);
ReadBasicSetting(Settings::values.tas_enable);
ReadBasicSetting(Settings::values.tas_loop);
ReadBasicSetting(Settings::values.tas_swap_controllers);
ReadBasicSetting(Settings::values.pause_tas_on_load);
ReadGlobalSetting(Settings::values.use_docked_mode);
// Disable docked mode if handheld is selected
@@ -661,6 +669,13 @@ void Config::ReadDataStorageValues() {
QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::DumpDir)))
.toString()
.toStdString());
FS::SetYuzuPath(FS::YuzuPath::TASDir,
qt_config
->value(QStringLiteral("tas_directory"),
QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::TASDir)))
.toString()
.toStdString());
ReadBasicSetting(Settings::values.gamecard_inserted);
ReadBasicSetting(Settings::values.gamecard_current_game);
ReadBasicSetting(Settings::values.gamecard_path);
@@ -812,7 +827,7 @@ void Config::ReadRendererValues() {
ReadGlobalSetting(Settings::values.use_disk_shader_cache);
ReadGlobalSetting(Settings::values.gpu_accuracy);
ReadGlobalSetting(Settings::values.use_asynchronous_gpu_emulation);
ReadGlobalSetting(Settings::values.use_nvdec_emulation);
ReadGlobalSetting(Settings::values.nvdec_emulation);
ReadGlobalSetting(Settings::values.accelerate_astc);
ReadGlobalSetting(Settings::values.use_vsync);
ReadGlobalSetting(Settings::values.shader_backend);
@@ -1163,7 +1178,6 @@ void Config::SaveAudioValues() {
WriteBasicSetting(Settings::values.sink_id);
WriteBasicSetting(Settings::values.audio_device_id);
}
WriteGlobalSetting(Settings::values.enable_audio_stretching);
WriteGlobalSetting(Settings::values.volume);
qt_config->endGroup();
@@ -1184,10 +1198,16 @@ void Config::SaveControlValues() {
WriteGlobalSetting(Settings::values.vibration_enabled);
WriteGlobalSetting(Settings::values.enable_accurate_vibrations);
WriteGlobalSetting(Settings::values.motion_enabled);
WriteBasicSetting(Settings::values.enable_raw_input);
WriteBasicSetting(Settings::values.keyboard_enabled);
WriteBasicSetting(Settings::values.emulate_analog_keyboard);
WriteBasicSetting(Settings::values.mouse_panning_sensitivity);
WriteBasicSetting(Settings::values.tas_enable);
WriteBasicSetting(Settings::values.tas_loop);
WriteBasicSetting(Settings::values.tas_swap_controllers);
WriteBasicSetting(Settings::values.pause_tas_on_load);
qt_config->endGroup();
}
@@ -1215,6 +1235,10 @@ void Config::SaveDataStorageValues() {
WriteSetting(QStringLiteral("dump_directory"),
QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::DumpDir)),
QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::DumpDir)));
WriteSetting(QStringLiteral("tas_directory"),
QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::TASDir)),
QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::TASDir)));
WriteBasicSetting(Settings::values.gamecard_inserted);
WriteBasicSetting(Settings::values.gamecard_current_game);
WriteBasicSetting(Settings::values.gamecard_path);
@@ -1349,7 +1373,10 @@ void Config::SaveRendererValues() {
static_cast<u32>(Settings::values.gpu_accuracy.GetDefault()),
Settings::values.gpu_accuracy.UsingGlobal());
WriteGlobalSetting(Settings::values.use_asynchronous_gpu_emulation);
WriteGlobalSetting(Settings::values.use_nvdec_emulation);
WriteSetting(QString::fromStdString(Settings::values.nvdec_emulation.GetLabel()),
static_cast<u32>(Settings::values.nvdec_emulation.GetValue(global)),
static_cast<u32>(Settings::values.nvdec_emulation.GetDefault()),
Settings::values.nvdec_emulation.UsingGlobal());
WriteGlobalSetting(Settings::values.accelerate_astc);
WriteGlobalSetting(Settings::values.use_vsync);
WriteSetting(QString::fromStdString(Settings::values.shader_backend.GetLabel()),

View File

@@ -42,7 +42,7 @@ public:
default_mouse_buttons;
static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys;
static const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> default_keyboard_mods;
static const std::array<UISettings::Shortcut, 18> default_hotkeys;
static const std::array<UISettings::Shortcut, 21> default_hotkeys;
private:
void Initialize(const std::string& config_name);
@@ -182,5 +182,6 @@ private:
Q_DECLARE_METATYPE(Settings::CPUAccuracy);
Q_DECLARE_METATYPE(Settings::GPUAccuracy);
Q_DECLARE_METATYPE(Settings::FullscreenMode);
Q_DECLARE_METATYPE(Settings::NvdecEmulation);
Q_DECLARE_METATYPE(Settings::RendererBackend);
Q_DECLARE_METATYPE(Settings::ShaderBackend);

View File

@@ -50,8 +50,6 @@ void ConfigureAudio::SetConfiguration() {
const auto volume_value = static_cast<int>(Settings::values.volume.GetValue());
ui->volume_slider->setValue(volume_value);
ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching.GetValue());
if (!Settings::IsConfiguringGlobal()) {
if (Settings::values.volume.UsingGlobal()) {
ui->volume_combo_box->setCurrentIndex(0);
@@ -100,8 +98,6 @@ void ConfigureAudio::SetVolumeIndicatorText(int percentage) {
}
void ConfigureAudio::ApplyConfiguration() {
ConfigurationShared::ApplyPerGameSetting(&Settings::values.enable_audio_stretching,
ui->toggle_audio_stretching, enable_audio_stretching);
if (Settings::IsConfiguringGlobal()) {
Settings::values.sink_id =
@@ -162,15 +158,10 @@ void ConfigureAudio::RetranslateUI() {
void ConfigureAudio::SetupPerGameUI() {
if (Settings::IsConfiguringGlobal()) {
ui->volume_slider->setEnabled(Settings::values.volume.UsingGlobal());
ui->toggle_audio_stretching->setEnabled(
Settings::values.enable_audio_stretching.UsingGlobal());
return;
}
ConfigurationShared::SetColoredTristate(ui->toggle_audio_stretching,
Settings::values.enable_audio_stretching,
enable_audio_stretching);
connect(ui->volume_combo_box, qOverload<int>(&QComboBox::activated), this, [this](int index) {
ui->volume_slider->setEnabled(index == 1);
ConfigurationShared::SetHighlight(ui->volume_layout, index == 1);

View File

@@ -41,6 +41,4 @@ private:
void SetupPerGameUI();
std::unique_ptr<Ui::ConfigureAudio> ui;
ConfigurationShared::CheckState enable_audio_stretching;
};

View File

@@ -31,16 +31,6 @@
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="toggle_audio_stretching">
<property name="toolTip">
<string>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</string>
</property>
<property name="text">
<string>Enable audio stretching</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="_2">
<item>

View File

@@ -88,24 +88,30 @@ void ConfigureGraphics::SetConfiguration() {
ui->api_widget->setEnabled(runtime_lock);
ui->use_asynchronous_gpu_emulation->setEnabled(runtime_lock);
ui->use_disk_shader_cache->setEnabled(runtime_lock);
ui->use_nvdec_emulation->setEnabled(runtime_lock);
ui->nvdec_emulation_widget->setEnabled(runtime_lock);
ui->accelerate_astc->setEnabled(runtime_lock);
ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache.GetValue());
ui->use_asynchronous_gpu_emulation->setChecked(
Settings::values.use_asynchronous_gpu_emulation.GetValue());
ui->use_nvdec_emulation->setChecked(Settings::values.use_nvdec_emulation.GetValue());
ui->accelerate_astc->setChecked(Settings::values.accelerate_astc.GetValue());
if (Settings::IsConfiguringGlobal()) {
ui->api->setCurrentIndex(static_cast<int>(Settings::values.renderer_backend.GetValue()));
ui->fullscreen_mode_combobox->setCurrentIndex(
static_cast<int>(Settings::values.fullscreen_mode.GetValue()));
ui->nvdec_emulation->setCurrentIndex(
static_cast<int>(Settings::values.nvdec_emulation.GetValue()));
ui->aspect_ratio_combobox->setCurrentIndex(Settings::values.aspect_ratio.GetValue());
} else {
ConfigurationShared::SetPerGameSetting(ui->api, &Settings::values.renderer_backend);
ConfigurationShared::SetHighlight(ui->api_widget,
!Settings::values.renderer_backend.UsingGlobal());
ConfigurationShared::SetPerGameSetting(ui->nvdec_emulation,
&Settings::values.nvdec_emulation);
ConfigurationShared::SetHighlight(ui->nvdec_emulation_widget,
!Settings::values.nvdec_emulation.UsingGlobal());
ConfigurationShared::SetPerGameSetting(ui->fullscreen_mode_combobox,
&Settings::values.fullscreen_mode);
ConfigurationShared::SetHighlight(ui->fullscreen_mode_label,
@@ -137,8 +143,6 @@ void ConfigureGraphics::ApplyConfiguration() {
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_gpu_emulation,
ui->use_asynchronous_gpu_emulation,
use_asynchronous_gpu_emulation);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_nvdec_emulation,
ui->use_nvdec_emulation, use_nvdec_emulation);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.accelerate_astc, ui->accelerate_astc,
accelerate_astc);
@@ -147,6 +151,9 @@ void ConfigureGraphics::ApplyConfiguration() {
if (Settings::values.renderer_backend.UsingGlobal()) {
Settings::values.renderer_backend.SetValue(GetCurrentGraphicsBackend());
}
if (Settings::values.nvdec_emulation.UsingGlobal()) {
Settings::values.nvdec_emulation.SetValue(GetCurrentNvdecEmulation());
}
if (Settings::values.shader_backend.UsingGlobal()) {
Settings::values.shader_backend.SetValue(shader_backend);
}
@@ -180,6 +187,13 @@ void ConfigureGraphics::ApplyConfiguration() {
}
}
if (ui->nvdec_emulation->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
Settings::values.nvdec_emulation.SetGlobal(true);
} else {
Settings::values.nvdec_emulation.SetGlobal(false);
Settings::values.nvdec_emulation.SetValue(GetCurrentNvdecEmulation());
}
if (ui->bg_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
Settings::values.bg_red.SetGlobal(true);
Settings::values.bg_green.SetGlobal(true);
@@ -278,6 +292,20 @@ Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const {
ConfigurationShared::USE_GLOBAL_OFFSET);
}
Settings::NvdecEmulation ConfigureGraphics::GetCurrentNvdecEmulation() const {
if (Settings::IsConfiguringGlobal()) {
return static_cast<Settings::NvdecEmulation>(ui->nvdec_emulation->currentIndex());
}
if (ui->nvdec_emulation->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
Settings::values.nvdec_emulation.SetGlobal(true);
return Settings::values.nvdec_emulation.GetValue();
}
Settings::values.nvdec_emulation.SetGlobal(false);
return static_cast<Settings::NvdecEmulation>(ui->nvdec_emulation->currentIndex() -
ConfigurationShared::USE_GLOBAL_OFFSET);
}
void ConfigureGraphics::SetupPerGameUI() {
if (Settings::IsConfiguringGlobal()) {
ui->api->setEnabled(Settings::values.renderer_backend.UsingGlobal());
@@ -286,7 +314,7 @@ void ConfigureGraphics::SetupPerGameUI() {
ui->aspect_ratio_combobox->setEnabled(Settings::values.aspect_ratio.UsingGlobal());
ui->use_asynchronous_gpu_emulation->setEnabled(
Settings::values.use_asynchronous_gpu_emulation.UsingGlobal());
ui->use_nvdec_emulation->setEnabled(Settings::values.use_nvdec_emulation.UsingGlobal());
ui->nvdec_emulation->setEnabled(Settings::values.nvdec_emulation.UsingGlobal());
ui->accelerate_astc->setEnabled(Settings::values.accelerate_astc.UsingGlobal());
ui->use_disk_shader_cache->setEnabled(Settings::values.use_disk_shader_cache.UsingGlobal());
ui->bg_button->setEnabled(Settings::values.bg_red.UsingGlobal());
@@ -301,8 +329,6 @@ void ConfigureGraphics::SetupPerGameUI() {
ConfigurationShared::SetColoredTristate(
ui->use_disk_shader_cache, Settings::values.use_disk_shader_cache, use_disk_shader_cache);
ConfigurationShared::SetColoredTristate(
ui->use_nvdec_emulation, Settings::values.use_nvdec_emulation, use_nvdec_emulation);
ConfigurationShared::SetColoredTristate(ui->accelerate_astc, Settings::values.accelerate_astc,
accelerate_astc);
ConfigurationShared::SetColoredTristate(ui->use_asynchronous_gpu_emulation,
@@ -316,4 +342,6 @@ void ConfigureGraphics::SetupPerGameUI() {
static_cast<int>(Settings::values.fullscreen_mode.GetValue(true)));
ConfigurationShared::InsertGlobalItem(
ui->api, static_cast<int>(Settings::values.renderer_backend.GetValue(true)));
ConfigurationShared::InsertGlobalItem(
ui->nvdec_emulation, static_cast<int>(Settings::values.nvdec_emulation.GetValue(true)));
}

View File

@@ -43,6 +43,7 @@ private:
void SetupPerGameUI();
Settings::RendererBackend GetCurrentGraphicsBackend() const;
Settings::NvdecEmulation GetCurrentNvdecEmulation() const;
std::unique_ptr<Ui::ConfigureGraphics> ui;
QColor bg_color;

View File

@@ -156,7 +156,7 @@
<item>
<widget class="QCheckBox" name="use_disk_shader_cache">
<property name="text">
<string>Use disk shader cache</string>
<string>Use disk pipeline cache</string>
</property>
</widget>
</item>
@@ -167,13 +167,6 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="use_nvdec_emulation">
<property name="text">
<string>Use NVDEC emulation</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="accelerate_astc">
<property name="text">
@@ -181,6 +174,50 @@
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="nvdec_emulation_widget" native="true">
<layout class="QHBoxLayout" name="nvdec_emulation_layout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="nvdec_emulation_label">
<property name="text">
<string>NVDEC emulation:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="nvdec_emulation">
<item>
<property name="text">
<string>Disabled</string>
</property>
</item>
<item>
<property name="text">
<string>CPU Decoding</string>
</property>
</item>
<item>
<property name="text">
<string>GPU Decoding</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="fullscreen_mode_layout" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_1">

View File

@@ -82,7 +82,7 @@
<string>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</string>
</property>
<property name="text">
<string>Use asynchronous shader building (hack)</string>
<string>Use asynchronous shader building (Hack)</string>
</property>
</widget>
</item>
@@ -92,7 +92,7 @@
<string>Enables Fast GPU Time. This option will force most games to run at their highest native resolution.</string>
</property>
<property name="text">
<string>Use Fast GPU Time (hack)</string>
<string>Use Fast GPU Time (Hack)</string>
</property>
</widget>
</item>

View File

@@ -126,6 +126,7 @@ void ConfigureInputAdvanced::ApplyConfiguration() {
Settings::values.mouse_panning_sensitivity =
static_cast<float>(ui->mouse_panning_sensitivity->value());
Settings::values.touchscreen.enabled = ui->touchscreen_enabled->isChecked();
Settings::values.enable_raw_input = ui->enable_raw_input->isChecked();
}
void ConfigureInputAdvanced::LoadConfiguration() {
@@ -155,6 +156,7 @@ void ConfigureInputAdvanced::LoadConfiguration() {
ui->mouse_panning->setChecked(Settings::values.mouse_panning.GetValue());
ui->mouse_panning_sensitivity->setValue(Settings::values.mouse_panning_sensitivity.GetValue());
ui->touchscreen_enabled->setChecked(Settings::values.touchscreen.enabled);
ui->enable_raw_input->setChecked(Settings::values.enable_raw_input.GetValue());
UpdateUIEnabled();
}

View File

@@ -2672,6 +2672,22 @@
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QCheckBox" name="enable_raw_input">
<property name="toolTip">
<string>Requires restarting yuzu</string>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>23</height>
</size>
</property>
<property name="text">
<string>Enable XInput 8 player support (disables web applet)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View File

@@ -124,6 +124,19 @@ QString ButtonToText(const Common::ParamPackage& param) {
return GetKeyName(param.Get("code", 0));
}
if (param.Get("engine", "") == "tas") {
if (param.Has("axis")) {
const QString axis_str = QString::fromStdString(param.Get("axis", ""));
return QObject::tr("TAS Axis %1").arg(axis_str);
}
if (param.Has("button")) {
const QString button_str = QString::number(int(std::log2(param.Get("button", 0))));
return QObject::tr("TAS Btn %1").arg(button_str);
}
return GetKeyName(param.Get("code", 0));
}
if (param.Get("engine", "") == "cemuhookudp") {
if (param.Has("pad_index")) {
const QString motion_str = QString::fromStdString(param.Get("pad_index", ""));
@@ -187,7 +200,8 @@ QString AnalogToText(const Common::ParamPackage& param, const std::string& dir)
const QString axis_y_str = QString::fromStdString(param.Get("axis_y", ""));
const bool invert_x = param.Get("invert_x", "+") == "-";
const bool invert_y = param.Get("invert_y", "+") == "-";
if (engine_str == "sdl" || engine_str == "gcpad" || engine_str == "mouse") {
if (engine_str == "sdl" || engine_str == "gcpad" || engine_str == "mouse" ||
engine_str == "tas") {
if (dir == "modifier") {
return QObject::tr("[unused]");
}
@@ -926,9 +940,9 @@ void ConfigureInputPlayer::UpdateUI() {
int slider_value;
auto& param = analogs_param[analog_id];
const bool is_controller = param.Get("engine", "") == "sdl" ||
param.Get("engine", "") == "gcpad" ||
param.Get("engine", "") == "mouse";
const bool is_controller =
param.Get("engine", "") == "sdl" || param.Get("engine", "") == "gcpad" ||
param.Get("engine", "") == "mouse" || param.Get("engine", "") == "tas";
if (is_controller) {
if (!param.Has("deadzone")) {
@@ -1045,8 +1059,12 @@ int ConfigureInputPlayer::GetIndexFromControllerType(Settings::ControllerType ty
void ConfigureInputPlayer::UpdateInputDevices() {
input_devices = input_subsystem->GetInputDevices();
ui->comboDevices->clear();
for (auto device : input_devices) {
ui->comboDevices->addItem(QString::fromStdString(device.Get("display", "Unknown")), {});
for (auto& device : input_devices) {
const std::string display = device.Get("display", "Unknown");
ui->comboDevices->addItem(QString::fromStdString(display), {});
if (display == "TAS") {
device.Set("pad", static_cast<u8>(player_index));
}
}
}

View File

@@ -175,7 +175,7 @@ void PlayerControlPreview::ResetInputs() {
}
void PlayerControlPreview::UpdateInput() {
if (!is_enabled && !mapping_active) {
if (!is_enabled && !mapping_active && !Settings::values.tas_enable) {
return;
}
bool input_changed = false;
@@ -222,6 +222,19 @@ void PlayerControlPreview::UpdateInput() {
if (input_changed) {
update();
if (controller_callback.input != nullptr) {
ControllerInput input{
.axis_values = {std::pair<float, float>{
axis_values[Settings::NativeAnalog::LStick].value.x(),
axis_values[Settings::NativeAnalog::LStick].value.y()},
std::pair<float, float>{
axis_values[Settings::NativeAnalog::RStick].value.x(),
axis_values[Settings::NativeAnalog::RStick].value.y()}},
.button_values = button_values,
.changed = true,
};
controller_callback.input(std::move(input));
}
}
if (mapping_active) {
@@ -229,6 +242,10 @@ void PlayerControlPreview::UpdateInput() {
}
}
void PlayerControlPreview::SetCallBack(ControllerCallback callback_) {
controller_callback = std::move(callback_);
}
void PlayerControlPreview::paintEvent(QPaintEvent* event) {
QFrame::paintEvent(event);
QPainter p(this);

View File

@@ -9,6 +9,7 @@
#include <QPointer>
#include "common/settings.h"
#include "core/frontend/input.h"
#include "yuzu/debugger/controller.h"
class QLabel;
@@ -33,6 +34,7 @@ public:
void BeginMappingAnalog(std::size_t button_id);
void EndMapping();
void UpdateInput();
void SetCallBack(ControllerCallback callback_);
protected:
void paintEvent(QPaintEvent* event) override;
@@ -181,6 +183,7 @@ private:
using StickArray =
std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID>;
ControllerCallback controller_callback;
bool is_enabled{};
bool mapping_active{};
int blink_counter{};

View File

@@ -0,0 +1,84 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QFileDialog>
#include <QMessageBox>
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/settings.h"
#include "ui_configure_tas.h"
#include "yuzu/configuration/configure_tas.h"
#include "yuzu/uisettings.h"
ConfigureTasDialog::ConfigureTasDialog(QWidget* parent)
: QDialog(parent), ui(std::make_unique<Ui::ConfigureTas>()) {
ui->setupUi(this);
setFocusPolicy(Qt::ClickFocus);
setWindowTitle(tr("TAS Configuration"));
connect(ui->tas_path_button, &QToolButton::pressed, this,
[this] { SetDirectory(DirectoryTarget::TAS, ui->tas_path_edit); });
LoadConfiguration();
}
ConfigureTasDialog::~ConfigureTasDialog() = default;
void ConfigureTasDialog::LoadConfiguration() {
ui->tas_path_edit->setText(
QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASDir)));
ui->tas_enable->setChecked(Settings::values.tas_enable.GetValue());
ui->tas_control_swap->setChecked(Settings::values.tas_swap_controllers.GetValue());
ui->tas_loop_script->setChecked(Settings::values.tas_loop.GetValue());
ui->tas_pause_on_load->setChecked(Settings::values.pause_tas_on_load.GetValue());
}
void ConfigureTasDialog::ApplyConfiguration() {
Common::FS::SetYuzuPath(Common::FS::YuzuPath::TASDir, ui->tas_path_edit->text().toStdString());
Settings::values.tas_enable.SetValue(ui->tas_enable->isChecked());
Settings::values.tas_swap_controllers.SetValue(ui->tas_control_swap->isChecked());
Settings::values.tas_loop.SetValue(ui->tas_loop_script->isChecked());
Settings::values.pause_tas_on_load.SetValue(ui->tas_pause_on_load->isChecked());
}
void ConfigureTasDialog::SetDirectory(DirectoryTarget target, QLineEdit* edit) {
QString caption;
switch (target) {
case DirectoryTarget::TAS:
caption = tr("Select TAS Load Directory...");
break;
}
QString str = QFileDialog::getExistingDirectory(this, caption, edit->text());
if (str.isEmpty()) {
return;
}
if (str.back() != QChar::fromLatin1('/')) {
str.append(QChar::fromLatin1('/'));
}
edit->setText(str);
}
void ConfigureTasDialog::changeEvent(QEvent* event) {
if (event->type() == QEvent::LanguageChange) {
RetranslateUI();
}
QDialog::changeEvent(event);
}
void ConfigureTasDialog::RetranslateUI() {
ui->retranslateUi(this);
}
void ConfigureTasDialog::HandleApplyButtonClicked() {
UISettings::values.configuration_applied = true;
ApplyConfiguration();
}

View File

@@ -0,0 +1,38 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <QDialog>
namespace Ui {
class ConfigureTas;
}
class ConfigureTasDialog : public QDialog {
Q_OBJECT
public:
explicit ConfigureTasDialog(QWidget* parent);
~ConfigureTasDialog() override;
/// Save all button configurations to settings file
void ApplyConfiguration();
private:
enum class DirectoryTarget {
TAS,
};
void LoadConfiguration();
void SetDirectory(DirectoryTarget target, QLineEdit* edit);
void changeEvent(QEvent* event) override;
void RetranslateUI();
void HandleApplyButtonClicked();
std::unique_ptr<Ui::ConfigureTas> ui;
};

View File

@@ -0,0 +1,153 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureTas</class>
<widget class="QDialog" name="ConfigureTas">
<layout class="QVBoxLayout" name="verticalLayout_1">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_1">
<item>
<widget class="QGroupBox" name="groupBox_1">
<property name="title">
<string>TAS</string>
</property>
<layout class="QGridLayout" name="gridLayout_1">
<item row="0" column="0" colspan="4">
<widget class="QLabel" name="label_1">
<property name="text">
<string>Reads controller input from scripts in the same format as TAS-nx scripts.&lt;br/&gt;For a more detailed explanation please consult the FAQ on the yuzu website.</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="4">
<widget class="QLabel" name="label_2">
<property name="text">
<string>To check which hotkeys control the playback/recording, please refer to the Hotkey settings (General -> Hotkeys).</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0" colspan="4">
<widget class="QLabel" name="label_3">
<property name="text">
<string>WARNING: This is an experimental feature.&lt;br/&gt;It will not play back scripts frame perfectly with the current, imperfect syncing method.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Settings</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0" colspan="4">
<widget class="QCheckBox" name="tas_enable">
<property name="text">
<string>Enable TAS features</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="4">
<widget class="QCheckBox" name="tas_control_swap">
<property name="text">
<string>Automatic controller profile swapping</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="4">
<widget class="QCheckBox" name="tas_loop_script">
<property name="text">
<string>Loop script</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="4">
<widget class="QCheckBox" name="tas_pause_on_load">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Pause execution during loads</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Script Directory</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Path</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QToolButton" name="tas_path_button">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLineEdit" name="tas_path_edit"/>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ConfigureTas</receiver>
<slot>accept()</slot>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ConfigureTas</receiver>
<slot>reject()</slot>
</connection>
</connections>
</ui>

View File

@@ -99,7 +99,7 @@ void ConfigureVibration::SetVibrationDevices(std::size_t player_index) {
const auto guid = param.Get("guid", "");
const auto port = param.Get("port", "");
if (engine.empty() || engine == "keyboard" || engine == "mouse") {
if (engine.empty() || engine == "keyboard" || engine == "mouse" || engine == "tas") {
continue;
}

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