summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.ci/scripts/linux/docker.sh2
-rw-r--r--.ci/scripts/windows/docker.sh2
-rw-r--r--CMakeLists.txt24
-rw-r--r--dist/qt_themes/colorful/style.qrc2
-rw-r--r--dist/qt_themes/colorful/style.qss4
-rw-r--r--dist/qt_themes/default/default.qrc13
-rw-r--r--dist/qt_themes/default/style.qss35
-rw-r--r--dist/qt_themes/qdarkstyle/style.qss260
-rw-r--r--externals/cmake-modules/FindSDL2.cmake239
-rw-r--r--externals/httplib/README.md2
-rw-r--r--externals/httplib/httplib.h2419
-rw-r--r--src/common/logging/backend.cpp2
-rw-r--r--src/common/logging/backend.h2
-rw-r--r--src/common/logging/log.h33
-rw-r--r--src/common/string_util.cpp22
-rw-r--r--src/common/thread.h9
-rw-r--r--src/core/CMakeLists.txt16
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic.cpp8
-rw-r--r--src/core/arm/exclusive_monitor.cpp14
-rw-r--r--src/core/arm/exclusive_monitor.h9
-rw-r--r--src/core/arm/unicorn/arm_unicorn.cpp3
-rw-r--r--src/core/core.cpp79
-rw-r--r--src/core/core.h10
-rw-r--r--src/core/core_cpu.cpp127
-rw-r--r--src/core/core_cpu.h120
-rw-r--r--src/core/core_manager.cpp70
-rw-r--r--src/core/core_manager.h63
-rw-r--r--src/core/core_timing.cpp3
-rw-r--r--src/core/core_timing_util.cpp18
-rw-r--r--src/core/core_timing_util.h12
-rw-r--r--src/core/cpu_core_manager.cpp152
-rw-r--r--src/core/cpu_core_manager.h62
-rw-r--r--src/core/cpu_manager.cpp81
-rw-r--r--src/core/cpu_manager.h49
-rw-r--r--src/core/frontend/emu_window.h7
-rw-r--r--src/core/frontend/framebuffer_layout.cpp21
-rw-r--r--src/core/frontend/framebuffer_layout.h15
-rw-r--r--src/core/frontend/input.h10
-rw-r--r--src/core/gdbstub/gdbstub.cpp2
-rw-r--r--src/core/hardware_properties.h45
-rw-r--r--src/core/hle/kernel/address_arbiter.cpp54
-rw-r--r--src/core/hle/kernel/address_arbiter.h2
-rw-r--r--src/core/hle/kernel/client_session.cpp7
-rw-r--r--src/core/hle/kernel/client_session.h6
-rw-r--r--src/core/hle/kernel/hle_ipc.cpp70
-rw-r--r--src/core/hle/kernel/kernel.cpp69
-rw-r--r--src/core/hle/kernel/kernel.h26
-rw-r--r--src/core/hle/kernel/physical_core.cpp51
-rw-r--r--src/core/hle/kernel/physical_core.h77
-rw-r--r--src/core/hle/kernel/process.cpp4
-rw-r--r--src/core/hle/kernel/process.h8
-rw-r--r--src/core/hle/kernel/readable_event.cpp16
-rw-r--r--src/core/hle/kernel/readable_event.h10
-rw-r--r--src/core/hle/kernel/scheduler.cpp13
-rw-r--r--src/core/hle/kernel/scheduler.h13
-rw-r--r--src/core/hle/kernel/server_port.cpp6
-rw-r--r--src/core/hle/kernel/server_port.h6
-rw-r--r--src/core/hle/kernel/server_session.cpp12
-rw-r--r--src/core/hle/kernel/server_session.h6
-rw-r--r--src/core/hle/kernel/session.cpp7
-rw-r--r--src/core/hle/kernel/session.h6
-rw-r--r--src/core/hle/kernel/svc.cpp82
-rw-r--r--src/core/hle/kernel/synchronization.cpp87
-rw-r--r--src/core/hle/kernel/synchronization.h44
-rw-r--r--src/core/hle/kernel/synchronization_object.cpp (renamed from src/core/hle/kernel/wait_object.cpp)44
-rw-r--r--src/core/hle/kernel/synchronization_object.h (renamed from src/core/hle/kernel/wait_object.h)23
-rw-r--r--src/core/hle/kernel/thread.cpp44
-rw-r--r--src/core/hle/kernel/thread.h23
-rw-r--r--src/core/hle/kernel/transfer_memory.cpp66
-rw-r--r--src/core/hle/kernel/transfer_memory.h19
-rw-r--r--src/core/hle/kernel/vm_manager.cpp3
-rw-r--r--src/core/hle/kernel/vm_manager.h60
-rw-r--r--src/core/hle/kernel/writable_event.cpp3
-rw-r--r--src/core/hle/service/am/am.cpp92
-rw-r--r--src/core/hle/service/am/am.h30
-rw-r--r--src/core/hle/service/am/applets/applets.cpp26
-rw-r--r--src/core/hle/service/am/applets/applets.h24
-rw-r--r--src/core/hle/service/am/applets/error.cpp2
-rw-r--r--src/core/hle/service/am/applets/general_backend.cpp14
-rw-r--r--src/core/hle/service/am/applets/profile_select.cpp4
-rw-r--r--src/core/hle/service/am/applets/software_keyboard.cpp13
-rw-r--r--src/core/hle/service/am/applets/web_browser.cpp2
-rw-r--r--src/core/hle/service/audio/hwopus.cpp6
-rw-r--r--src/core/hle/service/bcat/backend/backend.cpp4
-rw-r--r--src/core/hle/service/bcat/backend/boxcat.cpp7
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp16
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp39
-rw-r--r--src/core/hle/service/hid/hid.cpp7
-rw-r--r--src/core/hle/service/ldn/ldn.cpp10
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp12
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.h8
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.cpp7
-rw-r--r--src/core/hle/service/prepo/prepo.cpp30
-rw-r--r--src/core/hle/service/sockets/bsd.cpp48
-rw-r--r--src/core/hle/service/sockets/bsd.h4
-rw-r--r--src/core/hle/service/time/standard_steady_clock_core.cpp3
-rw-r--r--src/core/hle/service/time/tick_based_steady_clock_core.cpp3
-rw-r--r--src/core/hle/service/time/time.cpp3
-rw-r--r--src/core/hle/service/time/time_sharedmemory.cpp3
-rw-r--r--src/core/loader/nso.cpp11
-rw-r--r--src/core/loader/nso.h2
-rw-r--r--src/core/memory/cheat_engine.cpp3
-rw-r--r--src/core/settings.h13
-rw-r--r--src/core/telemetry_session.cpp12
-rw-r--r--src/core/tools/freezer.cpp3
-rw-r--r--src/input_common/CMakeLists.txt8
-rw-r--r--src/input_common/main.cpp13
-rw-r--r--src/input_common/sdl/sdl_impl.cpp16
-rw-r--r--src/input_common/udp/client.cpp286
-rw-r--r--src/input_common/udp/client.h95
-rw-r--r--src/input_common/udp/protocol.cpp79
-rw-r--r--src/input_common/udp/protocol.h255
-rw-r--r--src/input_common/udp/udp.cpp98
-rw-r--r--src/input_common/udp/udp.h25
-rw-r--r--src/video_core/CMakeLists.txt8
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h5
-rw-r--r--src/video_core/engines/const_buffer_engine_interface.h4
-rw-r--r--src/video_core/engines/kepler_compute.cpp8
-rw-r--r--src/video_core/engines/kepler_compute.h4
-rw-r--r--src/video_core/engines/maxwell_3d.cpp134
-rw-r--r--src/video_core/engines/maxwell_3d.h71
-rw-r--r--src/video_core/engines/shader_bytecode.h63
-rw-r--r--src/video_core/gpu.cpp16
-rw-r--r--src/video_core/gpu.h2
-rw-r--r--src/video_core/gpu_thread.h2
-rw-r--r--src/video_core/guest_driver.cpp36
-rw-r--r--src/video_core/guest_driver.h41
-rw-r--r--src/video_core/memory_manager.cpp11
-rw-r--r--src/video_core/query_cache.h359
-rw-r--r--src/video_core/rasterizer_interface.h33
-rw-r--r--src/video_core/renderer_opengl/gl_query_cache.cpp120
-rw-r--r--src/video_core/renderer_opengl/gl_query_cache.h78
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp214
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h52
-rw-r--r--src/video_core/renderer_opengl/gl_resource_manager.cpp17
-rw-r--r--src/video_core/renderer_opengl/gl_resource_manager.h25
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp4
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.cpp43
-rw-r--r--src/video_core/renderer_opengl/gl_shader_disk_cache.cpp12
-rw-r--r--src/video_core/renderer_opengl/gl_shader_disk_cache.h1
-rw-r--r--src/video_core/renderer_opengl/gl_state.cpp1
-rw-r--r--src/video_core/renderer_opengl/gl_state.h1
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.cpp40
-rw-r--r--src/video_core/renderer_opengl/maxwell_to_gl.h20
-rw-r--r--src/video_core/renderer_vulkan/maxwell_to_vk.cpp4
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.cpp265
-rw-r--r--src/video_core/renderer_vulkan/vk_device.cpp21
-rw-r--r--src/video_core/renderer_vulkan/vk_device.h7
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp3
-rw-r--r--src/video_core/renderer_vulkan/vk_query_cache.cpp122
-rw-r--r--src/video_core/renderer_vulkan/vk_query_cache.h104
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp40
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.h11
-rw-r--r--src/video_core/renderer_vulkan/vk_sampler_cache.cpp11
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.cpp8
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.h15
-rw-r--r--src/video_core/renderer_vulkan/vk_shader_decompiler.cpp145
-rw-r--r--src/video_core/renderer_vulkan/vk_shader_decompiler.h3
-rw-r--r--src/video_core/shader/ast.h10
-rw-r--r--src/video_core/shader/const_buffer_locker.cpp17
-rw-r--r--src/video_core/shader/const_buffer_locker.h21
-rw-r--r--src/video_core/shader/decode.cpp68
-rw-r--r--src/video_core/shader/decode/arithmetic.cpp11
-rw-r--r--src/video_core/shader/decode/arithmetic_integer.cpp6
-rw-r--r--src/video_core/shader/decode/bfi.cpp7
-rw-r--r--src/video_core/shader/decode/conversion.cpp14
-rw-r--r--src/video_core/shader/decode/memory.cpp107
-rw-r--r--src/video_core/shader/decode/other.cpp9
-rw-r--r--src/video_core/shader/decode/shift.cpp113
-rw-r--r--src/video_core/shader/decode/texture.cpp196
-rw-r--r--src/video_core/shader/node.h91
-rw-r--r--src/video_core/shader/node_helper.h6
-rw-r--r--src/video_core/shader/shader_ir.cpp9
-rw-r--r--src/video_core/shader/shader_ir.h16
-rw-r--r--src/video_core/shader/track.cpp106
-rw-r--r--src/video_core/texture_cache/surface_base.cpp2
-rw-r--r--src/video_core/texture_cache/texture_cache.h19
-rw-r--r--src/video_core/video_core.cpp15
-rw-r--r--src/web_service/telemetry_json.cpp1
-rw-r--r--src/web_service/web_backend.cpp7
-rw-r--r--src/yuzu/CMakeLists.txt11
-rw-r--r--src/yuzu/bootmanager.cpp284
-rw-r--r--src/yuzu/bootmanager.h30
-rw-r--r--src/yuzu/configuration/config.cpp28
-rw-r--r--src/yuzu/configuration/configure.ui6
-rw-r--r--src/yuzu/configuration/configure_debug.cpp3
-rw-r--r--src/yuzu/configuration/configure_debug.ui116
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp6
-rw-r--r--src/yuzu/configuration/configure_general.cpp8
-rw-r--r--src/yuzu/configuration/configure_general.ui37
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp96
-rw-r--r--src/yuzu/configuration/configure_graphics.h12
-rw-r--r--src/yuzu/configuration/configure_graphics.ui105
-rw-r--r--src/yuzu/configuration/configure_input_player.cpp26
-rw-r--r--src/yuzu/configuration/configure_input_player.h2
-rw-r--r--src/yuzu/configuration/configure_input_player.ui79
-rw-r--r--src/yuzu/configuration/configure_ui.cpp (renamed from src/yuzu/configuration/configure_gamelist.cpp)49
-rw-r--r--src/yuzu/configuration/configure_ui.h (renamed from src/yuzu/configuration/configure_gamelist.h)10
-rw-r--r--src/yuzu/configuration/configure_ui.ui (renamed from src/yuzu/configuration/configure_gamelist.ui)60
-rw-r--r--src/yuzu/debugger/wait_tree.cpp35
-rw-r--r--src/yuzu/debugger/wait_tree.h22
-rw-r--r--src/yuzu/game_list_worker.cpp3
-rw-r--r--src/yuzu/main.cpp184
-rw-r--r--src/yuzu/main.h5
-rw-r--r--src/yuzu/uisettings.h1
-rw-r--r--src/yuzu_cmd/CMakeLists.txt11
-rw-r--r--src/yuzu_cmd/config.cpp13
-rw-r--r--src/yuzu_cmd/default_ini.h32
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.cpp4
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.h3
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp7
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h4
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp162
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h39
-rw-r--r--src/yuzu_cmd/yuzu.cpp18
-rw-r--r--src/yuzu_tester/config.cpp2
-rw-r--r--src/yuzu_tester/default_ini.h4
-rw-r--r--src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp15
-rw-r--r--src/yuzu_tester/emu_window/emu_window_sdl2_hide.h7
219 files changed, 8099 insertions, 2844 deletions
diff --git a/.ci/scripts/linux/docker.sh b/.ci/scripts/linux/docker.sh
index 5559a527c..f11878128 100644
--- a/.ci/scripts/linux/docker.sh
+++ b/.ci/scripts/linux/docker.sh
@@ -5,7 +5,7 @@ cd /yuzu
5ccache -s 5ccache -s
6 6
7mkdir build || true && cd build 7mkdir build || true && cd build
8cmake .. -G Ninja -DDISPLAY_VERSION=$1 -DYUZU_USE_BUNDLED_UNICORN=ON -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON 8cmake .. -G Ninja -DDISPLAY_VERSION=$1 -DYUZU_USE_BUNDLED_UNICORN=ON -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_VULKAN=No
9 9
10ninja 10ninja
11 11
diff --git a/.ci/scripts/windows/docker.sh b/.ci/scripts/windows/docker.sh
index e8f26933a..beb554b65 100644
--- a/.ci/scripts/windows/docker.sh
+++ b/.ci/scripts/windows/docker.sh
@@ -13,7 +13,7 @@ echo '' >> /bin/cmd
13chmod +x /bin/cmd 13chmod +x /bin/cmd
14 14
15mkdir build || true && cd build 15mkdir build || true && cd build
16cmake .. -G Ninja -DDISPLAY_VERSION=$1 -DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MinGWCross.cmake" -DUSE_CCACHE=ON -DYUZU_USE_BUNDLED_UNICORN=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DCMAKE_BUILD_TYPE=Release 16cmake .. -G Ninja -DDISPLAY_VERSION=$1 -DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MinGWCross.cmake" -DUSE_CCACHE=ON -DYUZU_USE_BUNDLED_UNICORN=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_VULKAN=No
17ninja 17ninja
18 18
19# Clean up the dirty hacks 19# Clean up the dirty hacks
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 118572c03..467d769a2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -151,15 +151,22 @@ if (ENABLE_SDL2)
151 set(SDL2_INCLUDE_DIR "${SDL2_PREFIX}/include" CACHE PATH "Path to SDL2 headers") 151 set(SDL2_INCLUDE_DIR "${SDL2_PREFIX}/include" CACHE PATH "Path to SDL2 headers")
152 set(SDL2_LIBRARY "${SDL2_PREFIX}/lib/x64/SDL2.lib" CACHE PATH "Path to SDL2 library") 152 set(SDL2_LIBRARY "${SDL2_PREFIX}/lib/x64/SDL2.lib" CACHE PATH "Path to SDL2 library")
153 set(SDL2_DLL_DIR "${SDL2_PREFIX}/lib/x64/" CACHE PATH "Path to SDL2.dll") 153 set(SDL2_DLL_DIR "${SDL2_PREFIX}/lib/x64/" CACHE PATH "Path to SDL2.dll")
154 else()
155 find_package(SDL2 REQUIRED)
156 endif()
157 154
158 if (SDL2_FOUND)
159 # TODO(yuriks): Make FindSDL2.cmake export an IMPORTED library instead
160 add_library(SDL2 INTERFACE) 155 add_library(SDL2 INTERFACE)
161 target_link_libraries(SDL2 INTERFACE "${SDL2_LIBRARY}") 156 target_link_libraries(SDL2 INTERFACE "${SDL2_LIBRARY}")
162 target_include_directories(SDL2 INTERFACE "${SDL2_INCLUDE_DIR}") 157 target_include_directories(SDL2 INTERFACE "${SDL2_INCLUDE_DIR}")
158 else()
159 find_package(SDL2 REQUIRED)
160
161 # Some installations don't set SDL2_LIBRARIES
162 if("${SDL2_LIBRARIES}" STREQUAL "")
163 message(WARNING "SDL2_LIBRARIES wasn't set, manually setting to SDL2::SDL2")
164 set(SDL2_LIBRARIES "SDL2::SDL2")
165 endif()
166
167 include_directories(${SDL2_INCLUDE_DIRS})
168 add_library(SDL2 INTERFACE)
169 target_link_libraries(SDL2 INTERFACE "${SDL2_LIBRARIES}")
163 endif() 170 endif()
164else() 171else()
165 set(SDL2_FOUND NO) 172 set(SDL2_FOUND NO)
@@ -350,6 +357,13 @@ function(create_target_directory_groups target_name)
350 endforeach() 357 endforeach()
351endfunction() 358endfunction()
352 359
360# Prevent boost from linking against libs when building
361add_definitions(-DBOOST_ERROR_CODE_HEADER_ONLY
362 -DBOOST_SYSTEM_NO_LIB
363 -DBOOST_DATE_TIME_NO_LIB
364 -DBOOST_REGEX_NO_LIB
365)
366
353enable_testing() 367enable_testing()
354add_subdirectory(externals) 368add_subdirectory(externals)
355add_subdirectory(src) 369add_subdirectory(src)
diff --git a/dist/qt_themes/colorful/style.qrc b/dist/qt_themes/colorful/style.qrc
index af2f3fd56..36735519a 100644
--- a/dist/qt_themes/colorful/style.qrc
+++ b/dist/qt_themes/colorful/style.qrc
@@ -10,6 +10,6 @@
10 <file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file> 10 <file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
11 </qresource> 11 </qresource>
12 <qresource prefix="colorful"> 12 <qresource prefix="colorful">
13 <file>style.qss</file> 13 <file alias="style.qss">../default/style.qss</file>
14 </qresource> 14 </qresource>
15</RCC> 15</RCC>
diff --git a/dist/qt_themes/colorful/style.qss b/dist/qt_themes/colorful/style.qss
deleted file mode 100644
index 413fc81da..000000000
--- a/dist/qt_themes/colorful/style.qss
+++ /dev/null
@@ -1,4 +0,0 @@
1/*
2 This file is intentionally left blank.
3 We do not want to apply any stylesheet for colorful, only icons.
4*/
diff --git a/dist/qt_themes/default/default.qrc b/dist/qt_themes/default/default.qrc
index d1a0ee1be..c51fdb26c 100644
--- a/dist/qt_themes/default/default.qrc
+++ b/dist/qt_themes/default/default.qrc
@@ -1,25 +1,18 @@
1<RCC> 1<RCC>
2 <qresource prefix="icons/default"> 2 <qresource prefix="icons/default">
3 <file alias="index.theme">icons/index.theme</file> 3 <file alias="index.theme">icons/index.theme</file>
4
5 <file alias="16x16/checked.png">icons/16x16/checked.png</file> 4 <file alias="16x16/checked.png">icons/16x16/checked.png</file>
6
7 <file alias="16x16/failed.png">icons/16x16/failed.png</file> 5 <file alias="16x16/failed.png">icons/16x16/failed.png</file>
8
9 <file alias="16x16/lock.png">icons/16x16/lock.png</file> 6 <file alias="16x16/lock.png">icons/16x16/lock.png</file>
10
11 <file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file> 7 <file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
12
13 <file alias="48x48/chip.png">icons/48x48/chip.png</file> 8 <file alias="48x48/chip.png">icons/48x48/chip.png</file>
14
15 <file alias="48x48/folder.png">icons/48x48/folder.png</file> 9 <file alias="48x48/folder.png">icons/48x48/folder.png</file>
16
17 <file alias="48x48/plus.png">icons/48x48/plus.png</file> 10 <file alias="48x48/plus.png">icons/48x48/plus.png</file>
18
19 <file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file> 11 <file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
20
21 <file alias="256x256/yuzu.png">icons/256x256/yuzu.png</file> 12 <file alias="256x256/yuzu.png">icons/256x256/yuzu.png</file>
22
23 <file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file> 13 <file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
24 </qresource> 14 </qresource>
15 <qresource prefix="default">
16 <file>style.qss</file>
17 </qresource>
25</RCC> 18</RCC>
diff --git a/dist/qt_themes/default/style.qss b/dist/qt_themes/default/style.qss
new file mode 100644
index 000000000..6b5953e38
--- /dev/null
+++ b/dist/qt_themes/default/style.qss
@@ -0,0 +1,35 @@
1QPushButton#TogglableStatusBarButton {
2 color: #959595;
3 border: 1px solid transparent;
4 background-color: transparent;
5 padding: 0px 3px 0px 3px;
6 text-align: center;
7}
8
9QPushButton#TogglableStatusBarButton:checked {
10 color: #000000;
11}
12
13QPushButton#TogglableStatusBarButton:hover {
14 border: 1px solid #76797C;
15}
16
17QPushButton#RendererStatusBarButton {
18 color: #656565;
19 border: 1px solid transparent;
20 background-color: transparent;
21 padding: 0px 3px 0px 3px;
22 text-align: center;
23}
24
25QPushButton#RendererStatusBarButton:hover {
26 border: 1px solid #76797C;
27}
28
29QPushButton#RendererStatusBarButton:checked {
30 color: #e85c00;
31}
32
33QPushButton#RendererStatusBarButton:!checked{
34 color: #0066ff;
35}
diff --git a/dist/qt_themes/qdarkstyle/style.qss b/dist/qt_themes/qdarkstyle/style.qss
index a3983b27e..7d088a719 100644
--- a/dist/qt_themes/qdarkstyle/style.qss
+++ b/dist/qt_themes/qdarkstyle/style.qss
@@ -2,7 +2,8 @@ QToolTip {
2 border: 1px solid #76797C; 2 border: 1px solid #76797C;
3 background-color: #5A7566; 3 background-color: #5A7566;
4 color: white; 4 color: white;
5 padding: 0px; /*remove padding, for fix combobox tooltip.*/ 5 /*remove padding, for fix combobox tooltip.*/
6 padding: 0;
6 opacity: 200; 7 opacity: 200;
7} 8}
8 9
@@ -13,7 +14,7 @@ QWidget {
13 selection-color: #eff0f1; 14 selection-color: #eff0f1;
14 background-clip: border; 15 background-clip: border;
15 border-image: none; 16 border-image: none;
16 border: 0px transparent black; 17 border: 0;
17 outline: 0; 18 outline: 0;
18} 19}
19 20
@@ -27,10 +28,10 @@ QWidget:item:selected {
27} 28}
28 29
29QCheckBox { 30QCheckBox {
30 spacing: 5px; 31 spacing: 6px;
31 outline: none; 32 outline: none;
32 color: #eff0f1; 33 color: #eff0f1;
33 margin-bottom: 2px; 34 margin: 0 2px 1px 0;
34} 35}
35 36
36QCheckBox:disabled { 37QCheckBox:disabled {
@@ -163,7 +164,7 @@ QMenuBar::item:selected {
163} 164}
164 165
165QMenuBar::item:pressed { 166QMenuBar::item:pressed {
166 border: 1px solid #76797C; 167 border: 1px solid #18465d;
167 background-color: #3daee9; 168 background-color: #3daee9;
168 color: #eff0f1; 169 color: #eff0f1;
169 margin-bottom: -1px; 170 margin-bottom: -1px;
@@ -171,9 +172,9 @@ QMenuBar::item:pressed {
171} 172}
172 173
173QMenu { 174QMenu {
174 border: 1px solid #76797C; 175 border: 1px solid #434242;
176 padding: 2px;
175 color: #eff0f1; 177 color: #eff0f1;
176 margin: 2px;
177} 178}
178 179
179QMenu::icon { 180QMenu::icon {
@@ -190,11 +191,21 @@ QMenu::item:selected {
190 color: #eff0f1; 191 color: #eff0f1;
191} 192}
192 193
193QMenu::separator { 194QMenu::item:disabled {
194 height: 2px; 195 color: #54575B;
195 background: #76797C; 196}
196 margin-left: 10px; 197
197 margin-right: 5px; 198QMenu::item:disabled:hover,
199QMenu::item:disabled:selected {
200 background-color: #393e43;
201 color: #666;
202}
203
204QMenu::separator,
205QMenuBar::separator {
206 height: 1px;
207 background-color: #54575B;
208 margin: 2px 4px 2px 40px;
198} 209}
199 210
200QMenu::indicator { 211QMenu::indicator {
@@ -203,10 +214,7 @@ QMenu::indicator {
203 height: 18px; 214 height: 18px;
204} 215}
205 216
206 217/* non-exclusive indicator = check box style indicator (see QActionGroup::setExclusive) */
207/* non-exclusive indicator = check box style indicator
208 (see QActionGroup::setExclusive) */
209
210QMenu::indicator:non-exclusive:unchecked { 218QMenu::indicator:non-exclusive:unchecked {
211 image: url(:/qss_icons/rc/checkbox_unchecked.png); 219 image: url(:/qss_icons/rc/checkbox_unchecked.png);
212} 220}
@@ -223,9 +231,7 @@ QMenu::indicator:non-exclusive:checked:selected {
223 image: url(:/qss_icons/rc/checkbox_checked_disabled.png); 231 image: url(:/qss_icons/rc/checkbox_checked_disabled.png);
224} 232}
225 233
226
227/* exclusive indicator = radio button style indicator (see QActionGroup::setExclusive) */ 234/* exclusive indicator = radio button style indicator (see QActionGroup::setExclusive) */
228
229QMenu::indicator:exclusive:unchecked { 235QMenu::indicator:exclusive:unchecked {
230 image: url(:/qss_icons/rc/radio_unchecked.png); 236 image: url(:/qss_icons/rc/radio_unchecked.png);
231} 237}
@@ -243,12 +249,12 @@ QMenu::indicator:exclusive:checked:selected {
243} 249}
244 250
245QMenu::right-arrow { 251QMenu::right-arrow {
246 margin: 5px; 252 margin-right: 10px;
247 image: url(:/qss_icons/rc/right_arrow.png) 253 image: url(:/qss_icons/rc/right_arrow.png)
248} 254}
249 255
250QWidget:disabled { 256QWidget:disabled {
251 color: #454545; 257 color: #4f515b;
252 background-color: #31363b; 258 background-color: #31363b;
253} 259}
254 260
@@ -259,23 +265,30 @@ QAbstractItemView {
259 border-radius: 2px; 265 border-radius: 2px;
260} 266}
261 267
262QWidget:focus, 268QAbstractItemView:disabled,
263QMenuBar:focus { 269QAbstractItemView:read-only {
270 alternate-background-color: #232629;
271}
272
273QWidget:focus {
264 border: 1px solid #3daee9; 274 border: 1px solid #3daee9;
265} 275}
266 276
267QTabWidget:focus, 277QTabWidget:focus,
268QCheckBox:focus, 278QCheckBox:focus,
269QRadioButton:focus, 279QRadioButton:focus,
270QSlider:focus { 280QSlider:focus,
281QTreeView:focus,
282QMenu:focus,
283QMenuBar:focus,
284QTabBar:focus {
271 border: none; 285 border: none;
272} 286}
273 287
274QLineEdit { 288QLineEdit {
275 background-color: #232629; 289 background-color: #232629;
276 padding: 5px; 290 padding: 5px;
277 border-style: solid; 291 border: 1px solid #54575B;
278 border: 1px solid #76797C;
279 border-radius: 2px; 292 border-radius: 2px;
280 color: #eff0f1; 293 color: #eff0f1;
281} 294}
@@ -285,9 +298,10 @@ QAbstractItemView QLineEdit {
285} 298}
286 299
287QGroupBox { 300QGroupBox {
288 border: 1px solid #76797C; 301 border: 1px solid #54575B;
289 border-radius: 2px; 302 border-radius: 2px;
290 margin-top: 20px; 303 margin-top: 12px;
304 padding-top: 2px;
291} 305}
292 306
293QGroupBox::title { 307QGroupBox::title {
@@ -295,12 +309,12 @@ QGroupBox::title {
295 subcontrol-position: top center; 309 subcontrol-position: top center;
296 padding-left: 10px; 310 padding-left: 10px;
297 padding-right: 10px; 311 padding-right: 10px;
298 padding-top: 10px; 312 padding-top: 2px;
299} 313}
300 314
301QAbstractScrollArea { 315QAbstractScrollArea {
302 border-radius: 2px; 316 border-radius: 2px;
303 border: 1px solid #76797C; 317 border: 1px solid #54575B;
304 background-color: transparent; 318 background-color: transparent;
305} 319}
306 320
@@ -319,7 +333,7 @@ QScrollBar::handle:horizontal {
319} 333}
320 334
321QScrollBar::add-line:horizontal { 335QScrollBar::add-line:horizontal {
322 margin: 0px 3px 0px 3px; 336 margin: 0 3px;
323 border-image: url(:/qss_icons/rc/right_arrow_disabled.png); 337 border-image: url(:/qss_icons/rc/right_arrow_disabled.png);
324 width: 10px; 338 width: 10px;
325 height: 10px; 339 height: 10px;
@@ -328,7 +342,7 @@ QScrollBar::add-line:horizontal {
328} 342}
329 343
330QScrollBar::sub-line:horizontal { 344QScrollBar::sub-line:horizontal {
331 margin: 0px 3px 0px 3px; 345 margin: 0 3px;
332 border-image: url(:/qss_icons/rc/left_arrow_disabled.png); 346 border-image: url(:/qss_icons/rc/left_arrow_disabled.png);
333 height: 10px; 347 height: 10px;
334 width: 10px; 348 width: 10px;
@@ -379,7 +393,7 @@ QScrollBar::handle:vertical {
379} 393}
380 394
381QScrollBar::sub-line:vertical { 395QScrollBar::sub-line:vertical {
382 margin: 3px 0px 3px 0px; 396 margin: 3px 0;
383 border-image: url(:/qss_icons/rc/up_arrow_disabled.png); 397 border-image: url(:/qss_icons/rc/up_arrow_disabled.png);
384 height: 10px; 398 height: 10px;
385 width: 10px; 399 width: 10px;
@@ -388,7 +402,7 @@ QScrollBar::sub-line:vertical {
388} 402}
389 403
390QScrollBar::add-line:vertical { 404QScrollBar::add-line:vertical {
391 margin: 3px 0px 3px 0px; 405 margin: 3px 0;
392 border-image: url(:/qss_icons/rc/down_arrow_disabled.png); 406 border-image: url(:/qss_icons/rc/down_arrow_disabled.png);
393 height: 10px; 407 height: 10px;
394 width: 10px; 408 width: 10px;
@@ -427,15 +441,14 @@ QScrollBar::sub-page:vertical {
427QTextEdit { 441QTextEdit {
428 background-color: #232629; 442 background-color: #232629;
429 color: #eff0f1; 443 color: #eff0f1;
430 border: 1px solid #76797C; 444 border: 1px solid #54575B;
431} 445}
432 446
433QPlainTextEdit { 447QPlainTextEdit {
434 background-color: #232629; 448 background-color: #232629;
435 ;
436 color: #eff0f1; 449 color: #eff0f1;
437 border-radius: 2px; 450 border-radius: 2px;
438 border: 1px solid #76797C; 451 border: 1px solid #54575B;
439} 452}
440 453
441QHeaderView::section { 454QHeaderView::section {
@@ -467,15 +480,6 @@ QMainWindow::separator:hover {
467 spacing: 2px; 480 spacing: 2px;
468} 481}
469 482
470QMenu::separator {
471 height: 1px;
472 background-color: #76797C;
473 color: white;
474 padding-left: 4px;
475 margin-left: 10px;
476 margin-right: 5px;
477}
478
479QFrame { 483QFrame {
480 border-radius: 2px; 484 border-radius: 2px;
481 border: 1px solid #76797C; 485 border: 1px solid #76797C;
@@ -518,25 +522,19 @@ QToolButton#qt_toolbar_ext_button {
518 522
519QPushButton { 523QPushButton {
520 color: #eff0f1; 524 color: #eff0f1;
521 background-color: #31363b;
522 border-width: 1px; 525 border-width: 1px;
523 border-color: #76797C; 526 border-color: #54575B;
524 border-style: solid; 527 border-style: solid;
525 padding: 5px; 528 padding: 6px 4px;
526 border-radius: 2px; 529 border-radius: 2px;
527 outline: none; 530 outline: none;
531 min-width: 100px;
532 background-color: #232629;
528} 533}
529 534
530QPushButton:disabled { 535QPushButton:disabled {
531 background-color: #31363b; 536 background-color: #31363b;
532 border-width: 1px;
533 border-color: #454545; 537 border-color: #454545;
534 border-style: solid;
535 padding-top: 5px;
536 padding-bottom: 5px;
537 padding-left: 10px;
538 padding-right: 10px;
539 border-radius: 2px;
540 color: #454545; 538 color: #454545;
541} 539}
542 540
@@ -553,11 +551,11 @@ QPushButton:pressed {
553 551
554QComboBox { 552QComboBox {
555 selection-background-color: #3daee9; 553 selection-background-color: #3daee9;
556 border-style: solid; 554 border: 1px solid #54575B;
557 border: 1px solid #76797C;
558 border-radius: 2px; 555 border-radius: 2px;
559 padding: 5px; 556 padding: 4px 6px;
560 min-width: 75px; 557 min-width: 75px;
558 background-color: #232629;
561} 559}
562 560
563QPushButton:checked { 561QPushButton:checked {
@@ -571,8 +569,7 @@ QAbstractSpinBox:hover,
571QLineEdit:hover, 569QLineEdit:hover,
572QTextEdit:hover, 570QTextEdit:hover,
573QPlainTextEdit:hover, 571QPlainTextEdit:hover,
574QAbstractView:hover, 572QAbstractView:hover {
575QTreeView:hover {
576 border: 1px solid #3daee9; 573 border: 1px solid #3daee9;
577 color: #eff0f1; 574 color: #eff0f1;
578} 575}
@@ -591,6 +588,7 @@ QComboBox QAbstractItemView {
591QComboBox::drop-down { 588QComboBox::drop-down {
592 subcontrol-origin: padding; 589 subcontrol-origin: padding;
593 subcontrol-position: top right; 590 subcontrol-position: top right;
591 left: -6px;
594 width: 15px; 592 width: 15px;
595 border-left-width: 0px; 593 border-left-width: 0px;
596 border-left-color: darkgray; 594 border-left-color: darkgray;
@@ -610,8 +608,8 @@ QComboBox::down-arrow:focus {
610} 608}
611 609
612QAbstractSpinBox { 610QAbstractSpinBox {
613 padding: 5px; 611 padding: 4px 6px;
614 border: 1px solid #76797C; 612 border: 1px solid #54575B;
615 background-color: #232629; 613 background-color: #232629;
616 color: #eff0f1; 614 color: #eff0f1;
617 border-radius: 2px; 615 border-radius: 2px;
@@ -622,12 +620,14 @@ QAbstractSpinBox:up-button {
622 background-color: transparent; 620 background-color: transparent;
623 subcontrol-origin: border; 621 subcontrol-origin: border;
624 subcontrol-position: center right; 622 subcontrol-position: center right;
623 left: -6px;
625} 624}
626 625
627QAbstractSpinBox:down-button { 626QAbstractSpinBox:down-button {
628 background-color: transparent; 627 background-color: transparent;
629 subcontrol-origin: border; 628 subcontrol-origin: border;
630 subcontrol-position: center left; 629 subcontrol-position: center left;
630 right: -6px;
631} 631}
632 632
633QAbstractSpinBox::up-arrow, 633QAbstractSpinBox::up-arrow,
@@ -654,22 +654,27 @@ QAbstractSpinBox::down-arrow:hover {
654 image: url(:/qss_icons/rc/down_arrow.png); 654 image: url(:/qss_icons/rc/down_arrow.png);
655} 655}
656 656
657QLabel { 657QLabel,
658 border: 0px solid black; 658QTabWidget {
659 border: 0;
659} 660}
660 661
661QTabWidget { 662QTabWidget {
662 border: 0px transparent black; 663 padding-top: 1px;
663} 664}
664 665
665QTabWidget::pane { 666QTabWidget::pane {
666 border: 1px solid #76797C; 667 border: 1px solid #76797C;
667 padding: 5px; 668 padding: 5px;
668 margin: 0px; 669 position: absolute;
670 top: -1px;
671 border-top-right-radius: 2px;
672 border-bottom-right-radius: 2px;
673 border-bottom-left-radius: 2px;
669} 674}
670 675
671QTabWidget::tab-bar { 676QTabWidget::tab-bar {
672 /* left: 5px; move to the right by 5px */ 677 overflow: visible;
673} 678}
674 679
675QTabBar { 680QTabBar {
@@ -677,10 +682,6 @@ QTabBar {
677 border-radius: 3px; 682 border-radius: 3px;
678} 683}
679 684
680QTabBar:focus {
681 border: 0px transparent black;
682}
683
684QTabBar::close-button { 685QTabBar::close-button {
685 image: url(:/qss_icons/rc/close.png); 686 image: url(:/qss_icons/rc/close.png);
686 background: transparent; 687 background: transparent;
@@ -696,36 +697,33 @@ QTabBar::close-button:pressed {
696 background: transparent; 697 background: transparent;
697} 698}
698 699
699
700/* TOP TABS */ 700/* TOP TABS */
701
702QTabBar::tab:top { 701QTabBar::tab:top {
703 color: #eff0f1; 702 color: #eff0f1;
704 border: 1px solid #76797C; 703 border: 1px solid #54575B;
705 border-bottom: 2px transparent; 704 background-color: #2a2f33;
706 background-color: #31363b; 705 padding: 4px 16px 5px;
707 padding: 4px 16px 2px; 706 min-width: 36px;
708 min-width: 38px;
709 border-top-left-radius: 2px; 707 border-top-left-radius: 2px;
710 border-top-right-radius: 2px; 708 border-top-right-radius: 2px;
711} 709}
712 710
713QTabBar::tab:top:selected { 711QTabBar::tab:top:selected {
714 color: #eff0f1; 712 border-color: #76797C;
715 background-color: #54575B; 713 background-color: #31363b;
716 border: 1px solid #76797C; 714 border-bottom-color: #31363b;
717 border-bottom: 2px solid #3daee9; 715}
718 border-top-left-radius: 2px; 716
719 border-top-right-radius: 2px; 717QTabBar::tab:top:!selected {
718 margin-top: 1px;
719 border-bottom-color: #76797C;
720} 720}
721 721
722QTabBar::tab:top:!selected:hover { 722QTabBar::tab:top:!selected:hover {
723 background-color: #3daee9; 723 background-color: #3daee9;
724} 724}
725 725
726
727/* BOTTOM TABS */ 726/* BOTTOM TABS */
728
729QTabBar::tab:bottom { 727QTabBar::tab:bottom {
730 color: #eff0f1; 728 color: #eff0f1;
731 border: 1px solid #76797C; 729 border: 1px solid #76797C;
@@ -750,9 +748,7 @@ QTabBar::tab:bottom:!selected:hover {
750 background-color: #3daee9; 748 background-color: #3daee9;
751} 749}
752 750
753
754/* LEFT TABS */ 751/* LEFT TABS */
755
756QTabBar::tab:left { 752QTabBar::tab:left {
757 color: #eff0f1; 753 color: #eff0f1;
758 border: 1px solid #76797C; 754 border: 1px solid #76797C;
@@ -777,9 +773,7 @@ QTabBar::tab:left:!selected:hover {
777 background-color: #3daee9; 773 background-color: #3daee9;
778} 774}
779 775
780
781/* RIGHT TABS */ 776/* RIGHT TABS */
782
783QTabBar::tab:right { 777QTabBar::tab:right {
784 color: #eff0f1; 778 color: #eff0f1;
785 border: 1px solid #76797C; 779 border: 1px solid #76797C;
@@ -847,7 +841,7 @@ QDockWidget::float-button:pressed {
847 841
848QTreeView, 842QTreeView,
849QListView { 843QListView {
850 border: 1px solid #76797C; 844 border: 1px solid #54575B;
851 background-color: #232629; 845 background-color: #232629;
852} 846}
853 847
@@ -978,8 +972,8 @@ QSlider::handle:vertical {
978} 972}
979 973
980QToolButton { 974QToolButton {
981 background-color: transparent; 975 background-color: #232629;
982 border: 1px transparent #76797C; 976 border: 1px solid #54575B;
983 border-radius: 2px; 977 border-radius: 2px;
984 margin: 3px; 978 margin: 3px;
985 padding: 5px; 979 padding: 5px;
@@ -988,7 +982,6 @@ QToolButton {
988QToolButton[popupMode="1"] { 982QToolButton[popupMode="1"] {
989 /* only for MenuButtonPopup */ 983 /* only for MenuButtonPopup */
990 padding-right: 20px; 984 padding-right: 20px;
991 /* make way for the popup button */
992 border: 1px #76797C; 985 border: 1px #76797C;
993 border-radius: 5px; 986 border-radius: 5px;
994} 987}
@@ -996,7 +989,6 @@ QToolButton[popupMode="1"] {
996QToolButton[popupMode="2"] { 989QToolButton[popupMode="2"] {
997 /* only for InstantPopup */ 990 /* only for InstantPopup */
998 padding-right: 10px; 991 padding-right: 10px;
999 /* make way for the popup button */
1000 border: 1px #76797C; 992 border: 1px #76797C;
1001} 993}
1002 994
@@ -1015,19 +1007,14 @@ QToolButton::menu-button:pressed {
1015 padding: 5px; 1007 padding: 5px;
1016} 1008}
1017 1009
1018
1019/* the subcontrol below is used only in the InstantPopup or DelayedPopup mode */ 1010/* the subcontrol below is used only in the InstantPopup or DelayedPopup mode */
1020
1021QToolButton::menu-indicator { 1011QToolButton::menu-indicator {
1022 image: url(:/qss_icons/rc/down_arrow.png); 1012 image: url(:/qss_icons/rc/down_arrow.png);
1023 top: -7px; 1013 top: -7px;
1024 left: -2px; 1014 left: -2px;
1025 /* shift it a bit */
1026} 1015}
1027 1016
1028
1029/* the subcontrols below are used only in the MenuButtonPopup mode */ 1017/* the subcontrols below are used only in the MenuButtonPopup mode */
1030
1031QToolButton::menu-button { 1018QToolButton::menu-button {
1032 border: 1px transparent #76797C; 1019 border: 1px transparent #76797C;
1033 border-top-right-radius: 6px; 1020 border-top-right-radius: 6px;
@@ -1052,14 +1039,22 @@ QPushButton::menu-indicator {
1052} 1039}
1053 1040
1054QTableView { 1041QTableView {
1055 border: 1px solid #76797C; 1042 border: 1px solid #54575B;
1056 gridline-color: #31363b; 1043 gridline-color: #31363b;
1057 background-color: #232629; 1044 background-color: #232629;
1058} 1045}
1059 1046
1047QTreeView:disabled {
1048 background-color: #1f2225;
1049}
1050
1060QTableView, 1051QTableView,
1061QHeaderView { 1052QHeaderView {
1062 border-radius: 0px; 1053 border-radius: 0;
1054}
1055
1056QListView:focus {
1057 border-color: #54575B;
1063} 1058}
1064 1059
1065QTableView::item:pressed, 1060QTableView::item:pressed,
@@ -1088,7 +1083,7 @@ QHeaderView::section {
1088 background-color: #232629; 1083 background-color: #232629;
1089 color: #eff0f1; 1084 color: #eff0f1;
1090 padding: 0 5px; 1085 padding: 0 5px;
1091 border: 1px solid #403F3F; 1086 border: 1px solid #434242;
1092 border-bottom: 0; 1087 border-bottom: 0;
1093 border-radius: 0px; 1088 border-radius: 0px;
1094 text-align: center; 1089 text-align: center;
@@ -1118,9 +1113,7 @@ QHeaderView::section:checked {
1118 background-color: #334e5e; 1113 background-color: #334e5e;
1119} 1114}
1120 1115
1121 1116/* sort indicator */
1122/* style the sort indicator */
1123
1124QHeaderView::down-arrow { 1117QHeaderView::down-arrow {
1125 image: url(:/qss_icons/rc/down_arrow.png); 1118 image: url(:/qss_icons/rc/down_arrow.png);
1126} 1119}
@@ -1150,14 +1143,13 @@ QToolBox::tab {
1150} 1143}
1151 1144
1152QToolBox::tab:selected { 1145QToolBox::tab:selected {
1153 /* italicize selected tabs */
1154 font: italic; 1146 font: italic;
1155 background-color: #31363b; 1147 background-color: #31363b;
1156 border-color: #3daee9; 1148 border-color: #3daee9;
1157} 1149}
1158 1150
1159QStatusBar::item { 1151QStatusBar::item {
1160 border: 0px transparent dark; 1152 border: 0;
1161} 1153}
1162 1154
1163QFrame[height="3"], 1155QFrame[height="3"],
@@ -1194,7 +1186,6 @@ QProgressBar::chunk {
1194 1186
1195QDateEdit { 1187QDateEdit {
1196 selection-background-color: #3daee9; 1188 selection-background-color: #3daee9;
1197 border-style: solid;
1198 border: 1px solid #3375A3; 1189 border: 1px solid #3375A3;
1199 border-radius: 2px; 1190 border-radius: 2px;
1200 padding: 1px; 1191 padding: 1px;
@@ -1218,7 +1209,7 @@ QDateEdit::drop-down {
1218 subcontrol-origin: padding; 1209 subcontrol-origin: padding;
1219 subcontrol-position: top right; 1210 subcontrol-position: top right;
1220 width: 15px; 1211 width: 15px;
1221 border-left-width: 0px; 1212 border-left-width: 0;
1222 border-left-color: darkgray; 1213 border-left-color: darkgray;
1223 border-left-style: solid; 1214 border-left-style: solid;
1224 border-top-right-radius: 3px; 1215 border-top-right-radius: 3px;
@@ -1234,3 +1225,52 @@ QDateEdit::down-arrow:hover,
1234QDateEdit::down-arrow:focus { 1225QDateEdit::down-arrow:focus {
1235 image: url(:/qss_icons/rc/down_arrow.png); 1226 image: url(:/qss_icons/rc/down_arrow.png);
1236} 1227}
1228
1229QComboBox:disabled,
1230QPushButton:disabled,
1231QAbstractSpinBox:disabled,
1232QDateEdit:disabled,
1233QLineEdit:disabled,
1234QTextEdit:disabled,
1235QToolButton:disabled,
1236QPlainTextEdit:disabled {
1237 background-color: #2b2e31;
1238}
1239
1240QPushButton#TogglableStatusBarButton {
1241 min-width: 0px;
1242 color: #656565;
1243 border: 1px solid transparent;
1244 background-color: transparent;
1245 padding: 0px 3px 0px 3px;
1246 text-align: center;
1247}
1248
1249QPushButton#TogglableStatusBarButton:checked {
1250 color: #ffffff;
1251}
1252
1253QPushButton#TogglableStatusBarButton:hover {
1254 border: 1px solid #76797C;
1255}
1256
1257QPushButton#RendererStatusBarButton {
1258 min-width: 0px;
1259 color: #656565;
1260 border: 1px solid transparent;
1261 background-color: transparent;
1262 padding: 0px 3px 0px 3px;
1263 text-align: center;
1264}
1265
1266QPushButton#RendererStatusBarButton:hover {
1267 border: 1px solid #76797C;
1268}
1269
1270QPushButton#RendererStatusBarButton:checked {
1271 color: #e85c00;
1272}
1273
1274QPushButton#RendererStatusBarButton:!checked{
1275 color: #00ccdd;
1276} \ No newline at end of file
diff --git a/externals/cmake-modules/FindSDL2.cmake b/externals/cmake-modules/FindSDL2.cmake
deleted file mode 100644
index 22ce752c5..000000000
--- a/externals/cmake-modules/FindSDL2.cmake
+++ /dev/null
@@ -1,239 +0,0 @@
1
2# This module defines
3# SDL2_LIBRARY, the name of the library to link against
4# SDL2_FOUND, if false, do not try to link to SDL2
5# SDL2_INCLUDE_DIR, where to find SDL.h
6# SDL2_DLL_DIR, where to find SDL2.dll if it exists
7#
8# This module responds to the the flag:
9# SDL2_BUILDING_LIBRARY
10# If this is defined, then no SDL2main will be linked in because
11# only applications need main().
12# Otherwise, it is assumed you are building an application and this
13# module will attempt to locate and set the the proper link flags
14# as part of the returned SDL2_LIBRARY variable.
15#
16# Don't forget to include SDLmain.h and SDLmain.m your project for the
17# OS X framework based version. (Other versions link to -lSDL2main which
18# this module will try to find on your behalf.) Also for OS X, this
19# module will automatically add the -framework Cocoa on your behalf.
20#
21#
22# Additional Note: If you see an empty SDL2_LIBRARY_TEMP in your configuration
23# and no SDL2_LIBRARY, it means CMake did not find your SDL2 library
24# (SDL2.dll, libsdl2.so, SDL2.framework, etc).
25# Set SDL2_LIBRARY_TEMP to point to your SDL2 library, and configure again.
26# Similarly, if you see an empty SDL2MAIN_LIBRARY, you should set this value
27# as appropriate. These values are used to generate the final SDL2_LIBRARY
28# variable, but when these values are unset, SDL2_LIBRARY does not get created.
29#
30#
31# $SDL2DIR is an environment variable that would
32# correspond to the ./configure --prefix=$SDL2DIR
33# used in building SDL2.
34# l.e.galup 9-20-02
35#
36# Modified by Eric Wing.
37# Added code to assist with automated building by using environmental variables
38# and providing a more controlled/consistent search behavior.
39# Added new modifications to recognize OS X frameworks and
40# additional Unix paths (FreeBSD, etc).
41# Also corrected the header search path to follow "proper" SDL guidelines.
42# Added a search for SDL2main which is needed by some platforms.
43# Added a search for threads which is needed by some platforms.
44# Added needed compile switches for MinGW.
45#
46# On OSX, this will prefer the Framework version (if found) over others.
47# People will have to manually change the cache values of
48# SDL2_LIBRARY to override this selection or set the CMake environment
49# CMAKE_INCLUDE_PATH to modify the search paths.
50#
51# Note that the header path has changed from SDL2/SDL.h to just SDL.h
52# This needed to change because "proper" SDL convention
53# is #include "SDL.h", not <SDL2/SDL.h>. This is done for portability
54# reasons because not all systems place things in SDL2/ (see FreeBSD).
55
56#=============================================================================
57# Copyright 2003-2009 Kitware, Inc.
58#
59# Distributed under the OSI-approved BSD License (the "License").
60#
61# This software is distributed WITHOUT ANY WARRANTY; without even the
62# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
63# See the License for more information.
64#=============================================================================
65# CMake - Cross Platform Makefile Generator
66# Copyright 2000-2016 Kitware, Inc.
67# Copyright 2000-2011 Insight Software Consortium
68# All rights reserved.
69#
70# Redistribution and use in source and binary forms, with or without
71# modification, are permitted provided that the following conditions
72# are met:
73#
74# * Redistributions of source code must retain the above copyright
75# notice, this list of conditions and the following disclaimer.
76#
77# * Redistributions in binary form must reproduce the above copyright
78# notice, this list of conditions and the following disclaimer in the
79# documentation and/or other materials provided with the distribution.
80#
81# * Neither the names of Kitware, Inc., the Insight Software Consortium,
82# nor the names of their contributors may be used to endorse or promote
83# products derived from this software without specific prior written
84# permission.
85#
86# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
87# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
88# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
89# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
90# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
91# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
92# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
93# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
94# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
95# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
96# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
97#
98# ------------------------------------------------------------------------------
99#
100# The above copyright and license notice applies to distributions of
101# CMake in source and binary form. Some source files contain additional
102# notices of original copyright by their contributors; see each source
103# for details. Third-party software packages supplied with CMake under
104# compatible licenses provide their own copyright notices documented in
105# corresponding subdirectories.
106#
107# ------------------------------------------------------------------------------
108#
109# CMake was initially developed by Kitware with the following sponsorship:
110#
111# * National Library of Medicine at the National Institutes of Health
112# as part of the Insight Segmentation and Registration Toolkit (ITK).
113#
114# * US National Labs (Los Alamos, Livermore, Sandia) ASC Parallel
115# Visualization Initiative.
116#
117# * National Alliance for Medical Image Computing (NAMIC) is funded by the
118# National Institutes of Health through the NIH Roadmap for Medical Research,
119# Grant U54 EB005149.
120#
121# * Kitware, Inc.
122#
123
124message("<FindSDL2.cmake>")
125
126SET(SDL2_SEARCH_PATHS
127 ~/Library/Frameworks
128 /Library/Frameworks
129 /usr/local
130 /usr
131 /sw # Fink
132 /opt/local # DarwinPorts
133 /opt/csw # Blastwave
134 /opt
135 ${SDL2_PATH}
136)
137
138if(CMAKE_SIZEOF_VOID_P EQUAL 8)
139 set(VC_LIB_PATH_SUFFIX lib/x64)
140else()
141 set(VC_LIB_PATH_SUFFIX lib/x86)
142endif()
143
144FIND_LIBRARY(SDL2_LIBRARY_TEMP
145 NAMES SDL2
146 HINTS
147 $ENV{SDL2DIR}
148 PATH_SUFFIXES lib64 lib ${VC_LIB_PATH_SUFFIX}
149 PATHS ${SDL2_SEARCH_PATHS}
150)
151
152IF(SDL2_LIBRARY_TEMP)
153 if(MSVC)
154 get_filename_component(SDL2_DLL_DIR_TEMP ${SDL2_LIBRARY_TEMP} DIRECTORY)
155 if(EXISTS ${SDL2_DLL_DIR_TEMP}/SDL2.dll)
156 set(SDL2_DLL_DIR ${SDL2_DLL_DIR_TEMP})
157 unset(SDL2_DLL_DIR_TEMP)
158 endif()
159 endif()
160
161 FIND_PATH(SDL2_INCLUDE_DIR SDL.h
162 HINTS
163 $ENV{SDL2DIR}
164 PATH_SUFFIXES include/SDL2 include
165 PATHS ${SDL2_SEARCH_PATHS}
166 )
167
168 IF(NOT SDL2_BUILDING_LIBRARY)
169 IF(NOT ${SDL2_INCLUDE_DIR} MATCHES ".framework")
170 # Non-OS X framework versions expect you to also dynamically link to
171 # SDL2main. This is mainly for Windows and OS X. Other (Unix) platforms
172 # seem to provide SDL2main for compatibility even though they don't
173 # necessarily need it.
174 FIND_LIBRARY(SDL2MAIN_LIBRARY
175 NAMES SDL2main
176 HINTS
177 $ENV{SDL2DIR}
178 PATH_SUFFIXES lib64 lib
179 PATHS ${SDL2_SEARCH_PATHS}
180 )
181 ENDIF(NOT ${SDL2_INCLUDE_DIR} MATCHES ".framework")
182 ENDIF(NOT SDL2_BUILDING_LIBRARY)
183
184 # SDL2 may require threads on your system.
185 # The Apple build may not need an explicit flag because one of the
186 # frameworks may already provide it.
187 # But for non-OSX systems, I will use the CMake Threads package.
188 IF(NOT APPLE)
189 FIND_PACKAGE(Threads)
190 ENDIF(NOT APPLE)
191
192 # MinGW needs an additional library, mwindows
193 # It's total link flags should look like -lmingw32 -lSDL2main -lSDL2 -lmwindows
194 # (Actually on second look, I think it only needs one of the m* libraries.)
195 IF(MINGW)
196 SET(MINGW32_LIBRARY mingw32 CACHE STRING "mwindows for MinGW")
197 ENDIF(MINGW)
198
199 # For SDL2main
200 IF(NOT SDL2_BUILDING_LIBRARY)
201 IF(SDL2MAIN_LIBRARY)
202 SET(SDL2_LIBRARY_TEMP ${SDL2MAIN_LIBRARY} ${SDL2_LIBRARY_TEMP})
203 ENDIF(SDL2MAIN_LIBRARY)
204 ENDIF(NOT SDL2_BUILDING_LIBRARY)
205
206 # For OS X, SDL2 uses Cocoa as a backend so it must link to Cocoa.
207 # CMake doesn't display the -framework Cocoa string in the UI even
208 # though it actually is there if I modify a pre-used variable.
209 # I think it has something to do with the CACHE STRING.
210 # So I use a temporary variable until the end so I can set the
211 # "real" variable in one-shot.
212 IF(APPLE)
213 SET(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} "-framework Cocoa")
214 ENDIF(APPLE)
215
216 # For threads, as mentioned Apple doesn't need this.
217 # In fact, there seems to be a problem if I used the Threads package
218 # and try using this line, so I'm just skipping it entirely for OS X.
219 IF(NOT APPLE)
220 SET(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} ${CMAKE_THREAD_LIBS_INIT})
221 ENDIF(NOT APPLE)
222
223 # For MinGW library
224 IF(MINGW)
225 SET(SDL2_LIBRARY_TEMP ${MINGW32_LIBRARY} ${SDL2_LIBRARY_TEMP})
226 ENDIF(MINGW)
227
228 # Set the final string here so the GUI reflects the final state.
229 SET(SDL2_LIBRARY ${SDL2_LIBRARY_TEMP} CACHE STRING "Where the SDL2 Library can be found")
230
231 # Unset the temp variable to INTERNAL so it is not seen in the CMake GUI
232 UNSET(SDL2_LIBRARY_TEMP)
233ENDIF(SDL2_LIBRARY_TEMP)
234
235message("</FindSDL2.cmake>")
236
237INCLUDE(FindPackageHandleStandardArgs)
238
239FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2 REQUIRED_VARS SDL2_LIBRARY SDL2_INCLUDE_DIR)
diff --git a/externals/httplib/README.md b/externals/httplib/README.md
index 0e26522b5..73037d297 100644
--- a/externals/httplib/README.md
+++ b/externals/httplib/README.md
@@ -1,4 +1,4 @@
1From https://github.com/yhirose/cpp-httplib/commit/d9479bc0b12e8a1e8bce2d34da4feeef488581f3 1From https://github.com/yhirose/cpp-httplib/tree/fce8e6fefdab4ad48bc5b25c98e5ebfda4f3cf53
2 2
3MIT License 3MIT License
4 4
diff --git a/externals/httplib/httplib.h b/externals/httplib/httplib.h
index fa2edcc94..e03842e6d 100644
--- a/externals/httplib/httplib.h
+++ b/externals/httplib/httplib.h
@@ -1,7 +1,7 @@
1// 1//
2// httplib.h 2// httplib.h
3// 3//
4// Copyright (c) 2019 Yuji Hirose. All rights reserved. 4// Copyright (c) 2020 Yuji Hirose. All rights reserved.
5// MIT License 5// MIT License
6// 6//
7 7
@@ -11,6 +11,7 @@
11/* 11/*
12 * Configuration 12 * Configuration
13 */ 13 */
14
14#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 15#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND
15#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5 16#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5
16#endif 17#endif
@@ -40,7 +41,7 @@
40#endif 41#endif
41 42
42#ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH 43#ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH
43#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH (std::numeric_limits<size_t>::max)() 44#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH (std::numeric_limits<size_t>::max())
44#endif 45#endif
45 46
46#ifndef CPPHTTPLIB_RECV_BUFSIZ 47#ifndef CPPHTTPLIB_RECV_BUFSIZ
@@ -48,9 +49,14 @@
48#endif 49#endif
49 50
50#ifndef CPPHTTPLIB_THREAD_POOL_COUNT 51#ifndef CPPHTTPLIB_THREAD_POOL_COUNT
51#define CPPHTTPLIB_THREAD_POOL_COUNT 8 52#define CPPHTTPLIB_THREAD_POOL_COUNT \
53 (std::max(1u, std::thread::hardware_concurrency() - 1))
52#endif 54#endif
53 55
56/*
57 * Headers
58 */
59
54#ifdef _WIN32 60#ifdef _WIN32
55#ifndef _CRT_SECURE_NO_WARNINGS 61#ifndef _CRT_SECURE_NO_WARNINGS
56#define _CRT_SECURE_NO_WARNINGS 62#define _CRT_SECURE_NO_WARNINGS
@@ -62,9 +68,9 @@
62 68
63#if defined(_MSC_VER) 69#if defined(_MSC_VER)
64#ifdef _WIN64 70#ifdef _WIN64
65typedef __int64 ssize_t; 71using ssize_t = __int64;
66#else 72#else
67typedef int ssize_t; 73using ssize_t = int;
68#endif 74#endif
69 75
70#if _MSC_VER < 1900 76#if _MSC_VER < 1900
@@ -100,7 +106,7 @@ typedef int ssize_t;
100#define strcasecmp _stricmp 106#define strcasecmp _stricmp
101#endif // strcasecmp 107#endif // strcasecmp
102 108
103typedef SOCKET socket_t; 109using socket_t = SOCKET;
104#ifdef CPPHTTPLIB_USE_POLL 110#ifdef CPPHTTPLIB_USE_POLL
105#define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout) 111#define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout)
106#endif 112#endif
@@ -109,23 +115,25 @@ typedef SOCKET socket_t;
109 115
110#include <arpa/inet.h> 116#include <arpa/inet.h>
111#include <cstring> 117#include <cstring>
118#include <ifaddrs.h>
112#include <netdb.h> 119#include <netdb.h>
113#include <netinet/in.h> 120#include <netinet/in.h>
114#ifdef CPPHTTPLIB_USE_POLL 121#ifdef CPPHTTPLIB_USE_POLL
115#include <poll.h> 122#include <poll.h>
116#endif 123#endif
124#include <csignal>
117#include <pthread.h> 125#include <pthread.h>
118#include <signal.h>
119#include <sys/select.h> 126#include <sys/select.h>
120#include <sys/socket.h> 127#include <sys/socket.h>
121#include <unistd.h> 128#include <unistd.h>
122 129
123typedef int socket_t; 130using socket_t = int;
124#define INVALID_SOCKET (-1) 131#define INVALID_SOCKET (-1)
125#endif //_WIN32 132#endif //_WIN32
126 133
127#include <assert.h> 134#include <array>
128#include <atomic> 135#include <atomic>
136#include <cassert>
129#include <condition_variable> 137#include <condition_variable>
130#include <errno.h> 138#include <errno.h>
131#include <fcntl.h> 139#include <fcntl.h>
@@ -143,9 +151,13 @@ typedef int socket_t;
143 151
144#ifdef CPPHTTPLIB_OPENSSL_SUPPORT 152#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
145#include <openssl/err.h> 153#include <openssl/err.h>
154#include <openssl/md5.h>
146#include <openssl/ssl.h> 155#include <openssl/ssl.h>
147#include <openssl/x509v3.h> 156#include <openssl/x509v3.h>
148 157
158#include <iomanip>
159#include <sstream>
160
149// #if OPENSSL_VERSION_NUMBER < 0x1010100fL 161// #if OPENSSL_VERSION_NUMBER < 0x1010100fL
150// #error Sorry, OpenSSL versions prior to 1.1.1 are not supported 162// #error Sorry, OpenSSL versions prior to 1.1.1 are not supported
151// #endif 163// #endif
@@ -162,6 +174,9 @@ inline const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *asn1) {
162#include <zlib.h> 174#include <zlib.h>
163#endif 175#endif
164 176
177/*
178 * Declaration
179 */
165namespace httplib { 180namespace httplib {
166 181
167namespace detail { 182namespace detail {
@@ -176,37 +191,15 @@ struct ci {
176 191
177} // namespace detail 192} // namespace detail
178 193
179enum class HttpVersion { v1_0 = 0, v1_1 }; 194using Headers = std::multimap<std::string, std::string, detail::ci>;
180
181typedef std::multimap<std::string, std::string, detail::ci> Headers;
182
183typedef std::multimap<std::string, std::string> Params;
184typedef std::smatch Match;
185
186typedef std::function<void(const char *data, size_t data_len)> DataSink;
187
188typedef std::function<void()> Done;
189
190typedef std::function<void(size_t offset, size_t length, DataSink sink,
191 Done done)>
192 ContentProvider;
193 195
194typedef std::function<bool(const char *data, size_t data_length, size_t offset, 196using Params = std::multimap<std::string, std::string>;
195 uint64_t content_length)> 197using Match = std::smatch;
196 ContentReceiver;
197 198
198typedef std::function<bool(uint64_t current, uint64_t total)> Progress; 199using Progress = std::function<bool(uint64_t current, uint64_t total)>;
199 200
200struct Response; 201struct Response;
201typedef std::function<bool(const Response &response)> ResponseHandler; 202using ResponseHandler = std::function<bool(const Response &response)>;
202
203struct MultipartFile {
204 std::string filename;
205 std::string content_type;
206 size_t offset = 0;
207 size_t length = 0;
208};
209typedef std::multimap<std::string, MultipartFile> MultipartFiles;
210 203
211struct MultipartFormData { 204struct MultipartFormData {
212 std::string name; 205 std::string name;
@@ -214,10 +207,53 @@ struct MultipartFormData {
214 std::string filename; 207 std::string filename;
215 std::string content_type; 208 std::string content_type;
216}; 209};
217typedef std::vector<MultipartFormData> MultipartFormDataItems; 210using MultipartFormDataItems = std::vector<MultipartFormData>;
211using MultipartFormDataMap = std::multimap<std::string, MultipartFormData>;
212
213class DataSink {
214public:
215 DataSink() = default;
216 DataSink(const DataSink &) = delete;
217 DataSink &operator=(const DataSink &) = delete;
218 DataSink(DataSink &&) = delete;
219 DataSink &operator=(DataSink &&) = delete;
220
221 std::function<void(const char *data, size_t data_len)> write;
222 std::function<void()> done;
223 std::function<bool()> is_writable;
224};
225
226using ContentProvider =
227 std::function<void(size_t offset, size_t length, DataSink &sink)>;
228
229using ContentReceiver =
230 std::function<bool(const char *data, size_t data_length)>;
231
232using MultipartContentHeader =
233 std::function<bool(const MultipartFormData &file)>;
234
235class ContentReader {
236public:
237 using Reader = std::function<bool(ContentReceiver receiver)>;
238 using MultipartReader = std::function<bool(MultipartContentHeader header,
239 ContentReceiver receiver)>;
240
241 ContentReader(Reader reader, MultipartReader muitlpart_reader)
242 : reader_(reader), muitlpart_reader_(muitlpart_reader) {}
243
244 bool operator()(MultipartContentHeader header,
245 ContentReceiver receiver) const {
246 return muitlpart_reader_(header, receiver);
247 }
248
249 bool operator()(ContentReceiver receiver) const { return reader_(receiver); }
250
251 Reader reader_;
252 MultipartReader muitlpart_reader_;
253};
218 254
219typedef std::pair<ssize_t, ssize_t> Range; 255using Range = std::pair<ssize_t, ssize_t>;
220typedef std::vector<Range> Ranges; 256using Ranges = std::vector<Range>;
221 257
222struct Request { 258struct Request {
223 std::string method; 259 std::string method;
@@ -229,7 +265,7 @@ struct Request {
229 std::string version; 265 std::string version;
230 std::string target; 266 std::string target;
231 Params params; 267 Params params;
232 MultipartFiles files; 268 MultipartFormDataMap files;
233 Ranges ranges; 269 Ranges ranges;
234 Match matches; 270 Match matches;
235 271
@@ -253,13 +289,19 @@ struct Request {
253 std::string get_param_value(const char *key, size_t id = 0) const; 289 std::string get_param_value(const char *key, size_t id = 0) const;
254 size_t get_param_value_count(const char *key) const; 290 size_t get_param_value_count(const char *key) const;
255 291
292 bool is_multipart_form_data() const;
293
256 bool has_file(const char *key) const; 294 bool has_file(const char *key) const;
257 MultipartFile get_file_value(const char *key) const; 295 MultipartFormData get_file_value(const char *key) const;
296
297 // private members...
298 size_t content_length;
299 ContentProvider content_provider;
258}; 300};
259 301
260struct Response { 302struct Response {
261 std::string version; 303 std::string version;
262 int status; 304 int status = -1;
263 Headers headers; 305 Headers headers;
264 std::string body; 306 std::string body;
265 307
@@ -269,106 +311,81 @@ struct Response {
269 void set_header(const char *key, const char *val); 311 void set_header(const char *key, const char *val);
270 void set_header(const char *key, const std::string &val); 312 void set_header(const char *key, const std::string &val);
271 313
272 void set_redirect(const char *uri); 314 void set_redirect(const char *url);
273 void set_content(const char *s, size_t n, const char *content_type); 315 void set_content(const char *s, size_t n, const char *content_type);
274 void set_content(const std::string &s, const char *content_type); 316 void set_content(const std::string &s, const char *content_type);
275 317
276 void set_content_provider( 318 void set_content_provider(
277 size_t length, 319 size_t length,
278 std::function<void(size_t offset, size_t length, DataSink sink)> provider, 320 std::function<void(size_t offset, size_t length, DataSink &sink)>
321 provider,
279 std::function<void()> resource_releaser = [] {}); 322 std::function<void()> resource_releaser = [] {});
280 323
281 void set_chunked_content_provider( 324 void set_chunked_content_provider(
282 std::function<void(size_t offset, DataSink sink, Done done)> provider, 325 std::function<void(size_t offset, DataSink &sink)> provider,
283 std::function<void()> resource_releaser = [] {}); 326 std::function<void()> resource_releaser = [] {});
284 327
285 Response() : status(-1), content_provider_resource_length(0) {} 328 Response() = default;
286 329 Response(const Response &) = default;
330 Response &operator=(const Response &) = default;
331 Response(Response &&) = default;
332 Response &operator=(Response &&) = default;
287 ~Response() { 333 ~Response() {
288 if (content_provider_resource_releaser) { 334 if (content_provider_resource_releaser) {
289 content_provider_resource_releaser(); 335 content_provider_resource_releaser();
290 } 336 }
291 } 337 }
292 338
293 size_t content_provider_resource_length; 339 // private members...
340 size_t content_length = 0;
294 ContentProvider content_provider; 341 ContentProvider content_provider;
295 std::function<void()> content_provider_resource_releaser; 342 std::function<void()> content_provider_resource_releaser;
296}; 343};
297 344
298class Stream { 345class Stream {
299public: 346public:
300 virtual ~Stream() {} 347 virtual ~Stream() = default;
348
349 virtual bool is_readable() const = 0;
350 virtual bool is_writable() const = 0;
351
301 virtual int read(char *ptr, size_t size) = 0; 352 virtual int read(char *ptr, size_t size) = 0;
302 virtual int write(const char *ptr, size_t size1) = 0; 353 virtual int write(const char *ptr, size_t size) = 0;
303 virtual int write(const char *ptr) = 0;
304 virtual int write(const std::string &s) = 0;
305 virtual std::string get_remote_addr() const = 0; 354 virtual std::string get_remote_addr() const = 0;
306 355
307 template <typename... Args> 356 template <typename... Args>
308 int write_format(const char *fmt, const Args &... args); 357 int write_format(const char *fmt, const Args &... args);
309}; 358 int write(const char *ptr);
310 359 int write(const std::string &s);
311class SocketStream : public Stream {
312public:
313 SocketStream(socket_t sock);
314 virtual ~SocketStream();
315
316 virtual int read(char *ptr, size_t size);
317 virtual int write(const char *ptr, size_t size);
318 virtual int write(const char *ptr);
319 virtual int write(const std::string &s);
320 virtual std::string get_remote_addr() const;
321
322private:
323 socket_t sock_;
324};
325
326class BufferStream : public Stream {
327public:
328 BufferStream() {}
329 virtual ~BufferStream() {}
330
331 virtual int read(char *ptr, size_t size);
332 virtual int write(const char *ptr, size_t size);
333 virtual int write(const char *ptr);
334 virtual int write(const std::string &s);
335 virtual std::string get_remote_addr() const;
336
337 const std::string &get_buffer() const;
338
339private:
340 std::string buffer;
341}; 360};
342 361
343class TaskQueue { 362class TaskQueue {
344public: 363public:
345 TaskQueue() {} 364 TaskQueue() = default;
346 virtual ~TaskQueue() {} 365 virtual ~TaskQueue() = default;
347 virtual void enqueue(std::function<void()> fn) = 0; 366 virtual void enqueue(std::function<void()> fn) = 0;
348 virtual void shutdown() = 0; 367 virtual void shutdown() = 0;
349}; 368};
350 369
351#if CPPHTTPLIB_THREAD_POOL_COUNT > 0
352class ThreadPool : public TaskQueue { 370class ThreadPool : public TaskQueue {
353public: 371public:
354 ThreadPool(size_t n) : shutdown_(false) { 372 explicit ThreadPool(size_t n) : shutdown_(false) {
355 while (n) { 373 while (n) {
356 auto t = std::make_shared<std::thread>(worker(*this)); 374 threads_.emplace_back(worker(*this));
357 threads_.push_back(t);
358 n--; 375 n--;
359 } 376 }
360 } 377 }
361 378
362 ThreadPool(const ThreadPool &) = delete; 379 ThreadPool(const ThreadPool &) = delete;
363 virtual ~ThreadPool() {} 380 ~ThreadPool() override = default;
364 381
365 virtual void enqueue(std::function<void()> fn) override { 382 void enqueue(std::function<void()> fn) override {
366 std::unique_lock<std::mutex> lock(mutex_); 383 std::unique_lock<std::mutex> lock(mutex_);
367 jobs_.push_back(fn); 384 jobs_.push_back(fn);
368 cond_.notify_one(); 385 cond_.notify_one();
369 } 386 }
370 387
371 virtual void shutdown() override { 388 void shutdown() override {
372 // Stop all worker threads... 389 // Stop all worker threads...
373 { 390 {
374 std::unique_lock<std::mutex> lock(mutex_); 391 std::unique_lock<std::mutex> lock(mutex_);
@@ -378,14 +395,14 @@ public:
378 cond_.notify_all(); 395 cond_.notify_all();
379 396
380 // Join... 397 // Join...
381 for (auto t : threads_) { 398 for (auto &t : threads_) {
382 t->join(); 399 t.join();
383 } 400 }
384 } 401 }
385 402
386private: 403private:
387 struct worker { 404 struct worker {
388 worker(ThreadPool &pool) : pool_(pool) {} 405 explicit worker(ThreadPool &pool) : pool_(pool) {}
389 406
390 void operator()() { 407 void operator()() {
391 for (;;) { 408 for (;;) {
@@ -411,7 +428,7 @@ private:
411 }; 428 };
412 friend struct worker; 429 friend struct worker;
413 430
414 std::vector<std::shared_ptr<std::thread>> threads_; 431 std::vector<std::thread> threads_;
415 std::list<std::function<void()>> jobs_; 432 std::list<std::function<void()>> jobs_;
416 433
417 bool shutdown_; 434 bool shutdown_;
@@ -419,46 +436,16 @@ private:
419 std::condition_variable cond_; 436 std::condition_variable cond_;
420 std::mutex mutex_; 437 std::mutex mutex_;
421}; 438};
422#else
423class Threads : public TaskQueue {
424public:
425 Threads() : running_threads_(0) {}
426 virtual ~Threads() {}
427
428 virtual void enqueue(std::function<void()> fn) override {
429 std::thread([=]() {
430 {
431 std::lock_guard<std::mutex> guard(running_threads_mutex_);
432 running_threads_++;
433 }
434
435 fn();
436
437 {
438 std::lock_guard<std::mutex> guard(running_threads_mutex_);
439 running_threads_--;
440 }
441 }).detach();
442 }
443
444 virtual void shutdown() override {
445 for (;;) {
446 std::this_thread::sleep_for(std::chrono::milliseconds(10));
447 std::lock_guard<std::mutex> guard(running_threads_mutex_);
448 if (!running_threads_) { break; }
449 }
450 }
451 439
452private: 440using Logger = std::function<void(const Request &, const Response &)>;
453 std::mutex running_threads_mutex_;
454 int running_threads_;
455};
456#endif
457 441
458class Server { 442class Server {
459public: 443public:
460 typedef std::function<void(const Request &, Response &)> Handler; 444 using Handler = std::function<void(const Request &, Response &)>;
461 typedef std::function<void(const Request &, const Response &)> Logger; 445 using HandlerWithContentReader = std::function<void(
446 const Request &, Response &, const ContentReader &content_reader)>;
447 using Expect100ContinueHandler =
448 std::function<int(const Request &, Response &)>;
462 449
463 Server(); 450 Server();
464 451
@@ -468,21 +455,32 @@ public:
468 455
469 Server &Get(const char *pattern, Handler handler); 456 Server &Get(const char *pattern, Handler handler);
470 Server &Post(const char *pattern, Handler handler); 457 Server &Post(const char *pattern, Handler handler);
471 458 Server &Post(const char *pattern, HandlerWithContentReader handler);
472 Server &Put(const char *pattern, Handler handler); 459 Server &Put(const char *pattern, Handler handler);
460 Server &Put(const char *pattern, HandlerWithContentReader handler);
473 Server &Patch(const char *pattern, Handler handler); 461 Server &Patch(const char *pattern, Handler handler);
462 Server &Patch(const char *pattern, HandlerWithContentReader handler);
474 Server &Delete(const char *pattern, Handler handler); 463 Server &Delete(const char *pattern, Handler handler);
475 Server &Options(const char *pattern, Handler handler); 464 Server &Options(const char *pattern, Handler handler);
476 465
477 bool set_base_dir(const char *path); 466 [[deprecated]] bool set_base_dir(const char *dir,
467 const char *mount_point = nullptr);
468 bool set_mount_point(const char *mount_point, const char *dir);
469 bool remove_mount_point(const char *mount_point);
470 void set_file_extension_and_mimetype_mapping(const char *ext,
471 const char *mime);
478 void set_file_request_handler(Handler handler); 472 void set_file_request_handler(Handler handler);
479 473
480 void set_error_handler(Handler handler); 474 void set_error_handler(Handler handler);
481 void set_logger(Logger logger); 475 void set_logger(Logger logger);
482 476
477 void set_expect_100_continue_handler(Expect100ContinueHandler handler);
478
483 void set_keep_alive_max_count(size_t count); 479 void set_keep_alive_max_count(size_t count);
480 void set_read_timeout(time_t sec, time_t usec);
484 void set_payload_max_length(size_t length); 481 void set_payload_max_length(size_t length);
485 482
483 bool bind_to_port(const char *host, int port, int socket_flags = 0);
486 int bind_to_any_port(const char *host, int socket_flags = 0); 484 int bind_to_any_port(const char *host, int socket_flags = 0);
487 bool listen_after_bind(); 485 bool listen_after_bind();
488 486
@@ -496,22 +494,29 @@ public:
496protected: 494protected:
497 bool process_request(Stream &strm, bool last_connection, 495 bool process_request(Stream &strm, bool last_connection,
498 bool &connection_close, 496 bool &connection_close,
499 std::function<void(Request &)> setup_request); 497 const std::function<void(Request &)> &setup_request);
500 498
501 size_t keep_alive_max_count_; 499 size_t keep_alive_max_count_;
500 time_t read_timeout_sec_;
501 time_t read_timeout_usec_;
502 size_t payload_max_length_; 502 size_t payload_max_length_;
503 503
504private: 504private:
505 typedef std::vector<std::pair<std::regex, Handler>> Handlers; 505 using Handlers = std::vector<std::pair<std::regex, Handler>>;
506 using HandlersForContentReader =
507 std::vector<std::pair<std::regex, HandlerWithContentReader>>;
506 508
507 socket_t create_server_socket(const char *host, int port, 509 socket_t create_server_socket(const char *host, int port,
508 int socket_flags) const; 510 int socket_flags) const;
509 int bind_internal(const char *host, int port, int socket_flags); 511 int bind_internal(const char *host, int port, int socket_flags);
510 bool listen_internal(); 512 bool listen_internal();
511 513
512 bool routing(Request &req, Response &res); 514 bool routing(Request &req, Response &res, Stream &strm, bool last_connection);
513 bool handle_file_request(Request &req, Response &res); 515 bool handle_file_request(Request &req, Response &res, bool head = false);
514 bool dispatch_request(Request &req, Response &res, Handlers &handlers); 516 bool dispatch_request(Request &req, Response &res, Handlers &handlers);
517 bool dispatch_request_for_content_reader(Request &req, Response &res,
518 ContentReader content_reader,
519 HandlersForContentReader &handlers);
515 520
516 bool parse_request_line(const char *s, Request &req); 521 bool parse_request_line(const char *s, Request &req);
517 bool write_response(Stream &strm, bool last_connection, const Request &req, 522 bool write_response(Stream &strm, bool last_connection, const Request &req,
@@ -519,26 +524,43 @@ private:
519 bool write_content_with_provider(Stream &strm, const Request &req, 524 bool write_content_with_provider(Stream &strm, const Request &req,
520 Response &res, const std::string &boundary, 525 Response &res, const std::string &boundary,
521 const std::string &content_type); 526 const std::string &content_type);
527 bool read_content(Stream &strm, bool last_connection, Request &req,
528 Response &res);
529 bool read_content_with_content_receiver(
530 Stream &strm, bool last_connection, Request &req, Response &res,
531 ContentReceiver receiver, MultipartContentHeader multipart_header,
532 ContentReceiver multipart_receiver);
533 bool read_content_core(Stream &strm, bool last_connection, Request &req,
534 Response &res, ContentReceiver receiver,
535 MultipartContentHeader mulitpart_header,
536 ContentReceiver multipart_receiver);
522 537
523 virtual bool process_and_close_socket(socket_t sock); 538 virtual bool process_and_close_socket(socket_t sock);
524 539
525 std::atomic<bool> is_running_; 540 std::atomic<bool> is_running_;
526 std::atomic<socket_t> svr_sock_; 541 std::atomic<socket_t> svr_sock_;
527 std::string base_dir_; 542 std::vector<std::pair<std::string, std::string>> base_dirs_;
543 std::map<std::string, std::string> file_extension_and_mimetype_map_;
528 Handler file_request_handler_; 544 Handler file_request_handler_;
529 Handlers get_handlers_; 545 Handlers get_handlers_;
530 Handlers post_handlers_; 546 Handlers post_handlers_;
547 HandlersForContentReader post_handlers_for_content_reader_;
531 Handlers put_handlers_; 548 Handlers put_handlers_;
549 HandlersForContentReader put_handlers_for_content_reader_;
532 Handlers patch_handlers_; 550 Handlers patch_handlers_;
551 HandlersForContentReader patch_handlers_for_content_reader_;
533 Handlers delete_handlers_; 552 Handlers delete_handlers_;
534 Handlers options_handlers_; 553 Handlers options_handlers_;
535 Handler error_handler_; 554 Handler error_handler_;
536 Logger logger_; 555 Logger logger_;
556 Expect100ContinueHandler expect_100_continue_handler_;
537}; 557};
538 558
539class Client { 559class Client {
540public: 560public:
541 Client(const char *host, int port = 80, time_t timeout_sec = 300); 561 explicit Client(const std::string &host, int port = 80,
562 const std::string &client_cert_path = std::string(),
563 const std::string &client_key_path = std::string());
542 564
543 virtual ~Client(); 565 virtual ~Client();
544 566
@@ -586,6 +608,15 @@ public:
586 const std::string &body, 608 const std::string &body,
587 const char *content_type); 609 const char *content_type);
588 610
611 std::shared_ptr<Response> Post(const char *path, size_t content_length,
612 ContentProvider content_provider,
613 const char *content_type);
614
615 std::shared_ptr<Response> Post(const char *path, const Headers &headers,
616 size_t content_length,
617 ContentProvider content_provider,
618 const char *content_type);
619
589 std::shared_ptr<Response> Post(const char *path, const Params &params); 620 std::shared_ptr<Response> Post(const char *path, const Params &params);
590 621
591 std::shared_ptr<Response> Post(const char *path, const Headers &headers, 622 std::shared_ptr<Response> Post(const char *path, const Headers &headers,
@@ -604,6 +635,20 @@ public:
604 const std::string &body, 635 const std::string &body,
605 const char *content_type); 636 const char *content_type);
606 637
638 std::shared_ptr<Response> Put(const char *path, size_t content_length,
639 ContentProvider content_provider,
640 const char *content_type);
641
642 std::shared_ptr<Response> Put(const char *path, const Headers &headers,
643 size_t content_length,
644 ContentProvider content_provider,
645 const char *content_type);
646
647 std::shared_ptr<Response> Put(const char *path, const Params &params);
648
649 std::shared_ptr<Response> Put(const char *path, const Headers &headers,
650 const Params &params);
651
607 std::shared_ptr<Response> Patch(const char *path, const std::string &body, 652 std::shared_ptr<Response> Patch(const char *path, const std::string &body,
608 const char *content_type); 653 const char *content_type);
609 654
@@ -611,6 +656,15 @@ public:
611 const std::string &body, 656 const std::string &body,
612 const char *content_type); 657 const char *content_type);
613 658
659 std::shared_ptr<Response> Patch(const char *path, size_t content_length,
660 ContentProvider content_provider,
661 const char *content_type);
662
663 std::shared_ptr<Response> Patch(const char *path, const Headers &headers,
664 size_t content_length,
665 ContentProvider content_provider,
666 const char *content_type);
667
614 std::shared_ptr<Response> Delete(const char *path); 668 std::shared_ptr<Response> Delete(const char *path);
615 669
616 std::shared_ptr<Response> Delete(const char *path, const std::string &body, 670 std::shared_ptr<Response> Delete(const char *path, const std::string &body,
@@ -631,9 +685,33 @@ public:
631 bool send(const std::vector<Request> &requests, 685 bool send(const std::vector<Request> &requests,
632 std::vector<Response> &responses); 686 std::vector<Response> &responses);
633 687
688 void set_timeout_sec(time_t timeout_sec);
689
690 void set_read_timeout(time_t sec, time_t usec);
691
634 void set_keep_alive_max_count(size_t count); 692 void set_keep_alive_max_count(size_t count);
635 693
636 void follow_location(bool on); 694 void set_basic_auth(const char *username, const char *password);
695
696#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
697 void set_digest_auth(const char *username, const char *password);
698#endif
699
700 void set_follow_location(bool on);
701
702 void set_compress(bool on);
703
704 void set_interface(const char *intf);
705
706 void set_proxy(const char *host, int port);
707
708 void set_proxy_basic_auth(const char *username, const char *password);
709
710#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
711 void set_proxy_digest_auth(const char *username, const char *password);
712#endif
713
714 void set_logger(Logger logger);
637 715
638protected: 716protected:
639 bool process_request(Stream &strm, const Request &req, Response &res, 717 bool process_request(Stream &strm, const Request &req, Response &res,
@@ -641,16 +719,85 @@ protected:
641 719
642 const std::string host_; 720 const std::string host_;
643 const int port_; 721 const int port_;
644 time_t timeout_sec_;
645 const std::string host_and_port_; 722 const std::string host_and_port_;
646 size_t keep_alive_max_count_; 723
647 size_t follow_location_; 724 // Settings
725 std::string client_cert_path_;
726 std::string client_key_path_;
727
728 time_t timeout_sec_ = 300;
729 time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND;
730 time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND;
731
732 size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT;
733
734 std::string basic_auth_username_;
735 std::string basic_auth_password_;
736#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
737 std::string digest_auth_username_;
738 std::string digest_auth_password_;
739#endif
740
741 bool follow_location_ = false;
742
743 bool compress_ = false;
744
745 std::string interface_;
746
747 std::string proxy_host_;
748 int proxy_port_;
749
750 std::string proxy_basic_auth_username_;
751 std::string proxy_basic_auth_password_;
752#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
753 std::string proxy_digest_auth_username_;
754 std::string proxy_digest_auth_password_;
755#endif
756
757 Logger logger_;
758
759 void copy_settings(const Client &rhs) {
760 client_cert_path_ = rhs.client_cert_path_;
761 client_key_path_ = rhs.client_key_path_;
762 timeout_sec_ = rhs.timeout_sec_;
763 read_timeout_sec_ = rhs.read_timeout_sec_;
764 read_timeout_usec_ = rhs.read_timeout_usec_;
765 keep_alive_max_count_ = rhs.keep_alive_max_count_;
766 basic_auth_username_ = rhs.basic_auth_username_;
767 basic_auth_password_ = rhs.basic_auth_password_;
768#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
769 digest_auth_username_ = rhs.digest_auth_username_;
770 digest_auth_password_ = rhs.digest_auth_password_;
771#endif
772 follow_location_ = rhs.follow_location_;
773 compress_ = rhs.compress_;
774 interface_ = rhs.interface_;
775 proxy_host_ = rhs.proxy_host_;
776 proxy_port_ = rhs.proxy_port_;
777 proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_;
778 proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_;
779#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
780 proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_;
781 proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_;
782#endif
783 logger_ = rhs.logger_;
784 }
648 785
649private: 786private:
650 socket_t create_client_socket() const; 787 socket_t create_client_socket() const;
651 bool read_response_line(Stream &strm, Response &res); 788 bool read_response_line(Stream &strm, Response &res);
652 void write_request(Stream &strm, const Request &req, bool last_connection); 789 bool write_request(Stream &strm, const Request &req, bool last_connection);
653 bool redirect(const Request &req, Response &res); 790 bool redirect(const Request &req, Response &res);
791 bool handle_request(Stream &strm, const Request &req, Response &res,
792 bool last_connection, bool &connection_close);
793#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
794 bool connect(socket_t sock, Response &res, bool &error);
795#endif
796
797 std::shared_ptr<Response> send_with_content_provider(
798 const char *method, const char *path, const Headers &headers,
799 const std::string &body, size_t content_length,
800 ContentProvider content_provider, const char *content_type);
654 801
655 virtual bool process_and_close_socket( 802 virtual bool process_and_close_socket(
656 socket_t sock, size_t request_count, 803 socket_t sock, size_t request_count,
@@ -692,22 +839,6 @@ inline void Post(std::vector<Request> &requests, const char *path,
692} 839}
693 840
694#ifdef CPPHTTPLIB_OPENSSL_SUPPORT 841#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
695class SSLSocketStream : public Stream {
696public:
697 SSLSocketStream(socket_t sock, SSL *ssl);
698 virtual ~SSLSocketStream();
699
700 virtual int read(char *ptr, size_t size);
701 virtual int write(const char *ptr, size_t size);
702 virtual int write(const char *ptr);
703 virtual int write(const std::string &s);
704 virtual std::string get_remote_addr() const;
705
706private:
707 socket_t sock_;
708 SSL *ssl_;
709};
710
711class SSLServer : public Server { 842class SSLServer : public Server {
712public: 843public:
713 SSLServer(const char *cert_path, const char *private_key_path, 844 SSLServer(const char *cert_path, const char *private_key_path,
@@ -727,9 +858,9 @@ private:
727 858
728class SSLClient : public Client { 859class SSLClient : public Client {
729public: 860public:
730 SSLClient(const char *host, int port = 443, time_t timeout_sec = 300, 861 SSLClient(const std::string &host, int port = 443,
731 const char *client_cert_path = nullptr, 862 const std::string &client_cert_path = std::string(),
732 const char *client_key_path = nullptr); 863 const std::string &client_key_path = std::string());
733 864
734 virtual ~SSLClient(); 865 virtual ~SSLClient();
735 866
@@ -737,11 +868,12 @@ public:
737 868
738 void set_ca_cert_path(const char *ca_ceert_file_path, 869 void set_ca_cert_path(const char *ca_ceert_file_path,
739 const char *ca_cert_dir_path = nullptr); 870 const char *ca_cert_dir_path = nullptr);
871
740 void enable_server_certificate_verification(bool enabled); 872 void enable_server_certificate_verification(bool enabled);
741 873
742 long get_openssl_verify_result() const; 874 long get_openssl_verify_result() const;
743 875
744 SSL_CTX* ssl_context() const noexcept; 876 SSL_CTX *ssl_context() const noexcept;
745 877
746private: 878private:
747 virtual bool process_and_close_socket( 879 virtual bool process_and_close_socket(
@@ -759,6 +891,7 @@ private:
759 SSL_CTX *ctx_; 891 SSL_CTX *ctx_;
760 std::mutex ctx_mutex_; 892 std::mutex ctx_mutex_;
761 std::vector<std::string> host_components_; 893 std::vector<std::string> host_components_;
894
762 std::string ca_cert_file_path_; 895 std::string ca_cert_file_path_;
763 std::string ca_cert_dir_path_; 896 std::string ca_cert_dir_path_;
764 bool server_certificate_verification_ = false; 897 bool server_certificate_verification_ = false;
@@ -766,9 +899,12 @@ private:
766}; 899};
767#endif 900#endif
768 901
902// ----------------------------------------------------------------------------
903
769/* 904/*
770 * Implementation 905 * Implementation
771 */ 906 */
907
772namespace detail { 908namespace detail {
773 909
774inline bool is_hex(char c, int &v) { 910inline bool is_hex(char c, int &v) {
@@ -932,8 +1068,8 @@ inline void read_file(const std::string &path, std::string &out) {
932 1068
933inline std::string file_extension(const std::string &path) { 1069inline std::string file_extension(const std::string &path) {
934 std::smatch m; 1070 std::smatch m;
935 auto pat = std::regex("\\.([a-zA-Z0-9]+)$"); 1071 static auto re = std::regex("\\.([a-zA-Z0-9]+)$");
936 if (std::regex_search(path, m, pat)) { return m[1].str(); } 1072 if (std::regex_search(path, m, re)) { return m[1].str(); }
937 return std::string(); 1073 return std::string();
938} 1074}
939 1075
@@ -976,6 +1112,11 @@ public:
976 } 1112 }
977 } 1113 }
978 1114
1115 bool end_with_crlf() const {
1116 auto end = ptr() + size();
1117 return size() >= 2 && end[-2] == '\r' && end[-1] == '\n';
1118 }
1119
979 bool getline() { 1120 bool getline() {
980 fixed_buffer_used_size_ = 0; 1121 fixed_buffer_used_size_ = 0;
981 glowable_buffer_.clear(); 1122 glowable_buffer_.clear();
@@ -1019,7 +1160,7 @@ private:
1019 Stream &strm_; 1160 Stream &strm_;
1020 char *fixed_buffer_; 1161 char *fixed_buffer_;
1021 const size_t fixed_buffer_size_; 1162 const size_t fixed_buffer_size_;
1022 size_t fixed_buffer_used_size_; 1163 size_t fixed_buffer_used_size_ = 0;
1023 std::string glowable_buffer_; 1164 std::string glowable_buffer_;
1024}; 1165};
1025 1166
@@ -1053,6 +1194,28 @@ inline int select_read(socket_t sock, time_t sec, time_t usec) {
1053#endif 1194#endif
1054} 1195}
1055 1196
1197inline int select_write(socket_t sock, time_t sec, time_t usec) {
1198#ifdef CPPHTTPLIB_USE_POLL
1199 struct pollfd pfd_read;
1200 pfd_read.fd = sock;
1201 pfd_read.events = POLLOUT;
1202
1203 auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
1204
1205 return poll(&pfd_read, 1, timeout);
1206#else
1207 fd_set fds;
1208 FD_ZERO(&fds);
1209 FD_SET(sock, &fds);
1210
1211 timeval tv;
1212 tv.tv_sec = static_cast<long>(sec);
1213 tv.tv_usec = static_cast<long>(usec);
1214
1215 return select(static_cast<int>(sock + 1), nullptr, &fds, nullptr, &tv);
1216#endif
1217}
1218
1056inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) { 1219inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) {
1057#ifdef CPPHTTPLIB_USE_POLL 1220#ifdef CPPHTTPLIB_USE_POLL
1058 struct pollfd pfd_read; 1221 struct pollfd pfd_read;
@@ -1065,7 +1228,8 @@ inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) {
1065 pfd_read.revents & (POLLIN | POLLOUT)) { 1228 pfd_read.revents & (POLLIN | POLLOUT)) {
1066 int error = 0; 1229 int error = 0;
1067 socklen_t len = sizeof(error); 1230 socklen_t len = sizeof(error);
1068 return getsockopt(sock, SOL_SOCKET, SO_ERROR, reinterpret_cast<char*>(&error), &len) >= 0 && 1231 return getsockopt(sock, SOL_SOCKET, SO_ERROR,
1232 reinterpret_cast<char *>(&error), &len) >= 0 &&
1069 !error; 1233 !error;
1070 } 1234 }
1071 return false; 1235 return false;
@@ -1085,27 +1249,86 @@ inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) {
1085 (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) { 1249 (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) {
1086 int error = 0; 1250 int error = 0;
1087 socklen_t len = sizeof(error); 1251 socklen_t len = sizeof(error);
1088 return getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *)&error, &len) >= 0 && 1252 return getsockopt(sock, SOL_SOCKET, SO_ERROR,
1253 reinterpret_cast<char *>(&error), &len) >= 0 &&
1089 !error; 1254 !error;
1090 } 1255 }
1091 return false; 1256 return false;
1092#endif 1257#endif
1093} 1258}
1094 1259
1260class SocketStream : public Stream {
1261public:
1262 SocketStream(socket_t sock, time_t read_timeout_sec,
1263 time_t read_timeout_usec);
1264 ~SocketStream() override;
1265
1266 bool is_readable() const override;
1267 bool is_writable() const override;
1268 int read(char *ptr, size_t size) override;
1269 int write(const char *ptr, size_t size) override;
1270 std::string get_remote_addr() const override;
1271
1272private:
1273 socket_t sock_;
1274 time_t read_timeout_sec_;
1275 time_t read_timeout_usec_;
1276};
1277
1278#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
1279class SSLSocketStream : public Stream {
1280public:
1281 SSLSocketStream(socket_t sock, SSL *ssl, time_t read_timeout_sec,
1282 time_t read_timeout_usec);
1283 virtual ~SSLSocketStream();
1284
1285 bool is_readable() const override;
1286 bool is_writable() const override;
1287 int read(char *ptr, size_t size) override;
1288 int write(const char *ptr, size_t size) override;
1289 std::string get_remote_addr() const override;
1290
1291private:
1292 socket_t sock_;
1293 SSL *ssl_;
1294 time_t read_timeout_sec_;
1295 time_t read_timeout_usec_;
1296};
1297#endif
1298
1299class BufferStream : public Stream {
1300public:
1301 BufferStream() = default;
1302 ~BufferStream() override = default;
1303
1304 bool is_readable() const override;
1305 bool is_writable() const override;
1306 int read(char *ptr, size_t size) override;
1307 int write(const char *ptr, size_t size) override;
1308 std::string get_remote_addr() const override;
1309
1310 const std::string &get_buffer() const;
1311
1312private:
1313 std::string buffer;
1314 int position = 0;
1315};
1316
1095template <typename T> 1317template <typename T>
1096inline bool process_and_close_socket(bool is_client_request, socket_t sock, 1318inline bool process_socket(bool is_client_request, socket_t sock,
1097 size_t keep_alive_max_count, T callback) { 1319 size_t keep_alive_max_count, time_t read_timeout_sec,
1320 time_t read_timeout_usec, T callback) {
1098 assert(keep_alive_max_count > 0); 1321 assert(keep_alive_max_count > 0);
1099 1322
1100 bool ret = false; 1323 auto ret = false;
1101 1324
1102 if (keep_alive_max_count > 1) { 1325 if (keep_alive_max_count > 1) {
1103 auto count = keep_alive_max_count; 1326 auto count = keep_alive_max_count;
1104 while (count > 0 && 1327 while (count > 0 &&
1105 (is_client_request || 1328 (is_client_request ||
1106 detail::select_read(sock, CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND, 1329 select_read(sock, CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND,
1107 CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND) > 0)) { 1330 CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND) > 0)) {
1108 SocketStream strm(sock); 1331 SocketStream strm(sock, read_timeout_sec, read_timeout_usec);
1109 auto last_connection = count == 1; 1332 auto last_connection = count == 1;
1110 auto connection_close = false; 1333 auto connection_close = false;
1111 1334
@@ -1114,12 +1337,22 @@ inline bool process_and_close_socket(bool is_client_request, socket_t sock,
1114 1337
1115 count--; 1338 count--;
1116 } 1339 }
1117 } else { 1340 } else { // keep_alive_max_count is 0 or 1
1118 SocketStream strm(sock); 1341 SocketStream strm(sock, read_timeout_sec, read_timeout_usec);
1119 auto dummy_connection_close = false; 1342 auto dummy_connection_close = false;
1120 ret = callback(strm, true, dummy_connection_close); 1343 ret = callback(strm, true, dummy_connection_close);
1121 } 1344 }
1122 1345
1346 return ret;
1347}
1348
1349template <typename T>
1350inline bool process_and_close_socket(bool is_client_request, socket_t sock,
1351 size_t keep_alive_max_count,
1352 time_t read_timeout_sec,
1353 time_t read_timeout_usec, T callback) {
1354 auto ret = process_socket(is_client_request, sock, keep_alive_max_count,
1355 read_timeout_sec, read_timeout_usec, callback);
1123 close_socket(sock); 1356 close_socket(sock);
1124 return ret; 1357 return ret;
1125} 1358}
@@ -1165,6 +1398,23 @@ socket_t create_socket(const char *host, int port, Fn fn,
1165#ifdef _WIN32 1398#ifdef _WIN32
1166 auto sock = WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, 1399 auto sock = WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol,
1167 nullptr, 0, WSA_FLAG_NO_HANDLE_INHERIT); 1400 nullptr, 0, WSA_FLAG_NO_HANDLE_INHERIT);
1401 /**
1402 * Since the WSA_FLAG_NO_HANDLE_INHERIT is only supported on Windows 7 SP1
1403 * and above the socket creation fails on older Windows Systems.
1404 *
1405 * Let's try to create a socket the old way in this case.
1406 *
1407 * Reference:
1408 * https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa
1409 *
1410 * WSA_FLAG_NO_HANDLE_INHERIT:
1411 * This flag is supported on Windows 7 with SP1, Windows Server 2008 R2 with
1412 * SP1, and later
1413 *
1414 */
1415 if (sock == INVALID_SOCKET) {
1416 sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
1417 }
1168#else 1418#else
1169 auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); 1419 auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
1170#endif 1420#endif
@@ -1176,9 +1426,11 @@ socket_t create_socket(const char *host, int port, Fn fn,
1176 1426
1177 // Make 'reuse address' option available 1427 // Make 'reuse address' option available
1178 int yes = 1; 1428 int yes = 1;
1179 setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char*>(&yes), sizeof(yes)); 1429 setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char *>(&yes),
1430 sizeof(yes));
1180#ifdef SO_REUSEPORT 1431#ifdef SO_REUSEPORT
1181 setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast<char*>(&yes), sizeof(yes)); 1432 setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast<char *>(&yes),
1433 sizeof(yes));
1182#endif 1434#endif
1183 1435
1184 // bind or connect 1436 // bind or connect
@@ -1213,27 +1465,105 @@ inline bool is_connection_error() {
1213#endif 1465#endif
1214} 1466}
1215 1467
1468inline bool bind_ip_address(socket_t sock, const char *host) {
1469 struct addrinfo hints;
1470 struct addrinfo *result;
1471
1472 memset(&hints, 0, sizeof(struct addrinfo));
1473 hints.ai_family = AF_UNSPEC;
1474 hints.ai_socktype = SOCK_STREAM;
1475 hints.ai_protocol = 0;
1476
1477 if (getaddrinfo(host, "0", &hints, &result)) { return false; }
1478
1479 auto ret = false;
1480 for (auto rp = result; rp; rp = rp->ai_next) {
1481 const auto &ai = *rp;
1482 if (!::bind(sock, ai.ai_addr, static_cast<int>(ai.ai_addrlen))) {
1483 ret = true;
1484 break;
1485 }
1486 }
1487
1488 freeaddrinfo(result);
1489 return ret;
1490}
1491
1492inline std::string if2ip(const std::string &ifn) {
1493#ifndef _WIN32
1494 struct ifaddrs *ifap;
1495 getifaddrs(&ifap);
1496 for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) {
1497 if (ifa->ifa_addr && ifn == ifa->ifa_name) {
1498 if (ifa->ifa_addr->sa_family == AF_INET) {
1499 auto sa = reinterpret_cast<struct sockaddr_in *>(ifa->ifa_addr);
1500 char buf[INET_ADDRSTRLEN];
1501 if (inet_ntop(AF_INET, &sa->sin_addr, buf, INET_ADDRSTRLEN)) {
1502 freeifaddrs(ifap);
1503 return std::string(buf, INET_ADDRSTRLEN);
1504 }
1505 }
1506 }
1507 }
1508 freeifaddrs(ifap);
1509#endif
1510 return std::string();
1511}
1512
1513inline socket_t create_client_socket(const char *host, int port,
1514 time_t timeout_sec,
1515 const std::string &intf) {
1516 return create_socket(
1517 host, port, [&](socket_t sock, struct addrinfo &ai) -> bool {
1518 if (!intf.empty()) {
1519 auto ip = if2ip(intf);
1520 if (ip.empty()) { ip = intf; }
1521 if (!bind_ip_address(sock, ip.c_str())) { return false; }
1522 }
1523
1524 set_nonblocking(sock, true);
1525
1526 auto ret = ::connect(sock, ai.ai_addr, static_cast<int>(ai.ai_addrlen));
1527 if (ret < 0) {
1528 if (is_connection_error() ||
1529 !wait_until_socket_is_ready(sock, timeout_sec, 0)) {
1530 close_socket(sock);
1531 return false;
1532 }
1533 }
1534
1535 set_nonblocking(sock, false);
1536 return true;
1537 });
1538}
1539
1216inline std::string get_remote_addr(socket_t sock) { 1540inline std::string get_remote_addr(socket_t sock) {
1217 struct sockaddr_storage addr; 1541 struct sockaddr_storage addr;
1218 socklen_t len = sizeof(addr); 1542 socklen_t len = sizeof(addr);
1219 1543
1220 if (!getpeername(sock, reinterpret_cast<struct sockaddr *>(&addr), &len)) { 1544 if (!getpeername(sock, reinterpret_cast<struct sockaddr *>(&addr), &len)) {
1221 char ipstr[NI_MAXHOST]; 1545 std::array<char, NI_MAXHOST> ipstr{};
1222 1546
1223 if (!getnameinfo(reinterpret_cast<struct sockaddr *>(&addr), len, ipstr, sizeof(ipstr), 1547 if (!getnameinfo(reinterpret_cast<struct sockaddr *>(&addr), len,
1224 nullptr, 0, NI_NUMERICHOST)) { 1548 ipstr.data(), static_cast<unsigned int>(ipstr.size()), nullptr, 0, NI_NUMERICHOST)) {
1225 return ipstr; 1549 return ipstr.data();
1226 } 1550 }
1227 } 1551 }
1228 1552
1229 return std::string(); 1553 return std::string();
1230} 1554}
1231 1555
1232inline const char *find_content_type(const std::string &path) { 1556inline const char *
1557find_content_type(const std::string &path,
1558 const std::map<std::string, std::string> &user_data) {
1233 auto ext = file_extension(path); 1559 auto ext = file_extension(path);
1560
1561 auto it = user_data.find(ext);
1562 if (it != user_data.end()) { return it->second.c_str(); }
1563
1234 if (ext == "txt") { 1564 if (ext == "txt") {
1235 return "text/plain"; 1565 return "text/plain";
1236 } else if (ext == "html") { 1566 } else if (ext == "html" || ext == "htm") {
1237 return "text/html"; 1567 return "text/html";
1238 } else if (ext == "css") { 1568 } else if (ext == "css") {
1239 return "text/css"; 1569 return "text/css";
@@ -1253,6 +1583,8 @@ inline const char *find_content_type(const std::string &path) {
1253 return "application/pdf"; 1583 return "application/pdf";
1254 } else if (ext == "js") { 1584 } else if (ext == "js") {
1255 return "application/javascript"; 1585 return "application/javascript";
1586 } else if (ext == "wasm") {
1587 return "application/wasm";
1256 } else if (ext == "xml") { 1588 } else if (ext == "xml") {
1257 return "application/xml"; 1589 return "application/xml";
1258 } else if (ext == "xhtml") { 1590 } else if (ext == "xhtml") {
@@ -1263,19 +1595,25 @@ inline const char *find_content_type(const std::string &path) {
1263 1595
1264inline const char *status_message(int status) { 1596inline const char *status_message(int status) {
1265 switch (status) { 1597 switch (status) {
1598 case 100: return "Continue";
1266 case 200: return "OK"; 1599 case 200: return "OK";
1600 case 202: return "Accepted";
1601 case 204: return "No Content";
1267 case 206: return "Partial Content"; 1602 case 206: return "Partial Content";
1268 case 301: return "Moved Permanently"; 1603 case 301: return "Moved Permanently";
1269 case 302: return "Found"; 1604 case 302: return "Found";
1270 case 303: return "See Other"; 1605 case 303: return "See Other";
1271 case 304: return "Not Modified"; 1606 case 304: return "Not Modified";
1272 case 400: return "Bad Request"; 1607 case 400: return "Bad Request";
1608 case 401: return "Unauthorized";
1273 case 403: return "Forbidden"; 1609 case 403: return "Forbidden";
1274 case 404: return "Not Found"; 1610 case 404: return "Not Found";
1275 case 413: return "Payload Too Large"; 1611 case 413: return "Payload Too Large";
1276 case 414: return "Request-URI Too Long"; 1612 case 414: return "Request-URI Too Long";
1277 case 415: return "Unsupported Media Type"; 1613 case 415: return "Unsupported Media Type";
1278 case 416: return "Range Not Satisfiable"; 1614 case 416: return "Range Not Satisfiable";
1615 case 417: return "Expectation Failed";
1616 case 503: return "Service Unavailable";
1279 1617
1280 default: 1618 default:
1281 case 500: return "Internal Server Error"; 1619 case 500: return "Internal Server Error";
@@ -1302,18 +1640,18 @@ inline bool compress(std::string &content) {
1302 if (ret != Z_OK) { return false; } 1640 if (ret != Z_OK) { return false; }
1303 1641
1304 strm.avail_in = content.size(); 1642 strm.avail_in = content.size();
1305 strm.next_in = const_cast<Bytef*>(reinterpret_cast<const Bytef*>(content.data())); 1643 strm.next_in =
1644 const_cast<Bytef *>(reinterpret_cast<const Bytef *>(content.data()));
1306 1645
1307 std::string compressed; 1646 std::string compressed;
1308 1647
1309 const auto bufsiz = 16384; 1648 std::array<char, 16384> buff{};
1310 char buff[bufsiz];
1311 do { 1649 do {
1312 strm.avail_out = bufsiz; 1650 strm.avail_out = buff.size();
1313 strm.next_out = reinterpret_cast<Bytef*>(buff); 1651 strm.next_out = reinterpret_cast<Bytef *>(buff.data());
1314 ret = deflate(&strm, Z_FINISH); 1652 ret = deflate(&strm, Z_FINISH);
1315 assert(ret != Z_STREAM_ERROR); 1653 assert(ret != Z_STREAM_ERROR);
1316 compressed.append(buff, bufsiz - strm.avail_out); 1654 compressed.append(buff.data(), buff.size() - strm.avail_out);
1317 } while (strm.avail_out == 0); 1655 } while (strm.avail_out == 0);
1318 1656
1319 assert(ret == Z_STREAM_END); 1657 assert(ret == Z_STREAM_END);
@@ -1347,13 +1685,12 @@ public:
1347 int ret = Z_OK; 1685 int ret = Z_OK;
1348 1686
1349 strm.avail_in = data_length; 1687 strm.avail_in = data_length;
1350 strm.next_in = const_cast<Bytef*>(reinterpret_cast<const Bytef *>(data)); 1688 strm.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
1351 1689
1352 const auto bufsiz = 16384; 1690 std::array<char, 16384> buff{};
1353 char buff[bufsiz];
1354 do { 1691 do {
1355 strm.avail_out = bufsiz; 1692 strm.avail_out = buff.size();
1356 strm.next_out = reinterpret_cast<Bytef*>(buff); 1693 strm.next_out = reinterpret_cast<Bytef *>(buff.data());
1357 1694
1358 ret = inflate(&strm, Z_NO_FLUSH); 1695 ret = inflate(&strm, Z_NO_FLUSH);
1359 assert(ret != Z_STREAM_ERROR); 1696 assert(ret != Z_STREAM_ERROR);
@@ -1363,10 +1700,12 @@ public:
1363 case Z_MEM_ERROR: inflateEnd(&strm); return false; 1700 case Z_MEM_ERROR: inflateEnd(&strm); return false;
1364 } 1701 }
1365 1702
1366 if (!callback(buff, bufsiz - strm.avail_out)) { return false; } 1703 if (!callback(buff.data(), buff.size() - strm.avail_out)) {
1704 return false;
1705 }
1367 } while (strm.avail_out == 0); 1706 } while (strm.avail_out == 0);
1368 1707
1369 return ret == Z_STREAM_END; 1708 return ret == Z_OK || ret == Z_STREAM_END;
1370 } 1709 }
1371 1710
1372private: 1711private:
@@ -1397,18 +1736,35 @@ inline uint64_t get_header_value_uint64(const Headers &headers, const char *key,
1397} 1736}
1398 1737
1399inline bool read_headers(Stream &strm, Headers &headers) { 1738inline bool read_headers(Stream &strm, Headers &headers) {
1400 static std::regex re(R"((.+?):\s*(.+?)\s*\r\n)");
1401
1402 const auto bufsiz = 2048; 1739 const auto bufsiz = 2048;
1403 char buf[bufsiz]; 1740 char buf[bufsiz];
1404 1741 stream_line_reader line_reader(strm, buf, bufsiz);
1405 stream_line_reader reader(strm, buf, bufsiz);
1406 1742
1407 for (;;) { 1743 for (;;) {
1408 if (!reader.getline()) { return false; } 1744 if (!line_reader.getline()) { return false; }
1409 if (!strcmp(reader.ptr(), "\r\n")) { break; } 1745
1746 // Check if the line ends with CRLF.
1747 if (line_reader.end_with_crlf()) {
1748 // Blank line indicates end of headers.
1749 if (line_reader.size() == 2) { break; }
1750 } else {
1751 continue; // Skip invalid line.
1752 }
1753
1754 // Skip trailing spaces and tabs.
1755 auto end = line_reader.ptr() + line_reader.size() - 2;
1756 while (line_reader.ptr() < end && (end[-1] == ' ' || end[-1] == '\t')) {
1757 end--;
1758 }
1759
1760 // Horizontal tab and ' ' are considered whitespace and are ignored when on
1761 // the left or right side of the header value:
1762 // - https://stackoverflow.com/questions/50179659/
1763 // - https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html
1764 static const std::regex re(R"((.+?):[\t ]*(.+))");
1765
1410 std::cmatch m; 1766 std::cmatch m;
1411 if (std::regex_match(reader.ptr(), m, re)) { 1767 if (std::regex_match(line_reader.ptr(), end, m, re)) {
1412 auto key = std::string(m[1]); 1768 auto key = std::string(m[1]);
1413 auto val = std::string(m[2]); 1769 auto val = std::string(m[2]);
1414 headers.emplace(key, val); 1770 headers.emplace(key, val);
@@ -1418,12 +1774,8 @@ inline bool read_headers(Stream &strm, Headers &headers) {
1418 return true; 1774 return true;
1419} 1775}
1420 1776
1421typedef std::function<bool(const char *data, size_t data_length)>
1422 ContentReceiverCore;
1423
1424inline bool read_content_with_length(Stream &strm, uint64_t len, 1777inline bool read_content_with_length(Stream &strm, uint64_t len,
1425 Progress progress, 1778 Progress progress, ContentReceiver out) {
1426 ContentReceiverCore out) {
1427 char buf[CPPHTTPLIB_RECV_BUFSIZ]; 1779 char buf[CPPHTTPLIB_RECV_BUFSIZ];
1428 1780
1429 uint64_t r = 0; 1781 uint64_t r = 0;
@@ -1455,7 +1807,7 @@ inline void skip_content_with_length(Stream &strm, uint64_t len) {
1455 } 1807 }
1456} 1808}
1457 1809
1458inline bool read_content_without_length(Stream &strm, ContentReceiverCore out) { 1810inline bool read_content_without_length(Stream &strm, ContentReceiver out) {
1459 char buf[CPPHTTPLIB_RECV_BUFSIZ]; 1811 char buf[CPPHTTPLIB_RECV_BUFSIZ];
1460 for (;;) { 1812 for (;;) {
1461 auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ); 1813 auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ);
@@ -1470,33 +1822,34 @@ inline bool read_content_without_length(Stream &strm, ContentReceiverCore out) {
1470 return true; 1822 return true;
1471} 1823}
1472 1824
1473inline bool read_content_chunked(Stream &strm, ContentReceiverCore out) { 1825inline bool read_content_chunked(Stream &strm, ContentReceiver out) {
1474 const auto bufsiz = 16; 1826 const auto bufsiz = 16;
1475 char buf[bufsiz]; 1827 char buf[bufsiz];
1476 1828
1477 stream_line_reader reader(strm, buf, bufsiz); 1829 stream_line_reader line_reader(strm, buf, bufsiz);
1478 1830
1479 if (!reader.getline()) { return false; } 1831 if (!line_reader.getline()) { return false; }
1480 1832
1481 auto chunk_len = std::stoi(reader.ptr(), 0, 16); 1833 auto chunk_len = std::stoi(line_reader.ptr(), 0, 16);
1482 1834
1483 while (chunk_len > 0) { 1835 while (chunk_len > 0) {
1484 if (!read_content_with_length(strm, chunk_len, nullptr, out)) { 1836 if (!read_content_with_length(strm, chunk_len, nullptr, out)) {
1485 return false; 1837 return false;
1486 } 1838 }
1487 1839
1488 if (!reader.getline()) { return false; } 1840 if (!line_reader.getline()) { return false; }
1489 1841
1490 if (strcmp(reader.ptr(), "\r\n")) { break; } 1842 if (strcmp(line_reader.ptr(), "\r\n")) { break; }
1491 1843
1492 if (!reader.getline()) { return false; } 1844 if (!line_reader.getline()) { return false; }
1493 1845
1494 chunk_len = std::stoi(reader.ptr(), 0, 16); 1846 chunk_len = std::stoi(line_reader.ptr(), 0, 16);
1495 } 1847 }
1496 1848
1497 if (chunk_len == 0) { 1849 if (chunk_len == 0) {
1498 // Reader terminator after chunks 1850 // Reader terminator after chunks
1499 if (!reader.getline() || strcmp(reader.ptr(), "\r\n")) return false; 1851 if (!line_reader.getline() || strcmp(line_reader.ptr(), "\r\n"))
1852 return false;
1500 } 1853 }
1501 1854
1502 return true; 1855 return true;
@@ -1509,14 +1862,14 @@ inline bool is_chunked_transfer_encoding(const Headers &headers) {
1509 1862
1510template <typename T> 1863template <typename T>
1511bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status, 1864bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
1512 Progress progress, ContentReceiverCore receiver) { 1865 Progress progress, ContentReceiver receiver) {
1513 1866
1514 ContentReceiverCore out = [&](const char *buf, size_t n) { 1867 ContentReceiver out = [&](const char *buf, size_t n) {
1515 return receiver(buf, n); 1868 return receiver(buf, n);
1516 }; 1869 };
1517 1870
1518#ifdef CPPHTTPLIB_ZLIB_SUPPORT 1871#ifdef CPPHTTPLIB_ZLIB_SUPPORT
1519 detail::decompressor decompressor; 1872 decompressor decompressor;
1520 1873
1521 if (!decompressor.is_valid()) { 1874 if (!decompressor.is_valid()) {
1522 status = 500; 1875 status = 500;
@@ -1586,39 +1939,47 @@ inline ssize_t write_content(Stream &strm, ContentProvider content_provider,
1586 size_t end_offset = offset + length; 1939 size_t end_offset = offset + length;
1587 while (offset < end_offset) { 1940 while (offset < end_offset) {
1588 ssize_t written_length = 0; 1941 ssize_t written_length = 0;
1589 content_provider( 1942
1590 offset, end_offset - offset, 1943 DataSink data_sink;
1591 [&](const char *d, size_t l) { 1944 data_sink.write = [&](const char *d, size_t l) {
1592 offset += l; 1945 offset += l;
1593 written_length = strm.write(d, l); 1946 written_length = strm.write(d, l);
1594 }, 1947 };
1595 [&](void) { written_length = -1; }); 1948 data_sink.done = [&](void) { written_length = -1; };
1949 data_sink.is_writable = [&](void) { return strm.is_writable(); };
1950
1951 content_provider(offset, end_offset - offset, data_sink);
1596 if (written_length < 0) { return written_length; } 1952 if (written_length < 0) { return written_length; }
1597 } 1953 }
1598 return static_cast<ssize_t>(offset - begin_offset); 1954 return static_cast<ssize_t>(offset - begin_offset);
1599} 1955}
1600 1956
1957template <typename T>
1601inline ssize_t write_content_chunked(Stream &strm, 1958inline ssize_t write_content_chunked(Stream &strm,
1602 ContentProvider content_provider) { 1959 ContentProvider content_provider,
1960 T is_shutting_down) {
1603 size_t offset = 0; 1961 size_t offset = 0;
1604 auto data_available = true; 1962 auto data_available = true;
1605 ssize_t total_written_length = 0; 1963 ssize_t total_written_length = 0;
1606 while (data_available) { 1964 while (data_available && !is_shutting_down()) {
1607 ssize_t written_length = 0; 1965 ssize_t written_length = 0;
1608 content_provider( 1966
1609 offset, 0, 1967 DataSink data_sink;
1610 [&](const char *d, size_t l) { 1968 data_sink.write = [&](const char *d, size_t l) {
1611 data_available = l > 0; 1969 data_available = l > 0;
1612 offset += l; 1970 offset += l;
1613 1971
1614 // Emit chunked response header and footer for each chunk 1972 // Emit chunked response header and footer for each chunk
1615 auto chunk = from_i_to_hex(l) + "\r\n" + std::string(d, l) + "\r\n"; 1973 auto chunk = from_i_to_hex(l) + "\r\n" + std::string(d, l) + "\r\n";
1616 written_length = strm.write(chunk); 1974 written_length = strm.write(chunk);
1617 }, 1975 };
1618 [&](void) { 1976 data_sink.done = [&](void) {
1619 data_available = false; 1977 data_available = false;
1620 written_length = strm.write("0\r\n\r\n"); 1978 written_length = strm.write("0\r\n\r\n");
1621 }); 1979 };
1980 data_sink.is_writable = [&](void) { return strm.is_writable(); };
1981
1982 content_provider(offset, 0, data_sink);
1622 1983
1623 if (written_length < 0) { return written_length; } 1984 if (written_length < 0) { return written_length; }
1624 total_written_length += written_length; 1985 total_written_length += written_length;
@@ -1629,17 +1990,12 @@ inline ssize_t write_content_chunked(Stream &strm,
1629template <typename T> 1990template <typename T>
1630inline bool redirect(T &cli, const Request &req, Response &res, 1991inline bool redirect(T &cli, const Request &req, Response &res,
1631 const std::string &path) { 1992 const std::string &path) {
1632 Request new_req; 1993 Request new_req = req;
1633 new_req.method = req.method;
1634 new_req.path = path; 1994 new_req.path = path;
1635 new_req.headers = req.headers; 1995 new_req.redirect_count -= 1;
1636 new_req.body = req.body;
1637 new_req.redirect_count = req.redirect_count - 1;
1638 new_req.response_handler = req.response_handler;
1639 new_req.content_receiver = req.content_receiver;
1640 new_req.progress = req.progress;
1641 1996
1642 Response new_res; 1997 Response new_res;
1998
1643 auto ret = cli.send(new_req, new_res); 1999 auto ret = cli.send(new_req, new_res);
1644 if (ret) { res = new_res; } 2000 if (ret) { res = new_res; }
1645 return ret; 2001 return ret;
@@ -1656,7 +2012,7 @@ inline std::string encode_url(const std::string &s) {
1656 case '\n': result += "%0A"; break; 2012 case '\n': result += "%0A"; break;
1657 case '\'': result += "%27"; break; 2013 case '\'': result += "%27"; break;
1658 case ',': result += "%2C"; break; 2014 case ',': result += "%2C"; break;
1659 case ':': result += "%3A"; break; 2015 // case ':': result += "%3A"; break; // ok? probably...
1660 case ';': result += "%3B"; break; 2016 case ';': result += "%3B"; break;
1661 default: 2017 default:
1662 auto c = static_cast<uint8_t>(s[i]); 2018 auto c = static_cast<uint8_t>(s[i]);
@@ -1716,11 +2072,11 @@ inline void parse_query_text(const std::string &s, Params &params) {
1716 split(&s[0], &s[s.size()], '&', [&](const char *b, const char *e) { 2072 split(&s[0], &s[s.size()], '&', [&](const char *b, const char *e) {
1717 std::string key; 2073 std::string key;
1718 std::string val; 2074 std::string val;
1719 split(b, e, '=', [&](const char *b, const char *e) { 2075 split(b, e, '=', [&](const char *b2, const char *e2) {
1720 if (key.empty()) { 2076 if (key.empty()) {
1721 key.assign(b, e); 2077 key.assign(b2, e2);
1722 } else { 2078 } else {
1723 val.assign(b, e); 2079 val.assign(b2, e2);
1724 } 2080 }
1725 }); 2081 });
1726 params.emplace(key, decode_url(val)); 2082 params.emplace(key, decode_url(val));
@@ -1736,112 +2092,207 @@ inline bool parse_multipart_boundary(const std::string &content_type,
1736 return true; 2092 return true;
1737} 2093}
1738 2094
1739inline bool parse_multipart_formdata(const std::string &boundary, 2095inline bool parse_range_header(const std::string &s, Ranges &ranges) {
1740 const std::string &body, 2096 static auto re_first_range = std::regex(R"(bytes=(\d*-\d*(?:,\s*\d*-\d*)*))");
1741 MultipartFiles &files) { 2097 std::smatch m;
1742 static std::string dash = "--"; 2098 if (std::regex_match(s, m, re_first_range)) {
1743 static std::string crlf = "\r\n"; 2099 auto pos = m.position(1);
1744 2100 auto len = m.length(1);
1745 static std::regex re_content_type("Content-Type: (.*?)", 2101 bool all_valid_ranges = true;
1746 std::regex_constants::icase); 2102 split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) {
1747 2103 if (!all_valid_ranges) return;
1748 static std::regex re_content_disposition( 2104 static auto re_another_range = std::regex(R"(\s*(\d*)-(\d*))");
1749 "Content-Disposition: form-data; name=\"(.*?)\"(?:; filename=\"(.*?)\")?", 2105 std::cmatch cm;
1750 std::regex_constants::icase); 2106 if (std::regex_match(b, e, cm, re_another_range)) {
1751 2107 ssize_t first = -1;
1752 auto dash_boundary = dash + boundary; 2108 if (!cm.str(1).empty()) {
1753 2109 first = static_cast<ssize_t>(std::stoll(cm.str(1)));
1754 auto pos = body.find(dash_boundary); 2110 }
1755 if (pos != 0) { return false; }
1756
1757 pos += dash_boundary.size();
1758 2111
1759 auto next_pos = body.find(crlf, pos); 2112 ssize_t last = -1;
1760 if (next_pos == std::string::npos) { return false; } 2113 if (!cm.str(2).empty()) {
2114 last = static_cast<ssize_t>(std::stoll(cm.str(2)));
2115 }
1761 2116
1762 pos = next_pos + crlf.size(); 2117 if (first != -1 && last != -1 && first > last) {
2118 all_valid_ranges = false;
2119 return;
2120 }
2121 ranges.emplace_back(std::make_pair(first, last));
2122 }
2123 });
2124 return all_valid_ranges;
2125 }
2126 return false;
2127}
1763 2128
1764 while (pos < body.size()) { 2129class MultipartFormDataParser {
1765 next_pos = body.find(crlf, pos); 2130public:
1766 if (next_pos == std::string::npos) { return false; } 2131 MultipartFormDataParser() {}
1767 2132
1768 std::string name; 2133 void set_boundary(const std::string &boundary) { boundary_ = boundary; }
1769 MultipartFile file;
1770 2134
1771 auto header = body.substr(pos, (next_pos - pos)); 2135 bool is_valid() const { return is_valid_; }
1772 2136
1773 while (pos != next_pos) { 2137 template <typename T, typename U>
1774 std::smatch m; 2138 bool parse(const char *buf, size_t n, T content_callback, U header_callback) {
1775 if (std::regex_match(header, m, re_content_type)) { 2139 static const std::regex re_content_type(R"(^Content-Type:\s*(.*?)\s*$)",
1776 file.content_type = m[1]; 2140 std::regex_constants::icase);
1777 } else if (std::regex_match(header, m, re_content_disposition)) { 2141
1778 name = m[1]; 2142 static const std::regex re_content_disposition(
1779 file.filename = m[2]; 2143 "^Content-Disposition:\\s*form-data;\\s*name=\"(.*?)\"(?:;\\s*filename="
2144 "\"(.*?)\")?\\s*$",
2145 std::regex_constants::icase);
2146
2147 buf_.append(buf, n); // TODO: performance improvement
2148
2149 while (!buf_.empty()) {
2150 switch (state_) {
2151 case 0: { // Initial boundary
2152 auto pattern = dash_ + boundary_ + crlf_;
2153 if (pattern.size() > buf_.size()) { return true; }
2154 auto pos = buf_.find(pattern);
2155 if (pos != 0) {
2156 is_done_ = true;
2157 return false;
2158 }
2159 buf_.erase(0, pattern.size());
2160 off_ += pattern.size();
2161 state_ = 1;
2162 break;
1780 } 2163 }
2164 case 1: { // New entry
2165 clear_file_info();
2166 state_ = 2;
2167 break;
2168 }
2169 case 2: { // Headers
2170 auto pos = buf_.find(crlf_);
2171 while (pos != std::string::npos) {
2172 // Empty line
2173 if (pos == 0) {
2174 if (!header_callback(file_)) {
2175 is_valid_ = false;
2176 is_done_ = false;
2177 return false;
2178 }
2179 buf_.erase(0, crlf_.size());
2180 off_ += crlf_.size();
2181 state_ = 3;
2182 break;
2183 }
1781 2184
1782 pos = next_pos + crlf.size(); 2185 auto header = buf_.substr(0, pos);
1783 2186 {
1784 next_pos = body.find(crlf, pos); 2187 std::smatch m;
1785 if (next_pos == std::string::npos) { return false; } 2188 if (std::regex_match(header, m, re_content_type)) {
1786 2189 file_.content_type = m[1];
1787 header = body.substr(pos, (next_pos - pos)); 2190 } else if (std::regex_match(header, m, re_content_disposition)) {
1788 } 2191 file_.name = m[1];
1789 2192 file_.filename = m[2];
1790 pos = next_pos + crlf.size(); 2193 }
2194 }
1791 2195
1792 next_pos = body.find(crlf + dash_boundary, pos); 2196 buf_.erase(0, pos + crlf_.size());
2197 off_ += pos + crlf_.size();
2198 pos = buf_.find(crlf_);
2199 }
2200 break;
2201 }
2202 case 3: { // Body
2203 {
2204 auto pattern = crlf_ + dash_;
2205 if (pattern.size() > buf_.size()) { return true; }
2206
2207 auto pos = buf_.find(pattern);
2208 if (pos == std::string::npos) { pos = buf_.size(); }
2209 if (!content_callback(buf_.data(), pos)) {
2210 is_valid_ = false;
2211 is_done_ = false;
2212 return false;
2213 }
1793 2214
1794 if (next_pos == std::string::npos) { return false; } 2215 off_ += pos;
2216 buf_.erase(0, pos);
2217 }
1795 2218
1796 file.offset = pos; 2219 {
1797 file.length = next_pos - pos; 2220 auto pattern = crlf_ + dash_ + boundary_;
2221 if (pattern.size() > buf_.size()) { return true; }
2222
2223 auto pos = buf_.find(pattern);
2224 if (pos != std::string::npos) {
2225 if (!content_callback(buf_.data(), pos)) {
2226 is_valid_ = false;
2227 is_done_ = false;
2228 return false;
2229 }
1798 2230
1799 pos = next_pos + crlf.size() + dash_boundary.size(); 2231 off_ += pos + pattern.size();
2232 buf_.erase(0, pos + pattern.size());
2233 state_ = 4;
2234 } else {
2235 if (!content_callback(buf_.data(), pattern.size())) {
2236 is_valid_ = false;
2237 is_done_ = false;
2238 return false;
2239 }
1800 2240
1801 next_pos = body.find(crlf, pos); 2241 off_ += pattern.size();
1802 if (next_pos == std::string::npos) { return false; } 2242 buf_.erase(0, pattern.size());
2243 }
2244 }
2245 break;
2246 }
2247 case 4: { // Boundary
2248 if (crlf_.size() > buf_.size()) { return true; }
2249 if (buf_.find(crlf_) == 0) {
2250 buf_.erase(0, crlf_.size());
2251 off_ += crlf_.size();
2252 state_ = 1;
2253 } else {
2254 auto pattern = dash_ + crlf_;
2255 if (pattern.size() > buf_.size()) { return true; }
2256 if (buf_.find(pattern) == 0) {
2257 buf_.erase(0, pattern.size());
2258 off_ += pattern.size();
2259 is_valid_ = true;
2260 state_ = 5;
2261 } else {
2262 is_done_ = true;
2263 return true;
2264 }
2265 }
2266 break;
2267 }
2268 case 5: { // Done
2269 is_valid_ = false;
2270 return false;
2271 }
2272 }
2273 }
1803 2274
1804 files.emplace(name, file); 2275 return true;
2276 }
1805 2277
1806 pos = next_pos + crlf.size(); 2278private:
2279 void clear_file_info() {
2280 file_.name.clear();
2281 file_.filename.clear();
2282 file_.content_type.clear();
1807 } 2283 }
1808 2284
1809 return true; 2285 const std::string dash_ = "--";
1810} 2286 const std::string crlf_ = "\r\n";
2287 std::string boundary_;
1811 2288
1812inline bool parse_range_header(const std::string &s, Ranges &ranges) { 2289 std::string buf_;
1813 try { 2290 size_t state_ = 0;
1814 static auto re = std::regex(R"(bytes=(\d*-\d*(?:,\s*\d*-\d*)*))"); 2291 size_t is_valid_ = false;
1815 std::smatch m; 2292 size_t is_done_ = false;
1816 if (std::regex_match(s, m, re)) { 2293 size_t off_ = 0;
1817 auto pos = m.position(1); 2294 MultipartFormData file_;
1818 auto len = m.length(1); 2295};
1819 detail::split(&s[pos], &s[pos + len], ',',
1820 [&](const char *b, const char *e) {
1821 static auto re = std::regex(R"(\s*(\d*)-(\d*))");
1822 std::cmatch m;
1823 if (std::regex_match(b, e, m, re)) {
1824 ssize_t first = -1;
1825 if (!m.str(1).empty()) {
1826 first = static_cast<ssize_t>(std::stoll(m.str(1)));
1827 }
1828
1829 ssize_t last = -1;
1830 if (!m.str(2).empty()) {
1831 last = static_cast<ssize_t>(std::stoll(m.str(2)));
1832 }
1833
1834 if (first != -1 && last != -1 && first > last) {
1835 throw std::runtime_error("invalid range error");
1836 }
1837 ranges.emplace_back(std::make_pair(first, last));
1838 }
1839 });
1840 return true;
1841 }
1842 return false;
1843 } catch (...) { return false; }
1844}
1845 2296
1846inline std::string to_lower(const char *beg, const char *end) { 2297inline std::string to_lower(const char *beg, const char *end) {
1847 std::string out; 2298 std::string out;
@@ -1915,7 +2366,7 @@ bool process_multipart_ranges_data(const Request &req, Response &res,
1915 ctoken("\r\n"); 2366 ctoken("\r\n");
1916 } 2367 }
1917 2368
1918 auto offsets = detail::get_range_offset_and_length(req, res.body.size(), i); 2369 auto offsets = get_range_offset_and_length(req, res.body.size(), i);
1919 auto offset = offsets.first; 2370 auto offset = offsets.first;
1920 auto length = offsets.second; 2371 auto length = offsets.second;
1921 2372
@@ -1978,8 +2429,7 @@ inline bool write_multipart_ranges_data(Stream &strm, const Request &req,
1978 [&](const std::string &token) { strm.write(token); }, 2429 [&](const std::string &token) { strm.write(token); },
1979 [&](const char *token) { strm.write(token); }, 2430 [&](const char *token) { strm.write(token); },
1980 [&](size_t offset, size_t length) { 2431 [&](size_t offset, size_t length) {
1981 return detail::write_content(strm, res.content_provider, offset, 2432 return write_content(strm, res.content_provider, offset, length) >= 0;
1982 length) >= 0;
1983 }); 2433 });
1984} 2434}
1985 2435
@@ -1988,11 +2438,56 @@ get_range_offset_and_length(const Request &req, const Response &res,
1988 size_t index) { 2438 size_t index) {
1989 auto r = req.ranges[index]; 2439 auto r = req.ranges[index];
1990 2440
1991 if (r.second == -1) { r.second = res.content_provider_resource_length - 1; } 2441 if (r.second == -1) { r.second = res.content_length - 1; }
1992 2442
1993 return std::make_pair(r.first, r.second - r.first + 1); 2443 return std::make_pair(r.first, r.second - r.first + 1);
1994} 2444}
1995 2445
2446inline bool expect_content(const Request &req) {
2447 if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" ||
2448 req.method == "PRI") {
2449 return true;
2450 }
2451 // TODO: check if Content-Length is set
2452 return false;
2453}
2454
2455#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
2456template <typename CTX, typename Init, typename Update, typename Final>
2457inline std::string message_digest(const std::string &s, Init init,
2458 Update update, Final final,
2459 size_t digest_length) {
2460 using namespace std;
2461
2462 std::vector<unsigned char> md(digest_length, 0);
2463 CTX ctx;
2464 init(&ctx);
2465 update(&ctx, s.data(), s.size());
2466 final(md.data(), &ctx);
2467
2468 stringstream ss;
2469 for (auto c : md) {
2470 ss << setfill('0') << setw(2) << hex << (unsigned int)c;
2471 }
2472 return ss.str();
2473}
2474
2475inline std::string MD5(const std::string &s) {
2476 return message_digest<MD5_CTX>(s, MD5_Init, MD5_Update, MD5_Final,
2477 MD5_DIGEST_LENGTH);
2478}
2479
2480inline std::string SHA_256(const std::string &s) {
2481 return message_digest<SHA256_CTX>(s, SHA256_Init, SHA256_Update, SHA256_Final,
2482 SHA256_DIGEST_LENGTH);
2483}
2484
2485inline std::string SHA_512(const std::string &s) {
2486 return message_digest<SHA512_CTX>(s, SHA512_Init, SHA512_Update, SHA512_Final,
2487 SHA512_DIGEST_LENGTH);
2488}
2489#endif
2490
1996#ifdef _WIN32 2491#ifdef _WIN32
1997class WSInit { 2492class WSInit {
1998public: 2493public:
@@ -2025,9 +2520,103 @@ inline std::pair<std::string, std::string> make_range_header(Ranges ranges) {
2025 2520
2026inline std::pair<std::string, std::string> 2521inline std::pair<std::string, std::string>
2027make_basic_authentication_header(const std::string &username, 2522make_basic_authentication_header(const std::string &username,
2028 const std::string &password) { 2523 const std::string &password,
2524 bool is_proxy = false) {
2029 auto field = "Basic " + detail::base64_encode(username + ":" + password); 2525 auto field = "Basic " + detail::base64_encode(username + ":" + password);
2030 return std::make_pair("Authorization", field); 2526 auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
2527 return std::make_pair(key, field);
2528}
2529
2530#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
2531inline std::pair<std::string, std::string> make_digest_authentication_header(
2532 const Request &req, const std::map<std::string, std::string> &auth,
2533 size_t cnonce_count, const std::string &cnonce, const std::string &username,
2534 const std::string &password, bool is_proxy = false) {
2535 using namespace std;
2536
2537 string nc;
2538 {
2539 stringstream ss;
2540 ss << setfill('0') << setw(8) << hex << cnonce_count;
2541 nc = ss.str();
2542 }
2543
2544 auto qop = auth.at("qop");
2545 if (qop.find("auth-int") != std::string::npos) {
2546 qop = "auth-int";
2547 } else {
2548 qop = "auth";
2549 }
2550
2551 std::string algo = "MD5";
2552 if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); }
2553
2554 string response;
2555 {
2556 auto H = algo == "SHA-256"
2557 ? detail::SHA_256
2558 : algo == "SHA-512" ? detail::SHA_512 : detail::MD5;
2559
2560 auto A1 = username + ":" + auth.at("realm") + ":" + password;
2561
2562 auto A2 = req.method + ":" + req.path;
2563 if (qop == "auth-int") { A2 += ":" + H(req.body); }
2564
2565 response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce +
2566 ":" + qop + ":" + H(A2));
2567 }
2568
2569 auto field = "Digest username=\"hello\", realm=\"" + auth.at("realm") +
2570 "\", nonce=\"" + auth.at("nonce") + "\", uri=\"" + req.path +
2571 "\", algorithm=" + algo + ", qop=" + qop + ", nc=\"" + nc +
2572 "\", cnonce=\"" + cnonce + "\", response=\"" + response + "\"";
2573
2574 auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
2575 return std::make_pair(key, field);
2576}
2577#endif
2578
2579inline bool parse_www_authenticate(const httplib::Response &res,
2580 std::map<std::string, std::string> &auth,
2581 bool is_proxy) {
2582 auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate";
2583 if (res.has_header(auth_key)) {
2584 static auto re = std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~");
2585 auto s = res.get_header_value(auth_key);
2586 auto pos = s.find(' ');
2587 if (pos != std::string::npos) {
2588 auto type = s.substr(0, pos);
2589 if (type == "Basic") {
2590 return false;
2591 } else if (type == "Digest") {
2592 s = s.substr(pos + 1);
2593 auto beg = std::sregex_iterator(s.begin(), s.end(), re);
2594 for (auto i = beg; i != std::sregex_iterator(); ++i) {
2595 auto m = *i;
2596 auto key = s.substr(m.position(1), m.length(1));
2597 auto val = m.length(2) > 0 ? s.substr(m.position(2), m.length(2))
2598 : s.substr(m.position(3), m.length(3));
2599 auth[key] = val;
2600 }
2601 return true;
2602 }
2603 }
2604 }
2605 return false;
2606}
2607
2608// https://stackoverflow.com/questions/440133/how-do-i-create-a-random-alpha-numeric-string-in-c/440240#answer-440240
2609inline std::string random_string(size_t length) {
2610 auto randchar = []() -> char {
2611 const char charset[] = "0123456789"
2612 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
2613 "abcdefghijklmnopqrstuvwxyz";
2614 const size_t max_index = (sizeof(charset) - 1);
2615 return charset[rand() % max_index];
2616 };
2617 std::string str(length, 0);
2618 std::generate_n(str.begin(), length, randchar);
2619 return str;
2031} 2620}
2032 2621
2033// Request implementation 2622// Request implementation
@@ -2068,14 +2657,19 @@ inline size_t Request::get_param_value_count(const char *key) const {
2068 return std::distance(r.first, r.second); 2657 return std::distance(r.first, r.second);
2069} 2658}
2070 2659
2660inline bool Request::is_multipart_form_data() const {
2661 const auto &content_type = get_header_value("Content-Type");
2662 return !content_type.find("multipart/form-data");
2663}
2664
2071inline bool Request::has_file(const char *key) const { 2665inline bool Request::has_file(const char *key) const {
2072 return files.find(key) != files.end(); 2666 return files.find(key) != files.end();
2073} 2667}
2074 2668
2075inline MultipartFile Request::get_file_value(const char *key) const { 2669inline MultipartFormData Request::get_file_value(const char *key) const {
2076 auto it = files.find(key); 2670 auto it = files.find(key);
2077 if (it != files.end()) { return it->second; } 2671 if (it != files.end()) { return it->second; }
2078 return MultipartFile(); 2672 return MultipartFormData();
2079} 2673}
2080 2674
2081// Response implementation 2675// Response implementation
@@ -2119,40 +2713,47 @@ inline void Response::set_content(const std::string &s,
2119} 2713}
2120 2714
2121inline void Response::set_content_provider( 2715inline void Response::set_content_provider(
2122 size_t length, 2716 size_t in_length,
2123 std::function<void(size_t offset, size_t length, DataSink sink)> provider, 2717 std::function<void(size_t offset, size_t length, DataSink &sink)> provider,
2124 std::function<void()> resource_releaser) { 2718 std::function<void()> resource_releaser) {
2125 assert(length > 0); 2719 assert(in_length > 0);
2126 content_provider_resource_length = length; 2720 content_length = in_length;
2127 content_provider = [provider](size_t offset, size_t length, DataSink sink, 2721 content_provider = [provider](size_t offset, size_t length, DataSink &sink) {
2128 Done) { provider(offset, length, sink); }; 2722 provider(offset, length, sink);
2723 };
2129 content_provider_resource_releaser = resource_releaser; 2724 content_provider_resource_releaser = resource_releaser;
2130} 2725}
2131 2726
2132inline void Response::set_chunked_content_provider( 2727inline void Response::set_chunked_content_provider(
2133 std::function<void(size_t offset, DataSink sink, Done done)> provider, 2728 std::function<void(size_t offset, DataSink &sink)> provider,
2134 std::function<void()> resource_releaser) { 2729 std::function<void()> resource_releaser) {
2135 content_provider_resource_length = 0; 2730 content_length = 0;
2136 content_provider = [provider](size_t offset, size_t, DataSink sink, 2731 content_provider = [provider](size_t offset, size_t, DataSink &sink) {
2137 Done done) { provider(offset, sink, done); }; 2732 provider(offset, sink);
2733 };
2138 content_provider_resource_releaser = resource_releaser; 2734 content_provider_resource_releaser = resource_releaser;
2139} 2735}
2140 2736
2141// Rstream implementation 2737// Rstream implementation
2738inline int Stream::write(const char *ptr) { return write(ptr, strlen(ptr)); }
2739
2740inline int Stream::write(const std::string &s) {
2741 return write(s.data(), s.size());
2742}
2743
2142template <typename... Args> 2744template <typename... Args>
2143inline int Stream::write_format(const char *fmt, const Args &... args) { 2745inline int Stream::write_format(const char *fmt, const Args &... args) {
2144 const auto bufsiz = 2048; 2746 std::array<char, 2048> buf;
2145 char buf[bufsiz];
2146 2747
2147#if defined(_MSC_VER) && _MSC_VER < 1900 2748#if defined(_MSC_VER) && _MSC_VER < 1900
2148 auto n = _snprintf_s(buf, bufsiz, bufsiz - 1, fmt, args...); 2749 auto n = _snprintf_s(buf, bufsiz, buf.size() - 1, fmt, args...);
2149#else 2750#else
2150 auto n = snprintf(buf, bufsiz - 1, fmt, args...); 2751 auto n = snprintf(buf.data(), buf.size() - 1, fmt, args...);
2151#endif 2752#endif
2152 if (n <= 0) { return n; } 2753 if (n <= 0) { return n; }
2153 2754
2154 if (n >= bufsiz - 1) { 2755 if (n >= static_cast<int>(buf.size()) - 1) {
2155 std::vector<char> glowable_buf(bufsiz); 2756 std::vector<char> glowable_buf(buf.size());
2156 2757
2157 while (n >= static_cast<int>(glowable_buf.size() - 1)) { 2758 while (n >= static_cast<int>(glowable_buf.size() - 1)) {
2158 glowable_buf.resize(glowable_buf.size() * 2); 2759 glowable_buf.resize(glowable_buf.size() * 2);
@@ -2165,33 +2766,36 @@ inline int Stream::write_format(const char *fmt, const Args &... args) {
2165 } 2766 }
2166 return write(&glowable_buf[0], n); 2767 return write(&glowable_buf[0], n);
2167 } else { 2768 } else {
2168 return write(buf, n); 2769 return write(buf.data(), n);
2169 } 2770 }
2170} 2771}
2171 2772
2773namespace detail {
2774
2172// Socket stream implementation 2775// Socket stream implementation
2173inline SocketStream::SocketStream(socket_t sock) : sock_(sock) {} 2776inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec,
2777 time_t read_timeout_usec)
2778 : sock_(sock), read_timeout_sec_(read_timeout_sec),
2779 read_timeout_usec_(read_timeout_usec) {}
2174 2780
2175inline SocketStream::~SocketStream() {} 2781inline SocketStream::~SocketStream() {}
2176 2782
2177inline int SocketStream::read(char *ptr, size_t size) { 2783inline bool SocketStream::is_readable() const {
2178 if (detail::select_read(sock_, CPPHTTPLIB_READ_TIMEOUT_SECOND, 2784 return detail::select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
2179 CPPHTTPLIB_READ_TIMEOUT_USECOND) > 0) {
2180 return recv(sock_, ptr, static_cast<int>(size), 0);
2181 }
2182 return -1;
2183} 2785}
2184 2786
2185inline int SocketStream::write(const char *ptr, size_t size) { 2787inline bool SocketStream::is_writable() const {
2186 return send(sock_, ptr, static_cast<int>(size), 0); 2788 return detail::select_write(sock_, 0, 0) > 0;
2187} 2789}
2188 2790
2189inline int SocketStream::write(const char *ptr) { 2791inline int SocketStream::read(char *ptr, size_t size) {
2190 return write(ptr, strlen(ptr)); 2792 if (is_readable()) { return recv(sock_, ptr, static_cast<int>(size), 0); }
2793 return -1;
2191} 2794}
2192 2795
2193inline int SocketStream::write(const std::string &s) { 2796inline int SocketStream::write(const char *ptr, size_t size) {
2194 return write(s.data(), s.size()); 2797 if (is_writable()) { return send(sock_, ptr, static_cast<int>(size), 0); }
2798 return -1;
2195} 2799}
2196 2800
2197inline std::string SocketStream::get_remote_addr() const { 2801inline std::string SocketStream::get_remote_addr() const {
@@ -2199,12 +2803,18 @@ inline std::string SocketStream::get_remote_addr() const {
2199} 2803}
2200 2804
2201// Buffer stream implementation 2805// Buffer stream implementation
2806inline bool BufferStream::is_readable() const { return true; }
2807
2808inline bool BufferStream::is_writable() const { return true; }
2809
2202inline int BufferStream::read(char *ptr, size_t size) { 2810inline int BufferStream::read(char *ptr, size_t size) {
2203#if defined(_MSC_VER) && _MSC_VER < 1900 2811#if defined(_MSC_VER) && _MSC_VER < 1900
2204 return static_cast<int>(buffer._Copy_s(ptr, size, size)); 2812 int len_read = static_cast<int>(buffer._Copy_s(ptr, size, size, position));
2205#else 2813#else
2206 return static_cast<int>(buffer.copy(ptr, size)); 2814 int len_read = static_cast<int>(buffer.copy(ptr, size, position));
2207#endif 2815#endif
2816 position += len_read;
2817 return len_read;
2208} 2818}
2209 2819
2210inline int BufferStream::write(const char *ptr, size_t size) { 2820inline int BufferStream::write(const char *ptr, size_t size) {
@@ -2212,33 +2822,23 @@ inline int BufferStream::write(const char *ptr, size_t size) {
2212 return static_cast<int>(size); 2822 return static_cast<int>(size);
2213} 2823}
2214 2824
2215inline int BufferStream::write(const char *ptr) {
2216 return write(ptr, strlen(ptr));
2217}
2218
2219inline int BufferStream::write(const std::string &s) {
2220 return write(s.data(), s.size());
2221}
2222
2223inline std::string BufferStream::get_remote_addr() const { return ""; } 2825inline std::string BufferStream::get_remote_addr() const { return ""; }
2224 2826
2225inline const std::string &BufferStream::get_buffer() const { return buffer; } 2827inline const std::string &BufferStream::get_buffer() const { return buffer; }
2226 2828
2829} // namespace detail
2830
2227// HTTP server implementation 2831// HTTP server implementation
2228inline Server::Server() 2832inline Server::Server()
2229 : keep_alive_max_count_(CPPHTTPLIB_KEEPALIVE_MAX_COUNT), 2833 : keep_alive_max_count_(CPPHTTPLIB_KEEPALIVE_MAX_COUNT),
2834 read_timeout_sec_(CPPHTTPLIB_READ_TIMEOUT_SECOND),
2835 read_timeout_usec_(CPPHTTPLIB_READ_TIMEOUT_USECOND),
2230 payload_max_length_(CPPHTTPLIB_PAYLOAD_MAX_LENGTH), is_running_(false), 2836 payload_max_length_(CPPHTTPLIB_PAYLOAD_MAX_LENGTH), is_running_(false),
2231 svr_sock_(INVALID_SOCKET) { 2837 svr_sock_(INVALID_SOCKET) {
2232#ifndef _WIN32 2838#ifndef _WIN32
2233 signal(SIGPIPE, SIG_IGN); 2839 signal(SIGPIPE, SIG_IGN);
2234#endif 2840#endif
2235 new_task_queue = [] { 2841 new_task_queue = [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); };
2236#if CPPHTTPLIB_THREAD_POOL_COUNT > 0
2237 return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT);
2238#else
2239 return new Threads();
2240#endif
2241 };
2242} 2842}
2243 2843
2244inline Server::~Server() {} 2844inline Server::~Server() {}
@@ -2253,16 +2853,37 @@ inline Server &Server::Post(const char *pattern, Handler handler) {
2253 return *this; 2853 return *this;
2254} 2854}
2255 2855
2856inline Server &Server::Post(const char *pattern,
2857 HandlerWithContentReader handler) {
2858 post_handlers_for_content_reader_.push_back(
2859 std::make_pair(std::regex(pattern), handler));
2860 return *this;
2861}
2862
2256inline Server &Server::Put(const char *pattern, Handler handler) { 2863inline Server &Server::Put(const char *pattern, Handler handler) {
2257 put_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); 2864 put_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
2258 return *this; 2865 return *this;
2259} 2866}
2260 2867
2868inline Server &Server::Put(const char *pattern,
2869 HandlerWithContentReader handler) {
2870 put_handlers_for_content_reader_.push_back(
2871 std::make_pair(std::regex(pattern), handler));
2872 return *this;
2873}
2874
2261inline Server &Server::Patch(const char *pattern, Handler handler) { 2875inline Server &Server::Patch(const char *pattern, Handler handler) {
2262 patch_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); 2876 patch_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
2263 return *this; 2877 return *this;
2264} 2878}
2265 2879
2880inline Server &Server::Patch(const char *pattern,
2881 HandlerWithContentReader handler) {
2882 patch_handlers_for_content_reader_.push_back(
2883 std::make_pair(std::regex(pattern), handler));
2884 return *this;
2885}
2886
2266inline Server &Server::Delete(const char *pattern, Handler handler) { 2887inline Server &Server::Delete(const char *pattern, Handler handler) {
2267 delete_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); 2888 delete_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
2268 return *this; 2889 return *this;
@@ -2273,32 +2894,68 @@ inline Server &Server::Options(const char *pattern, Handler handler) {
2273 return *this; 2894 return *this;
2274} 2895}
2275 2896
2276inline bool Server::set_base_dir(const char *path) { 2897inline bool Server::set_base_dir(const char *dir, const char *mount_point) {
2277 if (detail::is_dir(path)) { 2898 return set_mount_point(mount_point, dir);
2278 base_dir_ = path; 2899}
2279 return true; 2900
2901inline bool Server::set_mount_point(const char *mount_point, const char *dir) {
2902 if (detail::is_dir(dir)) {
2903 std::string mnt = mount_point ? mount_point : "/";
2904 if (!mnt.empty() && mnt[0] == '/') {
2905 base_dirs_.emplace_back(mnt, dir);
2906 return true;
2907 }
2908 }
2909 return false;
2910}
2911
2912inline bool Server::remove_mount_point(const char *mount_point) {
2913 for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) {
2914 if (it->first == mount_point) {
2915 base_dirs_.erase(it);
2916 return true;
2917 }
2280 } 2918 }
2281 return false; 2919 return false;
2282} 2920}
2283 2921
2922inline void Server::set_file_extension_and_mimetype_mapping(const char *ext,
2923 const char *mime) {
2924 file_extension_and_mimetype_map_[ext] = mime;
2925}
2926
2284inline void Server::set_file_request_handler(Handler handler) { 2927inline void Server::set_file_request_handler(Handler handler) {
2285 file_request_handler_ = handler; 2928 file_request_handler_ = std::move(handler);
2286} 2929}
2287 2930
2288inline void Server::set_error_handler(Handler handler) { 2931inline void Server::set_error_handler(Handler handler) {
2289 error_handler_ = handler; 2932 error_handler_ = std::move(handler);
2290} 2933}
2291 2934
2292inline void Server::set_logger(Logger logger) { logger_ = logger; } 2935inline void Server::set_logger(Logger logger) { logger_ = std::move(logger); }
2936
2937inline void
2938Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) {
2939 expect_100_continue_handler_ = std::move(handler);
2940}
2293 2941
2294inline void Server::set_keep_alive_max_count(size_t count) { 2942inline void Server::set_keep_alive_max_count(size_t count) {
2295 keep_alive_max_count_ = count; 2943 keep_alive_max_count_ = count;
2296} 2944}
2297 2945
2946inline void Server::set_read_timeout(time_t sec, time_t usec) {
2947 read_timeout_sec_ = sec;
2948 read_timeout_usec_ = usec;
2949}
2950
2298inline void Server::set_payload_max_length(size_t length) { 2951inline void Server::set_payload_max_length(size_t length) {
2299 payload_max_length_ = length; 2952 payload_max_length_ = length;
2300} 2953}
2301 2954
2955inline bool Server::bind_to_port(const char *host, int port, int socket_flags) {
2956 if (bind_internal(host, port, socket_flags) < 0) return false;
2957 return true;
2958}
2302inline int Server::bind_to_any_port(const char *host, int socket_flags) { 2959inline int Server::bind_to_any_port(const char *host, int socket_flags) {
2303 return bind_internal(host, 0, socket_flags); 2960 return bind_internal(host, 0, socket_flags);
2304} 2961}
@@ -2306,8 +2963,7 @@ inline int Server::bind_to_any_port(const char *host, int socket_flags) {
2306inline bool Server::listen_after_bind() { return listen_internal(); } 2963inline bool Server::listen_after_bind() { return listen_internal(); }
2307 2964
2308inline bool Server::listen(const char *host, int port, int socket_flags) { 2965inline bool Server::listen(const char *host, int port, int socket_flags) {
2309 if (bind_internal(host, port, socket_flags) < 0) return false; 2966 return bind_to_port(host, port, socket_flags) && listen_internal();
2310 return listen_internal();
2311} 2967}
2312 2968
2313inline bool Server::is_running() const { return is_running_; } 2969inline bool Server::is_running() const { return is_running_; }
@@ -2322,8 +2978,9 @@ inline void Server::stop() {
2322} 2978}
2323 2979
2324inline bool Server::parse_request_line(const char *s, Request &req) { 2980inline bool Server::parse_request_line(const char *s, Request &req) {
2325 static std::regex re("(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH|PRI) " 2981 const static std::regex re(
2326 "(([^?]+)(?:\\?(.+?))?) (HTTP/1\\.[01])\r\n"); 2982 "(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH|PRI) "
2983 "(([^?]+)(?:\\?(.*?))?) (HTTP/1\\.[01])\r\n");
2327 2984
2328 std::cmatch m; 2985 std::cmatch m;
2329 if (std::regex_match(s, m, re)) { 2986 if (std::regex_match(s, m, re)) {
@@ -2348,9 +3005,11 @@ inline bool Server::write_response(Stream &strm, bool last_connection,
2348 3005
2349 if (400 <= res.status && error_handler_) { error_handler_(req, res); } 3006 if (400 <= res.status && error_handler_) { error_handler_(req, res); }
2350 3007
3008 detail::BufferStream bstrm;
3009
2351 // Response line 3010 // Response line
2352 if (!strm.write_format("HTTP/1.1 %d %s\r\n", res.status, 3011 if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status,
2353 detail::status_message(res.status))) { 3012 detail::status_message(res.status))) {
2354 return false; 3013 return false;
2355 } 3014 }
2356 3015
@@ -2363,11 +3022,12 @@ inline bool Server::write_response(Stream &strm, bool last_connection,
2363 res.set_header("Connection", "Keep-Alive"); 3022 res.set_header("Connection", "Keep-Alive");
2364 } 3023 }
2365 3024
2366 if (!res.has_header("Content-Type")) { 3025 if (!res.has_header("Content-Type") &&
3026 (!res.body.empty() || res.content_length > 0)) {
2367 res.set_header("Content-Type", "text/plain"); 3027 res.set_header("Content-Type", "text/plain");
2368 } 3028 }
2369 3029
2370 if (!res.has_header("Accept-Ranges")) { 3030 if (!res.has_header("Accept-Ranges") && req.method == "HEAD") {
2371 res.set_header("Accept-Ranges", "bytes"); 3031 res.set_header("Accept-Ranges", "bytes");
2372 } 3032 }
2373 3033
@@ -2388,17 +3048,17 @@ inline bool Server::write_response(Stream &strm, bool last_connection,
2388 } 3048 }
2389 3049
2390 if (res.body.empty()) { 3050 if (res.body.empty()) {
2391 if (res.content_provider_resource_length > 0) { 3051 if (res.content_length > 0) {
2392 size_t length = 0; 3052 size_t length = 0;
2393 if (req.ranges.empty()) { 3053 if (req.ranges.empty()) {
2394 length = res.content_provider_resource_length; 3054 length = res.content_length;
2395 } else if (req.ranges.size() == 1) { 3055 } else if (req.ranges.size() == 1) {
2396 auto offsets = detail::get_range_offset_and_length( 3056 auto offsets =
2397 req, res.content_provider_resource_length, 0); 3057 detail::get_range_offset_and_length(req, res.content_length, 0);
2398 auto offset = offsets.first; 3058 auto offset = offsets.first;
2399 length = offsets.second; 3059 length = offsets.second;
2400 auto content_range = detail::make_content_range_header_field( 3060 auto content_range = detail::make_content_range_header_field(
2401 offset, length, res.content_provider_resource_length); 3061 offset, length, res.content_length);
2402 res.set_header("Content-Range", content_range); 3062 res.set_header("Content-Range", content_range);
2403 } else { 3063 } else {
2404 length = detail::get_multipart_ranges_data_length(req, res, boundary, 3064 length = detail::get_multipart_ranges_data_length(req, res, boundary,
@@ -2430,7 +3090,7 @@ inline bool Server::write_response(Stream &strm, bool last_connection,
2430 } 3090 }
2431 3091
2432#ifdef CPPHTTPLIB_ZLIB_SUPPORT 3092#ifdef CPPHTTPLIB_ZLIB_SUPPORT
2433 // TODO: 'Accpet-Encoding' has gzip, not gzip;q=0 3093 // TODO: 'Accept-Encoding' has gzip, not gzip;q=0
2434 const auto &encodings = req.get_header_value("Accept-Encoding"); 3094 const auto &encodings = req.get_header_value("Accept-Encoding");
2435 if (encodings.find("gzip") != std::string::npos && 3095 if (encodings.find("gzip") != std::string::npos &&
2436 detail::can_compress(res.get_header_value("Content-Type"))) { 3096 detail::can_compress(res.get_header_value("Content-Type"))) {
@@ -2444,7 +3104,11 @@ inline bool Server::write_response(Stream &strm, bool last_connection,
2444 res.set_header("Content-Length", length); 3104 res.set_header("Content-Length", length);
2445 } 3105 }
2446 3106
2447 if (!detail::write_headers(strm, res, Headers())) { return false; } 3107 if (!detail::write_headers(bstrm, res, Headers())) { return false; }
3108
3109 // Flush buffer
3110 auto &data = bstrm.get_buffer();
3111 strm.write(data.data(), data.size());
2448 3112
2449 // Body 3113 // Body
2450 if (req.method != "HEAD") { 3114 if (req.method != "HEAD") {
@@ -2468,15 +3132,15 @@ inline bool
2468Server::write_content_with_provider(Stream &strm, const Request &req, 3132Server::write_content_with_provider(Stream &strm, const Request &req,
2469 Response &res, const std::string &boundary, 3133 Response &res, const std::string &boundary,
2470 const std::string &content_type) { 3134 const std::string &content_type) {
2471 if (res.content_provider_resource_length) { 3135 if (res.content_length) {
2472 if (req.ranges.empty()) { 3136 if (req.ranges.empty()) {
2473 if (detail::write_content(strm, res.content_provider, 0, 3137 if (detail::write_content(strm, res.content_provider, 0,
2474 res.content_provider_resource_length) < 0) { 3138 res.content_length) < 0) {
2475 return false; 3139 return false;
2476 } 3140 }
2477 } else if (req.ranges.size() == 1) { 3141 } else if (req.ranges.size() == 1) {
2478 auto offsets = detail::get_range_offset_and_length( 3142 auto offsets =
2479 req, res.content_provider_resource_length, 0); 3143 detail::get_range_offset_and_length(req, res.content_length, 0);
2480 auto offset = offsets.first; 3144 auto offset = offsets.first;
2481 auto length = offsets.second; 3145 auto length = offsets.second;
2482 if (detail::write_content(strm, res.content_provider, offset, length) < 3146 if (detail::write_content(strm, res.content_provider, offset, length) <
@@ -2490,29 +3154,123 @@ Server::write_content_with_provider(Stream &strm, const Request &req,
2490 } 3154 }
2491 } 3155 }
2492 } else { 3156 } else {
2493 if (detail::write_content_chunked(strm, res.content_provider) < 0) { 3157 auto is_shutting_down = [this]() {
3158 return this->svr_sock_ == INVALID_SOCKET;
3159 };
3160 if (detail::write_content_chunked(strm, res.content_provider,
3161 is_shutting_down) < 0) {
2494 return false; 3162 return false;
2495 } 3163 }
2496 } 3164 }
2497 return true; 3165 return true;
2498} 3166}
2499 3167
2500inline bool Server::handle_file_request(Request &req, Response &res) { 3168inline bool Server::read_content(Stream &strm, bool last_connection,
2501 if (!base_dir_.empty() && detail::is_valid_path(req.path)) { 3169 Request &req, Response &res) {
2502 std::string path = base_dir_ + req.path; 3170 MultipartFormDataMap::iterator cur;
3171 auto ret = read_content_core(
3172 strm, last_connection, req, res,
3173 // Regular
3174 [&](const char *buf, size_t n) {
3175 if (req.body.size() + n > req.body.max_size()) { return false; }
3176 req.body.append(buf, n);
3177 return true;
3178 },
3179 // Multipart
3180 [&](const MultipartFormData &file) {
3181 cur = req.files.emplace(file.name, file);
3182 return true;
3183 },
3184 [&](const char *buf, size_t n) {
3185 auto &content = cur->second.content;
3186 if (content.size() + n > content.max_size()) { return false; }
3187 content.append(buf, n);
3188 return true;
3189 });
2503 3190
2504 if (!path.empty() && path.back() == '/') { path += "index.html"; } 3191 const auto &content_type = req.get_header_value("Content-Type");
3192 if (!content_type.find("application/x-www-form-urlencoded")) {
3193 detail::parse_query_text(req.body, req.params);
3194 }
2505 3195
2506 if (detail::is_file(path)) { 3196 return ret;
2507 detail::read_file(path, res.body); 3197}
2508 auto type = detail::find_content_type(path); 3198
2509 if (type) { res.set_header("Content-Type", type); } 3199inline bool Server::read_content_with_content_receiver(
2510 res.status = 200; 3200 Stream &strm, bool last_connection, Request &req, Response &res,
2511 if (file_request_handler_) { file_request_handler_(req, res); } 3201 ContentReceiver receiver, MultipartContentHeader multipart_header,
2512 return true; 3202 ContentReceiver multipart_receiver) {
3203 return read_content_core(strm, last_connection, req, res, receiver,
3204 multipart_header, multipart_receiver);
3205}
3206
3207inline bool Server::read_content_core(Stream &strm, bool last_connection,
3208 Request &req, Response &res,
3209 ContentReceiver receiver,
3210 MultipartContentHeader mulitpart_header,
3211 ContentReceiver multipart_receiver) {
3212 detail::MultipartFormDataParser multipart_form_data_parser;
3213 ContentReceiver out;
3214
3215 if (req.is_multipart_form_data()) {
3216 const auto &content_type = req.get_header_value("Content-Type");
3217 std::string boundary;
3218 if (!detail::parse_multipart_boundary(content_type, boundary)) {
3219 res.status = 400;
3220 return write_response(strm, last_connection, req, res);
2513 } 3221 }
3222
3223 multipart_form_data_parser.set_boundary(boundary);
3224 out = [&](const char *buf, size_t n) {
3225 return multipart_form_data_parser.parse(buf, n, multipart_receiver,
3226 mulitpart_header);
3227 };
3228 } else {
3229 out = receiver;
3230 }
3231
3232 if (!detail::read_content(strm, req, payload_max_length_, res.status,
3233 Progress(), out)) {
3234 return write_response(strm, last_connection, req, res);
2514 } 3235 }
2515 3236
3237 if (req.is_multipart_form_data()) {
3238 if (!multipart_form_data_parser.is_valid()) {
3239 res.status = 400;
3240 return write_response(strm, last_connection, req, res);
3241 }
3242 }
3243
3244 return true;
3245}
3246
3247inline bool Server::handle_file_request(Request &req, Response &res,
3248 bool head) {
3249 for (const auto &kv : base_dirs_) {
3250 const auto &mount_point = kv.first;
3251 const auto &base_dir = kv.second;
3252
3253 // Prefix match
3254 if (!req.path.find(mount_point)) {
3255 std::string sub_path = "/" + req.path.substr(mount_point.size());
3256 if (detail::is_valid_path(sub_path)) {
3257 auto path = base_dir + sub_path;
3258 if (path.back() == '/') { path += "index.html"; }
3259
3260 if (detail::is_file(path)) {
3261 detail::read_file(path, res.body);
3262 auto type =
3263 detail::find_content_type(path, file_extension_and_mimetype_map_);
3264 if (type) { res.set_header("Content-Type", type); }
3265 res.status = 200;
3266 if (!head && file_request_handler_) {
3267 file_request_handler_(req, res);
3268 }
3269 return true;
3270 }
3271 }
3272 }
3273 }
2516 return false; 3274 return false;
2517} 3275}
2518 3276
@@ -2605,9 +3363,51 @@ inline bool Server::listen_internal() {
2605 return ret; 3363 return ret;
2606} 3364}
2607 3365
2608inline bool Server::routing(Request &req, Response &res) { 3366inline bool Server::routing(Request &req, Response &res, Stream &strm,
2609 if (req.method == "GET" && handle_file_request(req, res)) { return true; } 3367 bool last_connection) {
3368 // File handler
3369 bool is_head_request = req.method == "HEAD";
3370 if ((req.method == "GET" || is_head_request) &&
3371 handle_file_request(req, res, is_head_request)) {
3372 return true;
3373 }
3374
3375 if (detail::expect_content(req)) {
3376 // Content reader handler
3377 {
3378 ContentReader reader(
3379 [&](ContentReceiver receiver) {
3380 return read_content_with_content_receiver(
3381 strm, last_connection, req, res, receiver, nullptr, nullptr);
3382 },
3383 [&](MultipartContentHeader header, ContentReceiver receiver) {
3384 return read_content_with_content_receiver(
3385 strm, last_connection, req, res, nullptr, header, receiver);
3386 });
3387
3388 if (req.method == "POST") {
3389 if (dispatch_request_for_content_reader(
3390 req, res, reader, post_handlers_for_content_reader_)) {
3391 return true;
3392 }
3393 } else if (req.method == "PUT") {
3394 if (dispatch_request_for_content_reader(
3395 req, res, reader, put_handlers_for_content_reader_)) {
3396 return true;
3397 }
3398 } else if (req.method == "PATCH") {
3399 if (dispatch_request_for_content_reader(
3400 req, res, reader, patch_handlers_for_content_reader_)) {
3401 return true;
3402 }
3403 }
3404 }
2610 3405
3406 // Read content into `req.body`
3407 if (!read_content(strm, last_connection, req, res)) { return false; }
3408 }
3409
3410 // Regular handler
2611 if (req.method == "GET" || req.method == "HEAD") { 3411 if (req.method == "GET" || req.method == "HEAD") {
2612 return dispatch_request(req, res, get_handlers_); 3412 return dispatch_request(req, res, get_handlers_);
2613 } else if (req.method == "POST") { 3413 } else if (req.method == "POST") {
@@ -2640,17 +3440,31 @@ inline bool Server::dispatch_request(Request &req, Response &res,
2640 return false; 3440 return false;
2641} 3441}
2642 3442
3443inline bool Server::dispatch_request_for_content_reader(
3444 Request &req, Response &res, ContentReader content_reader,
3445 HandlersForContentReader &handlers) {
3446 for (const auto &x : handlers) {
3447 const auto &pattern = x.first;
3448 const auto &handler = x.second;
3449
3450 if (std::regex_match(req.path, req.matches, pattern)) {
3451 handler(req, res, content_reader);
3452 return true;
3453 }
3454 }
3455 return false;
3456}
3457
2643inline bool 3458inline bool
2644Server::process_request(Stream &strm, bool last_connection, 3459Server::process_request(Stream &strm, bool last_connection,
2645 bool &connection_close, 3460 bool &connection_close,
2646 std::function<void(Request &)> setup_request) { 3461 const std::function<void(Request &)> &setup_request) {
2647 const auto bufsiz = 2048; 3462 std::array<char, 2048> buf{};
2648 char buf[bufsiz];
2649 3463
2650 detail::stream_line_reader reader(strm, buf, bufsiz); 3464 detail::stream_line_reader line_reader(strm, buf.data(), buf.size());
2651 3465
2652 // Connection has been closed on client 3466 // Connection has been closed on client
2653 if (!reader.getline()) { return false; } 3467 if (!line_reader.getline()) { return false; }
2654 3468
2655 Request req; 3469 Request req;
2656 Response res; 3470 Response res;
@@ -2658,7 +3472,7 @@ Server::process_request(Stream &strm, bool last_connection,
2658 res.version = "HTTP/1.1"; 3472 res.version = "HTTP/1.1";
2659 3473
2660 // Check if the request URI doesn't exceed the limit 3474 // Check if the request URI doesn't exceed the limit
2661 if (reader.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) { 3475 if (line_reader.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) {
2662 Headers dummy; 3476 Headers dummy;
2663 detail::read_headers(strm, dummy); 3477 detail::read_headers(strm, dummy);
2664 res.status = 414; 3478 res.status = 414;
@@ -2666,7 +3480,7 @@ Server::process_request(Stream &strm, bool last_connection,
2666 } 3480 }
2667 3481
2668 // Request line and headers 3482 // Request line and headers
2669 if (!parse_request_line(reader.ptr(), req) || 3483 if (!parse_request_line(line_reader.ptr(), req) ||
2670 !detail::read_headers(strm, req.headers)) { 3484 !detail::read_headers(strm, req.headers)) {
2671 res.status = 400; 3485 res.status = 400;
2672 return write_response(strm, last_connection, req, res); 3486 return write_response(strm, last_connection, req, res);
@@ -2683,33 +3497,6 @@ Server::process_request(Stream &strm, bool last_connection,
2683 3497
2684 req.set_header("REMOTE_ADDR", strm.get_remote_addr()); 3498 req.set_header("REMOTE_ADDR", strm.get_remote_addr());
2685 3499
2686 // Body
2687 if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" || req.method == "PRI") {
2688 if (!detail::read_content(strm, req, payload_max_length_, res.status,
2689 Progress(), [&](const char *buf, size_t n) {
2690 if (req.body.size() + n > req.body.max_size()) {
2691 return false;
2692 }
2693 req.body.append(buf, n);
2694 return true;
2695 })) {
2696 return write_response(strm, last_connection, req, res);
2697 }
2698
2699 const auto &content_type = req.get_header_value("Content-Type");
2700
2701 if (!content_type.find("application/x-www-form-urlencoded")) {
2702 detail::parse_query_text(req.body, req.params);
2703 } else if (!content_type.find("multipart/form-data")) {
2704 std::string boundary;
2705 if (!detail::parse_multipart_boundary(content_type, boundary) ||
2706 !detail::parse_multipart_formdata(boundary, req.body, req.files)) {
2707 res.status = 400;
2708 return write_response(strm, last_connection, req, res);
2709 }
2710 }
2711 }
2712
2713 if (req.has_header("Range")) { 3500 if (req.has_header("Range")) {
2714 const auto &range_header_value = req.get_header_value("Range"); 3501 const auto &range_header_value = req.get_header_value("Range");
2715 if (!detail::parse_range_header(range_header_value, req.ranges)) { 3502 if (!detail::parse_range_header(range_header_value, req.ranges)) {
@@ -2719,7 +3506,23 @@ Server::process_request(Stream &strm, bool last_connection,
2719 3506
2720 if (setup_request) { setup_request(req); } 3507 if (setup_request) { setup_request(req); }
2721 3508
2722 if (routing(req, res)) { 3509 if (req.get_header_value("Expect") == "100-continue") {
3510 auto status = 100;
3511 if (expect_100_continue_handler_) {
3512 status = expect_100_continue_handler_(req, res);
3513 }
3514 switch (status) {
3515 case 100:
3516 case 417:
3517 strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status,
3518 detail::status_message(status));
3519 break;
3520 default: return write_response(strm, last_connection, req, res);
3521 }
3522 }
3523
3524 // Rounting
3525 if (routing(req, res, strm, last_connection)) {
2723 if (res.status == -1) { res.status = req.ranges.empty() ? 200 : 206; } 3526 if (res.status == -1) { res.status = req.ranges.empty() ? 200 : 206; }
2724 } else { 3527 } else {
2725 if (res.status == -1) { res.status = 404; } 3528 if (res.status == -1) { res.status = 404; }
@@ -2732,7 +3535,7 @@ inline bool Server::is_valid() const { return true; }
2732 3535
2733inline bool Server::process_and_close_socket(socket_t sock) { 3536inline bool Server::process_and_close_socket(socket_t sock) {
2734 return detail::process_and_close_socket( 3537 return detail::process_and_close_socket(
2735 false, sock, keep_alive_max_count_, 3538 false, sock, keep_alive_max_count_, read_timeout_sec_, read_timeout_usec_,
2736 [this](Stream &strm, bool last_connection, bool &connection_close) { 3539 [this](Stream &strm, bool last_connection, bool &connection_close) {
2737 return process_request(strm, last_connection, connection_close, 3540 return process_request(strm, last_connection, connection_close,
2738 nullptr); 3541 nullptr);
@@ -2740,47 +3543,37 @@ inline bool Server::process_and_close_socket(socket_t sock) {
2740} 3543}
2741 3544
2742// HTTP client implementation 3545// HTTP client implementation
2743inline Client::Client(const char *host, int port, time_t timeout_sec) 3546inline Client::Client(const std::string &host, int port,
2744 : host_(host), port_(port), timeout_sec_(timeout_sec), 3547 const std::string &client_cert_path,
3548 const std::string &client_key_path)
3549 : host_(host), port_(port),
2745 host_and_port_(host_ + ":" + std::to_string(port_)), 3550 host_and_port_(host_ + ":" + std::to_string(port_)),
2746 keep_alive_max_count_(CPPHTTPLIB_KEEPALIVE_MAX_COUNT), 3551 client_cert_path_(client_cert_path), client_key_path_(client_key_path) {}
2747 follow_location_(false) {}
2748 3552
2749inline Client::~Client() {} 3553inline Client::~Client() {}
2750 3554
2751inline bool Client::is_valid() const { return true; } 3555inline bool Client::is_valid() const { return true; }
2752 3556
2753inline socket_t Client::create_client_socket() const { 3557inline socket_t Client::create_client_socket() const {
2754 return detail::create_socket( 3558 if (!proxy_host_.empty()) {
2755 host_.c_str(), port_, [=](socket_t sock, struct addrinfo &ai) -> bool { 3559 return detail::create_client_socket(proxy_host_.c_str(), proxy_port_,
2756 detail::set_nonblocking(sock, true); 3560 timeout_sec_, interface_);
2757 3561 }
2758 auto ret = connect(sock, ai.ai_addr, static_cast<int>(ai.ai_addrlen)); 3562 return detail::create_client_socket(host_.c_str(), port_, timeout_sec_,
2759 if (ret < 0) { 3563 interface_);
2760 if (detail::is_connection_error() ||
2761 !detail::wait_until_socket_is_ready(sock, timeout_sec_, 0)) {
2762 detail::close_socket(sock);
2763 return false;
2764 }
2765 }
2766
2767 detail::set_nonblocking(sock, false);
2768 return true;
2769 });
2770} 3564}
2771 3565
2772inline bool Client::read_response_line(Stream &strm, Response &res) { 3566inline bool Client::read_response_line(Stream &strm, Response &res) {
2773 const auto bufsiz = 2048; 3567 std::array<char, 2048> buf;
2774 char buf[bufsiz];
2775 3568
2776 detail::stream_line_reader reader(strm, buf, bufsiz); 3569 detail::stream_line_reader line_reader(strm, buf.data(), buf.size());
2777 3570
2778 if (!reader.getline()) { return false; } 3571 if (!line_reader.getline()) { return false; }
2779 3572
2780 const static std::regex re("(HTTP/1\\.[01]) (\\d+?) .*\r\n"); 3573 const static std::regex re("(HTTP/1\\.[01]) (\\d+?) .*\r\n");
2781 3574
2782 std::cmatch m; 3575 std::cmatch m;
2783 if (std::regex_match(reader.ptr(), m, re)) { 3576 if (std::regex_match(line_reader.ptr(), m, re)) {
2784 res.version = std::string(m[1]); 3577 res.version = std::string(m[1]);
2785 res.status = std::stoi(std::string(m[2])); 3578 res.status = std::stoi(std::string(m[2]));
2786 } 3579 }
@@ -2789,22 +3582,21 @@ inline bool Client::read_response_line(Stream &strm, Response &res) {
2789} 3582}
2790 3583
2791inline bool Client::send(const Request &req, Response &res) { 3584inline bool Client::send(const Request &req, Response &res) {
2792 if (req.path.empty()) { return false; }
2793
2794 auto sock = create_client_socket(); 3585 auto sock = create_client_socket();
2795 if (sock == INVALID_SOCKET) { return false; } 3586 if (sock == INVALID_SOCKET) { return false; }
2796 3587
2797 auto ret = process_and_close_socket( 3588#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
2798 sock, 1, [&](Stream &strm, bool last_connection, bool &connection_close) { 3589 if (is_ssl() && !proxy_host_.empty()) {
2799 return process_request(strm, req, res, last_connection, 3590 bool error;
2800 connection_close); 3591 if (!connect(sock, res, error)) { return error; }
2801 });
2802
2803 if (ret && follow_location_ && (300 < res.status && res.status < 400)) {
2804 ret = redirect(req, res);
2805 } 3592 }
3593#endif
2806 3594
2807 return ret; 3595 return process_and_close_socket(
3596 sock, 1, [&](Stream &strm, bool last_connection, bool &connection_close) {
3597 return handle_request(strm, req, res, last_connection,
3598 connection_close);
3599 });
2808} 3600}
2809 3601
2810inline bool Client::send(const std::vector<Request> &requests, 3602inline bool Client::send(const std::vector<Request> &requests,
@@ -2814,32 +3606,136 @@ inline bool Client::send(const std::vector<Request> &requests,
2814 auto sock = create_client_socket(); 3606 auto sock = create_client_socket();
2815 if (sock == INVALID_SOCKET) { return false; } 3607 if (sock == INVALID_SOCKET) { return false; }
2816 3608
2817 if (!process_and_close_socket( 3609#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
2818 sock, requests.size() - i, 3610 if (is_ssl() && !proxy_host_.empty()) {
2819 [&](Stream &strm, bool last_connection, bool &connection_close) -> bool { 3611 Response res;
2820 auto &req = requests[i]; 3612 bool error;
2821 auto res = Response(); 3613 if (!connect(sock, res, error)) { return false; }
2822 i++; 3614 }
3615#endif
3616
3617 if (!process_and_close_socket(sock, requests.size() - i,
3618 [&](Stream &strm, bool last_connection,
3619 bool &connection_close) -> bool {
3620 auto &req = requests[i++];
3621 auto res = Response();
3622 auto ret = handle_request(strm, req, res,
3623 last_connection,
3624 connection_close);
3625 if (ret) {
3626 responses.emplace_back(std::move(res));
3627 }
3628 return ret;
3629 })) {
3630 return false;
3631 }
3632 }
2823 3633
2824 if (req.path.empty()) { return false; } 3634 return true;
2825 auto ret = process_request(strm, req, res, last_connection, 3635}
2826 connection_close); 3636
3637inline bool Client::handle_request(Stream &strm, const Request &req,
3638 Response &res, bool last_connection,
3639 bool &connection_close) {
3640 if (req.path.empty()) { return false; }
3641
3642 bool ret;
3643
3644 if (!is_ssl() && !proxy_host_.empty()) {
3645 auto req2 = req;
3646 req2.path = "http://" + host_and_port_ + req.path;
3647 ret = process_request(strm, req2, res, last_connection, connection_close);
3648 } else {
3649 ret = process_request(strm, req, res, last_connection, connection_close);
3650 }
3651
3652 if (!ret) { return false; }
3653
3654 if (300 < res.status && res.status < 400 && follow_location_) {
3655 ret = redirect(req, res);
3656 }
2827 3657
2828 if (ret && follow_location_ && 3658#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
2829 (300 < res.status && res.status < 400)) { 3659 if (res.status == 401 || res.status == 407) {
2830 ret = redirect(req, res); 3660 auto is_proxy = res.status == 407;
2831 } 3661 const auto &username =
3662 is_proxy ? proxy_digest_auth_username_ : digest_auth_username_;
3663 const auto &password =
3664 is_proxy ? proxy_digest_auth_password_ : digest_auth_password_;
3665
3666 if (!username.empty() && !password.empty()) {
3667 std::map<std::string, std::string> auth;
3668 if (parse_www_authenticate(res, auth, is_proxy)) {
3669 Request new_req = req;
3670 auto key = is_proxy ? "Proxy-Authorization" : "WWW-Authorization";
3671 new_req.headers.erase(key);
3672 new_req.headers.insert(make_digest_authentication_header(
3673 req, auth, 1, random_string(10), username, password, is_proxy));
3674
3675 Response new_res;
3676
3677 ret = send(new_req, new_res);
3678 if (ret) { res = new_res; }
3679 }
3680 }
3681 }
3682#endif
3683
3684 return ret;
3685}
2832 3686
2833 if (ret) { responses.emplace_back(std::move(res)); } 3687#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
3688inline bool Client::connect(socket_t sock, Response &res, bool &error) {
3689 error = true;
3690 Response res2;
3691
3692 if (!detail::process_socket(
3693 true, sock, 1, read_timeout_sec_, read_timeout_usec_,
3694 [&](Stream &strm, bool /*last_connection*/, bool &connection_close) {
3695 Request req2;
3696 req2.method = "CONNECT";
3697 req2.path = host_and_port_;
3698 return process_request(strm, req2, res2, false, connection_close);
3699 })) {
3700 detail::close_socket(sock);
3701 error = false;
3702 return false;
3703 }
2834 3704
2835 return ret; 3705 if (res2.status == 407) {
2836 })) { 3706 if (!proxy_digest_auth_username_.empty() &&
3707 !proxy_digest_auth_password_.empty()) {
3708 std::map<std::string, std::string> auth;
3709 if (parse_www_authenticate(res2, auth, true)) {
3710 Response res3;
3711 if (!detail::process_socket(
3712 true, sock, 1, read_timeout_sec_, read_timeout_usec_,
3713 [&](Stream &strm, bool /*last_connection*/,
3714 bool &connection_close) {
3715 Request req3;
3716 req3.method = "CONNECT";
3717 req3.path = host_and_port_;
3718 req3.headers.insert(make_digest_authentication_header(
3719 req3, auth, 1, random_string(10),
3720 proxy_digest_auth_username_, proxy_digest_auth_password_,
3721 true));
3722 return process_request(strm, req3, res3, false,
3723 connection_close);
3724 })) {
3725 detail::close_socket(sock);
3726 error = false;
3727 return false;
3728 }
3729 }
3730 } else {
3731 res = res2;
2837 return false; 3732 return false;
2838 } 3733 }
2839 } 3734 }
2840 3735
2841 return true; 3736 return true;
2842} 3737}
3738#endif
2843 3739
2844inline bool Client::redirect(const Request &req, Response &res) { 3740inline bool Client::redirect(const Request &req, Response &res) {
2845 if (req.redirect_count == 0) { return false; } 3741 if (req.redirect_count == 0) { return false; }
@@ -2847,46 +3743,47 @@ inline bool Client::redirect(const Request &req, Response &res) {
2847 auto location = res.get_header_value("location"); 3743 auto location = res.get_header_value("location");
2848 if (location.empty()) { return false; } 3744 if (location.empty()) { return false; }
2849 3745
2850 std::regex re( 3746 const static std::regex re(
2851 R"(^(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*(?:\?[^#]*)?)(?:#.*)?)"); 3747 R"(^(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*(?:\?[^#]*)?)(?:#.*)?)");
2852 3748
3749 std::smatch m;
3750 if (!regex_match(location, m, re)) { return false; }
3751
2853 auto scheme = is_ssl() ? "https" : "http"; 3752 auto scheme = is_ssl() ? "https" : "http";
2854 3753
2855 std::smatch m; 3754 auto next_scheme = m[1].str();
2856 if (regex_match(location, m, re)) { 3755 auto next_host = m[2].str();
2857 auto next_scheme = m[1].str(); 3756 auto next_path = m[3].str();
2858 auto next_host = m[2].str(); 3757 if (next_scheme.empty()) { next_scheme = scheme; }
2859 auto next_path = m[3].str(); 3758 if (next_scheme.empty()) { next_scheme = scheme; }
2860 if (next_host.empty()) { next_host = host_; } 3759 if (next_host.empty()) { next_host = host_; }
2861 if (next_path.empty()) { next_path = "/"; } 3760 if (next_path.empty()) { next_path = "/"; }
2862 3761
2863 if (next_scheme == scheme && next_host == host_) { 3762 if (next_scheme == scheme && next_host == host_) {
2864 return detail::redirect(*this, req, res, next_path); 3763 return detail::redirect(*this, req, res, next_path);
2865 } else { 3764 } else {
2866 if (next_scheme == "https") { 3765 if (next_scheme == "https") {
2867#ifdef CPPHTTPLIB_OPENSSL_SUPPORT 3766#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
2868 SSLClient cli(next_host.c_str()); 3767 SSLClient cli(next_host.c_str());
2869 cli.follow_location(true); 3768 cli.copy_settings(*this);
2870 return detail::redirect(cli, req, res, next_path); 3769 return detail::redirect(cli, req, res, next_path);
2871#else 3770#else
2872 return false; 3771 return false;
2873#endif 3772#endif
2874 } else { 3773 } else {
2875 Client cli(next_host.c_str()); 3774 Client cli(next_host.c_str());
2876 cli.follow_location(true); 3775 cli.copy_settings(*this);
2877 return detail::redirect(cli, req, res, next_path); 3776 return detail::redirect(cli, req, res, next_path);
2878 }
2879 } 3777 }
2880 } 3778 }
2881 return false;
2882} 3779}
2883 3780
2884inline void Client::write_request(Stream &strm, const Request &req, 3781inline bool Client::write_request(Stream &strm, const Request &req,
2885 bool last_connection) { 3782 bool last_connection) {
2886 BufferStream bstrm; 3783 detail::BufferStream bstrm;
2887 3784
2888 // Request line 3785 // Request line
2889 auto path = detail::encode_url(req.path); 3786 const auto &path = detail::encode_url(req.path);
2890 3787
2891 bstrm.write_format("%s %s HTTP/1.1\r\n", req.method.c_str(), path.c_str()); 3788 bstrm.write_format("%s %s HTTP/1.1\r\n", req.method.c_str(), path.c_str());
2892 3789
@@ -2913,11 +3810,14 @@ inline void Client::write_request(Stream &strm, const Request &req,
2913 if (!req.has_header("Accept")) { headers.emplace("Accept", "*/*"); } 3810 if (!req.has_header("Accept")) { headers.emplace("Accept", "*/*"); }
2914 3811
2915 if (!req.has_header("User-Agent")) { 3812 if (!req.has_header("User-Agent")) {
2916 headers.emplace("User-Agent", "cpp-httplib/0.2"); 3813 headers.emplace("User-Agent", "cpp-httplib/0.5");
2917 } 3814 }
2918 3815
2919 if (req.body.empty()) { 3816 if (req.body.empty()) {
2920 if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH") { 3817 if (req.content_provider) {
3818 auto length = std::to_string(req.content_length);
3819 headers.emplace("Content-Length", length);
3820 } else {
2921 headers.emplace("Content-Length", "0"); 3821 headers.emplace("Content-Length", "0");
2922 } 3822 }
2923 } else { 3823 } else {
@@ -2931,21 +3831,100 @@ inline void Client::write_request(Stream &strm, const Request &req,
2931 } 3831 }
2932 } 3832 }
2933 3833
2934 detail::write_headers(bstrm, req, headers); 3834 if (!basic_auth_username_.empty() && !basic_auth_password_.empty()) {
3835 headers.insert(make_basic_authentication_header(
3836 basic_auth_username_, basic_auth_password_, false));
3837 }
2935 3838
2936 // Body 3839 if (!proxy_basic_auth_username_.empty() &&
2937 if (!req.body.empty()) { bstrm.write(req.body); } 3840 !proxy_basic_auth_password_.empty()) {
3841 headers.insert(make_basic_authentication_header(
3842 proxy_basic_auth_username_, proxy_basic_auth_password_, true));
3843 }
3844
3845 detail::write_headers(bstrm, req, headers);
2938 3846
2939 // Flush buffer 3847 // Flush buffer
2940 auto &data = bstrm.get_buffer(); 3848 auto &data = bstrm.get_buffer();
2941 strm.write(data.data(), data.size()); 3849 strm.write(data.data(), data.size());
3850
3851 // Body
3852 if (req.body.empty()) {
3853 if (req.content_provider) {
3854 size_t offset = 0;
3855 size_t end_offset = req.content_length;
3856
3857 DataSink data_sink;
3858 data_sink.write = [&](const char *d, size_t l) {
3859 auto written_length = strm.write(d, l);
3860 offset += written_length;
3861 };
3862 data_sink.is_writable = [&](void) { return strm.is_writable(); };
3863
3864 while (offset < end_offset) {
3865 req.content_provider(offset, end_offset - offset, data_sink);
3866 }
3867 }
3868 } else {
3869 strm.write(req.body);
3870 }
3871
3872 return true;
3873}
3874
3875inline std::shared_ptr<Response> Client::send_with_content_provider(
3876 const char *method, const char *path, const Headers &headers,
3877 const std::string &body, size_t content_length,
3878 ContentProvider content_provider, const char *content_type) {
3879 Request req;
3880 req.method = method;
3881 req.headers = headers;
3882 req.path = path;
3883
3884 req.headers.emplace("Content-Type", content_type);
3885
3886#ifdef CPPHTTPLIB_ZLIB_SUPPORT
3887 if (compress_) {
3888 if (content_provider) {
3889 size_t offset = 0;
3890
3891 DataSink data_sink;
3892 data_sink.write = [&](const char *data, size_t data_len) {
3893 req.body.append(data, data_len);
3894 offset += data_len;
3895 };
3896 data_sink.is_writable = [&](void) { return true; };
3897
3898 while (offset < content_length) {
3899 content_provider(offset, content_length - offset, data_sink);
3900 }
3901 } else {
3902 req.body = body;
3903 }
3904
3905 if (!detail::compress(req.body)) { return nullptr; }
3906 req.headers.emplace("Content-Encoding", "gzip");
3907 } else
3908#endif
3909 {
3910 if (content_provider) {
3911 req.content_length = content_length;
3912 req.content_provider = content_provider;
3913 } else {
3914 req.body = body;
3915 }
3916 }
3917
3918 auto res = std::make_shared<Response>();
3919
3920 return send(req, *res) ? res : nullptr;
2942} 3921}
2943 3922
2944inline bool Client::process_request(Stream &strm, const Request &req, 3923inline bool Client::process_request(Stream &strm, const Request &req,
2945 Response &res, bool last_connection, 3924 Response &res, bool last_connection,
2946 bool &connection_close) { 3925 bool &connection_close) {
2947 // Send request 3926 // Send request
2948 write_request(strm, req, last_connection); 3927 if (!write_request(strm, req, last_connection)) { return false; }
2949 3928
2950 // Receive response and headers 3929 // Receive response and headers
2951 if (!read_response_line(strm, res) || 3930 if (!read_response_line(strm, res) ||
@@ -2963,21 +3942,16 @@ inline bool Client::process_request(Stream &strm, const Request &req,
2963 } 3942 }
2964 3943
2965 // Body 3944 // Body
2966 if (req.method != "HEAD") { 3945 if (req.method != "HEAD" && req.method != "CONNECT") {
2967 detail::ContentReceiverCore out = [&](const char *buf, size_t n) { 3946 ContentReceiver out = [&](const char *buf, size_t n) {
2968 if (res.body.size() + n > res.body.max_size()) { return false; } 3947 if (res.body.size() + n > res.body.max_size()) { return false; }
2969 res.body.append(buf, n); 3948 res.body.append(buf, n);
2970 return true; 3949 return true;
2971 }; 3950 };
2972 3951
2973 if (req.content_receiver) { 3952 if (req.content_receiver) {
2974 auto offset = std::make_shared<size_t>(); 3953 out = [&](const char *buf, size_t n) {
2975 auto length = get_header_value_uint64(res.headers, "Content-Length", 0); 3954 return req.content_receiver(buf, n);
2976 auto receiver = req.content_receiver;
2977 out = [offset, length, receiver](const char *buf, size_t n) {
2978 auto ret = receiver(buf, n, *offset, length);
2979 (*offset) += n;
2980 return ret;
2981 }; 3955 };
2982 } 3956 }
2983 3957
@@ -2988,6 +3962,9 @@ inline bool Client::process_request(Stream &strm, const Request &req,
2988 } 3962 }
2989 } 3963 }
2990 3964
3965 // Log
3966 if (logger_) { logger_(req, res); }
3967
2991 return true; 3968 return true;
2992} 3969}
2993 3970
@@ -2997,25 +3974,25 @@ inline bool Client::process_and_close_socket(
2997 bool &connection_close)> 3974 bool &connection_close)>
2998 callback) { 3975 callback) {
2999 request_count = std::min(request_count, keep_alive_max_count_); 3976 request_count = std::min(request_count, keep_alive_max_count_);
3000 return detail::process_and_close_socket(true, sock, request_count, callback); 3977 return detail::process_and_close_socket(true, sock, request_count,
3978 read_timeout_sec_, read_timeout_usec_,
3979 callback);
3001} 3980}
3002 3981
3003inline bool Client::is_ssl() const { return false; } 3982inline bool Client::is_ssl() const { return false; }
3004 3983
3005inline std::shared_ptr<Response> Client::Get(const char *path) { 3984inline std::shared_ptr<Response> Client::Get(const char *path) {
3006 Progress dummy; 3985 return Get(path, Headers(), Progress());
3007 return Get(path, Headers(), dummy);
3008} 3986}
3009 3987
3010inline std::shared_ptr<Response> Client::Get(const char *path, 3988inline std::shared_ptr<Response> Client::Get(const char *path,
3011 Progress progress) { 3989 Progress progress) {
3012 return Get(path, Headers(), progress); 3990 return Get(path, Headers(), std::move(progress));
3013} 3991}
3014 3992
3015inline std::shared_ptr<Response> Client::Get(const char *path, 3993inline std::shared_ptr<Response> Client::Get(const char *path,
3016 const Headers &headers) { 3994 const Headers &headers) {
3017 Progress dummy; 3995 return Get(path, headers, Progress());
3018 return Get(path, headers, dummy);
3019} 3996}
3020 3997
3021inline std::shared_ptr<Response> 3998inline std::shared_ptr<Response>
@@ -3024,7 +4001,7 @@ Client::Get(const char *path, const Headers &headers, Progress progress) {
3024 req.method = "GET"; 4001 req.method = "GET";
3025 req.path = path; 4002 req.path = path;
3026 req.headers = headers; 4003 req.headers = headers;
3027 req.progress = progress; 4004 req.progress = std::move(progress);
3028 4005
3029 auto res = std::make_shared<Response>(); 4006 auto res = std::make_shared<Response>();
3030 return send(req, *res) ? res : nullptr; 4007 return send(req, *res) ? res : nullptr;
@@ -3032,36 +4009,36 @@ Client::Get(const char *path, const Headers &headers, Progress progress) {
3032 4009
3033inline std::shared_ptr<Response> Client::Get(const char *path, 4010inline std::shared_ptr<Response> Client::Get(const char *path,
3034 ContentReceiver content_receiver) { 4011 ContentReceiver content_receiver) {
3035 Progress dummy; 4012 return Get(path, Headers(), nullptr, std::move(content_receiver), Progress());
3036 return Get(path, Headers(), nullptr, content_receiver, dummy);
3037} 4013}
3038 4014
3039inline std::shared_ptr<Response> Client::Get(const char *path, 4015inline std::shared_ptr<Response> Client::Get(const char *path,
3040 ContentReceiver content_receiver, 4016 ContentReceiver content_receiver,
3041 Progress progress) { 4017 Progress progress) {
3042 return Get(path, Headers(), nullptr, content_receiver, progress); 4018 return Get(path, Headers(), nullptr, std::move(content_receiver),
4019 std::move(progress));
3043} 4020}
3044 4021
3045inline std::shared_ptr<Response> Client::Get(const char *path, 4022inline std::shared_ptr<Response> Client::Get(const char *path,
3046 const Headers &headers, 4023 const Headers &headers,
3047 ContentReceiver content_receiver) { 4024 ContentReceiver content_receiver) {
3048 Progress dummy; 4025 return Get(path, headers, nullptr, std::move(content_receiver), Progress());
3049 return Get(path, headers, nullptr, content_receiver, dummy);
3050} 4026}
3051 4027
3052inline std::shared_ptr<Response> Client::Get(const char *path, 4028inline std::shared_ptr<Response> Client::Get(const char *path,
3053 const Headers &headers, 4029 const Headers &headers,
3054 ContentReceiver content_receiver, 4030 ContentReceiver content_receiver,
3055 Progress progress) { 4031 Progress progress) {
3056 return Get(path, headers, nullptr, content_receiver, progress); 4032 return Get(path, headers, nullptr, std::move(content_receiver),
4033 std::move(progress));
3057} 4034}
3058 4035
3059inline std::shared_ptr<Response> Client::Get(const char *path, 4036inline std::shared_ptr<Response> Client::Get(const char *path,
3060 const Headers &headers, 4037 const Headers &headers,
3061 ResponseHandler response_handler, 4038 ResponseHandler response_handler,
3062 ContentReceiver content_receiver) { 4039 ContentReceiver content_receiver) {
3063 Progress dummy; 4040 return Get(path, headers, std::move(response_handler), content_receiver,
3064 return Get(path, headers, response_handler, content_receiver, dummy); 4041 Progress());
3065} 4042}
3066 4043
3067inline std::shared_ptr<Response> Client::Get(const char *path, 4044inline std::shared_ptr<Response> Client::Get(const char *path,
@@ -3073,9 +4050,9 @@ inline std::shared_ptr<Response> Client::Get(const char *path,
3073 req.method = "GET"; 4050 req.method = "GET";
3074 req.path = path; 4051 req.path = path;
3075 req.headers = headers; 4052 req.headers = headers;
3076 req.response_handler = response_handler; 4053 req.response_handler = std::move(response_handler);
3077 req.content_receiver = content_receiver; 4054 req.content_receiver = std::move(content_receiver);
3078 req.progress = progress; 4055 req.progress = std::move(progress);
3079 4056
3080 auto res = std::make_shared<Response>(); 4057 auto res = std::make_shared<Response>();
3081 return send(req, *res) ? res : nullptr; 4058 return send(req, *res) ? res : nullptr;
@@ -3107,17 +4084,8 @@ inline std::shared_ptr<Response> Client::Post(const char *path,
3107 const Headers &headers, 4084 const Headers &headers,
3108 const std::string &body, 4085 const std::string &body,
3109 const char *content_type) { 4086 const char *content_type) {
3110 Request req; 4087 return send_with_content_provider("POST", path, headers, body, 0, nullptr,
3111 req.method = "POST"; 4088 content_type);
3112 req.headers = headers;
3113 req.path = path;
3114
3115 req.headers.emplace("Content-Type", content_type);
3116 req.body = body;
3117
3118 auto res = std::make_shared<Response>();
3119
3120 return send(req, *res) ? res : nullptr;
3121} 4089}
3122 4090
3123inline std::shared_ptr<Response> Client::Post(const char *path, 4091inline std::shared_ptr<Response> Client::Post(const char *path,
@@ -3125,6 +4093,21 @@ inline std::shared_ptr<Response> Client::Post(const char *path,
3125 return Post(path, Headers(), params); 4093 return Post(path, Headers(), params);
3126} 4094}
3127 4095
4096inline std::shared_ptr<Response> Client::Post(const char *path,
4097 size_t content_length,
4098 ContentProvider content_provider,
4099 const char *content_type) {
4100 return Post(path, Headers(), content_length, content_provider, content_type);
4101}
4102
4103inline std::shared_ptr<Response>
4104Client::Post(const char *path, const Headers &headers, size_t content_length,
4105 ContentProvider content_provider, const char *content_type) {
4106 return send_with_content_provider("POST", path, headers, std::string(),
4107 content_length, content_provider,
4108 content_type);
4109}
4110
3128inline std::shared_ptr<Response> 4111inline std::shared_ptr<Response>
3129Client::Post(const char *path, const Headers &headers, const Params &params) { 4112Client::Post(const char *path, const Headers &headers, const Params &params) {
3130 std::string query; 4113 std::string query;
@@ -3146,35 +4129,28 @@ Client::Post(const char *path, const MultipartFormDataItems &items) {
3146inline std::shared_ptr<Response> 4129inline std::shared_ptr<Response>
3147Client::Post(const char *path, const Headers &headers, 4130Client::Post(const char *path, const Headers &headers,
3148 const MultipartFormDataItems &items) { 4131 const MultipartFormDataItems &items) {
3149 Request req;
3150 req.method = "POST";
3151 req.headers = headers;
3152 req.path = path;
3153
3154 auto boundary = detail::make_multipart_data_boundary(); 4132 auto boundary = detail::make_multipart_data_boundary();
3155 4133
3156 req.headers.emplace("Content-Type", 4134 std::string body;
3157 "multipart/form-data; boundary=" + boundary);
3158 4135
3159 for (const auto &item : items) { 4136 for (const auto &item : items) {
3160 req.body += "--" + boundary + "\r\n"; 4137 body += "--" + boundary + "\r\n";
3161 req.body += "Content-Disposition: form-data; name=\"" + item.name + "\""; 4138 body += "Content-Disposition: form-data; name=\"" + item.name + "\"";
3162 if (!item.filename.empty()) { 4139 if (!item.filename.empty()) {
3163 req.body += "; filename=\"" + item.filename + "\""; 4140 body += "; filename=\"" + item.filename + "\"";
3164 } 4141 }
3165 req.body += "\r\n"; 4142 body += "\r\n";
3166 if (!item.content_type.empty()) { 4143 if (!item.content_type.empty()) {
3167 req.body += "Content-Type: " + item.content_type + "\r\n"; 4144 body += "Content-Type: " + item.content_type + "\r\n";
3168 } 4145 }
3169 req.body += "\r\n"; 4146 body += "\r\n";
3170 req.body += item.content + "\r\n"; 4147 body += item.content + "\r\n";
3171 } 4148 }
3172 4149
3173 req.body += "--" + boundary + "--\r\n"; 4150 body += "--" + boundary + "--\r\n";
3174 4151
3175 auto res = std::make_shared<Response>(); 4152 std::string content_type = "multipart/form-data; boundary=" + boundary;
3176 4153 return Post(path, headers, body, content_type.c_str());
3177 return send(req, *res) ? res : nullptr;
3178} 4154}
3179 4155
3180inline std::shared_ptr<Response> Client::Put(const char *path, 4156inline std::shared_ptr<Response> Client::Put(const char *path,
@@ -3187,17 +4163,41 @@ inline std::shared_ptr<Response> Client::Put(const char *path,
3187 const Headers &headers, 4163 const Headers &headers,
3188 const std::string &body, 4164 const std::string &body,
3189 const char *content_type) { 4165 const char *content_type) {
3190 Request req; 4166 return send_with_content_provider("PUT", path, headers, body, 0, nullptr,
3191 req.method = "PUT"; 4167 content_type);
3192 req.headers = headers; 4168}
3193 req.path = path;
3194 4169
3195 req.headers.emplace("Content-Type", content_type); 4170inline std::shared_ptr<Response> Client::Put(const char *path,
3196 req.body = body; 4171 size_t content_length,
4172 ContentProvider content_provider,
4173 const char *content_type) {
4174 return Put(path, Headers(), content_length, content_provider, content_type);
4175}
3197 4176
3198 auto res = std::make_shared<Response>(); 4177inline std::shared_ptr<Response>
4178Client::Put(const char *path, const Headers &headers, size_t content_length,
4179 ContentProvider content_provider, const char *content_type) {
4180 return send_with_content_provider("PUT", path, headers, std::string(),
4181 content_length, content_provider,
4182 content_type);
4183}
3199 4184
3200 return send(req, *res) ? res : nullptr; 4185inline std::shared_ptr<Response> Client::Put(const char *path,
4186 const Params &params) {
4187 return Put(path, Headers(), params);
4188}
4189
4190inline std::shared_ptr<Response>
4191Client::Put(const char *path, const Headers &headers, const Params &params) {
4192 std::string query;
4193 for (auto it = params.begin(); it != params.end(); ++it) {
4194 if (it != params.begin()) { query += "&"; }
4195 query += it->first;
4196 query += "=";
4197 query += detail::encode_url(it->second);
4198 }
4199
4200 return Put(path, headers, query, "application/x-www-form-urlencoded");
3201} 4201}
3202 4202
3203inline std::shared_ptr<Response> Client::Patch(const char *path, 4203inline std::shared_ptr<Response> Client::Patch(const char *path,
@@ -3210,17 +4210,23 @@ inline std::shared_ptr<Response> Client::Patch(const char *path,
3210 const Headers &headers, 4210 const Headers &headers,
3211 const std::string &body, 4211 const std::string &body,
3212 const char *content_type) { 4212 const char *content_type) {
3213 Request req; 4213 return send_with_content_provider("PATCH", path, headers, body, 0, nullptr,
3214 req.method = "PATCH"; 4214 content_type);
3215 req.headers = headers; 4215}
3216 req.path = path;
3217
3218 req.headers.emplace("Content-Type", content_type);
3219 req.body = body;
3220 4216
3221 auto res = std::make_shared<Response>(); 4217inline std::shared_ptr<Response> Client::Patch(const char *path,
4218 size_t content_length,
4219 ContentProvider content_provider,
4220 const char *content_type) {
4221 return Patch(path, Headers(), content_length, content_provider, content_type);
4222}
3222 4223
3223 return send(req, *res) ? res : nullptr; 4224inline std::shared_ptr<Response>
4225Client::Patch(const char *path, const Headers &headers, size_t content_length,
4226 ContentProvider content_provider, const char *content_type) {
4227 return send_with_content_provider("PATCH", path, headers, std::string(),
4228 content_length, content_provider,
4229 content_type);
3224} 4230}
3225 4231
3226inline std::shared_ptr<Response> Client::Delete(const char *path) { 4232inline std::shared_ptr<Response> Client::Delete(const char *path) {
@@ -3271,11 +4277,58 @@ inline std::shared_ptr<Response> Client::Options(const char *path,
3271 return send(req, *res) ? res : nullptr; 4277 return send(req, *res) ? res : nullptr;
3272} 4278}
3273 4279
4280inline void Client::set_timeout_sec(time_t timeout_sec) {
4281 timeout_sec_ = timeout_sec;
4282}
4283
4284inline void Client::set_read_timeout(time_t sec, time_t usec) {
4285 read_timeout_sec_ = sec;
4286 read_timeout_usec_ = usec;
4287}
4288
3274inline void Client::set_keep_alive_max_count(size_t count) { 4289inline void Client::set_keep_alive_max_count(size_t count) {
3275 keep_alive_max_count_ = count; 4290 keep_alive_max_count_ = count;
3276} 4291}
3277 4292
3278inline void Client::follow_location(bool on) { follow_location_ = on; } 4293inline void Client::set_basic_auth(const char *username, const char *password) {
4294 basic_auth_username_ = username;
4295 basic_auth_password_ = password;
4296}
4297
4298#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
4299inline void Client::set_digest_auth(const char *username,
4300 const char *password) {
4301 digest_auth_username_ = username;
4302 digest_auth_password_ = password;
4303}
4304#endif
4305
4306inline void Client::set_follow_location(bool on) { follow_location_ = on; }
4307
4308inline void Client::set_compress(bool on) { compress_ = on; }
4309
4310inline void Client::set_interface(const char *intf) { interface_ = intf; }
4311
4312inline void Client::set_proxy(const char *host, int port) {
4313 proxy_host_ = host;
4314 proxy_port_ = port;
4315}
4316
4317inline void Client::set_proxy_basic_auth(const char *username,
4318 const char *password) {
4319 proxy_basic_auth_username_ = username;
4320 proxy_basic_auth_password_ = password;
4321}
4322
4323#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
4324inline void Client::set_proxy_digest_auth(const char *username,
4325 const char *password) {
4326 proxy_digest_auth_username_ = username;
4327 proxy_digest_auth_password_ = password;
4328}
4329#endif
4330
4331inline void Client::set_logger(Logger logger) { logger_ = std::move(logger); }
3279 4332
3280/* 4333/*
3281 * SSL Implementation 4334 * SSL Implementation
@@ -3284,11 +4337,10 @@ inline void Client::follow_location(bool on) { follow_location_ = on; }
3284namespace detail { 4337namespace detail {
3285 4338
3286template <typename U, typename V, typename T> 4339template <typename U, typename V, typename T>
3287inline bool process_and_close_socket_ssl(bool is_client_request, socket_t sock, 4340inline bool process_and_close_socket_ssl(
3288 size_t keep_alive_max_count, 4341 bool is_client_request, socket_t sock, size_t keep_alive_max_count,
3289 SSL_CTX *ctx, std::mutex &ctx_mutex, 4342 time_t read_timeout_sec, time_t read_timeout_usec, SSL_CTX *ctx,
3290 U SSL_connect_or_accept, V setup, 4343 std::mutex &ctx_mutex, U SSL_connect_or_accept, V setup, T callback) {
3291 T callback) {
3292 assert(keep_alive_max_count > 0); 4344 assert(keep_alive_max_count > 0);
3293 4345
3294 SSL *ssl = nullptr; 4346 SSL *ssl = nullptr;
@@ -3316,7 +4368,7 @@ inline bool process_and_close_socket_ssl(bool is_client_request, socket_t sock,
3316 return false; 4368 return false;
3317 } 4369 }
3318 4370
3319 bool ret = false; 4371 auto ret = false;
3320 4372
3321 if (SSL_connect_or_accept(ssl) == 1) { 4373 if (SSL_connect_or_accept(ssl) == 1) {
3322 if (keep_alive_max_count > 1) { 4374 if (keep_alive_max_count > 1) {
@@ -3325,7 +4377,7 @@ inline bool process_and_close_socket_ssl(bool is_client_request, socket_t sock,
3325 (is_client_request || 4377 (is_client_request ||
3326 detail::select_read(sock, CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND, 4378 detail::select_read(sock, CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND,
3327 CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND) > 0)) { 4379 CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND) > 0)) {
3328 SSLSocketStream strm(sock, ssl); 4380 SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec);
3329 auto last_connection = count == 1; 4381 auto last_connection = count == 1;
3330 auto connection_close = false; 4382 auto connection_close = false;
3331 4383
@@ -3335,7 +4387,7 @@ inline bool process_and_close_socket_ssl(bool is_client_request, socket_t sock,
3335 count--; 4387 count--;
3336 } 4388 }
3337 } else { 4389 } else {
3338 SSLSocketStream strm(sock, ssl); 4390 SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec);
3339 auto dummy_connection_close = false; 4391 auto dummy_connection_close = false;
3340 ret = callback(ssl, strm, true, dummy_connection_close); 4392 ret = callback(ssl, strm, true, dummy_connection_close);
3341 } 4393 }
@@ -3382,11 +4434,20 @@ private:
3382class SSLInit { 4434class SSLInit {
3383public: 4435public:
3384 SSLInit() { 4436 SSLInit() {
4437#if OPENSSL_VERSION_NUMBER < 0x1010001fL
3385 SSL_load_error_strings(); 4438 SSL_load_error_strings();
3386 SSL_library_init(); 4439 SSL_library_init();
4440#else
4441 OPENSSL_init_ssl(
4442 OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL);
4443#endif
3387 } 4444 }
3388 4445
3389 ~SSLInit() { ERR_free_strings(); } 4446 ~SSLInit() {
4447#if OPENSSL_VERSION_NUMBER < 0x1010001fL
4448 ERR_free_strings();
4449#endif
4450 }
3390 4451
3391private: 4452private:
3392#if OPENSSL_VERSION_NUMBER < 0x10100000L 4453#if OPENSSL_VERSION_NUMBER < 0x10100000L
@@ -3394,41 +4455,44 @@ private:
3394#endif 4455#endif
3395}; 4456};
3396 4457
3397static SSLInit sslinit_;
3398
3399} // namespace detail
3400
3401// SSL socket stream implementation 4458// SSL socket stream implementation
3402inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL *ssl) 4459inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL *ssl,
3403 : sock_(sock), ssl_(ssl) {} 4460 time_t read_timeout_sec,
4461 time_t read_timeout_usec)
4462 : sock_(sock), ssl_(ssl), read_timeout_sec_(read_timeout_sec),
4463 read_timeout_usec_(read_timeout_usec) {}
3404 4464
3405inline SSLSocketStream::~SSLSocketStream() {} 4465inline SSLSocketStream::~SSLSocketStream() {}
3406 4466
4467inline bool SSLSocketStream::is_readable() const {
4468 return detail::select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
4469}
4470
4471inline bool SSLSocketStream::is_writable() const {
4472 return detail::select_write(sock_, 0, 0) > 0;
4473}
4474
3407inline int SSLSocketStream::read(char *ptr, size_t size) { 4475inline int SSLSocketStream::read(char *ptr, size_t size) {
3408 if (SSL_pending(ssl_) > 0 || 4476 if (SSL_pending(ssl_) > 0 ||
3409 detail::select_read(sock_, CPPHTTPLIB_READ_TIMEOUT_SECOND, 4477 select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0) {
3410 CPPHTTPLIB_READ_TIMEOUT_USECOND) > 0) {
3411 return SSL_read(ssl_, ptr, static_cast<int>(size)); 4478 return SSL_read(ssl_, ptr, static_cast<int>(size));
3412 } 4479 }
3413 return -1; 4480 return -1;
3414} 4481}
3415 4482
3416inline int SSLSocketStream::write(const char *ptr, size_t size) { 4483inline int SSLSocketStream::write(const char *ptr, size_t size) {
3417 return SSL_write(ssl_, ptr, static_cast<int>(size)); 4484 if (is_writable()) { return SSL_write(ssl_, ptr, static_cast<int>(size)); }
3418} 4485 return -1;
3419
3420inline int SSLSocketStream::write(const char *ptr) {
3421 return write(ptr, strlen(ptr));
3422}
3423
3424inline int SSLSocketStream::write(const std::string &s) {
3425 return write(s.data(), s.size());
3426} 4486}
3427 4487
3428inline std::string SSLSocketStream::get_remote_addr() const { 4488inline std::string SSLSocketStream::get_remote_addr() const {
3429 return detail::get_remote_addr(sock_); 4489 return detail::get_remote_addr(sock_);
3430} 4490}
3431 4491
4492static SSLInit sslinit_;
4493
4494} // namespace detail
4495
3432// SSL HTTP server implementation 4496// SSL HTTP server implementation
3433inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path, 4497inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path,
3434 const char *client_ca_cert_file_path, 4498 const char *client_ca_cert_file_path,
@@ -3476,8 +4540,8 @@ inline bool SSLServer::is_valid() const { return ctx_; }
3476 4540
3477inline bool SSLServer::process_and_close_socket(socket_t sock) { 4541inline bool SSLServer::process_and_close_socket(socket_t sock) {
3478 return detail::process_and_close_socket_ssl( 4542 return detail::process_and_close_socket_ssl(
3479 false, sock, keep_alive_max_count_, ctx_, ctx_mutex_, SSL_accept, 4543 false, sock, keep_alive_max_count_, read_timeout_sec_, read_timeout_usec_,
3480 [](SSL * /*ssl*/) { return true; }, 4544 ctx_, ctx_mutex_, SSL_accept, [](SSL * /*ssl*/) { return true; },
3481 [this](SSL *ssl, Stream &strm, bool last_connection, 4545 [this](SSL *ssl, Stream &strm, bool last_connection,
3482 bool &connection_close) { 4546 bool &connection_close) {
3483 return process_request(strm, last_connection, connection_close, 4547 return process_request(strm, last_connection, connection_close,
@@ -3486,21 +4550,21 @@ inline bool SSLServer::process_and_close_socket(socket_t sock) {
3486} 4550}
3487 4551
3488// SSL HTTP client implementation 4552// SSL HTTP client implementation
3489inline SSLClient::SSLClient(const char *host, int port, time_t timeout_sec, 4553inline SSLClient::SSLClient(const std::string &host, int port,
3490 const char *client_cert_path, 4554 const std::string &client_cert_path,
3491 const char *client_key_path) 4555 const std::string &client_key_path)
3492 : Client(host, port, timeout_sec) { 4556 : Client(host, port, client_cert_path, client_key_path) {
3493 ctx_ = SSL_CTX_new(SSLv23_client_method()); 4557 ctx_ = SSL_CTX_new(SSLv23_client_method());
3494 4558
3495 detail::split(&host_[0], &host_[host_.size()], '.', 4559 detail::split(&host_[0], &host_[host_.size()], '.',
3496 [&](const char *b, const char *e) { 4560 [&](const char *b, const char *e) {
3497 host_components_.emplace_back(std::string(b, e)); 4561 host_components_.emplace_back(std::string(b, e));
3498 }); 4562 });
3499 if (client_cert_path && client_key_path) { 4563 if (!client_cert_path.empty() && !client_key_path.empty()) {
3500 if (SSL_CTX_use_certificate_file(ctx_, client_cert_path, 4564 if (SSL_CTX_use_certificate_file(ctx_, client_cert_path.c_str(),
3501 SSL_FILETYPE_PEM) != 1 || 4565 SSL_FILETYPE_PEM) != 1 ||
3502 SSL_CTX_use_PrivateKey_file(ctx_, client_key_path, SSL_FILETYPE_PEM) != 4566 SSL_CTX_use_PrivateKey_file(ctx_, client_key_path.c_str(),
3503 1) { 4567 SSL_FILETYPE_PEM) != 1) {
3504 SSL_CTX_free(ctx_); 4568 SSL_CTX_free(ctx_);
3505 ctx_ = nullptr; 4569 ctx_ = nullptr;
3506 } 4570 }
@@ -3527,9 +4591,7 @@ inline long SSLClient::get_openssl_verify_result() const {
3527 return verify_result_; 4591 return verify_result_;
3528} 4592}
3529 4593
3530inline SSL_CTX* SSLClient::ssl_context() const noexcept { 4594inline SSL_CTX *SSLClient::ssl_context() const noexcept { return ctx_; }
3531 return ctx_;
3532}
3533 4595
3534inline bool SSLClient::process_and_close_socket( 4596inline bool SSLClient::process_and_close_socket(
3535 socket_t sock, size_t request_count, 4597 socket_t sock, size_t request_count,
@@ -3541,7 +4603,8 @@ inline bool SSLClient::process_and_close_socket(
3541 4603
3542 return is_valid() && 4604 return is_valid() &&
3543 detail::process_and_close_socket_ssl( 4605 detail::process_and_close_socket_ssl(
3544 true, sock, request_count, ctx_, ctx_mutex_, 4606 true, sock, request_count, read_timeout_sec_, read_timeout_usec_,
4607 ctx_, ctx_mutex_,
3545 [&](SSL *ssl) { 4608 [&](SSL *ssl) {
3546 if (ca_cert_file_path_.empty()) { 4609 if (ca_cert_file_path_.empty()) {
3547 SSL_CTX_set_verify(ctx_, SSL_VERIFY_NONE, nullptr); 4610 SSL_CTX_set_verify(ctx_, SSL_VERIFY_NONE, nullptr);
@@ -3712,6 +4775,8 @@ inline bool SSLClient::check_host_name(const char *pattern,
3712} 4775}
3713#endif 4776#endif
3714 4777
4778// ----------------------------------------------------------------------------
4779
3715} // namespace httplib 4780} // namespace httplib
3716 4781
3717#endif // CPPHTTPLIB_HTTPLIB_H 4782#endif // CPPHTTPLIB_HTTPLIB_H
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index 8f2591d53..04bc3128f 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -120,7 +120,7 @@ private:
120 duration_cast<std::chrono::microseconds>(steady_clock::now() - time_origin); 120 duration_cast<std::chrono::microseconds>(steady_clock::now() - time_origin);
121 entry.log_class = log_class; 121 entry.log_class = log_class;
122 entry.log_level = log_level; 122 entry.log_level = log_level;
123 entry.filename = Common::TrimSourcePath(filename); 123 entry.filename = filename;
124 entry.line_num = line_nr; 124 entry.line_num = line_nr;
125 entry.function = function; 125 entry.function = function;
126 entry.message = std::move(message); 126 entry.message = std::move(message);
diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h
index fca0267a1..fc338c70d 100644
--- a/src/common/logging/backend.h
+++ b/src/common/logging/backend.h
@@ -23,7 +23,7 @@ struct Entry {
23 std::chrono::microseconds timestamp; 23 std::chrono::microseconds timestamp;
24 Class log_class; 24 Class log_class;
25 Level log_level; 25 Level log_level;
26 std::string filename; 26 const char* filename;
27 unsigned int line_num; 27 unsigned int line_num;
28 std::string function; 28 std::string function;
29 std::string message; 29 std::string message;
diff --git a/src/common/logging/log.h b/src/common/logging/log.h
index 259708116..13a4f1e30 100644
--- a/src/common/logging/log.h
+++ b/src/common/logging/log.h
@@ -9,6 +9,15 @@
9 9
10namespace Log { 10namespace Log {
11 11
12// trims up to and including the last of ../, ..\, src/, src\ in a string
13constexpr const char* TrimSourcePath(std::string_view source) {
14 const auto rfind = [source](const std::string_view match) {
15 return source.rfind(match) == source.npos ? 0 : (source.rfind(match) + match.size());
16 };
17 auto idx = std::max({rfind("src/"), rfind("src\\"), rfind("../"), rfind("..\\")});
18 return source.data() + idx;
19}
20
12/// Specifies the severity or level of detail of the log message. 21/// Specifies the severity or level of detail of the log message.
13enum class Level : u8 { 22enum class Level : u8 {
14 Trace, ///< Extremely detailed and repetitive debugging information that is likely to 23 Trace, ///< Extremely detailed and repetitive debugging information that is likely to
@@ -141,24 +150,24 @@ void FmtLogMessage(Class log_class, Level log_level, const char* filename, unsig
141 150
142#ifdef _DEBUG 151#ifdef _DEBUG
143#define LOG_TRACE(log_class, ...) \ 152#define LOG_TRACE(log_class, ...) \
144 ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Trace, __FILE__, __LINE__, \ 153 ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Trace, \
145 __func__, __VA_ARGS__) 154 ::Log::TrimSourcePath(__FILE__), __LINE__, __func__, __VA_ARGS__)
146#else 155#else
147#define LOG_TRACE(log_class, fmt, ...) (void(0)) 156#define LOG_TRACE(log_class, fmt, ...) (void(0))
148#endif 157#endif
149 158
150#define LOG_DEBUG(log_class, ...) \ 159#define LOG_DEBUG(log_class, ...) \
151 ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Debug, __FILE__, __LINE__, \ 160 ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Debug, \
152 __func__, __VA_ARGS__) 161 ::Log::TrimSourcePath(__FILE__), __LINE__, __func__, __VA_ARGS__)
153#define LOG_INFO(log_class, ...) \ 162#define LOG_INFO(log_class, ...) \
154 ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Info, __FILE__, __LINE__, \ 163 ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Info, \
155 __func__, __VA_ARGS__) 164 ::Log::TrimSourcePath(__FILE__), __LINE__, __func__, __VA_ARGS__)
156#define LOG_WARNING(log_class, ...) \ 165#define LOG_WARNING(log_class, ...) \
157 ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Warning, __FILE__, __LINE__, \ 166 ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Warning, \
158 __func__, __VA_ARGS__) 167 ::Log::TrimSourcePath(__FILE__), __LINE__, __func__, __VA_ARGS__)
159#define LOG_ERROR(log_class, ...) \ 168#define LOG_ERROR(log_class, ...) \
160 ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Error, __FILE__, __LINE__, \ 169 ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Error, \
161 __func__, __VA_ARGS__) 170 ::Log::TrimSourcePath(__FILE__), __LINE__, __func__, __VA_ARGS__)
162#define LOG_CRITICAL(log_class, ...) \ 171#define LOG_CRITICAL(log_class, ...) \
163 ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Critical, __FILE__, __LINE__, \ 172 ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Critical, \
164 __func__, __VA_ARGS__) 173 ::Log::TrimSourcePath(__FILE__), __LINE__, __func__, __VA_ARGS__)
diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp
index 959f278aa..84883a1d3 100644
--- a/src/common/string_util.cpp
+++ b/src/common/string_util.cpp
@@ -223,26 +223,4 @@ std::u16string UTF16StringFromFixedZeroTerminatedBuffer(std::u16string_view buff
223 return std::u16string(buffer.begin(), buffer.begin() + len); 223 return std::u16string(buffer.begin(), buffer.begin() + len);
224} 224}
225 225
226const char* TrimSourcePath(const char* path, const char* root) {
227 const char* p = path;
228
229 while (*p != '\0') {
230 const char* next_slash = p;
231 while (*next_slash != '\0' && *next_slash != '/' && *next_slash != '\\') {
232 ++next_slash;
233 }
234
235 bool is_src = Common::ComparePartialString(p, next_slash, root);
236 p = next_slash;
237
238 if (*p != '\0') {
239 ++p;
240 }
241 if (is_src) {
242 path = p;
243 }
244 }
245 return path;
246}
247
248} // namespace Common 226} // namespace Common
diff --git a/src/common/thread.h b/src/common/thread.h
index 0cfd98be6..2fc071685 100644
--- a/src/common/thread.h
+++ b/src/common/thread.h
@@ -28,6 +28,15 @@ public:
28 is_set = false; 28 is_set = false;
29 } 29 }
30 30
31 template <class Duration>
32 bool WaitFor(const std::chrono::duration<Duration>& time) {
33 std::unique_lock lk{mutex};
34 if (!condvar.wait_for(lk, time, [this] { return is_set; }))
35 return false;
36 is_set = false;
37 return true;
38 }
39
31 template <class Clock, class Duration> 40 template <class Clock, class Duration>
32 bool WaitUntil(const std::chrono::time_point<Clock, Duration>& time) { 41 bool WaitUntil(const std::chrono::time_point<Clock, Duration>& time) {
33 std::unique_lock lk{mutex}; 42 std::unique_lock lk{mutex};
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 1a3647a67..26612e692 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -15,14 +15,14 @@ add_library(core STATIC
15 constants.h 15 constants.h
16 core.cpp 16 core.cpp
17 core.h 17 core.h
18 core_cpu.cpp 18 core_manager.cpp
19 core_cpu.h 19 core_manager.h
20 core_timing.cpp 20 core_timing.cpp
21 core_timing.h 21 core_timing.h
22 core_timing_util.cpp 22 core_timing_util.cpp
23 core_timing_util.h 23 core_timing_util.h
24 cpu_core_manager.cpp 24 cpu_manager.cpp
25 cpu_core_manager.h 25 cpu_manager.h
26 crypto/aes_util.cpp 26 crypto/aes_util.cpp
27 crypto/aes_util.h 27 crypto/aes_util.h
28 crypto/encryption_layer.cpp 28 crypto/encryption_layer.cpp
@@ -158,6 +158,8 @@ add_library(core STATIC
158 hle/kernel/mutex.h 158 hle/kernel/mutex.h
159 hle/kernel/object.cpp 159 hle/kernel/object.cpp
160 hle/kernel/object.h 160 hle/kernel/object.h
161 hle/kernel/physical_core.cpp
162 hle/kernel/physical_core.h
161 hle/kernel/process.cpp 163 hle/kernel/process.cpp
162 hle/kernel/process.h 164 hle/kernel/process.h
163 hle/kernel/process_capability.cpp 165 hle/kernel/process_capability.cpp
@@ -179,14 +181,16 @@ add_library(core STATIC
179 hle/kernel/svc.cpp 181 hle/kernel/svc.cpp
180 hle/kernel/svc.h 182 hle/kernel/svc.h
181 hle/kernel/svc_wrap.h 183 hle/kernel/svc_wrap.h
184 hle/kernel/synchronization_object.cpp
185 hle/kernel/synchronization_object.h
186 hle/kernel/synchronization.cpp
187 hle/kernel/synchronization.h
182 hle/kernel/thread.cpp 188 hle/kernel/thread.cpp
183 hle/kernel/thread.h 189 hle/kernel/thread.h
184 hle/kernel/transfer_memory.cpp 190 hle/kernel/transfer_memory.cpp
185 hle/kernel/transfer_memory.h 191 hle/kernel/transfer_memory.h
186 hle/kernel/vm_manager.cpp 192 hle/kernel/vm_manager.cpp
187 hle/kernel/vm_manager.h 193 hle/kernel/vm_manager.h
188 hle/kernel/wait_object.cpp
189 hle/kernel/wait_object.h
190 hle/kernel/writable_event.cpp 194 hle/kernel/writable_event.cpp
191 hle/kernel/writable_event.h 195 hle/kernel/writable_event.h
192 hle/lock.cpp 196 hle/lock.cpp
diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp
index e825c0526..29eaf74e5 100644
--- a/src/core/arm/dynarmic/arm_dynarmic.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic.cpp
@@ -10,11 +10,13 @@
10#include "common/microprofile.h" 10#include "common/microprofile.h"
11#include "core/arm/dynarmic/arm_dynarmic.h" 11#include "core/arm/dynarmic/arm_dynarmic.h"
12#include "core/core.h" 12#include "core/core.h"
13#include "core/core_cpu.h" 13#include "core/core_manager.h"
14#include "core/core_timing.h" 14#include "core/core_timing.h"
15#include "core/core_timing_util.h" 15#include "core/core_timing_util.h"
16#include "core/gdbstub/gdbstub.h" 16#include "core/gdbstub/gdbstub.h"
17#include "core/hardware_properties.h"
17#include "core/hle/kernel/process.h" 18#include "core/hle/kernel/process.h"
19#include "core/hle/kernel/scheduler.h"
18#include "core/hle/kernel/svc.h" 20#include "core/hle/kernel/svc.h"
19#include "core/hle/kernel/vm_manager.h" 21#include "core/hle/kernel/vm_manager.h"
20#include "core/memory.h" 22#include "core/memory.h"
@@ -87,7 +89,7 @@ public:
87 if (GDBStub::IsServerEnabled()) { 89 if (GDBStub::IsServerEnabled()) {
88 parent.jit->HaltExecution(); 90 parent.jit->HaltExecution();
89 parent.SetPC(pc); 91 parent.SetPC(pc);
90 Kernel::Thread* thread = Kernel::GetCurrentThread(); 92 Kernel::Thread* const thread = parent.system.CurrentScheduler().GetCurrentThread();
91 parent.SaveContext(thread->GetContext()); 93 parent.SaveContext(thread->GetContext());
92 GDBStub::Break(); 94 GDBStub::Break();
93 GDBStub::SendTrap(thread, 5); 95 GDBStub::SendTrap(thread, 5);
@@ -152,7 +154,7 @@ std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit(Common::PageTable& pag
152 config.tpidr_el0 = &cb->tpidr_el0; 154 config.tpidr_el0 = &cb->tpidr_el0;
153 config.dczid_el0 = 4; 155 config.dczid_el0 = 4;
154 config.ctr_el0 = 0x8444c004; 156 config.ctr_el0 = 0x8444c004;
155 config.cntfrq_el0 = Timing::CNTFREQ; 157 config.cntfrq_el0 = Hardware::CNTFREQ;
156 158
157 // Unpredictable instructions 159 // Unpredictable instructions
158 config.define_unpredictable_behaviour = true; 160 config.define_unpredictable_behaviour = true;
diff --git a/src/core/arm/exclusive_monitor.cpp b/src/core/arm/exclusive_monitor.cpp
index abd59ff4b..94570e520 100644
--- a/src/core/arm/exclusive_monitor.cpp
+++ b/src/core/arm/exclusive_monitor.cpp
@@ -2,10 +2,24 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#ifdef ARCHITECTURE_x86_64
6#include "core/arm/dynarmic/arm_dynarmic.h"
7#endif
5#include "core/arm/exclusive_monitor.h" 8#include "core/arm/exclusive_monitor.h"
9#include "core/memory.h"
6 10
7namespace Core { 11namespace Core {
8 12
9ExclusiveMonitor::~ExclusiveMonitor() = default; 13ExclusiveMonitor::~ExclusiveMonitor() = default;
10 14
15std::unique_ptr<Core::ExclusiveMonitor> MakeExclusiveMonitor(Memory::Memory& memory,
16 std::size_t num_cores) {
17#ifdef ARCHITECTURE_x86_64
18 return std::make_unique<Core::DynarmicExclusiveMonitor>(memory, num_cores);
19#else
20 // TODO(merry): Passthrough exclusive monitor
21 return nullptr;
22#endif
23}
24
11} // namespace Core 25} // namespace Core
diff --git a/src/core/arm/exclusive_monitor.h b/src/core/arm/exclusive_monitor.h
index f59aca667..4ef418b90 100644
--- a/src/core/arm/exclusive_monitor.h
+++ b/src/core/arm/exclusive_monitor.h
@@ -4,8 +4,14 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <memory>
8
7#include "common/common_types.h" 9#include "common/common_types.h"
8 10
11namespace Memory {
12class Memory;
13}
14
9namespace Core { 15namespace Core {
10 16
11class ExclusiveMonitor { 17class ExclusiveMonitor {
@@ -22,4 +28,7 @@ public:
22 virtual bool ExclusiveWrite128(std::size_t core_index, VAddr vaddr, u128 value) = 0; 28 virtual bool ExclusiveWrite128(std::size_t core_index, VAddr vaddr, u128 value) = 0;
23}; 29};
24 30
31std::unique_ptr<Core::ExclusiveMonitor> MakeExclusiveMonitor(Memory::Memory& memory,
32 std::size_t num_cores);
33
25} // namespace Core 34} // namespace Core
diff --git a/src/core/arm/unicorn/arm_unicorn.cpp b/src/core/arm/unicorn/arm_unicorn.cpp
index 48182c99a..f99ad5802 100644
--- a/src/core/arm/unicorn/arm_unicorn.cpp
+++ b/src/core/arm/unicorn/arm_unicorn.cpp
@@ -9,6 +9,7 @@
9#include "core/arm/unicorn/arm_unicorn.h" 9#include "core/arm/unicorn/arm_unicorn.h"
10#include "core/core.h" 10#include "core/core.h"
11#include "core/core_timing.h" 11#include "core/core_timing.h"
12#include "core/hle/kernel/scheduler.h"
12#include "core/hle/kernel/svc.h" 13#include "core/hle/kernel/svc.h"
13 14
14namespace Core { 15namespace Core {
@@ -177,7 +178,7 @@ void ARM_Unicorn::ExecuteInstructions(std::size_t num_instructions) {
177 uc_reg_write(uc, UC_ARM64_REG_PC, &last_bkpt.address); 178 uc_reg_write(uc, UC_ARM64_REG_PC, &last_bkpt.address);
178 } 179 }
179 180
180 Kernel::Thread* thread = Kernel::GetCurrentThread(); 181 Kernel::Thread* const thread = system.CurrentScheduler().GetCurrentThread();
181 SaveContext(thread->GetContext()); 182 SaveContext(thread->GetContext());
182 if (last_bkpt_hit || GDBStub::IsMemoryBreak() || GDBStub::GetCpuStepFlag()) { 183 if (last_bkpt_hit || GDBStub::IsMemoryBreak() || GDBStub::GetCpuStepFlag()) {
183 last_bkpt_hit = false; 184 last_bkpt_hit = false;
diff --git a/src/core/core.cpp b/src/core/core.cpp
index d697b80ef..0eb0c0dca 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -11,9 +11,9 @@
11#include "common/string_util.h" 11#include "common/string_util.h"
12#include "core/arm/exclusive_monitor.h" 12#include "core/arm/exclusive_monitor.h"
13#include "core/core.h" 13#include "core/core.h"
14#include "core/core_cpu.h" 14#include "core/core_manager.h"
15#include "core/core_timing.h" 15#include "core/core_timing.h"
16#include "core/cpu_core_manager.h" 16#include "core/cpu_manager.h"
17#include "core/file_sys/bis_factory.h" 17#include "core/file_sys/bis_factory.h"
18#include "core/file_sys/card_image.h" 18#include "core/file_sys/card_image.h"
19#include "core/file_sys/mode.h" 19#include "core/file_sys/mode.h"
@@ -28,6 +28,7 @@
28#include "core/hardware_interrupt_manager.h" 28#include "core/hardware_interrupt_manager.h"
29#include "core/hle/kernel/client_port.h" 29#include "core/hle/kernel/client_port.h"
30#include "core/hle/kernel/kernel.h" 30#include "core/hle/kernel/kernel.h"
31#include "core/hle/kernel/physical_core.h"
31#include "core/hle/kernel/process.h" 32#include "core/hle/kernel/process.h"
32#include "core/hle/kernel/scheduler.h" 33#include "core/hle/kernel/scheduler.h"
33#include "core/hle/kernel/thread.h" 34#include "core/hle/kernel/thread.h"
@@ -113,16 +114,25 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
113struct System::Impl { 114struct System::Impl {
114 explicit Impl(System& system) 115 explicit Impl(System& system)
115 : kernel{system}, fs_controller{system}, memory{system}, 116 : kernel{system}, fs_controller{system}, memory{system},
116 cpu_core_manager{system}, reporter{system}, applet_manager{system} {} 117 cpu_manager{system}, reporter{system}, applet_manager{system} {}
117 118
118 Cpu& CurrentCpuCore() { 119 CoreManager& CurrentCoreManager() {
119 return cpu_core_manager.GetCurrentCore(); 120 return cpu_manager.GetCurrentCoreManager();
121 }
122
123 Kernel::PhysicalCore& CurrentPhysicalCore() {
124 const auto index = cpu_manager.GetActiveCoreIndex();
125 return kernel.PhysicalCore(index);
126 }
127
128 Kernel::PhysicalCore& GetPhysicalCore(std::size_t index) {
129 return kernel.PhysicalCore(index);
120 } 130 }
121 131
122 ResultStatus RunLoop(bool tight_loop) { 132 ResultStatus RunLoop(bool tight_loop) {
123 status = ResultStatus::Success; 133 status = ResultStatus::Success;
124 134
125 cpu_core_manager.RunLoop(tight_loop); 135 cpu_manager.RunLoop(tight_loop);
126 136
127 return status; 137 return status;
128 } 138 }
@@ -131,8 +141,8 @@ struct System::Impl {
131 LOG_DEBUG(HW_Memory, "initialized OK"); 141 LOG_DEBUG(HW_Memory, "initialized OK");
132 142
133 core_timing.Initialize(); 143 core_timing.Initialize();
134 cpu_core_manager.Initialize();
135 kernel.Initialize(); 144 kernel.Initialize();
145 cpu_manager.Initialize();
136 146
137 const auto current_time = std::chrono::duration_cast<std::chrono::seconds>( 147 const auto current_time = std::chrono::duration_cast<std::chrono::seconds>(
138 std::chrono::system_clock::now().time_since_epoch()); 148 std::chrono::system_clock::now().time_since_epoch());
@@ -205,7 +215,6 @@ struct System::Impl {
205 // Main process has been loaded and been made current. 215 // Main process has been loaded and been made current.
206 // Begin GPU and CPU execution. 216 // Begin GPU and CPU execution.
207 gpu_core->Start(); 217 gpu_core->Start();
208 cpu_core_manager.StartThreads();
209 218
210 // Initialize cheat engine 219 // Initialize cheat engine
211 if (cheat_engine) { 220 if (cheat_engine) {
@@ -259,7 +268,9 @@ struct System::Impl {
259 is_powered_on = false; 268 is_powered_on = false;
260 exit_lock = false; 269 exit_lock = false;
261 270
262 gpu_core->WaitIdle(); 271 if (gpu_core) {
272 gpu_core->WaitIdle();
273 }
263 274
264 // Shutdown emulation session 275 // Shutdown emulation session
265 renderer.reset(); 276 renderer.reset();
@@ -272,7 +283,7 @@ struct System::Impl {
272 gpu_core.reset(); 283 gpu_core.reset();
273 284
274 // Close all CPU/threading state 285 // Close all CPU/threading state
275 cpu_core_manager.Shutdown(); 286 cpu_manager.Shutdown();
276 287
277 // Shutdown kernel and core timing 288 // Shutdown kernel and core timing
278 kernel.Shutdown(); 289 kernel.Shutdown();
@@ -342,7 +353,7 @@ struct System::Impl {
342 std::unique_ptr<Tegra::GPU> gpu_core; 353 std::unique_ptr<Tegra::GPU> gpu_core;
343 std::unique_ptr<Hardware::InterruptManager> interrupt_manager; 354 std::unique_ptr<Hardware::InterruptManager> interrupt_manager;
344 Memory::Memory memory; 355 Memory::Memory memory;
345 CpuCoreManager cpu_core_manager; 356 CpuManager cpu_manager;
346 bool is_powered_on = false; 357 bool is_powered_on = false;
347 bool exit_lock = false; 358 bool exit_lock = false;
348 359
@@ -377,12 +388,12 @@ struct System::Impl {
377System::System() : impl{std::make_unique<Impl>(*this)} {} 388System::System() : impl{std::make_unique<Impl>(*this)} {}
378System::~System() = default; 389System::~System() = default;
379 390
380Cpu& System::CurrentCpuCore() { 391CoreManager& System::CurrentCoreManager() {
381 return impl->CurrentCpuCore(); 392 return impl->CurrentCoreManager();
382} 393}
383 394
384const Cpu& System::CurrentCpuCore() const { 395const CoreManager& System::CurrentCoreManager() const {
385 return impl->CurrentCpuCore(); 396 return impl->CurrentCoreManager();
386} 397}
387 398
388System::ResultStatus System::RunLoop(bool tight_loop) { 399System::ResultStatus System::RunLoop(bool tight_loop) {
@@ -394,7 +405,7 @@ System::ResultStatus System::SingleStep() {
394} 405}
395 406
396void System::InvalidateCpuInstructionCaches() { 407void System::InvalidateCpuInstructionCaches() {
397 impl->cpu_core_manager.InvalidateAllInstructionCaches(); 408 impl->kernel.InvalidateAllInstructionCaches();
398} 409}
399 410
400System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath) { 411System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath) {
@@ -406,13 +417,11 @@ bool System::IsPoweredOn() const {
406} 417}
407 418
408void System::PrepareReschedule() { 419void System::PrepareReschedule() {
409 CurrentCpuCore().PrepareReschedule(); 420 impl->CurrentPhysicalCore().Stop();
410} 421}
411 422
412void System::PrepareReschedule(const u32 core_index) { 423void System::PrepareReschedule(const u32 core_index) {
413 if (core_index < GlobalScheduler().CpuCoresCount()) { 424 impl->kernel.PrepareReschedule(core_index);
414 CpuCore(core_index).PrepareReschedule();
415 }
416} 425}
417 426
418PerfStatsResults System::GetAndResetPerfStats() { 427PerfStatsResults System::GetAndResetPerfStats() {
@@ -428,31 +437,31 @@ const TelemetrySession& System::TelemetrySession() const {
428} 437}
429 438
430ARM_Interface& System::CurrentArmInterface() { 439ARM_Interface& System::CurrentArmInterface() {
431 return CurrentCpuCore().ArmInterface(); 440 return impl->CurrentPhysicalCore().ArmInterface();
432} 441}
433 442
434const ARM_Interface& System::CurrentArmInterface() const { 443const ARM_Interface& System::CurrentArmInterface() const {
435 return CurrentCpuCore().ArmInterface(); 444 return impl->CurrentPhysicalCore().ArmInterface();
436} 445}
437 446
438std::size_t System::CurrentCoreIndex() const { 447std::size_t System::CurrentCoreIndex() const {
439 return CurrentCpuCore().CoreIndex(); 448 return impl->cpu_manager.GetActiveCoreIndex();
440} 449}
441 450
442Kernel::Scheduler& System::CurrentScheduler() { 451Kernel::Scheduler& System::CurrentScheduler() {
443 return CurrentCpuCore().Scheduler(); 452 return impl->CurrentPhysicalCore().Scheduler();
444} 453}
445 454
446const Kernel::Scheduler& System::CurrentScheduler() const { 455const Kernel::Scheduler& System::CurrentScheduler() const {
447 return CurrentCpuCore().Scheduler(); 456 return impl->CurrentPhysicalCore().Scheduler();
448} 457}
449 458
450Kernel::Scheduler& System::Scheduler(std::size_t core_index) { 459Kernel::Scheduler& System::Scheduler(std::size_t core_index) {
451 return CpuCore(core_index).Scheduler(); 460 return impl->GetPhysicalCore(core_index).Scheduler();
452} 461}
453 462
454const Kernel::Scheduler& System::Scheduler(std::size_t core_index) const { 463const Kernel::Scheduler& System::Scheduler(std::size_t core_index) const {
455 return CpuCore(core_index).Scheduler(); 464 return impl->GetPhysicalCore(core_index).Scheduler();
456} 465}
457 466
458/// Gets the global scheduler 467/// Gets the global scheduler
@@ -474,28 +483,28 @@ const Kernel::Process* System::CurrentProcess() const {
474} 483}
475 484
476ARM_Interface& System::ArmInterface(std::size_t core_index) { 485ARM_Interface& System::ArmInterface(std::size_t core_index) {
477 return CpuCore(core_index).ArmInterface(); 486 return impl->GetPhysicalCore(core_index).ArmInterface();
478} 487}
479 488
480const ARM_Interface& System::ArmInterface(std::size_t core_index) const { 489const ARM_Interface& System::ArmInterface(std::size_t core_index) const {
481 return CpuCore(core_index).ArmInterface(); 490 return impl->GetPhysicalCore(core_index).ArmInterface();
482} 491}
483 492
484Cpu& System::CpuCore(std::size_t core_index) { 493CoreManager& System::GetCoreManager(std::size_t core_index) {
485 return impl->cpu_core_manager.GetCore(core_index); 494 return impl->cpu_manager.GetCoreManager(core_index);
486} 495}
487 496
488const Cpu& System::CpuCore(std::size_t core_index) const { 497const CoreManager& System::GetCoreManager(std::size_t core_index) const {
489 ASSERT(core_index < NUM_CPU_CORES); 498 ASSERT(core_index < NUM_CPU_CORES);
490 return impl->cpu_core_manager.GetCore(core_index); 499 return impl->cpu_manager.GetCoreManager(core_index);
491} 500}
492 501
493ExclusiveMonitor& System::Monitor() { 502ExclusiveMonitor& System::Monitor() {
494 return impl->cpu_core_manager.GetExclusiveMonitor(); 503 return impl->kernel.GetExclusiveMonitor();
495} 504}
496 505
497const ExclusiveMonitor& System::Monitor() const { 506const ExclusiveMonitor& System::Monitor() const {
498 return impl->cpu_core_manager.GetExclusiveMonitor(); 507 return impl->kernel.GetExclusiveMonitor();
499} 508}
500 509
501Memory::Memory& System::Memory() { 510Memory::Memory& System::Memory() {
diff --git a/src/core/core.h b/src/core/core.h
index e240c5c58..e69d68fcf 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -93,7 +93,7 @@ class Memory;
93namespace Core { 93namespace Core {
94 94
95class ARM_Interface; 95class ARM_Interface;
96class Cpu; 96class CoreManager;
97class ExclusiveMonitor; 97class ExclusiveMonitor;
98class FrameLimiter; 98class FrameLimiter;
99class PerfStats; 99class PerfStats;
@@ -218,10 +218,10 @@ public:
218 const ARM_Interface& ArmInterface(std::size_t core_index) const; 218 const ARM_Interface& ArmInterface(std::size_t core_index) const;
219 219
220 /// Gets a CPU interface to the CPU core with the specified index 220 /// Gets a CPU interface to the CPU core with the specified index
221 Cpu& CpuCore(std::size_t core_index); 221 CoreManager& GetCoreManager(std::size_t core_index);
222 222
223 /// Gets a CPU interface to the CPU core with the specified index 223 /// Gets a CPU interface to the CPU core with the specified index
224 const Cpu& CpuCore(std::size_t core_index) const; 224 const CoreManager& GetCoreManager(std::size_t core_index) const;
225 225
226 /// Gets a reference to the exclusive monitor 226 /// Gets a reference to the exclusive monitor
227 ExclusiveMonitor& Monitor(); 227 ExclusiveMonitor& Monitor();
@@ -364,10 +364,10 @@ private:
364 System(); 364 System();
365 365
366 /// Returns the currently running CPU core 366 /// Returns the currently running CPU core
367 Cpu& CurrentCpuCore(); 367 CoreManager& CurrentCoreManager();
368 368
369 /// Returns the currently running CPU core 369 /// Returns the currently running CPU core
370 const Cpu& CurrentCpuCore() const; 370 const CoreManager& CurrentCoreManager() const;
371 371
372 /** 372 /**
373 * Initialize the emulated system. 373 * Initialize the emulated system.
diff --git a/src/core/core_cpu.cpp b/src/core/core_cpu.cpp
deleted file mode 100644
index 630cd4feb..000000000
--- a/src/core/core_cpu.cpp
+++ /dev/null
@@ -1,127 +0,0 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <condition_variable>
6#include <mutex>
7
8#include "common/logging/log.h"
9#ifdef ARCHITECTURE_x86_64
10#include "core/arm/dynarmic/arm_dynarmic.h"
11#endif
12#include "core/arm/exclusive_monitor.h"
13#include "core/arm/unicorn/arm_unicorn.h"
14#include "core/core.h"
15#include "core/core_cpu.h"
16#include "core/core_timing.h"
17#include "core/hle/kernel/scheduler.h"
18#include "core/hle/kernel/thread.h"
19#include "core/hle/lock.h"
20#include "core/settings.h"
21
22namespace Core {
23
24void CpuBarrier::NotifyEnd() {
25 std::unique_lock lock{mutex};
26 end = true;
27 condition.notify_all();
28}
29
30bool CpuBarrier::Rendezvous() {
31 if (!Settings::values.use_multi_core) {
32 // Meaningless when running in single-core mode
33 return true;
34 }
35
36 if (!end) {
37 std::unique_lock lock{mutex};
38
39 --cores_waiting;
40 if (!cores_waiting) {
41 cores_waiting = NUM_CPU_CORES;
42 condition.notify_all();
43 return true;
44 }
45
46 condition.wait(lock);
47 return true;
48 }
49
50 return false;
51}
52
53Cpu::Cpu(System& system, ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_barrier,
54 std::size_t core_index)
55 : cpu_barrier{cpu_barrier}, global_scheduler{system.GlobalScheduler()},
56 core_timing{system.CoreTiming()}, core_index{core_index} {
57#ifdef ARCHITECTURE_x86_64
58 arm_interface = std::make_unique<ARM_Dynarmic>(system, exclusive_monitor, core_index);
59#else
60 arm_interface = std::make_unique<ARM_Unicorn>(system);
61 LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
62#endif
63
64 scheduler = std::make_unique<Kernel::Scheduler>(system, *arm_interface, core_index);
65}
66
67Cpu::~Cpu() = default;
68
69std::unique_ptr<ExclusiveMonitor> Cpu::MakeExclusiveMonitor(
70 [[maybe_unused]] Memory::Memory& memory, [[maybe_unused]] std::size_t num_cores) {
71#ifdef ARCHITECTURE_x86_64
72 return std::make_unique<DynarmicExclusiveMonitor>(memory, num_cores);
73#else
74 // TODO(merry): Passthrough exclusive monitor
75 return nullptr;
76#endif
77}
78
79void Cpu::RunLoop(bool tight_loop) {
80 // Wait for all other CPU cores to complete the previous slice, such that they run in lock-step
81 if (!cpu_barrier.Rendezvous()) {
82 // If rendezvous failed, session has been killed
83 return;
84 }
85
86 Reschedule();
87
88 // If we don't have a currently active thread then don't execute instructions,
89 // instead advance to the next event and try to yield to the next thread
90 if (Kernel::GetCurrentThread() == nullptr) {
91 LOG_TRACE(Core, "Core-{} idling", core_index);
92 core_timing.Idle();
93 } else {
94 if (tight_loop) {
95 arm_interface->Run();
96 } else {
97 arm_interface->Step();
98 }
99 // We are stopping a run, exclusive state must be cleared
100 arm_interface->ClearExclusiveState();
101 }
102 core_timing.Advance();
103
104 Reschedule();
105}
106
107void Cpu::SingleStep() {
108 return RunLoop(false);
109}
110
111void Cpu::PrepareReschedule() {
112 arm_interface->PrepareReschedule();
113}
114
115void Cpu::Reschedule() {
116 // Lock the global kernel mutex when we manipulate the HLE state
117 std::lock_guard lock(HLE::g_hle_lock);
118
119 global_scheduler.SelectThread(core_index);
120 scheduler->TryDoContextSwitch();
121}
122
123void Cpu::Shutdown() {
124 scheduler->Shutdown();
125}
126
127} // namespace Core
diff --git a/src/core/core_cpu.h b/src/core/core_cpu.h
deleted file mode 100644
index 78f5021a2..000000000
--- a/src/core/core_cpu.h
+++ /dev/null
@@ -1,120 +0,0 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <atomic>
8#include <condition_variable>
9#include <cstddef>
10#include <memory>
11#include <mutex>
12#include "common/common_types.h"
13
14namespace Kernel {
15class GlobalScheduler;
16class Scheduler;
17} // namespace Kernel
18
19namespace Core {
20class System;
21}
22
23namespace Core::Timing {
24class CoreTiming;
25}
26
27namespace Memory {
28class Memory;
29}
30
31namespace Core {
32
33class ARM_Interface;
34class ExclusiveMonitor;
35
36constexpr unsigned NUM_CPU_CORES{4};
37
38class CpuBarrier {
39public:
40 bool IsAlive() const {
41 return !end;
42 }
43
44 void NotifyEnd();
45
46 bool Rendezvous();
47
48private:
49 unsigned cores_waiting{NUM_CPU_CORES};
50 std::mutex mutex;
51 std::condition_variable condition;
52 std::atomic<bool> end{};
53};
54
55class Cpu {
56public:
57 Cpu(System& system, ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_barrier,
58 std::size_t core_index);
59 ~Cpu();
60
61 void RunLoop(bool tight_loop = true);
62
63 void SingleStep();
64
65 void PrepareReschedule();
66
67 ARM_Interface& ArmInterface() {
68 return *arm_interface;
69 }
70
71 const ARM_Interface& ArmInterface() const {
72 return *arm_interface;
73 }
74
75 Kernel::Scheduler& Scheduler() {
76 return *scheduler;
77 }
78
79 const Kernel::Scheduler& Scheduler() const {
80 return *scheduler;
81 }
82
83 bool IsMainCore() const {
84 return core_index == 0;
85 }
86
87 std::size_t CoreIndex() const {
88 return core_index;
89 }
90
91 void Shutdown();
92
93 /**
94 * Creates an exclusive monitor to handle exclusive reads/writes.
95 *
96 * @param memory The current memory subsystem that the monitor may wish
97 * to keep track of.
98 *
99 * @param num_cores The number of cores to assume about the CPU.
100 *
101 * @returns The constructed exclusive monitor instance, or nullptr if the current
102 * CPU backend is unable to use an exclusive monitor.
103 */
104 static std::unique_ptr<ExclusiveMonitor> MakeExclusiveMonitor(Memory::Memory& memory,
105 std::size_t num_cores);
106
107private:
108 void Reschedule();
109
110 std::unique_ptr<ARM_Interface> arm_interface;
111 CpuBarrier& cpu_barrier;
112 Kernel::GlobalScheduler& global_scheduler;
113 std::unique_ptr<Kernel::Scheduler> scheduler;
114 Timing::CoreTiming& core_timing;
115
116 std::atomic<bool> reschedule_pending = false;
117 std::size_t core_index;
118};
119
120} // namespace Core
diff --git a/src/core/core_manager.cpp b/src/core/core_manager.cpp
new file mode 100644
index 000000000..8eacf92dd
--- /dev/null
+++ b/src/core/core_manager.cpp
@@ -0,0 +1,70 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <condition_variable>
6#include <mutex>
7
8#include "common/logging/log.h"
9#ifdef ARCHITECTURE_x86_64
10#include "core/arm/dynarmic/arm_dynarmic.h"
11#endif
12#include "core/arm/exclusive_monitor.h"
13#include "core/arm/unicorn/arm_unicorn.h"
14#include "core/core.h"
15#include "core/core_manager.h"
16#include "core/core_timing.h"
17#include "core/hle/kernel/kernel.h"
18#include "core/hle/kernel/physical_core.h"
19#include "core/hle/kernel/scheduler.h"
20#include "core/hle/kernel/thread.h"
21#include "core/hle/lock.h"
22#include "core/settings.h"
23
24namespace Core {
25
26CoreManager::CoreManager(System& system, std::size_t core_index)
27 : global_scheduler{system.GlobalScheduler()}, physical_core{system.Kernel().PhysicalCore(
28 core_index)},
29 core_timing{system.CoreTiming()}, core_index{core_index} {}
30
31CoreManager::~CoreManager() = default;
32
33void CoreManager::RunLoop(bool tight_loop) {
34 Reschedule();
35
36 // If we don't have a currently active thread then don't execute instructions,
37 // instead advance to the next event and try to yield to the next thread
38 if (Kernel::GetCurrentThread() == nullptr) {
39 LOG_TRACE(Core, "Core-{} idling", core_index);
40 core_timing.Idle();
41 } else {
42 if (tight_loop) {
43 physical_core.Run();
44 } else {
45 physical_core.Step();
46 }
47 }
48 core_timing.Advance();
49
50 Reschedule();
51}
52
53void CoreManager::SingleStep() {
54 return RunLoop(false);
55}
56
57void CoreManager::PrepareReschedule() {
58 physical_core.Stop();
59}
60
61void CoreManager::Reschedule() {
62 // Lock the global kernel mutex when we manipulate the HLE state
63 std::lock_guard lock(HLE::g_hle_lock);
64
65 global_scheduler.SelectThread(core_index);
66
67 physical_core.Scheduler().TryDoContextSwitch();
68}
69
70} // namespace Core
diff --git a/src/core/core_manager.h b/src/core/core_manager.h
new file mode 100644
index 000000000..b14e723d7
--- /dev/null
+++ b/src/core/core_manager.h
@@ -0,0 +1,63 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <atomic>
8#include <cstddef>
9#include <memory>
10#include "common/common_types.h"
11
12namespace Kernel {
13class GlobalScheduler;
14class PhysicalCore;
15} // namespace Kernel
16
17namespace Core {
18class System;
19}
20
21namespace Core::Timing {
22class CoreTiming;
23}
24
25namespace Memory {
26class Memory;
27}
28
29namespace Core {
30
31constexpr unsigned NUM_CPU_CORES{4};
32
33class CoreManager {
34public:
35 CoreManager(System& system, std::size_t core_index);
36 ~CoreManager();
37
38 void RunLoop(bool tight_loop = true);
39
40 void SingleStep();
41
42 void PrepareReschedule();
43
44 bool IsMainCore() const {
45 return core_index == 0;
46 }
47
48 std::size_t CoreIndex() const {
49 return core_index;
50 }
51
52private:
53 void Reschedule();
54
55 Kernel::GlobalScheduler& global_scheduler;
56 Kernel::PhysicalCore& physical_core;
57 Timing::CoreTiming& core_timing;
58
59 std::atomic<bool> reschedule_pending = false;
60 std::size_t core_index;
61};
62
63} // namespace Core
diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp
index aa09fa453..46d4178c4 100644
--- a/src/core/core_timing.cpp
+++ b/src/core/core_timing.cpp
@@ -12,6 +12,7 @@
12#include "common/assert.h" 12#include "common/assert.h"
13#include "common/thread.h" 13#include "common/thread.h"
14#include "core/core_timing_util.h" 14#include "core/core_timing_util.h"
15#include "core/hardware_properties.h"
15 16
16namespace Core::Timing { 17namespace Core::Timing {
17 18
@@ -215,7 +216,7 @@ void CoreTiming::Idle() {
215} 216}
216 217
217std::chrono::microseconds CoreTiming::GetGlobalTimeUs() const { 218std::chrono::microseconds CoreTiming::GetGlobalTimeUs() const {
218 return std::chrono::microseconds{GetTicks() * 1000000 / BASE_CLOCK_RATE}; 219 return std::chrono::microseconds{GetTicks() * 1000000 / Hardware::BASE_CLOCK_RATE};
219} 220}
220 221
221s64 CoreTiming::GetDowncount() const { 222s64 CoreTiming::GetDowncount() const {
diff --git a/src/core/core_timing_util.cpp b/src/core/core_timing_util.cpp
index a10472a95..de50d3b14 100644
--- a/src/core/core_timing_util.cpp
+++ b/src/core/core_timing_util.cpp
@@ -11,7 +11,7 @@
11 11
12namespace Core::Timing { 12namespace Core::Timing {
13 13
14constexpr u64 MAX_VALUE_TO_MULTIPLY = std::numeric_limits<s64>::max() / BASE_CLOCK_RATE; 14constexpr u64 MAX_VALUE_TO_MULTIPLY = std::numeric_limits<s64>::max() / Hardware::BASE_CLOCK_RATE;
15 15
16s64 msToCycles(std::chrono::milliseconds ms) { 16s64 msToCycles(std::chrono::milliseconds ms) {
17 if (static_cast<u64>(ms.count() / 1000) > MAX_VALUE_TO_MULTIPLY) { 17 if (static_cast<u64>(ms.count() / 1000) > MAX_VALUE_TO_MULTIPLY) {
@@ -20,9 +20,9 @@ s64 msToCycles(std::chrono::milliseconds ms) {
20 } 20 }
21 if (static_cast<u64>(ms.count()) > MAX_VALUE_TO_MULTIPLY) { 21 if (static_cast<u64>(ms.count()) > MAX_VALUE_TO_MULTIPLY) {
22 LOG_DEBUG(Core_Timing, "Time very big, do rounding"); 22 LOG_DEBUG(Core_Timing, "Time very big, do rounding");
23 return BASE_CLOCK_RATE * (ms.count() / 1000); 23 return Hardware::BASE_CLOCK_RATE * (ms.count() / 1000);
24 } 24 }
25 return (BASE_CLOCK_RATE * ms.count()) / 1000; 25 return (Hardware::BASE_CLOCK_RATE * ms.count()) / 1000;
26} 26}
27 27
28s64 usToCycles(std::chrono::microseconds us) { 28s64 usToCycles(std::chrono::microseconds us) {
@@ -32,9 +32,9 @@ s64 usToCycles(std::chrono::microseconds us) {
32 } 32 }
33 if (static_cast<u64>(us.count()) > MAX_VALUE_TO_MULTIPLY) { 33 if (static_cast<u64>(us.count()) > MAX_VALUE_TO_MULTIPLY) {
34 LOG_DEBUG(Core_Timing, "Time very big, do rounding"); 34 LOG_DEBUG(Core_Timing, "Time very big, do rounding");
35 return BASE_CLOCK_RATE * (us.count() / 1000000); 35 return Hardware::BASE_CLOCK_RATE * (us.count() / 1000000);
36 } 36 }
37 return (BASE_CLOCK_RATE * us.count()) / 1000000; 37 return (Hardware::BASE_CLOCK_RATE * us.count()) / 1000000;
38} 38}
39 39
40s64 nsToCycles(std::chrono::nanoseconds ns) { 40s64 nsToCycles(std::chrono::nanoseconds ns) {
@@ -44,14 +44,14 @@ s64 nsToCycles(std::chrono::nanoseconds ns) {
44 } 44 }
45 if (static_cast<u64>(ns.count()) > MAX_VALUE_TO_MULTIPLY) { 45 if (static_cast<u64>(ns.count()) > MAX_VALUE_TO_MULTIPLY) {
46 LOG_DEBUG(Core_Timing, "Time very big, do rounding"); 46 LOG_DEBUG(Core_Timing, "Time very big, do rounding");
47 return BASE_CLOCK_RATE * (ns.count() / 1000000000); 47 return Hardware::BASE_CLOCK_RATE * (ns.count() / 1000000000);
48 } 48 }
49 return (BASE_CLOCK_RATE * ns.count()) / 1000000000; 49 return (Hardware::BASE_CLOCK_RATE * ns.count()) / 1000000000;
50} 50}
51 51
52u64 CpuCyclesToClockCycles(u64 ticks) { 52u64 CpuCyclesToClockCycles(u64 ticks) {
53 const u128 temporal = Common::Multiply64Into128(ticks, CNTFREQ); 53 const u128 temporal = Common::Multiply64Into128(ticks, Hardware::CNTFREQ);
54 return Common::Divide128On32(temporal, static_cast<u32>(BASE_CLOCK_RATE)).first; 54 return Common::Divide128On32(temporal, static_cast<u32>(Hardware::BASE_CLOCK_RATE)).first;
55} 55}
56 56
57} // namespace Core::Timing 57} // namespace Core::Timing
diff --git a/src/core/core_timing_util.h b/src/core/core_timing_util.h
index cdd84d70f..addc72b19 100644
--- a/src/core/core_timing_util.h
+++ b/src/core/core_timing_util.h
@@ -6,28 +6,24 @@
6 6
7#include <chrono> 7#include <chrono>
8#include "common/common_types.h" 8#include "common/common_types.h"
9#include "core/hardware_properties.h"
9 10
10namespace Core::Timing { 11namespace Core::Timing {
11 12
12// The below clock rate is based on Switch's clockspeed being widely known as 1.020GHz
13// The exact value used is of course unverified.
14constexpr u64 BASE_CLOCK_RATE = 1019215872; // Switch clock speed is 1020MHz un/docked
15constexpr u64 CNTFREQ = 19200000; // Value from fusee.
16
17s64 msToCycles(std::chrono::milliseconds ms); 13s64 msToCycles(std::chrono::milliseconds ms);
18s64 usToCycles(std::chrono::microseconds us); 14s64 usToCycles(std::chrono::microseconds us);
19s64 nsToCycles(std::chrono::nanoseconds ns); 15s64 nsToCycles(std::chrono::nanoseconds ns);
20 16
21inline std::chrono::milliseconds CyclesToMs(s64 cycles) { 17inline std::chrono::milliseconds CyclesToMs(s64 cycles) {
22 return std::chrono::milliseconds(cycles * 1000 / BASE_CLOCK_RATE); 18 return std::chrono::milliseconds(cycles * 1000 / Hardware::BASE_CLOCK_RATE);
23} 19}
24 20
25inline std::chrono::nanoseconds CyclesToNs(s64 cycles) { 21inline std::chrono::nanoseconds CyclesToNs(s64 cycles) {
26 return std::chrono::nanoseconds(cycles * 1000000000 / BASE_CLOCK_RATE); 22 return std::chrono::nanoseconds(cycles * 1000000000 / Hardware::BASE_CLOCK_RATE);
27} 23}
28 24
29inline std::chrono::microseconds CyclesToUs(s64 cycles) { 25inline std::chrono::microseconds CyclesToUs(s64 cycles) {
30 return std::chrono::microseconds(cycles * 1000000 / BASE_CLOCK_RATE); 26 return std::chrono::microseconds(cycles * 1000000 / Hardware::BASE_CLOCK_RATE);
31} 27}
32 28
33u64 CpuCyclesToClockCycles(u64 ticks); 29u64 CpuCyclesToClockCycles(u64 ticks);
diff --git a/src/core/cpu_core_manager.cpp b/src/core/cpu_core_manager.cpp
deleted file mode 100644
index f04a34133..000000000
--- a/src/core/cpu_core_manager.cpp
+++ /dev/null
@@ -1,152 +0,0 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "common/assert.h"
6#include "core/arm/exclusive_monitor.h"
7#include "core/core.h"
8#include "core/core_cpu.h"
9#include "core/core_timing.h"
10#include "core/cpu_core_manager.h"
11#include "core/gdbstub/gdbstub.h"
12#include "core/settings.h"
13
14namespace Core {
15namespace {
16void RunCpuCore(const System& system, Cpu& cpu_state) {
17 while (system.IsPoweredOn()) {
18 cpu_state.RunLoop(true);
19 }
20}
21} // Anonymous namespace
22
23CpuCoreManager::CpuCoreManager(System& system) : system{system} {}
24CpuCoreManager::~CpuCoreManager() = default;
25
26void CpuCoreManager::Initialize() {
27 barrier = std::make_unique<CpuBarrier>();
28 exclusive_monitor = Cpu::MakeExclusiveMonitor(system.Memory(), cores.size());
29
30 for (std::size_t index = 0; index < cores.size(); ++index) {
31 cores[index] = std::make_unique<Cpu>(system, *exclusive_monitor, *barrier, index);
32 }
33}
34
35void CpuCoreManager::StartThreads() {
36 // Create threads for CPU cores 1-3, and build thread_to_cpu map
37 // CPU core 0 is run on the main thread
38 thread_to_cpu[std::this_thread::get_id()] = cores[0].get();
39 if (!Settings::values.use_multi_core) {
40 return;
41 }
42
43 for (std::size_t index = 0; index < core_threads.size(); ++index) {
44 core_threads[index] = std::make_unique<std::thread>(RunCpuCore, std::cref(system),
45 std::ref(*cores[index + 1]));
46 thread_to_cpu[core_threads[index]->get_id()] = cores[index + 1].get();
47 }
48}
49
50void CpuCoreManager::Shutdown() {
51 barrier->NotifyEnd();
52 if (Settings::values.use_multi_core) {
53 for (auto& thread : core_threads) {
54 thread->join();
55 thread.reset();
56 }
57 }
58
59 thread_to_cpu.clear();
60 for (auto& cpu_core : cores) {
61 cpu_core->Shutdown();
62 cpu_core.reset();
63 }
64
65 exclusive_monitor.reset();
66 barrier.reset();
67}
68
69Cpu& CpuCoreManager::GetCore(std::size_t index) {
70 return *cores.at(index);
71}
72
73const Cpu& CpuCoreManager::GetCore(std::size_t index) const {
74 return *cores.at(index);
75}
76
77ExclusiveMonitor& CpuCoreManager::GetExclusiveMonitor() {
78 return *exclusive_monitor;
79}
80
81const ExclusiveMonitor& CpuCoreManager::GetExclusiveMonitor() const {
82 return *exclusive_monitor;
83}
84
85Cpu& CpuCoreManager::GetCurrentCore() {
86 if (Settings::values.use_multi_core) {
87 const auto& search = thread_to_cpu.find(std::this_thread::get_id());
88 ASSERT(search != thread_to_cpu.end());
89 ASSERT(search->second);
90 return *search->second;
91 }
92
93 // Otherwise, use single-threaded mode active_core variable
94 return *cores[active_core];
95}
96
97const Cpu& CpuCoreManager::GetCurrentCore() const {
98 if (Settings::values.use_multi_core) {
99 const auto& search = thread_to_cpu.find(std::this_thread::get_id());
100 ASSERT(search != thread_to_cpu.end());
101 ASSERT(search->second);
102 return *search->second;
103 }
104
105 // Otherwise, use single-threaded mode active_core variable
106 return *cores[active_core];
107}
108
109void CpuCoreManager::RunLoop(bool tight_loop) {
110 // Update thread_to_cpu in case Core 0 is run from a different host thread
111 thread_to_cpu[std::this_thread::get_id()] = cores[0].get();
112
113 if (GDBStub::IsServerEnabled()) {
114 GDBStub::HandlePacket();
115
116 // If the loop is halted and we want to step, use a tiny (1) number of instructions to
117 // execute. Otherwise, get out of the loop function.
118 if (GDBStub::GetCpuHaltFlag()) {
119 if (GDBStub::GetCpuStepFlag()) {
120 tight_loop = false;
121 } else {
122 return;
123 }
124 }
125 }
126
127 auto& core_timing = system.CoreTiming();
128 core_timing.ResetRun();
129 bool keep_running{};
130 do {
131 keep_running = false;
132 for (active_core = 0; active_core < NUM_CPU_CORES; ++active_core) {
133 core_timing.SwitchContext(active_core);
134 if (core_timing.CanCurrentContextRun()) {
135 cores[active_core]->RunLoop(tight_loop);
136 }
137 keep_running |= core_timing.CanCurrentContextRun();
138 }
139 } while (keep_running);
140
141 if (GDBStub::IsServerEnabled()) {
142 GDBStub::SetCpuStepFlag(false);
143 }
144}
145
146void CpuCoreManager::InvalidateAllInstructionCaches() {
147 for (auto& cpu : cores) {
148 cpu->ArmInterface().ClearInstructionCache();
149 }
150}
151
152} // namespace Core
diff --git a/src/core/cpu_core_manager.h b/src/core/cpu_core_manager.h
deleted file mode 100644
index 2cbbf8216..000000000
--- a/src/core/cpu_core_manager.h
+++ /dev/null
@@ -1,62 +0,0 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <map>
9#include <memory>
10#include <thread>
11
12namespace Core {
13
14class Cpu;
15class CpuBarrier;
16class ExclusiveMonitor;
17class System;
18
19class CpuCoreManager {
20public:
21 explicit CpuCoreManager(System& system);
22 CpuCoreManager(const CpuCoreManager&) = delete;
23 CpuCoreManager(CpuCoreManager&&) = delete;
24
25 ~CpuCoreManager();
26
27 CpuCoreManager& operator=(const CpuCoreManager&) = delete;
28 CpuCoreManager& operator=(CpuCoreManager&&) = delete;
29
30 void Initialize();
31 void StartThreads();
32 void Shutdown();
33
34 Cpu& GetCore(std::size_t index);
35 const Cpu& GetCore(std::size_t index) const;
36
37 Cpu& GetCurrentCore();
38 const Cpu& GetCurrentCore() const;
39
40 ExclusiveMonitor& GetExclusiveMonitor();
41 const ExclusiveMonitor& GetExclusiveMonitor() const;
42
43 void RunLoop(bool tight_loop);
44
45 void InvalidateAllInstructionCaches();
46
47private:
48 static constexpr std::size_t NUM_CPU_CORES = 4;
49
50 std::unique_ptr<ExclusiveMonitor> exclusive_monitor;
51 std::unique_ptr<CpuBarrier> barrier;
52 std::array<std::unique_ptr<Cpu>, NUM_CPU_CORES> cores;
53 std::array<std::unique_ptr<std::thread>, NUM_CPU_CORES - 1> core_threads;
54 std::size_t active_core{}; ///< Active core, only used in single thread mode
55
56 /// Map of guest threads to CPU cores
57 std::map<std::thread::id, Cpu*> thread_to_cpu;
58
59 System& system;
60};
61
62} // namespace Core
diff --git a/src/core/cpu_manager.cpp b/src/core/cpu_manager.cpp
new file mode 100644
index 000000000..70ddbdcca
--- /dev/null
+++ b/src/core/cpu_manager.cpp
@@ -0,0 +1,81 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "core/arm/exclusive_monitor.h"
6#include "core/core.h"
7#include "core/core_manager.h"
8#include "core/core_timing.h"
9#include "core/cpu_manager.h"
10#include "core/gdbstub/gdbstub.h"
11
12namespace Core {
13
14CpuManager::CpuManager(System& system) : system{system} {}
15CpuManager::~CpuManager() = default;
16
17void CpuManager::Initialize() {
18 for (std::size_t index = 0; index < core_managers.size(); ++index) {
19 core_managers[index] = std::make_unique<CoreManager>(system, index);
20 }
21}
22
23void CpuManager::Shutdown() {
24 for (auto& cpu_core : core_managers) {
25 cpu_core.reset();
26 }
27}
28
29CoreManager& CpuManager::GetCoreManager(std::size_t index) {
30 return *core_managers.at(index);
31}
32
33const CoreManager& CpuManager::GetCoreManager(std::size_t index) const {
34 return *core_managers.at(index);
35}
36
37CoreManager& CpuManager::GetCurrentCoreManager() {
38 // Otherwise, use single-threaded mode active_core variable
39 return *core_managers[active_core];
40}
41
42const CoreManager& CpuManager::GetCurrentCoreManager() const {
43 // Otherwise, use single-threaded mode active_core variable
44 return *core_managers[active_core];
45}
46
47void CpuManager::RunLoop(bool tight_loop) {
48 if (GDBStub::IsServerEnabled()) {
49 GDBStub::HandlePacket();
50
51 // If the loop is halted and we want to step, use a tiny (1) number of instructions to
52 // execute. Otherwise, get out of the loop function.
53 if (GDBStub::GetCpuHaltFlag()) {
54 if (GDBStub::GetCpuStepFlag()) {
55 tight_loop = false;
56 } else {
57 return;
58 }
59 }
60 }
61
62 auto& core_timing = system.CoreTiming();
63 core_timing.ResetRun();
64 bool keep_running{};
65 do {
66 keep_running = false;
67 for (active_core = 0; active_core < NUM_CPU_CORES; ++active_core) {
68 core_timing.SwitchContext(active_core);
69 if (core_timing.CanCurrentContextRun()) {
70 core_managers[active_core]->RunLoop(tight_loop);
71 }
72 keep_running |= core_timing.CanCurrentContextRun();
73 }
74 } while (keep_running);
75
76 if (GDBStub::IsServerEnabled()) {
77 GDBStub::SetCpuStepFlag(false);
78 }
79}
80
81} // namespace Core
diff --git a/src/core/cpu_manager.h b/src/core/cpu_manager.h
new file mode 100644
index 000000000..97554d1bb
--- /dev/null
+++ b/src/core/cpu_manager.h
@@ -0,0 +1,49 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <memory>
9#include "core/hardware_properties.h"
10
11namespace Core {
12
13class CoreManager;
14class System;
15
16class CpuManager {
17public:
18 explicit CpuManager(System& system);
19 CpuManager(const CpuManager&) = delete;
20 CpuManager(CpuManager&&) = delete;
21
22 ~CpuManager();
23
24 CpuManager& operator=(const CpuManager&) = delete;
25 CpuManager& operator=(CpuManager&&) = delete;
26
27 void Initialize();
28 void Shutdown();
29
30 CoreManager& GetCoreManager(std::size_t index);
31 const CoreManager& GetCoreManager(std::size_t index) const;
32
33 CoreManager& GetCurrentCoreManager();
34 const CoreManager& GetCurrentCoreManager() const;
35
36 std::size_t GetActiveCoreIndex() const {
37 return active_core;
38 }
39
40 void RunLoop(bool tight_loop);
41
42private:
43 std::array<std::unique_ptr<CoreManager>, Hardware::NUM_CPU_CORES> core_managers;
44 std::size_t active_core{}; ///< Active core, only used in single thread mode
45
46 System& system;
47};
48
49} // namespace Core
diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h
index 4a9912641..3376eedc5 100644
--- a/src/core/frontend/emu_window.h
+++ b/src/core/frontend/emu_window.h
@@ -75,6 +75,13 @@ public:
75 return nullptr; 75 return nullptr;
76 } 76 }
77 77
78 /// Returns if window is shown (not minimized)
79 virtual bool IsShown() const = 0;
80
81 /// Retrieves Vulkan specific handlers from the window
82 virtual void RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
83 void* surface) const = 0;
84
78 /** 85 /**
79 * Signal that a touch pressed event has occurred (e.g. mouse click pressed) 86 * Signal that a touch pressed event has occurred (e.g. mouse click pressed)
80 * @param framebuffer_x Framebuffer x-coordinate that was pressed 87 * @param framebuffer_x Framebuffer x-coordinate that was pressed
diff --git a/src/core/frontend/framebuffer_layout.cpp b/src/core/frontend/framebuffer_layout.cpp
index d6d2cf3f0..2dc795d56 100644
--- a/src/core/frontend/framebuffer_layout.cpp
+++ b/src/core/frontend/framebuffer_layout.cpp
@@ -27,9 +27,9 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height) {
27 // so just calculate them both even if the other isn't showing. 27 // so just calculate them both even if the other isn't showing.
28 FramebufferLayout res{width, height}; 28 FramebufferLayout res{width, height};
29 29
30 const float emulation_aspect_ratio{static_cast<float>(ScreenUndocked::Height) / 30 const float window_aspect_ratio = static_cast<float>(height) / width;
31 ScreenUndocked::Width}; 31 const float emulation_aspect_ratio = EmulationAspectRatio(
32 const auto window_aspect_ratio = static_cast<float>(height) / width; 32 static_cast<AspectRatio>(Settings::values.aspect_ratio), window_aspect_ratio);
33 33
34 const Common::Rectangle<u32> screen_window_area{0, 0, width, height}; 34 const Common::Rectangle<u32> screen_window_area{0, 0, width, height};
35 Common::Rectangle<u32> screen = MaxRectangle(screen_window_area, emulation_aspect_ratio); 35 Common::Rectangle<u32> screen = MaxRectangle(screen_window_area, emulation_aspect_ratio);
@@ -58,4 +58,19 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale) {
58 return DefaultFrameLayout(width, height); 58 return DefaultFrameLayout(width, height);
59} 59}
60 60
61float EmulationAspectRatio(AspectRatio aspect, float window_aspect_ratio) {
62 switch (aspect) {
63 case AspectRatio::Default:
64 return static_cast<float>(ScreenUndocked::Height) / ScreenUndocked::Width;
65 case AspectRatio::R4_3:
66 return 3.0f / 4.0f;
67 case AspectRatio::R21_9:
68 return 9.0f / 21.0f;
69 case AspectRatio::StretchToWindow:
70 return window_aspect_ratio;
71 default:
72 return static_cast<float>(ScreenUndocked::Height) / ScreenUndocked::Width;
73 }
74}
75
61} // namespace Layout 76} // namespace Layout
diff --git a/src/core/frontend/framebuffer_layout.h b/src/core/frontend/framebuffer_layout.h
index d2370adde..1d39c1faf 100644
--- a/src/core/frontend/framebuffer_layout.h
+++ b/src/core/frontend/framebuffer_layout.h
@@ -18,6 +18,13 @@ enum ScreenDocked : u32 {
18 HeightDocked = 1080, 18 HeightDocked = 1080,
19}; 19};
20 20
21enum class AspectRatio {
22 Default,
23 R4_3,
24 R21_9,
25 StretchToWindow,
26};
27
21/// Describes the layout of the window framebuffer 28/// Describes the layout of the window framebuffer
22struct FramebufferLayout { 29struct FramebufferLayout {
23 u32 width{ScreenUndocked::Width}; 30 u32 width{ScreenUndocked::Width};
@@ -48,4 +55,12 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height);
48 */ 55 */
49FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale); 56FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale);
50 57
58/**
59 * Convenience method to determine emulation aspect ratio
60 * @param aspect Represents the index of aspect ratio stored in Settings::values.aspect_ratio
61 * @param window_aspect_ratio Current window aspect ratio
62 * @return Emulation render window aspect ratio
63 */
64float EmulationAspectRatio(AspectRatio aspect, float window_aspect_ratio);
65
51} // namespace Layout 66} // namespace Layout
diff --git a/src/core/frontend/input.h b/src/core/frontend/input.h
index 7c11d7546..2b098b7c6 100644
--- a/src/core/frontend/input.h
+++ b/src/core/frontend/input.h
@@ -15,6 +15,13 @@
15 15
16namespace Input { 16namespace Input {
17 17
18enum class AnalogDirection : u8 {
19 RIGHT,
20 LEFT,
21 UP,
22 DOWN,
23};
24
18/// An abstract class template for an input device (a button, an analog input, etc.). 25/// An abstract class template for an input device (a button, an analog input, etc.).
19template <typename StatusType> 26template <typename StatusType>
20class InputDevice { 27class InputDevice {
@@ -23,6 +30,9 @@ public:
23 virtual StatusType GetStatus() const { 30 virtual StatusType GetStatus() const {
24 return {}; 31 return {};
25 } 32 }
33 virtual bool GetAnalogDirectionStatus(AnalogDirection direction) const {
34 return {};
35 }
26}; 36};
27 37
28/// An abstract class template for a factory that can create input devices. 38/// An abstract class template for a factory that can create input devices.
diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp
index 37cb28848..67e95999d 100644
--- a/src/core/gdbstub/gdbstub.cpp
+++ b/src/core/gdbstub/gdbstub.cpp
@@ -35,7 +35,7 @@
35#include "common/swap.h" 35#include "common/swap.h"
36#include "core/arm/arm_interface.h" 36#include "core/arm/arm_interface.h"
37#include "core/core.h" 37#include "core/core.h"
38#include "core/core_cpu.h" 38#include "core/core_manager.h"
39#include "core/gdbstub/gdbstub.h" 39#include "core/gdbstub/gdbstub.h"
40#include "core/hle/kernel/process.h" 40#include "core/hle/kernel/process.h"
41#include "core/hle/kernel/scheduler.h" 41#include "core/hle/kernel/scheduler.h"
diff --git a/src/core/hardware_properties.h b/src/core/hardware_properties.h
new file mode 100644
index 000000000..213461b6a
--- /dev/null
+++ b/src/core/hardware_properties.h
@@ -0,0 +1,45 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <tuple>
8
9#include "common/common_types.h"
10
11namespace Core {
12
13namespace Hardware {
14
15// The below clock rate is based on Switch's clockspeed being widely known as 1.020GHz
16// The exact value used is of course unverified.
17constexpr u64 BASE_CLOCK_RATE = 1019215872; // Switch cpu frequency is 1020MHz un/docked
18constexpr u64 CNTFREQ = 19200000; // Switch's hardware clock speed
19constexpr u32 NUM_CPU_CORES = 4; // Number of CPU Cores
20
21} // namespace Hardware
22
23struct EmuThreadHandle {
24 u32 host_handle;
25 u32 guest_handle;
26
27 u64 GetRaw() const {
28 return (static_cast<u64>(host_handle) << 32) | guest_handle;
29 }
30
31 bool operator==(const EmuThreadHandle& rhs) const {
32 return std::tie(host_handle, guest_handle) == std::tie(rhs.host_handle, rhs.guest_handle);
33 }
34
35 bool operator!=(const EmuThreadHandle& rhs) const {
36 return !operator==(rhs);
37 }
38
39 static constexpr EmuThreadHandle InvalidHandle() {
40 constexpr u32 invalid_handle = 0xFFFFFFFF;
41 return {invalid_handle, invalid_handle};
42 }
43};
44
45} // namespace Core
diff --git a/src/core/hle/kernel/address_arbiter.cpp b/src/core/hle/kernel/address_arbiter.cpp
index db189c8e3..8475b698c 100644
--- a/src/core/hle/kernel/address_arbiter.cpp
+++ b/src/core/hle/kernel/address_arbiter.cpp
@@ -8,7 +8,6 @@
8#include "common/assert.h" 8#include "common/assert.h"
9#include "common/common_types.h" 9#include "common/common_types.h"
10#include "core/core.h" 10#include "core/core.h"
11#include "core/core_cpu.h"
12#include "core/hle/kernel/address_arbiter.h" 11#include "core/hle/kernel/address_arbiter.h"
13#include "core/hle/kernel/errors.h" 12#include "core/hle/kernel/errors.h"
14#include "core/hle/kernel/scheduler.h" 13#include "core/hle/kernel/scheduler.h"
@@ -202,42 +201,39 @@ void AddressArbiter::HandleWakeupThread(std::shared_ptr<Thread> thread) {
202void AddressArbiter::InsertThread(std::shared_ptr<Thread> thread) { 201void AddressArbiter::InsertThread(std::shared_ptr<Thread> thread) {
203 const VAddr arb_addr = thread->GetArbiterWaitAddress(); 202 const VAddr arb_addr = thread->GetArbiterWaitAddress();
204 std::list<std::shared_ptr<Thread>>& thread_list = arb_threads[arb_addr]; 203 std::list<std::shared_ptr<Thread>>& thread_list = arb_threads[arb_addr];
205 auto it = thread_list.begin(); 204
206 while (it != thread_list.end()) { 205 const auto iter =
207 const std::shared_ptr<Thread>& current_thread = *it; 206 std::find_if(thread_list.cbegin(), thread_list.cend(), [&thread](const auto& entry) {
208 if (current_thread->GetPriority() >= thread->GetPriority()) { 207 return entry->GetPriority() >= thread->GetPriority();
209 thread_list.insert(it, thread); 208 });
210 return; 209
211 } 210 if (iter == thread_list.cend()) {
212 ++it; 211 thread_list.push_back(std::move(thread));
212 } else {
213 thread_list.insert(iter, std::move(thread));
213 } 214 }
214 thread_list.push_back(std::move(thread));
215} 215}
216 216
217void AddressArbiter::RemoveThread(std::shared_ptr<Thread> thread) { 217void AddressArbiter::RemoveThread(std::shared_ptr<Thread> thread) {
218 const VAddr arb_addr = thread->GetArbiterWaitAddress(); 218 const VAddr arb_addr = thread->GetArbiterWaitAddress();
219 std::list<std::shared_ptr<Thread>>& thread_list = arb_threads[arb_addr]; 219 std::list<std::shared_ptr<Thread>>& thread_list = arb_threads[arb_addr];
220 auto it = thread_list.begin(); 220
221 while (it != thread_list.end()) { 221 const auto iter = std::find_if(thread_list.cbegin(), thread_list.cend(),
222 const std::shared_ptr<Thread>& current_thread = *it; 222 [&thread](const auto& entry) { return thread == entry; });
223 if (current_thread.get() == thread.get()) { 223
224 thread_list.erase(it); 224 ASSERT(iter != thread_list.cend());
225 return; 225
226 } 226 thread_list.erase(iter);
227 ++it;
228 }
229 UNREACHABLE();
230} 227}
231 228
232std::vector<std::shared_ptr<Thread>> AddressArbiter::GetThreadsWaitingOnAddress(VAddr address) { 229std::vector<std::shared_ptr<Thread>> AddressArbiter::GetThreadsWaitingOnAddress(
233 std::vector<std::shared_ptr<Thread>> result; 230 VAddr address) const {
234 std::list<std::shared_ptr<Thread>>& thread_list = arb_threads[address]; 231 const auto iter = arb_threads.find(address);
235 auto it = thread_list.begin(); 232 if (iter == arb_threads.cend()) {
236 while (it != thread_list.end()) { 233 return {};
237 std::shared_ptr<Thread> current_thread = *it;
238 result.push_back(std::move(current_thread));
239 ++it;
240 } 234 }
241 return result; 235
236 const std::list<std::shared_ptr<Thread>>& thread_list = iter->second;
237 return {thread_list.cbegin(), thread_list.cend()};
242} 238}
243} // namespace Kernel 239} // namespace Kernel
diff --git a/src/core/hle/kernel/address_arbiter.h b/src/core/hle/kernel/address_arbiter.h
index 386983e54..f958eee5a 100644
--- a/src/core/hle/kernel/address_arbiter.h
+++ b/src/core/hle/kernel/address_arbiter.h
@@ -86,7 +86,7 @@ private:
86 void RemoveThread(std::shared_ptr<Thread> thread); 86 void RemoveThread(std::shared_ptr<Thread> thread);
87 87
88 // Gets the threads waiting on an address. 88 // Gets the threads waiting on an address.
89 std::vector<std::shared_ptr<Thread>> GetThreadsWaitingOnAddress(VAddr address); 89 std::vector<std::shared_ptr<Thread>> GetThreadsWaitingOnAddress(VAddr address) const;
90 90
91 /// List of threads waiting for a address arbiter 91 /// List of threads waiting for a address arbiter
92 std::unordered_map<VAddr, std::list<std::shared_ptr<Thread>>> arb_threads; 92 std::unordered_map<VAddr, std::list<std::shared_ptr<Thread>>> arb_threads;
diff --git a/src/core/hle/kernel/client_session.cpp b/src/core/hle/kernel/client_session.cpp
index 4669a14ad..6d66276bc 100644
--- a/src/core/hle/kernel/client_session.cpp
+++ b/src/core/hle/kernel/client_session.cpp
@@ -12,7 +12,7 @@
12 12
13namespace Kernel { 13namespace Kernel {
14 14
15ClientSession::ClientSession(KernelCore& kernel) : WaitObject{kernel} {} 15ClientSession::ClientSession(KernelCore& kernel) : SynchronizationObject{kernel} {}
16 16
17ClientSession::~ClientSession() { 17ClientSession::~ClientSession() {
18 // This destructor will be called automatically when the last ClientSession handle is closed by 18 // This destructor will be called automatically when the last ClientSession handle is closed by
@@ -31,6 +31,11 @@ void ClientSession::Acquire(Thread* thread) {
31 UNIMPLEMENTED(); 31 UNIMPLEMENTED();
32} 32}
33 33
34bool ClientSession::IsSignaled() const {
35 UNIMPLEMENTED();
36 return true;
37}
38
34ResultVal<std::shared_ptr<ClientSession>> ClientSession::Create(KernelCore& kernel, 39ResultVal<std::shared_ptr<ClientSession>> ClientSession::Create(KernelCore& kernel,
35 std::shared_ptr<Session> parent, 40 std::shared_ptr<Session> parent,
36 std::string name) { 41 std::string name) {
diff --git a/src/core/hle/kernel/client_session.h b/src/core/hle/kernel/client_session.h
index b4289a9a8..d15b09554 100644
--- a/src/core/hle/kernel/client_session.h
+++ b/src/core/hle/kernel/client_session.h
@@ -7,7 +7,7 @@
7#include <memory> 7#include <memory>
8#include <string> 8#include <string>
9 9
10#include "core/hle/kernel/wait_object.h" 10#include "core/hle/kernel/synchronization_object.h"
11#include "core/hle/result.h" 11#include "core/hle/result.h"
12 12
13union ResultCode; 13union ResultCode;
@@ -22,7 +22,7 @@ class KernelCore;
22class Session; 22class Session;
23class Thread; 23class Thread;
24 24
25class ClientSession final : public WaitObject { 25class ClientSession final : public SynchronizationObject {
26public: 26public:
27 explicit ClientSession(KernelCore& kernel); 27 explicit ClientSession(KernelCore& kernel);
28 ~ClientSession() override; 28 ~ClientSession() override;
@@ -48,6 +48,8 @@ public:
48 48
49 void Acquire(Thread* thread) override; 49 void Acquire(Thread* thread) override;
50 50
51 bool IsSignaled() const override;
52
51private: 53private:
52 static ResultVal<std::shared_ptr<ClientSession>> Create(KernelCore& kernel, 54 static ResultVal<std::shared_ptr<ClientSession>> Create(KernelCore& kernel,
53 std::shared_ptr<Session> parent, 55 std::shared_ptr<Session> parent,
diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp
index 2db28dcf0..c558a2f33 100644
--- a/src/core/hle/kernel/hle_ipc.cpp
+++ b/src/core/hle/kernel/hle_ipc.cpp
@@ -47,15 +47,15 @@ std::shared_ptr<WritableEvent> HLERequestContext::SleepClientThread(
47 const std::string& reason, u64 timeout, WakeupCallback&& callback, 47 const std::string& reason, u64 timeout, WakeupCallback&& callback,
48 std::shared_ptr<WritableEvent> writable_event) { 48 std::shared_ptr<WritableEvent> writable_event) {
49 // Put the client thread to sleep until the wait event is signaled or the timeout expires. 49 // Put the client thread to sleep until the wait event is signaled or the timeout expires.
50 thread->SetWakeupCallback([context = *this, callback](ThreadWakeupReason reason, 50 thread->SetWakeupCallback(
51 std::shared_ptr<Thread> thread, 51 [context = *this, callback](ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
52 std::shared_ptr<WaitObject> object, 52 std::shared_ptr<SynchronizationObject> object,
53 std::size_t index) mutable -> bool { 53 std::size_t index) mutable -> bool {
54 ASSERT(thread->GetStatus() == ThreadStatus::WaitHLEEvent); 54 ASSERT(thread->GetStatus() == ThreadStatus::WaitHLEEvent);
55 callback(thread, context, reason); 55 callback(thread, context, reason);
56 context.WriteToOutgoingCommandBuffer(*thread); 56 context.WriteToOutgoingCommandBuffer(*thread);
57 return true; 57 return true;
58 }); 58 });
59 59
60 auto& kernel = Core::System::GetInstance().Kernel(); 60 auto& kernel = Core::System::GetInstance().Kernel();
61 if (!writable_event) { 61 if (!writable_event) {
@@ -67,7 +67,7 @@ std::shared_ptr<WritableEvent> HLERequestContext::SleepClientThread(
67 const auto readable_event{writable_event->GetReadableEvent()}; 67 const auto readable_event{writable_event->GetReadableEvent()};
68 writable_event->Clear(); 68 writable_event->Clear();
69 thread->SetStatus(ThreadStatus::WaitHLEEvent); 69 thread->SetStatus(ThreadStatus::WaitHLEEvent);
70 thread->SetWaitObjects({readable_event}); 70 thread->SetSynchronizationObjects({readable_event});
71 readable_event->AddWaitingThread(thread); 71 readable_event->AddWaitingThread(thread);
72 72
73 if (timeout > 0) { 73 if (timeout > 0) {
@@ -284,13 +284,18 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(Thread& thread) {
284 284
285std::vector<u8> HLERequestContext::ReadBuffer(int buffer_index) const { 285std::vector<u8> HLERequestContext::ReadBuffer(int buffer_index) const {
286 std::vector<u8> buffer; 286 std::vector<u8> buffer;
287 const bool is_buffer_a{BufferDescriptorA().size() && BufferDescriptorA()[buffer_index].Size()}; 287 const bool is_buffer_a{BufferDescriptorA().size() > buffer_index &&
288 BufferDescriptorA()[buffer_index].Size()};
288 auto& memory = Core::System::GetInstance().Memory(); 289 auto& memory = Core::System::GetInstance().Memory();
289 290
290 if (is_buffer_a) { 291 if (is_buffer_a) {
292 ASSERT_MSG(BufferDescriptorA().size() > buffer_index,
293 "BufferDescriptorA invalid buffer_index {}", buffer_index);
291 buffer.resize(BufferDescriptorA()[buffer_index].Size()); 294 buffer.resize(BufferDescriptorA()[buffer_index].Size());
292 memory.ReadBlock(BufferDescriptorA()[buffer_index].Address(), buffer.data(), buffer.size()); 295 memory.ReadBlock(BufferDescriptorA()[buffer_index].Address(), buffer.data(), buffer.size());
293 } else { 296 } else {
297 ASSERT_MSG(BufferDescriptorX().size() > buffer_index,
298 "BufferDescriptorX invalid buffer_index {}", buffer_index);
294 buffer.resize(BufferDescriptorX()[buffer_index].Size()); 299 buffer.resize(BufferDescriptorX()[buffer_index].Size());
295 memory.ReadBlock(BufferDescriptorX()[buffer_index].Address(), buffer.data(), buffer.size()); 300 memory.ReadBlock(BufferDescriptorX()[buffer_index].Address(), buffer.data(), buffer.size());
296 } 301 }
@@ -305,7 +310,8 @@ std::size_t HLERequestContext::WriteBuffer(const void* buffer, std::size_t size,
305 return 0; 310 return 0;
306 } 311 }
307 312
308 const bool is_buffer_b{BufferDescriptorB().size() && BufferDescriptorB()[buffer_index].Size()}; 313 const bool is_buffer_b{BufferDescriptorB().size() > buffer_index &&
314 BufferDescriptorB()[buffer_index].Size()};
309 const std::size_t buffer_size{GetWriteBufferSize(buffer_index)}; 315 const std::size_t buffer_size{GetWriteBufferSize(buffer_index)};
310 if (size > buffer_size) { 316 if (size > buffer_size) {
311 LOG_CRITICAL(Core, "size ({:016X}) is greater than buffer_size ({:016X})", size, 317 LOG_CRITICAL(Core, "size ({:016X}) is greater than buffer_size ({:016X})", size,
@@ -315,8 +321,16 @@ std::size_t HLERequestContext::WriteBuffer(const void* buffer, std::size_t size,
315 321
316 auto& memory = Core::System::GetInstance().Memory(); 322 auto& memory = Core::System::GetInstance().Memory();
317 if (is_buffer_b) { 323 if (is_buffer_b) {
324 ASSERT_MSG(BufferDescriptorB().size() > buffer_index,
325 "BufferDescriptorB invalid buffer_index {}", buffer_index);
326 ASSERT_MSG(BufferDescriptorB()[buffer_index].Size() >= size,
327 "BufferDescriptorB buffer_index {} is not large enough", buffer_index);
318 memory.WriteBlock(BufferDescriptorB()[buffer_index].Address(), buffer, size); 328 memory.WriteBlock(BufferDescriptorB()[buffer_index].Address(), buffer, size);
319 } else { 329 } else {
330 ASSERT_MSG(BufferDescriptorC().size() > buffer_index,
331 "BufferDescriptorC invalid buffer_index {}", buffer_index);
332 ASSERT_MSG(BufferDescriptorC()[buffer_index].Size() >= size,
333 "BufferDescriptorC buffer_index {} is not large enough", buffer_index);
320 memory.WriteBlock(BufferDescriptorC()[buffer_index].Address(), buffer, size); 334 memory.WriteBlock(BufferDescriptorC()[buffer_index].Address(), buffer, size);
321 } 335 }
322 336
@@ -324,15 +338,35 @@ std::size_t HLERequestContext::WriteBuffer(const void* buffer, std::size_t size,
324} 338}
325 339
326std::size_t HLERequestContext::GetReadBufferSize(int buffer_index) const { 340std::size_t HLERequestContext::GetReadBufferSize(int buffer_index) const {
327 const bool is_buffer_a{BufferDescriptorA().size() && BufferDescriptorA()[buffer_index].Size()}; 341 const bool is_buffer_a{BufferDescriptorA().size() > buffer_index &&
328 return is_buffer_a ? BufferDescriptorA()[buffer_index].Size() 342 BufferDescriptorA()[buffer_index].Size()};
329 : BufferDescriptorX()[buffer_index].Size(); 343 if (is_buffer_a) {
344 ASSERT_MSG(BufferDescriptorA().size() > buffer_index,
345 "BufferDescriptorA invalid buffer_index {}", buffer_index);
346 ASSERT_MSG(BufferDescriptorA()[buffer_index].Size() > 0,
347 "BufferDescriptorA buffer_index {} is empty", buffer_index);
348 return BufferDescriptorA()[buffer_index].Size();
349 } else {
350 ASSERT_MSG(BufferDescriptorX().size() > buffer_index,
351 "BufferDescriptorX invalid buffer_index {}", buffer_index);
352 ASSERT_MSG(BufferDescriptorX()[buffer_index].Size() > 0,
353 "BufferDescriptorX buffer_index {} is empty", buffer_index);
354 return BufferDescriptorX()[buffer_index].Size();
355 }
330} 356}
331 357
332std::size_t HLERequestContext::GetWriteBufferSize(int buffer_index) const { 358std::size_t HLERequestContext::GetWriteBufferSize(int buffer_index) const {
333 const bool is_buffer_b{BufferDescriptorB().size() && BufferDescriptorB()[buffer_index].Size()}; 359 const bool is_buffer_b{BufferDescriptorB().size() > buffer_index &&
334 return is_buffer_b ? BufferDescriptorB()[buffer_index].Size() 360 BufferDescriptorB()[buffer_index].Size()};
335 : BufferDescriptorC()[buffer_index].Size(); 361 if (is_buffer_b) {
362 ASSERT_MSG(BufferDescriptorB().size() > buffer_index,
363 "BufferDescriptorB invalid buffer_index {}", buffer_index);
364 return BufferDescriptorB()[buffer_index].Size();
365 } else {
366 ASSERT_MSG(BufferDescriptorC().size() > buffer_index,
367 "BufferDescriptorC invalid buffer_index {}", buffer_index);
368 return BufferDescriptorC()[buffer_index].Size();
369 }
336} 370}
337 371
338std::string HLERequestContext::Description() const { 372std::string HLERequestContext::Description() const {
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index 1d0783bd3..4eb1d8703 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -3,13 +3,15 @@
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <atomic> 5#include <atomic>
6#include <functional>
6#include <memory> 7#include <memory>
7#include <mutex> 8#include <mutex>
8#include <utility> 9#include <utility>
9 10
10#include "common/assert.h" 11#include "common/assert.h"
11#include "common/logging/log.h" 12#include "common/logging/log.h"
12 13#include "core/arm/arm_interface.h"
14#include "core/arm/exclusive_monitor.h"
13#include "core/core.h" 15#include "core/core.h"
14#include "core/core_timing.h" 16#include "core/core_timing.h"
15#include "core/core_timing_util.h" 17#include "core/core_timing_util.h"
@@ -17,9 +19,11 @@
17#include "core/hle/kernel/errors.h" 19#include "core/hle/kernel/errors.h"
18#include "core/hle/kernel/handle_table.h" 20#include "core/hle/kernel/handle_table.h"
19#include "core/hle/kernel/kernel.h" 21#include "core/hle/kernel/kernel.h"
22#include "core/hle/kernel/physical_core.h"
20#include "core/hle/kernel/process.h" 23#include "core/hle/kernel/process.h"
21#include "core/hle/kernel/resource_limit.h" 24#include "core/hle/kernel/resource_limit.h"
22#include "core/hle/kernel/scheduler.h" 25#include "core/hle/kernel/scheduler.h"
26#include "core/hle/kernel/synchronization.h"
23#include "core/hle/kernel/thread.h" 27#include "core/hle/kernel/thread.h"
24#include "core/hle/lock.h" 28#include "core/hle/lock.h"
25#include "core/hle/result.h" 29#include "core/hle/result.h"
@@ -51,10 +55,10 @@ static void ThreadWakeupCallback(u64 thread_handle, [[maybe_unused]] s64 cycles_
51 if (thread->GetStatus() == ThreadStatus::WaitSynch || 55 if (thread->GetStatus() == ThreadStatus::WaitSynch ||
52 thread->GetStatus() == ThreadStatus::WaitHLEEvent) { 56 thread->GetStatus() == ThreadStatus::WaitHLEEvent) {
53 // Remove the thread from each of its waiting objects' waitlists 57 // Remove the thread from each of its waiting objects' waitlists
54 for (const auto& object : thread->GetWaitObjects()) { 58 for (const auto& object : thread->GetSynchronizationObjects()) {
55 object->RemoveWaitingThread(thread); 59 object->RemoveWaitingThread(thread);
56 } 60 }
57 thread->ClearWaitObjects(); 61 thread->ClearSynchronizationObjects();
58 62
59 // Invoke the wakeup callback before clearing the wait objects 63 // Invoke the wakeup callback before clearing the wait objects
60 if (thread->HasWakeupCallback()) { 64 if (thread->HasWakeupCallback()) {
@@ -93,11 +97,13 @@ static void ThreadWakeupCallback(u64 thread_handle, [[maybe_unused]] s64 cycles_
93} 97}
94 98
95struct KernelCore::Impl { 99struct KernelCore::Impl {
96 explicit Impl(Core::System& system) : system{system}, global_scheduler{system} {} 100 explicit Impl(Core::System& system)
101 : system{system}, global_scheduler{system}, synchronization{system} {}
97 102
98 void Initialize(KernelCore& kernel) { 103 void Initialize(KernelCore& kernel) {
99 Shutdown(); 104 Shutdown();
100 105
106 InitializePhysicalCores();
101 InitializeSystemResourceLimit(kernel); 107 InitializeSystemResourceLimit(kernel);
102 InitializeThreads(); 108 InitializeThreads();
103 InitializePreemption(); 109 InitializePreemption();
@@ -121,6 +127,21 @@ struct KernelCore::Impl {
121 global_scheduler.Shutdown(); 127 global_scheduler.Shutdown();
122 128
123 named_ports.clear(); 129 named_ports.clear();
130
131 for (auto& core : cores) {
132 core.Shutdown();
133 }
134 cores.clear();
135
136 exclusive_monitor.reset();
137 }
138
139 void InitializePhysicalCores() {
140 exclusive_monitor =
141 Core::MakeExclusiveMonitor(system.Memory(), global_scheduler.CpuCoresCount());
142 for (std::size_t i = 0; i < global_scheduler.CpuCoresCount(); i++) {
143 cores.emplace_back(system, i, *exclusive_monitor);
144 }
124 } 145 }
125 146
126 // Creates the default system resource limit 147 // Creates the default system resource limit
@@ -172,6 +193,7 @@ struct KernelCore::Impl {
172 std::vector<std::shared_ptr<Process>> process_list; 193 std::vector<std::shared_ptr<Process>> process_list;
173 Process* current_process = nullptr; 194 Process* current_process = nullptr;
174 Kernel::GlobalScheduler global_scheduler; 195 Kernel::GlobalScheduler global_scheduler;
196 Kernel::Synchronization synchronization;
175 197
176 std::shared_ptr<ResourceLimit> system_resource_limit; 198 std::shared_ptr<ResourceLimit> system_resource_limit;
177 199
@@ -186,6 +208,9 @@ struct KernelCore::Impl {
186 /// the ConnectToPort SVC. 208 /// the ConnectToPort SVC.
187 NamedPortTable named_ports; 209 NamedPortTable named_ports;
188 210
211 std::unique_ptr<Core::ExclusiveMonitor> exclusive_monitor;
212 std::vector<Kernel::PhysicalCore> cores;
213
189 // System context 214 // System context
190 Core::System& system; 215 Core::System& system;
191}; 216};
@@ -240,6 +265,42 @@ const Kernel::GlobalScheduler& KernelCore::GlobalScheduler() const {
240 return impl->global_scheduler; 265 return impl->global_scheduler;
241} 266}
242 267
268Kernel::PhysicalCore& KernelCore::PhysicalCore(std::size_t id) {
269 return impl->cores[id];
270}
271
272const Kernel::PhysicalCore& KernelCore::PhysicalCore(std::size_t id) const {
273 return impl->cores[id];
274}
275
276Kernel::Synchronization& KernelCore::Synchronization() {
277 return impl->synchronization;
278}
279
280const Kernel::Synchronization& KernelCore::Synchronization() const {
281 return impl->synchronization;
282}
283
284Core::ExclusiveMonitor& KernelCore::GetExclusiveMonitor() {
285 return *impl->exclusive_monitor;
286}
287
288const Core::ExclusiveMonitor& KernelCore::GetExclusiveMonitor() const {
289 return *impl->exclusive_monitor;
290}
291
292void KernelCore::InvalidateAllInstructionCaches() {
293 for (std::size_t i = 0; i < impl->global_scheduler.CpuCoresCount(); i++) {
294 PhysicalCore(i).ArmInterface().ClearInstructionCache();
295 }
296}
297
298void KernelCore::PrepareReschedule(std::size_t id) {
299 if (id < impl->global_scheduler.CpuCoresCount()) {
300 impl->cores[id].Stop();
301 }
302}
303
243void KernelCore::AddNamedPort(std::string name, std::shared_ptr<ClientPort> port) { 304void KernelCore::AddNamedPort(std::string name, std::shared_ptr<ClientPort> port) {
244 impl->named_ports.emplace(std::move(name), std::move(port)); 305 impl->named_ports.emplace(std::move(name), std::move(port));
245} 306}
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index 3bf0068ed..1eede3063 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -11,8 +11,9 @@
11#include "core/hle/kernel/object.h" 11#include "core/hle/kernel/object.h"
12 12
13namespace Core { 13namespace Core {
14class ExclusiveMonitor;
14class System; 15class System;
15} 16} // namespace Core
16 17
17namespace Core::Timing { 18namespace Core::Timing {
18class CoreTiming; 19class CoreTiming;
@@ -25,8 +26,10 @@ class AddressArbiter;
25class ClientPort; 26class ClientPort;
26class GlobalScheduler; 27class GlobalScheduler;
27class HandleTable; 28class HandleTable;
29class PhysicalCore;
28class Process; 30class Process;
29class ResourceLimit; 31class ResourceLimit;
32class Synchronization;
30class Thread; 33class Thread;
31 34
32/// Represents a single instance of the kernel. 35/// Represents a single instance of the kernel.
@@ -84,6 +87,27 @@ public:
84 /// Gets the sole instance of the global scheduler 87 /// Gets the sole instance of the global scheduler
85 const Kernel::GlobalScheduler& GlobalScheduler() const; 88 const Kernel::GlobalScheduler& GlobalScheduler() const;
86 89
90 /// Gets the an instance of the respective physical CPU core.
91 Kernel::PhysicalCore& PhysicalCore(std::size_t id);
92
93 /// Gets the an instance of the respective physical CPU core.
94 const Kernel::PhysicalCore& PhysicalCore(std::size_t id) const;
95
96 /// Gets the an instance of the Synchronization Interface.
97 Kernel::Synchronization& Synchronization();
98
99 /// Gets the an instance of the Synchronization Interface.
100 const Kernel::Synchronization& Synchronization() const;
101
102 /// Stops execution of 'id' core, in order to reschedule a new thread.
103 void PrepareReschedule(std::size_t id);
104
105 Core::ExclusiveMonitor& GetExclusiveMonitor();
106
107 const Core::ExclusiveMonitor& GetExclusiveMonitor() const;
108
109 void InvalidateAllInstructionCaches();
110
87 /// Adds a port to the named port table 111 /// Adds a port to the named port table
88 void AddNamedPort(std::string name, std::shared_ptr<ClientPort> port); 112 void AddNamedPort(std::string name, std::shared_ptr<ClientPort> port);
89 113
diff --git a/src/core/hle/kernel/physical_core.cpp b/src/core/hle/kernel/physical_core.cpp
new file mode 100644
index 000000000..9303dd273
--- /dev/null
+++ b/src/core/hle/kernel/physical_core.cpp
@@ -0,0 +1,51 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "common/logging/log.h"
6#include "core/arm/arm_interface.h"
7#ifdef ARCHITECTURE_x86_64
8#include "core/arm/dynarmic/arm_dynarmic.h"
9#endif
10#include "core/arm/exclusive_monitor.h"
11#include "core/arm/unicorn/arm_unicorn.h"
12#include "core/core.h"
13#include "core/hle/kernel/physical_core.h"
14#include "core/hle/kernel/scheduler.h"
15#include "core/hle/kernel/thread.h"
16
17namespace Kernel {
18
19PhysicalCore::PhysicalCore(Core::System& system, std::size_t id,
20 Core::ExclusiveMonitor& exclusive_monitor)
21 : core_index{id} {
22#ifdef ARCHITECTURE_x86_64
23 arm_interface = std::make_unique<Core::ARM_Dynarmic>(system, exclusive_monitor, core_index);
24#else
25 arm_interface = std::make_shared<Core::ARM_Unicorn>(system);
26 LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
27#endif
28
29 scheduler = std::make_unique<Kernel::Scheduler>(system, *arm_interface, core_index);
30}
31
32PhysicalCore::~PhysicalCore() = default;
33
34void PhysicalCore::Run() {
35 arm_interface->Run();
36 arm_interface->ClearExclusiveState();
37}
38
39void PhysicalCore::Step() {
40 arm_interface->Step();
41}
42
43void PhysicalCore::Stop() {
44 arm_interface->PrepareReschedule();
45}
46
47void PhysicalCore::Shutdown() {
48 scheduler->Shutdown();
49}
50
51} // namespace Kernel
diff --git a/src/core/hle/kernel/physical_core.h b/src/core/hle/kernel/physical_core.h
new file mode 100644
index 000000000..4c32c0f1b
--- /dev/null
+++ b/src/core/hle/kernel/physical_core.h
@@ -0,0 +1,77 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <cstddef>
8#include <memory>
9
10namespace Kernel {
11class Scheduler;
12} // namespace Kernel
13
14namespace Core {
15class ARM_Interface;
16class ExclusiveMonitor;
17class System;
18} // namespace Core
19
20namespace Kernel {
21
22class PhysicalCore {
23public:
24 PhysicalCore(Core::System& system, std::size_t id, Core::ExclusiveMonitor& exclusive_monitor);
25 ~PhysicalCore();
26
27 PhysicalCore(const PhysicalCore&) = delete;
28 PhysicalCore& operator=(const PhysicalCore&) = delete;
29
30 PhysicalCore(PhysicalCore&&) = default;
31 PhysicalCore& operator=(PhysicalCore&&) = default;
32
33 /// Execute current jit state
34 void Run();
35 /// Execute a single instruction in current jit.
36 void Step();
37 /// Stop JIT execution/exit
38 void Stop();
39
40 // Shutdown this physical core.
41 void Shutdown();
42
43 Core::ARM_Interface& ArmInterface() {
44 return *arm_interface;
45 }
46
47 const Core::ARM_Interface& ArmInterface() const {
48 return *arm_interface;
49 }
50
51 bool IsMainCore() const {
52 return core_index == 0;
53 }
54
55 bool IsSystemCore() const {
56 return core_index == 3;
57 }
58
59 std::size_t CoreIndex() const {
60 return core_index;
61 }
62
63 Kernel::Scheduler& Scheduler() {
64 return *scheduler;
65 }
66
67 const Kernel::Scheduler& Scheduler() const {
68 return *scheduler;
69 }
70
71private:
72 std::size_t core_index;
73 std::unique_ptr<Core::ARM_Interface> arm_interface;
74 std::unique_ptr<Kernel::Scheduler> scheduler;
75};
76
77} // namespace Kernel
diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp
index b9035a0be..2fcb7326c 100644
--- a/src/core/hle/kernel/process.cpp
+++ b/src/core/hle/kernel/process.cpp
@@ -337,7 +337,7 @@ void Process::LoadModule(CodeSet module_, VAddr base_addr) {
337} 337}
338 338
339Process::Process(Core::System& system) 339Process::Process(Core::System& system)
340 : WaitObject{system.Kernel()}, vm_manager{system}, 340 : SynchronizationObject{system.Kernel()}, vm_manager{system},
341 address_arbiter{system}, mutex{system}, system{system} {} 341 address_arbiter{system}, mutex{system}, system{system} {}
342 342
343Process::~Process() = default; 343Process::~Process() = default;
@@ -357,7 +357,7 @@ void Process::ChangeStatus(ProcessStatus new_status) {
357 357
358 status = new_status; 358 status = new_status;
359 is_signaled = true; 359 is_signaled = true;
360 WakeupAllWaitingThreads(); 360 Signal();
361} 361}
362 362
363void Process::AllocateMainThreadStack(u64 stack_size) { 363void Process::AllocateMainThreadStack(u64 stack_size) {
diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h
index 3483fa19d..4887132a7 100644
--- a/src/core/hle/kernel/process.h
+++ b/src/core/hle/kernel/process.h
@@ -15,8 +15,8 @@
15#include "core/hle/kernel/handle_table.h" 15#include "core/hle/kernel/handle_table.h"
16#include "core/hle/kernel/mutex.h" 16#include "core/hle/kernel/mutex.h"
17#include "core/hle/kernel/process_capability.h" 17#include "core/hle/kernel/process_capability.h"
18#include "core/hle/kernel/synchronization_object.h"
18#include "core/hle/kernel/vm_manager.h" 19#include "core/hle/kernel/vm_manager.h"
19#include "core/hle/kernel/wait_object.h"
20#include "core/hle/result.h" 20#include "core/hle/result.h"
21 21
22namespace Core { 22namespace Core {
@@ -60,7 +60,7 @@ enum class ProcessStatus {
60 DebugBreak, 60 DebugBreak,
61}; 61};
62 62
63class Process final : public WaitObject { 63class Process final : public SynchronizationObject {
64public: 64public:
65 explicit Process(Core::System& system); 65 explicit Process(Core::System& system);
66 ~Process() override; 66 ~Process() override;
@@ -359,10 +359,6 @@ private:
359 /// specified by metadata provided to the process during loading. 359 /// specified by metadata provided to the process during loading.
360 bool is_64bit_process = true; 360 bool is_64bit_process = true;
361 361
362 /// Whether or not this process is signaled. This occurs
363 /// upon the process changing to a different state.
364 bool is_signaled = false;
365
366 /// Total running time for the process in ticks. 362 /// Total running time for the process in ticks.
367 u64 total_process_running_time_ticks = 0; 363 u64 total_process_running_time_ticks = 0;
368 364
diff --git a/src/core/hle/kernel/readable_event.cpp b/src/core/hle/kernel/readable_event.cpp
index d8ac97aa1..9d3d3a81b 100644
--- a/src/core/hle/kernel/readable_event.cpp
+++ b/src/core/hle/kernel/readable_event.cpp
@@ -11,30 +11,30 @@
11 11
12namespace Kernel { 12namespace Kernel {
13 13
14ReadableEvent::ReadableEvent(KernelCore& kernel) : WaitObject{kernel} {} 14ReadableEvent::ReadableEvent(KernelCore& kernel) : SynchronizationObject{kernel} {}
15ReadableEvent::~ReadableEvent() = default; 15ReadableEvent::~ReadableEvent() = default;
16 16
17bool ReadableEvent::ShouldWait(const Thread* thread) const { 17bool ReadableEvent::ShouldWait(const Thread* thread) const {
18 return !signaled; 18 return !is_signaled;
19} 19}
20 20
21void ReadableEvent::Acquire(Thread* thread) { 21void ReadableEvent::Acquire(Thread* thread) {
22 ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); 22 ASSERT_MSG(IsSignaled(), "object unavailable!");
23} 23}
24 24
25void ReadableEvent::Signal() { 25void ReadableEvent::Signal() {
26 if (!signaled) { 26 if (!is_signaled) {
27 signaled = true; 27 is_signaled = true;
28 WakeupAllWaitingThreads(); 28 SynchronizationObject::Signal();
29 }; 29 };
30} 30}
31 31
32void ReadableEvent::Clear() { 32void ReadableEvent::Clear() {
33 signaled = false; 33 is_signaled = false;
34} 34}
35 35
36ResultCode ReadableEvent::Reset() { 36ResultCode ReadableEvent::Reset() {
37 if (!signaled) { 37 if (!is_signaled) {
38 return ERR_INVALID_STATE; 38 return ERR_INVALID_STATE;
39 } 39 }
40 40
diff --git a/src/core/hle/kernel/readable_event.h b/src/core/hle/kernel/readable_event.h
index 11ff71c3a..3264dd066 100644
--- a/src/core/hle/kernel/readable_event.h
+++ b/src/core/hle/kernel/readable_event.h
@@ -5,7 +5,7 @@
5#pragma once 5#pragma once
6 6
7#include "core/hle/kernel/object.h" 7#include "core/hle/kernel/object.h"
8#include "core/hle/kernel/wait_object.h" 8#include "core/hle/kernel/synchronization_object.h"
9 9
10union ResultCode; 10union ResultCode;
11 11
@@ -14,7 +14,7 @@ namespace Kernel {
14class KernelCore; 14class KernelCore;
15class WritableEvent; 15class WritableEvent;
16 16
17class ReadableEvent final : public WaitObject { 17class ReadableEvent final : public SynchronizationObject {
18 friend class WritableEvent; 18 friend class WritableEvent;
19 19
20public: 20public:
@@ -46,13 +46,11 @@ public:
46 /// then ERR_INVALID_STATE will be returned. 46 /// then ERR_INVALID_STATE will be returned.
47 ResultCode Reset(); 47 ResultCode Reset();
48 48
49 void Signal() override;
50
49private: 51private:
50 explicit ReadableEvent(KernelCore& kernel); 52 explicit ReadableEvent(KernelCore& kernel);
51 53
52 void Signal();
53
54 bool signaled{};
55
56 std::string name; ///< Name of event (optional) 54 std::string name; ///< Name of event (optional)
57}; 55};
58 56
diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp
index d36fcd7d9..86f1421bf 100644
--- a/src/core/hle/kernel/scheduler.cpp
+++ b/src/core/hle/kernel/scheduler.cpp
@@ -14,7 +14,6 @@
14#include "common/logging/log.h" 14#include "common/logging/log.h"
15#include "core/arm/arm_interface.h" 15#include "core/arm/arm_interface.h"
16#include "core/core.h" 16#include "core/core.h"
17#include "core/core_cpu.h"
18#include "core/core_timing.h" 17#include "core/core_timing.h"
19#include "core/hle/kernel/kernel.h" 18#include "core/hle/kernel/kernel.h"
20#include "core/hle/kernel/process.h" 19#include "core/hle/kernel/process.h"
@@ -125,8 +124,8 @@ bool GlobalScheduler::YieldThreadAndBalanceLoad(Thread* yielding_thread) {
125 "Thread yielding without being in front"); 124 "Thread yielding without being in front");
126 scheduled_queue[core_id].yield(priority); 125 scheduled_queue[core_id].yield(priority);
127 126
128 std::array<Thread*, NUM_CPU_CORES> current_threads; 127 std::array<Thread*, Core::Hardware::NUM_CPU_CORES> current_threads;
129 for (u32 i = 0; i < NUM_CPU_CORES; i++) { 128 for (std::size_t i = 0; i < current_threads.size(); i++) {
130 current_threads[i] = scheduled_queue[i].empty() ? nullptr : scheduled_queue[i].front(); 129 current_threads[i] = scheduled_queue[i].empty() ? nullptr : scheduled_queue[i].front();
131 } 130 }
132 131
@@ -178,8 +177,8 @@ bool GlobalScheduler::YieldThreadAndWaitForLoadBalancing(Thread* yielding_thread
178 // function... 177 // function...
179 if (scheduled_queue[core_id].empty()) { 178 if (scheduled_queue[core_id].empty()) {
180 // Here, "current_threads" is calculated after the ""yield"", unlike yield -1 179 // Here, "current_threads" is calculated after the ""yield"", unlike yield -1
181 std::array<Thread*, NUM_CPU_CORES> current_threads; 180 std::array<Thread*, Core::Hardware::NUM_CPU_CORES> current_threads;
182 for (u32 i = 0; i < NUM_CPU_CORES; i++) { 181 for (std::size_t i = 0; i < current_threads.size(); i++) {
183 current_threads[i] = scheduled_queue[i].empty() ? nullptr : scheduled_queue[i].front(); 182 current_threads[i] = scheduled_queue[i].empty() ? nullptr : scheduled_queue[i].front();
184 } 183 }
185 for (auto& thread : suggested_queue[core_id]) { 184 for (auto& thread : suggested_queue[core_id]) {
@@ -209,7 +208,7 @@ bool GlobalScheduler::YieldThreadAndWaitForLoadBalancing(Thread* yielding_thread
209} 208}
210 209
211void GlobalScheduler::PreemptThreads() { 210void GlobalScheduler::PreemptThreads() {
212 for (std::size_t core_id = 0; core_id < NUM_CPU_CORES; core_id++) { 211 for (std::size_t core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
213 const u32 priority = preemption_priorities[core_id]; 212 const u32 priority = preemption_priorities[core_id];
214 213
215 if (scheduled_queue[core_id].size(priority) > 0) { 214 if (scheduled_queue[core_id].size(priority) > 0) {
@@ -350,7 +349,7 @@ bool GlobalScheduler::AskForReselectionOrMarkRedundant(Thread* current_thread,
350} 349}
351 350
352void GlobalScheduler::Shutdown() { 351void GlobalScheduler::Shutdown() {
353 for (std::size_t core = 0; core < NUM_CPU_CORES; core++) { 352 for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
354 scheduled_queue[core].clear(); 353 scheduled_queue[core].clear();
355 suggested_queue[core].clear(); 354 suggested_queue[core].clear();
356 } 355 }
diff --git a/src/core/hle/kernel/scheduler.h b/src/core/hle/kernel/scheduler.h
index 14b77960a..96db049cb 100644
--- a/src/core/hle/kernel/scheduler.h
+++ b/src/core/hle/kernel/scheduler.h
@@ -10,6 +10,7 @@
10 10
11#include "common/common_types.h" 11#include "common/common_types.h"
12#include "common/multi_level_queue.h" 12#include "common/multi_level_queue.h"
13#include "core/hardware_properties.h"
13#include "core/hle/kernel/thread.h" 14#include "core/hle/kernel/thread.h"
14 15
15namespace Core { 16namespace Core {
@@ -23,8 +24,6 @@ class Process;
23 24
24class GlobalScheduler final { 25class GlobalScheduler final {
25public: 26public:
26 static constexpr u32 NUM_CPU_CORES = 4;
27
28 explicit GlobalScheduler(Core::System& system); 27 explicit GlobalScheduler(Core::System& system);
29 ~GlobalScheduler(); 28 ~GlobalScheduler();
30 29
@@ -125,7 +124,7 @@ public:
125 void PreemptThreads(); 124 void PreemptThreads();
126 125
127 u32 CpuCoresCount() const { 126 u32 CpuCoresCount() const {
128 return NUM_CPU_CORES; 127 return Core::Hardware::NUM_CPU_CORES;
129 } 128 }
130 129
131 void SetReselectionPending() { 130 void SetReselectionPending() {
@@ -149,13 +148,15 @@ private:
149 bool AskForReselectionOrMarkRedundant(Thread* current_thread, const Thread* winner); 148 bool AskForReselectionOrMarkRedundant(Thread* current_thread, const Thread* winner);
150 149
151 static constexpr u32 min_regular_priority = 2; 150 static constexpr u32 min_regular_priority = 2;
152 std::array<Common::MultiLevelQueue<Thread*, THREADPRIO_COUNT>, NUM_CPU_CORES> scheduled_queue; 151 std::array<Common::MultiLevelQueue<Thread*, THREADPRIO_COUNT>, Core::Hardware::NUM_CPU_CORES>
153 std::array<Common::MultiLevelQueue<Thread*, THREADPRIO_COUNT>, NUM_CPU_CORES> suggested_queue; 152 scheduled_queue;
153 std::array<Common::MultiLevelQueue<Thread*, THREADPRIO_COUNT>, Core::Hardware::NUM_CPU_CORES>
154 suggested_queue;
154 std::atomic<bool> is_reselection_pending{false}; 155 std::atomic<bool> is_reselection_pending{false};
155 156
156 // The priority levels at which the global scheduler preempts threads every 10 ms. They are 157 // The priority levels at which the global scheduler preempts threads every 10 ms. They are
157 // ordered from Core 0 to Core 3. 158 // ordered from Core 0 to Core 3.
158 std::array<u32, NUM_CPU_CORES> preemption_priorities = {59, 59, 59, 62}; 159 std::array<u32, Core::Hardware::NUM_CPU_CORES> preemption_priorities = {59, 59, 59, 62};
159 160
160 /// Lists all thread ids that aren't deleted/etc. 161 /// Lists all thread ids that aren't deleted/etc.
161 std::vector<std::shared_ptr<Thread>> thread_list; 162 std::vector<std::shared_ptr<Thread>> thread_list;
diff --git a/src/core/hle/kernel/server_port.cpp b/src/core/hle/kernel/server_port.cpp
index a4ccfa35e..a549ae9d7 100644
--- a/src/core/hle/kernel/server_port.cpp
+++ b/src/core/hle/kernel/server_port.cpp
@@ -13,7 +13,7 @@
13 13
14namespace Kernel { 14namespace Kernel {
15 15
16ServerPort::ServerPort(KernelCore& kernel) : WaitObject{kernel} {} 16ServerPort::ServerPort(KernelCore& kernel) : SynchronizationObject{kernel} {}
17ServerPort::~ServerPort() = default; 17ServerPort::~ServerPort() = default;
18 18
19ResultVal<std::shared_ptr<ServerSession>> ServerPort::Accept() { 19ResultVal<std::shared_ptr<ServerSession>> ServerPort::Accept() {
@@ -39,6 +39,10 @@ void ServerPort::Acquire(Thread* thread) {
39 ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); 39 ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
40} 40}
41 41
42bool ServerPort::IsSignaled() const {
43 return !pending_sessions.empty();
44}
45
42ServerPort::PortPair ServerPort::CreatePortPair(KernelCore& kernel, u32 max_sessions, 46ServerPort::PortPair ServerPort::CreatePortPair(KernelCore& kernel, u32 max_sessions,
43 std::string name) { 47 std::string name) {
44 std::shared_ptr<ServerPort> server_port = std::make_shared<ServerPort>(kernel); 48 std::shared_ptr<ServerPort> server_port = std::make_shared<ServerPort>(kernel);
diff --git a/src/core/hle/kernel/server_port.h b/src/core/hle/kernel/server_port.h
index 8be8a75ea..41b191b86 100644
--- a/src/core/hle/kernel/server_port.h
+++ b/src/core/hle/kernel/server_port.h
@@ -10,7 +10,7 @@
10#include <vector> 10#include <vector>
11#include "common/common_types.h" 11#include "common/common_types.h"
12#include "core/hle/kernel/object.h" 12#include "core/hle/kernel/object.h"
13#include "core/hle/kernel/wait_object.h" 13#include "core/hle/kernel/synchronization_object.h"
14#include "core/hle/result.h" 14#include "core/hle/result.h"
15 15
16namespace Kernel { 16namespace Kernel {
@@ -20,7 +20,7 @@ class KernelCore;
20class ServerSession; 20class ServerSession;
21class SessionRequestHandler; 21class SessionRequestHandler;
22 22
23class ServerPort final : public WaitObject { 23class ServerPort final : public SynchronizationObject {
24public: 24public:
25 explicit ServerPort(KernelCore& kernel); 25 explicit ServerPort(KernelCore& kernel);
26 ~ServerPort() override; 26 ~ServerPort() override;
@@ -82,6 +82,8 @@ public:
82 bool ShouldWait(const Thread* thread) const override; 82 bool ShouldWait(const Thread* thread) const override;
83 void Acquire(Thread* thread) override; 83 void Acquire(Thread* thread) override;
84 84
85 bool IsSignaled() const override;
86
85private: 87private:
86 /// ServerSessions waiting to be accepted by the port 88 /// ServerSessions waiting to be accepted by the port
87 std::vector<std::shared_ptr<ServerSession>> pending_sessions; 89 std::vector<std::shared_ptr<ServerSession>> pending_sessions;
diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp
index 7825e1ec4..4604e35c5 100644
--- a/src/core/hle/kernel/server_session.cpp
+++ b/src/core/hle/kernel/server_session.cpp
@@ -24,7 +24,7 @@
24 24
25namespace Kernel { 25namespace Kernel {
26 26
27ServerSession::ServerSession(KernelCore& kernel) : WaitObject{kernel} {} 27ServerSession::ServerSession(KernelCore& kernel) : SynchronizationObject{kernel} {}
28ServerSession::~ServerSession() = default; 28ServerSession::~ServerSession() = default;
29 29
30ResultVal<std::shared_ptr<ServerSession>> ServerSession::Create(KernelCore& kernel, 30ResultVal<std::shared_ptr<ServerSession>> ServerSession::Create(KernelCore& kernel,
@@ -50,6 +50,16 @@ bool ServerSession::ShouldWait(const Thread* thread) const {
50 return pending_requesting_threads.empty() || currently_handling != nullptr; 50 return pending_requesting_threads.empty() || currently_handling != nullptr;
51} 51}
52 52
53bool ServerSession::IsSignaled() const {
54 // Closed sessions should never wait, an error will be returned from svcReplyAndReceive.
55 if (!parent->Client()) {
56 return true;
57 }
58
59 // Wait if we have no pending requests, or if we're currently handling a request.
60 return !pending_requesting_threads.empty() && currently_handling == nullptr;
61}
62
53void ServerSession::Acquire(Thread* thread) { 63void ServerSession::Acquire(Thread* thread) {
54 ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); 64 ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
55 // We are now handling a request, pop it from the stack. 65 // We are now handling a request, pop it from the stack.
diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h
index d6e48109e..77e4f6721 100644
--- a/src/core/hle/kernel/server_session.h
+++ b/src/core/hle/kernel/server_session.h
@@ -10,7 +10,7 @@
10#include <vector> 10#include <vector>
11 11
12#include "common/threadsafe_queue.h" 12#include "common/threadsafe_queue.h"
13#include "core/hle/kernel/wait_object.h" 13#include "core/hle/kernel/synchronization_object.h"
14#include "core/hle/result.h" 14#include "core/hle/result.h"
15 15
16namespace Memory { 16namespace Memory {
@@ -41,7 +41,7 @@ class Thread;
41 * After the server replies to the request, the response is marshalled back to the caller's 41 * After the server replies to the request, the response is marshalled back to the caller's
42 * TLS buffer and control is transferred back to it. 42 * TLS buffer and control is transferred back to it.
43 */ 43 */
44class ServerSession final : public WaitObject { 44class ServerSession final : public SynchronizationObject {
45public: 45public:
46 explicit ServerSession(KernelCore& kernel); 46 explicit ServerSession(KernelCore& kernel);
47 ~ServerSession() override; 47 ~ServerSession() override;
@@ -73,6 +73,8 @@ public:
73 return parent.get(); 73 return parent.get();
74 } 74 }
75 75
76 bool IsSignaled() const override;
77
76 /** 78 /**
77 * Sets the HLE handler for the session. This handler will be called to service IPC requests 79 * Sets the HLE handler for the session. This handler will be called to service IPC requests
78 * instead of the regular IPC machinery. (The regular IPC machinery is currently not 80 * instead of the regular IPC machinery. (The regular IPC machinery is currently not
diff --git a/src/core/hle/kernel/session.cpp b/src/core/hle/kernel/session.cpp
index dee6e2b72..e4dd53e24 100644
--- a/src/core/hle/kernel/session.cpp
+++ b/src/core/hle/kernel/session.cpp
@@ -9,7 +9,7 @@
9 9
10namespace Kernel { 10namespace Kernel {
11 11
12Session::Session(KernelCore& kernel) : WaitObject{kernel} {} 12Session::Session(KernelCore& kernel) : SynchronizationObject{kernel} {}
13Session::~Session() = default; 13Session::~Session() = default;
14 14
15Session::SessionPair Session::Create(KernelCore& kernel, std::string name) { 15Session::SessionPair Session::Create(KernelCore& kernel, std::string name) {
@@ -29,6 +29,11 @@ bool Session::ShouldWait(const Thread* thread) const {
29 return {}; 29 return {};
30} 30}
31 31
32bool Session::IsSignaled() const {
33 UNIMPLEMENTED();
34 return true;
35}
36
32void Session::Acquire(Thread* thread) { 37void Session::Acquire(Thread* thread) {
33 UNIMPLEMENTED(); 38 UNIMPLEMENTED();
34} 39}
diff --git a/src/core/hle/kernel/session.h b/src/core/hle/kernel/session.h
index 15a5ac15f..7cd9c0d77 100644
--- a/src/core/hle/kernel/session.h
+++ b/src/core/hle/kernel/session.h
@@ -8,7 +8,7 @@
8#include <string> 8#include <string>
9#include <utility> 9#include <utility>
10 10
11#include "core/hle/kernel/wait_object.h" 11#include "core/hle/kernel/synchronization_object.h"
12 12
13namespace Kernel { 13namespace Kernel {
14 14
@@ -19,7 +19,7 @@ class ServerSession;
19 * Parent structure to link the client and server endpoints of a session with their associated 19 * Parent structure to link the client and server endpoints of a session with their associated
20 * client port. 20 * client port.
21 */ 21 */
22class Session final : public WaitObject { 22class Session final : public SynchronizationObject {
23public: 23public:
24 explicit Session(KernelCore& kernel); 24 explicit Session(KernelCore& kernel);
25 ~Session() override; 25 ~Session() override;
@@ -39,6 +39,8 @@ public:
39 39
40 bool ShouldWait(const Thread* thread) const override; 40 bool ShouldWait(const Thread* thread) const override;
41 41
42 bool IsSignaled() const override;
43
42 void Acquire(Thread* thread) override; 44 void Acquire(Thread* thread) override;
43 45
44 std::shared_ptr<ClientSession> Client() { 46 std::shared_ptr<ClientSession> Client() {
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index dbcdb0b88..fd91779a3 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -15,7 +15,7 @@
15#include "common/string_util.h" 15#include "common/string_util.h"
16#include "core/arm/exclusive_monitor.h" 16#include "core/arm/exclusive_monitor.h"
17#include "core/core.h" 17#include "core/core.h"
18#include "core/core_cpu.h" 18#include "core/core_manager.h"
19#include "core/core_timing.h" 19#include "core/core_timing.h"
20#include "core/core_timing_util.h" 20#include "core/core_timing_util.h"
21#include "core/hle/kernel/address_arbiter.h" 21#include "core/hle/kernel/address_arbiter.h"
@@ -32,6 +32,7 @@
32#include "core/hle/kernel/shared_memory.h" 32#include "core/hle/kernel/shared_memory.h"
33#include "core/hle/kernel/svc.h" 33#include "core/hle/kernel/svc.h"
34#include "core/hle/kernel/svc_wrap.h" 34#include "core/hle/kernel/svc_wrap.h"
35#include "core/hle/kernel/synchronization.h"
35#include "core/hle/kernel/thread.h" 36#include "core/hle/kernel/thread.h"
36#include "core/hle/kernel/transfer_memory.h" 37#include "core/hle/kernel/transfer_memory.h"
37#include "core/hle/kernel/writable_event.h" 38#include "core/hle/kernel/writable_event.h"
@@ -433,22 +434,6 @@ static ResultCode GetProcessId(Core::System& system, u64* process_id, Handle han
433 return ERR_INVALID_HANDLE; 434 return ERR_INVALID_HANDLE;
434} 435}
435 436
436/// Default thread wakeup callback for WaitSynchronization
437static bool DefaultThreadWakeupCallback(ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
438 std::shared_ptr<WaitObject> object, std::size_t index) {
439 ASSERT(thread->GetStatus() == ThreadStatus::WaitSynch);
440
441 if (reason == ThreadWakeupReason::Timeout) {
442 thread->SetWaitSynchronizationResult(RESULT_TIMEOUT);
443 return true;
444 }
445
446 ASSERT(reason == ThreadWakeupReason::Signal);
447 thread->SetWaitSynchronizationResult(RESULT_SUCCESS);
448 thread->SetWaitSynchronizationOutput(static_cast<u32>(index));
449 return true;
450};
451
452/// Wait for the given handles to synchronize, timeout after the specified nanoseconds 437/// Wait for the given handles to synchronize, timeout after the specified nanoseconds
453static ResultCode WaitSynchronization(Core::System& system, Handle* index, VAddr handles_address, 438static ResultCode WaitSynchronization(Core::System& system, Handle* index, VAddr handles_address,
454 u64 handle_count, s64 nano_seconds) { 439 u64 handle_count, s64 nano_seconds) {
@@ -472,14 +457,14 @@ static ResultCode WaitSynchronization(Core::System& system, Handle* index, VAddr
472 } 457 }
473 458
474 auto* const thread = system.CurrentScheduler().GetCurrentThread(); 459 auto* const thread = system.CurrentScheduler().GetCurrentThread();
475 460 auto& kernel = system.Kernel();
476 using ObjectPtr = Thread::ThreadWaitObjects::value_type; 461 using ObjectPtr = Thread::ThreadSynchronizationObjects::value_type;
477 Thread::ThreadWaitObjects objects(handle_count); 462 Thread::ThreadSynchronizationObjects objects(handle_count);
478 const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); 463 const auto& handle_table = kernel.CurrentProcess()->GetHandleTable();
479 464
480 for (u64 i = 0; i < handle_count; ++i) { 465 for (u64 i = 0; i < handle_count; ++i) {
481 const Handle handle = memory.Read32(handles_address + i * sizeof(Handle)); 466 const Handle handle = memory.Read32(handles_address + i * sizeof(Handle));
482 const auto object = handle_table.Get<WaitObject>(handle); 467 const auto object = handle_table.Get<SynchronizationObject>(handle);
483 468
484 if (object == nullptr) { 469 if (object == nullptr) {
485 LOG_ERROR(Kernel_SVC, "Object is a nullptr"); 470 LOG_ERROR(Kernel_SVC, "Object is a nullptr");
@@ -488,47 +473,10 @@ static ResultCode WaitSynchronization(Core::System& system, Handle* index, VAddr
488 473
489 objects[i] = object; 474 objects[i] = object;
490 } 475 }
491 476 auto& synchronization = kernel.Synchronization();
492 // Find the first object that is acquirable in the provided list of objects 477 const auto [result, handle_result] = synchronization.WaitFor(objects, nano_seconds);
493 auto itr = std::find_if(objects.begin(), objects.end(), [thread](const ObjectPtr& object) { 478 *index = handle_result;
494 return !object->ShouldWait(thread); 479 return result;
495 });
496
497 if (itr != objects.end()) {
498 // We found a ready object, acquire it and set the result value
499 WaitObject* object = itr->get();
500 object->Acquire(thread);
501 *index = static_cast<s32>(std::distance(objects.begin(), itr));
502 return RESULT_SUCCESS;
503 }
504
505 // No objects were ready to be acquired, prepare to suspend the thread.
506
507 // If a timeout value of 0 was provided, just return the Timeout error code instead of
508 // suspending the thread.
509 if (nano_seconds == 0) {
510 return RESULT_TIMEOUT;
511 }
512
513 if (thread->IsSyncCancelled()) {
514 thread->SetSyncCancelled(false);
515 return ERR_SYNCHRONIZATION_CANCELED;
516 }
517
518 for (auto& object : objects) {
519 object->AddWaitingThread(SharedFrom(thread));
520 }
521
522 thread->SetWaitObjects(std::move(objects));
523 thread->SetStatus(ThreadStatus::WaitSynch);
524
525 // Create an event to wake the thread up after the specified nanosecond delay has passed
526 thread->WakeAfterDelay(nano_seconds);
527 thread->SetWakeupCallback(DefaultThreadWakeupCallback);
528
529 system.PrepareReschedule(thread->GetProcessorID());
530
531 return RESULT_TIMEOUT;
532} 480}
533 481
534/// Resumes a thread waiting on WaitSynchronization 482/// Resumes a thread waiting on WaitSynchronization
@@ -1863,10 +1811,14 @@ static ResultCode CreateTransferMemory(Core::System& system, Handle* handle, VAd
1863 } 1811 }
1864 1812
1865 auto& kernel = system.Kernel(); 1813 auto& kernel = system.Kernel();
1866 auto transfer_mem_handle = TransferMemory::Create(kernel, addr, size, perms); 1814 auto transfer_mem_handle = TransferMemory::Create(kernel, system.Memory(), addr, size, perms);
1815
1816 if (const auto reserve_result{transfer_mem_handle->Reserve()}; reserve_result.IsError()) {
1817 return reserve_result;
1818 }
1867 1819
1868 auto& handle_table = kernel.CurrentProcess()->GetHandleTable(); 1820 auto& handle_table = kernel.CurrentProcess()->GetHandleTable();
1869 const auto result = handle_table.Create(std::move(transfer_mem_handle)); 1821 const auto result{handle_table.Create(std::move(transfer_mem_handle))};
1870 if (result.Failed()) { 1822 if (result.Failed()) {
1871 return result.Code(); 1823 return result.Code();
1872 } 1824 }
diff --git a/src/core/hle/kernel/synchronization.cpp b/src/core/hle/kernel/synchronization.cpp
new file mode 100644
index 000000000..dc37fad1a
--- /dev/null
+++ b/src/core/hle/kernel/synchronization.cpp
@@ -0,0 +1,87 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "core/core.h"
6#include "core/hle/kernel/errors.h"
7#include "core/hle/kernel/handle_table.h"
8#include "core/hle/kernel/kernel.h"
9#include "core/hle/kernel/scheduler.h"
10#include "core/hle/kernel/synchronization.h"
11#include "core/hle/kernel/synchronization_object.h"
12#include "core/hle/kernel/thread.h"
13
14namespace Kernel {
15
16/// Default thread wakeup callback for WaitSynchronization
17static bool DefaultThreadWakeupCallback(ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
18 std::shared_ptr<SynchronizationObject> object,
19 std::size_t index) {
20 ASSERT(thread->GetStatus() == ThreadStatus::WaitSynch);
21
22 if (reason == ThreadWakeupReason::Timeout) {
23 thread->SetWaitSynchronizationResult(RESULT_TIMEOUT);
24 return true;
25 }
26
27 ASSERT(reason == ThreadWakeupReason::Signal);
28 thread->SetWaitSynchronizationResult(RESULT_SUCCESS);
29 thread->SetWaitSynchronizationOutput(static_cast<u32>(index));
30 return true;
31}
32
33Synchronization::Synchronization(Core::System& system) : system{system} {}
34
35void Synchronization::SignalObject(SynchronizationObject& obj) const {
36 if (obj.IsSignaled()) {
37 obj.WakeupAllWaitingThreads();
38 }
39}
40
41std::pair<ResultCode, Handle> Synchronization::WaitFor(
42 std::vector<std::shared_ptr<SynchronizationObject>>& sync_objects, s64 nano_seconds) {
43 auto* const thread = system.CurrentScheduler().GetCurrentThread();
44 // Find the first object that is acquirable in the provided list of objects
45 const auto itr = std::find_if(sync_objects.begin(), sync_objects.end(),
46 [thread](const std::shared_ptr<SynchronizationObject>& object) {
47 return object->IsSignaled();
48 });
49
50 if (itr != sync_objects.end()) {
51 // We found a ready object, acquire it and set the result value
52 SynchronizationObject* object = itr->get();
53 object->Acquire(thread);
54 const u32 index = static_cast<s32>(std::distance(sync_objects.begin(), itr));
55 return {RESULT_SUCCESS, index};
56 }
57
58 // No objects were ready to be acquired, prepare to suspend the thread.
59
60 // If a timeout value of 0 was provided, just return the Timeout error code instead of
61 // suspending the thread.
62 if (nano_seconds == 0) {
63 return {RESULT_TIMEOUT, InvalidHandle};
64 }
65
66 if (thread->IsSyncCancelled()) {
67 thread->SetSyncCancelled(false);
68 return {ERR_SYNCHRONIZATION_CANCELED, InvalidHandle};
69 }
70
71 for (auto& object : sync_objects) {
72 object->AddWaitingThread(SharedFrom(thread));
73 }
74
75 thread->SetSynchronizationObjects(std::move(sync_objects));
76 thread->SetStatus(ThreadStatus::WaitSynch);
77
78 // Create an event to wake the thread up after the specified nanosecond delay has passed
79 thread->WakeAfterDelay(nano_seconds);
80 thread->SetWakeupCallback(DefaultThreadWakeupCallback);
81
82 system.PrepareReschedule(thread->GetProcessorID());
83
84 return {RESULT_TIMEOUT, InvalidHandle};
85}
86
87} // namespace Kernel
diff --git a/src/core/hle/kernel/synchronization.h b/src/core/hle/kernel/synchronization.h
new file mode 100644
index 000000000..379f4b1d3
--- /dev/null
+++ b/src/core/hle/kernel/synchronization.h
@@ -0,0 +1,44 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include <utility>
9#include <vector>
10
11#include "core/hle/kernel/object.h"
12#include "core/hle/result.h"
13
14namespace Core {
15class System;
16} // namespace Core
17
18namespace Kernel {
19
20class SynchronizationObject;
21
22/**
23 * The 'Synchronization' class is an interface for handling synchronization methods
24 * used by Synchronization objects and synchronization SVCs. This centralizes processing of
25 * such
26 */
27class Synchronization {
28public:
29 explicit Synchronization(Core::System& system);
30
31 /// Signals a synchronization object, waking up all its waiting threads
32 void SignalObject(SynchronizationObject& obj) const;
33
34 /// Tries to see if waiting for any of the sync_objects is necessary, if not
35 /// it returns Success and the handle index of the signaled sync object. In
36 /// case not, the current thread will be locked and wait for nano_seconds or
37 /// for a synchronization object to signal.
38 std::pair<ResultCode, Handle> WaitFor(
39 std::vector<std::shared_ptr<SynchronizationObject>>& sync_objects, s64 nano_seconds);
40
41private:
42 Core::System& system;
43};
44} // namespace Kernel
diff --git a/src/core/hle/kernel/wait_object.cpp b/src/core/hle/kernel/synchronization_object.cpp
index 745f2c4e8..43f3eef18 100644
--- a/src/core/hle/kernel/wait_object.cpp
+++ b/src/core/hle/kernel/synchronization_object.cpp
@@ -7,24 +7,29 @@
7#include "common/common_types.h" 7#include "common/common_types.h"
8#include "common/logging/log.h" 8#include "common/logging/log.h"
9#include "core/core.h" 9#include "core/core.h"
10#include "core/core_cpu.h"
11#include "core/hle/kernel/kernel.h" 10#include "core/hle/kernel/kernel.h"
12#include "core/hle/kernel/object.h" 11#include "core/hle/kernel/object.h"
13#include "core/hle/kernel/process.h" 12#include "core/hle/kernel/process.h"
13#include "core/hle/kernel/synchronization.h"
14#include "core/hle/kernel/synchronization_object.h"
14#include "core/hle/kernel/thread.h" 15#include "core/hle/kernel/thread.h"
15 16
16namespace Kernel { 17namespace Kernel {
17 18
18WaitObject::WaitObject(KernelCore& kernel) : Object{kernel} {} 19SynchronizationObject::SynchronizationObject(KernelCore& kernel) : Object{kernel} {}
19WaitObject::~WaitObject() = default; 20SynchronizationObject::~SynchronizationObject() = default;
20 21
21void WaitObject::AddWaitingThread(std::shared_ptr<Thread> thread) { 22void SynchronizationObject::Signal() {
23 kernel.Synchronization().SignalObject(*this);
24}
25
26void SynchronizationObject::AddWaitingThread(std::shared_ptr<Thread> thread) {
22 auto itr = std::find(waiting_threads.begin(), waiting_threads.end(), thread); 27 auto itr = std::find(waiting_threads.begin(), waiting_threads.end(), thread);
23 if (itr == waiting_threads.end()) 28 if (itr == waiting_threads.end())
24 waiting_threads.push_back(std::move(thread)); 29 waiting_threads.push_back(std::move(thread));
25} 30}
26 31
27void WaitObject::RemoveWaitingThread(std::shared_ptr<Thread> thread) { 32void SynchronizationObject::RemoveWaitingThread(std::shared_ptr<Thread> thread) {
28 auto itr = std::find(waiting_threads.begin(), waiting_threads.end(), thread); 33 auto itr = std::find(waiting_threads.begin(), waiting_threads.end(), thread);
29 // If a thread passed multiple handles to the same object, 34 // If a thread passed multiple handles to the same object,
30 // the kernel might attempt to remove the thread from the object's 35 // the kernel might attempt to remove the thread from the object's
@@ -33,7 +38,7 @@ void WaitObject::RemoveWaitingThread(std::shared_ptr<Thread> thread) {
33 waiting_threads.erase(itr); 38 waiting_threads.erase(itr);
34} 39}
35 40
36std::shared_ptr<Thread> WaitObject::GetHighestPriorityReadyThread() const { 41std::shared_ptr<Thread> SynchronizationObject::GetHighestPriorityReadyThread() const {
37 Thread* candidate = nullptr; 42 Thread* candidate = nullptr;
38 u32 candidate_priority = THREADPRIO_LOWEST + 1; 43 u32 candidate_priority = THREADPRIO_LOWEST + 1;
39 44
@@ -51,23 +56,14 @@ std::shared_ptr<Thread> WaitObject::GetHighestPriorityReadyThread() const {
51 if (ShouldWait(thread.get())) 56 if (ShouldWait(thread.get()))
52 continue; 57 continue;
53 58
54 // A thread is ready to run if it's either in ThreadStatus::WaitSynch 59 candidate = thread.get();
55 // and the rest of the objects it is waiting on are ready. 60 candidate_priority = thread->GetPriority();
56 bool ready_to_run = true;
57 if (thread_status == ThreadStatus::WaitSynch) {
58 ready_to_run = thread->AllWaitObjectsReady();
59 }
60
61 if (ready_to_run) {
62 candidate = thread.get();
63 candidate_priority = thread->GetPriority();
64 }
65 } 61 }
66 62
67 return SharedFrom(candidate); 63 return SharedFrom(candidate);
68} 64}
69 65
70void WaitObject::WakeupWaitingThread(std::shared_ptr<Thread> thread) { 66void SynchronizationObject::WakeupWaitingThread(std::shared_ptr<Thread> thread) {
71 ASSERT(!ShouldWait(thread.get())); 67 ASSERT(!ShouldWait(thread.get()));
72 68
73 if (!thread) { 69 if (!thread) {
@@ -75,7 +71,7 @@ void WaitObject::WakeupWaitingThread(std::shared_ptr<Thread> thread) {
75 } 71 }
76 72
77 if (thread->IsSleepingOnWait()) { 73 if (thread->IsSleepingOnWait()) {
78 for (const auto& object : thread->GetWaitObjects()) { 74 for (const auto& object : thread->GetSynchronizationObjects()) {
79 ASSERT(!object->ShouldWait(thread.get())); 75 ASSERT(!object->ShouldWait(thread.get()));
80 object->Acquire(thread.get()); 76 object->Acquire(thread.get());
81 } 77 }
@@ -83,9 +79,9 @@ void WaitObject::WakeupWaitingThread(std::shared_ptr<Thread> thread) {
83 Acquire(thread.get()); 79 Acquire(thread.get());
84 } 80 }
85 81
86 const std::size_t index = thread->GetWaitObjectIndex(SharedFrom(this)); 82 const std::size_t index = thread->GetSynchronizationObjectIndex(SharedFrom(this));
87 83
88 thread->ClearWaitObjects(); 84 thread->ClearSynchronizationObjects();
89 85
90 thread->CancelWakeupTimer(); 86 thread->CancelWakeupTimer();
91 87
@@ -96,17 +92,17 @@ void WaitObject::WakeupWaitingThread(std::shared_ptr<Thread> thread) {
96 } 92 }
97 if (resume) { 93 if (resume) {
98 thread->ResumeFromWait(); 94 thread->ResumeFromWait();
99 Core::System::GetInstance().PrepareReschedule(thread->GetProcessorID()); 95 kernel.PrepareReschedule(thread->GetProcessorID());
100 } 96 }
101} 97}
102 98
103void WaitObject::WakeupAllWaitingThreads() { 99void SynchronizationObject::WakeupAllWaitingThreads() {
104 while (auto thread = GetHighestPriorityReadyThread()) { 100 while (auto thread = GetHighestPriorityReadyThread()) {
105 WakeupWaitingThread(thread); 101 WakeupWaitingThread(thread);
106 } 102 }
107} 103}
108 104
109const std::vector<std::shared_ptr<Thread>>& WaitObject::GetWaitingThreads() const { 105const std::vector<std::shared_ptr<Thread>>& SynchronizationObject::GetWaitingThreads() const {
110 return waiting_threads; 106 return waiting_threads;
111} 107}
112 108
diff --git a/src/core/hle/kernel/wait_object.h b/src/core/hle/kernel/synchronization_object.h
index 9a17958a4..741c31faf 100644
--- a/src/core/hle/kernel/wait_object.h
+++ b/src/core/hle/kernel/synchronization_object.h
@@ -15,10 +15,10 @@ class KernelCore;
15class Thread; 15class Thread;
16 16
17/// Class that represents a Kernel object that a thread can be waiting on 17/// Class that represents a Kernel object that a thread can be waiting on
18class WaitObject : public Object { 18class SynchronizationObject : public Object {
19public: 19public:
20 explicit WaitObject(KernelCore& kernel); 20 explicit SynchronizationObject(KernelCore& kernel);
21 ~WaitObject() override; 21 ~SynchronizationObject() override;
22 22
23 /** 23 /**
24 * Check if the specified thread should wait until the object is available 24 * Check if the specified thread should wait until the object is available
@@ -30,6 +30,13 @@ public:
30 /// Acquire/lock the object for the specified thread if it is available 30 /// Acquire/lock the object for the specified thread if it is available
31 virtual void Acquire(Thread* thread) = 0; 31 virtual void Acquire(Thread* thread) = 0;
32 32
33 /// Signal this object
34 virtual void Signal();
35
36 virtual bool IsSignaled() const {
37 return is_signaled;
38 }
39
33 /** 40 /**
34 * Add a thread to wait on this object 41 * Add a thread to wait on this object
35 * @param thread Pointer to thread to add 42 * @param thread Pointer to thread to add
@@ -60,16 +67,20 @@ public:
60 /// Get a const reference to the waiting threads list for debug use 67 /// Get a const reference to the waiting threads list for debug use
61 const std::vector<std::shared_ptr<Thread>>& GetWaitingThreads() const; 68 const std::vector<std::shared_ptr<Thread>>& GetWaitingThreads() const;
62 69
70protected:
71 bool is_signaled{}; // Tells if this sync object is signalled;
72
63private: 73private:
64 /// Threads waiting for this object to become available 74 /// Threads waiting for this object to become available
65 std::vector<std::shared_ptr<Thread>> waiting_threads; 75 std::vector<std::shared_ptr<Thread>> waiting_threads;
66}; 76};
67 77
68// Specialization of DynamicObjectCast for WaitObjects 78// Specialization of DynamicObjectCast for SynchronizationObjects
69template <> 79template <>
70inline std::shared_ptr<WaitObject> DynamicObjectCast<WaitObject>(std::shared_ptr<Object> object) { 80inline std::shared_ptr<SynchronizationObject> DynamicObjectCast<SynchronizationObject>(
81 std::shared_ptr<Object> object) {
71 if (object != nullptr && object->IsWaitable()) { 82 if (object != nullptr && object->IsWaitable()) {
72 return std::static_pointer_cast<WaitObject>(object); 83 return std::static_pointer_cast<SynchronizationObject>(object);
73 } 84 }
74 return nullptr; 85 return nullptr;
75} 86}
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index e84e5ce0d..ae5f2c8bd 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -13,9 +13,9 @@
13#include "common/thread_queue_list.h" 13#include "common/thread_queue_list.h"
14#include "core/arm/arm_interface.h" 14#include "core/arm/arm_interface.h"
15#include "core/core.h" 15#include "core/core.h"
16#include "core/core_cpu.h"
17#include "core/core_timing.h" 16#include "core/core_timing.h"
18#include "core/core_timing_util.h" 17#include "core/core_timing_util.h"
18#include "core/hardware_properties.h"
19#include "core/hle/kernel/errors.h" 19#include "core/hle/kernel/errors.h"
20#include "core/hle/kernel/handle_table.h" 20#include "core/hle/kernel/handle_table.h"
21#include "core/hle/kernel/kernel.h" 21#include "core/hle/kernel/kernel.h"
@@ -32,11 +32,15 @@ bool Thread::ShouldWait(const Thread* thread) const {
32 return status != ThreadStatus::Dead; 32 return status != ThreadStatus::Dead;
33} 33}
34 34
35bool Thread::IsSignaled() const {
36 return status == ThreadStatus::Dead;
37}
38
35void Thread::Acquire(Thread* thread) { 39void Thread::Acquire(Thread* thread) {
36 ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); 40 ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
37} 41}
38 42
39Thread::Thread(KernelCore& kernel) : WaitObject{kernel} {} 43Thread::Thread(KernelCore& kernel) : SynchronizationObject{kernel} {}
40Thread::~Thread() = default; 44Thread::~Thread() = default;
41 45
42void Thread::Stop() { 46void Thread::Stop() {
@@ -46,7 +50,7 @@ void Thread::Stop() {
46 kernel.ThreadWakeupCallbackHandleTable().Close(callback_handle); 50 kernel.ThreadWakeupCallbackHandleTable().Close(callback_handle);
47 callback_handle = 0; 51 callback_handle = 0;
48 SetStatus(ThreadStatus::Dead); 52 SetStatus(ThreadStatus::Dead);
49 WakeupAllWaitingThreads(); 53 Signal();
50 54
51 // Clean up any dangling references in objects that this thread was waiting for 55 // Clean up any dangling references in objects that this thread was waiting for
52 for (auto& wait_object : wait_objects) { 56 for (auto& wait_object : wait_objects) {
@@ -216,7 +220,7 @@ void Thread::SetWaitSynchronizationOutput(s32 output) {
216 context.cpu_registers[1] = output; 220 context.cpu_registers[1] = output;
217} 221}
218 222
219s32 Thread::GetWaitObjectIndex(std::shared_ptr<WaitObject> object) const { 223s32 Thread::GetSynchronizationObjectIndex(std::shared_ptr<SynchronizationObject> object) const {
220 ASSERT_MSG(!wait_objects.empty(), "Thread is not waiting for anything"); 224 ASSERT_MSG(!wait_objects.empty(), "Thread is not waiting for anything");
221 const auto match = std::find(wait_objects.rbegin(), wait_objects.rend(), object); 225 const auto match = std::find(wait_objects.rbegin(), wait_objects.rend(), object);
222 return static_cast<s32>(std::distance(match, wait_objects.rend()) - 1); 226 return static_cast<s32>(std::distance(match, wait_objects.rend()) - 1);
@@ -337,14 +341,16 @@ void Thread::ChangeCore(u32 core, u64 mask) {
337 SetCoreAndAffinityMask(core, mask); 341 SetCoreAndAffinityMask(core, mask);
338} 342}
339 343
340bool Thread::AllWaitObjectsReady() const { 344bool Thread::AllSynchronizationObjectsReady() const {
341 return std::none_of( 345 return std::none_of(wait_objects.begin(), wait_objects.end(),
342 wait_objects.begin(), wait_objects.end(), 346 [this](const std::shared_ptr<SynchronizationObject>& object) {
343 [this](const std::shared_ptr<WaitObject>& object) { return object->ShouldWait(this); }); 347 return object->ShouldWait(this);
348 });
344} 349}
345 350
346bool Thread::InvokeWakeupCallback(ThreadWakeupReason reason, std::shared_ptr<Thread> thread, 351bool Thread::InvokeWakeupCallback(ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
347 std::shared_ptr<WaitObject> object, std::size_t index) { 352 std::shared_ptr<SynchronizationObject> object,
353 std::size_t index) {
348 ASSERT(wakeup_callback); 354 ASSERT(wakeup_callback);
349 return wakeup_callback(reason, std::move(thread), std::move(object), index); 355 return wakeup_callback(reason, std::move(thread), std::move(object), index);
350} 356}
@@ -356,7 +362,7 @@ void Thread::SetActivity(ThreadActivity value) {
356 // Set status if not waiting 362 // Set status if not waiting
357 if (status == ThreadStatus::Ready || status == ThreadStatus::Running) { 363 if (status == ThreadStatus::Ready || status == ThreadStatus::Running) {
358 SetStatus(ThreadStatus::Paused); 364 SetStatus(ThreadStatus::Paused);
359 Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule(); 365 kernel.PrepareReschedule(processor_id);
360 } 366 }
361 } else if (status == ThreadStatus::Paused) { 367 } else if (status == ThreadStatus::Paused) {
362 // Ready to reschedule 368 // Ready to reschedule
@@ -426,7 +432,7 @@ ResultCode Thread::SetCoreAndAffinityMask(s32 new_core, u64 new_affinity_mask) {
426 const s32 old_core = processor_id; 432 const s32 old_core = processor_id;
427 if (processor_id >= 0 && ((affinity_mask >> processor_id) & 1) == 0) { 433 if (processor_id >= 0 && ((affinity_mask >> processor_id) & 1) == 0) {
428 if (static_cast<s32>(ideal_core) < 0) { 434 if (static_cast<s32>(ideal_core) < 0) {
429 processor_id = HighestSetCore(affinity_mask, GlobalScheduler::NUM_CPU_CORES); 435 processor_id = HighestSetCore(affinity_mask, Core::Hardware::NUM_CPU_CORES);
430 } else { 436 } else {
431 processor_id = ideal_core; 437 processor_id = ideal_core;
432 } 438 }
@@ -450,7 +456,7 @@ void Thread::AdjustSchedulingOnStatus(u32 old_flags) {
450 scheduler.Unschedule(current_priority, static_cast<u32>(processor_id), this); 456 scheduler.Unschedule(current_priority, static_cast<u32>(processor_id), this);
451 } 457 }
452 458
453 for (u32 core = 0; core < GlobalScheduler::NUM_CPU_CORES; core++) { 459 for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
454 if (core != static_cast<u32>(processor_id) && ((affinity_mask >> core) & 1) != 0) { 460 if (core != static_cast<u32>(processor_id) && ((affinity_mask >> core) & 1) != 0) {
455 scheduler.Unsuggest(current_priority, core, this); 461 scheduler.Unsuggest(current_priority, core, this);
456 } 462 }
@@ -461,7 +467,7 @@ void Thread::AdjustSchedulingOnStatus(u32 old_flags) {
461 scheduler.Schedule(current_priority, static_cast<u32>(processor_id), this); 467 scheduler.Schedule(current_priority, static_cast<u32>(processor_id), this);
462 } 468 }
463 469
464 for (u32 core = 0; core < GlobalScheduler::NUM_CPU_CORES; core++) { 470 for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
465 if (core != static_cast<u32>(processor_id) && ((affinity_mask >> core) & 1) != 0) { 471 if (core != static_cast<u32>(processor_id) && ((affinity_mask >> core) & 1) != 0) {
466 scheduler.Suggest(current_priority, core, this); 472 scheduler.Suggest(current_priority, core, this);
467 } 473 }
@@ -475,12 +481,12 @@ void Thread::AdjustSchedulingOnPriority(u32 old_priority) {
475 if (GetSchedulingStatus() != ThreadSchedStatus::Runnable) { 481 if (GetSchedulingStatus() != ThreadSchedStatus::Runnable) {
476 return; 482 return;
477 } 483 }
478 auto& scheduler = Core::System::GetInstance().GlobalScheduler(); 484 auto& scheduler = kernel.GlobalScheduler();
479 if (processor_id >= 0) { 485 if (processor_id >= 0) {
480 scheduler.Unschedule(old_priority, static_cast<u32>(processor_id), this); 486 scheduler.Unschedule(old_priority, static_cast<u32>(processor_id), this);
481 } 487 }
482 488
483 for (u32 core = 0; core < GlobalScheduler::NUM_CPU_CORES; core++) { 489 for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
484 if (core != static_cast<u32>(processor_id) && ((affinity_mask >> core) & 1) != 0) { 490 if (core != static_cast<u32>(processor_id) && ((affinity_mask >> core) & 1) != 0) {
485 scheduler.Unsuggest(old_priority, core, this); 491 scheduler.Unsuggest(old_priority, core, this);
486 } 492 }
@@ -497,7 +503,7 @@ void Thread::AdjustSchedulingOnPriority(u32 old_priority) {
497 } 503 }
498 } 504 }
499 505
500 for (u32 core = 0; core < GlobalScheduler::NUM_CPU_CORES; core++) { 506 for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
501 if (core != static_cast<u32>(processor_id) && ((affinity_mask >> core) & 1) != 0) { 507 if (core != static_cast<u32>(processor_id) && ((affinity_mask >> core) & 1) != 0) {
502 scheduler.Suggest(current_priority, core, this); 508 scheduler.Suggest(current_priority, core, this);
503 } 509 }
@@ -507,13 +513,13 @@ void Thread::AdjustSchedulingOnPriority(u32 old_priority) {
507} 513}
508 514
509void Thread::AdjustSchedulingOnAffinity(u64 old_affinity_mask, s32 old_core) { 515void Thread::AdjustSchedulingOnAffinity(u64 old_affinity_mask, s32 old_core) {
510 auto& scheduler = Core::System::GetInstance().GlobalScheduler(); 516 auto& scheduler = kernel.GlobalScheduler();
511 if (GetSchedulingStatus() != ThreadSchedStatus::Runnable || 517 if (GetSchedulingStatus() != ThreadSchedStatus::Runnable ||
512 current_priority >= THREADPRIO_COUNT) { 518 current_priority >= THREADPRIO_COUNT) {
513 return; 519 return;
514 } 520 }
515 521
516 for (u32 core = 0; core < GlobalScheduler::NUM_CPU_CORES; core++) { 522 for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
517 if (((old_affinity_mask >> core) & 1) != 0) { 523 if (((old_affinity_mask >> core) & 1) != 0) {
518 if (core == static_cast<u32>(old_core)) { 524 if (core == static_cast<u32>(old_core)) {
519 scheduler.Unschedule(current_priority, core, this); 525 scheduler.Unschedule(current_priority, core, this);
@@ -523,7 +529,7 @@ void Thread::AdjustSchedulingOnAffinity(u64 old_affinity_mask, s32 old_core) {
523 } 529 }
524 } 530 }
525 531
526 for (u32 core = 0; core < GlobalScheduler::NUM_CPU_CORES; core++) { 532 for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
527 if (((affinity_mask >> core) & 1) != 0) { 533 if (((affinity_mask >> core) & 1) != 0) {
528 if (core == static_cast<u32>(processor_id)) { 534 if (core == static_cast<u32>(processor_id)) {
529 scheduler.Schedule(current_priority, core, this); 535 scheduler.Schedule(current_priority, core, this);
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h
index 3bcf9e137..7a4916318 100644
--- a/src/core/hle/kernel/thread.h
+++ b/src/core/hle/kernel/thread.h
@@ -11,7 +11,7 @@
11#include "common/common_types.h" 11#include "common/common_types.h"
12#include "core/arm/arm_interface.h" 12#include "core/arm/arm_interface.h"
13#include "core/hle/kernel/object.h" 13#include "core/hle/kernel/object.h"
14#include "core/hle/kernel/wait_object.h" 14#include "core/hle/kernel/synchronization_object.h"
15#include "core/hle/result.h" 15#include "core/hle/result.h"
16 16
17namespace Kernel { 17namespace Kernel {
@@ -95,7 +95,7 @@ enum class ThreadSchedMasks : u32 {
95 ForcePauseMask = 0x0070, 95 ForcePauseMask = 0x0070,
96}; 96};
97 97
98class Thread final : public WaitObject { 98class Thread final : public SynchronizationObject {
99public: 99public:
100 explicit Thread(KernelCore& kernel); 100 explicit Thread(KernelCore& kernel);
101 ~Thread() override; 101 ~Thread() override;
@@ -104,11 +104,11 @@ public:
104 104
105 using ThreadContext = Core::ARM_Interface::ThreadContext; 105 using ThreadContext = Core::ARM_Interface::ThreadContext;
106 106
107 using ThreadWaitObjects = std::vector<std::shared_ptr<WaitObject>>; 107 using ThreadSynchronizationObjects = std::vector<std::shared_ptr<SynchronizationObject>>;
108 108
109 using WakeupCallback = 109 using WakeupCallback =
110 std::function<bool(ThreadWakeupReason reason, std::shared_ptr<Thread> thread, 110 std::function<bool(ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
111 std::shared_ptr<WaitObject> object, std::size_t index)>; 111 std::shared_ptr<SynchronizationObject> object, std::size_t index)>;
112 112
113 /** 113 /**
114 * Creates and returns a new thread. The new thread is immediately scheduled 114 * Creates and returns a new thread. The new thread is immediately scheduled
@@ -146,6 +146,7 @@ public:
146 146
147 bool ShouldWait(const Thread* thread) const override; 147 bool ShouldWait(const Thread* thread) const override;
148 void Acquire(Thread* thread) override; 148 void Acquire(Thread* thread) override;
149 bool IsSignaled() const override;
149 150
150 /** 151 /**
151 * Gets the thread's current priority 152 * Gets the thread's current priority
@@ -233,7 +234,7 @@ public:
233 * 234 *
234 * @param object Object to query the index of. 235 * @param object Object to query the index of.
235 */ 236 */
236 s32 GetWaitObjectIndex(std::shared_ptr<WaitObject> object) const; 237 s32 GetSynchronizationObjectIndex(std::shared_ptr<SynchronizationObject> object) const;
237 238
238 /** 239 /**
239 * Stops a thread, invalidating it from further use 240 * Stops a thread, invalidating it from further use
@@ -314,15 +315,15 @@ public:
314 return owner_process; 315 return owner_process;
315 } 316 }
316 317
317 const ThreadWaitObjects& GetWaitObjects() const { 318 const ThreadSynchronizationObjects& GetSynchronizationObjects() const {
318 return wait_objects; 319 return wait_objects;
319 } 320 }
320 321
321 void SetWaitObjects(ThreadWaitObjects objects) { 322 void SetSynchronizationObjects(ThreadSynchronizationObjects objects) {
322 wait_objects = std::move(objects); 323 wait_objects = std::move(objects);
323 } 324 }
324 325
325 void ClearWaitObjects() { 326 void ClearSynchronizationObjects() {
326 for (const auto& waiting_object : wait_objects) { 327 for (const auto& waiting_object : wait_objects) {
327 waiting_object->RemoveWaitingThread(SharedFrom(this)); 328 waiting_object->RemoveWaitingThread(SharedFrom(this));
328 } 329 }
@@ -330,7 +331,7 @@ public:
330 } 331 }
331 332
332 /// Determines whether all the objects this thread is waiting on are ready. 333 /// Determines whether all the objects this thread is waiting on are ready.
333 bool AllWaitObjectsReady() const; 334 bool AllSynchronizationObjectsReady() const;
334 335
335 const MutexWaitingThreads& GetMutexWaitingThreads() const { 336 const MutexWaitingThreads& GetMutexWaitingThreads() const {
336 return wait_mutex_threads; 337 return wait_mutex_threads;
@@ -395,7 +396,7 @@ public:
395 * will cause an assertion to trigger. 396 * will cause an assertion to trigger.
396 */ 397 */
397 bool InvokeWakeupCallback(ThreadWakeupReason reason, std::shared_ptr<Thread> thread, 398 bool InvokeWakeupCallback(ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
398 std::shared_ptr<WaitObject> object, std::size_t index); 399 std::shared_ptr<SynchronizationObject> object, std::size_t index);
399 400
400 u32 GetIdealCore() const { 401 u32 GetIdealCore() const {
401 return ideal_core; 402 return ideal_core;
@@ -494,7 +495,7 @@ private:
494 495
495 /// Objects that the thread is waiting on, in the same order as they were 496 /// Objects that the thread is waiting on, in the same order as they were
496 /// passed to WaitSynchronization. 497 /// passed to WaitSynchronization.
497 ThreadWaitObjects wait_objects; 498 ThreadSynchronizationObjects wait_objects;
498 499
499 /// List of threads that are waiting for a mutex that is held by this thread. 500 /// List of threads that are waiting for a mutex that is held by this thread.
500 MutexWaitingThreads wait_mutex_threads; 501 MutexWaitingThreads wait_mutex_threads;
diff --git a/src/core/hle/kernel/transfer_memory.cpp b/src/core/hle/kernel/transfer_memory.cpp
index f0e73f57b..f2d3f8b49 100644
--- a/src/core/hle/kernel/transfer_memory.cpp
+++ b/src/core/hle/kernel/transfer_memory.cpp
@@ -8,15 +8,23 @@
8#include "core/hle/kernel/shared_memory.h" 8#include "core/hle/kernel/shared_memory.h"
9#include "core/hle/kernel/transfer_memory.h" 9#include "core/hle/kernel/transfer_memory.h"
10#include "core/hle/result.h" 10#include "core/hle/result.h"
11#include "core/memory.h"
11 12
12namespace Kernel { 13namespace Kernel {
13 14
14TransferMemory::TransferMemory(KernelCore& kernel) : Object{kernel} {} 15TransferMemory::TransferMemory(KernelCore& kernel, Memory::Memory& memory)
15TransferMemory::~TransferMemory() = default; 16 : Object{kernel}, memory{memory} {}
16 17
17std::shared_ptr<TransferMemory> TransferMemory::Create(KernelCore& kernel, VAddr base_address, 18TransferMemory::~TransferMemory() {
18 u64 size, MemoryPermission permissions) { 19 // Release memory region when transfer memory is destroyed
19 std::shared_ptr<TransferMemory> transfer_memory{std::make_shared<TransferMemory>(kernel)}; 20 Reset();
21}
22
23std::shared_ptr<TransferMemory> TransferMemory::Create(KernelCore& kernel, Memory::Memory& memory,
24 VAddr base_address, u64 size,
25 MemoryPermission permissions) {
26 std::shared_ptr<TransferMemory> transfer_memory{
27 std::make_shared<TransferMemory>(kernel, memory)};
20 28
21 transfer_memory->base_address = base_address; 29 transfer_memory->base_address = base_address;
22 transfer_memory->memory_size = size; 30 transfer_memory->memory_size = size;
@@ -27,7 +35,7 @@ std::shared_ptr<TransferMemory> TransferMemory::Create(KernelCore& kernel, VAddr
27} 35}
28 36
29const u8* TransferMemory::GetPointer() const { 37const u8* TransferMemory::GetPointer() const {
30 return backing_block.get()->data(); 38 return memory.GetPointer(base_address);
31} 39}
32 40
33u64 TransferMemory::GetSize() const { 41u64 TransferMemory::GetSize() const {
@@ -62,6 +70,52 @@ ResultCode TransferMemory::MapMemory(VAddr address, u64 size, MemoryPermission p
62 return RESULT_SUCCESS; 70 return RESULT_SUCCESS;
63} 71}
64 72
73ResultCode TransferMemory::Reserve() {
74 auto& vm_manager{owner_process->VMManager()};
75 const auto check_range_result{vm_manager.CheckRangeState(
76 base_address, memory_size, MemoryState::FlagTransfer | MemoryState::FlagMemoryPoolAllocated,
77 MemoryState::FlagTransfer | MemoryState::FlagMemoryPoolAllocated, VMAPermission::All,
78 VMAPermission::ReadWrite, MemoryAttribute::Mask, MemoryAttribute::None,
79 MemoryAttribute::IpcAndDeviceMapped)};
80
81 if (check_range_result.Failed()) {
82 return check_range_result.Code();
83 }
84
85 auto [state_, permissions_, attribute] = *check_range_result;
86
87 if (const auto result{vm_manager.ReprotectRange(
88 base_address, memory_size, SharedMemory::ConvertPermissions(owner_permissions))};
89 result.IsError()) {
90 return result;
91 }
92
93 return vm_manager.SetMemoryAttribute(base_address, memory_size, MemoryAttribute::Mask,
94 attribute | MemoryAttribute::Locked);
95}
96
97ResultCode TransferMemory::Reset() {
98 auto& vm_manager{owner_process->VMManager()};
99 if (const auto result{vm_manager.CheckRangeState(
100 base_address, memory_size,
101 MemoryState::FlagTransfer | MemoryState::FlagMemoryPoolAllocated,
102 MemoryState::FlagTransfer | MemoryState::FlagMemoryPoolAllocated, VMAPermission::None,
103 VMAPermission::None, MemoryAttribute::Mask, MemoryAttribute::Locked,
104 MemoryAttribute::IpcAndDeviceMapped)};
105 result.Failed()) {
106 return result.Code();
107 }
108
109 if (const auto result{
110 vm_manager.ReprotectRange(base_address, memory_size, VMAPermission::ReadWrite)};
111 result.IsError()) {
112 return result;
113 }
114
115 return vm_manager.SetMemoryAttribute(base_address, memory_size, MemoryAttribute::Mask,
116 MemoryAttribute::None);
117}
118
65ResultCode TransferMemory::UnmapMemory(VAddr address, u64 size) { 119ResultCode TransferMemory::UnmapMemory(VAddr address, u64 size) {
66 if (memory_size != size) { 120 if (memory_size != size) {
67 return ERR_INVALID_SIZE; 121 return ERR_INVALID_SIZE;
diff --git a/src/core/hle/kernel/transfer_memory.h b/src/core/hle/kernel/transfer_memory.h
index 0a6e15d18..6e388536a 100644
--- a/src/core/hle/kernel/transfer_memory.h
+++ b/src/core/hle/kernel/transfer_memory.h
@@ -11,6 +11,10 @@
11 11
12union ResultCode; 12union ResultCode;
13 13
14namespace Memory {
15class Memory;
16}
17
14namespace Kernel { 18namespace Kernel {
15 19
16class KernelCore; 20class KernelCore;
@@ -26,12 +30,13 @@ enum class MemoryPermission : u32;
26/// 30///
27class TransferMemory final : public Object { 31class TransferMemory final : public Object {
28public: 32public:
29 explicit TransferMemory(KernelCore& kernel); 33 explicit TransferMemory(KernelCore& kernel, Memory::Memory& memory);
30 ~TransferMemory() override; 34 ~TransferMemory() override;
31 35
32 static constexpr HandleType HANDLE_TYPE = HandleType::TransferMemory; 36 static constexpr HandleType HANDLE_TYPE = HandleType::TransferMemory;
33 37
34 static std::shared_ptr<TransferMemory> Create(KernelCore& kernel, VAddr base_address, u64 size, 38 static std::shared_ptr<TransferMemory> Create(KernelCore& kernel, Memory::Memory& memory,
39 VAddr base_address, u64 size,
35 MemoryPermission permissions); 40 MemoryPermission permissions);
36 41
37 TransferMemory(const TransferMemory&) = delete; 42 TransferMemory(const TransferMemory&) = delete;
@@ -80,6 +85,14 @@ public:
80 /// 85 ///
81 ResultCode UnmapMemory(VAddr address, u64 size); 86 ResultCode UnmapMemory(VAddr address, u64 size);
82 87
88 /// Reserves the region to be used for the transfer memory, called after the transfer memory is
89 /// created.
90 ResultCode Reserve();
91
92 /// Resets the region previously used for the transfer memory, called after the transfer memory
93 /// is closed.
94 ResultCode Reset();
95
83private: 96private:
84 /// Memory block backing this instance. 97 /// Memory block backing this instance.
85 std::shared_ptr<PhysicalMemory> backing_block; 98 std::shared_ptr<PhysicalMemory> backing_block;
@@ -98,6 +111,8 @@ private:
98 111
99 /// Whether or not this transfer memory instance has mapped memory. 112 /// Whether or not this transfer memory instance has mapped memory.
100 bool is_mapped = false; 113 bool is_mapped = false;
114
115 Memory::Memory& memory;
101}; 116};
102 117
103} // namespace Kernel 118} // namespace Kernel
diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp
index 0b3500fce..024c22901 100644
--- a/src/core/hle/kernel/vm_manager.cpp
+++ b/src/core/hle/kernel/vm_manager.cpp
@@ -544,7 +544,8 @@ MemoryInfo VMManager::QueryMemory(VAddr address) const {
544 544
545ResultCode VMManager::SetMemoryAttribute(VAddr address, u64 size, MemoryAttribute mask, 545ResultCode VMManager::SetMemoryAttribute(VAddr address, u64 size, MemoryAttribute mask,
546 MemoryAttribute attribute) { 546 MemoryAttribute attribute) {
547 constexpr auto ignore_mask = MemoryAttribute::Uncached | MemoryAttribute::DeviceMapped; 547 constexpr auto ignore_mask =
548 MemoryAttribute::Uncached | MemoryAttribute::DeviceMapped | MemoryAttribute::Locked;
548 constexpr auto attribute_mask = ~ignore_mask; 549 constexpr auto attribute_mask = ~ignore_mask;
549 550
550 const auto result = CheckRangeState( 551 const auto result = CheckRangeState(
diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h
index 850a7ebc3..90b4b006a 100644
--- a/src/core/hle/kernel/vm_manager.h
+++ b/src/core/hle/kernel/vm_manager.h
@@ -98,6 +98,8 @@ enum class MemoryAttribute : u32 {
98 DeviceMapped = 4, 98 DeviceMapped = 4,
99 /// Uncached memory 99 /// Uncached memory
100 Uncached = 8, 100 Uncached = 8,
101
102 IpcAndDeviceMapped = LockedForIPC | DeviceMapped,
101}; 103};
102 104
103constexpr MemoryAttribute operator|(MemoryAttribute lhs, MemoryAttribute rhs) { 105constexpr MemoryAttribute operator|(MemoryAttribute lhs, MemoryAttribute rhs) {
@@ -654,6 +656,35 @@ public:
654 /// is scheduled. 656 /// is scheduled.
655 Common::PageTable page_table{Memory::PAGE_BITS}; 657 Common::PageTable page_table{Memory::PAGE_BITS};
656 658
659 using CheckResults = ResultVal<std::tuple<MemoryState, VMAPermission, MemoryAttribute>>;
660
661 /// Checks if an address range adheres to the specified states provided.
662 ///
663 /// @param address The starting address of the address range.
664 /// @param size The size of the address range.
665 /// @param state_mask The memory state mask.
666 /// @param state The state to compare the individual VMA states against,
667 /// which is done in the form of: (vma.state & state_mask) != state.
668 /// @param permission_mask The memory permissions mask.
669 /// @param permissions The permission to compare the individual VMA permissions against,
670 /// which is done in the form of:
671 /// (vma.permission & permission_mask) != permission.
672 /// @param attribute_mask The memory attribute mask.
673 /// @param attribute The memory attributes to compare the individual VMA attributes
674 /// against, which is done in the form of:
675 /// (vma.attributes & attribute_mask) != attribute.
676 /// @param ignore_mask The memory attributes to ignore during the check.
677 ///
678 /// @returns If successful, returns a tuple containing the memory attributes
679 /// (with ignored bits specified by ignore_mask unset), memory permissions, and
680 /// memory state across the memory range.
681 /// @returns If not successful, returns ERR_INVALID_ADDRESS_STATE.
682 ///
683 CheckResults CheckRangeState(VAddr address, u64 size, MemoryState state_mask, MemoryState state,
684 VMAPermission permission_mask, VMAPermission permissions,
685 MemoryAttribute attribute_mask, MemoryAttribute attribute,
686 MemoryAttribute ignore_mask) const;
687
657private: 688private:
658 using VMAIter = VMAMap::iterator; 689 using VMAIter = VMAMap::iterator;
659 690
@@ -707,35 +738,6 @@ private:
707 /// Clears out the page table 738 /// Clears out the page table
708 void ClearPageTable(); 739 void ClearPageTable();
709 740
710 using CheckResults = ResultVal<std::tuple<MemoryState, VMAPermission, MemoryAttribute>>;
711
712 /// Checks if an address range adheres to the specified states provided.
713 ///
714 /// @param address The starting address of the address range.
715 /// @param size The size of the address range.
716 /// @param state_mask The memory state mask.
717 /// @param state The state to compare the individual VMA states against,
718 /// which is done in the form of: (vma.state & state_mask) != state.
719 /// @param permission_mask The memory permissions mask.
720 /// @param permissions The permission to compare the individual VMA permissions against,
721 /// which is done in the form of:
722 /// (vma.permission & permission_mask) != permission.
723 /// @param attribute_mask The memory attribute mask.
724 /// @param attribute The memory attributes to compare the individual VMA attributes
725 /// against, which is done in the form of:
726 /// (vma.attributes & attribute_mask) != attribute.
727 /// @param ignore_mask The memory attributes to ignore during the check.
728 ///
729 /// @returns If successful, returns a tuple containing the memory attributes
730 /// (with ignored bits specified by ignore_mask unset), memory permissions, and
731 /// memory state across the memory range.
732 /// @returns If not successful, returns ERR_INVALID_ADDRESS_STATE.
733 ///
734 CheckResults CheckRangeState(VAddr address, u64 size, MemoryState state_mask, MemoryState state,
735 VMAPermission permission_mask, VMAPermission permissions,
736 MemoryAttribute attribute_mask, MemoryAttribute attribute,
737 MemoryAttribute ignore_mask) const;
738
739 /// Gets the amount of memory currently mapped (state != Unmapped) in a range. 741 /// Gets the amount of memory currently mapped (state != Unmapped) in a range.
740 ResultVal<std::size_t> SizeOfAllocatedVMAsInRange(VAddr address, std::size_t size) const; 742 ResultVal<std::size_t> SizeOfAllocatedVMAsInRange(VAddr address, std::size_t size) const;
741 743
diff --git a/src/core/hle/kernel/writable_event.cpp b/src/core/hle/kernel/writable_event.cpp
index c9332e3e1..fc2f7c424 100644
--- a/src/core/hle/kernel/writable_event.cpp
+++ b/src/core/hle/kernel/writable_event.cpp
@@ -22,7 +22,6 @@ EventPair WritableEvent::CreateEventPair(KernelCore& kernel, std::string name) {
22 writable_event->name = name + ":Writable"; 22 writable_event->name = name + ":Writable";
23 writable_event->readable = readable_event; 23 writable_event->readable = readable_event;
24 readable_event->name = name + ":Readable"; 24 readable_event->name = name + ":Readable";
25 readable_event->signaled = false;
26 25
27 return {std::move(readable_event), std::move(writable_event)}; 26 return {std::move(readable_event), std::move(writable_event)};
28} 27}
@@ -40,7 +39,7 @@ void WritableEvent::Clear() {
40} 39}
41 40
42bool WritableEvent::IsSignaled() const { 41bool WritableEvent::IsSignaled() const {
43 return readable->signaled; 42 return readable->IsSignaled();
44} 43}
45 44
46} // namespace Kernel 45} // namespace Kernel
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 95aa5d23d..cc978713b 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -709,8 +709,34 @@ void ICommonStateGetter::SetCpuBoostMode(Kernel::HLERequestContext& ctx) {
709 apm_sys->SetCpuBoostMode(ctx); 709 apm_sys->SetCpuBoostMode(ctx);
710} 710}
711 711
712IStorage::IStorage(std::vector<u8> buffer) 712IStorageImpl::~IStorageImpl() = default;
713 : ServiceFramework("IStorage"), buffer(std::move(buffer)) { 713
714class StorageDataImpl final : public IStorageImpl {
715public:
716 explicit StorageDataImpl(std::vector<u8>&& buffer) : buffer{std::move(buffer)} {}
717
718 std::vector<u8>& GetData() override {
719 return buffer;
720 }
721
722 const std::vector<u8>& GetData() const override {
723 return buffer;
724 }
725
726 std::size_t GetSize() const override {
727 return buffer.size();
728 }
729
730private:
731 std::vector<u8> buffer;
732};
733
734IStorage::IStorage(std::vector<u8>&& buffer)
735 : ServiceFramework("IStorage"), impl{std::make_shared<StorageDataImpl>(std::move(buffer))} {
736 Register();
737}
738
739void IStorage::Register() {
714 // clang-format off 740 // clang-format off
715 static const FunctionInfo functions[] = { 741 static const FunctionInfo functions[] = {
716 {0, &IStorage::Open, "Open"}, 742 {0, &IStorage::Open, "Open"},
@@ -723,8 +749,13 @@ IStorage::IStorage(std::vector<u8> buffer)
723 749
724IStorage::~IStorage() = default; 750IStorage::~IStorage() = default;
725 751
726const std::vector<u8>& IStorage::GetData() const { 752void IStorage::Open(Kernel::HLERequestContext& ctx) {
727 return buffer; 753 LOG_DEBUG(Service_AM, "called");
754
755 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
756
757 rb.Push(RESULT_SUCCESS);
758 rb.PushIpcInterface<IStorageAccessor>(*this);
728} 759}
729 760
730void ICommonStateGetter::GetOperationMode(Kernel::HLERequestContext& ctx) { 761void ICommonStateGetter::GetOperationMode(Kernel::HLERequestContext& ctx) {
@@ -816,7 +847,7 @@ private:
816 LOG_DEBUG(Service_AM, "called"); 847 LOG_DEBUG(Service_AM, "called");
817 848
818 IPC::RequestParser rp{ctx}; 849 IPC::RequestParser rp{ctx};
819 applet->GetBroker().PushNormalDataFromGame(*rp.PopIpcInterface<IStorage>()); 850 applet->GetBroker().PushNormalDataFromGame(rp.PopIpcInterface<IStorage>());
820 851
821 IPC::ResponseBuilder rb{ctx, 2}; 852 IPC::ResponseBuilder rb{ctx, 2};
822 rb.Push(RESULT_SUCCESS); 853 rb.Push(RESULT_SUCCESS);
@@ -825,26 +856,25 @@ private:
825 void PopOutData(Kernel::HLERequestContext& ctx) { 856 void PopOutData(Kernel::HLERequestContext& ctx) {
826 LOG_DEBUG(Service_AM, "called"); 857 LOG_DEBUG(Service_AM, "called");
827 858
828 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
829
830 const auto storage = applet->GetBroker().PopNormalDataToGame(); 859 const auto storage = applet->GetBroker().PopNormalDataToGame();
831 if (storage == nullptr) { 860 if (storage == nullptr) {
832 LOG_ERROR(Service_AM, 861 LOG_ERROR(Service_AM,
833 "storage is a nullptr. There is no data in the current normal channel"); 862 "storage is a nullptr. There is no data in the current normal channel");
834 863 IPC::ResponseBuilder rb{ctx, 2};
835 rb.Push(ERR_NO_DATA_IN_CHANNEL); 864 rb.Push(ERR_NO_DATA_IN_CHANNEL);
836 return; 865 return;
837 } 866 }
838 867
868 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
839 rb.Push(RESULT_SUCCESS); 869 rb.Push(RESULT_SUCCESS);
840 rb.PushIpcInterface<IStorage>(std::move(*storage)); 870 rb.PushIpcInterface<IStorage>(std::move(storage));
841 } 871 }
842 872
843 void PushInteractiveInData(Kernel::HLERequestContext& ctx) { 873 void PushInteractiveInData(Kernel::HLERequestContext& ctx) {
844 LOG_DEBUG(Service_AM, "called"); 874 LOG_DEBUG(Service_AM, "called");
845 875
846 IPC::RequestParser rp{ctx}; 876 IPC::RequestParser rp{ctx};
847 applet->GetBroker().PushInteractiveDataFromGame(*rp.PopIpcInterface<IStorage>()); 877 applet->GetBroker().PushInteractiveDataFromGame(rp.PopIpcInterface<IStorage>());
848 878
849 ASSERT(applet->IsInitialized()); 879 ASSERT(applet->IsInitialized());
850 applet->ExecuteInteractive(); 880 applet->ExecuteInteractive();
@@ -857,19 +887,18 @@ private:
857 void PopInteractiveOutData(Kernel::HLERequestContext& ctx) { 887 void PopInteractiveOutData(Kernel::HLERequestContext& ctx) {
858 LOG_DEBUG(Service_AM, "called"); 888 LOG_DEBUG(Service_AM, "called");
859 889
860 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
861
862 const auto storage = applet->GetBroker().PopInteractiveDataToGame(); 890 const auto storage = applet->GetBroker().PopInteractiveDataToGame();
863 if (storage == nullptr) { 891 if (storage == nullptr) {
864 LOG_ERROR(Service_AM, 892 LOG_ERROR(Service_AM,
865 "storage is a nullptr. There is no data in the current interactive channel"); 893 "storage is a nullptr. There is no data in the current interactive channel");
866 894 IPC::ResponseBuilder rb{ctx, 2};
867 rb.Push(ERR_NO_DATA_IN_CHANNEL); 895 rb.Push(ERR_NO_DATA_IN_CHANNEL);
868 return; 896 return;
869 } 897 }
870 898
899 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
871 rb.Push(RESULT_SUCCESS); 900 rb.Push(RESULT_SUCCESS);
872 rb.PushIpcInterface<IStorage>(std::move(*storage)); 901 rb.PushIpcInterface<IStorage>(std::move(storage));
873 } 902 }
874 903
875 void GetPopOutDataEvent(Kernel::HLERequestContext& ctx) { 904 void GetPopOutDataEvent(Kernel::HLERequestContext& ctx) {
@@ -891,15 +920,6 @@ private:
891 std::shared_ptr<Applets::Applet> applet; 920 std::shared_ptr<Applets::Applet> applet;
892}; 921};
893 922
894void IStorage::Open(Kernel::HLERequestContext& ctx) {
895 LOG_DEBUG(Service_AM, "called");
896
897 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
898
899 rb.Push(RESULT_SUCCESS);
900 rb.PushIpcInterface<IStorageAccessor>(*this);
901}
902
903IStorageAccessor::IStorageAccessor(IStorage& storage) 923IStorageAccessor::IStorageAccessor(IStorage& storage)
904 : ServiceFramework("IStorageAccessor"), backing(storage) { 924 : ServiceFramework("IStorageAccessor"), backing(storage) {
905 // clang-format off 925 // clang-format off
@@ -921,7 +941,7 @@ void IStorageAccessor::GetSize(Kernel::HLERequestContext& ctx) {
921 IPC::ResponseBuilder rb{ctx, 4}; 941 IPC::ResponseBuilder rb{ctx, 4};
922 942
923 rb.Push(RESULT_SUCCESS); 943 rb.Push(RESULT_SUCCESS);
924 rb.Push(static_cast<u64>(backing.buffer.size())); 944 rb.Push(static_cast<u64>(backing.GetSize()));
925} 945}
926 946
927void IStorageAccessor::Write(Kernel::HLERequestContext& ctx) { 947void IStorageAccessor::Write(Kernel::HLERequestContext& ctx) {
@@ -932,17 +952,17 @@ void IStorageAccessor::Write(Kernel::HLERequestContext& ctx) {
932 952
933 LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, data.size()); 953 LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, data.size());
934 954
935 if (data.size() > backing.buffer.size() - offset) { 955 if (data.size() > backing.GetSize() - offset) {
936 LOG_ERROR(Service_AM, 956 LOG_ERROR(Service_AM,
937 "offset is out of bounds, backing_buffer_sz={}, data_size={}, offset={}", 957 "offset is out of bounds, backing_buffer_sz={}, data_size={}, offset={}",
938 backing.buffer.size(), data.size(), offset); 958 backing.GetSize(), data.size(), offset);
939 959
940 IPC::ResponseBuilder rb{ctx, 2}; 960 IPC::ResponseBuilder rb{ctx, 2};
941 rb.Push(ERR_SIZE_OUT_OF_BOUNDS); 961 rb.Push(ERR_SIZE_OUT_OF_BOUNDS);
942 return; 962 return;
943 } 963 }
944 964
945 std::memcpy(backing.buffer.data() + offset, data.data(), data.size()); 965 std::memcpy(backing.GetData().data() + offset, data.data(), data.size());
946 966
947 IPC::ResponseBuilder rb{ctx, 2}; 967 IPC::ResponseBuilder rb{ctx, 2};
948 rb.Push(RESULT_SUCCESS); 968 rb.Push(RESULT_SUCCESS);
@@ -956,16 +976,16 @@ void IStorageAccessor::Read(Kernel::HLERequestContext& ctx) {
956 976
957 LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, size); 977 LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, size);
958 978
959 if (size > backing.buffer.size() - offset) { 979 if (size > backing.GetSize() - offset) {
960 LOG_ERROR(Service_AM, "offset is out of bounds, backing_buffer_sz={}, size={}, offset={}", 980 LOG_ERROR(Service_AM, "offset is out of bounds, backing_buffer_sz={}, size={}, offset={}",
961 backing.buffer.size(), size, offset); 981 backing.GetSize(), size, offset);
962 982
963 IPC::ResponseBuilder rb{ctx, 2}; 983 IPC::ResponseBuilder rb{ctx, 2};
964 rb.Push(ERR_SIZE_OUT_OF_BOUNDS); 984 rb.Push(ERR_SIZE_OUT_OF_BOUNDS);
965 return; 985 return;
966 } 986 }
967 987
968 ctx.WriteBuffer(backing.buffer.data() + offset, size); 988 ctx.WriteBuffer(backing.GetData().data() + offset, size);
969 989
970 IPC::ResponseBuilder rb{ctx, 2}; 990 IPC::ResponseBuilder rb{ctx, 2};
971 rb.Push(RESULT_SUCCESS); 991 rb.Push(RESULT_SUCCESS);
@@ -1031,7 +1051,7 @@ void ILibraryAppletCreator::CreateTransferMemoryStorage(Kernel::HLERequestContex
1031 rp.SetCurrentOffset(3); 1051 rp.SetCurrentOffset(3);
1032 const auto handle{rp.Pop<Kernel::Handle>()}; 1052 const auto handle{rp.Pop<Kernel::Handle>()};
1033 1053
1034 const auto transfer_mem = 1054 auto transfer_mem =
1035 system.CurrentProcess()->GetHandleTable().Get<Kernel::TransferMemory>(handle); 1055 system.CurrentProcess()->GetHandleTable().Get<Kernel::TransferMemory>(handle);
1036 1056
1037 if (transfer_mem == nullptr) { 1057 if (transfer_mem == nullptr) {
@@ -1047,7 +1067,7 @@ void ILibraryAppletCreator::CreateTransferMemoryStorage(Kernel::HLERequestContex
1047 1067
1048 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 1068 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
1049 rb.Push(RESULT_SUCCESS); 1069 rb.Push(RESULT_SUCCESS);
1050 rb.PushIpcInterface(std::make_shared<IStorage>(std::move(memory))); 1070 rb.PushIpcInterface<IStorage>(std::move(memory));
1051} 1071}
1052 1072
1053IApplicationFunctions::IApplicationFunctions(Core::System& system_) 1073IApplicationFunctions::IApplicationFunctions(Core::System& system_)
@@ -1189,13 +1209,11 @@ void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) {
1189 u64 build_id{}; 1209 u64 build_id{};
1190 std::memcpy(&build_id, build_id_full.data(), sizeof(u64)); 1210 std::memcpy(&build_id, build_id_full.data(), sizeof(u64));
1191 1211
1192 const auto data = 1212 auto data = backend->GetLaunchParameter({system.CurrentProcess()->GetTitleID(), build_id});
1193 backend->GetLaunchParameter({system.CurrentProcess()->GetTitleID(), build_id});
1194
1195 if (data.has_value()) { 1213 if (data.has_value()) {
1196 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 1214 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
1197 rb.Push(RESULT_SUCCESS); 1215 rb.Push(RESULT_SUCCESS);
1198 rb.PushIpcInterface<AM::IStorage>(*data); 1216 rb.PushIpcInterface<IStorage>(std::move(*data));
1199 launch_popped_application_specific = true; 1217 launch_popped_application_specific = true;
1200 return; 1218 return;
1201 } 1219 }
@@ -1218,7 +1236,7 @@ void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) {
1218 std::vector<u8> buffer(sizeof(LaunchParameterAccountPreselectedUser)); 1236 std::vector<u8> buffer(sizeof(LaunchParameterAccountPreselectedUser));
1219 std::memcpy(buffer.data(), &params, buffer.size()); 1237 std::memcpy(buffer.data(), &params, buffer.size());
1220 1238
1221 rb.PushIpcInterface<AM::IStorage>(buffer); 1239 rb.PushIpcInterface<IStorage>(std::move(buffer));
1222 launch_popped_account_preselect = true; 1240 launch_popped_account_preselect = true;
1223 return; 1241 return;
1224 } 1242 }
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index 448817be9..0b9a4332d 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -12,7 +12,8 @@
12 12
13namespace Kernel { 13namespace Kernel {
14class KernelCore; 14class KernelCore;
15} 15class TransferMemory;
16} // namespace Kernel
16 17
17namespace Service::NVFlinger { 18namespace Service::NVFlinger {
18class NVFlinger; 19class NVFlinger;
@@ -188,19 +189,36 @@ private:
188 std::shared_ptr<AppletMessageQueue> msg_queue; 189 std::shared_ptr<AppletMessageQueue> msg_queue;
189}; 190};
190 191
192class IStorageImpl {
193public:
194 virtual ~IStorageImpl();
195 virtual std::vector<u8>& GetData() = 0;
196 virtual const std::vector<u8>& GetData() const = 0;
197 virtual std::size_t GetSize() const = 0;
198};
199
191class IStorage final : public ServiceFramework<IStorage> { 200class IStorage final : public ServiceFramework<IStorage> {
192public: 201public:
193 explicit IStorage(std::vector<u8> buffer); 202 explicit IStorage(std::vector<u8>&& buffer);
194 ~IStorage() override; 203 ~IStorage() override;
195 204
196 const std::vector<u8>& GetData() const; 205 std::vector<u8>& GetData() {
206 return impl->GetData();
207 }
208
209 const std::vector<u8>& GetData() const {
210 return impl->GetData();
211 }
212
213 std::size_t GetSize() const {
214 return impl->GetSize();
215 }
197 216
198private: 217private:
218 void Register();
199 void Open(Kernel::HLERequestContext& ctx); 219 void Open(Kernel::HLERequestContext& ctx);
200 220
201 std::vector<u8> buffer; 221 std::shared_ptr<IStorageImpl> impl;
202
203 friend class IStorageAccessor;
204}; 222};
205 223
206class IStorageAccessor final : public ServiceFramework<IStorageAccessor> { 224class IStorageAccessor final : public ServiceFramework<IStorageAccessor> {
diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp
index 92f995f8f..c3261f3e6 100644
--- a/src/core/hle/service/am/applets/applets.cpp
+++ b/src/core/hle/service/am/applets/applets.cpp
@@ -50,16 +50,17 @@ AppletDataBroker::RawChannelData AppletDataBroker::PeekDataToAppletForDebug() co
50 return {std::move(out_normal), std::move(out_interactive)}; 50 return {std::move(out_normal), std::move(out_interactive)};
51} 51}
52 52
53std::unique_ptr<IStorage> AppletDataBroker::PopNormalDataToGame() { 53std::shared_ptr<IStorage> AppletDataBroker::PopNormalDataToGame() {
54 if (out_channel.empty()) 54 if (out_channel.empty())
55 return nullptr; 55 return nullptr;
56 56
57 auto out = std::move(out_channel.front()); 57 auto out = std::move(out_channel.front());
58 out_channel.pop_front(); 58 out_channel.pop_front();
59 pop_out_data_event.writable->Clear();
59 return out; 60 return out;
60} 61}
61 62
62std::unique_ptr<IStorage> AppletDataBroker::PopNormalDataToApplet() { 63std::shared_ptr<IStorage> AppletDataBroker::PopNormalDataToApplet() {
63 if (in_channel.empty()) 64 if (in_channel.empty())
64 return nullptr; 65 return nullptr;
65 66
@@ -68,16 +69,17 @@ std::unique_ptr<IStorage> AppletDataBroker::PopNormalDataToApplet() {
68 return out; 69 return out;
69} 70}
70 71
71std::unique_ptr<IStorage> AppletDataBroker::PopInteractiveDataToGame() { 72std::shared_ptr<IStorage> AppletDataBroker::PopInteractiveDataToGame() {
72 if (out_interactive_channel.empty()) 73 if (out_interactive_channel.empty())
73 return nullptr; 74 return nullptr;
74 75
75 auto out = std::move(out_interactive_channel.front()); 76 auto out = std::move(out_interactive_channel.front());
76 out_interactive_channel.pop_front(); 77 out_interactive_channel.pop_front();
78 pop_interactive_out_data_event.writable->Clear();
77 return out; 79 return out;
78} 80}
79 81
80std::unique_ptr<IStorage> AppletDataBroker::PopInteractiveDataToApplet() { 82std::shared_ptr<IStorage> AppletDataBroker::PopInteractiveDataToApplet() {
81 if (in_interactive_channel.empty()) 83 if (in_interactive_channel.empty())
82 return nullptr; 84 return nullptr;
83 85
@@ -86,21 +88,21 @@ std::unique_ptr<IStorage> AppletDataBroker::PopInteractiveDataToApplet() {
86 return out; 88 return out;
87} 89}
88 90
89void AppletDataBroker::PushNormalDataFromGame(IStorage storage) { 91void AppletDataBroker::PushNormalDataFromGame(std::shared_ptr<IStorage>&& storage) {
90 in_channel.push_back(std::make_unique<IStorage>(storage)); 92 in_channel.emplace_back(std::move(storage));
91} 93}
92 94
93void AppletDataBroker::PushNormalDataFromApplet(IStorage storage) { 95void AppletDataBroker::PushNormalDataFromApplet(std::shared_ptr<IStorage>&& storage) {
94 out_channel.push_back(std::make_unique<IStorage>(storage)); 96 out_channel.emplace_back(std::move(storage));
95 pop_out_data_event.writable->Signal(); 97 pop_out_data_event.writable->Signal();
96} 98}
97 99
98void AppletDataBroker::PushInteractiveDataFromGame(IStorage storage) { 100void AppletDataBroker::PushInteractiveDataFromGame(std::shared_ptr<IStorage>&& storage) {
99 in_interactive_channel.push_back(std::make_unique<IStorage>(storage)); 101 in_interactive_channel.emplace_back(std::move(storage));
100} 102}
101 103
102void AppletDataBroker::PushInteractiveDataFromApplet(IStorage storage) { 104void AppletDataBroker::PushInteractiveDataFromApplet(std::shared_ptr<IStorage>&& storage) {
103 out_interactive_channel.push_back(std::make_unique<IStorage>(storage)); 105 out_interactive_channel.emplace_back(std::move(storage));
104 pop_interactive_out_data_event.writable->Signal(); 106 pop_interactive_out_data_event.writable->Signal();
105} 107}
106 108
diff --git a/src/core/hle/service/am/applets/applets.h b/src/core/hle/service/am/applets/applets.h
index 16e61fc6f..e75be86a2 100644
--- a/src/core/hle/service/am/applets/applets.h
+++ b/src/core/hle/service/am/applets/applets.h
@@ -72,17 +72,17 @@ public:
72 // Retrieves but does not pop the data sent to applet. 72 // Retrieves but does not pop the data sent to applet.
73 RawChannelData PeekDataToAppletForDebug() const; 73 RawChannelData PeekDataToAppletForDebug() const;
74 74
75 std::unique_ptr<IStorage> PopNormalDataToGame(); 75 std::shared_ptr<IStorage> PopNormalDataToGame();
76 std::unique_ptr<IStorage> PopNormalDataToApplet(); 76 std::shared_ptr<IStorage> PopNormalDataToApplet();
77 77
78 std::unique_ptr<IStorage> PopInteractiveDataToGame(); 78 std::shared_ptr<IStorage> PopInteractiveDataToGame();
79 std::unique_ptr<IStorage> PopInteractiveDataToApplet(); 79 std::shared_ptr<IStorage> PopInteractiveDataToApplet();
80 80
81 void PushNormalDataFromGame(IStorage storage); 81 void PushNormalDataFromGame(std::shared_ptr<IStorage>&& storage);
82 void PushNormalDataFromApplet(IStorage storage); 82 void PushNormalDataFromApplet(std::shared_ptr<IStorage>&& storage);
83 83
84 void PushInteractiveDataFromGame(IStorage storage); 84 void PushInteractiveDataFromGame(std::shared_ptr<IStorage>&& storage);
85 void PushInteractiveDataFromApplet(IStorage storage); 85 void PushInteractiveDataFromApplet(std::shared_ptr<IStorage>&& storage);
86 86
87 void SignalStateChanged() const; 87 void SignalStateChanged() const;
88 88
@@ -94,16 +94,16 @@ private:
94 // Queues are named from applet's perspective 94 // Queues are named from applet's perspective
95 95
96 // PopNormalDataToApplet and PushNormalDataFromGame 96 // PopNormalDataToApplet and PushNormalDataFromGame
97 std::deque<std::unique_ptr<IStorage>> in_channel; 97 std::deque<std::shared_ptr<IStorage>> in_channel;
98 98
99 // PopNormalDataToGame and PushNormalDataFromApplet 99 // PopNormalDataToGame and PushNormalDataFromApplet
100 std::deque<std::unique_ptr<IStorage>> out_channel; 100 std::deque<std::shared_ptr<IStorage>> out_channel;
101 101
102 // PopInteractiveDataToApplet and PushInteractiveDataFromGame 102 // PopInteractiveDataToApplet and PushInteractiveDataFromGame
103 std::deque<std::unique_ptr<IStorage>> in_interactive_channel; 103 std::deque<std::shared_ptr<IStorage>> in_interactive_channel;
104 104
105 // PopInteractiveDataToGame and PushInteractiveDataFromApplet 105 // PopInteractiveDataToGame and PushInteractiveDataFromApplet
106 std::deque<std::unique_ptr<IStorage>> out_interactive_channel; 106 std::deque<std::shared_ptr<IStorage>> out_interactive_channel;
107 107
108 Kernel::EventPair state_changed_event; 108 Kernel::EventPair state_changed_event;
109 109
diff --git a/src/core/hle/service/am/applets/error.cpp b/src/core/hle/service/am/applets/error.cpp
index eab0d42c9..f12fd7f89 100644
--- a/src/core/hle/service/am/applets/error.cpp
+++ b/src/core/hle/service/am/applets/error.cpp
@@ -186,7 +186,7 @@ void Error::Execute() {
186 186
187void Error::DisplayCompleted() { 187void Error::DisplayCompleted() {
188 complete = true; 188 complete = true;
189 broker.PushNormalDataFromApplet(IStorage{{}}); 189 broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::vector<u8>{}));
190 broker.SignalStateChanged(); 190 broker.SignalStateChanged();
191} 191}
192 192
diff --git a/src/core/hle/service/am/applets/general_backend.cpp b/src/core/hle/service/am/applets/general_backend.cpp
index 328438a1d..104501ac5 100644
--- a/src/core/hle/service/am/applets/general_backend.cpp
+++ b/src/core/hle/service/am/applets/general_backend.cpp
@@ -20,7 +20,7 @@ namespace Service::AM::Applets {
20constexpr ResultCode ERROR_INVALID_PIN{ErrorModule::PCTL, 221}; 20constexpr ResultCode ERROR_INVALID_PIN{ErrorModule::PCTL, 221};
21 21
22static void LogCurrentStorage(AppletDataBroker& broker, std::string_view prefix) { 22static void LogCurrentStorage(AppletDataBroker& broker, std::string_view prefix) {
23 std::unique_ptr<IStorage> storage = broker.PopNormalDataToApplet(); 23 std::shared_ptr<IStorage> storage = broker.PopNormalDataToApplet();
24 for (; storage != nullptr; storage = broker.PopNormalDataToApplet()) { 24 for (; storage != nullptr; storage = broker.PopNormalDataToApplet()) {
25 const auto data = storage->GetData(); 25 const auto data = storage->GetData();
26 LOG_INFO(Service_AM, 26 LOG_INFO(Service_AM,
@@ -148,7 +148,7 @@ void Auth::AuthFinished(bool successful) {
148 std::vector<u8> out(sizeof(Return)); 148 std::vector<u8> out(sizeof(Return));
149 std::memcpy(out.data(), &return_, sizeof(Return)); 149 std::memcpy(out.data(), &return_, sizeof(Return));
150 150
151 broker.PushNormalDataFromApplet(IStorage{out}); 151 broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::move(out)));
152 broker.SignalStateChanged(); 152 broker.SignalStateChanged();
153} 153}
154 154
@@ -198,7 +198,7 @@ void PhotoViewer::Execute() {
198} 198}
199 199
200void PhotoViewer::ViewFinished() { 200void PhotoViewer::ViewFinished() {
201 broker.PushNormalDataFromApplet(IStorage{{}}); 201 broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::vector<u8>{}));
202 broker.SignalStateChanged(); 202 broker.SignalStateChanged();
203} 203}
204 204
@@ -234,8 +234,8 @@ void StubApplet::ExecuteInteractive() {
234 LOG_WARNING(Service_AM, "called (STUBBED)"); 234 LOG_WARNING(Service_AM, "called (STUBBED)");
235 LogCurrentStorage(broker, "ExecuteInteractive"); 235 LogCurrentStorage(broker, "ExecuteInteractive");
236 236
237 broker.PushNormalDataFromApplet(IStorage{std::vector<u8>(0x1000)}); 237 broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::vector<u8>(0x1000)));
238 broker.PushInteractiveDataFromApplet(IStorage{std::vector<u8>(0x1000)}); 238 broker.PushInteractiveDataFromApplet(std::make_shared<IStorage>(std::vector<u8>(0x1000)));
239 broker.SignalStateChanged(); 239 broker.SignalStateChanged();
240} 240}
241 241
@@ -243,8 +243,8 @@ void StubApplet::Execute() {
243 LOG_WARNING(Service_AM, "called (STUBBED)"); 243 LOG_WARNING(Service_AM, "called (STUBBED)");
244 LogCurrentStorage(broker, "Execute"); 244 LogCurrentStorage(broker, "Execute");
245 245
246 broker.PushNormalDataFromApplet(IStorage{std::vector<u8>(0x1000)}); 246 broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::vector<u8>(0x1000)));
247 broker.PushInteractiveDataFromApplet(IStorage{std::vector<u8>(0x1000)}); 247 broker.PushInteractiveDataFromApplet(std::make_shared<IStorage>(std::vector<u8>(0x1000)));
248 broker.SignalStateChanged(); 248 broker.SignalStateChanged();
249} 249}
250 250
diff --git a/src/core/hle/service/am/applets/profile_select.cpp b/src/core/hle/service/am/applets/profile_select.cpp
index 3eba696ca..70cc23552 100644
--- a/src/core/hle/service/am/applets/profile_select.cpp
+++ b/src/core/hle/service/am/applets/profile_select.cpp
@@ -50,7 +50,7 @@ void ProfileSelect::ExecuteInteractive() {
50 50
51void ProfileSelect::Execute() { 51void ProfileSelect::Execute() {
52 if (complete) { 52 if (complete) {
53 broker.PushNormalDataFromApplet(IStorage{final_data}); 53 broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::move(final_data)));
54 return; 54 return;
55 } 55 }
56 56
@@ -71,7 +71,7 @@ void ProfileSelect::SelectionComplete(std::optional<Common::UUID> uuid) {
71 71
72 final_data = std::vector<u8>(sizeof(UserSelectionOutput)); 72 final_data = std::vector<u8>(sizeof(UserSelectionOutput));
73 std::memcpy(final_data.data(), &output, final_data.size()); 73 std::memcpy(final_data.data(), &output, final_data.size());
74 broker.PushNormalDataFromApplet(IStorage{final_data}); 74 broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::move(final_data)));
75 broker.SignalStateChanged(); 75 broker.SignalStateChanged();
76} 76}
77 77
diff --git a/src/core/hle/service/am/applets/software_keyboard.cpp b/src/core/hle/service/am/applets/software_keyboard.cpp
index 748559cd0..54e63c138 100644
--- a/src/core/hle/service/am/applets/software_keyboard.cpp
+++ b/src/core/hle/service/am/applets/software_keyboard.cpp
@@ -102,7 +102,8 @@ void SoftwareKeyboard::ExecuteInteractive() {
102 102
103void SoftwareKeyboard::Execute() { 103void SoftwareKeyboard::Execute() {
104 if (complete) { 104 if (complete) {
105 broker.PushNormalDataFromApplet(IStorage{final_data}); 105 broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::move(final_data)));
106 broker.SignalStateChanged();
106 return; 107 return;
107 } 108 }
108 109
@@ -119,7 +120,7 @@ void SoftwareKeyboard::WriteText(std::optional<std::u16string> text) {
119 std::vector<u8> output_sub(SWKBD_OUTPUT_BUFFER_SIZE); 120 std::vector<u8> output_sub(SWKBD_OUTPUT_BUFFER_SIZE);
120 121
121 if (config.utf_8) { 122 if (config.utf_8) {
122 const u64 size = text->size() + 8; 123 const u64 size = text->size() + sizeof(u64);
123 const auto new_text = Common::UTF16ToUTF8(*text); 124 const auto new_text = Common::UTF16ToUTF8(*text);
124 125
125 std::memcpy(output_sub.data(), &size, sizeof(u64)); 126 std::memcpy(output_sub.data(), &size, sizeof(u64));
@@ -130,7 +131,7 @@ void SoftwareKeyboard::WriteText(std::optional<std::u16string> text) {
130 std::memcpy(output_main.data() + 4, new_text.data(), 131 std::memcpy(output_main.data() + 4, new_text.data(),
131 std::min(new_text.size(), SWKBD_OUTPUT_BUFFER_SIZE - 4)); 132 std::min(new_text.size(), SWKBD_OUTPUT_BUFFER_SIZE - 4));
132 } else { 133 } else {
133 const u64 size = text->size() * 2 + 8; 134 const u64 size = text->size() * 2 + sizeof(u64);
134 std::memcpy(output_sub.data(), &size, sizeof(u64)); 135 std::memcpy(output_sub.data(), &size, sizeof(u64));
135 std::memcpy(output_sub.data() + 8, text->data(), 136 std::memcpy(output_sub.data() + 8, text->data(),
136 std::min(text->size() * 2, SWKBD_OUTPUT_BUFFER_SIZE - 8)); 137 std::min(text->size() * 2, SWKBD_OUTPUT_BUFFER_SIZE - 8));
@@ -144,15 +145,15 @@ void SoftwareKeyboard::WriteText(std::optional<std::u16string> text) {
144 final_data = output_main; 145 final_data = output_main;
145 146
146 if (complete) { 147 if (complete) {
147 broker.PushNormalDataFromApplet(IStorage{output_main}); 148 broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::move(output_main)));
148 broker.SignalStateChanged(); 149 broker.SignalStateChanged();
149 } else { 150 } else {
150 broker.PushInteractiveDataFromApplet(IStorage{output_sub}); 151 broker.PushInteractiveDataFromApplet(std::make_shared<IStorage>(std::move(output_sub)));
151 } 152 }
152 } else { 153 } else {
153 output_main[0] = 1; 154 output_main[0] = 1;
154 complete = true; 155 complete = true;
155 broker.PushNormalDataFromApplet(IStorage{output_main}); 156 broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::move(output_main)));
156 broker.SignalStateChanged(); 157 broker.SignalStateChanged();
157 } 158 }
158} 159}
diff --git a/src/core/hle/service/am/applets/web_browser.cpp b/src/core/hle/service/am/applets/web_browser.cpp
index 5546ef6e8..12443c910 100644
--- a/src/core/hle/service/am/applets/web_browser.cpp
+++ b/src/core/hle/service/am/applets/web_browser.cpp
@@ -284,7 +284,7 @@ void WebBrowser::Finalize() {
284 std::vector<u8> data(sizeof(WebCommonReturnValue)); 284 std::vector<u8> data(sizeof(WebCommonReturnValue));
285 std::memcpy(data.data(), &out, sizeof(WebCommonReturnValue)); 285 std::memcpy(data.data(), &out, sizeof(WebCommonReturnValue));
286 286
287 broker.PushNormalDataFromApplet(IStorage{data}); 287 broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::move(data)));
288 broker.SignalStateChanged(); 288 broker.SignalStateChanged();
289 289
290 if (!temporary_dir.empty() && FileUtil::IsDirectory(temporary_dir)) { 290 if (!temporary_dir.empty() && FileUtil::IsDirectory(temporary_dir)) {
diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp
index cb839e4a2..d19513cbb 100644
--- a/src/core/hle/service/audio/hwopus.cpp
+++ b/src/core/hle/service/audio/hwopus.cpp
@@ -170,8 +170,10 @@ public:
170 {3, nullptr, "SetContextForMultiStream"}, 170 {3, nullptr, "SetContextForMultiStream"},
171 {4, &IHardwareOpusDecoderManager::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"}, 171 {4, &IHardwareOpusDecoderManager::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"},
172 {5, nullptr, "DecodeInterleavedForMultiStreamWithPerfOld"}, 172 {5, nullptr, "DecodeInterleavedForMultiStreamWithPerfOld"},
173 {6, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleaved"}, 173 {6, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleavedWithPerfAndResetOld"},
174 {7, nullptr, "DecodeInterleavedForMultiStream"}, 174 {7, nullptr, "DecodeInterleavedForMultiStreamWithPerfAndResetOld"},
175 {8, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleaved"},
176 {9, nullptr, "DecodeInterleavedForMultiStream"},
175 }; 177 };
176 // clang-format on 178 // clang-format on
177 179
diff --git a/src/core/hle/service/bcat/backend/backend.cpp b/src/core/hle/service/bcat/backend/backend.cpp
index 6f5ea095a..def3410cc 100644
--- a/src/core/hle/service/bcat/backend/backend.cpp
+++ b/src/core/hle/service/bcat/backend/backend.cpp
@@ -117,13 +117,13 @@ bool NullBackend::SynchronizeDirectory(TitleIDVersion title, std::string name,
117} 117}
118 118
119bool NullBackend::Clear(u64 title_id) { 119bool NullBackend::Clear(u64 title_id) {
120 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}"); 120 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", title_id);
121 121
122 return true; 122 return true;
123} 123}
124 124
125void NullBackend::SetPassphrase(u64 title_id, const Passphrase& passphrase) { 125void NullBackend::SetPassphrase(u64 title_id, const Passphrase& passphrase) {
126 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase = {}", title_id, 126 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase={}", title_id,
127 Common::HexToString(passphrase)); 127 Common::HexToString(passphrase));
128} 128}
129 129
diff --git a/src/core/hle/service/bcat/backend/boxcat.cpp b/src/core/hle/service/bcat/backend/boxcat.cpp
index 67e39a5c4..f589864ee 100644
--- a/src/core/hle/service/bcat/backend/boxcat.cpp
+++ b/src/core/hle/service/bcat/backend/boxcat.cpp
@@ -200,7 +200,8 @@ private:
200 DownloadResult DownloadInternal(const std::string& resolved_path, u32 timeout_seconds, 200 DownloadResult DownloadInternal(const std::string& resolved_path, u32 timeout_seconds,
201 const std::string& content_type_name) { 201 const std::string& content_type_name) {
202 if (client == nullptr) { 202 if (client == nullptr) {
203 client = std::make_unique<httplib::SSLClient>(BOXCAT_HOSTNAME, PORT, timeout_seconds); 203 client = std::make_unique<httplib::SSLClient>(BOXCAT_HOSTNAME, PORT);
204 client->set_timeout_sec(timeout_seconds);
204 } 205 }
205 206
206 httplib::Headers headers{ 207 httplib::Headers headers{
@@ -448,8 +449,8 @@ std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title)
448 449
449Boxcat::StatusResult Boxcat::GetStatus(std::optional<std::string>& global, 450Boxcat::StatusResult Boxcat::GetStatus(std::optional<std::string>& global,
450 std::map<std::string, EventStatus>& games) { 451 std::map<std::string, EventStatus>& games) {
451 httplib::SSLClient client{BOXCAT_HOSTNAME, static_cast<int>(PORT), 452 httplib::SSLClient client{BOXCAT_HOSTNAME, static_cast<int>(PORT)};
452 static_cast<int>(TIMEOUT_SECONDS)}; 453 client.set_timeout_sec(static_cast<int>(TIMEOUT_SECONDS));
453 454
454 httplib::Headers headers{ 455 httplib::Headers headers{
455 {std::string("Game-Assets-API-Version"), std::string(BOXCAT_API_VERSION)}, 456 {std::string("Game-Assets-API-Version"), std::string(BOXCAT_API_VERSION)},
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp
index 55d62fc5e..e6811d5b5 100644
--- a/src/core/hle/service/filesystem/fsp_srv.cpp
+++ b/src/core/hle/service/filesystem/fsp_srv.cpp
@@ -420,7 +420,7 @@ public:
420 return; 420 return;
421 } 421 }
422 422
423 IFile file(result.Unwrap()); 423 auto file = std::make_shared<IFile>(result.Unwrap());
424 424
425 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 425 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
426 rb.Push(RESULT_SUCCESS); 426 rb.Push(RESULT_SUCCESS);
@@ -445,7 +445,7 @@ public:
445 return; 445 return;
446 } 446 }
447 447
448 IDirectory directory(result.Unwrap()); 448 auto directory = std::make_shared<IDirectory>(result.Unwrap());
449 449
450 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 450 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
451 rb.Push(RESULT_SUCCESS); 451 rb.Push(RESULT_SUCCESS);
@@ -794,8 +794,8 @@ void FSP_SRV::OpenFileSystemWithPatch(Kernel::HLERequestContext& ctx) {
794void FSP_SRV::OpenSdCardFileSystem(Kernel::HLERequestContext& ctx) { 794void FSP_SRV::OpenSdCardFileSystem(Kernel::HLERequestContext& ctx) {
795 LOG_DEBUG(Service_FS, "called"); 795 LOG_DEBUG(Service_FS, "called");
796 796
797 IFileSystem filesystem(fsc.OpenSDMC().Unwrap(), 797 auto filesystem = std::make_shared<IFileSystem>(
798 SizeGetter::FromStorageId(fsc, FileSys::StorageId::SdCard)); 798 fsc.OpenSDMC().Unwrap(), SizeGetter::FromStorageId(fsc, FileSys::StorageId::SdCard));
799 799
800 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 800 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
801 rb.Push(RESULT_SUCCESS); 801 rb.Push(RESULT_SUCCESS);
@@ -846,7 +846,8 @@ void FSP_SRV::OpenSaveDataFileSystem(Kernel::HLERequestContext& ctx) {
846 id = FileSys::StorageId::NandSystem; 846 id = FileSys::StorageId::NandSystem;
847 } 847 }
848 848
849 IFileSystem filesystem(std::move(dir.Unwrap()), SizeGetter::FromStorageId(fsc, id)); 849 auto filesystem =
850 std::make_shared<IFileSystem>(std::move(dir.Unwrap()), SizeGetter::FromStorageId(fsc, id));
850 851
851 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 852 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
852 rb.Push(RESULT_SUCCESS); 853 rb.Push(RESULT_SUCCESS);
@@ -898,7 +899,7 @@ void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) {
898 return; 899 return;
899 } 900 }
900 901
901 IStorage storage(std::move(romfs.Unwrap())); 902 auto storage = std::make_shared<IStorage>(std::move(romfs.Unwrap()));
902 903
903 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 904 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
904 rb.Push(RESULT_SUCCESS); 905 rb.Push(RESULT_SUCCESS);
@@ -937,7 +938,8 @@ void FSP_SRV::OpenDataStorageByDataId(Kernel::HLERequestContext& ctx) {
937 938
938 FileSys::PatchManager pm{title_id}; 939 FileSys::PatchManager pm{title_id};
939 940
940 IStorage storage(pm.PatchRomFS(std::move(data.Unwrap()), 0, FileSys::ContentRecordType::Data)); 941 auto storage = std::make_shared<IStorage>(
942 pm.PatchRomFS(std::move(data.Unwrap()), 0, FileSys::ContentRecordType::Data));
941 943
942 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 944 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
943 rb.Push(RESULT_SUCCESS); 945 rb.Push(RESULT_SUCCESS);
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index 4d952adc0..15c09f04c 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -250,6 +250,10 @@ void Controller_NPad::RequestPadStateUpdate(u32 npad_id) {
250 auto& rstick_entry = npad_pad_states[controller_idx].r_stick; 250 auto& rstick_entry = npad_pad_states[controller_idx].r_stick;
251 const auto& button_state = buttons[controller_idx]; 251 const auto& button_state = buttons[controller_idx];
252 const auto& analog_state = sticks[controller_idx]; 252 const auto& analog_state = sticks[controller_idx];
253 const auto [stick_l_x_f, stick_l_y_f] =
254 analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetStatus();
255 const auto [stick_r_x_f, stick_r_y_f] =
256 analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)]->GetStatus();
253 257
254 using namespace Settings::NativeButton; 258 using namespace Settings::NativeButton;
255 pad_state.a.Assign(button_state[A - BUTTON_HID_BEGIN]->GetStatus()); 259 pad_state.a.Assign(button_state[A - BUTTON_HID_BEGIN]->GetStatus());
@@ -270,23 +274,32 @@ void Controller_NPad::RequestPadStateUpdate(u32 npad_id) {
270 pad_state.d_right.Assign(button_state[DRight - BUTTON_HID_BEGIN]->GetStatus()); 274 pad_state.d_right.Assign(button_state[DRight - BUTTON_HID_BEGIN]->GetStatus());
271 pad_state.d_down.Assign(button_state[DDown - BUTTON_HID_BEGIN]->GetStatus()); 275 pad_state.d_down.Assign(button_state[DDown - BUTTON_HID_BEGIN]->GetStatus());
272 276
273 pad_state.l_stick_left.Assign(button_state[LStick_Left - BUTTON_HID_BEGIN]->GetStatus()); 277 pad_state.l_stick_right.Assign(
274 pad_state.l_stick_up.Assign(button_state[LStick_Up - BUTTON_HID_BEGIN]->GetStatus()); 278 analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetAnalogDirectionStatus(
275 pad_state.l_stick_right.Assign(button_state[LStick_Right - BUTTON_HID_BEGIN]->GetStatus()); 279 Input::AnalogDirection::RIGHT));
276 pad_state.l_stick_down.Assign(button_state[LStick_Down - BUTTON_HID_BEGIN]->GetStatus()); 280 pad_state.l_stick_left.Assign(
277 281 analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetAnalogDirectionStatus(
278 pad_state.r_stick_left.Assign(button_state[RStick_Left - BUTTON_HID_BEGIN]->GetStatus()); 282 Input::AnalogDirection::LEFT));
279 pad_state.r_stick_up.Assign(button_state[RStick_Up - BUTTON_HID_BEGIN]->GetStatus()); 283 pad_state.l_stick_up.Assign(
280 pad_state.r_stick_right.Assign(button_state[RStick_Right - BUTTON_HID_BEGIN]->GetStatus()); 284 analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetAnalogDirectionStatus(
281 pad_state.r_stick_down.Assign(button_state[RStick_Down - BUTTON_HID_BEGIN]->GetStatus()); 285 Input::AnalogDirection::UP));
286 pad_state.l_stick_down.Assign(
287 analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetAnalogDirectionStatus(
288 Input::AnalogDirection::DOWN));
289
290 pad_state.r_stick_up.Assign(analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)]
291 ->GetAnalogDirectionStatus(Input::AnalogDirection::RIGHT));
292 pad_state.r_stick_left.Assign(analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)]
293 ->GetAnalogDirectionStatus(Input::AnalogDirection::LEFT));
294 pad_state.r_stick_right.Assign(
295 analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)]
296 ->GetAnalogDirectionStatus(Input::AnalogDirection::UP));
297 pad_state.r_stick_down.Assign(analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)]
298 ->GetAnalogDirectionStatus(Input::AnalogDirection::DOWN));
282 299
283 pad_state.left_sl.Assign(button_state[SL - BUTTON_HID_BEGIN]->GetStatus()); 300 pad_state.left_sl.Assign(button_state[SL - BUTTON_HID_BEGIN]->GetStatus());
284 pad_state.left_sr.Assign(button_state[SR - BUTTON_HID_BEGIN]->GetStatus()); 301 pad_state.left_sr.Assign(button_state[SR - BUTTON_HID_BEGIN]->GetStatus());
285 302
286 const auto [stick_l_x_f, stick_l_y_f] =
287 analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetStatus();
288 const auto [stick_r_x_f, stick_r_y_f] =
289 analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)]->GetStatus();
290 lstick_entry.x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX); 303 lstick_entry.x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX);
291 lstick_entry.y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX); 304 lstick_entry.y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX);
292 rstick_entry.x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX); 305 rstick_entry.x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX);
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 89bf8b815..e6b56a9f9 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -10,6 +10,7 @@
10#include "core/core_timing_util.h" 10#include "core/core_timing_util.h"
11#include "core/frontend/emu_window.h" 11#include "core/frontend/emu_window.h"
12#include "core/frontend/input.h" 12#include "core/frontend/input.h"
13#include "core/hardware_properties.h"
13#include "core/hle/ipc_helpers.h" 14#include "core/hle/ipc_helpers.h"
14#include "core/hle/kernel/client_port.h" 15#include "core/hle/kernel/client_port.h"
15#include "core/hle/kernel/client_session.h" 16#include "core/hle/kernel/client_session.h"
@@ -37,11 +38,11 @@ namespace Service::HID {
37 38
38// Updating period for each HID device. 39// Updating period for each HID device.
39// TODO(ogniK): Find actual polling rate of hid 40// TODO(ogniK): Find actual polling rate of hid
40constexpr s64 pad_update_ticks = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 66); 41constexpr s64 pad_update_ticks = static_cast<s64>(Core::Hardware::BASE_CLOCK_RATE / 66);
41[[maybe_unused]] constexpr s64 accelerometer_update_ticks = 42[[maybe_unused]] constexpr s64 accelerometer_update_ticks =
42 static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 100); 43 static_cast<s64>(Core::Hardware::BASE_CLOCK_RATE / 100);
43[[maybe_unused]] constexpr s64 gyroscope_update_ticks = 44[[maybe_unused]] constexpr s64 gyroscope_update_ticks =
44 static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 100); 45 static_cast<s64>(Core::Hardware::BASE_CLOCK_RATE / 100);
45constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000; 46constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000;
46 47
47IAppletResource::IAppletResource(Core::System& system) 48IAppletResource::IAppletResource(Core::System& system)
diff --git a/src/core/hle/service/ldn/ldn.cpp b/src/core/hle/service/ldn/ldn.cpp
index ed5059047..92adde6d4 100644
--- a/src/core/hle/service/ldn/ldn.cpp
+++ b/src/core/hle/service/ldn/ldn.cpp
@@ -129,12 +129,20 @@ public:
129 {304, nullptr, "Disconnect"}, 129 {304, nullptr, "Disconnect"},
130 {400, nullptr, "Initialize"}, 130 {400, nullptr, "Initialize"},
131 {401, nullptr, "Finalize"}, 131 {401, nullptr, "Finalize"},
132 {402, nullptr, "SetOperationMode"}, 132 {402, &IUserLocalCommunicationService::Initialize2, "Initialize2"}, // 7.0.0+
133 }; 133 };
134 // clang-format on 134 // clang-format on
135 135
136 RegisterHandlers(functions); 136 RegisterHandlers(functions);
137 } 137 }
138
139 void Initialize2(Kernel::HLERequestContext& ctx) {
140 LOG_WARNING(Service_LDN, "(STUBBED) called");
141 // Result success seem make this services start network and continue.
142 // If we just pass result error then it will stop and maybe try again and again.
143 IPC::ResponseBuilder rb{ctx, 2};
144 rb.Push(RESULT_UNKNOWN);
145 }
138}; 146};
139 147
140class LDNS final : public ServiceFramework<LDNS> { 148class LDNS final : public ServiceFramework<LDNS> {
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
index 6d8bca8bb..f1966ac0e 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
@@ -44,6 +44,8 @@ u32 nvhost_gpu::ioctl(Ioctl command, const std::vector<u8>& input, const std::ve
44 return GetWaitbase(input, output); 44 return GetWaitbase(input, output);
45 case IoctlCommand::IocChannelSetTimeoutCommand: 45 case IoctlCommand::IocChannelSetTimeoutCommand:
46 return ChannelSetTimeout(input, output); 46 return ChannelSetTimeout(input, output);
47 case IoctlCommand::IocChannelSetTimeslice:
48 return ChannelSetTimeslice(input, output);
47 default: 49 default:
48 break; 50 break;
49 } 51 }
@@ -228,4 +230,14 @@ u32 nvhost_gpu::ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>&
228 return 0; 230 return 0;
229} 231}
230 232
233u32 nvhost_gpu::ChannelSetTimeslice(const std::vector<u8>& input, std::vector<u8>& output) {
234 IoctlSetTimeslice params{};
235 std::memcpy(&params, input.data(), sizeof(IoctlSetTimeslice));
236 LOG_INFO(Service_NVDRV, "called, timeslice=0x{:X}", params.timeslice);
237
238 channel_timeslice = params.timeslice;
239
240 return 0;
241}
242
231} // namespace Service::Nvidia::Devices 243} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
index d056dd046..2ac74743f 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
@@ -48,6 +48,7 @@ private:
48 IocAllocObjCtxCommand = 0xC0104809, 48 IocAllocObjCtxCommand = 0xC0104809,
49 IocChannelGetWaitbaseCommand = 0xC0080003, 49 IocChannelGetWaitbaseCommand = 0xC0080003,
50 IocChannelSetTimeoutCommand = 0x40044803, 50 IocChannelSetTimeoutCommand = 0x40044803,
51 IocChannelSetTimeslice = 0xC004481D,
51 }; 52 };
52 53
53 enum class CtxObjects : u32_le { 54 enum class CtxObjects : u32_le {
@@ -101,6 +102,11 @@ private:
101 static_assert(sizeof(IoctlChannelSetPriority) == 4, 102 static_assert(sizeof(IoctlChannelSetPriority) == 4,
102 "IoctlChannelSetPriority is incorrect size"); 103 "IoctlChannelSetPriority is incorrect size");
103 104
105 struct IoctlSetTimeslice {
106 u32_le timeslice;
107 };
108 static_assert(sizeof(IoctlSetTimeslice) == 4, "IoctlSetTimeslice is incorrect size");
109
104 struct IoctlEventIdControl { 110 struct IoctlEventIdControl {
105 u32_le cmd; // 0=disable, 1=enable, 2=clear 111 u32_le cmd; // 0=disable, 1=enable, 2=clear
106 u32_le id; 112 u32_le id;
@@ -174,6 +180,7 @@ private:
174 u64_le user_data{}; 180 u64_le user_data{};
175 IoctlZCullBind zcull_params{}; 181 IoctlZCullBind zcull_params{};
176 u32_le channel_priority{}; 182 u32_le channel_priority{};
183 u32_le channel_timeslice{};
177 184
178 u32 SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output); 185 u32 SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output);
179 u32 SetClientData(const std::vector<u8>& input, std::vector<u8>& output); 186 u32 SetClientData(const std::vector<u8>& input, std::vector<u8>& output);
@@ -188,6 +195,7 @@ private:
188 const std::vector<u8>& input2, IoctlVersion version); 195 const std::vector<u8>& input2, IoctlVersion version);
189 u32 GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output); 196 u32 GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output);
190 u32 ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>& output); 197 u32 ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>& output);
198 u32 ChannelSetTimeslice(const std::vector<u8>& input, std::vector<u8>& output);
191 199
192 std::shared_ptr<nvmap> nvmap_dev; 200 std::shared_ptr<nvmap> nvmap_dev;
193 u32 assigned_syncpoints{}; 201 u32 assigned_syncpoints{};
diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp
index 62752e419..134152210 100644
--- a/src/core/hle/service/nvflinger/nvflinger.cpp
+++ b/src/core/hle/service/nvflinger/nvflinger.cpp
@@ -12,6 +12,7 @@
12#include "core/core.h" 12#include "core/core.h"
13#include "core/core_timing.h" 13#include "core/core_timing.h"
14#include "core/core_timing_util.h" 14#include "core/core_timing_util.h"
15#include "core/hardware_properties.h"
15#include "core/hle/kernel/kernel.h" 16#include "core/hle/kernel/kernel.h"
16#include "core/hle/kernel/readable_event.h" 17#include "core/hle/kernel/readable_event.h"
17#include "core/hle/service/nvdrv/devices/nvdisp_disp0.h" 18#include "core/hle/service/nvdrv/devices/nvdisp_disp0.h"
@@ -26,8 +27,8 @@
26 27
27namespace Service::NVFlinger { 28namespace Service::NVFlinger {
28 29
29constexpr s64 frame_ticks = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 60); 30constexpr s64 frame_ticks = static_cast<s64>(Core::Hardware::BASE_CLOCK_RATE / 60);
30constexpr s64 frame_ticks_30fps = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 30); 31constexpr s64 frame_ticks_30fps = static_cast<s64>(Core::Hardware::BASE_CLOCK_RATE / 30);
31 32
32NVFlinger::NVFlinger(Core::System& system) : system(system) { 33NVFlinger::NVFlinger(Core::System& system) : system(system) {
33 displays.emplace_back(0, "Default", system); 34 displays.emplace_back(0, "Default", system);
@@ -222,7 +223,7 @@ void NVFlinger::Compose() {
222 223
223s64 NVFlinger::GetNextTicks() const { 224s64 NVFlinger::GetNextTicks() const {
224 constexpr s64 max_hertz = 120LL; 225 constexpr s64 max_hertz = 120LL;
225 return (Core::Timing::BASE_CLOCK_RATE * (1LL << swap_interval)) / max_hertz; 226 return (Core::Hardware::BASE_CLOCK_RATE * (1LL << swap_interval)) / max_hertz;
226} 227}
227 228
228} // namespace Service::NVFlinger 229} // namespace Service::NVFlinger
diff --git a/src/core/hle/service/prepo/prepo.cpp b/src/core/hle/service/prepo/prepo.cpp
index 5eb26caf8..8f1be0e48 100644
--- a/src/core/hle/service/prepo/prepo.cpp
+++ b/src/core/hle/service/prepo/prepo.cpp
@@ -50,16 +50,16 @@ private:
50 IPC::RequestParser rp{ctx}; 50 IPC::RequestParser rp{ctx};
51 const auto process_id = rp.PopRaw<u64>(); 51 const auto process_id = rp.PopRaw<u64>();
52 52
53 const auto data1 = ctx.ReadBuffer(0); 53 std::vector<std::vector<u8>> data{ctx.ReadBuffer(0)};
54 const auto data2 = ctx.ReadBuffer(1); 54 if (Type == Core::Reporter::PlayReportType::New) {
55 data.emplace_back(ctx.ReadBuffer(1));
56 }
55 57
56 LOG_DEBUG(Service_PREPO, 58 LOG_DEBUG(Service_PREPO, "called, type={:02X}, process_id={:016X}, data1_size={:016X}",
57 "called, type={:02X}, process_id={:016X}, data1_size={:016X}, data2_size={:016X}", 59 static_cast<u8>(Type), process_id, data[0].size());
58 static_cast<u8>(Type), process_id, data1.size(), data2.size());
59 60
60 const auto& reporter{system.GetReporter()}; 61 const auto& reporter{system.GetReporter()};
61 reporter.SavePlayReport(Type, system.CurrentProcess()->GetTitleID(), {data1, data2}, 62 reporter.SavePlayReport(Type, system.CurrentProcess()->GetTitleID(), data, process_id);
62 process_id);
63 63
64 IPC::ResponseBuilder rb{ctx, 2}; 64 IPC::ResponseBuilder rb{ctx, 2};
65 rb.Push(RESULT_SUCCESS); 65 rb.Push(RESULT_SUCCESS);
@@ -70,19 +70,19 @@ private:
70 IPC::RequestParser rp{ctx}; 70 IPC::RequestParser rp{ctx};
71 const auto user_id = rp.PopRaw<u128>(); 71 const auto user_id = rp.PopRaw<u128>();
72 const auto process_id = rp.PopRaw<u64>(); 72 const auto process_id = rp.PopRaw<u64>();
73 73 std::vector<std::vector<u8>> data{ctx.ReadBuffer(0)};
74 const auto data1 = ctx.ReadBuffer(0); 74 if (Type == Core::Reporter::PlayReportType::New) {
75 const auto data2 = ctx.ReadBuffer(1); 75 data.emplace_back(ctx.ReadBuffer(1));
76 }
76 77
77 LOG_DEBUG( 78 LOG_DEBUG(
78 Service_PREPO, 79 Service_PREPO,
79 "called, type={:02X}, user_id={:016X}{:016X}, process_id={:016X}, data1_size={:016X}, " 80 "called, type={:02X}, user_id={:016X}{:016X}, process_id={:016X}, data1_size={:016X}",
80 "data2_size={:016X}", 81 static_cast<u8>(Type), user_id[1], user_id[0], process_id, data[0].size());
81 static_cast<u8>(Type), user_id[1], user_id[0], process_id, data1.size(), data2.size());
82 82
83 const auto& reporter{system.GetReporter()}; 83 const auto& reporter{system.GetReporter()};
84 reporter.SavePlayReport(Type, system.CurrentProcess()->GetTitleID(), {data1, data2}, 84 reporter.SavePlayReport(Type, system.CurrentProcess()->GetTitleID(), data, process_id,
85 process_id, user_id); 85 user_id);
86 86
87 IPC::ResponseBuilder rb{ctx, 2}; 87 IPC::ResponseBuilder rb{ctx, 2};
88 rb.Push(RESULT_SUCCESS); 88 rb.Push(RESULT_SUCCESS);
diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp
index 884ad173b..f67fab2f9 100644
--- a/src/core/hle/service/sockets/bsd.cpp
+++ b/src/core/hle/service/sockets/bsd.cpp
@@ -42,6 +42,26 @@ void BSD::Socket(Kernel::HLERequestContext& ctx) {
42 rb.Push<u32>(0); // bsd errno 42 rb.Push<u32>(0); // bsd errno
43} 43}
44 44
45void BSD::Select(Kernel::HLERequestContext& ctx) {
46 LOG_WARNING(Service, "(STUBBED) called");
47
48 IPC::ResponseBuilder rb{ctx, 4};
49
50 rb.Push(RESULT_SUCCESS);
51 rb.Push<u32>(0); // ret
52 rb.Push<u32>(0); // bsd errno
53}
54
55void BSD::Bind(Kernel::HLERequestContext& ctx) {
56 LOG_WARNING(Service, "(STUBBED) called");
57
58 IPC::ResponseBuilder rb{ctx, 4};
59
60 rb.Push(RESULT_SUCCESS);
61 rb.Push<u32>(0); // ret
62 rb.Push<u32>(0); // bsd errno
63}
64
45void BSD::Connect(Kernel::HLERequestContext& ctx) { 65void BSD::Connect(Kernel::HLERequestContext& ctx) {
46 LOG_WARNING(Service, "(STUBBED) called"); 66 LOG_WARNING(Service, "(STUBBED) called");
47 67
@@ -52,6 +72,26 @@ void BSD::Connect(Kernel::HLERequestContext& ctx) {
52 rb.Push<u32>(0); // bsd errno 72 rb.Push<u32>(0); // bsd errno
53} 73}
54 74
75void BSD::Listen(Kernel::HLERequestContext& ctx) {
76 LOG_WARNING(Service, "(STUBBED) called");
77
78 IPC::ResponseBuilder rb{ctx, 4};
79
80 rb.Push(RESULT_SUCCESS);
81 rb.Push<u32>(0); // ret
82 rb.Push<u32>(0); // bsd errno
83}
84
85void BSD::SetSockOpt(Kernel::HLERequestContext& ctx) {
86 LOG_WARNING(Service, "(STUBBED) called");
87
88 IPC::ResponseBuilder rb{ctx, 4};
89
90 rb.Push(RESULT_SUCCESS);
91 rb.Push<u32>(0); // ret
92 rb.Push<u32>(0); // bsd errno
93}
94
55void BSD::SendTo(Kernel::HLERequestContext& ctx) { 95void BSD::SendTo(Kernel::HLERequestContext& ctx) {
56 LOG_WARNING(Service, "(STUBBED) called"); 96 LOG_WARNING(Service, "(STUBBED) called");
57 97
@@ -80,7 +120,7 @@ BSD::BSD(const char* name) : ServiceFramework(name) {
80 {2, &BSD::Socket, "Socket"}, 120 {2, &BSD::Socket, "Socket"},
81 {3, nullptr, "SocketExempt"}, 121 {3, nullptr, "SocketExempt"},
82 {4, nullptr, "Open"}, 122 {4, nullptr, "Open"},
83 {5, nullptr, "Select"}, 123 {5, &BSD::Select, "Select"},
84 {6, nullptr, "Poll"}, 124 {6, nullptr, "Poll"},
85 {7, nullptr, "Sysctl"}, 125 {7, nullptr, "Sysctl"},
86 {8, nullptr, "Recv"}, 126 {8, nullptr, "Recv"},
@@ -88,15 +128,15 @@ BSD::BSD(const char* name) : ServiceFramework(name) {
88 {10, nullptr, "Send"}, 128 {10, nullptr, "Send"},
89 {11, &BSD::SendTo, "SendTo"}, 129 {11, &BSD::SendTo, "SendTo"},
90 {12, nullptr, "Accept"}, 130 {12, nullptr, "Accept"},
91 {13, nullptr, "Bind"}, 131 {13, &BSD::Bind, "Bind"},
92 {14, &BSD::Connect, "Connect"}, 132 {14, &BSD::Connect, "Connect"},
93 {15, nullptr, "GetPeerName"}, 133 {15, nullptr, "GetPeerName"},
94 {16, nullptr, "GetSockName"}, 134 {16, nullptr, "GetSockName"},
95 {17, nullptr, "GetSockOpt"}, 135 {17, nullptr, "GetSockOpt"},
96 {18, nullptr, "Listen"}, 136 {18, &BSD::Listen, "Listen"},
97 {19, nullptr, "Ioctl"}, 137 {19, nullptr, "Ioctl"},
98 {20, nullptr, "Fcntl"}, 138 {20, nullptr, "Fcntl"},
99 {21, nullptr, "SetSockOpt"}, 139 {21, &BSD::SetSockOpt, "SetSockOpt"},
100 {22, nullptr, "Shutdown"}, 140 {22, nullptr, "Shutdown"},
101 {23, nullptr, "ShutdownAllSockets"}, 141 {23, nullptr, "ShutdownAllSockets"},
102 {24, nullptr, "Write"}, 142 {24, nullptr, "Write"},
diff --git a/src/core/hle/service/sockets/bsd.h b/src/core/hle/service/sockets/bsd.h
index 0fe0e65c6..3098e3baf 100644
--- a/src/core/hle/service/sockets/bsd.h
+++ b/src/core/hle/service/sockets/bsd.h
@@ -18,7 +18,11 @@ private:
18 void RegisterClient(Kernel::HLERequestContext& ctx); 18 void RegisterClient(Kernel::HLERequestContext& ctx);
19 void StartMonitoring(Kernel::HLERequestContext& ctx); 19 void StartMonitoring(Kernel::HLERequestContext& ctx);
20 void Socket(Kernel::HLERequestContext& ctx); 20 void Socket(Kernel::HLERequestContext& ctx);
21 void Select(Kernel::HLERequestContext& ctx);
22 void Bind(Kernel::HLERequestContext& ctx);
21 void Connect(Kernel::HLERequestContext& ctx); 23 void Connect(Kernel::HLERequestContext& ctx);
24 void Listen(Kernel::HLERequestContext& ctx);
25 void SetSockOpt(Kernel::HLERequestContext& ctx);
22 void SendTo(Kernel::HLERequestContext& ctx); 26 void SendTo(Kernel::HLERequestContext& ctx);
23 void Close(Kernel::HLERequestContext& ctx); 27 void Close(Kernel::HLERequestContext& ctx);
24 28
diff --git a/src/core/hle/service/time/standard_steady_clock_core.cpp b/src/core/hle/service/time/standard_steady_clock_core.cpp
index ca1a783fc..1575f0b49 100644
--- a/src/core/hle/service/time/standard_steady_clock_core.cpp
+++ b/src/core/hle/service/time/standard_steady_clock_core.cpp
@@ -5,6 +5,7 @@
5#include "core/core.h" 5#include "core/core.h"
6#include "core/core_timing.h" 6#include "core/core_timing.h"
7#include "core/core_timing_util.h" 7#include "core/core_timing_util.h"
8#include "core/hardware_properties.h"
8#include "core/hle/service/time/standard_steady_clock_core.h" 9#include "core/hle/service/time/standard_steady_clock_core.h"
9 10
10namespace Service::Time::Clock { 11namespace Service::Time::Clock {
@@ -12,7 +13,7 @@ namespace Service::Time::Clock {
12TimeSpanType StandardSteadyClockCore::GetCurrentRawTimePoint(Core::System& system) { 13TimeSpanType StandardSteadyClockCore::GetCurrentRawTimePoint(Core::System& system) {
13 const TimeSpanType ticks_time_span{TimeSpanType::FromTicks( 14 const TimeSpanType ticks_time_span{TimeSpanType::FromTicks(
14 Core::Timing::CpuCyclesToClockCycles(system.CoreTiming().GetTicks()), 15 Core::Timing::CpuCyclesToClockCycles(system.CoreTiming().GetTicks()),
15 Core::Timing::CNTFREQ)}; 16 Core::Hardware::CNTFREQ)};
16 TimeSpanType raw_time_point{setup_value.nanoseconds + ticks_time_span.nanoseconds}; 17 TimeSpanType raw_time_point{setup_value.nanoseconds + ticks_time_span.nanoseconds};
17 18
18 if (raw_time_point.nanoseconds < cached_raw_time_point.nanoseconds) { 19 if (raw_time_point.nanoseconds < cached_raw_time_point.nanoseconds) {
diff --git a/src/core/hle/service/time/tick_based_steady_clock_core.cpp b/src/core/hle/service/time/tick_based_steady_clock_core.cpp
index c77b98189..44d5bc651 100644
--- a/src/core/hle/service/time/tick_based_steady_clock_core.cpp
+++ b/src/core/hle/service/time/tick_based_steady_clock_core.cpp
@@ -5,6 +5,7 @@
5#include "core/core.h" 5#include "core/core.h"
6#include "core/core_timing.h" 6#include "core/core_timing.h"
7#include "core/core_timing_util.h" 7#include "core/core_timing_util.h"
8#include "core/hardware_properties.h"
8#include "core/hle/service/time/tick_based_steady_clock_core.h" 9#include "core/hle/service/time/tick_based_steady_clock_core.h"
9 10
10namespace Service::Time::Clock { 11namespace Service::Time::Clock {
@@ -12,7 +13,7 @@ namespace Service::Time::Clock {
12SteadyClockTimePoint TickBasedSteadyClockCore::GetTimePoint(Core::System& system) { 13SteadyClockTimePoint TickBasedSteadyClockCore::GetTimePoint(Core::System& system) {
13 const TimeSpanType ticks_time_span{TimeSpanType::FromTicks( 14 const TimeSpanType ticks_time_span{TimeSpanType::FromTicks(
14 Core::Timing::CpuCyclesToClockCycles(system.CoreTiming().GetTicks()), 15 Core::Timing::CpuCyclesToClockCycles(system.CoreTiming().GetTicks()),
15 Core::Timing::CNTFREQ)}; 16 Core::Hardware::CNTFREQ)};
16 17
17 return {ticks_time_span.ToSeconds(), GetClockSourceId()}; 18 return {ticks_time_span.ToSeconds(), GetClockSourceId()};
18} 19}
diff --git a/src/core/hle/service/time/time.cpp b/src/core/hle/service/time/time.cpp
index 8ef4efcef..749b7be70 100644
--- a/src/core/hle/service/time/time.cpp
+++ b/src/core/hle/service/time/time.cpp
@@ -6,6 +6,7 @@
6#include "core/core.h" 6#include "core/core.h"
7#include "core/core_timing.h" 7#include "core/core_timing.h"
8#include "core/core_timing_util.h" 8#include "core/core_timing_util.h"
9#include "core/hardware_properties.h"
9#include "core/hle/ipc_helpers.h" 10#include "core/hle/ipc_helpers.h"
10#include "core/hle/kernel/client_port.h" 11#include "core/hle/kernel/client_port.h"
11#include "core/hle/kernel/client_session.h" 12#include "core/hle/kernel/client_session.h"
@@ -233,7 +234,7 @@ void Module::Interface::CalculateMonotonicSystemClockBaseTimePoint(Kernel::HLERe
233 if (current_time_point.clock_source_id == context.steady_time_point.clock_source_id) { 234 if (current_time_point.clock_source_id == context.steady_time_point.clock_source_id) {
234 const auto ticks{Clock::TimeSpanType::FromTicks( 235 const auto ticks{Clock::TimeSpanType::FromTicks(
235 Core::Timing::CpuCyclesToClockCycles(system.CoreTiming().GetTicks()), 236 Core::Timing::CpuCyclesToClockCycles(system.CoreTiming().GetTicks()),
236 Core::Timing::CNTFREQ)}; 237 Core::Hardware::CNTFREQ)};
237 const s64 base_time_point{context.offset + current_time_point.time_point - 238 const s64 base_time_point{context.offset + current_time_point.time_point -
238 ticks.ToSeconds()}; 239 ticks.ToSeconds()};
239 IPC::ResponseBuilder rb{ctx, (sizeof(s64) / 4) + 2}; 240 IPC::ResponseBuilder rb{ctx, (sizeof(s64) / 4) + 2};
diff --git a/src/core/hle/service/time/time_sharedmemory.cpp b/src/core/hle/service/time/time_sharedmemory.cpp
index 9b03191bf..fdaef233f 100644
--- a/src/core/hle/service/time/time_sharedmemory.cpp
+++ b/src/core/hle/service/time/time_sharedmemory.cpp
@@ -5,6 +5,7 @@
5#include "core/core.h" 5#include "core/core.h"
6#include "core/core_timing.h" 6#include "core/core_timing.h"
7#include "core/core_timing_util.h" 7#include "core/core_timing_util.h"
8#include "core/hardware_properties.h"
8#include "core/hle/service/time/clock_types.h" 9#include "core/hle/service/time/clock_types.h"
9#include "core/hle/service/time/steady_clock_core.h" 10#include "core/hle/service/time/steady_clock_core.h"
10#include "core/hle/service/time/time_sharedmemory.h" 11#include "core/hle/service/time/time_sharedmemory.h"
@@ -31,7 +32,7 @@ void SharedMemory::SetupStandardSteadyClock(Core::System& system,
31 Clock::TimeSpanType current_time_point) { 32 Clock::TimeSpanType current_time_point) {
32 const Clock::TimeSpanType ticks_time_span{Clock::TimeSpanType::FromTicks( 33 const Clock::TimeSpanType ticks_time_span{Clock::TimeSpanType::FromTicks(
33 Core::Timing::CpuCyclesToClockCycles(system.CoreTiming().GetTicks()), 34 Core::Timing::CpuCyclesToClockCycles(system.CoreTiming().GetTicks()),
34 Core::Timing::CNTFREQ)}; 35 Core::Hardware::CNTFREQ)};
35 const Clock::SteadyClockContext context{ 36 const Clock::SteadyClockContext context{
36 static_cast<u64>(current_time_point.nanoseconds - ticks_time_span.nanoseconds), 37 static_cast<u64>(current_time_point.nanoseconds - ticks_time_span.nanoseconds),
37 clock_source_id}; 38 clock_source_id};
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index 515c5accb..044067a5b 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.cpp
@@ -97,7 +97,8 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
97 if (nso_header.IsSegmentCompressed(i)) { 97 if (nso_header.IsSegmentCompressed(i)) {
98 data = DecompressSegment(data, nso_header.segments[i]); 98 data = DecompressSegment(data, nso_header.segments[i]);
99 } 99 }
100 program_image.resize(nso_header.segments[i].location + data.size()); 100 program_image.resize(nso_header.segments[i].location +
101 PageAlignSize(static_cast<u32>(data.size())));
101 std::memcpy(program_image.data() + nso_header.segments[i].location, data.data(), 102 std::memcpy(program_image.data() + nso_header.segments[i].location, data.data(),
102 data.size()); 103 data.size());
103 codeset.segments[i].addr = nso_header.segments[i].location; 104 codeset.segments[i].addr = nso_header.segments[i].location;
@@ -105,8 +106,12 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
105 codeset.segments[i].size = PageAlignSize(static_cast<u32>(data.size())); 106 codeset.segments[i].size = PageAlignSize(static_cast<u32>(data.size()));
106 } 107 }
107 108
108 if (should_pass_arguments && !Settings::values.program_args.empty()) { 109 if (should_pass_arguments) {
109 const auto arg_data = Settings::values.program_args; 110 std::vector<u8> arg_data{Settings::values.program_args.begin(),
111 Settings::values.program_args.end()};
112 if (arg_data.empty()) {
113 arg_data.resize(NSO_ARGUMENT_DEFAULT_SIZE);
114 }
110 codeset.DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE; 115 codeset.DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE;
111 NSOArgumentHeader args_header{ 116 NSOArgumentHeader args_header{
112 NSO_ARGUMENT_DATA_ALLOCATION_SIZE, static_cast<u32_le>(arg_data.size()), {}}; 117 NSO_ARGUMENT_DATA_ALLOCATION_SIZE, static_cast<u32_le>(arg_data.size()), {}};
diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h
index 58cbe162d..d2d600cd9 100644
--- a/src/core/loader/nso.h
+++ b/src/core/loader/nso.h
@@ -56,6 +56,8 @@ static_assert(sizeof(NSOHeader) == 0x100, "NSOHeader has incorrect size.");
56static_assert(std::is_trivially_copyable_v<NSOHeader>, "NSOHeader must be trivially copyable."); 56static_assert(std::is_trivially_copyable_v<NSOHeader>, "NSOHeader must be trivially copyable.");
57 57
58constexpr u64 NSO_ARGUMENT_DATA_ALLOCATION_SIZE = 0x9000; 58constexpr u64 NSO_ARGUMENT_DATA_ALLOCATION_SIZE = 0x9000;
59// NOTE: Official software default argument state is unverified.
60constexpr u64 NSO_ARGUMENT_DEFAULT_SIZE = 1;
59 61
60struct NSOArgumentHeader { 62struct NSOArgumentHeader {
61 u32_le allocated_size; 63 u32_le allocated_size;
diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp
index d1e6bed93..4472500d2 100644
--- a/src/core/memory/cheat_engine.cpp
+++ b/src/core/memory/cheat_engine.cpp
@@ -9,6 +9,7 @@
9#include "core/core.h" 9#include "core/core.h"
10#include "core/core_timing.h" 10#include "core/core_timing.h"
11#include "core/core_timing_util.h" 11#include "core/core_timing_util.h"
12#include "core/hardware_properties.h"
12#include "core/hle/kernel/process.h" 13#include "core/hle/kernel/process.h"
13#include "core/hle/service/hid/controllers/npad.h" 14#include "core/hle/service/hid/controllers/npad.h"
14#include "core/hle/service/hid/hid.h" 15#include "core/hle/service/hid/hid.h"
@@ -17,7 +18,7 @@
17 18
18namespace Memory { 19namespace Memory {
19 20
20constexpr s64 CHEAT_ENGINE_TICKS = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 12); 21constexpr s64 CHEAT_ENGINE_TICKS = static_cast<s64>(Core::Hardware::BASE_CLOCK_RATE / 12);
21constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF; 22constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF;
22 23
23StandardVmCallbacks::StandardVmCallbacks(Core::System& system, const CheatProcessMetadata& metadata) 24StandardVmCallbacks::StandardVmCallbacks(Core::System& system, const CheatProcessMetadata& metadata)
diff --git a/src/core/settings.h b/src/core/settings.h
index 9c98a9287..f837d3fbc 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -371,6 +371,11 @@ enum class SDMCSize : u64 {
371 S1TB = 0x10000000000ULL, 371 S1TB = 0x10000000000ULL,
372}; 372};
373 373
374enum class RendererBackend {
375 OpenGL = 0,
376 Vulkan = 1,
377};
378
374struct Values { 379struct Values {
375 // System 380 // System
376 bool use_docked_mode; 381 bool use_docked_mode;
@@ -401,6 +406,9 @@ struct Values {
401 std::string motion_device; 406 std::string motion_device;
402 TouchscreenInput touchscreen; 407 TouchscreenInput touchscreen;
403 std::atomic_bool is_device_reload_pending{true}; 408 std::atomic_bool is_device_reload_pending{true};
409 std::string udp_input_address;
410 u16 udp_input_port;
411 u8 udp_pad_index;
404 412
405 // Core 413 // Core
406 bool use_multi_core; 414 bool use_multi_core;
@@ -416,7 +424,12 @@ struct Values {
416 SDMCSize sdmc_size; 424 SDMCSize sdmc_size;
417 425
418 // Renderer 426 // Renderer
427 RendererBackend renderer_backend;
428 bool renderer_debug;
429 int vulkan_device;
430
419 float resolution_factor; 431 float resolution_factor;
432 int aspect_ratio;
420 bool use_frame_limit; 433 bool use_frame_limit;
421 u16 frame_limit; 434 u16 frame_limit;
422 bool use_disk_shader_cache; 435 bool use_disk_shader_cache;
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index 320e8ad73..0e72d31cd 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -46,6 +46,16 @@ static u64 GenerateTelemetryId() {
46 return telemetry_id; 46 return telemetry_id;
47} 47}
48 48
49static const char* TranslateRenderer(Settings::RendererBackend backend) {
50 switch (backend) {
51 case Settings::RendererBackend::OpenGL:
52 return "OpenGL";
53 case Settings::RendererBackend::Vulkan:
54 return "Vulkan";
55 }
56 return "Unknown";
57}
58
49u64 GetTelemetryId() { 59u64 GetTelemetryId() {
50 u64 telemetry_id{}; 60 u64 telemetry_id{};
51 const std::string filename{FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + 61 const std::string filename{FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) +
@@ -169,7 +179,7 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader) {
169 AddField(field_type, "Audio_SinkId", Settings::values.sink_id); 179 AddField(field_type, "Audio_SinkId", Settings::values.sink_id);
170 AddField(field_type, "Audio_EnableAudioStretching", Settings::values.enable_audio_stretching); 180 AddField(field_type, "Audio_EnableAudioStretching", Settings::values.enable_audio_stretching);
171 AddField(field_type, "Core_UseMultiCore", Settings::values.use_multi_core); 181 AddField(field_type, "Core_UseMultiCore", Settings::values.use_multi_core);
172 AddField(field_type, "Renderer_Backend", "OpenGL"); 182 AddField(field_type, "Renderer_Backend", TranslateRenderer(Settings::values.renderer_backend));
173 AddField(field_type, "Renderer_ResolutionFactor", Settings::values.resolution_factor); 183 AddField(field_type, "Renderer_ResolutionFactor", Settings::values.resolution_factor);
174 AddField(field_type, "Renderer_UseFrameLimit", Settings::values.use_frame_limit); 184 AddField(field_type, "Renderer_UseFrameLimit", Settings::values.use_frame_limit);
175 AddField(field_type, "Renderer_FrameLimit", Settings::values.frame_limit); 185 AddField(field_type, "Renderer_FrameLimit", Settings::values.frame_limit);
diff --git a/src/core/tools/freezer.cpp b/src/core/tools/freezer.cpp
index 55e0dbc49..1e060f009 100644
--- a/src/core/tools/freezer.cpp
+++ b/src/core/tools/freezer.cpp
@@ -7,13 +7,14 @@
7#include "core/core.h" 7#include "core/core.h"
8#include "core/core_timing.h" 8#include "core/core_timing.h"
9#include "core/core_timing_util.h" 9#include "core/core_timing_util.h"
10#include "core/hardware_properties.h"
10#include "core/memory.h" 11#include "core/memory.h"
11#include "core/tools/freezer.h" 12#include "core/tools/freezer.h"
12 13
13namespace Tools { 14namespace Tools {
14namespace { 15namespace {
15 16
16constexpr s64 MEMORY_FREEZER_TICKS = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 60); 17constexpr s64 MEMORY_FREEZER_TICKS = static_cast<s64>(Core::Hardware::BASE_CLOCK_RATE / 60);
17 18
18u64 MemoryReadWidth(Memory::Memory& memory, u32 width, VAddr addr) { 19u64 MemoryReadWidth(Memory::Memory& memory, u32 width, VAddr addr) {
19 switch (width) { 20 switch (width) {
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index 5b4e032bd..2520ba321 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -9,6 +9,12 @@ add_library(input_common STATIC
9 motion_emu.h 9 motion_emu.h
10 sdl/sdl.cpp 10 sdl/sdl.cpp
11 sdl/sdl.h 11 sdl/sdl.h
12 udp/client.cpp
13 udp/client.h
14 udp/protocol.cpp
15 udp/protocol.h
16 udp/udp.cpp
17 udp/udp.h
12) 18)
13 19
14if(SDL2_FOUND) 20if(SDL2_FOUND)
@@ -21,4 +27,4 @@ if(SDL2_FOUND)
21endif() 27endif()
22 28
23create_target_directory_groups(input_common) 29create_target_directory_groups(input_common)
24target_link_libraries(input_common PUBLIC core PRIVATE common) 30target_link_libraries(input_common PUBLIC core PRIVATE common ${Boost_LIBRARIES})
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index 8e66c1b15..c98c848cf 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -9,6 +9,7 @@
9#include "input_common/keyboard.h" 9#include "input_common/keyboard.h"
10#include "input_common/main.h" 10#include "input_common/main.h"
11#include "input_common/motion_emu.h" 11#include "input_common/motion_emu.h"
12#include "input_common/udp/udp.h"
12#ifdef HAVE_SDL2 13#ifdef HAVE_SDL2
13#include "input_common/sdl/sdl.h" 14#include "input_common/sdl/sdl.h"
14#endif 15#endif
@@ -18,6 +19,7 @@ namespace InputCommon {
18static std::shared_ptr<Keyboard> keyboard; 19static std::shared_ptr<Keyboard> keyboard;
19static std::shared_ptr<MotionEmu> motion_emu; 20static std::shared_ptr<MotionEmu> motion_emu;
20static std::unique_ptr<SDL::State> sdl; 21static std::unique_ptr<SDL::State> sdl;
22static std::unique_ptr<CemuhookUDP::State> udp;
21 23
22void Init() { 24void Init() {
23 keyboard = std::make_shared<Keyboard>(); 25 keyboard = std::make_shared<Keyboard>();
@@ -28,6 +30,8 @@ void Init() {
28 Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu); 30 Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu);
29 31
30 sdl = SDL::Init(); 32 sdl = SDL::Init();
33
34 udp = CemuhookUDP::Init();
31} 35}
32 36
33void Shutdown() { 37void Shutdown() {
@@ -37,6 +41,7 @@ void Shutdown() {
37 Input::UnregisterFactory<Input::MotionDevice>("motion_emu"); 41 Input::UnregisterFactory<Input::MotionDevice>("motion_emu");
38 motion_emu.reset(); 42 motion_emu.reset();
39 sdl.reset(); 43 sdl.reset();
44 udp.reset();
40} 45}
41 46
42Keyboard* GetKeyboard() { 47Keyboard* GetKeyboard() {
@@ -72,11 +77,13 @@ std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left,
72namespace Polling { 77namespace Polling {
73 78
74std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) { 79std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) {
80 std::vector<std::unique_ptr<DevicePoller>> pollers;
81
75#ifdef HAVE_SDL2 82#ifdef HAVE_SDL2
76 return sdl->GetPollers(type); 83 pollers = sdl->GetPollers(type);
77#else
78 return {};
79#endif 84#endif
85
86 return pollers;
80} 87}
81 88
82} // namespace Polling 89} // namespace Polling
diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp
index d2e9d278f..a2e0c0bd2 100644
--- a/src/input_common/sdl/sdl_impl.cpp
+++ b/src/input_common/sdl/sdl_impl.cpp
@@ -342,6 +342,22 @@ public:
342 return std::make_tuple<float, float>(0.0f, 0.0f); 342 return std::make_tuple<float, float>(0.0f, 0.0f);
343 } 343 }
344 344
345 bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {
346 const auto [x, y] = GetStatus();
347 const float directional_deadzone = 0.4f;
348 switch (direction) {
349 case Input::AnalogDirection::RIGHT:
350 return x > directional_deadzone;
351 case Input::AnalogDirection::LEFT:
352 return x < -directional_deadzone;
353 case Input::AnalogDirection::UP:
354 return y > directional_deadzone;
355 case Input::AnalogDirection::DOWN:
356 return y < -directional_deadzone;
357 }
358 return false;
359 }
360
345private: 361private:
346 std::shared_ptr<SDLJoystick> joystick; 362 std::shared_ptr<SDLJoystick> joystick;
347 const int axis_x; 363 const int axis_x;
diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp
new file mode 100644
index 000000000..2228571a6
--- /dev/null
+++ b/src/input_common/udp/client.cpp
@@ -0,0 +1,286 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include <array>
7#include <chrono>
8#include <cstring>
9#include <functional>
10#include <thread>
11#include <boost/asio.hpp>
12#include <boost/bind.hpp>
13#include "common/logging/log.h"
14#include "input_common/udp/client.h"
15#include "input_common/udp/protocol.h"
16
17using boost::asio::ip::udp;
18
19namespace InputCommon::CemuhookUDP {
20
21struct SocketCallback {
22 std::function<void(Response::Version)> version;
23 std::function<void(Response::PortInfo)> port_info;
24 std::function<void(Response::PadData)> pad_data;
25};
26
27class Socket {
28public:
29 using clock = std::chrono::system_clock;
30
31 explicit Socket(const std::string& host, u16 port, u8 pad_index, u32 client_id,
32 SocketCallback callback)
33 : callback(std::move(callback)), timer(io_service),
34 socket(io_service, udp::endpoint(udp::v4(), 0)), client_id(client_id),
35 pad_index(pad_index),
36 send_endpoint(udp::endpoint(boost::asio::ip::make_address_v4(host), port)) {}
37
38 void Stop() {
39 io_service.stop();
40 }
41
42 void Loop() {
43 io_service.run();
44 }
45
46 void StartSend(const clock::time_point& from) {
47 timer.expires_at(from + std::chrono::seconds(3));
48 timer.async_wait([this](const boost::system::error_code& error) { HandleSend(error); });
49 }
50
51 void StartReceive() {
52 socket.async_receive_from(
53 boost::asio::buffer(receive_buffer), receive_endpoint,
54 [this](const boost::system::error_code& error, std::size_t bytes_transferred) {
55 HandleReceive(error, bytes_transferred);
56 });
57 }
58
59private:
60 void HandleReceive(const boost::system::error_code& error, std::size_t bytes_transferred) {
61 if (auto type = Response::Validate(receive_buffer.data(), bytes_transferred)) {
62 switch (*type) {
63 case Type::Version: {
64 Response::Version version;
65 std::memcpy(&version, &receive_buffer[sizeof(Header)], sizeof(Response::Version));
66 callback.version(std::move(version));
67 break;
68 }
69 case Type::PortInfo: {
70 Response::PortInfo port_info;
71 std::memcpy(&port_info, &receive_buffer[sizeof(Header)],
72 sizeof(Response::PortInfo));
73 callback.port_info(std::move(port_info));
74 break;
75 }
76 case Type::PadData: {
77 Response::PadData pad_data;
78 std::memcpy(&pad_data, &receive_buffer[sizeof(Header)], sizeof(Response::PadData));
79 callback.pad_data(std::move(pad_data));
80 break;
81 }
82 }
83 }
84 StartReceive();
85 }
86
87 void HandleSend(const boost::system::error_code& error) {
88 // Send a request for getting port info for the pad
89 Request::PortInfo port_info{1, {pad_index, 0, 0, 0}};
90 const auto port_message = Request::Create(port_info, client_id);
91 std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE);
92 socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint);
93
94 // Send a request for getting pad data for the pad
95 Request::PadData pad_data{Request::PadData::Flags::Id, pad_index, EMPTY_MAC_ADDRESS};
96 const auto pad_message = Request::Create(pad_data, client_id);
97 std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE);
98 socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint);
99 StartSend(timer.expiry());
100 }
101
102 SocketCallback callback;
103 boost::asio::io_service io_service;
104 boost::asio::basic_waitable_timer<clock> timer;
105 udp::socket socket;
106
107 u32 client_id{};
108 u8 pad_index{};
109
110 static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message<Request::PortInfo>);
111 static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message<Request::PadData>);
112 std::array<u8, PORT_INFO_SIZE> send_buffer1;
113 std::array<u8, PAD_DATA_SIZE> send_buffer2;
114 udp::endpoint send_endpoint;
115
116 std::array<u8, MAX_PACKET_SIZE> receive_buffer;
117 udp::endpoint receive_endpoint;
118};
119
120static void SocketLoop(Socket* socket) {
121 socket->StartReceive();
122 socket->StartSend(Socket::clock::now());
123 socket->Loop();
124}
125
126Client::Client(std::shared_ptr<DeviceStatus> status, const std::string& host, u16 port,
127 u8 pad_index, u32 client_id)
128 : status(std::move(status)) {
129 StartCommunication(host, port, pad_index, client_id);
130}
131
132Client::~Client() {
133 socket->Stop();
134 thread.join();
135}
136
137void Client::ReloadSocket(const std::string& host, u16 port, u8 pad_index, u32 client_id) {
138 socket->Stop();
139 thread.join();
140 StartCommunication(host, port, pad_index, client_id);
141}
142
143void Client::OnVersion(Response::Version data) {
144 LOG_TRACE(Input, "Version packet received: {}", data.version);
145}
146
147void Client::OnPortInfo(Response::PortInfo data) {
148 LOG_TRACE(Input, "PortInfo packet received: {}", data.model);
149}
150
151void Client::OnPadData(Response::PadData data) {
152 LOG_TRACE(Input, "PadData packet received");
153 if (data.packet_counter <= packet_sequence) {
154 LOG_WARNING(
155 Input,
156 "PadData packet dropped because its stale info. Current count: {} Packet count: {}",
157 packet_sequence, data.packet_counter);
158 return;
159 }
160 packet_sequence = data.packet_counter;
161 // TODO: Check how the Switch handles motions and how the CemuhookUDP motion
162 // directions correspond to the ones of the Switch
163 Common::Vec3f accel = Common::MakeVec<float>(data.accel.x, data.accel.y, data.accel.z);
164 Common::Vec3f gyro = Common::MakeVec<float>(data.gyro.pitch, data.gyro.yaw, data.gyro.roll);
165 {
166 std::lock_guard guard(status->update_mutex);
167
168 status->motion_status = {accel, gyro};
169
170 // TODO: add a setting for "click" touch. Click touch refers to a device that differentiates
171 // between a simple "tap" and a hard press that causes the touch screen to click.
172 const bool is_active = data.touch_1.is_active != 0;
173
174 float x = 0;
175 float y = 0;
176
177 if (is_active && status->touch_calibration) {
178 const u16 min_x = status->touch_calibration->min_x;
179 const u16 max_x = status->touch_calibration->max_x;
180 const u16 min_y = status->touch_calibration->min_y;
181 const u16 max_y = status->touch_calibration->max_y;
182
183 x = (std::clamp(static_cast<u16>(data.touch_1.x), min_x, max_x) - min_x) /
184 static_cast<float>(max_x - min_x);
185 y = (std::clamp(static_cast<u16>(data.touch_1.y), min_y, max_y) - min_y) /
186 static_cast<float>(max_y - min_y);
187 }
188
189 status->touch_status = {x, y, is_active};
190 }
191}
192
193void Client::StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id) {
194 SocketCallback callback{[this](Response::Version version) { OnVersion(version); },
195 [this](Response::PortInfo info) { OnPortInfo(info); },
196 [this](Response::PadData data) { OnPadData(data); }};
197 LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port);
198 socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback);
199 thread = std::thread{SocketLoop, this->socket.get()};
200}
201
202void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id,
203 std::function<void()> success_callback,
204 std::function<void()> failure_callback) {
205 std::thread([=] {
206 Common::Event success_event;
207 SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {},
208 [&](Response::PadData data) { success_event.Set(); }};
209 Socket socket{host, port, pad_index, client_id, std::move(callback)};
210 std::thread worker_thread{SocketLoop, &socket};
211 bool result = success_event.WaitFor(std::chrono::seconds(8));
212 socket.Stop();
213 worker_thread.join();
214 if (result) {
215 success_callback();
216 } else {
217 failure_callback();
218 }
219 })
220 .detach();
221}
222
223CalibrationConfigurationJob::CalibrationConfigurationJob(
224 const std::string& host, u16 port, u8 pad_index, u32 client_id,
225 std::function<void(Status)> status_callback,
226 std::function<void(u16, u16, u16, u16)> data_callback) {
227
228 std::thread([=] {
229 constexpr u16 CALIBRATION_THRESHOLD = 100;
230
231 u16 min_x{UINT16_MAX};
232 u16 min_y{UINT16_MAX};
233 u16 max_x{};
234 u16 max_y{};
235
236 Status current_status{Status::Initialized};
237 SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {},
238 [&](Response::PadData data) {
239 if (current_status == Status::Initialized) {
240 // Receiving data means the communication is ready now
241 current_status = Status::Ready;
242 status_callback(current_status);
243 }
244 if (!data.touch_1.is_active) {
245 return;
246 }
247 LOG_DEBUG(Input, "Current touch: {} {}", data.touch_1.x,
248 data.touch_1.y);
249 min_x = std::min(min_x, static_cast<u16>(data.touch_1.x));
250 min_y = std::min(min_y, static_cast<u16>(data.touch_1.y));
251 if (current_status == Status::Ready) {
252 // First touch - min data (min_x/min_y)
253 current_status = Status::Stage1Completed;
254 status_callback(current_status);
255 }
256 if (data.touch_1.x - min_x > CALIBRATION_THRESHOLD &&
257 data.touch_1.y - min_y > CALIBRATION_THRESHOLD) {
258 // Set the current position as max value and finishes
259 // configuration
260 max_x = data.touch_1.x;
261 max_y = data.touch_1.y;
262 current_status = Status::Completed;
263 data_callback(min_x, min_y, max_x, max_y);
264 status_callback(current_status);
265
266 complete_event.Set();
267 }
268 }};
269 Socket socket{host, port, pad_index, client_id, std::move(callback)};
270 std::thread worker_thread{SocketLoop, &socket};
271 complete_event.Wait();
272 socket.Stop();
273 worker_thread.join();
274 })
275 .detach();
276}
277
278CalibrationConfigurationJob::~CalibrationConfigurationJob() {
279 Stop();
280}
281
282void CalibrationConfigurationJob::Stop() {
283 complete_event.Set();
284}
285
286} // namespace InputCommon::CemuhookUDP
diff --git a/src/input_common/udp/client.h b/src/input_common/udp/client.h
new file mode 100644
index 000000000..b8c654755
--- /dev/null
+++ b/src/input_common/udp/client.h
@@ -0,0 +1,95 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <functional>
8#include <memory>
9#include <mutex>
10#include <optional>
11#include <string>
12#include <thread>
13#include <tuple>
14#include "common/common_types.h"
15#include "common/thread.h"
16#include "common/vector_math.h"
17
18namespace InputCommon::CemuhookUDP {
19
20constexpr u16 DEFAULT_PORT = 26760;
21constexpr char DEFAULT_ADDR[] = "127.0.0.1";
22
23class Socket;
24
25namespace Response {
26struct PadData;
27struct PortInfo;
28struct Version;
29} // namespace Response
30
31struct DeviceStatus {
32 std::mutex update_mutex;
33 std::tuple<Common::Vec3<float>, Common::Vec3<float>> motion_status;
34 std::tuple<float, float, bool> touch_status;
35
36 // calibration data for scaling the device's touch area to 3ds
37 struct CalibrationData {
38 u16 min_x{};
39 u16 min_y{};
40 u16 max_x{};
41 u16 max_y{};
42 };
43 std::optional<CalibrationData> touch_calibration;
44};
45
46class Client {
47public:
48 explicit Client(std::shared_ptr<DeviceStatus> status, const std::string& host = DEFAULT_ADDR,
49 u16 port = DEFAULT_PORT, u8 pad_index = 0, u32 client_id = 24872);
50 ~Client();
51 void ReloadSocket(const std::string& host = "127.0.0.1", u16 port = 26760, u8 pad_index = 0,
52 u32 client_id = 24872);
53
54private:
55 void OnVersion(Response::Version);
56 void OnPortInfo(Response::PortInfo);
57 void OnPadData(Response::PadData);
58 void StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id);
59
60 std::unique_ptr<Socket> socket;
61 std::shared_ptr<DeviceStatus> status;
62 std::thread thread;
63 u64 packet_sequence = 0;
64};
65
66/// An async job allowing configuration of the touchpad calibration.
67class CalibrationConfigurationJob {
68public:
69 enum class Status {
70 Initialized,
71 Ready,
72 Stage1Completed,
73 Completed,
74 };
75 /**
76 * Constructs and starts the job with the specified parameter.
77 *
78 * @param status_callback Callback for job status updates
79 * @param data_callback Called when calibration data is ready
80 */
81 explicit CalibrationConfigurationJob(const std::string& host, u16 port, u8 pad_index,
82 u32 client_id, std::function<void(Status)> status_callback,
83 std::function<void(u16, u16, u16, u16)> data_callback);
84 ~CalibrationConfigurationJob();
85 void Stop();
86
87private:
88 Common::Event complete_event;
89};
90
91void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id,
92 std::function<void()> success_callback,
93 std::function<void()> failure_callback);
94
95} // namespace InputCommon::CemuhookUDP
diff --git a/src/input_common/udp/protocol.cpp b/src/input_common/udp/protocol.cpp
new file mode 100644
index 000000000..a982ac49d
--- /dev/null
+++ b/src/input_common/udp/protocol.cpp
@@ -0,0 +1,79 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <cstddef>
6#include <cstring>
7#include "common/logging/log.h"
8#include "input_common/udp/protocol.h"
9
10namespace InputCommon::CemuhookUDP {
11
12static constexpr std::size_t GetSizeOfResponseType(Type t) {
13 switch (t) {
14 case Type::Version:
15 return sizeof(Response::Version);
16 case Type::PortInfo:
17 return sizeof(Response::PortInfo);
18 case Type::PadData:
19 return sizeof(Response::PadData);
20 }
21 return 0;
22}
23
24namespace Response {
25
26/**
27 * Returns Type if the packet is valid, else none
28 *
29 * Note: Modifies the buffer to zero out the crc (since thats the easiest way to check without
30 * copying the buffer)
31 */
32std::optional<Type> Validate(u8* data, std::size_t size) {
33 if (size < sizeof(Header)) {
34 LOG_DEBUG(Input, "Invalid UDP packet received");
35 return std::nullopt;
36 }
37 Header header{};
38 std::memcpy(&header, data, sizeof(Header));
39 if (header.magic != SERVER_MAGIC) {
40 LOG_ERROR(Input, "UDP Packet has an unexpected magic value");
41 return std::nullopt;
42 }
43 if (header.protocol_version != PROTOCOL_VERSION) {
44 LOG_ERROR(Input, "UDP Packet protocol mismatch");
45 return std::nullopt;
46 }
47 if (header.type < Type::Version || header.type > Type::PadData) {
48 LOG_ERROR(Input, "UDP Packet is an unknown type");
49 return std::nullopt;
50 }
51
52 // Packet size must equal sizeof(Header) + sizeof(Data)
53 // and also verify that the packet info mentions the correct size. Since the spec includes the
54 // type of the packet as part of the data, we need to include it in size calculations here
55 // ie: payload_length == sizeof(T) + sizeof(Type)
56 const std::size_t data_len = GetSizeOfResponseType(header.type);
57 if (header.payload_length != data_len + sizeof(Type) || size < data_len + sizeof(Header)) {
58 LOG_ERROR(
59 Input,
60 "UDP Packet payload length doesn't match. Received: {} PayloadLength: {} Expected: {}",
61 size, header.payload_length, data_len + sizeof(Type));
62 return std::nullopt;
63 }
64
65 const u32 crc32 = header.crc;
66 boost::crc_32_type result;
67 // zero out the crc in the buffer and then run the crc against it
68 std::memset(&data[offsetof(Header, crc)], 0, sizeof(u32_le));
69
70 result.process_bytes(data, data_len + sizeof(Header));
71 if (crc32 != result.checksum()) {
72 LOG_ERROR(Input, "UDP Packet CRC check failed. Offset: {}", offsetof(Header, crc));
73 return std::nullopt;
74 }
75 return header.type;
76}
77} // namespace Response
78
79} // namespace InputCommon::CemuhookUDP
diff --git a/src/input_common/udp/protocol.h b/src/input_common/udp/protocol.h
new file mode 100644
index 000000000..3ba4d1fc8
--- /dev/null
+++ b/src/input_common/udp/protocol.h
@@ -0,0 +1,255 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <optional>
9#include <type_traits>
10#include <boost/crc.hpp>
11#include "common/bit_field.h"
12#include "common/swap.h"
13
14namespace InputCommon::CemuhookUDP {
15
16constexpr std::size_t MAX_PACKET_SIZE = 100;
17constexpr u16 PROTOCOL_VERSION = 1001;
18constexpr u32 CLIENT_MAGIC = 0x43555344; // DSUC (but flipped for LE)
19constexpr u32 SERVER_MAGIC = 0x53555344; // DSUS (but flipped for LE)
20
21enum class Type : u32 {
22 Version = 0x00100000,
23 PortInfo = 0x00100001,
24 PadData = 0x00100002,
25};
26
27struct Header {
28 u32_le magic{};
29 u16_le protocol_version{};
30 u16_le payload_length{};
31 u32_le crc{};
32 u32_le id{};
33 ///> In the protocol, the type of the packet is not part of the header, but its convenient to
34 ///> include in the header so the callee doesn't have to duplicate the type twice when building
35 ///> the data
36 Type type{};
37};
38static_assert(sizeof(Header) == 20, "UDP Message Header struct has wrong size");
39static_assert(std::is_trivially_copyable_v<Header>, "UDP Message Header is not trivially copyable");
40
41using MacAddress = std::array<u8, 6>;
42constexpr MacAddress EMPTY_MAC_ADDRESS = {0, 0, 0, 0, 0, 0};
43
44#pragma pack(push, 1)
45template <typename T>
46struct Message {
47 Header header{};
48 T data;
49};
50#pragma pack(pop)
51
52template <typename T>
53constexpr Type GetMessageType();
54
55namespace Request {
56
57struct Version {};
58/**
59 * Requests the server to send information about what controllers are plugged into the ports
60 * In citra's case, we only have one controller, so for simplicity's sake, we can just send a
61 * request explicitly for the first controller port and leave it at that. In the future it would be
62 * nice to make this configurable
63 */
64constexpr u32 MAX_PORTS = 4;
65struct PortInfo {
66 u32_le pad_count{}; ///> Number of ports to request data for
67 std::array<u8, MAX_PORTS> port;
68};
69static_assert(std::is_trivially_copyable_v<PortInfo>,
70 "UDP Request PortInfo is not trivially copyable");
71
72/**
73 * Request the latest pad information from the server. If the server hasn't received this message
74 * from the client in a reasonable time frame, the server will stop sending updates. The default
75 * timeout seems to be 5 seconds.
76 */
77struct PadData {
78 enum class Flags : u8 {
79 AllPorts,
80 Id,
81 Mac,
82 };
83 /// Determines which method will be used as a look up for the controller
84 Flags flags{};
85 /// Index of the port of the controller to retrieve data about
86 u8 port_id{};
87 /// Mac address of the controller to retrieve data about
88 MacAddress mac;
89};
90static_assert(sizeof(PadData) == 8, "UDP Request PadData struct has wrong size");
91static_assert(std::is_trivially_copyable_v<PadData>,
92 "UDP Request PadData is not trivially copyable");
93
94/**
95 * Creates a message with the proper header data that can be sent to the server.
96 * @param T data Request body to send
97 * @param client_id ID of the udp client (usually not checked on the server)
98 */
99template <typename T>
100Message<T> Create(const T data, const u32 client_id = 0) {
101 boost::crc_32_type crc;
102 Header header{
103 CLIENT_MAGIC, PROTOCOL_VERSION, sizeof(T) + sizeof(Type), 0, client_id, GetMessageType<T>(),
104 };
105 Message<T> message{header, data};
106 crc.process_bytes(&message, sizeof(Message<T>));
107 message.header.crc = crc.checksum();
108 return message;
109}
110} // namespace Request
111
112namespace Response {
113
114struct Version {
115 u16_le version{};
116};
117static_assert(sizeof(Version) == 2, "UDP Response Version struct has wrong size");
118static_assert(std::is_trivially_copyable_v<Version>,
119 "UDP Response Version is not trivially copyable");
120
121struct PortInfo {
122 u8 id{};
123 u8 state{};
124 u8 model{};
125 u8 connection_type{};
126 MacAddress mac;
127 u8 battery{};
128 u8 is_pad_active{};
129};
130static_assert(sizeof(PortInfo) == 12, "UDP Response PortInfo struct has wrong size");
131static_assert(std::is_trivially_copyable_v<PortInfo>,
132 "UDP Response PortInfo is not trivially copyable");
133
134#pragma pack(push, 1)
135struct PadData {
136 PortInfo info{};
137 u32_le packet_counter{};
138
139 u16_le digital_button{};
140 // The following union isn't trivially copyable but we don't use this input anyway.
141 // union DigitalButton {
142 // u16_le button;
143 // BitField<0, 1, u16> button_1; // Share
144 // BitField<1, 1, u16> button_2; // L3
145 // BitField<2, 1, u16> button_3; // R3
146 // BitField<3, 1, u16> button_4; // Options
147 // BitField<4, 1, u16> button_5; // Up
148 // BitField<5, 1, u16> button_6; // Right
149 // BitField<6, 1, u16> button_7; // Down
150 // BitField<7, 1, u16> button_8; // Left
151 // BitField<8, 1, u16> button_9; // L2
152 // BitField<9, 1, u16> button_10; // R2
153 // BitField<10, 1, u16> button_11; // L1
154 // BitField<11, 1, u16> button_12; // R1
155 // BitField<12, 1, u16> button_13; // Triangle
156 // BitField<13, 1, u16> button_14; // Circle
157 // BitField<14, 1, u16> button_15; // Cross
158 // BitField<15, 1, u16> button_16; // Square
159 // } digital_button;
160
161 u8 home;
162 /// If the device supports a "click" on the touchpad, this will change to 1 when a click happens
163 u8 touch_hard_press{};
164 u8 left_stick_x{};
165 u8 left_stick_y{};
166 u8 right_stick_x{};
167 u8 right_stick_y{};
168
169 struct AnalogButton {
170 u8 button_8{};
171 u8 button_7{};
172 u8 button_6{};
173 u8 button_5{};
174 u8 button_12{};
175 u8 button_11{};
176 u8 button_10{};
177 u8 button_9{};
178 u8 button_16{};
179 u8 button_15{};
180 u8 button_14{};
181 u8 button_13{};
182 } analog_button;
183
184 struct TouchPad {
185 u8 is_active{};
186 u8 id{};
187 u16_le x{};
188 u16_le y{};
189 } touch_1, touch_2;
190
191 u64_le motion_timestamp;
192
193 struct Accelerometer {
194 float x{};
195 float y{};
196 float z{};
197 } accel;
198
199 struct Gyroscope {
200 float pitch{};
201 float yaw{};
202 float roll{};
203 } gyro;
204};
205#pragma pack(pop)
206
207static_assert(sizeof(PadData) == 80, "UDP Response PadData struct has wrong size ");
208static_assert(std::is_trivially_copyable_v<PadData>,
209 "UDP Response PadData is not trivially copyable");
210
211static_assert(sizeof(Message<PadData>) == MAX_PACKET_SIZE,
212 "UDP MAX_PACKET_SIZE is no longer larger than Message<PadData>");
213
214static_assert(sizeof(PadData::AnalogButton) == 12,
215 "UDP Response AnalogButton struct has wrong size ");
216static_assert(sizeof(PadData::TouchPad) == 6, "UDP Response TouchPad struct has wrong size ");
217static_assert(sizeof(PadData::Accelerometer) == 12,
218 "UDP Response Accelerometer struct has wrong size ");
219static_assert(sizeof(PadData::Gyroscope) == 12, "UDP Response Gyroscope struct has wrong size ");
220
221/**
222 * Create a Response Message from the data
223 * @param data array of bytes sent from the server
224 * @return boost::none if it failed to parse or Type if it succeeded. The client can then safely
225 * copy the data into the appropriate struct for that Type
226 */
227std::optional<Type> Validate(u8* data, std::size_t size);
228
229} // namespace Response
230
231template <>
232constexpr Type GetMessageType<Request::Version>() {
233 return Type::Version;
234}
235template <>
236constexpr Type GetMessageType<Request::PortInfo>() {
237 return Type::PortInfo;
238}
239template <>
240constexpr Type GetMessageType<Request::PadData>() {
241 return Type::PadData;
242}
243template <>
244constexpr Type GetMessageType<Response::Version>() {
245 return Type::Version;
246}
247template <>
248constexpr Type GetMessageType<Response::PortInfo>() {
249 return Type::PortInfo;
250}
251template <>
252constexpr Type GetMessageType<Response::PadData>() {
253 return Type::PadData;
254}
255} // namespace InputCommon::CemuhookUDP
diff --git a/src/input_common/udp/udp.cpp b/src/input_common/udp/udp.cpp
new file mode 100644
index 000000000..ca99cc22f
--- /dev/null
+++ b/src/input_common/udp/udp.cpp
@@ -0,0 +1,98 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <mutex>
6#include <tuple>
7
8#include "common/param_package.h"
9#include "core/frontend/input.h"
10#include "core/settings.h"
11#include "input_common/udp/client.h"
12#include "input_common/udp/udp.h"
13
14namespace InputCommon::CemuhookUDP {
15
16class UDPTouchDevice final : public Input::TouchDevice {
17public:
18 explicit UDPTouchDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
19 std::tuple<float, float, bool> GetStatus() const override {
20 std::lock_guard guard(status->update_mutex);
21 return status->touch_status;
22 }
23
24private:
25 std::shared_ptr<DeviceStatus> status;
26};
27
28class UDPMotionDevice final : public Input::MotionDevice {
29public:
30 explicit UDPMotionDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
31 std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const override {
32 std::lock_guard guard(status->update_mutex);
33 return status->motion_status;
34 }
35
36private:
37 std::shared_ptr<DeviceStatus> status;
38};
39
40class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> {
41public:
42 explicit UDPTouchFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
43
44 std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override {
45 {
46 std::lock_guard guard(status->update_mutex);
47 status->touch_calibration.emplace();
48 // These default values work well for DS4 but probably not other touch inputs
49 status->touch_calibration->min_x = params.Get("min_x", 100);
50 status->touch_calibration->min_y = params.Get("min_y", 50);
51 status->touch_calibration->max_x = params.Get("max_x", 1800);
52 status->touch_calibration->max_y = params.Get("max_y", 850);
53 }
54 return std::make_unique<UDPTouchDevice>(status);
55 }
56
57private:
58 std::shared_ptr<DeviceStatus> status;
59};
60
61class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> {
62public:
63 explicit UDPMotionFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
64
65 std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override {
66 return std::make_unique<UDPMotionDevice>(status);
67 }
68
69private:
70 std::shared_ptr<DeviceStatus> status;
71};
72
73State::State() {
74 auto status = std::make_shared<DeviceStatus>();
75 client =
76 std::make_unique<Client>(status, Settings::values.udp_input_address,
77 Settings::values.udp_input_port, Settings::values.udp_pad_index);
78
79 Input::RegisterFactory<Input::TouchDevice>("cemuhookudp",
80 std::make_shared<UDPTouchFactory>(status));
81 Input::RegisterFactory<Input::MotionDevice>("cemuhookudp",
82 std::make_shared<UDPMotionFactory>(status));
83}
84
85State::~State() {
86 Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp");
87 Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp");
88}
89
90void State::ReloadUDPClient() {
91 client->ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port,
92 Settings::values.udp_pad_index);
93}
94
95std::unique_ptr<State> Init() {
96 return std::make_unique<State>();
97}
98} // namespace InputCommon::CemuhookUDP
diff --git a/src/input_common/udp/udp.h b/src/input_common/udp/udp.h
new file mode 100644
index 000000000..4f83f0441
--- /dev/null
+++ b/src/input_common/udp/udp.h
@@ -0,0 +1,25 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8
9namespace InputCommon::CemuhookUDP {
10
11class Client;
12
13class State {
14public:
15 State();
16 ~State();
17 void ReloadUDPClient();
18
19private:
20 std::unique_ptr<Client> client;
21};
22
23std::unique_ptr<State> Init();
24
25} // namespace InputCommon::CemuhookUDP
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index ccfed4f2e..4b0c6346f 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -29,12 +29,15 @@ add_library(video_core STATIC
29 gpu_synch.h 29 gpu_synch.h
30 gpu_thread.cpp 30 gpu_thread.cpp
31 gpu_thread.h 31 gpu_thread.h
32 guest_driver.cpp
33 guest_driver.h
32 macro_interpreter.cpp 34 macro_interpreter.cpp
33 macro_interpreter.h 35 macro_interpreter.h
34 memory_manager.cpp 36 memory_manager.cpp
35 memory_manager.h 37 memory_manager.h
36 morton.cpp 38 morton.cpp
37 morton.h 39 morton.h
40 query_cache.h
38 rasterizer_accelerated.cpp 41 rasterizer_accelerated.cpp
39 rasterizer_accelerated.h 42 rasterizer_accelerated.h
40 rasterizer_cache.cpp 43 rasterizer_cache.cpp
@@ -72,6 +75,8 @@ add_library(video_core STATIC
72 renderer_opengl/gl_stream_buffer.h 75 renderer_opengl/gl_stream_buffer.h
73 renderer_opengl/gl_texture_cache.cpp 76 renderer_opengl/gl_texture_cache.cpp
74 renderer_opengl/gl_texture_cache.h 77 renderer_opengl/gl_texture_cache.h
78 renderer_opengl/gl_query_cache.cpp
79 renderer_opengl/gl_query_cache.h
75 renderer_opengl/maxwell_to_gl.h 80 renderer_opengl/maxwell_to_gl.h
76 renderer_opengl/renderer_opengl.cpp 81 renderer_opengl/renderer_opengl.cpp
77 renderer_opengl/renderer_opengl.h 82 renderer_opengl/renderer_opengl.h
@@ -154,6 +159,7 @@ if (ENABLE_VULKAN)
154 renderer_vulkan/maxwell_to_vk.cpp 159 renderer_vulkan/maxwell_to_vk.cpp
155 renderer_vulkan/maxwell_to_vk.h 160 renderer_vulkan/maxwell_to_vk.h
156 renderer_vulkan/renderer_vulkan.h 161 renderer_vulkan/renderer_vulkan.h
162 renderer_vulkan/renderer_vulkan.cpp
157 renderer_vulkan/vk_blit_screen.cpp 163 renderer_vulkan/vk_blit_screen.cpp
158 renderer_vulkan/vk_blit_screen.h 164 renderer_vulkan/vk_blit_screen.h
159 renderer_vulkan/vk_buffer_cache.cpp 165 renderer_vulkan/vk_buffer_cache.cpp
@@ -174,6 +180,8 @@ if (ENABLE_VULKAN)
174 renderer_vulkan/vk_memory_manager.h 180 renderer_vulkan/vk_memory_manager.h
175 renderer_vulkan/vk_pipeline_cache.cpp 181 renderer_vulkan/vk_pipeline_cache.cpp
176 renderer_vulkan/vk_pipeline_cache.h 182 renderer_vulkan/vk_pipeline_cache.h
183 renderer_vulkan/vk_query_cache.cpp
184 renderer_vulkan/vk_query_cache.h
177 renderer_vulkan/vk_rasterizer.cpp 185 renderer_vulkan/vk_rasterizer.cpp
178 renderer_vulkan/vk_rasterizer.h 186 renderer_vulkan/vk_rasterizer.h
179 renderer_vulkan/vk_renderpass_cache.cpp 187 renderer_vulkan/vk_renderpass_cache.cpp
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index 0510ed777..186aca61d 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -101,7 +101,10 @@ public:
101 void TickFrame() { 101 void TickFrame() {
102 ++epoch; 102 ++epoch;
103 while (!pending_destruction.empty()) { 103 while (!pending_destruction.empty()) {
104 if (pending_destruction.front()->GetEpoch() + 1 > epoch) { 104 // Delay at least 4 frames before destruction.
105 // This is due to triple buffering happening on some drivers.
106 static constexpr u64 epochs_to_destroy = 5;
107 if (pending_destruction.front()->GetEpoch() + epochs_to_destroy > epoch) {
105 break; 108 break;
106 } 109 }
107 pending_destruction.pop_front(); 110 pending_destruction.pop_front();
diff --git a/src/video_core/engines/const_buffer_engine_interface.h b/src/video_core/engines/const_buffer_engine_interface.h
index 44b8b8d22..d56a47710 100644
--- a/src/video_core/engines/const_buffer_engine_interface.h
+++ b/src/video_core/engines/const_buffer_engine_interface.h
@@ -9,6 +9,7 @@
9#include "common/common_types.h" 9#include "common/common_types.h"
10#include "video_core/engines/shader_bytecode.h" 10#include "video_core/engines/shader_bytecode.h"
11#include "video_core/engines/shader_type.h" 11#include "video_core/engines/shader_type.h"
12#include "video_core/guest_driver.h"
12#include "video_core/textures/texture.h" 13#include "video_core/textures/texture.h"
13 14
14namespace Tegra::Engines { 15namespace Tegra::Engines {
@@ -106,6 +107,9 @@ public:
106 virtual SamplerDescriptor AccessBindlessSampler(ShaderType stage, u64 const_buffer, 107 virtual SamplerDescriptor AccessBindlessSampler(ShaderType stage, u64 const_buffer,
107 u64 offset) const = 0; 108 u64 offset) const = 0;
108 virtual u32 GetBoundBuffer() const = 0; 109 virtual u32 GetBoundBuffer() const = 0;
110
111 virtual VideoCore::GuestDriverProfile& AccessGuestDriverProfile() = 0;
112 virtual const VideoCore::GuestDriverProfile& AccessGuestDriverProfile() const = 0;
109}; 113};
110 114
111} // namespace Tegra::Engines 115} // namespace Tegra::Engines
diff --git a/src/video_core/engines/kepler_compute.cpp b/src/video_core/engines/kepler_compute.cpp
index 110406f2f..4b824aa4e 100644
--- a/src/video_core/engines/kepler_compute.cpp
+++ b/src/video_core/engines/kepler_compute.cpp
@@ -94,6 +94,14 @@ SamplerDescriptor KeplerCompute::AccessBindlessSampler(ShaderType stage, u64 con
94 return result; 94 return result;
95} 95}
96 96
97VideoCore::GuestDriverProfile& KeplerCompute::AccessGuestDriverProfile() {
98 return rasterizer.AccessGuestDriverProfile();
99}
100
101const VideoCore::GuestDriverProfile& KeplerCompute::AccessGuestDriverProfile() const {
102 return rasterizer.AccessGuestDriverProfile();
103}
104
97void KeplerCompute::ProcessLaunch() { 105void KeplerCompute::ProcessLaunch() {
98 const GPUVAddr launch_desc_loc = regs.launch_desc_loc.Address(); 106 const GPUVAddr launch_desc_loc = regs.launch_desc_loc.Address();
99 memory_manager.ReadBlockUnsafe(launch_desc_loc, &launch_description, 107 memory_manager.ReadBlockUnsafe(launch_desc_loc, &launch_description,
diff --git a/src/video_core/engines/kepler_compute.h b/src/video_core/engines/kepler_compute.h
index 4ef3e0613..eeb79c56f 100644
--- a/src/video_core/engines/kepler_compute.h
+++ b/src/video_core/engines/kepler_compute.h
@@ -218,6 +218,10 @@ public:
218 return regs.tex_cb_index; 218 return regs.tex_cb_index;
219 } 219 }
220 220
221 VideoCore::GuestDriverProfile& AccessGuestDriverProfile() override;
222
223 const VideoCore::GuestDriverProfile& AccessGuestDriverProfile() const override;
224
221private: 225private:
222 Core::System& system; 226 Core::System& system;
223 VideoCore::RasterizerInterface& rasterizer; 227 VideoCore::RasterizerInterface& rasterizer;
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index 58dfa8033..b28de1092 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -4,17 +4,21 @@
4 4
5#include <cinttypes> 5#include <cinttypes>
6#include <cstring> 6#include <cstring>
7#include <optional>
7#include "common/assert.h" 8#include "common/assert.h"
8#include "core/core.h" 9#include "core/core.h"
9#include "core/core_timing.h" 10#include "core/core_timing.h"
10#include "video_core/engines/maxwell_3d.h" 11#include "video_core/engines/maxwell_3d.h"
11#include "video_core/engines/shader_type.h" 12#include "video_core/engines/shader_type.h"
13#include "video_core/gpu.h"
12#include "video_core/memory_manager.h" 14#include "video_core/memory_manager.h"
13#include "video_core/rasterizer_interface.h" 15#include "video_core/rasterizer_interface.h"
14#include "video_core/textures/texture.h" 16#include "video_core/textures/texture.h"
15 17
16namespace Tegra::Engines { 18namespace Tegra::Engines {
17 19
20using VideoCore::QueryType;
21
18/// First register id that is actually a Macro call. 22/// First register id that is actually a Macro call.
19constexpr u32 MacroRegistersStart = 0xE00; 23constexpr u32 MacroRegistersStart = 0xE00;
20 24
@@ -399,6 +403,10 @@ void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) {
399 ProcessQueryCondition(); 403 ProcessQueryCondition();
400 break; 404 break;
401 } 405 }
406 case MAXWELL3D_REG_INDEX(counter_reset): {
407 ProcessCounterReset();
408 break;
409 }
402 case MAXWELL3D_REG_INDEX(sync_info): { 410 case MAXWELL3D_REG_INDEX(sync_info): {
403 ProcessSyncPoint(); 411 ProcessSyncPoint();
404 break; 412 break;
@@ -481,7 +489,7 @@ void Maxwell3D::FlushMMEInlineDraw() {
481 489
482 const bool is_indexed = mme_draw.current_mode == MMEDrawMode::Indexed; 490 const bool is_indexed = mme_draw.current_mode == MMEDrawMode::Indexed;
483 if (ShouldExecute()) { 491 if (ShouldExecute()) {
484 rasterizer.DrawMultiBatch(is_indexed); 492 rasterizer.Draw(is_indexed, true);
485 } 493 }
486 494
487 // TODO(bunnei): Below, we reset vertex count so that we can use these registers to determine if 495 // TODO(bunnei): Below, we reset vertex count so that we can use these registers to determine if
@@ -519,61 +527,51 @@ void Maxwell3D::ProcessFirmwareCall4() {
519 regs.reg_array[0xd00] = 1; 527 regs.reg_array[0xd00] = 1;
520} 528}
521 529
522void Maxwell3D::ProcessQueryGet() { 530void Maxwell3D::StampQueryResult(u64 payload, bool long_query) {
531 struct LongQueryResult {
532 u64_le value;
533 u64_le timestamp;
534 };
535 static_assert(sizeof(LongQueryResult) == 16, "LongQueryResult has wrong size");
523 const GPUVAddr sequence_address{regs.query.QueryAddress()}; 536 const GPUVAddr sequence_address{regs.query.QueryAddress()};
524 // Since the sequence address is given as a GPU VAddr, we have to convert it to an application 537 if (long_query) {
525 // VAddr before writing. 538 // Write the 128-bit result structure in long mode. Note: We emulate an infinitely fast
539 // GPU, this command may actually take a while to complete in real hardware due to GPU
540 // wait queues.
541 LongQueryResult query_result{payload, system.GPU().GetTicks()};
542 memory_manager.WriteBlock(sequence_address, &query_result, sizeof(query_result));
543 } else {
544 memory_manager.Write<u32>(sequence_address, static_cast<u32>(payload));
545 }
546}
526 547
548void Maxwell3D::ProcessQueryGet() {
527 // TODO(Subv): Support the other query units. 549 // TODO(Subv): Support the other query units.
528 ASSERT_MSG(regs.query.query_get.unit == Regs::QueryUnit::Crop, 550 ASSERT_MSG(regs.query.query_get.unit == Regs::QueryUnit::Crop,
529 "Units other than CROP are unimplemented"); 551 "Units other than CROP are unimplemented");
530 552
531 u64 result = 0; 553 switch (regs.query.query_get.operation) {
532 554 case Regs::QueryOperation::Release:
533 // TODO(Subv): Support the other query variables 555 StampQueryResult(regs.query.query_sequence, regs.query.query_get.short_query == 0);
534 switch (regs.query.query_get.select) {
535 case Regs::QuerySelect::Zero:
536 // This seems to actually write the query sequence to the query address.
537 result = regs.query.query_sequence;
538 break; 556 break;
539 default: 557 case Regs::QueryOperation::Acquire:
540 result = 1; 558 // TODO(Blinkhawk): Under this operation, the GPU waits for the CPU to write a value that
541 UNIMPLEMENTED_MSG("Unimplemented query select type {}", 559 // matches the current payload.
542 static_cast<u32>(regs.query.query_get.select.Value())); 560 UNIMPLEMENTED_MSG("Unimplemented query operation ACQUIRE");
543 } 561 break;
544 562 case Regs::QueryOperation::Counter:
545 // TODO(Subv): Research and implement how query sync conditions work. 563 if (const std::optional<u64> result = GetQueryResult()) {
546 564 // If the query returns an empty optional it means it's cached and deferred.
547 struct LongQueryResult { 565 // In this case we have a non-empty result, so we stamp it immediately.
548 u64_le value; 566 StampQueryResult(*result, regs.query.query_get.short_query == 0);
549 u64_le timestamp;
550 };
551 static_assert(sizeof(LongQueryResult) == 16, "LongQueryResult has wrong size");
552
553 switch (regs.query.query_get.mode) {
554 case Regs::QueryMode::Write:
555 case Regs::QueryMode::Write2: {
556 u32 sequence = regs.query.query_sequence;
557 if (regs.query.query_get.short_query) {
558 // Write the current query sequence to the sequence address.
559 // TODO(Subv): Find out what happens if you use a long query type but mark it as a short
560 // query.
561 memory_manager.Write<u32>(sequence_address, sequence);
562 } else {
563 // Write the 128-bit result structure in long mode. Note: We emulate an infinitely fast
564 // GPU, this command may actually take a while to complete in real hardware due to GPU
565 // wait queues.
566 LongQueryResult query_result{};
567 query_result.value = result;
568 // TODO(Subv): Generate a real GPU timestamp and write it here instead of CoreTiming
569 query_result.timestamp = system.CoreTiming().GetTicks();
570 memory_manager.WriteBlock(sequence_address, &query_result, sizeof(query_result));
571 } 567 }
572 break; 568 break;
573 } 569 case Regs::QueryOperation::Trap:
570 UNIMPLEMENTED_MSG("Unimplemented query operation TRAP");
571 break;
574 default: 572 default:
575 UNIMPLEMENTED_MSG("Query mode {} not implemented", 573 UNIMPLEMENTED_MSG("Unknown query operation");
576 static_cast<u32>(regs.query.query_get.mode.Value())); 574 break;
577 } 575 }
578} 576}
579 577
@@ -590,20 +588,20 @@ void Maxwell3D::ProcessQueryCondition() {
590 } 588 }
591 case Regs::ConditionMode::ResNonZero: { 589 case Regs::ConditionMode::ResNonZero: {
592 Regs::QueryCompare cmp; 590 Regs::QueryCompare cmp;
593 memory_manager.ReadBlockUnsafe(condition_address, &cmp, sizeof(cmp)); 591 memory_manager.ReadBlock(condition_address, &cmp, sizeof(cmp));
594 execute_on = cmp.initial_sequence != 0U && cmp.initial_mode != 0U; 592 execute_on = cmp.initial_sequence != 0U && cmp.initial_mode != 0U;
595 break; 593 break;
596 } 594 }
597 case Regs::ConditionMode::Equal: { 595 case Regs::ConditionMode::Equal: {
598 Regs::QueryCompare cmp; 596 Regs::QueryCompare cmp;
599 memory_manager.ReadBlockUnsafe(condition_address, &cmp, sizeof(cmp)); 597 memory_manager.ReadBlock(condition_address, &cmp, sizeof(cmp));
600 execute_on = 598 execute_on =
601 cmp.initial_sequence == cmp.current_sequence && cmp.initial_mode == cmp.current_mode; 599 cmp.initial_sequence == cmp.current_sequence && cmp.initial_mode == cmp.current_mode;
602 break; 600 break;
603 } 601 }
604 case Regs::ConditionMode::NotEqual: { 602 case Regs::ConditionMode::NotEqual: {
605 Regs::QueryCompare cmp; 603 Regs::QueryCompare cmp;
606 memory_manager.ReadBlockUnsafe(condition_address, &cmp, sizeof(cmp)); 604 memory_manager.ReadBlock(condition_address, &cmp, sizeof(cmp));
607 execute_on = 605 execute_on =
608 cmp.initial_sequence != cmp.current_sequence || cmp.initial_mode != cmp.current_mode; 606 cmp.initial_sequence != cmp.current_sequence || cmp.initial_mode != cmp.current_mode;
609 break; 607 break;
@@ -616,6 +614,18 @@ void Maxwell3D::ProcessQueryCondition() {
616 } 614 }
617} 615}
618 616
617void Maxwell3D::ProcessCounterReset() {
618 switch (regs.counter_reset) {
619 case Regs::CounterReset::SampleCnt:
620 rasterizer.ResetCounter(QueryType::SamplesPassed);
621 break;
622 default:
623 LOG_WARNING(Render_OpenGL, "Unimplemented counter reset={}",
624 static_cast<int>(regs.counter_reset));
625 break;
626 }
627}
628
619void Maxwell3D::ProcessSyncPoint() { 629void Maxwell3D::ProcessSyncPoint() {
620 const u32 sync_point = regs.sync_info.sync_point.Value(); 630 const u32 sync_point = regs.sync_info.sync_point.Value();
621 const u32 increment = regs.sync_info.increment.Value(); 631 const u32 increment = regs.sync_info.increment.Value();
@@ -644,7 +654,7 @@ void Maxwell3D::DrawArrays() {
644 654
645 const bool is_indexed{regs.index_array.count && !regs.vertex_buffer.count}; 655 const bool is_indexed{regs.index_array.count && !regs.vertex_buffer.count};
646 if (ShouldExecute()) { 656 if (ShouldExecute()) {
647 rasterizer.DrawBatch(is_indexed); 657 rasterizer.Draw(is_indexed, false);
648 } 658 }
649 659
650 // TODO(bunnei): Below, we reset vertex count so that we can use these registers to determine if 660 // TODO(bunnei): Below, we reset vertex count so that we can use these registers to determine if
@@ -658,6 +668,22 @@ void Maxwell3D::DrawArrays() {
658 } 668 }
659} 669}
660 670
671std::optional<u64> Maxwell3D::GetQueryResult() {
672 switch (regs.query.query_get.select) {
673 case Regs::QuerySelect::Zero:
674 return 0;
675 case Regs::QuerySelect::SamplesPassed:
676 // Deferred.
677 rasterizer.Query(regs.query.QueryAddress(), VideoCore::QueryType::SamplesPassed,
678 system.GPU().GetTicks());
679 return {};
680 default:
681 UNIMPLEMENTED_MSG("Unimplemented query select type {}",
682 static_cast<u32>(regs.query.query_get.select.Value()));
683 return 1;
684 }
685}
686
661void Maxwell3D::ProcessCBBind(std::size_t stage_index) { 687void Maxwell3D::ProcessCBBind(std::size_t stage_index) {
662 // Bind the buffer currently in CB_ADDRESS to the specified index in the desired shader stage. 688 // Bind the buffer currently in CB_ADDRESS to the specified index in the desired shader stage.
663 auto& shader = state.shader_stages[stage_index]; 689 auto& shader = state.shader_stages[stage_index];
@@ -784,4 +810,12 @@ SamplerDescriptor Maxwell3D::AccessBindlessSampler(ShaderType stage, u64 const_b
784 return result; 810 return result;
785} 811}
786 812
813VideoCore::GuestDriverProfile& Maxwell3D::AccessGuestDriverProfile() {
814 return rasterizer.AccessGuestDriverProfile();
815}
816
817const VideoCore::GuestDriverProfile& Maxwell3D::AccessGuestDriverProfile() const {
818 return rasterizer.AccessGuestDriverProfile();
819}
820
787} // namespace Tegra::Engines 821} // namespace Tegra::Engines
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index ee79260fc..26939be3f 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -6,6 +6,7 @@
6 6
7#include <array> 7#include <array>
8#include <bitset> 8#include <bitset>
9#include <optional>
9#include <type_traits> 10#include <type_traits>
10#include <unordered_map> 11#include <unordered_map>
11#include <vector> 12#include <vector>
@@ -71,12 +72,11 @@ public:
71 static constexpr std::size_t MaxConstBuffers = 18; 72 static constexpr std::size_t MaxConstBuffers = 18;
72 static constexpr std::size_t MaxConstBufferSize = 0x10000; 73 static constexpr std::size_t MaxConstBufferSize = 0x10000;
73 74
74 enum class QueryMode : u32 { 75 enum class QueryOperation : u32 {
75 Write = 0, 76 Release = 0,
76 Sync = 1, 77 Acquire = 1,
77 // TODO(Subv): It is currently unknown what the difference between method 2 and method 0 78 Counter = 2,
78 // is. 79 Trap = 3,
79 Write2 = 2,
80 }; 80 };
81 81
82 enum class QueryUnit : u32 { 82 enum class QueryUnit : u32 {
@@ -410,6 +410,27 @@ public:
410 Linear = 1, 410 Linear = 1,
411 }; 411 };
412 412
413 enum class CounterReset : u32 {
414 SampleCnt = 0x01,
415 Unk02 = 0x02,
416 Unk03 = 0x03,
417 Unk04 = 0x04,
418 EmittedPrimitives = 0x10, // Not tested
419 Unk11 = 0x11,
420 Unk12 = 0x12,
421 Unk13 = 0x13,
422 Unk15 = 0x15,
423 Unk16 = 0x16,
424 Unk17 = 0x17,
425 Unk18 = 0x18,
426 Unk1A = 0x1A,
427 Unk1B = 0x1B,
428 Unk1C = 0x1C,
429 Unk1D = 0x1D,
430 Unk1E = 0x1E,
431 GeneratedPrimitives = 0x1F,
432 };
433
413 struct Cull { 434 struct Cull {
414 enum class FrontFace : u32 { 435 enum class FrontFace : u32 {
415 ClockWise = 0x0900, 436 ClockWise = 0x0900,
@@ -704,8 +725,8 @@ public:
704 INSERT_UNION_PADDING_WORDS(0x15); 725 INSERT_UNION_PADDING_WORDS(0x15);
705 726
706 s32 stencil_back_func_ref; 727 s32 stencil_back_func_ref;
707 u32 stencil_back_func_mask;
708 u32 stencil_back_mask; 728 u32 stencil_back_mask;
729 u32 stencil_back_func_mask;
709 730
710 INSERT_UNION_PADDING_WORDS(0xC); 731 INSERT_UNION_PADDING_WORDS(0xC);
711 732
@@ -858,11 +879,19 @@ public:
858 BitField<7, 1, u32> c7; 879 BitField<7, 1, u32> c7;
859 } clip_distance_enabled; 880 } clip_distance_enabled;
860 881
861 INSERT_UNION_PADDING_WORDS(0x1); 882 u32 samplecnt_enable;
862 883
863 float point_size; 884 float point_size;
864 885
865 INSERT_UNION_PADDING_WORDS(0x7); 886 INSERT_UNION_PADDING_WORDS(0x1);
887
888 u32 point_sprite_enable;
889
890 INSERT_UNION_PADDING_WORDS(0x3);
891
892 CounterReset counter_reset;
893
894 INSERT_UNION_PADDING_WORDS(0x1);
866 895
867 u32 zeta_enable; 896 u32 zeta_enable;
868 897
@@ -1077,7 +1106,7 @@ public:
1077 u32 query_sequence; 1106 u32 query_sequence;
1078 union { 1107 union {
1079 u32 raw; 1108 u32 raw;
1080 BitField<0, 2, QueryMode> mode; 1109 BitField<0, 2, QueryOperation> operation;
1081 BitField<4, 1, u32> fence; 1110 BitField<4, 1, u32> fence;
1082 BitField<12, 4, QueryUnit> unit; 1111 BitField<12, 4, QueryUnit> unit;
1083 BitField<16, 1, QuerySyncCondition> sync_cond; 1112 BitField<16, 1, QuerySyncCondition> sync_cond;
@@ -1306,6 +1335,10 @@ public:
1306 return regs.tex_cb_index; 1335 return regs.tex_cb_index;
1307 } 1336 }
1308 1337
1338 VideoCore::GuestDriverProfile& AccessGuestDriverProfile() override;
1339
1340 const VideoCore::GuestDriverProfile& AccessGuestDriverProfile() const override;
1341
1309 /// Memory for macro code - it's undetermined how big this is, however 1MB is much larger than 1342 /// Memory for macro code - it's undetermined how big this is, however 1MB is much larger than
1310 /// we've seen used. 1343 /// we've seen used.
1311 using MacroMemory = std::array<u32, 0x40000>; 1344 using MacroMemory = std::array<u32, 0x40000>;
@@ -1405,9 +1438,15 @@ private:
1405 /// Handles a write to the QUERY_GET register. 1438 /// Handles a write to the QUERY_GET register.
1406 void ProcessQueryGet(); 1439 void ProcessQueryGet();
1407 1440
1408 // Handles Conditional Rendering 1441 /// Writes the query result accordingly.
1442 void StampQueryResult(u64 payload, bool long_query);
1443
1444 /// Handles conditional rendering.
1409 void ProcessQueryCondition(); 1445 void ProcessQueryCondition();
1410 1446
1447 /// Handles counter resets.
1448 void ProcessCounterReset();
1449
1411 /// Handles writes to syncing register. 1450 /// Handles writes to syncing register.
1412 void ProcessSyncPoint(); 1451 void ProcessSyncPoint();
1413 1452
@@ -1424,6 +1463,9 @@ private:
1424 1463
1425 // Handles a instance drawcall from MME 1464 // Handles a instance drawcall from MME
1426 void StepInstance(MMEDrawMode expected_mode, u32 count); 1465 void StepInstance(MMEDrawMode expected_mode, u32 count);
1466
1467 /// Returns a query's value or an empty object if the value will be deferred through a cache.
1468 std::optional<u64> GetQueryResult();
1427}; 1469};
1428 1470
1429#define ASSERT_REG_POSITION(field_name, position) \ 1471#define ASSERT_REG_POSITION(field_name, position) \
@@ -1454,8 +1496,8 @@ ASSERT_REG_POSITION(polygon_offset_fill_enable, 0x372);
1454ASSERT_REG_POSITION(patch_vertices, 0x373); 1496ASSERT_REG_POSITION(patch_vertices, 0x373);
1455ASSERT_REG_POSITION(scissor_test, 0x380); 1497ASSERT_REG_POSITION(scissor_test, 0x380);
1456ASSERT_REG_POSITION(stencil_back_func_ref, 0x3D5); 1498ASSERT_REG_POSITION(stencil_back_func_ref, 0x3D5);
1457ASSERT_REG_POSITION(stencil_back_func_mask, 0x3D6); 1499ASSERT_REG_POSITION(stencil_back_mask, 0x3D6);
1458ASSERT_REG_POSITION(stencil_back_mask, 0x3D7); 1500ASSERT_REG_POSITION(stencil_back_func_mask, 0x3D7);
1459ASSERT_REG_POSITION(color_mask_common, 0x3E4); 1501ASSERT_REG_POSITION(color_mask_common, 0x3E4);
1460ASSERT_REG_POSITION(rt_separate_frag_data, 0x3EB); 1502ASSERT_REG_POSITION(rt_separate_frag_data, 0x3EB);
1461ASSERT_REG_POSITION(depth_bounds, 0x3E7); 1503ASSERT_REG_POSITION(depth_bounds, 0x3E7);
@@ -1489,7 +1531,10 @@ ASSERT_REG_POSITION(screen_y_control, 0x4EB);
1489ASSERT_REG_POSITION(vb_element_base, 0x50D); 1531ASSERT_REG_POSITION(vb_element_base, 0x50D);
1490ASSERT_REG_POSITION(vb_base_instance, 0x50E); 1532ASSERT_REG_POSITION(vb_base_instance, 0x50E);
1491ASSERT_REG_POSITION(clip_distance_enabled, 0x544); 1533ASSERT_REG_POSITION(clip_distance_enabled, 0x544);
1534ASSERT_REG_POSITION(samplecnt_enable, 0x545);
1492ASSERT_REG_POSITION(point_size, 0x546); 1535ASSERT_REG_POSITION(point_size, 0x546);
1536ASSERT_REG_POSITION(point_sprite_enable, 0x548);
1537ASSERT_REG_POSITION(counter_reset, 0x54C);
1493ASSERT_REG_POSITION(zeta_enable, 0x54E); 1538ASSERT_REG_POSITION(zeta_enable, 0x54E);
1494ASSERT_REG_POSITION(multisample_control, 0x54F); 1539ASSERT_REG_POSITION(multisample_control, 0x54F);
1495ASSERT_REG_POSITION(condition, 0x554); 1540ASSERT_REG_POSITION(condition, 0x554);
diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h
index 6f98bd827..c9bc83cd7 100644
--- a/src/video_core/engines/shader_bytecode.h
+++ b/src/video_core/engines/shader_bytecode.h
@@ -227,6 +227,28 @@ enum class AtomicOp : u64 {
227 Exch = 8, 227 Exch = 8,
228}; 228};
229 229
230enum class GlobalAtomicOp : u64 {
231 Add = 0,
232 Min = 1,
233 Max = 2,
234 Inc = 3,
235 Dec = 4,
236 And = 5,
237 Or = 6,
238 Xor = 7,
239 Exch = 8,
240 SafeAdd = 10,
241};
242
243enum class GlobalAtomicType : u64 {
244 U32 = 0,
245 S32 = 1,
246 U64 = 2,
247 F32_FTZ_RN = 3,
248 F16x2_FTZ_RN = 4,
249 S64 = 5,
250};
251
230enum class UniformType : u64 { 252enum class UniformType : u64 {
231 UnsignedByte = 0, 253 UnsignedByte = 0,
232 SignedByte = 1, 254 SignedByte = 1,
@@ -602,6 +624,19 @@ enum class ShuffleOperation : u64 {
602 Bfly = 3, // shuffleXorNV 624 Bfly = 3, // shuffleXorNV
603}; 625};
604 626
627enum class ShfType : u64 {
628 Bits32 = 0,
629 U64 = 2,
630 S64 = 3,
631};
632
633enum class ShfXmode : u64 {
634 None = 0,
635 HI = 1,
636 X = 2,
637 XHI = 3,
638};
639
605union Instruction { 640union Instruction {
606 constexpr Instruction& operator=(const Instruction& instr) { 641 constexpr Instruction& operator=(const Instruction& instr) {
607 value = instr.value; 642 value = instr.value;
@@ -754,6 +789,13 @@ union Instruction {
754 } shr; 789 } shr;
755 790
756 union { 791 union {
792 BitField<37, 2, ShfType> type;
793 BitField<48, 2, ShfXmode> xmode;
794 BitField<50, 1, u64> wrap;
795 BitField<20, 6, u64> immediate;
796 } shf;
797
798 union {
757 BitField<39, 5, u64> shift_amount; 799 BitField<39, 5, u64> shift_amount;
758 BitField<48, 1, u64> negate_b; 800 BitField<48, 1, u64> negate_b;
759 BitField<49, 1, u64> negate_a; 801 BitField<49, 1, u64> negate_a;
@@ -958,6 +1000,12 @@ union Instruction {
958 } stg; 1000 } stg;
959 1001
960 union { 1002 union {
1003 BitField<52, 4, GlobalAtomicOp> operation;
1004 BitField<49, 3, GlobalAtomicType> type;
1005 BitField<28, 20, s64> offset;
1006 } atom;
1007
1008 union {
961 BitField<52, 4, AtomicOp> operation; 1009 BitField<52, 4, AtomicOp> operation;
962 BitField<28, 2, AtomicType> type; 1010 BitField<28, 2, AtomicType> type;
963 BitField<30, 22, s64> offset; 1011 BitField<30, 22, s64> offset;
@@ -1096,6 +1144,11 @@ union Instruction {
1096 } fset; 1144 } fset;
1097 1145
1098 union { 1146 union {
1147 BitField<47, 1, u64> ftz;
1148 BitField<48, 4, PredCondition> cond;
1149 } fcmp;
1150
1151 union {
1099 BitField<49, 1, u64> bf; 1152 BitField<49, 1, u64> bf;
1100 BitField<35, 3, PredCondition> cond; 1153 BitField<35, 3, PredCondition> cond;
1101 BitField<50, 1, u64> ftz; 1154 BitField<50, 1, u64> ftz;
@@ -1624,11 +1677,11 @@ union Instruction {
1624 } xmad; 1677 } xmad;
1625 1678
1626 union { 1679 union {
1627 BitField<20, 14, u64> offset; 1680 BitField<20, 14, u64> shifted_offset;
1628 BitField<34, 5, u64> index; 1681 BitField<34, 5, u64> index;
1629 1682
1630 u64 GetOffset() const { 1683 u64 GetOffset() const {
1631 return offset * 4; 1684 return shifted_offset * 4;
1632 } 1685 }
1633 } cbuf34; 1686 } cbuf34;
1634 1687
@@ -1675,6 +1728,7 @@ public:
1675 BFE_C, 1728 BFE_C,
1676 BFE_R, 1729 BFE_R,
1677 BFE_IMM, 1730 BFE_IMM,
1731 BFI_RC,
1678 BFI_IMM_R, 1732 BFI_IMM_R,
1679 BRA, 1733 BRA,
1680 BRX, 1734 BRX,
@@ -1690,6 +1744,7 @@ public:
1690 ST_S, 1744 ST_S,
1691 ST, // Store in generic memory 1745 ST, // Store in generic memory
1692 STG, // Store in global memory 1746 STG, // Store in global memory
1747 ATOM, // Atomic operation on global memory
1693 ATOMS, // Atomic operation on shared memory 1748 ATOMS, // Atomic operation on shared memory
1694 AL2P, // Transforms attribute memory into physical memory 1749 AL2P, // Transforms attribute memory into physical memory
1695 TEX, 1750 TEX,
@@ -1771,6 +1826,7 @@ public:
1771 ICMP_R, 1826 ICMP_R,
1772 ICMP_CR, 1827 ICMP_CR,
1773 ICMP_IMM, 1828 ICMP_IMM,
1829 FCMP_R,
1774 MUFU, // Multi-Function Operator 1830 MUFU, // Multi-Function Operator
1775 RRO_C, // Range Reduction Operator 1831 RRO_C, // Range Reduction Operator
1776 RRO_R, 1832 RRO_R,
@@ -1994,6 +2050,7 @@ private:
1994 INST("1110111101010---", Id::ST_L, Type::Memory, "ST_L"), 2050 INST("1110111101010---", Id::ST_L, Type::Memory, "ST_L"),
1995 INST("101-------------", Id::ST, Type::Memory, "ST"), 2051 INST("101-------------", Id::ST, Type::Memory, "ST"),
1996 INST("1110111011011---", Id::STG, Type::Memory, "STG"), 2052 INST("1110111011011---", Id::STG, Type::Memory, "STG"),
2053 INST("11101101--------", Id::ATOM, Type::Memory, "ATOM"),
1997 INST("11101100--------", Id::ATOMS, Type::Memory, "ATOMS"), 2054 INST("11101100--------", Id::ATOMS, Type::Memory, "ATOMS"),
1998 INST("1110111110100---", Id::AL2P, Type::Memory, "AL2P"), 2055 INST("1110111110100---", Id::AL2P, Type::Memory, "AL2P"),
1999 INST("110000----111---", Id::TEX, Type::Texture, "TEX"), 2056 INST("110000----111---", Id::TEX, Type::Texture, "TEX"),
@@ -2074,6 +2131,7 @@ private:
2074 INST("0101110100100---", Id::HSETP2_R, Type::HalfSetPredicate, "HSETP2_R"), 2131 INST("0101110100100---", Id::HSETP2_R, Type::HalfSetPredicate, "HSETP2_R"),
2075 INST("0111111-0-------", Id::HSETP2_IMM, Type::HalfSetPredicate, "HSETP2_IMM"), 2132 INST("0111111-0-------", Id::HSETP2_IMM, Type::HalfSetPredicate, "HSETP2_IMM"),
2076 INST("0101110100011---", Id::HSET2_R, Type::HalfSet, "HSET2_R"), 2133 INST("0101110100011---", Id::HSET2_R, Type::HalfSet, "HSET2_R"),
2134 INST("010110111010----", Id::FCMP_R, Type::Arithmetic, "FCMP_R"),
2077 INST("0101000010000---", Id::MUFU, Type::Arithmetic, "MUFU"), 2135 INST("0101000010000---", Id::MUFU, Type::Arithmetic, "MUFU"),
2078 INST("0100110010010---", Id::RRO_C, Type::Arithmetic, "RRO_C"), 2136 INST("0100110010010---", Id::RRO_C, Type::Arithmetic, "RRO_C"),
2079 INST("0101110010010---", Id::RRO_R, Type::Arithmetic, "RRO_R"), 2137 INST("0101110010010---", Id::RRO_R, Type::Arithmetic, "RRO_R"),
@@ -2098,6 +2156,7 @@ private:
2098 INST("0100110000000---", Id::BFE_C, Type::Bfe, "BFE_C"), 2156 INST("0100110000000---", Id::BFE_C, Type::Bfe, "BFE_C"),
2099 INST("0101110000000---", Id::BFE_R, Type::Bfe, "BFE_R"), 2157 INST("0101110000000---", Id::BFE_R, Type::Bfe, "BFE_R"),
2100 INST("0011100-00000---", Id::BFE_IMM, Type::Bfe, "BFE_IMM"), 2158 INST("0011100-00000---", Id::BFE_IMM, Type::Bfe, "BFE_IMM"),
2159 INST("0101001111110---", Id::BFI_RC, Type::Bfi, "BFI_RC"),
2101 INST("0011011-11110---", Id::BFI_IMM_R, Type::Bfi, "BFI_IMM_R"), 2160 INST("0011011-11110---", Id::BFI_IMM_R, Type::Bfi, "BFI_IMM_R"),
2102 INST("0100110001000---", Id::LOP_C, Type::ArithmeticInteger, "LOP_C"), 2161 INST("0100110001000---", Id::LOP_C, Type::ArithmeticInteger, "LOP_C"),
2103 INST("0101110001000---", Id::LOP_R, Type::ArithmeticInteger, "LOP_R"), 2162 INST("0101110001000---", Id::LOP_R, Type::ArithmeticInteger, "LOP_R"),
diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp
index b9c5c41a2..7d7137109 100644
--- a/src/video_core/gpu.cpp
+++ b/src/video_core/gpu.cpp
@@ -6,6 +6,7 @@
6#include "common/microprofile.h" 6#include "common/microprofile.h"
7#include "core/core.h" 7#include "core/core.h"
8#include "core/core_timing.h" 8#include "core/core_timing.h"
9#include "core/core_timing_util.h"
9#include "core/memory.h" 10#include "core/memory.h"
10#include "video_core/engines/fermi_2d.h" 11#include "video_core/engines/fermi_2d.h"
11#include "video_core/engines/kepler_compute.h" 12#include "video_core/engines/kepler_compute.h"
@@ -122,6 +123,19 @@ bool GPU::CancelSyncptInterrupt(const u32 syncpoint_id, const u32 value) {
122 return true; 123 return true;
123} 124}
124 125
126u64 GPU::GetTicks() const {
127 // This values were reversed engineered by fincs from NVN
128 // The gpu clock is reported in units of 385/625 nanoseconds
129 constexpr u64 gpu_ticks_num = 384;
130 constexpr u64 gpu_ticks_den = 625;
131
132 const u64 cpu_ticks = system.CoreTiming().GetTicks();
133 const u64 nanoseconds = Core::Timing::CyclesToNs(cpu_ticks).count();
134 const u64 nanoseconds_num = nanoseconds / gpu_ticks_den;
135 const u64 nanoseconds_rem = nanoseconds % gpu_ticks_den;
136 return nanoseconds_num * gpu_ticks_num + (nanoseconds_rem * gpu_ticks_num) / gpu_ticks_den;
137}
138
125void GPU::FlushCommands() { 139void GPU::FlushCommands() {
126 renderer.Rasterizer().FlushCommands(); 140 renderer.Rasterizer().FlushCommands();
127} 141}
@@ -340,7 +354,7 @@ void GPU::ProcessSemaphoreTriggerMethod() {
340 block.sequence = regs.semaphore_sequence; 354 block.sequence = regs.semaphore_sequence;
341 // TODO(Kmather73): Generate a real GPU timestamp and write it here instead of 355 // TODO(Kmather73): Generate a real GPU timestamp and write it here instead of
342 // CoreTiming 356 // CoreTiming
343 block.timestamp = system.CoreTiming().GetTicks(); 357 block.timestamp = GetTicks();
344 memory_manager->WriteBlock(regs.semaphore_address.SemaphoreAddress(), &block, 358 memory_manager->WriteBlock(regs.semaphore_address.SemaphoreAddress(), &block,
345 sizeof(block)); 359 sizeof(block));
346 } else { 360 } else {
diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h
index b648317bb..07727210c 100644
--- a/src/video_core/gpu.h
+++ b/src/video_core/gpu.h
@@ -192,6 +192,8 @@ public:
192 192
193 bool CancelSyncptInterrupt(u32 syncpoint_id, u32 value); 193 bool CancelSyncptInterrupt(u32 syncpoint_id, u32 value);
194 194
195 u64 GetTicks() const;
196
195 std::unique_lock<std::mutex> LockSync() { 197 std::unique_lock<std::mutex> LockSync() {
196 return std::unique_lock{sync_mutex}; 198 return std::unique_lock{sync_mutex};
197 } 199 }
diff --git a/src/video_core/gpu_thread.h b/src/video_core/gpu_thread.h
index 08dc96bb3..882e2d9c7 100644
--- a/src/video_core/gpu_thread.h
+++ b/src/video_core/gpu_thread.h
@@ -86,7 +86,7 @@ struct CommandDataContainer {
86struct SynchState final { 86struct SynchState final {
87 std::atomic_bool is_running{true}; 87 std::atomic_bool is_running{true};
88 88
89 using CommandQueue = Common::SPSCQueue<CommandDataContainer>; 89 using CommandQueue = Common::MPSCQueue<CommandDataContainer>;
90 CommandQueue queue; 90 CommandQueue queue;
91 u64 last_fence{}; 91 u64 last_fence{};
92 std::atomic<u64> signaled_fence{}; 92 std::atomic<u64> signaled_fence{};
diff --git a/src/video_core/guest_driver.cpp b/src/video_core/guest_driver.cpp
new file mode 100644
index 000000000..6adef459e
--- /dev/null
+++ b/src/video_core/guest_driver.cpp
@@ -0,0 +1,36 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include <limits>
7
8#include "video_core/guest_driver.h"
9
10namespace VideoCore {
11
12void GuestDriverProfile::DeduceTextureHandlerSize(std::vector<u32>&& bound_offsets) {
13 if (texture_handler_size_deduced) {
14 return;
15 }
16 const std::size_t size = bound_offsets.size();
17 if (size < 2) {
18 return;
19 }
20 std::sort(bound_offsets.begin(), bound_offsets.end(), std::less{});
21 u32 min_val = std::numeric_limits<u32>::max();
22 for (std::size_t i = 1; i < size; ++i) {
23 if (bound_offsets[i] == bound_offsets[i - 1]) {
24 continue;
25 }
26 const u32 new_min = bound_offsets[i] - bound_offsets[i - 1];
27 min_val = std::min(min_val, new_min);
28 }
29 if (min_val > 2) {
30 return;
31 }
32 texture_handler_size_deduced = true;
33 texture_handler_size = min_texture_handler_size * min_val;
34}
35
36} // namespace VideoCore
diff --git a/src/video_core/guest_driver.h b/src/video_core/guest_driver.h
new file mode 100644
index 000000000..fc1917347
--- /dev/null
+++ b/src/video_core/guest_driver.h
@@ -0,0 +1,41 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <vector>
8
9#include "common/common_types.h"
10
11namespace VideoCore {
12
13/**
14 * The GuestDriverProfile class is used to learn about the GPU drivers behavior and collect
15 * information necessary for impossible to avoid HLE methods like shader tracks as they are
16 * Entscheidungsproblems.
17 */
18class GuestDriverProfile {
19public:
20 void DeduceTextureHandlerSize(std::vector<u32>&& bound_offsets);
21
22 u32 GetTextureHandlerSize() const {
23 return texture_handler_size;
24 }
25
26 bool TextureHandlerSizeKnown() const {
27 return texture_handler_size_deduced;
28 }
29
30private:
31 // Minimum size of texture handler any driver can use.
32 static constexpr u32 min_texture_handler_size = 4;
33 // This goes with Vulkan and OpenGL standards but Nvidia GPUs can easily
34 // use 4 bytes instead. Thus, certain drivers may squish the size.
35 static constexpr u32 default_texture_handler_size = 8;
36
37 u32 texture_handler_size = default_texture_handler_size;
38 bool texture_handler_size_deduced = false;
39};
40
41} // namespace VideoCore
diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp
index 11848fbce..f5d33f27a 100644
--- a/src/video_core/memory_manager.cpp
+++ b/src/video_core/memory_manager.cpp
@@ -9,6 +9,7 @@
9#include "core/hle/kernel/process.h" 9#include "core/hle/kernel/process.h"
10#include "core/hle/kernel/vm_manager.h" 10#include "core/hle/kernel/vm_manager.h"
11#include "core/memory.h" 11#include "core/memory.h"
12#include "video_core/gpu.h"
12#include "video_core/memory_manager.h" 13#include "video_core/memory_manager.h"
13#include "video_core/rasterizer_interface.h" 14#include "video_core/rasterizer_interface.h"
14 15
@@ -84,7 +85,9 @@ GPUVAddr MemoryManager::UnmapBuffer(GPUVAddr gpu_addr, u64 size) {
84 const auto cpu_addr = GpuToCpuAddress(gpu_addr); 85 const auto cpu_addr = GpuToCpuAddress(gpu_addr);
85 ASSERT(cpu_addr); 86 ASSERT(cpu_addr);
86 87
87 rasterizer.FlushAndInvalidateRegion(cache_addr, aligned_size); 88 // Flush and invalidate through the GPU interface, to be asynchronous if possible.
89 system.GPU().FlushAndInvalidateRegion(cache_addr, aligned_size);
90
88 UnmapRange(gpu_addr, aligned_size); 91 UnmapRange(gpu_addr, aligned_size);
89 ASSERT(system.CurrentProcess() 92 ASSERT(system.CurrentProcess()
90 ->VMManager() 93 ->VMManager()
@@ -242,6 +245,8 @@ void MemoryManager::ReadBlock(GPUVAddr src_addr, void* dest_buffer, const std::s
242 switch (page_table.attributes[page_index]) { 245 switch (page_table.attributes[page_index]) {
243 case Common::PageType::Memory: { 246 case Common::PageType::Memory: {
244 const u8* src_ptr{page_table.pointers[page_index] + page_offset}; 247 const u8* src_ptr{page_table.pointers[page_index] + page_offset};
248 // Flush must happen on the rasterizer interface, such that memory is always synchronous
249 // when it is read (even when in asynchronous GPU mode). Fixes Dead Cells title menu.
245 rasterizer.FlushRegion(ToCacheAddr(src_ptr), copy_amount); 250 rasterizer.FlushRegion(ToCacheAddr(src_ptr), copy_amount);
246 std::memcpy(dest_buffer, src_ptr, copy_amount); 251 std::memcpy(dest_buffer, src_ptr, copy_amount);
247 break; 252 break;
@@ -292,6 +297,8 @@ void MemoryManager::WriteBlock(GPUVAddr dest_addr, const void* src_buffer, const
292 switch (page_table.attributes[page_index]) { 297 switch (page_table.attributes[page_index]) {
293 case Common::PageType::Memory: { 298 case Common::PageType::Memory: {
294 u8* dest_ptr{page_table.pointers[page_index] + page_offset}; 299 u8* dest_ptr{page_table.pointers[page_index] + page_offset};
300 // Invalidate must happen on the rasterizer interface, such that memory is always
301 // synchronous when it is written (even when in asynchronous GPU mode).
295 rasterizer.InvalidateRegion(ToCacheAddr(dest_ptr), copy_amount); 302 rasterizer.InvalidateRegion(ToCacheAddr(dest_ptr), copy_amount);
296 std::memcpy(dest_ptr, src_buffer, copy_amount); 303 std::memcpy(dest_ptr, src_buffer, copy_amount);
297 break; 304 break;
@@ -339,6 +346,8 @@ void MemoryManager::CopyBlock(GPUVAddr dest_addr, GPUVAddr src_addr, const std::
339 346
340 switch (page_table.attributes[page_index]) { 347 switch (page_table.attributes[page_index]) {
341 case Common::PageType::Memory: { 348 case Common::PageType::Memory: {
349 // Flush must happen on the rasterizer interface, such that memory is always synchronous
350 // when it is copied (even when in asynchronous GPU mode).
342 const u8* src_ptr{page_table.pointers[page_index] + page_offset}; 351 const u8* src_ptr{page_table.pointers[page_index] + page_offset};
343 rasterizer.FlushRegion(ToCacheAddr(src_ptr), copy_amount); 352 rasterizer.FlushRegion(ToCacheAddr(src_ptr), copy_amount);
344 WriteBlock(dest_addr, src_ptr, copy_amount); 353 WriteBlock(dest_addr, src_ptr, copy_amount);
diff --git a/src/video_core/query_cache.h b/src/video_core/query_cache.h
new file mode 100644
index 000000000..e66054ed0
--- /dev/null
+++ b/src/video_core/query_cache.h
@@ -0,0 +1,359 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <algorithm>
8#include <array>
9#include <cstring>
10#include <iterator>
11#include <memory>
12#include <mutex>
13#include <optional>
14#include <unordered_map>
15#include <vector>
16
17#include "common/assert.h"
18#include "core/core.h"
19#include "video_core/engines/maxwell_3d.h"
20#include "video_core/gpu.h"
21#include "video_core/memory_manager.h"
22#include "video_core/rasterizer_interface.h"
23
24namespace VideoCommon {
25
26template <class QueryCache, class HostCounter>
27class CounterStreamBase {
28public:
29 explicit CounterStreamBase(QueryCache& cache, VideoCore::QueryType type)
30 : cache{cache}, type{type} {}
31
32 /// Updates the state of the stream, enabling or disabling as needed.
33 void Update(bool enabled) {
34 if (enabled) {
35 Enable();
36 } else {
37 Disable();
38 }
39 }
40
41 /// Resets the stream to zero. It doesn't disable the query after resetting.
42 void Reset() {
43 if (current) {
44 current->EndQuery();
45
46 // Immediately start a new query to avoid disabling its state.
47 current = cache.Counter(nullptr, type);
48 }
49 last = nullptr;
50 }
51
52 /// Returns the current counter slicing as needed.
53 std::shared_ptr<HostCounter> Current() {
54 if (!current) {
55 return nullptr;
56 }
57 current->EndQuery();
58 last = std::move(current);
59 current = cache.Counter(last, type);
60 return last;
61 }
62
63 /// Returns true when the counter stream is enabled.
64 bool IsEnabled() const {
65 return current != nullptr;
66 }
67
68private:
69 /// Enables the stream.
70 void Enable() {
71 if (current) {
72 return;
73 }
74 current = cache.Counter(last, type);
75 }
76
77 // Disables the stream.
78 void Disable() {
79 if (current) {
80 current->EndQuery();
81 }
82 last = std::exchange(current, nullptr);
83 }
84
85 QueryCache& cache;
86 const VideoCore::QueryType type;
87
88 std::shared_ptr<HostCounter> current;
89 std::shared_ptr<HostCounter> last;
90};
91
92template <class QueryCache, class CachedQuery, class CounterStream, class HostCounter,
93 class QueryPool>
94class QueryCacheBase {
95public:
96 explicit QueryCacheBase(Core::System& system, VideoCore::RasterizerInterface& rasterizer)
97 : system{system}, rasterizer{rasterizer}, streams{{CounterStream{
98 static_cast<QueryCache&>(*this),
99 VideoCore::QueryType::SamplesPassed}}} {}
100
101 void InvalidateRegion(CacheAddr addr, std::size_t size) {
102 std::unique_lock lock{mutex};
103 FlushAndRemoveRegion(addr, size);
104 }
105
106 void FlushRegion(CacheAddr addr, std::size_t size) {
107 std::unique_lock lock{mutex};
108 FlushAndRemoveRegion(addr, size);
109 }
110
111 /**
112 * Records a query in GPU mapped memory, potentially marked with a timestamp.
113 * @param gpu_addr GPU address to flush to when the mapped memory is read.
114 * @param type Query type, e.g. SamplesPassed.
115 * @param timestamp Timestamp, when empty the flushed query is assumed to be short.
116 */
117 void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) {
118 std::unique_lock lock{mutex};
119 auto& memory_manager = system.GPU().MemoryManager();
120 const auto host_ptr = memory_manager.GetPointer(gpu_addr);
121
122 CachedQuery* query = TryGet(ToCacheAddr(host_ptr));
123 if (!query) {
124 const auto cpu_addr = memory_manager.GpuToCpuAddress(gpu_addr);
125 ASSERT_OR_EXECUTE(cpu_addr, return;);
126
127 query = Register(type, *cpu_addr, host_ptr, timestamp.has_value());
128 }
129
130 query->BindCounter(Stream(type).Current(), timestamp);
131 }
132
133 /// Updates counters from GPU state. Expected to be called once per draw, clear or dispatch.
134 void UpdateCounters() {
135 std::unique_lock lock{mutex};
136 const auto& regs = system.GPU().Maxwell3D().regs;
137 Stream(VideoCore::QueryType::SamplesPassed).Update(regs.samplecnt_enable);
138 }
139
140 /// Resets a counter to zero. It doesn't disable the query after resetting.
141 void ResetCounter(VideoCore::QueryType type) {
142 std::unique_lock lock{mutex};
143 Stream(type).Reset();
144 }
145
146 /// Disable all active streams. Expected to be called at the end of a command buffer.
147 void DisableStreams() {
148 std::unique_lock lock{mutex};
149 for (auto& stream : streams) {
150 stream.Update(false);
151 }
152 }
153
154 /// Returns a new host counter.
155 std::shared_ptr<HostCounter> Counter(std::shared_ptr<HostCounter> dependency,
156 VideoCore::QueryType type) {
157 return std::make_shared<HostCounter>(static_cast<QueryCache&>(*this), std::move(dependency),
158 type);
159 }
160
161 /// Returns the counter stream of the specified type.
162 CounterStream& Stream(VideoCore::QueryType type) {
163 return streams[static_cast<std::size_t>(type)];
164 }
165
166 /// Returns the counter stream of the specified type.
167 const CounterStream& Stream(VideoCore::QueryType type) const {
168 return streams[static_cast<std::size_t>(type)];
169 }
170
171protected:
172 std::array<QueryPool, VideoCore::NumQueryTypes> query_pools;
173
174private:
175 /// Flushes a memory range to guest memory and removes it from the cache.
176 void FlushAndRemoveRegion(CacheAddr addr, std::size_t size) {
177 const u64 addr_begin = static_cast<u64>(addr);
178 const u64 addr_end = addr_begin + static_cast<u64>(size);
179 const auto in_range = [addr_begin, addr_end](CachedQuery& query) {
180 const u64 cache_begin = query.GetCacheAddr();
181 const u64 cache_end = cache_begin + query.SizeInBytes();
182 return cache_begin < addr_end && addr_begin < cache_end;
183 };
184
185 const u64 page_end = addr_end >> PAGE_SHIFT;
186 for (u64 page = addr_begin >> PAGE_SHIFT; page <= page_end; ++page) {
187 const auto& it = cached_queries.find(page);
188 if (it == std::end(cached_queries)) {
189 continue;
190 }
191 auto& contents = it->second;
192 for (auto& query : contents) {
193 if (!in_range(query)) {
194 continue;
195 }
196 rasterizer.UpdatePagesCachedCount(query.CpuAddr(), query.SizeInBytes(), -1);
197 query.Flush();
198 }
199 contents.erase(std::remove_if(std::begin(contents), std::end(contents), in_range),
200 std::end(contents));
201 }
202 }
203
204 /// Registers the passed parameters as cached and returns a pointer to the stored cached query.
205 CachedQuery* Register(VideoCore::QueryType type, VAddr cpu_addr, u8* host_ptr, bool timestamp) {
206 rasterizer.UpdatePagesCachedCount(cpu_addr, CachedQuery::SizeInBytes(timestamp), 1);
207 const u64 page = static_cast<u64>(ToCacheAddr(host_ptr)) >> PAGE_SHIFT;
208 return &cached_queries[page].emplace_back(static_cast<QueryCache&>(*this), type, cpu_addr,
209 host_ptr);
210 }
211
212 /// Tries to a get a cached query. Returns nullptr on failure.
213 CachedQuery* TryGet(CacheAddr addr) {
214 const u64 page = static_cast<u64>(addr) >> PAGE_SHIFT;
215 const auto it = cached_queries.find(page);
216 if (it == std::end(cached_queries)) {
217 return nullptr;
218 }
219 auto& contents = it->second;
220 const auto found =
221 std::find_if(std::begin(contents), std::end(contents),
222 [addr](auto& query) { return query.GetCacheAddr() == addr; });
223 return found != std::end(contents) ? &*found : nullptr;
224 }
225
226 static constexpr std::uintptr_t PAGE_SIZE = 4096;
227 static constexpr unsigned PAGE_SHIFT = 12;
228
229 Core::System& system;
230 VideoCore::RasterizerInterface& rasterizer;
231
232 std::recursive_mutex mutex;
233
234 std::unordered_map<u64, std::vector<CachedQuery>> cached_queries;
235
236 std::array<CounterStream, VideoCore::NumQueryTypes> streams;
237};
238
239template <class QueryCache, class HostCounter>
240class HostCounterBase {
241public:
242 explicit HostCounterBase(std::shared_ptr<HostCounter> dependency_)
243 : dependency{std::move(dependency_)}, depth{dependency ? (dependency->Depth() + 1) : 0} {
244 // Avoid nesting too many dependencies to avoid a stack overflow when these are deleted.
245 constexpr u64 depth_threshold = 96;
246 if (depth > depth_threshold) {
247 depth = 0;
248 base_result = dependency->Query();
249 dependency = nullptr;
250 }
251 }
252 virtual ~HostCounterBase() = default;
253
254 /// Returns the current value of the query.
255 u64 Query() {
256 if (result) {
257 return *result;
258 }
259
260 u64 value = BlockingQuery() + base_result;
261 if (dependency) {
262 value += dependency->Query();
263 dependency = nullptr;
264 }
265
266 result = value;
267 return *result;
268 }
269
270 /// Returns true when flushing this query will potentially wait.
271 bool WaitPending() const noexcept {
272 return result.has_value();
273 }
274
275 u64 Depth() const noexcept {
276 return depth;
277 }
278
279protected:
280 /// Returns the value of query from the backend API blocking as needed.
281 virtual u64 BlockingQuery() const = 0;
282
283private:
284 std::shared_ptr<HostCounter> dependency; ///< Counter to add to this value.
285 std::optional<u64> result; ///< Filled with the already returned value.
286 u64 depth; ///< Number of nested dependencies.
287 u64 base_result = 0; ///< Equivalent to nested dependencies value.
288};
289
290template <class HostCounter>
291class CachedQueryBase {
292public:
293 explicit CachedQueryBase(VAddr cpu_addr, u8* host_ptr)
294 : cpu_addr{cpu_addr}, host_ptr{host_ptr} {}
295 virtual ~CachedQueryBase() = default;
296
297 CachedQueryBase(CachedQueryBase&&) noexcept = default;
298 CachedQueryBase(const CachedQueryBase&) = delete;
299
300 CachedQueryBase& operator=(CachedQueryBase&&) noexcept = default;
301 CachedQueryBase& operator=(const CachedQueryBase&) = delete;
302
303 /// Flushes the query to guest memory.
304 virtual void Flush() {
305 // When counter is nullptr it means that it's just been reseted. We are supposed to write a
306 // zero in these cases.
307 const u64 value = counter ? counter->Query() : 0;
308 std::memcpy(host_ptr, &value, sizeof(u64));
309
310 if (timestamp) {
311 std::memcpy(host_ptr + TIMESTAMP_OFFSET, &*timestamp, sizeof(u64));
312 }
313 }
314
315 /// Binds a counter to this query.
316 void BindCounter(std::shared_ptr<HostCounter> counter_, std::optional<u64> timestamp_) {
317 if (counter) {
318 // If there's an old counter set it means the query is being rewritten by the game.
319 // To avoid losing the data forever, flush here.
320 Flush();
321 }
322 counter = std::move(counter_);
323 timestamp = timestamp_;
324 }
325
326 VAddr CpuAddr() const noexcept {
327 return cpu_addr;
328 }
329
330 CacheAddr GetCacheAddr() const noexcept {
331 return ToCacheAddr(host_ptr);
332 }
333
334 u64 SizeInBytes() const noexcept {
335 return SizeInBytes(timestamp.has_value());
336 }
337
338 static constexpr u64 SizeInBytes(bool with_timestamp) noexcept {
339 return with_timestamp ? LARGE_QUERY_SIZE : SMALL_QUERY_SIZE;
340 }
341
342protected:
343 /// Returns true when querying the counter may potentially block.
344 bool WaitPending() const noexcept {
345 return counter && counter->WaitPending();
346 }
347
348private:
349 static constexpr std::size_t SMALL_QUERY_SIZE = 8; // Query size without timestamp.
350 static constexpr std::size_t LARGE_QUERY_SIZE = 16; // Query size with timestamp.
351 static constexpr std::intptr_t TIMESTAMP_OFFSET = 8; // Timestamp offset in a large query.
352
353 VAddr cpu_addr; ///< Guest CPU address.
354 u8* host_ptr; ///< Writable host pointer.
355 std::shared_ptr<HostCounter> counter; ///< Host counter to query, owns the dependency tree.
356 std::optional<u64> timestamp; ///< Timestamp to flush to guest memory.
357};
358
359} // namespace VideoCommon
diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h
index 5b0eca9e2..f18eaf4bc 100644
--- a/src/video_core/rasterizer_interface.h
+++ b/src/video_core/rasterizer_interface.h
@@ -6,9 +6,11 @@
6 6
7#include <atomic> 7#include <atomic>
8#include <functional> 8#include <functional>
9#include <optional>
9#include "common/common_types.h" 10#include "common/common_types.h"
10#include "video_core/engines/fermi_2d.h" 11#include "video_core/engines/fermi_2d.h"
11#include "video_core/gpu.h" 12#include "video_core/gpu.h"
13#include "video_core/guest_driver.h"
12 14
13namespace Tegra { 15namespace Tegra {
14class MemoryManager; 16class MemoryManager;
@@ -16,6 +18,11 @@ class MemoryManager;
16 18
17namespace VideoCore { 19namespace VideoCore {
18 20
21enum class QueryType {
22 SamplesPassed,
23};
24constexpr std::size_t NumQueryTypes = 1;
25
19enum class LoadCallbackStage { 26enum class LoadCallbackStage {
20 Prepare, 27 Prepare,
21 Decompile, 28 Decompile,
@@ -28,11 +35,8 @@ class RasterizerInterface {
28public: 35public:
29 virtual ~RasterizerInterface() {} 36 virtual ~RasterizerInterface() {}
30 37
31 /// Draw the current batch of vertex arrays 38 /// Dispatches a draw invocation
32 virtual bool DrawBatch(bool is_indexed) = 0; 39 virtual void Draw(bool is_indexed, bool is_instanced) = 0;
33
34 /// Draw the current batch of multiple instances of vertex arrays
35 virtual bool DrawMultiBatch(bool is_indexed) = 0;
36 40
37 /// Clear the current framebuffer 41 /// Clear the current framebuffer
38 virtual void Clear() = 0; 42 virtual void Clear() = 0;
@@ -40,6 +44,12 @@ public:
40 /// Dispatches a compute shader invocation 44 /// Dispatches a compute shader invocation
41 virtual void DispatchCompute(GPUVAddr code_addr) = 0; 45 virtual void DispatchCompute(GPUVAddr code_addr) = 0;
42 46
47 /// Resets the counter of a query
48 virtual void ResetCounter(QueryType type) = 0;
49
50 /// Records a GPU query and caches it
51 virtual void Query(GPUVAddr gpu_addr, QueryType type, std::optional<u64> timestamp) = 0;
52
43 /// Notify rasterizer that all caches should be flushed to Switch memory 53 /// Notify rasterizer that all caches should be flushed to Switch memory
44 virtual void FlushAll() = 0; 54 virtual void FlushAll() = 0;
45 55
@@ -78,5 +88,18 @@ public:
78 /// Initialize disk cached resources for the game being emulated 88 /// Initialize disk cached resources for the game being emulated
79 virtual void LoadDiskResources(const std::atomic_bool& stop_loading = false, 89 virtual void LoadDiskResources(const std::atomic_bool& stop_loading = false,
80 const DiskResourceLoadCallback& callback = {}) {} 90 const DiskResourceLoadCallback& callback = {}) {}
91
92 /// Grant access to the Guest Driver Profile for recording/obtaining info on the guest driver.
93 GuestDriverProfile& AccessGuestDriverProfile() {
94 return guest_driver_profile;
95 }
96
97 /// Grant access to the Guest Driver Profile for recording/obtaining info on the guest driver.
98 const GuestDriverProfile& AccessGuestDriverProfile() const {
99 return guest_driver_profile;
100 }
101
102private:
103 GuestDriverProfile guest_driver_profile{};
81}; 104};
82} // namespace VideoCore 105} // namespace VideoCore
diff --git a/src/video_core/renderer_opengl/gl_query_cache.cpp b/src/video_core/renderer_opengl/gl_query_cache.cpp
new file mode 100644
index 000000000..f12e9f55f
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_query_cache.cpp
@@ -0,0 +1,120 @@
1// Copyright 2019 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include <cstring>
7#include <memory>
8#include <unordered_map>
9#include <utility>
10#include <vector>
11
12#include <glad/glad.h>
13
14#include "common/assert.h"
15#include "core/core.h"
16#include "video_core/engines/maxwell_3d.h"
17#include "video_core/memory_manager.h"
18#include "video_core/renderer_opengl/gl_query_cache.h"
19#include "video_core/renderer_opengl/gl_rasterizer.h"
20
21namespace OpenGL {
22
23namespace {
24
25constexpr std::array<GLenum, VideoCore::NumQueryTypes> QueryTargets = {GL_SAMPLES_PASSED};
26
27constexpr GLenum GetTarget(VideoCore::QueryType type) {
28 return QueryTargets[static_cast<std::size_t>(type)];
29}
30
31} // Anonymous namespace
32
33QueryCache::QueryCache(Core::System& system, RasterizerOpenGL& gl_rasterizer)
34 : VideoCommon::QueryCacheBase<
35 QueryCache, CachedQuery, CounterStream, HostCounter,
36 std::vector<OGLQuery>>{system,
37 static_cast<VideoCore::RasterizerInterface&>(gl_rasterizer)},
38 gl_rasterizer{gl_rasterizer} {}
39
40QueryCache::~QueryCache() = default;
41
42OGLQuery QueryCache::AllocateQuery(VideoCore::QueryType type) {
43 auto& reserve = query_pools[static_cast<std::size_t>(type)];
44 OGLQuery query;
45 if (reserve.empty()) {
46 query.Create(GetTarget(type));
47 return query;
48 }
49
50 query = std::move(reserve.back());
51 reserve.pop_back();
52 return query;
53}
54
55void QueryCache::Reserve(VideoCore::QueryType type, OGLQuery&& query) {
56 query_pools[static_cast<std::size_t>(type)].push_back(std::move(query));
57}
58
59bool QueryCache::AnyCommandQueued() const noexcept {
60 return gl_rasterizer.AnyCommandQueued();
61}
62
63HostCounter::HostCounter(QueryCache& cache, std::shared_ptr<HostCounter> dependency,
64 VideoCore::QueryType type)
65 : VideoCommon::HostCounterBase<QueryCache, HostCounter>{std::move(dependency)}, cache{cache},
66 type{type}, query{cache.AllocateQuery(type)} {
67 glBeginQuery(GetTarget(type), query.handle);
68}
69
70HostCounter::~HostCounter() {
71 cache.Reserve(type, std::move(query));
72}
73
74void HostCounter::EndQuery() {
75 if (!cache.AnyCommandQueued()) {
76 // There are chances a query waited on without commands (glDraw, glClear, glDispatch). Not
77 // having any of these causes a lock. glFlush is considered a command, so we can safely wait
78 // for this. Insert to the OpenGL command stream a flush.
79 glFlush();
80 }
81 glEndQuery(GetTarget(type));
82}
83
84u64 HostCounter::BlockingQuery() const {
85 GLint64 value;
86 glGetQueryObjecti64v(query.handle, GL_QUERY_RESULT, &value);
87 return static_cast<u64>(value);
88}
89
90CachedQuery::CachedQuery(QueryCache& cache, VideoCore::QueryType type, VAddr cpu_addr, u8* host_ptr)
91 : VideoCommon::CachedQueryBase<HostCounter>{cpu_addr, host_ptr}, cache{&cache}, type{type} {}
92
93CachedQuery::CachedQuery(CachedQuery&& rhs) noexcept
94 : VideoCommon::CachedQueryBase<HostCounter>(std::move(rhs)), cache{rhs.cache}, type{rhs.type} {}
95
96CachedQuery& CachedQuery::operator=(CachedQuery&& rhs) noexcept {
97 VideoCommon::CachedQueryBase<HostCounter>::operator=(std::move(rhs));
98 cache = rhs.cache;
99 type = rhs.type;
100 return *this;
101}
102
103void CachedQuery::Flush() {
104 // Waiting for a query while another query of the same target is enabled locks Nvidia's driver.
105 // To avoid this disable and re-enable keeping the dependency stream.
106 // But we only have to do this if we have pending waits to be done.
107 auto& stream = cache->Stream(type);
108 const bool slice_counter = WaitPending() && stream.IsEnabled();
109 if (slice_counter) {
110 stream.Update(false);
111 }
112
113 VideoCommon::CachedQueryBase<HostCounter>::Flush();
114
115 if (slice_counter) {
116 stream.Update(true);
117 }
118}
119
120} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_query_cache.h b/src/video_core/renderer_opengl/gl_query_cache.h
new file mode 100644
index 000000000..d8e7052a1
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_query_cache.h
@@ -0,0 +1,78 @@
1// Copyright 2019 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <memory>
9#include <vector>
10
11#include "common/common_types.h"
12#include "video_core/query_cache.h"
13#include "video_core/rasterizer_interface.h"
14#include "video_core/renderer_opengl/gl_resource_manager.h"
15
16namespace Core {
17class System;
18}
19
20namespace OpenGL {
21
22class CachedQuery;
23class HostCounter;
24class QueryCache;
25class RasterizerOpenGL;
26
27using CounterStream = VideoCommon::CounterStreamBase<QueryCache, HostCounter>;
28
29class QueryCache final : public VideoCommon::QueryCacheBase<QueryCache, CachedQuery, CounterStream,
30 HostCounter, std::vector<OGLQuery>> {
31public:
32 explicit QueryCache(Core::System& system, RasterizerOpenGL& rasterizer);
33 ~QueryCache();
34
35 OGLQuery AllocateQuery(VideoCore::QueryType type);
36
37 void Reserve(VideoCore::QueryType type, OGLQuery&& query);
38
39 bool AnyCommandQueued() const noexcept;
40
41private:
42 RasterizerOpenGL& gl_rasterizer;
43};
44
45class HostCounter final : public VideoCommon::HostCounterBase<QueryCache, HostCounter> {
46public:
47 explicit HostCounter(QueryCache& cache, std::shared_ptr<HostCounter> dependency,
48 VideoCore::QueryType type);
49 ~HostCounter();
50
51 void EndQuery();
52
53private:
54 u64 BlockingQuery() const override;
55
56 QueryCache& cache;
57 const VideoCore::QueryType type;
58 OGLQuery query;
59};
60
61class CachedQuery final : public VideoCommon::CachedQueryBase<HostCounter> {
62public:
63 explicit CachedQuery(QueryCache& cache, VideoCore::QueryType type, VAddr cpu_addr,
64 u8* host_ptr);
65 CachedQuery(CachedQuery&& rhs) noexcept;
66 CachedQuery(const CachedQuery&) = delete;
67
68 CachedQuery& operator=(CachedQuery&& rhs) noexcept;
69 CachedQuery& operator=(const CachedQuery&) = delete;
70
71 void Flush() override;
72
73private:
74 QueryCache* cache;
75 VideoCore::QueryType type;
76};
77
78} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index c428f06e4..e1965fb21 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -25,6 +25,7 @@
25#include "video_core/engines/maxwell_3d.h" 25#include "video_core/engines/maxwell_3d.h"
26#include "video_core/engines/shader_type.h" 26#include "video_core/engines/shader_type.h"
27#include "video_core/memory_manager.h" 27#include "video_core/memory_manager.h"
28#include "video_core/renderer_opengl/gl_query_cache.h"
28#include "video_core/renderer_opengl/gl_rasterizer.h" 29#include "video_core/renderer_opengl/gl_rasterizer.h"
29#include "video_core/renderer_opengl/gl_shader_cache.h" 30#include "video_core/renderer_opengl/gl_shader_cache.h"
30#include "video_core/renderer_opengl/gl_shader_gen.h" 31#include "video_core/renderer_opengl/gl_shader_gen.h"
@@ -55,16 +56,20 @@ namespace {
55 56
56template <typename Engine, typename Entry> 57template <typename Engine, typename Entry>
57Tegra::Texture::FullTextureInfo GetTextureInfo(const Engine& engine, const Entry& entry, 58Tegra::Texture::FullTextureInfo GetTextureInfo(const Engine& engine, const Entry& entry,
58 Tegra::Engines::ShaderType shader_type) { 59 Tegra::Engines::ShaderType shader_type,
60 std::size_t index = 0) {
59 if (entry.IsBindless()) { 61 if (entry.IsBindless()) {
60 const Tegra::Texture::TextureHandle tex_handle = 62 const Tegra::Texture::TextureHandle tex_handle =
61 engine.AccessConstBuffer32(shader_type, entry.GetBuffer(), entry.GetOffset()); 63 engine.AccessConstBuffer32(shader_type, entry.GetBuffer(), entry.GetOffset());
62 return engine.GetTextureInfo(tex_handle); 64 return engine.GetTextureInfo(tex_handle);
63 } 65 }
66 const auto& gpu_profile = engine.AccessGuestDriverProfile();
67 const u32 offset =
68 entry.GetOffset() + static_cast<u32>(index * gpu_profile.GetTextureHandlerSize());
64 if constexpr (std::is_same_v<Engine, Tegra::Engines::Maxwell3D>) { 69 if constexpr (std::is_same_v<Engine, Tegra::Engines::Maxwell3D>) {
65 return engine.GetStageTexture(shader_type, entry.GetOffset()); 70 return engine.GetStageTexture(shader_type, offset);
66 } else { 71 } else {
67 return engine.GetTexture(entry.GetOffset()); 72 return engine.GetTexture(offset);
68 } 73 }
69} 74}
70 75
@@ -88,8 +93,8 @@ std::size_t GetConstBufferSize(const Tegra::Engines::ConstBufferInfo& buffer,
88RasterizerOpenGL::RasterizerOpenGL(Core::System& system, Core::Frontend::EmuWindow& emu_window, 93RasterizerOpenGL::RasterizerOpenGL(Core::System& system, Core::Frontend::EmuWindow& emu_window,
89 ScreenInfo& info) 94 ScreenInfo& info)
90 : RasterizerAccelerated{system.Memory()}, texture_cache{system, *this, device}, 95 : RasterizerAccelerated{system.Memory()}, texture_cache{system, *this, device},
91 shader_cache{*this, system, emu_window, device}, system{system}, screen_info{info}, 96 shader_cache{*this, system, emu_window, device}, query_cache{system, *this}, system{system},
92 buffer_cache{*this, system, device, STREAM_BUFFER_SIZE} { 97 screen_info{info}, buffer_cache{*this, system, device, STREAM_BUFFER_SIZE} {
93 shader_program_manager = std::make_unique<GLShader::ProgramManager>(); 98 shader_program_manager = std::make_unique<GLShader::ProgramManager>();
94 state.draw.shader_program = 0; 99 state.draw.shader_program = 0;
95 state.Apply(); 100 state.Apply();
@@ -244,9 +249,6 @@ void RasterizerOpenGL::SetupVertexInstances(GLuint vao) {
244} 249}
245 250
246GLintptr RasterizerOpenGL::SetupIndexBuffer() { 251GLintptr RasterizerOpenGL::SetupIndexBuffer() {
247 if (accelerate_draw != AccelDraw::Indexed) {
248 return 0;
249 }
250 MICROPROFILE_SCOPE(OpenGL_Index); 252 MICROPROFILE_SCOPE(OpenGL_Index);
251 const auto& regs = system.GPU().Maxwell3D().regs; 253 const auto& regs = system.GPU().Maxwell3D().regs;
252 const std::size_t size = CalculateIndexBufferSize(); 254 const std::size_t size = CalculateIndexBufferSize();
@@ -540,10 +542,16 @@ void RasterizerOpenGL::Clear() {
540 } else if (use_stencil) { 542 } else if (use_stencil) {
541 glClearBufferiv(GL_STENCIL, 0, &regs.clear_stencil); 543 glClearBufferiv(GL_STENCIL, 0, &regs.clear_stencil);
542 } 544 }
545
546 ++num_queued_commands;
543} 547}
544 548
545void RasterizerOpenGL::DrawPrelude() { 549void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
550 MICROPROFILE_SCOPE(OpenGL_Drawing);
546 auto& gpu = system.GPU().Maxwell3D(); 551 auto& gpu = system.GPU().Maxwell3D();
552 const auto& regs = gpu.regs;
553
554 query_cache.UpdateCounters();
547 555
548 SyncRasterizeEnable(state); 556 SyncRasterizeEnable(state);
549 SyncColorMask(); 557 SyncColorMask();
@@ -563,9 +571,6 @@ void RasterizerOpenGL::DrawPrelude() {
563 571
564 buffer_cache.Acquire(); 572 buffer_cache.Acquire();
565 573
566 // Draw the vertex batch
567 const bool is_indexed = accelerate_draw == AccelDraw::Indexed;
568
569 std::size_t buffer_size = CalculateVertexArraysSize(); 574 std::size_t buffer_size = CalculateVertexArraysSize();
570 575
571 // Add space for index buffer 576 // Add space for index buffer
@@ -592,7 +597,11 @@ void RasterizerOpenGL::DrawPrelude() {
592 // Upload vertex and index data. 597 // Upload vertex and index data.
593 SetupVertexBuffer(vao); 598 SetupVertexBuffer(vao);
594 SetupVertexInstances(vao); 599 SetupVertexInstances(vao);
595 index_buffer_offset = SetupIndexBuffer(); 600
601 GLintptr index_buffer_offset;
602 if (is_indexed) {
603 index_buffer_offset = SetupIndexBuffer();
604 }
596 605
597 // Prepare packed bindings. 606 // Prepare packed bindings.
598 bind_ubo_pushbuffer.Setup(); 607 bind_ubo_pushbuffer.Setup();
@@ -608,7 +617,7 @@ void RasterizerOpenGL::DrawPrelude() {
608 617
609 // Setup shaders and their used resources. 618 // Setup shaders and their used resources.
610 texture_cache.GuardSamplers(true); 619 texture_cache.GuardSamplers(true);
611 const auto primitive_mode = MaxwellToGL::PrimitiveTopology(gpu.regs.draw.topology); 620 const GLenum primitive_mode = MaxwellToGL::PrimitiveTopology(gpu.regs.draw.topology);
612 SetupShaders(primitive_mode); 621 SetupShaders(primitive_mode);
613 texture_cache.GuardSamplers(false); 622 texture_cache.GuardSamplers(false);
614 623
@@ -626,6 +635,7 @@ void RasterizerOpenGL::DrawPrelude() {
626 // As all cached buffers are invalidated, we need to recheck their state. 635 // As all cached buffers are invalidated, we need to recheck their state.
627 gpu.dirty.ResetVertexArrays(); 636 gpu.dirty.ResetVertexArrays();
628 } 637 }
638 gpu.dirty.memory_general = false;
629 639
630 shader_program_manager->ApplyTo(state); 640 shader_program_manager->ApplyTo(state);
631 state.Apply(); 641 state.Apply();
@@ -633,107 +643,46 @@ void RasterizerOpenGL::DrawPrelude() {
633 if (texture_cache.TextureBarrier()) { 643 if (texture_cache.TextureBarrier()) {
634 glTextureBarrier(); 644 glTextureBarrier();
635 } 645 }
636}
637 646
638struct DrawParams { 647 ++num_queued_commands;
639 bool is_indexed{}; 648
640 bool is_instanced{}; 649 const GLuint base_instance = static_cast<GLuint>(gpu.regs.vb_base_instance);
641 GLenum primitive_mode{}; 650 const GLsizei num_instances =
642 GLint count{}; 651 static_cast<GLsizei>(is_instanced ? gpu.mme_draw.instance_count : 1);
643 GLint base_vertex{}; 652 if (is_indexed) {
644 653 const GLint base_vertex = static_cast<GLint>(gpu.regs.vb_element_base);
645 // Indexed settings 654 const GLsizei num_vertices = static_cast<GLsizei>(gpu.regs.index_array.count);
646 GLenum index_format{}; 655 const GLvoid* offset = reinterpret_cast<const GLvoid*>(index_buffer_offset);
647 GLintptr index_buffer_offset{}; 656 const GLenum format = MaxwellToGL::IndexFormat(gpu.regs.index_array.format);
648 657 if (num_instances == 1 && base_instance == 0 && base_vertex == 0) {
649 // Instanced setting 658 glDrawElements(primitive_mode, num_vertices, format, offset);
650 GLint num_instances{}; 659 } else if (num_instances == 1 && base_instance == 0) {
651 GLint base_instance{}; 660 glDrawElementsBaseVertex(primitive_mode, num_vertices, format, offset, base_vertex);
652 661 } else if (base_vertex == 0 && base_instance == 0) {
653 void DispatchDraw() { 662 glDrawElementsInstanced(primitive_mode, num_vertices, format, offset, num_instances);
654 if (is_indexed) { 663 } else if (base_vertex == 0) {
655 const auto index_buffer_ptr = reinterpret_cast<const void*>(index_buffer_offset); 664 glDrawElementsInstancedBaseInstance(primitive_mode, num_vertices, format, offset,
656 if (is_instanced) { 665 num_instances, base_instance);
657 glDrawElementsInstancedBaseVertexBaseInstance(primitive_mode, count, index_format, 666 } else if (base_instance == 0) {
658 index_buffer_ptr, num_instances, 667 glDrawElementsInstancedBaseVertex(primitive_mode, num_vertices, format, offset,
659 base_vertex, base_instance); 668 num_instances, base_vertex);
660 } else {
661 glDrawElementsBaseVertex(primitive_mode, count, index_format, index_buffer_ptr,
662 base_vertex);
663 }
664 } else { 669 } else {
665 if (is_instanced) { 670 glDrawElementsInstancedBaseVertexBaseInstance(primitive_mode, num_vertices, format,
666 glDrawArraysInstancedBaseInstance(primitive_mode, base_vertex, count, num_instances, 671 offset, num_instances, base_vertex,
667 base_instance); 672 base_instance);
668 } else {
669 glDrawArrays(primitive_mode, base_vertex, count);
670 }
671 } 673 }
672 }
673};
674
675bool RasterizerOpenGL::DrawBatch(bool is_indexed) {
676 accelerate_draw = is_indexed ? AccelDraw::Indexed : AccelDraw::Arrays;
677
678 MICROPROFILE_SCOPE(OpenGL_Drawing);
679
680 DrawPrelude();
681
682 auto& maxwell3d = system.GPU().Maxwell3D();
683 const auto& regs = maxwell3d.regs;
684 const auto current_instance = maxwell3d.state.current_instance;
685 DrawParams draw_call{};
686 draw_call.is_indexed = is_indexed;
687 draw_call.num_instances = static_cast<GLint>(1);
688 draw_call.base_instance = static_cast<GLint>(current_instance);
689 draw_call.is_instanced = current_instance > 0;
690 draw_call.primitive_mode = MaxwellToGL::PrimitiveTopology(regs.draw.topology);
691 if (draw_call.is_indexed) {
692 draw_call.count = static_cast<GLint>(regs.index_array.count);
693 draw_call.base_vertex = static_cast<GLint>(regs.vb_element_base);
694 draw_call.index_format = MaxwellToGL::IndexFormat(regs.index_array.format);
695 draw_call.index_buffer_offset = index_buffer_offset;
696 } else { 674 } else {
697 draw_call.count = static_cast<GLint>(regs.vertex_buffer.count); 675 const GLint base_vertex = static_cast<GLint>(gpu.regs.vertex_buffer.first);
698 draw_call.base_vertex = static_cast<GLint>(regs.vertex_buffer.first); 676 const GLsizei num_vertices = static_cast<GLsizei>(gpu.regs.vertex_buffer.count);
699 } 677 if (num_instances == 1 && base_instance == 0) {
700 draw_call.DispatchDraw(); 678 glDrawArrays(primitive_mode, base_vertex, num_vertices);
701 679 } else if (base_instance == 0) {
702 maxwell3d.dirty.memory_general = false; 680 glDrawArraysInstanced(primitive_mode, base_vertex, num_vertices, num_instances);
703 accelerate_draw = AccelDraw::Disabled; 681 } else {
704 return true; 682 glDrawArraysInstancedBaseInstance(primitive_mode, base_vertex, num_vertices,
705} 683 num_instances, base_instance);
706 684 }
707bool RasterizerOpenGL::DrawMultiBatch(bool is_indexed) {
708 accelerate_draw = is_indexed ? AccelDraw::Indexed : AccelDraw::Arrays;
709
710 MICROPROFILE_SCOPE(OpenGL_Drawing);
711
712 DrawPrelude();
713
714 auto& maxwell3d = system.GPU().Maxwell3D();
715 const auto& regs = maxwell3d.regs;
716 const auto& draw_setup = maxwell3d.mme_draw;
717 DrawParams draw_call{};
718 draw_call.is_indexed = is_indexed;
719 draw_call.num_instances = static_cast<GLint>(draw_setup.instance_count);
720 draw_call.base_instance = static_cast<GLint>(regs.vb_base_instance);
721 draw_call.is_instanced = draw_setup.instance_count > 1;
722 draw_call.primitive_mode = MaxwellToGL::PrimitiveTopology(regs.draw.topology);
723 if (draw_call.is_indexed) {
724 draw_call.count = static_cast<GLint>(regs.index_array.count);
725 draw_call.base_vertex = static_cast<GLint>(regs.vb_element_base);
726 draw_call.index_format = MaxwellToGL::IndexFormat(regs.index_array.format);
727 draw_call.index_buffer_offset = index_buffer_offset;
728 } else {
729 draw_call.count = static_cast<GLint>(regs.vertex_buffer.count);
730 draw_call.base_vertex = static_cast<GLint>(regs.vertex_buffer.first);
731 } 685 }
732 draw_call.DispatchDraw();
733
734 maxwell3d.dirty.memory_general = false;
735 accelerate_draw = AccelDraw::Disabled;
736 return true;
737} 686}
738 687
739void RasterizerOpenGL::DispatchCompute(GPUVAddr code_addr) { 688void RasterizerOpenGL::DispatchCompute(GPUVAddr code_addr) {
@@ -776,6 +725,16 @@ void RasterizerOpenGL::DispatchCompute(GPUVAddr code_addr) {
776 state.ApplyProgramPipeline(); 725 state.ApplyProgramPipeline();
777 726
778 glDispatchCompute(launch_desc.grid_dim_x, launch_desc.grid_dim_y, launch_desc.grid_dim_z); 727 glDispatchCompute(launch_desc.grid_dim_x, launch_desc.grid_dim_y, launch_desc.grid_dim_z);
728 ++num_queued_commands;
729}
730
731void RasterizerOpenGL::ResetCounter(VideoCore::QueryType type) {
732 query_cache.ResetCounter(type);
733}
734
735void RasterizerOpenGL::Query(GPUVAddr gpu_addr, VideoCore::QueryType type,
736 std::optional<u64> timestamp) {
737 query_cache.Query(gpu_addr, type, timestamp);
779} 738}
780 739
781void RasterizerOpenGL::FlushAll() {} 740void RasterizerOpenGL::FlushAll() {}
@@ -787,6 +746,7 @@ void RasterizerOpenGL::FlushRegion(CacheAddr addr, u64 size) {
787 } 746 }
788 texture_cache.FlushRegion(addr, size); 747 texture_cache.FlushRegion(addr, size);
789 buffer_cache.FlushRegion(addr, size); 748 buffer_cache.FlushRegion(addr, size);
749 query_cache.FlushRegion(addr, size);
790} 750}
791 751
792void RasterizerOpenGL::InvalidateRegion(CacheAddr addr, u64 size) { 752void RasterizerOpenGL::InvalidateRegion(CacheAddr addr, u64 size) {
@@ -797,6 +757,7 @@ void RasterizerOpenGL::InvalidateRegion(CacheAddr addr, u64 size) {
797 texture_cache.InvalidateRegion(addr, size); 757 texture_cache.InvalidateRegion(addr, size);
798 shader_cache.InvalidateRegion(addr, size); 758 shader_cache.InvalidateRegion(addr, size);
799 buffer_cache.InvalidateRegion(addr, size); 759 buffer_cache.InvalidateRegion(addr, size);
760 query_cache.InvalidateRegion(addr, size);
800} 761}
801 762
802void RasterizerOpenGL::FlushAndInvalidateRegion(CacheAddr addr, u64 size) { 763void RasterizerOpenGL::FlushAndInvalidateRegion(CacheAddr addr, u64 size) {
@@ -807,10 +768,18 @@ void RasterizerOpenGL::FlushAndInvalidateRegion(CacheAddr addr, u64 size) {
807} 768}
808 769
809void RasterizerOpenGL::FlushCommands() { 770void RasterizerOpenGL::FlushCommands() {
771 // Only flush when we have commands queued to OpenGL.
772 if (num_queued_commands == 0) {
773 return;
774 }
775 num_queued_commands = 0;
810 glFlush(); 776 glFlush();
811} 777}
812 778
813void RasterizerOpenGL::TickFrame() { 779void RasterizerOpenGL::TickFrame() {
780 // Ticking a frame means that buffers will be swapped, calling glFlush implicitly.
781 num_queued_commands = 0;
782
814 buffer_cache.TickFrame(); 783 buffer_cache.TickFrame();
815} 784}
816 785
@@ -942,8 +911,15 @@ void RasterizerOpenGL::SetupDrawTextures(std::size_t stage_index, const Shader&
942 u32 binding = device.GetBaseBindings(stage_index).sampler; 911 u32 binding = device.GetBaseBindings(stage_index).sampler;
943 for (const auto& entry : shader->GetShaderEntries().samplers) { 912 for (const auto& entry : shader->GetShaderEntries().samplers) {
944 const auto shader_type = static_cast<Tegra::Engines::ShaderType>(stage_index); 913 const auto shader_type = static_cast<Tegra::Engines::ShaderType>(stage_index);
945 const auto texture = GetTextureInfo(maxwell3d, entry, shader_type); 914 if (!entry.IsIndexed()) {
946 SetupTexture(binding++, texture, entry); 915 const auto texture = GetTextureInfo(maxwell3d, entry, shader_type);
916 SetupTexture(binding++, texture, entry);
917 } else {
918 for (std::size_t i = 0; i < entry.Size(); ++i) {
919 const auto texture = GetTextureInfo(maxwell3d, entry, shader_type, i);
920 SetupTexture(binding++, texture, entry);
921 }
922 }
947 } 923 }
948} 924}
949 925
@@ -952,8 +928,17 @@ void RasterizerOpenGL::SetupComputeTextures(const Shader& kernel) {
952 const auto& compute = system.GPU().KeplerCompute(); 928 const auto& compute = system.GPU().KeplerCompute();
953 u32 binding = 0; 929 u32 binding = 0;
954 for (const auto& entry : kernel->GetShaderEntries().samplers) { 930 for (const auto& entry : kernel->GetShaderEntries().samplers) {
955 const auto texture = GetTextureInfo(compute, entry, Tegra::Engines::ShaderType::Compute); 931 if (!entry.IsIndexed()) {
956 SetupTexture(binding++, texture, entry); 932 const auto texture =
933 GetTextureInfo(compute, entry, Tegra::Engines::ShaderType::Compute);
934 SetupTexture(binding++, texture, entry);
935 } else {
936 for (std::size_t i = 0; i < entry.Size(); ++i) {
937 const auto texture =
938 GetTextureInfo(compute, entry, Tegra::Engines::ShaderType::Compute, i);
939 SetupTexture(binding++, texture, entry);
940 }
941 }
957 } 942 }
958} 943}
959 944
@@ -1273,6 +1258,7 @@ void RasterizerOpenGL::SyncPointState() {
1273 // Limit the point size to 1 since nouveau sometimes sets a point size of 0 (and that's invalid 1258 // Limit the point size to 1 since nouveau sometimes sets a point size of 0 (and that's invalid
1274 // in OpenGL). 1259 // in OpenGL).
1275 state.point.program_control = regs.vp_point_size.enable != 0; 1260 state.point.program_control = regs.vp_point_size.enable != 0;
1261 state.point.sprite = regs.point_sprite_enable != 0;
1276 state.point.size = std::max(1.0f, regs.point_size); 1262 state.point.size = std::max(1.0f, regs.point_size);
1277} 1263}
1278 1264
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 6a27cf497..68abe9a21 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -24,6 +24,7 @@
24#include "video_core/renderer_opengl/gl_buffer_cache.h" 24#include "video_core/renderer_opengl/gl_buffer_cache.h"
25#include "video_core/renderer_opengl/gl_device.h" 25#include "video_core/renderer_opengl/gl_device.h"
26#include "video_core/renderer_opengl/gl_framebuffer_cache.h" 26#include "video_core/renderer_opengl/gl_framebuffer_cache.h"
27#include "video_core/renderer_opengl/gl_query_cache.h"
27#include "video_core/renderer_opengl/gl_resource_manager.h" 28#include "video_core/renderer_opengl/gl_resource_manager.h"
28#include "video_core/renderer_opengl/gl_sampler_cache.h" 29#include "video_core/renderer_opengl/gl_sampler_cache.h"
29#include "video_core/renderer_opengl/gl_shader_cache.h" 30#include "video_core/renderer_opengl/gl_shader_cache.h"
@@ -57,10 +58,11 @@ public:
57 ScreenInfo& info); 58 ScreenInfo& info);
58 ~RasterizerOpenGL() override; 59 ~RasterizerOpenGL() override;
59 60
60 bool DrawBatch(bool is_indexed) override; 61 void Draw(bool is_indexed, bool is_instanced) override;
61 bool DrawMultiBatch(bool is_indexed) override;
62 void Clear() override; 62 void Clear() override;
63 void DispatchCompute(GPUVAddr code_addr) override; 63 void DispatchCompute(GPUVAddr code_addr) override;
64 void ResetCounter(VideoCore::QueryType type) override;
65 void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) override;
64 void FlushAll() override; 66 void FlushAll() override;
65 void FlushRegion(CacheAddr addr, u64 size) override; 67 void FlushRegion(CacheAddr addr, u64 size) override;
66 void InvalidateRegion(CacheAddr addr, u64 size) override; 68 void InvalidateRegion(CacheAddr addr, u64 size) override;
@@ -75,6 +77,11 @@ public:
75 void LoadDiskResources(const std::atomic_bool& stop_loading, 77 void LoadDiskResources(const std::atomic_bool& stop_loading,
76 const VideoCore::DiskResourceLoadCallback& callback) override; 78 const VideoCore::DiskResourceLoadCallback& callback) override;
77 79
80 /// Returns true when there are commands queued to the OpenGL server.
81 bool AnyCommandQueued() const {
82 return num_queued_commands > 0;
83 }
84
78private: 85private:
79 /// Configures the color and depth framebuffer states. 86 /// Configures the color and depth framebuffer states.
80 void ConfigureFramebuffers(); 87 void ConfigureFramebuffers();
@@ -102,9 +109,6 @@ private:
102 void SetupGlobalMemory(u32 binding, const GLShader::GlobalMemoryEntry& entry, GPUVAddr gpu_addr, 109 void SetupGlobalMemory(u32 binding, const GLShader::GlobalMemoryEntry& entry, GPUVAddr gpu_addr,
103 std::size_t size); 110 std::size_t size);
104 111
105 /// Syncs all the state, shaders, render targets and textures setting before a draw call.
106 void DrawPrelude();
107
108 /// Configures the current textures to use for the draw command. 112 /// Configures the current textures to use for the draw command.
109 void SetupDrawTextures(std::size_t stage_index, const Shader& shader); 113 void SetupDrawTextures(std::size_t stage_index, const Shader& shader);
110 114
@@ -180,10 +184,23 @@ private:
180 /// Syncs the alpha test state to match the guest state 184 /// Syncs the alpha test state to match the guest state
181 void SyncAlphaTest(); 185 void SyncAlphaTest();
182 186
183 /// Check for extension that are not strictly required 187 /// Check for extension that are not strictly required but are needed for correct emulation
184 /// but are needed for correct emulation
185 void CheckExtensions(); 188 void CheckExtensions();
186 189
190 std::size_t CalculateVertexArraysSize() const;
191
192 std::size_t CalculateIndexBufferSize() const;
193
194 /// Updates and returns a vertex array object representing current vertex format
195 GLuint SetupVertexFormat();
196
197 void SetupVertexBuffer(GLuint vao);
198 void SetupVertexInstances(GLuint vao);
199
200 GLintptr SetupIndexBuffer();
201
202 void SetupShaders(GLenum primitive_mode);
203
187 const Device device; 204 const Device device;
188 OpenGLState state; 205 OpenGLState state;
189 206
@@ -191,6 +208,7 @@ private:
191 ShaderCacheOpenGL shader_cache; 208 ShaderCacheOpenGL shader_cache;
192 SamplerCacheOpenGL sampler_cache; 209 SamplerCacheOpenGL sampler_cache;
193 FramebufferCacheOpenGL framebuffer_cache; 210 FramebufferCacheOpenGL framebuffer_cache;
211 QueryCache query_cache;
194 212
195 Core::System& system; 213 Core::System& system;
196 ScreenInfo& screen_info; 214 ScreenInfo& screen_info;
@@ -208,24 +226,8 @@ private:
208 BindBuffersRangePushBuffer bind_ubo_pushbuffer{GL_UNIFORM_BUFFER}; 226 BindBuffersRangePushBuffer bind_ubo_pushbuffer{GL_UNIFORM_BUFFER};
209 BindBuffersRangePushBuffer bind_ssbo_pushbuffer{GL_SHADER_STORAGE_BUFFER}; 227 BindBuffersRangePushBuffer bind_ssbo_pushbuffer{GL_SHADER_STORAGE_BUFFER};
210 228
211 std::size_t CalculateVertexArraysSize() const; 229 /// Number of commands queued to the OpenGL driver. Reseted on flush.
212 230 std::size_t num_queued_commands = 0;
213 std::size_t CalculateIndexBufferSize() const;
214
215 /// Updates and returns a vertex array object representing current vertex format
216 GLuint SetupVertexFormat();
217
218 void SetupVertexBuffer(GLuint vao);
219 void SetupVertexInstances(GLuint vao);
220
221 GLintptr SetupIndexBuffer();
222
223 GLintptr index_buffer_offset;
224
225 void SetupShaders(GLenum primitive_mode);
226
227 enum class AccelDraw { Disabled, Arrays, Indexed };
228 AccelDraw accelerate_draw = AccelDraw::Disabled;
229}; 231};
230 232
231} // namespace OpenGL 233} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_resource_manager.cpp b/src/video_core/renderer_opengl/gl_resource_manager.cpp
index 5c96c1d46..f0ddfb276 100644
--- a/src/video_core/renderer_opengl/gl_resource_manager.cpp
+++ b/src/video_core/renderer_opengl/gl_resource_manager.cpp
@@ -207,4 +207,21 @@ void OGLFramebuffer::Release() {
207 handle = 0; 207 handle = 0;
208} 208}
209 209
210void OGLQuery::Create(GLenum target) {
211 if (handle != 0)
212 return;
213
214 MICROPROFILE_SCOPE(OpenGL_ResourceCreation);
215 glCreateQueries(target, 1, &handle);
216}
217
218void OGLQuery::Release() {
219 if (handle == 0)
220 return;
221
222 MICROPROFILE_SCOPE(OpenGL_ResourceDeletion);
223 glDeleteQueries(1, &handle);
224 handle = 0;
225}
226
210} // namespace OpenGL 227} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_resource_manager.h b/src/video_core/renderer_opengl/gl_resource_manager.h
index 3a85a1d4c..514d1d165 100644
--- a/src/video_core/renderer_opengl/gl_resource_manager.h
+++ b/src/video_core/renderer_opengl/gl_resource_manager.h
@@ -266,4 +266,29 @@ public:
266 GLuint handle = 0; 266 GLuint handle = 0;
267}; 267};
268 268
269class OGLQuery : private NonCopyable {
270public:
271 OGLQuery() = default;
272
273 OGLQuery(OGLQuery&& o) noexcept : handle(std::exchange(o.handle, 0)) {}
274
275 ~OGLQuery() {
276 Release();
277 }
278
279 OGLQuery& operator=(OGLQuery&& o) noexcept {
280 Release();
281 handle = std::exchange(o.handle, 0);
282 return *this;
283 }
284
285 /// Creates a new internal OpenGL resource and stores the handle
286 void Create(GLenum target);
287
288 /// Deletes the internal OpenGL resource
289 void Release();
290
291 GLuint handle = 0;
292};
293
269} // namespace OpenGL 294} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index 3c5bdd377..489eb143c 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -214,6 +214,7 @@ std::unique_ptr<ConstBufferLocker> MakeLocker(Core::System& system, ShaderType s
214} 214}
215 215
216void FillLocker(ConstBufferLocker& locker, const ShaderDiskCacheUsage& usage) { 216void FillLocker(ConstBufferLocker& locker, const ShaderDiskCacheUsage& usage) {
217 locker.SetBoundBuffer(usage.bound_buffer);
217 for (const auto& key : usage.keys) { 218 for (const auto& key : usage.keys) {
218 const auto [buffer, offset] = key.first; 219 const auto [buffer, offset] = key.first;
219 locker.InsertKey(buffer, offset, key.second); 220 locker.InsertKey(buffer, offset, key.second);
@@ -418,7 +419,8 @@ bool CachedShader::EnsureValidLockerVariant() {
418 419
419ShaderDiskCacheUsage CachedShader::GetUsage(const ProgramVariant& variant, 420ShaderDiskCacheUsage CachedShader::GetUsage(const ProgramVariant& variant,
420 const ConstBufferLocker& locker) const { 421 const ConstBufferLocker& locker) const {
421 return ShaderDiskCacheUsage{unique_identifier, variant, locker.GetKeys(), 422 return ShaderDiskCacheUsage{unique_identifier, variant,
423 locker.GetBoundBuffer(), locker.GetKeys(),
422 locker.GetBoundSamplers(), locker.GetBindlessSamplers()}; 424 locker.GetBoundSamplers(), locker.GetBindlessSamplers()};
423} 425}
424 426
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
index 2996aaf08..4735000b5 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
@@ -391,6 +391,7 @@ public:
391 DeclareVertex(); 391 DeclareVertex();
392 DeclareGeometry(); 392 DeclareGeometry();
393 DeclareRegisters(); 393 DeclareRegisters();
394 DeclareCustomVariables();
394 DeclarePredicates(); 395 DeclarePredicates();
395 DeclareLocalMemory(); 396 DeclareLocalMemory();
396 DeclareInternalFlags(); 397 DeclareInternalFlags();
@@ -503,6 +504,16 @@ private:
503 } 504 }
504 } 505 }
505 506
507 void DeclareCustomVariables() {
508 const u32 num_custom_variables = ir.GetNumCustomVariables();
509 for (u32 i = 0; i < num_custom_variables; ++i) {
510 code.AddLine("float {} = 0.0f;", GetCustomVariable(i));
511 }
512 if (num_custom_variables > 0) {
513 code.AddNewLine();
514 }
515 }
516
506 void DeclarePredicates() { 517 void DeclarePredicates() {
507 const auto& predicates = ir.GetPredicates(); 518 const auto& predicates = ir.GetPredicates();
508 for (const auto pred : predicates) { 519 for (const auto pred : predicates) {
@@ -655,7 +666,8 @@ private:
655 u32 binding = device.GetBaseBindings(stage).sampler; 666 u32 binding = device.GetBaseBindings(stage).sampler;
656 for (const auto& sampler : ir.GetSamplers()) { 667 for (const auto& sampler : ir.GetSamplers()) {
657 const std::string name = GetSampler(sampler); 668 const std::string name = GetSampler(sampler);
658 const std::string description = fmt::format("layout (binding = {}) uniform", binding++); 669 const std::string description = fmt::format("layout (binding = {}) uniform", binding);
670 binding += sampler.IsIndexed() ? sampler.Size() : 1;
659 671
660 std::string sampler_type = [&]() { 672 std::string sampler_type = [&]() {
661 if (sampler.IsBuffer()) { 673 if (sampler.IsBuffer()) {
@@ -682,7 +694,11 @@ private:
682 sampler_type += "Shadow"; 694 sampler_type += "Shadow";
683 } 695 }
684 696
685 code.AddLine("{} {} {};", description, sampler_type, name); 697 if (!sampler.IsIndexed()) {
698 code.AddLine("{} {} {};", description, sampler_type, name);
699 } else {
700 code.AddLine("{} {} {}[{}];", description, sampler_type, name, sampler.Size());
701 }
686 } 702 }
687 if (!ir.GetSamplers().empty()) { 703 if (!ir.GetSamplers().empty()) {
688 code.AddNewLine(); 704 code.AddNewLine();
@@ -775,6 +791,11 @@ private:
775 return {GetRegister(index), Type::Float}; 791 return {GetRegister(index), Type::Float};
776 } 792 }
777 793
794 if (const auto cv = std::get_if<CustomVarNode>(&*node)) {
795 const u32 index = cv->GetIndex();
796 return {GetCustomVariable(index), Type::Float};
797 }
798
778 if (const auto immediate = std::get_if<ImmediateNode>(&*node)) { 799 if (const auto immediate = std::get_if<ImmediateNode>(&*node)) {
779 const u32 value = immediate->GetValue(); 800 const u32 value = immediate->GetValue();
780 if (value < 10) { 801 if (value < 10) {
@@ -1019,7 +1040,6 @@ private:
1019 } 1040 }
1020 return {{"gl_ViewportIndex", Type::Int}}; 1041 return {{"gl_ViewportIndex", Type::Int}};
1021 case 3: 1042 case 3:
1022 UNIMPLEMENTED_MSG("Requires some state changes for gl_PointSize to work in shader");
1023 return {{"gl_PointSize", Type::Float}}; 1043 return {{"gl_PointSize", Type::Float}};
1024 } 1044 }
1025 return {}; 1045 return {};
@@ -1099,7 +1119,11 @@ private:
1099 } else if (!meta->ptp.empty()) { 1119 } else if (!meta->ptp.empty()) {
1100 expr += "Offsets"; 1120 expr += "Offsets";
1101 } 1121 }
1102 expr += '(' + GetSampler(meta->sampler) + ", "; 1122 if (!meta->sampler.IsIndexed()) {
1123 expr += '(' + GetSampler(meta->sampler) + ", ";
1124 } else {
1125 expr += '(' + GetSampler(meta->sampler) + '[' + Visit(meta->index).AsUint() + "], ";
1126 }
1103 expr += coord_constructors.at(count + (has_array ? 1 : 0) + 1127 expr += coord_constructors.at(count + (has_array ? 1 : 0) +
1104 (has_shadow && !separate_dc ? 1 : 0) - 1); 1128 (has_shadow && !separate_dc ? 1 : 0) - 1);
1105 expr += '('; 1129 expr += '(';
@@ -1311,6 +1335,8 @@ private:
1311 const std::string final_offset = fmt::format("({} - {}) >> 2", real, base); 1335 const std::string final_offset = fmt::format("({} - {}) >> 2", real, base);
1312 target = {fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset), 1336 target = {fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset),
1313 Type::Uint}; 1337 Type::Uint};
1338 } else if (const auto cv = std::get_if<CustomVarNode>(&*dest)) {
1339 target = {GetCustomVariable(cv->GetIndex()), Type::Float};
1314 } else { 1340 } else {
1315 UNREACHABLE_MSG("Assign called without a proper target"); 1341 UNREACHABLE_MSG("Assign called without a proper target");
1316 } 1342 }
@@ -1858,10 +1884,7 @@ private:
1858 1884
1859 template <const std::string_view& opname, Type type> 1885 template <const std::string_view& opname, Type type>
1860 Expression Atomic(Operation operation) { 1886 Expression Atomic(Operation operation) {
1861 ASSERT(stage == ShaderType::Compute); 1887 return {fmt::format("atomic{}({}, {})", opname, Visit(operation[0]).GetCode(),
1862 auto& smem = std::get<SmemNode>(*operation[0]);
1863
1864 return {fmt::format("atomic{}(smem[{} >> 2], {})", opname, Visit(smem.GetAddress()).AsInt(),
1865 Visit(operation[1]).As(type)), 1888 Visit(operation[1]).As(type)),
1866 type}; 1889 type};
1867 } 1890 }
@@ -2241,6 +2264,10 @@ private:
2241 return GetDeclarationWithSuffix(index, "gpr"); 2264 return GetDeclarationWithSuffix(index, "gpr");
2242 } 2265 }
2243 2266
2267 std::string GetCustomVariable(u32 index) const {
2268 return GetDeclarationWithSuffix(index, "custom_var");
2269 }
2270
2244 std::string GetPredicate(Tegra::Shader::Pred pred) const { 2271 std::string GetPredicate(Tegra::Shader::Pred pred) const {
2245 return GetDeclarationWithSuffix(static_cast<u32>(pred), "pred"); 2272 return GetDeclarationWithSuffix(static_cast<u32>(pred), "pred");
2246 } 2273 }
diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
index cf874a09a..1fc204f6f 100644
--- a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
@@ -53,7 +53,7 @@ struct BindlessSamplerKey {
53 Tegra::Engines::SamplerDescriptor sampler{}; 53 Tegra::Engines::SamplerDescriptor sampler{};
54}; 54};
55 55
56constexpr u32 NativeVersion = 11; 56constexpr u32 NativeVersion = 12;
57 57
58// Making sure sizes doesn't change by accident 58// Making sure sizes doesn't change by accident
59static_assert(sizeof(ProgramVariant) == 20); 59static_assert(sizeof(ProgramVariant) == 20);
@@ -186,7 +186,8 @@ ShaderDiskCacheOpenGL::LoadTransferable() {
186 u32 num_bound_samplers{}; 186 u32 num_bound_samplers{};
187 u32 num_bindless_samplers{}; 187 u32 num_bindless_samplers{};
188 if (file.ReadArray(&usage.unique_identifier, 1) != 1 || 188 if (file.ReadArray(&usage.unique_identifier, 1) != 1 ||
189 file.ReadArray(&usage.variant, 1) != 1 || file.ReadArray(&num_keys, 1) != 1 || 189 file.ReadArray(&usage.variant, 1) != 1 ||
190 file.ReadArray(&usage.bound_buffer, 1) != 1 || file.ReadArray(&num_keys, 1) != 1 ||
190 file.ReadArray(&num_bound_samplers, 1) != 1 || 191 file.ReadArray(&num_bound_samplers, 1) != 1 ||
191 file.ReadArray(&num_bindless_samplers, 1) != 1) { 192 file.ReadArray(&num_bindless_samplers, 1) != 1) {
192 LOG_ERROR(Render_OpenGL, error_loading); 193 LOG_ERROR(Render_OpenGL, error_loading);
@@ -281,7 +282,9 @@ ShaderDiskCacheOpenGL::LoadPrecompiledFile(FileUtil::IOFile& file) {
281 u32 num_bindless_samplers{}; 282 u32 num_bindless_samplers{};
282 ShaderDiskCacheUsage usage; 283 ShaderDiskCacheUsage usage;
283 if (!LoadObjectFromPrecompiled(usage.unique_identifier) || 284 if (!LoadObjectFromPrecompiled(usage.unique_identifier) ||
284 !LoadObjectFromPrecompiled(usage.variant) || !LoadObjectFromPrecompiled(num_keys) || 285 !LoadObjectFromPrecompiled(usage.variant) ||
286 !LoadObjectFromPrecompiled(usage.bound_buffer) ||
287 !LoadObjectFromPrecompiled(num_keys) ||
285 !LoadObjectFromPrecompiled(num_bound_samplers) || 288 !LoadObjectFromPrecompiled(num_bound_samplers) ||
286 !LoadObjectFromPrecompiled(num_bindless_samplers)) { 289 !LoadObjectFromPrecompiled(num_bindless_samplers)) {
287 return {}; 290 return {};
@@ -393,6 +396,7 @@ void ShaderDiskCacheOpenGL::SaveUsage(const ShaderDiskCacheUsage& usage) {
393 396
394 if (file.WriteObject(TransferableEntryKind::Usage) != 1 || 397 if (file.WriteObject(TransferableEntryKind::Usage) != 1 ||
395 file.WriteObject(usage.unique_identifier) != 1 || file.WriteObject(usage.variant) != 1 || 398 file.WriteObject(usage.unique_identifier) != 1 || file.WriteObject(usage.variant) != 1 ||
399 file.WriteObject(usage.bound_buffer) != 1 ||
396 file.WriteObject(static_cast<u32>(usage.keys.size())) != 1 || 400 file.WriteObject(static_cast<u32>(usage.keys.size())) != 1 ||
397 file.WriteObject(static_cast<u32>(usage.bound_samplers.size())) != 1 || 401 file.WriteObject(static_cast<u32>(usage.bound_samplers.size())) != 1 ||
398 file.WriteObject(static_cast<u32>(usage.bindless_samplers.size())) != 1) { 402 file.WriteObject(static_cast<u32>(usage.bindless_samplers.size())) != 1) {
@@ -447,7 +451,7 @@ void ShaderDiskCacheOpenGL::SaveDump(const ShaderDiskCacheUsage& usage, GLuint p
447 }; 451 };
448 452
449 if (!SaveObjectToPrecompiled(usage.unique_identifier) || 453 if (!SaveObjectToPrecompiled(usage.unique_identifier) ||
450 !SaveObjectToPrecompiled(usage.variant) || 454 !SaveObjectToPrecompiled(usage.variant) || !SaveObjectToPrecompiled(usage.bound_buffer) ||
451 !SaveObjectToPrecompiled(static_cast<u32>(usage.keys.size())) || 455 !SaveObjectToPrecompiled(static_cast<u32>(usage.keys.size())) ||
452 !SaveObjectToPrecompiled(static_cast<u32>(usage.bound_samplers.size())) || 456 !SaveObjectToPrecompiled(static_cast<u32>(usage.bound_samplers.size())) ||
453 !SaveObjectToPrecompiled(static_cast<u32>(usage.bindless_samplers.size()))) { 457 !SaveObjectToPrecompiled(static_cast<u32>(usage.bindless_samplers.size()))) {
diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.h b/src/video_core/renderer_opengl/gl_shader_disk_cache.h
index 69a2fbdda..ef2371f6d 100644
--- a/src/video_core/renderer_opengl/gl_shader_disk_cache.h
+++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.h
@@ -79,6 +79,7 @@ static_assert(std::is_trivially_copyable_v<ProgramVariant>);
79struct ShaderDiskCacheUsage { 79struct ShaderDiskCacheUsage {
80 u64 unique_identifier{}; 80 u64 unique_identifier{};
81 ProgramVariant variant; 81 ProgramVariant variant;
82 u32 bound_buffer{};
82 VideoCommon::Shader::KeyMap keys; 83 VideoCommon::Shader::KeyMap keys;
83 VideoCommon::Shader::BoundSamplerMap bound_samplers; 84 VideoCommon::Shader::BoundSamplerMap bound_samplers;
84 VideoCommon::Shader::BindlessSamplerMap bindless_samplers; 85 VideoCommon::Shader::BindlessSamplerMap bindless_samplers;
diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp
index cc185e9e1..ab1f7983c 100644
--- a/src/video_core/renderer_opengl/gl_state.cpp
+++ b/src/video_core/renderer_opengl/gl_state.cpp
@@ -128,6 +128,7 @@ void OpenGLState::ApplyClipDistances() {
128 128
129void OpenGLState::ApplyPointSize() { 129void OpenGLState::ApplyPointSize() {
130 Enable(GL_PROGRAM_POINT_SIZE, cur_state.point.program_control, point.program_control); 130 Enable(GL_PROGRAM_POINT_SIZE, cur_state.point.program_control, point.program_control);
131 Enable(GL_POINT_SPRITE, cur_state.point.sprite, point.sprite);
131 if (UpdateValue(cur_state.point.size, point.size)) { 132 if (UpdateValue(cur_state.point.size, point.size)) {
132 glPointSize(point.size); 133 glPointSize(point.size);
133 } 134 }
diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h
index 678e5cd89..4953eeda2 100644
--- a/src/video_core/renderer_opengl/gl_state.h
+++ b/src/video_core/renderer_opengl/gl_state.h
@@ -132,6 +132,7 @@ public:
132 132
133 struct { 133 struct {
134 bool program_control = false; // GL_PROGRAM_POINT_SIZE 134 bool program_control = false; // GL_PROGRAM_POINT_SIZE
135 bool sprite = false; // GL_POINT_SPRITE
135 GLfloat size = 1.0f; // GL_POINT_SIZE 136 GLfloat size = 1.0f; // GL_POINT_SIZE
136 } point; 137 } point;
137 138
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp
index e95eb069e..d4b81cd87 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp
@@ -176,6 +176,19 @@ GLint GetSwizzleSource(SwizzleSource source) {
176 return GL_NONE; 176 return GL_NONE;
177} 177}
178 178
179GLenum GetComponent(PixelFormat format, bool is_first) {
180 switch (format) {
181 case PixelFormat::Z24S8:
182 case PixelFormat::Z32FS8:
183 return is_first ? GL_DEPTH_COMPONENT : GL_STENCIL_INDEX;
184 case PixelFormat::S8Z24:
185 return is_first ? GL_STENCIL_INDEX : GL_DEPTH_COMPONENT;
186 default:
187 UNREACHABLE();
188 return GL_DEPTH_COMPONENT;
189 }
190}
191
179void ApplyTextureDefaults(const SurfaceParams& params, GLuint texture) { 192void ApplyTextureDefaults(const SurfaceParams& params, GLuint texture) {
180 if (params.IsBuffer()) { 193 if (params.IsBuffer()) {
181 return; 194 return;
@@ -184,7 +197,7 @@ void ApplyTextureDefaults(const SurfaceParams& params, GLuint texture) {
184 glTextureParameteri(texture, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 197 glTextureParameteri(texture, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
185 glTextureParameteri(texture, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 198 glTextureParameteri(texture, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
186 glTextureParameteri(texture, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 199 glTextureParameteri(texture, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
187 glTextureParameteri(texture, GL_TEXTURE_MAX_LEVEL, params.num_levels - 1); 200 glTextureParameteri(texture, GL_TEXTURE_MAX_LEVEL, static_cast<GLint>(params.num_levels - 1));
188 if (params.num_levels == 1) { 201 if (params.num_levels == 1) {
189 glTextureParameterf(texture, GL_TEXTURE_LOD_BIAS, 1000.0f); 202 glTextureParameterf(texture, GL_TEXTURE_LOD_BIAS, 1000.0f);
190 } 203 }
@@ -416,11 +429,21 @@ void CachedSurfaceView::ApplySwizzle(SwizzleSource x_source, SwizzleSource y_sou
416 if (new_swizzle == swizzle) 429 if (new_swizzle == swizzle)
417 return; 430 return;
418 swizzle = new_swizzle; 431 swizzle = new_swizzle;
419 const std::array<GLint, 4> gl_swizzle = {GetSwizzleSource(x_source), GetSwizzleSource(y_source), 432 const std::array gl_swizzle = {GetSwizzleSource(x_source), GetSwizzleSource(y_source),
420 GetSwizzleSource(z_source), 433 GetSwizzleSource(z_source), GetSwizzleSource(w_source)};
421 GetSwizzleSource(w_source)};
422 const GLuint handle = GetTexture(); 434 const GLuint handle = GetTexture();
423 glTextureParameteriv(handle, GL_TEXTURE_SWIZZLE_RGBA, gl_swizzle.data()); 435 const PixelFormat format = surface.GetSurfaceParams().pixel_format;
436 switch (format) {
437 case PixelFormat::Z24S8:
438 case PixelFormat::Z32FS8:
439 case PixelFormat::S8Z24:
440 glTextureParameteri(handle, GL_DEPTH_STENCIL_TEXTURE_MODE,
441 GetComponent(format, x_source == SwizzleSource::R));
442 break;
443 default:
444 glTextureParameteriv(handle, GL_TEXTURE_SWIZZLE_RGBA, gl_swizzle.data());
445 break;
446 }
424} 447}
425 448
426OGLTextureView CachedSurfaceView::CreateTextureView() const { 449OGLTextureView CachedSurfaceView::CreateTextureView() const {
@@ -529,8 +552,11 @@ void TextureCacheOpenGL::ImageBlit(View& src_view, View& dst_view,
529 const Common::Rectangle<u32>& dst_rect = copy_config.dst_rect; 552 const Common::Rectangle<u32>& dst_rect = copy_config.dst_rect;
530 const bool is_linear = copy_config.filter == Tegra::Engines::Fermi2D::Filter::Linear; 553 const bool is_linear = copy_config.filter == Tegra::Engines::Fermi2D::Filter::Linear;
531 554
532 glBlitFramebuffer(src_rect.left, src_rect.top, src_rect.right, src_rect.bottom, dst_rect.left, 555 glBlitFramebuffer(static_cast<GLint>(src_rect.left), static_cast<GLint>(src_rect.top),
533 dst_rect.top, dst_rect.right, dst_rect.bottom, buffers, 556 static_cast<GLint>(src_rect.right), static_cast<GLint>(src_rect.bottom),
557 static_cast<GLint>(dst_rect.left), static_cast<GLint>(dst_rect.top),
558 static_cast<GLint>(dst_rect.right), static_cast<GLint>(dst_rect.bottom),
559 buffers,
534 is_linear && (buffers == GL_COLOR_BUFFER_BIT) ? GL_LINEAR : GL_NEAREST); 560 is_linear && (buffers == GL_COLOR_BUFFER_BIT) ? GL_LINEAR : GL_NEAREST);
535} 561}
536 562
diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h
index ea4f35663..7ed505628 100644
--- a/src/video_core/renderer_opengl/maxwell_to_gl.h
+++ b/src/video_core/renderer_opengl/maxwell_to_gl.h
@@ -47,8 +47,7 @@ inline GLenum VertexType(Maxwell::VertexAttribute attrib) {
47 case Maxwell::VertexAttribute::Size::Size_10_10_10_2: 47 case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
48 return GL_UNSIGNED_INT_2_10_10_10_REV; 48 return GL_UNSIGNED_INT_2_10_10_10_REV;
49 default: 49 default:
50 LOG_CRITICAL(Render_OpenGL, "Unimplemented vertex size={}", attrib.SizeString()); 50 LOG_ERROR(Render_OpenGL, "Unimplemented vertex size={}", attrib.SizeString());
51 UNREACHABLE();
52 return {}; 51 return {};
53 } 52 }
54 case Maxwell::VertexAttribute::Type::SignedInt: 53 case Maxwell::VertexAttribute::Type::SignedInt:
@@ -72,8 +71,7 @@ inline GLenum VertexType(Maxwell::VertexAttribute attrib) {
72 case Maxwell::VertexAttribute::Size::Size_10_10_10_2: 71 case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
73 return GL_INT_2_10_10_10_REV; 72 return GL_INT_2_10_10_10_REV;
74 default: 73 default:
75 LOG_CRITICAL(Render_OpenGL, "Unimplemented vertex size={}", attrib.SizeString()); 74 LOG_ERROR(Render_OpenGL, "Unimplemented vertex size={}", attrib.SizeString());
76 UNREACHABLE();
77 return {}; 75 return {};
78 } 76 }
79 case Maxwell::VertexAttribute::Type::Float: 77 case Maxwell::VertexAttribute::Type::Float:
@@ -89,13 +87,19 @@ inline GLenum VertexType(Maxwell::VertexAttribute attrib) {
89 case Maxwell::VertexAttribute::Size::Size_32_32_32_32: 87 case Maxwell::VertexAttribute::Size::Size_32_32_32_32:
90 return GL_FLOAT; 88 return GL_FLOAT;
91 default: 89 default:
92 LOG_CRITICAL(Render_OpenGL, "Unimplemented vertex size={}", attrib.SizeString()); 90 LOG_ERROR(Render_OpenGL, "Unimplemented vertex size={}", attrib.SizeString());
93 UNREACHABLE(); 91 return {};
92 }
93 case Maxwell::VertexAttribute::Type::UnsignedScaled:
94 switch (attrib.size) {
95 case Maxwell::VertexAttribute::Size::Size_8_8:
96 return GL_UNSIGNED_BYTE;
97 default:
98 LOG_ERROR(Render_OpenGL, "Unimplemented vertex size={}", attrib.SizeString());
94 return {}; 99 return {};
95 } 100 }
96 default: 101 default:
97 LOG_CRITICAL(Render_OpenGL, "Unimplemented vertex type={}", attrib.TypeString()); 102 LOG_ERROR(Render_OpenGL, "Unimplemented vertex type={}", attrib.TypeString());
98 UNREACHABLE();
99 return {}; 103 return {};
100 } 104 }
101} 105}
diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
index 331808113..5403c3ab7 100644
--- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
+++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
@@ -164,7 +164,7 @@ struct FormatTuple {
164 {vk::Format::eUndefined, {}}, // ASTC_2D_5X4 164 {vk::Format::eUndefined, {}}, // ASTC_2D_5X4
165 {vk::Format::eUndefined, {}}, // BGRA8_SRGB 165 {vk::Format::eUndefined, {}}, // BGRA8_SRGB
166 {vk::Format::eBc1RgbaSrgbBlock, {}}, // DXT1_SRGB 166 {vk::Format::eBc1RgbaSrgbBlock, {}}, // DXT1_SRGB
167 {vk::Format::eUndefined, {}}, // DXT23_SRGB 167 {vk::Format::eBc2SrgbBlock, {}}, // DXT23_SRGB
168 {vk::Format::eBc3SrgbBlock, {}}, // DXT45_SRGB 168 {vk::Format::eBc3SrgbBlock, {}}, // DXT45_SRGB
169 {vk::Format::eBc7SrgbBlock, {}}, // BC7U_SRGB 169 {vk::Format::eBc7SrgbBlock, {}}, // BC7U_SRGB
170 {vk::Format::eR4G4B4A4UnormPack16, Attachable}, // R4G4B4A4U 170 {vk::Format::eR4G4B4A4UnormPack16, Attachable}, // R4G4B4A4U
@@ -363,6 +363,8 @@ vk::Format VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttr
363 return vk::Format::eR8G8B8A8Uint; 363 return vk::Format::eR8G8B8A8Uint;
364 case Maxwell::VertexAttribute::Size::Size_32: 364 case Maxwell::VertexAttribute::Size::Size_32:
365 return vk::Format::eR32Uint; 365 return vk::Format::eR32Uint;
366 case Maxwell::VertexAttribute::Size::Size_32_32_32_32:
367 return vk::Format::eR32G32B32A32Uint;
366 default: 368 default:
367 break; 369 break;
368 } 370 }
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
new file mode 100644
index 000000000..d5032b432
--- /dev/null
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -0,0 +1,265 @@
1// Copyright 2018 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <memory>
6#include <optional>
7#include <vector>
8
9#include <fmt/format.h>
10
11#include "common/assert.h"
12#include "common/logging/log.h"
13#include "common/telemetry.h"
14#include "core/core.h"
15#include "core/core_timing.h"
16#include "core/frontend/emu_window.h"
17#include "core/memory.h"
18#include "core/perf_stats.h"
19#include "core/settings.h"
20#include "core/telemetry_session.h"
21#include "video_core/gpu.h"
22#include "video_core/renderer_vulkan/declarations.h"
23#include "video_core/renderer_vulkan/renderer_vulkan.h"
24#include "video_core/renderer_vulkan/vk_blit_screen.h"
25#include "video_core/renderer_vulkan/vk_device.h"
26#include "video_core/renderer_vulkan/vk_memory_manager.h"
27#include "video_core/renderer_vulkan/vk_rasterizer.h"
28#include "video_core/renderer_vulkan/vk_resource_manager.h"
29#include "video_core/renderer_vulkan/vk_scheduler.h"
30#include "video_core/renderer_vulkan/vk_swapchain.h"
31
32namespace Vulkan {
33
34namespace {
35
36VkBool32 DebugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity_,
37 VkDebugUtilsMessageTypeFlagsEXT type,
38 const VkDebugUtilsMessengerCallbackDataEXT* data,
39 [[maybe_unused]] void* user_data) {
40 const vk::DebugUtilsMessageSeverityFlagBitsEXT severity{severity_};
41 const char* message{data->pMessage};
42
43 if (severity & vk::DebugUtilsMessageSeverityFlagBitsEXT::eError) {
44 LOG_CRITICAL(Render_Vulkan, "{}", message);
45 } else if (severity & vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) {
46 LOG_WARNING(Render_Vulkan, "{}", message);
47 } else if (severity & vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo) {
48 LOG_INFO(Render_Vulkan, "{}", message);
49 } else if (severity & vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose) {
50 LOG_DEBUG(Render_Vulkan, "{}", message);
51 }
52 return VK_FALSE;
53}
54
55std::string GetReadableVersion(u32 version) {
56 return fmt::format("{}.{}.{}", VK_VERSION_MAJOR(version), VK_VERSION_MINOR(version),
57 VK_VERSION_PATCH(version));
58}
59
60std::string GetDriverVersion(const VKDevice& device) {
61 // Extracted from
62 // https://github.com/SaschaWillems/vulkan.gpuinfo.org/blob/5dddea46ea1120b0df14eef8f15ff8e318e35462/functions.php#L308-L314
63 const u32 version = device.GetDriverVersion();
64
65 if (device.GetDriverID() == vk::DriverIdKHR::eNvidiaProprietary) {
66 const u32 major = (version >> 22) & 0x3ff;
67 const u32 minor = (version >> 14) & 0x0ff;
68 const u32 secondary = (version >> 6) & 0x0ff;
69 const u32 tertiary = version & 0x003f;
70 return fmt::format("{}.{}.{}.{}", major, minor, secondary, tertiary);
71 }
72 if (device.GetDriverID() == vk::DriverIdKHR::eIntelProprietaryWindows) {
73 const u32 major = version >> 14;
74 const u32 minor = version & 0x3fff;
75 return fmt::format("{}.{}", major, minor);
76 }
77
78 return GetReadableVersion(version);
79}
80
81std::string BuildCommaSeparatedExtensions(std::vector<std::string> available_extensions) {
82 std::sort(std::begin(available_extensions), std::end(available_extensions));
83
84 static constexpr std::size_t AverageExtensionSize = 64;
85 std::string separated_extensions;
86 separated_extensions.reserve(available_extensions.size() * AverageExtensionSize);
87
88 const auto end = std::end(available_extensions);
89 for (auto extension = std::begin(available_extensions); extension != end; ++extension) {
90 if (const bool is_last = extension + 1 == end; is_last) {
91 separated_extensions += *extension;
92 } else {
93 separated_extensions += fmt::format("{},", *extension);
94 }
95 }
96 return separated_extensions;
97}
98
99} // Anonymous namespace
100
101RendererVulkan::RendererVulkan(Core::Frontend::EmuWindow& window, Core::System& system)
102 : RendererBase(window), system{system} {}
103
104RendererVulkan::~RendererVulkan() {
105 ShutDown();
106}
107
108void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
109 const auto& layout = render_window.GetFramebufferLayout();
110 if (framebuffer && layout.width > 0 && layout.height > 0 && render_window.IsShown()) {
111 const VAddr framebuffer_addr = framebuffer->address + framebuffer->offset;
112 const bool use_accelerated =
113 rasterizer->AccelerateDisplay(*framebuffer, framebuffer_addr, framebuffer->stride);
114 const bool is_srgb = use_accelerated && screen_info.is_srgb;
115 if (swapchain->HasFramebufferChanged(layout) || swapchain->GetSrgbState() != is_srgb) {
116 swapchain->Create(layout.width, layout.height, is_srgb);
117 blit_screen->Recreate();
118 }
119
120 scheduler->WaitWorker();
121
122 swapchain->AcquireNextImage();
123 const auto [fence, render_semaphore] = blit_screen->Draw(*framebuffer, use_accelerated);
124
125 scheduler->Flush(false, render_semaphore);
126
127 if (swapchain->Present(render_semaphore, fence)) {
128 blit_screen->Recreate();
129 }
130
131 render_window.SwapBuffers();
132 rasterizer->TickFrame();
133 }
134
135 render_window.PollEvents();
136}
137
138bool RendererVulkan::Init() {
139 PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr{};
140 render_window.RetrieveVulkanHandlers(&vkGetInstanceProcAddr, &instance, &surface);
141 const vk::DispatchLoaderDynamic dldi(instance, vkGetInstanceProcAddr);
142
143 std::optional<vk::DebugUtilsMessengerEXT> callback;
144 if (Settings::values.renderer_debug && dldi.vkCreateDebugUtilsMessengerEXT) {
145 callback = CreateDebugCallback(dldi);
146 if (!callback) {
147 return false;
148 }
149 }
150
151 if (!PickDevices(dldi)) {
152 if (callback) {
153 instance.destroy(*callback, nullptr, dldi);
154 }
155 return false;
156 }
157 debug_callback = UniqueDebugUtilsMessengerEXT(
158 *callback, vk::ObjectDestroy<vk::Instance, vk::DispatchLoaderDynamic>(
159 instance, nullptr, device->GetDispatchLoader()));
160
161 Report();
162
163 memory_manager = std::make_unique<VKMemoryManager>(*device);
164
165 resource_manager = std::make_unique<VKResourceManager>(*device);
166
167 const auto& framebuffer = render_window.GetFramebufferLayout();
168 swapchain = std::make_unique<VKSwapchain>(surface, *device);
169 swapchain->Create(framebuffer.width, framebuffer.height, false);
170
171 scheduler = std::make_unique<VKScheduler>(*device, *resource_manager);
172
173 rasterizer = std::make_unique<RasterizerVulkan>(system, render_window, screen_info, *device,
174 *resource_manager, *memory_manager, *scheduler);
175
176 blit_screen = std::make_unique<VKBlitScreen>(system, render_window, *rasterizer, *device,
177 *resource_manager, *memory_manager, *swapchain,
178 *scheduler, screen_info);
179
180 return true;
181}
182
183void RendererVulkan::ShutDown() {
184 if (!device) {
185 return;
186 }
187 const auto dev = device->GetLogical();
188 const auto& dld = device->GetDispatchLoader();
189 if (dev && dld.vkDeviceWaitIdle) {
190 dev.waitIdle(dld);
191 }
192
193 rasterizer.reset();
194 blit_screen.reset();
195 scheduler.reset();
196 swapchain.reset();
197 memory_manager.reset();
198 resource_manager.reset();
199 device.reset();
200}
201
202std::optional<vk::DebugUtilsMessengerEXT> RendererVulkan::CreateDebugCallback(
203 const vk::DispatchLoaderDynamic& dldi) {
204 const vk::DebugUtilsMessengerCreateInfoEXT callback_ci(
205 {},
206 vk::DebugUtilsMessageSeverityFlagBitsEXT::eError |
207 vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning |
208 vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo |
209 vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose,
210 vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral |
211 vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation |
212 vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance,
213 &DebugCallback, nullptr);
214 vk::DebugUtilsMessengerEXT callback;
215 if (instance.createDebugUtilsMessengerEXT(&callback_ci, nullptr, &callback, dldi) !=
216 vk::Result::eSuccess) {
217 LOG_ERROR(Render_Vulkan, "Failed to create debug callback");
218 return {};
219 }
220 return callback;
221}
222
223bool RendererVulkan::PickDevices(const vk::DispatchLoaderDynamic& dldi) {
224 const auto devices = instance.enumeratePhysicalDevices(dldi);
225
226 // TODO(Rodrigo): Choose device from config file
227 const s32 device_index = Settings::values.vulkan_device;
228 if (device_index < 0 || device_index >= static_cast<s32>(devices.size())) {
229 LOG_ERROR(Render_Vulkan, "Invalid device index {}!", device_index);
230 return false;
231 }
232 const vk::PhysicalDevice physical_device = devices[device_index];
233
234 if (!VKDevice::IsSuitable(dldi, physical_device, surface)) {
235 return false;
236 }
237
238 device = std::make_unique<VKDevice>(dldi, physical_device, surface);
239 return device->Create(dldi, instance);
240}
241
242void RendererVulkan::Report() const {
243 const std::string vendor_name{device->GetVendorName()};
244 const std::string model_name{device->GetModelName()};
245 const std::string driver_version = GetDriverVersion(*device);
246 const std::string driver_name = fmt::format("{} {}", vendor_name, driver_version);
247
248 const std::string api_version = GetReadableVersion(device->GetApiVersion());
249
250 const std::string extensions = BuildCommaSeparatedExtensions(device->GetAvailableExtensions());
251
252 LOG_INFO(Render_Vulkan, "Driver: {}", driver_name);
253 LOG_INFO(Render_Vulkan, "Device: {}", model_name);
254 LOG_INFO(Render_Vulkan, "Vulkan: {}", api_version);
255
256 auto& telemetry_session = system.TelemetrySession();
257 constexpr auto field = Telemetry::FieldType::UserSystem;
258 telemetry_session.AddField(field, "GPU_Vendor", vendor_name);
259 telemetry_session.AddField(field, "GPU_Model", model_name);
260 telemetry_session.AddField(field, "GPU_Vulkan_Driver", driver_name);
261 telemetry_session.AddField(field, "GPU_Vulkan_Version", api_version);
262 telemetry_session.AddField(field, "GPU_Vulkan_Extensions", extensions);
263}
264
265} // namespace Vulkan \ No newline at end of file
diff --git a/src/video_core/renderer_vulkan/vk_device.cpp b/src/video_core/renderer_vulkan/vk_device.cpp
index 939eebe83..d1da4f9d3 100644
--- a/src/video_core/renderer_vulkan/vk_device.cpp
+++ b/src/video_core/renderer_vulkan/vk_device.cpp
@@ -104,8 +104,11 @@ bool VKDevice::Create(const vk::DispatchLoaderDynamic& dldi, vk::Instance instan
104 features.depthBiasClamp = true; 104 features.depthBiasClamp = true;
105 features.geometryShader = true; 105 features.geometryShader = true;
106 features.tessellationShader = true; 106 features.tessellationShader = true;
107 features.occlusionQueryPrecise = true;
107 features.fragmentStoresAndAtomics = true; 108 features.fragmentStoresAndAtomics = true;
108 features.shaderImageGatherExtended = true; 109 features.shaderImageGatherExtended = true;
110 features.shaderStorageImageReadWithoutFormat =
111 is_shader_storage_img_read_without_format_supported;
109 features.shaderStorageImageWriteWithoutFormat = true; 112 features.shaderStorageImageWriteWithoutFormat = true;
110 features.textureCompressionASTC_LDR = is_optimal_astc_supported; 113 features.textureCompressionASTC_LDR = is_optimal_astc_supported;
111 114
@@ -117,6 +120,10 @@ bool VKDevice::Create(const vk::DispatchLoaderDynamic& dldi, vk::Instance instan
117 bit8_storage.uniformAndStorageBuffer8BitAccess = true; 120 bit8_storage.uniformAndStorageBuffer8BitAccess = true;
118 SetNext(next, bit8_storage); 121 SetNext(next, bit8_storage);
119 122
123 vk::PhysicalDeviceHostQueryResetFeaturesEXT host_query_reset;
124 host_query_reset.hostQueryReset = true;
125 SetNext(next, host_query_reset);
126
120 vk::PhysicalDeviceFloat16Int8FeaturesKHR float16_int8; 127 vk::PhysicalDeviceFloat16Int8FeaturesKHR float16_int8;
121 if (is_float16_supported) { 128 if (is_float16_supported) {
122 float16_int8.shaderFloat16 = true; 129 float16_int8.shaderFloat16 = true;
@@ -273,6 +280,7 @@ bool VKDevice::IsSuitable(const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDev
273 VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME, 280 VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME,
274 VK_EXT_SHADER_SUBGROUP_BALLOT_EXTENSION_NAME, 281 VK_EXT_SHADER_SUBGROUP_BALLOT_EXTENSION_NAME,
275 VK_EXT_SHADER_SUBGROUP_VOTE_EXTENSION_NAME, 282 VK_EXT_SHADER_SUBGROUP_VOTE_EXTENSION_NAME,
283 VK_EXT_HOST_QUERY_RESET_EXTENSION_NAME,
276 }; 284 };
277 std::bitset<required_extensions.size()> available_extensions{}; 285 std::bitset<required_extensions.size()> available_extensions{};
278 286
@@ -340,6 +348,7 @@ bool VKDevice::IsSuitable(const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDev
340 std::make_pair(features.depthBiasClamp, "depthBiasClamp"), 348 std::make_pair(features.depthBiasClamp, "depthBiasClamp"),
341 std::make_pair(features.geometryShader, "geometryShader"), 349 std::make_pair(features.geometryShader, "geometryShader"),
342 std::make_pair(features.tessellationShader, "tessellationShader"), 350 std::make_pair(features.tessellationShader, "tessellationShader"),
351 std::make_pair(features.occlusionQueryPrecise, "occlusionQueryPrecise"),
343 std::make_pair(features.fragmentStoresAndAtomics, "fragmentStoresAndAtomics"), 352 std::make_pair(features.fragmentStoresAndAtomics, "fragmentStoresAndAtomics"),
344 std::make_pair(features.shaderImageGatherExtended, "shaderImageGatherExtended"), 353 std::make_pair(features.shaderImageGatherExtended, "shaderImageGatherExtended"),
345 std::make_pair(features.shaderStorageImageWriteWithoutFormat, 354 std::make_pair(features.shaderStorageImageWriteWithoutFormat,
@@ -376,7 +385,7 @@ std::vector<const char*> VKDevice::LoadExtensions(const vk::DispatchLoaderDynami
376 } 385 }
377 }; 386 };
378 387
379 extensions.reserve(13); 388 extensions.reserve(14);
380 extensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME); 389 extensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
381 extensions.push_back(VK_KHR_16BIT_STORAGE_EXTENSION_NAME); 390 extensions.push_back(VK_KHR_16BIT_STORAGE_EXTENSION_NAME);
382 extensions.push_back(VK_KHR_8BIT_STORAGE_EXTENSION_NAME); 391 extensions.push_back(VK_KHR_8BIT_STORAGE_EXTENSION_NAME);
@@ -384,6 +393,7 @@ std::vector<const char*> VKDevice::LoadExtensions(const vk::DispatchLoaderDynami
384 extensions.push_back(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME); 393 extensions.push_back(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME);
385 extensions.push_back(VK_EXT_SHADER_SUBGROUP_BALLOT_EXTENSION_NAME); 394 extensions.push_back(VK_EXT_SHADER_SUBGROUP_BALLOT_EXTENSION_NAME);
386 extensions.push_back(VK_EXT_SHADER_SUBGROUP_VOTE_EXTENSION_NAME); 395 extensions.push_back(VK_EXT_SHADER_SUBGROUP_VOTE_EXTENSION_NAME);
396 extensions.push_back(VK_EXT_HOST_QUERY_RESET_EXTENSION_NAME);
387 397
388 [[maybe_unused]] const bool nsight = 398 [[maybe_unused]] const bool nsight =
389 std::getenv("NVTX_INJECTION64_PATH") || std::getenv("NSIGHT_LAUNCHED"); 399 std::getenv("NVTX_INJECTION64_PATH") || std::getenv("NSIGHT_LAUNCHED");
@@ -400,8 +410,10 @@ std::vector<const char*> VKDevice::LoadExtensions(const vk::DispatchLoaderDynami
400 VK_EXT_SHADER_VIEWPORT_INDEX_LAYER_EXTENSION_NAME, true); 410 VK_EXT_SHADER_VIEWPORT_INDEX_LAYER_EXTENSION_NAME, true);
401 Test(extension, ext_subgroup_size_control, VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME, 411 Test(extension, ext_subgroup_size_control, VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME,
402 false); 412 false);
403 Test(extension, nv_device_diagnostic_checkpoints, 413 if (Settings::values.renderer_debug) {
404 VK_NV_DEVICE_DIAGNOSTIC_CHECKPOINTS_EXTENSION_NAME, true); 414 Test(extension, nv_device_diagnostic_checkpoints,
415 VK_NV_DEVICE_DIAGNOSTIC_CHECKPOINTS_EXTENSION_NAME, true);
416 }
405 } 417 }
406 418
407 if (khr_shader_float16_int8) { 419 if (khr_shader_float16_int8) {
@@ -455,6 +467,8 @@ void VKDevice::SetupFamilies(const vk::DispatchLoaderDynamic& dldi, vk::SurfaceK
455 467
456void VKDevice::SetupFeatures(const vk::DispatchLoaderDynamic& dldi) { 468void VKDevice::SetupFeatures(const vk::DispatchLoaderDynamic& dldi) {
457 const auto supported_features{physical.getFeatures(dldi)}; 469 const auto supported_features{physical.getFeatures(dldi)};
470 is_shader_storage_img_read_without_format_supported =
471 supported_features.shaderStorageImageReadWithoutFormat;
458 is_optimal_astc_supported = IsOptimalAstcSupported(supported_features, dldi); 472 is_optimal_astc_supported = IsOptimalAstcSupported(supported_features, dldi);
459} 473}
460 474
@@ -528,6 +542,7 @@ std::unordered_map<vk::Format, vk::FormatProperties> VKDevice::GetFormatProperti
528 vk::Format::eBc6HUfloatBlock, 542 vk::Format::eBc6HUfloatBlock,
529 vk::Format::eBc6HSfloatBlock, 543 vk::Format::eBc6HSfloatBlock,
530 vk::Format::eBc1RgbaSrgbBlock, 544 vk::Format::eBc1RgbaSrgbBlock,
545 vk::Format::eBc2SrgbBlock,
531 vk::Format::eBc3SrgbBlock, 546 vk::Format::eBc3SrgbBlock,
532 vk::Format::eBc7SrgbBlock, 547 vk::Format::eBc7SrgbBlock,
533 vk::Format::eAstc4x4SrgbBlock, 548 vk::Format::eAstc4x4SrgbBlock,
diff --git a/src/video_core/renderer_vulkan/vk_device.h b/src/video_core/renderer_vulkan/vk_device.h
index 72603f9f6..2c27ad730 100644
--- a/src/video_core/renderer_vulkan/vk_device.h
+++ b/src/video_core/renderer_vulkan/vk_device.h
@@ -122,6 +122,11 @@ public:
122 return properties.limits.maxPushConstantsSize; 122 return properties.limits.maxPushConstantsSize;
123 } 123 }
124 124
125 /// Returns true if Shader storage Image Read Without Format supported.
126 bool IsShaderStorageImageReadWithoutFormatSupported() const {
127 return is_shader_storage_img_read_without_format_supported;
128 }
129
125 /// Returns true if ASTC is natively supported. 130 /// Returns true if ASTC is natively supported.
126 bool IsOptimalAstcSupported() const { 131 bool IsOptimalAstcSupported() const {
127 return is_optimal_astc_supported; 132 return is_optimal_astc_supported;
@@ -227,6 +232,8 @@ private:
227 bool ext_depth_range_unrestricted{}; ///< Support for VK_EXT_depth_range_unrestricted. 232 bool ext_depth_range_unrestricted{}; ///< Support for VK_EXT_depth_range_unrestricted.
228 bool ext_shader_viewport_index_layer{}; ///< Support for VK_EXT_shader_viewport_index_layer. 233 bool ext_shader_viewport_index_layer{}; ///< Support for VK_EXT_shader_viewport_index_layer.
229 bool nv_device_diagnostic_checkpoints{}; ///< Support for VK_NV_device_diagnostic_checkpoints. 234 bool nv_device_diagnostic_checkpoints{}; ///< Support for VK_NV_device_diagnostic_checkpoints.
235 bool is_shader_storage_img_read_without_format_supported{}; ///< Support for shader storage
236 ///< image read without format
230 237
231 // Telemetry parameters 238 // Telemetry parameters
232 std::string vendor_name; ///< Device's driver name. 239 std::string vendor_name; ///< Device's driver name.
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index 48e23d4cd..7ddf7d3ee 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
@@ -325,9 +325,6 @@ VKPipelineCache::DecompileShaders(const GraphicsPipelineCacheKey& key) {
325 specialization.tessellation.primitive = fixed_state.tessellation.primitive; 325 specialization.tessellation.primitive = fixed_state.tessellation.primitive;
326 specialization.tessellation.spacing = fixed_state.tessellation.spacing; 326 specialization.tessellation.spacing = fixed_state.tessellation.spacing;
327 specialization.tessellation.clockwise = fixed_state.tessellation.clockwise; 327 specialization.tessellation.clockwise = fixed_state.tessellation.clockwise;
328 for (const auto& rt : key.renderpass_params.color_attachments) {
329 specialization.enabled_rendertargets.set(rt.index);
330 }
331 328
332 SPIRVProgram program; 329 SPIRVProgram program;
333 std::vector<vk::DescriptorSetLayoutBinding> bindings; 330 std::vector<vk::DescriptorSetLayoutBinding> bindings;
diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp
new file mode 100644
index 000000000..ffbf60dda
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_query_cache.cpp
@@ -0,0 +1,122 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include <cstddef>
7#include <cstdint>
8#include <utility>
9#include <vector>
10
11#include "video_core/renderer_vulkan/declarations.h"
12#include "video_core/renderer_vulkan/vk_device.h"
13#include "video_core/renderer_vulkan/vk_query_cache.h"
14#include "video_core/renderer_vulkan/vk_resource_manager.h"
15#include "video_core/renderer_vulkan/vk_scheduler.h"
16
17namespace Vulkan {
18
19namespace {
20
21constexpr std::array QUERY_TARGETS = {vk::QueryType::eOcclusion};
22
23constexpr vk::QueryType GetTarget(VideoCore::QueryType type) {
24 return QUERY_TARGETS[static_cast<std::size_t>(type)];
25}
26
27} // Anonymous namespace
28
29QueryPool::QueryPool() : VKFencedPool{GROW_STEP} {}
30
31QueryPool::~QueryPool() = default;
32
33void QueryPool::Initialize(const VKDevice& device_, VideoCore::QueryType type_) {
34 device = &device_;
35 type = type_;
36}
37
38std::pair<vk::QueryPool, std::uint32_t> QueryPool::Commit(VKFence& fence) {
39 std::size_t index;
40 do {
41 index = CommitResource(fence);
42 } while (usage[index]);
43 usage[index] = true;
44
45 return {*pools[index / GROW_STEP], static_cast<std::uint32_t>(index % GROW_STEP)};
46}
47
48void QueryPool::Allocate(std::size_t begin, std::size_t end) {
49 usage.resize(end);
50
51 const auto dev = device->GetLogical();
52 const u32 size = static_cast<u32>(end - begin);
53 const vk::QueryPoolCreateInfo query_pool_ci({}, GetTarget(type), size, {});
54 pools.push_back(dev.createQueryPoolUnique(query_pool_ci, nullptr, device->GetDispatchLoader()));
55}
56
57void QueryPool::Reserve(std::pair<vk::QueryPool, std::uint32_t> query) {
58 const auto it =
59 std::find_if(std::begin(pools), std::end(pools),
60 [query_pool = query.first](auto& pool) { return query_pool == *pool; });
61 ASSERT(it != std::end(pools));
62
63 const std::ptrdiff_t pool_index = std::distance(std::begin(pools), it);
64 usage[pool_index * GROW_STEP + static_cast<std::ptrdiff_t>(query.second)] = false;
65}
66
67VKQueryCache::VKQueryCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
68 const VKDevice& device, VKScheduler& scheduler)
69 : VideoCommon::QueryCacheBase<VKQueryCache, CachedQuery, CounterStream, HostCounter,
70 QueryPool>{system, rasterizer},
71 device{device}, scheduler{scheduler} {
72 for (std::size_t i = 0; i < static_cast<std::size_t>(VideoCore::NumQueryTypes); ++i) {
73 query_pools[i].Initialize(device, static_cast<VideoCore::QueryType>(i));
74 }
75}
76
77VKQueryCache::~VKQueryCache() = default;
78
79std::pair<vk::QueryPool, std::uint32_t> VKQueryCache::AllocateQuery(VideoCore::QueryType type) {
80 return query_pools[static_cast<std::size_t>(type)].Commit(scheduler.GetFence());
81}
82
83void VKQueryCache::Reserve(VideoCore::QueryType type,
84 std::pair<vk::QueryPool, std::uint32_t> query) {
85 query_pools[static_cast<std::size_t>(type)].Reserve(query);
86}
87
88HostCounter::HostCounter(VKQueryCache& cache, std::shared_ptr<HostCounter> dependency,
89 VideoCore::QueryType type)
90 : VideoCommon::HostCounterBase<VKQueryCache, HostCounter>{std::move(dependency)}, cache{cache},
91 type{type}, query{cache.AllocateQuery(type)}, ticks{cache.Scheduler().Ticks()} {
92 const auto dev = cache.Device().GetLogical();
93 cache.Scheduler().Record([dev, query = query](vk::CommandBuffer cmdbuf, auto& dld) {
94 dev.resetQueryPoolEXT(query.first, query.second, 1, dld);
95 cmdbuf.beginQuery(query.first, query.second, vk::QueryControlFlagBits::ePrecise, dld);
96 });
97}
98
99HostCounter::~HostCounter() {
100 cache.Reserve(type, query);
101}
102
103void HostCounter::EndQuery() {
104 cache.Scheduler().Record([query = query](auto cmdbuf, auto& dld) {
105 cmdbuf.endQuery(query.first, query.second, dld);
106 });
107}
108
109u64 HostCounter::BlockingQuery() const {
110 if (ticks >= cache.Scheduler().Ticks()) {
111 cache.Scheduler().Flush();
112 }
113
114 const auto dev = cache.Device().GetLogical();
115 const auto& dld = cache.Device().GetDispatchLoader();
116 u64 value;
117 dev.getQueryPoolResults(query.first, query.second, 1, sizeof(value), &value, sizeof(value),
118 vk::QueryResultFlagBits::e64 | vk::QueryResultFlagBits::eWait, dld);
119 return value;
120}
121
122} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_query_cache.h b/src/video_core/renderer_vulkan/vk_query_cache.h
new file mode 100644
index 000000000..c3092ee96
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_query_cache.h
@@ -0,0 +1,104 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <cstddef>
8#include <cstdint>
9#include <memory>
10#include <utility>
11#include <vector>
12
13#include "common/common_types.h"
14#include "video_core/query_cache.h"
15#include "video_core/renderer_vulkan/declarations.h"
16#include "video_core/renderer_vulkan/vk_resource_manager.h"
17
18namespace VideoCore {
19class RasterizerInterface;
20}
21
22namespace Vulkan {
23
24class CachedQuery;
25class HostCounter;
26class VKDevice;
27class VKQueryCache;
28class VKScheduler;
29
30using CounterStream = VideoCommon::CounterStreamBase<VKQueryCache, HostCounter>;
31
32class QueryPool final : public VKFencedPool {
33public:
34 explicit QueryPool();
35 ~QueryPool() override;
36
37 void Initialize(const VKDevice& device, VideoCore::QueryType type);
38
39 std::pair<vk::QueryPool, std::uint32_t> Commit(VKFence& fence);
40
41 void Reserve(std::pair<vk::QueryPool, std::uint32_t> query);
42
43protected:
44 void Allocate(std::size_t begin, std::size_t end) override;
45
46private:
47 static constexpr std::size_t GROW_STEP = 512;
48
49 const VKDevice* device = nullptr;
50 VideoCore::QueryType type = {};
51
52 std::vector<UniqueQueryPool> pools;
53 std::vector<bool> usage;
54};
55
56class VKQueryCache final
57 : public VideoCommon::QueryCacheBase<VKQueryCache, CachedQuery, CounterStream, HostCounter,
58 QueryPool> {
59public:
60 explicit VKQueryCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
61 const VKDevice& device, VKScheduler& scheduler);
62 ~VKQueryCache();
63
64 std::pair<vk::QueryPool, std::uint32_t> AllocateQuery(VideoCore::QueryType type);
65
66 void Reserve(VideoCore::QueryType type, std::pair<vk::QueryPool, std::uint32_t> query);
67
68 const VKDevice& Device() const noexcept {
69 return device;
70 }
71
72 VKScheduler& Scheduler() const noexcept {
73 return scheduler;
74 }
75
76private:
77 const VKDevice& device;
78 VKScheduler& scheduler;
79};
80
81class HostCounter final : public VideoCommon::HostCounterBase<VKQueryCache, HostCounter> {
82public:
83 explicit HostCounter(VKQueryCache& cache, std::shared_ptr<HostCounter> dependency,
84 VideoCore::QueryType type);
85 ~HostCounter();
86
87 void EndQuery();
88
89private:
90 u64 BlockingQuery() const override;
91
92 VKQueryCache& cache;
93 const VideoCore::QueryType type;
94 const std::pair<vk::QueryPool, std::uint32_t> query;
95 const u64 ticks;
96};
97
98class CachedQuery : public VideoCommon::CachedQueryBase<HostCounter> {
99public:
100 explicit CachedQuery(VKQueryCache&, VideoCore::QueryType, VAddr cpu_addr, u8* host_ptr)
101 : VideoCommon::CachedQueryBase<HostCounter>{cpu_addr, host_ptr} {}
102};
103
104} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index d2c6b1189..31c078f6a 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -289,25 +289,19 @@ RasterizerVulkan::RasterizerVulkan(Core::System& system, Core::Frontend::EmuWind
289 staging_pool), 289 staging_pool),
290 pipeline_cache(system, *this, device, scheduler, descriptor_pool, update_descriptor_queue), 290 pipeline_cache(system, *this, device, scheduler, descriptor_pool, update_descriptor_queue),
291 buffer_cache(*this, system, device, memory_manager, scheduler, staging_pool), 291 buffer_cache(*this, system, device, memory_manager, scheduler, staging_pool),
292 sampler_cache(device) {} 292 sampler_cache(device), query_cache(system, *this, device, scheduler) {
293 293 scheduler.SetQueryCache(query_cache);
294RasterizerVulkan::~RasterizerVulkan() = default;
295
296bool RasterizerVulkan::DrawBatch(bool is_indexed) {
297 Draw(is_indexed, false);
298 return true;
299} 294}
300 295
301bool RasterizerVulkan::DrawMultiBatch(bool is_indexed) { 296RasterizerVulkan::~RasterizerVulkan() = default;
302 Draw(is_indexed, true);
303 return true;
304}
305 297
306void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) { 298void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) {
307 MICROPROFILE_SCOPE(Vulkan_Drawing); 299 MICROPROFILE_SCOPE(Vulkan_Drawing);
308 300
309 FlushWork(); 301 FlushWork();
310 302
303 query_cache.UpdateCounters();
304
311 const auto& gpu = system.GPU().Maxwell3D(); 305 const auto& gpu = system.GPU().Maxwell3D();
312 GraphicsPipelineCacheKey key{GetFixedPipelineState(gpu.regs)}; 306 GraphicsPipelineCacheKey key{GetFixedPipelineState(gpu.regs)};
313 307
@@ -362,6 +356,8 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) {
362void RasterizerVulkan::Clear() { 356void RasterizerVulkan::Clear() {
363 MICROPROFILE_SCOPE(Vulkan_Clearing); 357 MICROPROFILE_SCOPE(Vulkan_Clearing);
364 358
359 query_cache.UpdateCounters();
360
365 const auto& gpu = system.GPU().Maxwell3D(); 361 const auto& gpu = system.GPU().Maxwell3D();
366 if (!system.GPU().Maxwell3D().ShouldExecute()) { 362 if (!system.GPU().Maxwell3D().ShouldExecute()) {
367 return; 363 return;
@@ -429,6 +425,8 @@ void RasterizerVulkan::DispatchCompute(GPUVAddr code_addr) {
429 sampled_views.clear(); 425 sampled_views.clear();
430 image_views.clear(); 426 image_views.clear();
431 427
428 query_cache.UpdateCounters();
429
432 const auto& launch_desc = system.GPU().KeplerCompute().launch_description; 430 const auto& launch_desc = system.GPU().KeplerCompute().launch_description;
433 const ComputePipelineCacheKey key{ 431 const ComputePipelineCacheKey key{
434 code_addr, 432 code_addr,
@@ -471,17 +469,28 @@ void RasterizerVulkan::DispatchCompute(GPUVAddr code_addr) {
471 }); 469 });
472} 470}
473 471
472void RasterizerVulkan::ResetCounter(VideoCore::QueryType type) {
473 query_cache.ResetCounter(type);
474}
475
476void RasterizerVulkan::Query(GPUVAddr gpu_addr, VideoCore::QueryType type,
477 std::optional<u64> timestamp) {
478 query_cache.Query(gpu_addr, type, timestamp);
479}
480
474void RasterizerVulkan::FlushAll() {} 481void RasterizerVulkan::FlushAll() {}
475 482
476void RasterizerVulkan::FlushRegion(CacheAddr addr, u64 size) { 483void RasterizerVulkan::FlushRegion(CacheAddr addr, u64 size) {
477 texture_cache.FlushRegion(addr, size); 484 texture_cache.FlushRegion(addr, size);
478 buffer_cache.FlushRegion(addr, size); 485 buffer_cache.FlushRegion(addr, size);
486 query_cache.FlushRegion(addr, size);
479} 487}
480 488
481void RasterizerVulkan::InvalidateRegion(CacheAddr addr, u64 size) { 489void RasterizerVulkan::InvalidateRegion(CacheAddr addr, u64 size) {
482 texture_cache.InvalidateRegion(addr, size); 490 texture_cache.InvalidateRegion(addr, size);
483 pipeline_cache.InvalidateRegion(addr, size); 491 pipeline_cache.InvalidateRegion(addr, size);
484 buffer_cache.InvalidateRegion(addr, size); 492 buffer_cache.InvalidateRegion(addr, size);
493 query_cache.InvalidateRegion(addr, size);
485} 494}
486 495
487void RasterizerVulkan::FlushAndInvalidateRegion(CacheAddr addr, u64 size) { 496void RasterizerVulkan::FlushAndInvalidateRegion(CacheAddr addr, u64 size) {
@@ -571,7 +580,7 @@ RasterizerVulkan::Texceptions RasterizerVulkan::UpdateAttachments() {
571 color_attachments[rt] = texture_cache.GetColorBufferSurface(rt, true); 580 color_attachments[rt] = texture_cache.GetColorBufferSurface(rt, true);
572 } 581 }
573 if (color_attachments[rt] && WalkAttachmentOverlaps(*color_attachments[rt])) { 582 if (color_attachments[rt] && WalkAttachmentOverlaps(*color_attachments[rt])) {
574 texceptions.set(rt); 583 texceptions[rt] = true;
575 } 584 }
576 } 585 }
577 586
@@ -579,7 +588,7 @@ RasterizerVulkan::Texceptions RasterizerVulkan::UpdateAttachments() {
579 zeta_attachment = texture_cache.GetDepthBufferSurface(true); 588 zeta_attachment = texture_cache.GetDepthBufferSurface(true);
580 } 589 }
581 if (zeta_attachment && WalkAttachmentOverlaps(*zeta_attachment)) { 590 if (zeta_attachment && WalkAttachmentOverlaps(*zeta_attachment)) {
582 texceptions.set(ZETA_TEXCEPTION_INDEX); 591 texceptions[ZETA_TEXCEPTION_INDEX] = true;
583 } 592 }
584 593
585 texture_cache.GuardRenderTargets(false); 594 texture_cache.GuardRenderTargets(false);
@@ -1122,11 +1131,12 @@ RenderPassParams RasterizerVulkan::GetRenderPassParams(Texceptions texceptions)
1122 1131
1123 for (std::size_t rt = 0; rt < static_cast<std::size_t>(regs.rt_control.count); ++rt) { 1132 for (std::size_t rt = 0; rt < static_cast<std::size_t>(regs.rt_control.count); ++rt) {
1124 const auto& rendertarget = regs.rt[rt]; 1133 const auto& rendertarget = regs.rt[rt];
1125 if (rendertarget.Address() == 0 || rendertarget.format == Tegra::RenderTargetFormat::NONE) 1134 if (rendertarget.Address() == 0 || rendertarget.format == Tegra::RenderTargetFormat::NONE) {
1126 continue; 1135 continue;
1136 }
1127 renderpass_params.color_attachments.push_back(RenderPassParams::ColorAttachment{ 1137 renderpass_params.color_attachments.push_back(RenderPassParams::ColorAttachment{
1128 static_cast<u32>(rt), PixelFormatFromRenderTargetFormat(rendertarget.format), 1138 static_cast<u32>(rt), PixelFormatFromRenderTargetFormat(rendertarget.format),
1129 texceptions.test(rt)}); 1139 texceptions[rt]});
1130 } 1140 }
1131 1141
1132 renderpass_params.has_zeta = regs.zeta_enable; 1142 renderpass_params.has_zeta = regs.zeta_enable;
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h
index 7be71e734..138903d60 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.h
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.h
@@ -24,6 +24,7 @@
24#include "video_core/renderer_vulkan/vk_descriptor_pool.h" 24#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
25#include "video_core/renderer_vulkan/vk_memory_manager.h" 25#include "video_core/renderer_vulkan/vk_memory_manager.h"
26#include "video_core/renderer_vulkan/vk_pipeline_cache.h" 26#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
27#include "video_core/renderer_vulkan/vk_query_cache.h"
27#include "video_core/renderer_vulkan/vk_renderpass_cache.h" 28#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
28#include "video_core/renderer_vulkan/vk_resource_manager.h" 29#include "video_core/renderer_vulkan/vk_resource_manager.h"
29#include "video_core/renderer_vulkan/vk_sampler_cache.h" 30#include "video_core/renderer_vulkan/vk_sampler_cache.h"
@@ -96,7 +97,7 @@ struct ImageView {
96 vk::ImageLayout* layout = nullptr; 97 vk::ImageLayout* layout = nullptr;
97}; 98};
98 99
99class RasterizerVulkan : public VideoCore::RasterizerAccelerated { 100class RasterizerVulkan final : public VideoCore::RasterizerAccelerated {
100public: 101public:
101 explicit RasterizerVulkan(Core::System& system, Core::Frontend::EmuWindow& render_window, 102 explicit RasterizerVulkan(Core::System& system, Core::Frontend::EmuWindow& render_window,
102 VKScreenInfo& screen_info, const VKDevice& device, 103 VKScreenInfo& screen_info, const VKDevice& device,
@@ -104,10 +105,11 @@ public:
104 VKScheduler& scheduler); 105 VKScheduler& scheduler);
105 ~RasterizerVulkan() override; 106 ~RasterizerVulkan() override;
106 107
107 bool DrawBatch(bool is_indexed) override; 108 void Draw(bool is_indexed, bool is_instanced) override;
108 bool DrawMultiBatch(bool is_indexed) override;
109 void Clear() override; 109 void Clear() override;
110 void DispatchCompute(GPUVAddr code_addr) override; 110 void DispatchCompute(GPUVAddr code_addr) override;
111 void ResetCounter(VideoCore::QueryType type) override;
112 void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) override;
111 void FlushAll() override; 113 void FlushAll() override;
112 void FlushRegion(CacheAddr addr, u64 size) override; 114 void FlushRegion(CacheAddr addr, u64 size) override;
113 void InvalidateRegion(CacheAddr addr, u64 size) override; 115 void InvalidateRegion(CacheAddr addr, u64 size) override;
@@ -140,8 +142,6 @@ private:
140 142
141 static constexpr std::size_t ZETA_TEXCEPTION_INDEX = 8; 143 static constexpr std::size_t ZETA_TEXCEPTION_INDEX = 8;
142 144
143 void Draw(bool is_indexed, bool is_instanced);
144
145 void FlushWork(); 145 void FlushWork();
146 146
147 Texceptions UpdateAttachments(); 147 Texceptions UpdateAttachments();
@@ -247,6 +247,7 @@ private:
247 VKPipelineCache pipeline_cache; 247 VKPipelineCache pipeline_cache;
248 VKBufferCache buffer_cache; 248 VKBufferCache buffer_cache;
249 VKSamplerCache sampler_cache; 249 VKSamplerCache sampler_cache;
250 VKQueryCache query_cache;
250 251
251 std::array<View, Maxwell::NumRenderTargets> color_attachments; 252 std::array<View, Maxwell::NumRenderTargets> color_attachments;
252 View zeta_attachment; 253 View zeta_attachment;
diff --git a/src/video_core/renderer_vulkan/vk_sampler_cache.cpp b/src/video_core/renderer_vulkan/vk_sampler_cache.cpp
index 0a8ec8398..204b7c39c 100644
--- a/src/video_core/renderer_vulkan/vk_sampler_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_sampler_cache.cpp
@@ -23,7 +23,14 @@ static std::optional<vk::BorderColor> TryConvertBorderColor(std::array<float, 4>
23 } else if (color == std::array<float, 4>{1, 1, 1, 1}) { 23 } else if (color == std::array<float, 4>{1, 1, 1, 1}) {
24 return vk::BorderColor::eFloatOpaqueWhite; 24 return vk::BorderColor::eFloatOpaqueWhite;
25 } else { 25 } else {
26 return {}; 26 if (color[0] + color[1] + color[2] > 1.35f) {
27 // If color elements are brighter than roughly 0.5 average, use white border
28 return vk::BorderColor::eFloatOpaqueWhite;
29 }
30 if (color[3] > 0.5f) {
31 return vk::BorderColor::eFloatOpaqueBlack;
32 }
33 return vk::BorderColor::eFloatTransparentBlack;
27 } 34 }
28} 35}
29 36
@@ -37,8 +44,6 @@ UniqueSampler VKSamplerCache::CreateSampler(const Tegra::Texture::TSCEntry& tsc)
37 44
38 const auto border_color{tsc.GetBorderColor()}; 45 const auto border_color{tsc.GetBorderColor()};
39 const auto vk_border_color{TryConvertBorderColor(border_color)}; 46 const auto vk_border_color{TryConvertBorderColor(border_color)};
40 UNIMPLEMENTED_IF_MSG(!vk_border_color, "Unimplemented border color {} {} {} {}",
41 border_color[0], border_color[1], border_color[2], border_color[3]);
42 47
43 constexpr bool unnormalized_coords{false}; 48 constexpr bool unnormalized_coords{false};
44 49
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp
index d66133ad1..92bd6c344 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.cpp
+++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp
@@ -6,6 +6,7 @@
6#include "common/microprofile.h" 6#include "common/microprofile.h"
7#include "video_core/renderer_vulkan/declarations.h" 7#include "video_core/renderer_vulkan/declarations.h"
8#include "video_core/renderer_vulkan/vk_device.h" 8#include "video_core/renderer_vulkan/vk_device.h"
9#include "video_core/renderer_vulkan/vk_query_cache.h"
9#include "video_core/renderer_vulkan/vk_resource_manager.h" 10#include "video_core/renderer_vulkan/vk_resource_manager.h"
10#include "video_core/renderer_vulkan/vk_scheduler.h" 11#include "video_core/renderer_vulkan/vk_scheduler.h"
11 12
@@ -139,6 +140,8 @@ void VKScheduler::SubmitExecution(vk::Semaphore semaphore) {
139} 140}
140 141
141void VKScheduler::AllocateNewContext() { 142void VKScheduler::AllocateNewContext() {
143 ++ticks;
144
142 std::unique_lock lock{mutex}; 145 std::unique_lock lock{mutex};
143 current_fence = next_fence; 146 current_fence = next_fence;
144 next_fence = &resource_manager.CommitFence(); 147 next_fence = &resource_manager.CommitFence();
@@ -146,6 +149,10 @@ void VKScheduler::AllocateNewContext() {
146 current_cmdbuf = resource_manager.CommitCommandBuffer(*current_fence); 149 current_cmdbuf = resource_manager.CommitCommandBuffer(*current_fence);
147 current_cmdbuf.begin({vk::CommandBufferUsageFlagBits::eOneTimeSubmit}, 150 current_cmdbuf.begin({vk::CommandBufferUsageFlagBits::eOneTimeSubmit},
148 device.GetDispatchLoader()); 151 device.GetDispatchLoader());
152 // Enable counters once again. These are disabled when a command buffer is finished.
153 if (query_cache) {
154 query_cache->UpdateCounters();
155 }
149} 156}
150 157
151void VKScheduler::InvalidateState() { 158void VKScheduler::InvalidateState() {
@@ -159,6 +166,7 @@ void VKScheduler::InvalidateState() {
159} 166}
160 167
161void VKScheduler::EndPendingOperations() { 168void VKScheduler::EndPendingOperations() {
169 query_cache->DisableStreams();
162 EndRenderPass(); 170 EndRenderPass();
163} 171}
164 172
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h
index bcdffbba0..62fd7858b 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.h
+++ b/src/video_core/renderer_vulkan/vk_scheduler.h
@@ -4,6 +4,7 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <atomic>
7#include <condition_variable> 8#include <condition_variable>
8#include <memory> 9#include <memory>
9#include <optional> 10#include <optional>
@@ -18,6 +19,7 @@ namespace Vulkan {
18 19
19class VKDevice; 20class VKDevice;
20class VKFence; 21class VKFence;
22class VKQueryCache;
21class VKResourceManager; 23class VKResourceManager;
22 24
23class VKFenceView { 25class VKFenceView {
@@ -67,6 +69,11 @@ public:
67 /// Binds a pipeline to the current execution context. 69 /// Binds a pipeline to the current execution context.
68 void BindGraphicsPipeline(vk::Pipeline pipeline); 70 void BindGraphicsPipeline(vk::Pipeline pipeline);
69 71
72 /// Assigns the query cache.
73 void SetQueryCache(VKQueryCache& query_cache_) {
74 query_cache = &query_cache_;
75 }
76
70 /// Returns true when viewports have been set in the current command buffer. 77 /// Returns true when viewports have been set in the current command buffer.
71 bool TouchViewports() { 78 bool TouchViewports() {
72 return std::exchange(state.viewports, true); 79 return std::exchange(state.viewports, true);
@@ -112,6 +119,11 @@ public:
112 return current_fence; 119 return current_fence;
113 } 120 }
114 121
122 /// Returns the current command buffer tick.
123 u64 Ticks() const {
124 return ticks;
125 }
126
115private: 127private:
116 class Command { 128 class Command {
117 public: 129 public:
@@ -205,6 +217,8 @@ private:
205 217
206 const VKDevice& device; 218 const VKDevice& device;
207 VKResourceManager& resource_manager; 219 VKResourceManager& resource_manager;
220 VKQueryCache* query_cache = nullptr;
221
208 vk::CommandBuffer current_cmdbuf; 222 vk::CommandBuffer current_cmdbuf;
209 VKFence* current_fence = nullptr; 223 VKFence* current_fence = nullptr;
210 VKFence* next_fence = nullptr; 224 VKFence* next_fence = nullptr;
@@ -227,6 +241,7 @@ private:
227 Common::SPSCQueue<std::unique_ptr<CommandChunk>> chunk_reserve; 241 Common::SPSCQueue<std::unique_ptr<CommandChunk>> chunk_reserve;
228 std::mutex mutex; 242 std::mutex mutex;
229 std::condition_variable cv; 243 std::condition_variable cv;
244 std::atomic<u64> ticks = 0;
230 bool quit = false; 245 bool quit = false;
231}; 246};
232 247
diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
index dd6d2ef03..6d0bf6aa1 100644
--- a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
+++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
@@ -275,12 +275,14 @@ public:
275 AddCapability(spv::Capability::ImageGatherExtended); 275 AddCapability(spv::Capability::ImageGatherExtended);
276 AddCapability(spv::Capability::SampledBuffer); 276 AddCapability(spv::Capability::SampledBuffer);
277 AddCapability(spv::Capability::StorageImageWriteWithoutFormat); 277 AddCapability(spv::Capability::StorageImageWriteWithoutFormat);
278 AddCapability(spv::Capability::DrawParameters);
278 AddCapability(spv::Capability::SubgroupBallotKHR); 279 AddCapability(spv::Capability::SubgroupBallotKHR);
279 AddCapability(spv::Capability::SubgroupVoteKHR); 280 AddCapability(spv::Capability::SubgroupVoteKHR);
280 AddExtension("SPV_KHR_shader_ballot"); 281 AddExtension("SPV_KHR_shader_ballot");
281 AddExtension("SPV_KHR_subgroup_vote"); 282 AddExtension("SPV_KHR_subgroup_vote");
282 AddExtension("SPV_KHR_storage_buffer_storage_class"); 283 AddExtension("SPV_KHR_storage_buffer_storage_class");
283 AddExtension("SPV_KHR_variable_pointers"); 284 AddExtension("SPV_KHR_variable_pointers");
285 AddExtension("SPV_KHR_shader_draw_parameters");
284 286
285 if (ir.UsesViewportIndex()) { 287 if (ir.UsesViewportIndex()) {
286 AddCapability(spv::Capability::MultiViewport); 288 AddCapability(spv::Capability::MultiViewport);
@@ -290,6 +292,10 @@ public:
290 } 292 }
291 } 293 }
292 294
295 if (device.IsShaderStorageImageReadWithoutFormatSupported()) {
296 AddCapability(spv::Capability::StorageImageReadWithoutFormat);
297 }
298
293 if (device.IsFloat16Supported()) { 299 if (device.IsFloat16Supported()) {
294 AddCapability(spv::Capability::Float16); 300 AddCapability(spv::Capability::Float16);
295 } 301 }
@@ -353,6 +359,7 @@ private:
353 DeclareFragment(); 359 DeclareFragment();
354 DeclareCompute(); 360 DeclareCompute();
355 DeclareRegisters(); 361 DeclareRegisters();
362 DeclareCustomVariables();
356 DeclarePredicates(); 363 DeclarePredicates();
357 DeclareLocalMemory(); 364 DeclareLocalMemory();
358 DeclareSharedMemory(); 365 DeclareSharedMemory();
@@ -491,9 +498,11 @@ private:
491 interfaces.push_back(AddGlobalVariable(Name(out_vertex, "out_vertex"))); 498 interfaces.push_back(AddGlobalVariable(Name(out_vertex, "out_vertex")));
492 499
493 // Declare input attributes 500 // Declare input attributes
494 vertex_index = DeclareInputBuiltIn(spv::BuiltIn::VertexIndex, t_in_uint, "vertex_index"); 501 vertex_index = DeclareInputBuiltIn(spv::BuiltIn::VertexIndex, t_in_int, "vertex_index");
495 instance_index = 502 instance_index =
496 DeclareInputBuiltIn(spv::BuiltIn::InstanceIndex, t_in_uint, "instance_index"); 503 DeclareInputBuiltIn(spv::BuiltIn::InstanceIndex, t_in_int, "instance_index");
504 base_vertex = DeclareInputBuiltIn(spv::BuiltIn::BaseVertex, t_in_int, "base_vertex");
505 base_instance = DeclareInputBuiltIn(spv::BuiltIn::BaseInstance, t_in_int, "base_instance");
497 } 506 }
498 507
499 void DeclareTessControl() { 508 void DeclareTessControl() {
@@ -542,11 +551,10 @@ private:
542 return; 551 return;
543 } 552 }
544 553
545 for (u32 rt = 0; rt < static_cast<u32>(frag_colors.size()); ++rt) { 554 for (u32 rt = 0; rt < static_cast<u32>(std::size(frag_colors)); ++rt) {
546 if (!specialization.enabled_rendertargets[rt]) { 555 if (!IsRenderTargetEnabled(rt)) {
547 continue; 556 continue;
548 } 557 }
549
550 const Id id = AddGlobalVariable(OpVariable(t_out_float4, spv::StorageClass::Output)); 558 const Id id = AddGlobalVariable(OpVariable(t_out_float4, spv::StorageClass::Output));
551 Name(id, fmt::format("frag_color{}", rt)); 559 Name(id, fmt::format("frag_color{}", rt));
552 Decorate(id, spv::Decoration::Location, rt); 560 Decorate(id, spv::Decoration::Location, rt);
@@ -587,6 +595,15 @@ private:
587 } 595 }
588 } 596 }
589 597
598 void DeclareCustomVariables() {
599 const u32 num_custom_variables = ir.GetNumCustomVariables();
600 for (u32 i = 0; i < num_custom_variables; ++i) {
601 const Id id = OpVariable(t_prv_float, spv::StorageClass::Private, v_float_zero);
602 Name(id, fmt::format("custom_var_{}", i));
603 custom_variables.emplace(i, AddGlobalVariable(id));
604 }
605 }
606
590 void DeclarePredicates() { 607 void DeclarePredicates() {
591 for (const auto pred : ir.GetPredicates()) { 608 for (const auto pred : ir.GetPredicates()) {
592 const Id id = OpVariable(t_prv_bool, spv::StorageClass::Private, v_false); 609 const Id id = OpVariable(t_prv_bool, spv::StorageClass::Private, v_false);
@@ -852,6 +869,15 @@ private:
852 return binding; 869 return binding;
853 } 870 }
854 871
872 bool IsRenderTargetEnabled(u32 rt) const {
873 for (u32 component = 0; component < 4; ++component) {
874 if (header.ps.IsColorComponentOutputEnabled(rt, component)) {
875 return true;
876 }
877 }
878 return false;
879 }
880
855 bool IsInputAttributeArray() const { 881 bool IsInputAttributeArray() const {
856 return stage == ShaderType::TesselationControl || stage == ShaderType::TesselationEval || 882 return stage == ShaderType::TesselationControl || stage == ShaderType::TesselationEval ||
857 stage == ShaderType::Geometry; 883 stage == ShaderType::Geometry;
@@ -974,6 +1000,11 @@ private:
974 return {OpLoad(t_float, registers.at(index)), Type::Float}; 1000 return {OpLoad(t_float, registers.at(index)), Type::Float};
975 } 1001 }
976 1002
1003 if (const auto cv = std::get_if<CustomVarNode>(&*node)) {
1004 const u32 index = cv->GetIndex();
1005 return {OpLoad(t_float, custom_variables.at(index)), Type::Float};
1006 }
1007
977 if (const auto immediate = std::get_if<ImmediateNode>(&*node)) { 1008 if (const auto immediate = std::get_if<ImmediateNode>(&*node)) {
978 return {Constant(t_uint, immediate->GetValue()), Type::Uint}; 1009 return {Constant(t_uint, immediate->GetValue()), Type::Uint};
979 } 1010 }
@@ -1045,9 +1076,12 @@ private:
1045 return {OpLoad(t_float, AccessElement(t_in_float, tess_coord, element)), 1076 return {OpLoad(t_float, AccessElement(t_in_float, tess_coord, element)),
1046 Type::Float}; 1077 Type::Float};
1047 case 2: 1078 case 2:
1048 return {OpLoad(t_uint, instance_index), Type::Uint}; 1079 return {
1080 OpISub(t_int, OpLoad(t_int, instance_index), OpLoad(t_int, base_instance)),
1081 Type::Int};
1049 case 3: 1082 case 3:
1050 return {OpLoad(t_uint, vertex_index), Type::Uint}; 1083 return {OpISub(t_int, OpLoad(t_int, vertex_index), OpLoad(t_int, base_vertex)),
1084 Type::Int};
1051 } 1085 }
1052 UNIMPLEMENTED_MSG("Unmanaged TessCoordInstanceIDVertexID element={}", element); 1086 UNIMPLEMENTED_MSG("Unmanaged TessCoordInstanceIDVertexID element={}", element);
1053 return {Constant(t_uint, 0U), Type::Uint}; 1087 return {Constant(t_uint, 0U), Type::Uint};
@@ -1115,15 +1149,7 @@ private:
1115 } 1149 }
1116 1150
1117 if (const auto gmem = std::get_if<GmemNode>(&*node)) { 1151 if (const auto gmem = std::get_if<GmemNode>(&*node)) {
1118 const Id gmem_buffer = global_buffers.at(gmem->GetDescriptor()); 1152 return {OpLoad(t_uint, GetGlobalMemoryPointer(*gmem)), Type::Uint};
1119 const Id real = AsUint(Visit(gmem->GetRealAddress()));
1120 const Id base = AsUint(Visit(gmem->GetBaseAddress()));
1121
1122 Id offset = OpISub(t_uint, real, base);
1123 offset = OpUDiv(t_uint, offset, Constant(t_uint, 4U));
1124 return {OpLoad(t_float,
1125 OpAccessChain(t_gmem_float, gmem_buffer, Constant(t_uint, 0U), offset)),
1126 Type::Float};
1127 } 1153 }
1128 1154
1129 if (const auto lmem = std::get_if<LmemNode>(&*node)) { 1155 if (const auto lmem = std::get_if<LmemNode>(&*node)) {
@@ -1134,10 +1160,7 @@ private:
1134 } 1160 }
1135 1161
1136 if (const auto smem = std::get_if<SmemNode>(&*node)) { 1162 if (const auto smem = std::get_if<SmemNode>(&*node)) {
1137 Id address = AsUint(Visit(smem->GetAddress())); 1163 return {OpLoad(t_uint, GetSharedMemoryPointer(*smem)), Type::Uint};
1138 address = OpShiftRightLogical(t_uint, address, Constant(t_uint, 2U));
1139 const Id pointer = OpAccessChain(t_smem_uint, shared_memory, address);
1140 return {OpLoad(t_uint, pointer), Type::Uint};
1141 } 1164 }
1142 1165
1143 if (const auto internal_flag = std::get_if<InternalFlagNode>(&*node)) { 1166 if (const auto internal_flag = std::get_if<InternalFlagNode>(&*node)) {
@@ -1331,20 +1354,13 @@ private:
1331 target = {OpAccessChain(t_prv_float, local_memory, address), Type::Float}; 1354 target = {OpAccessChain(t_prv_float, local_memory, address), Type::Float};
1332 1355
1333 } else if (const auto smem = std::get_if<SmemNode>(&*dest)) { 1356 } else if (const auto smem = std::get_if<SmemNode>(&*dest)) {
1334 ASSERT(stage == ShaderType::Compute); 1357 target = {GetSharedMemoryPointer(*smem), Type::Uint};
1335 Id address = AsUint(Visit(smem->GetAddress()));
1336 address = OpShiftRightLogical(t_uint, address, Constant(t_uint, 2U));
1337 target = {OpAccessChain(t_smem_uint, shared_memory, address), Type::Uint};
1338 1358
1339 } else if (const auto gmem = std::get_if<GmemNode>(&*dest)) { 1359 } else if (const auto gmem = std::get_if<GmemNode>(&*dest)) {
1340 const Id real = AsUint(Visit(gmem->GetRealAddress())); 1360 target = {GetGlobalMemoryPointer(*gmem), Type::Uint};
1341 const Id base = AsUint(Visit(gmem->GetBaseAddress()));
1342 const Id diff = OpISub(t_uint, real, base);
1343 const Id offset = OpShiftRightLogical(t_uint, diff, Constant(t_uint, 2));
1344 1361
1345 const Id gmem_buffer = global_buffers.at(gmem->GetDescriptor()); 1362 } else if (const auto cv = std::get_if<CustomVarNode>(&*dest)) {
1346 target = {OpAccessChain(t_gmem_float, gmem_buffer, Constant(t_uint, 0), offset), 1363 target = {custom_variables.at(cv->GetIndex()), Type::Float};
1347 Type::Float};
1348 1364
1349 } else { 1365 } else {
1350 UNIMPLEMENTED(); 1366 UNIMPLEMENTED();
@@ -1743,8 +1759,16 @@ private:
1743 } 1759 }
1744 1760
1745 Expression ImageLoad(Operation operation) { 1761 Expression ImageLoad(Operation operation) {
1746 UNIMPLEMENTED(); 1762 if (!device.IsShaderStorageImageReadWithoutFormatSupported()) {
1747 return {}; 1763 return {v_float_zero, Type::Float};
1764 }
1765
1766 const auto& meta{std::get<MetaImage>(operation.GetMeta())};
1767
1768 const Id coords = GetCoordinates(operation, Type::Int);
1769 const Id texel = OpImageRead(t_uint4, GetImage(operation), coords);
1770
1771 return {OpCompositeExtract(t_uint, texel, meta.element), Type::Uint};
1748 } 1772 }
1749 1773
1750 Expression ImageStore(Operation operation) { 1774 Expression ImageStore(Operation operation) {
@@ -1796,11 +1820,16 @@ private:
1796 return {}; 1820 return {};
1797 } 1821 }
1798 1822
1799 Expression UAtomicAdd(Operation operation) { 1823 Expression AtomicAdd(Operation operation) {
1800 const auto& smem = std::get<SmemNode>(*operation[0]); 1824 Id pointer;
1801 Id address = AsUint(Visit(smem.GetAddress())); 1825 if (const auto smem = std::get_if<SmemNode>(&*operation[0])) {
1802 address = OpShiftRightLogical(t_uint, address, Constant(t_uint, 2U)); 1826 pointer = GetSharedMemoryPointer(*smem);
1803 const Id pointer = OpAccessChain(t_smem_uint, shared_memory, address); 1827 } else if (const auto gmem = std::get_if<GmemNode>(&*operation[0])) {
1828 pointer = GetGlobalMemoryPointer(*gmem);
1829 } else {
1830 UNREACHABLE();
1831 return {Constant(t_uint, 0), Type::Uint};
1832 }
1804 1833
1805 const Id scope = Constant(t_uint, static_cast<u32>(spv::Scope::Device)); 1834 const Id scope = Constant(t_uint, static_cast<u32>(spv::Scope::Device));
1806 const Id semantics = Constant(t_uint, 0U); 1835 const Id semantics = Constant(t_uint, 0U);
@@ -1889,19 +1918,14 @@ private:
1889 // rendertargets/components are skipped in the register assignment. 1918 // rendertargets/components are skipped in the register assignment.
1890 u32 current_reg = 0; 1919 u32 current_reg = 0;
1891 for (u32 rt = 0; rt < Maxwell::NumRenderTargets; ++rt) { 1920 for (u32 rt = 0; rt < Maxwell::NumRenderTargets; ++rt) {
1892 if (!specialization.enabled_rendertargets[rt]) {
1893 // Skip rendertargets that are not enabled
1894 continue;
1895 }
1896 // TODO(Subv): Figure out how dual-source blending is configured in the Switch. 1921 // TODO(Subv): Figure out how dual-source blending is configured in the Switch.
1897 for (u32 component = 0; component < 4; ++component) { 1922 for (u32 component = 0; component < 4; ++component) {
1898 const Id pointer = AccessElement(t_out_float, frag_colors.at(rt), component); 1923 if (!header.ps.IsColorComponentOutputEnabled(rt, component)) {
1899 if (header.ps.IsColorComponentOutputEnabled(rt, component)) { 1924 continue;
1900 OpStore(pointer, SafeGetRegister(current_reg));
1901 ++current_reg;
1902 } else {
1903 OpStore(pointer, component == 3 ? v_float_one : v_float_zero);
1904 } 1925 }
1926 const Id pointer = AccessElement(t_out_float, frag_colors[rt], component);
1927 OpStore(pointer, SafeGetRegister(current_reg));
1928 ++current_reg;
1905 } 1929 }
1906 } 1930 }
1907 if (header.ps.omap.depth) { 1931 if (header.ps.omap.depth) {
@@ -2240,6 +2264,22 @@ private:
2240 return {}; 2264 return {};
2241 } 2265 }
2242 2266
2267 Id GetGlobalMemoryPointer(const GmemNode& gmem) {
2268 const Id real = AsUint(Visit(gmem.GetRealAddress()));
2269 const Id base = AsUint(Visit(gmem.GetBaseAddress()));
2270 const Id diff = OpISub(t_uint, real, base);
2271 const Id offset = OpShiftRightLogical(t_uint, diff, Constant(t_uint, 2));
2272 const Id buffer = global_buffers.at(gmem.GetDescriptor());
2273 return OpAccessChain(t_gmem_uint, buffer, Constant(t_uint, 0), offset);
2274 }
2275
2276 Id GetSharedMemoryPointer(const SmemNode& smem) {
2277 ASSERT(stage == ShaderType::Compute);
2278 Id address = AsUint(Visit(smem.GetAddress()));
2279 address = OpShiftRightLogical(t_uint, address, Constant(t_uint, 2U));
2280 return OpAccessChain(t_smem_uint, shared_memory, address);
2281 }
2282
2243 static constexpr std::array operation_decompilers = { 2283 static constexpr std::array operation_decompilers = {
2244 &SPIRVDecompiler::Assign, 2284 &SPIRVDecompiler::Assign,
2245 2285
@@ -2386,7 +2426,7 @@ private:
2386 &SPIRVDecompiler::AtomicImageXor, 2426 &SPIRVDecompiler::AtomicImageXor,
2387 &SPIRVDecompiler::AtomicImageExchange, 2427 &SPIRVDecompiler::AtomicImageExchange,
2388 2428
2389 &SPIRVDecompiler::UAtomicAdd, 2429 &SPIRVDecompiler::AtomicAdd,
2390 2430
2391 &SPIRVDecompiler::Branch, 2431 &SPIRVDecompiler::Branch,
2392 &SPIRVDecompiler::BranchIndirect, 2432 &SPIRVDecompiler::BranchIndirect,
@@ -2482,9 +2522,9 @@ private:
2482 2522
2483 Id t_smem_uint{}; 2523 Id t_smem_uint{};
2484 2524
2485 const Id t_gmem_float = TypePointer(spv::StorageClass::StorageBuffer, t_float); 2525 const Id t_gmem_uint = TypePointer(spv::StorageClass::StorageBuffer, t_uint);
2486 const Id t_gmem_array = 2526 const Id t_gmem_array =
2487 Name(Decorate(TypeRuntimeArray(t_float), spv::Decoration::ArrayStride, 4U), "GmemArray"); 2527 Name(Decorate(TypeRuntimeArray(t_uint), spv::Decoration::ArrayStride, 4U), "GmemArray");
2488 const Id t_gmem_struct = MemberDecorate( 2528 const Id t_gmem_struct = MemberDecorate(
2489 Decorate(TypeStruct(t_gmem_array), spv::Decoration::Block), 0, spv::Decoration::Offset, 0); 2529 Decorate(TypeStruct(t_gmem_array), spv::Decoration::Block), 0, spv::Decoration::Offset, 0);
2490 const Id t_gmem_ssbo = TypePointer(spv::StorageClass::StorageBuffer, t_gmem_struct); 2530 const Id t_gmem_ssbo = TypePointer(spv::StorageClass::StorageBuffer, t_gmem_struct);
@@ -2505,6 +2545,7 @@ private:
2505 Id out_vertex{}; 2545 Id out_vertex{};
2506 Id in_vertex{}; 2546 Id in_vertex{};
2507 std::map<u32, Id> registers; 2547 std::map<u32, Id> registers;
2548 std::map<u32, Id> custom_variables;
2508 std::map<Tegra::Shader::Pred, Id> predicates; 2549 std::map<Tegra::Shader::Pred, Id> predicates;
2509 std::map<u32, Id> flow_variables; 2550 std::map<u32, Id> flow_variables;
2510 Id local_memory{}; 2551 Id local_memory{};
@@ -2520,6 +2561,8 @@ private:
2520 2561
2521 Id instance_index{}; 2562 Id instance_index{};
2522 Id vertex_index{}; 2563 Id vertex_index{};
2564 Id base_instance{};
2565 Id base_vertex{};
2523 std::array<Id, Maxwell::NumRenderTargets> frag_colors{}; 2566 std::array<Id, Maxwell::NumRenderTargets> frag_colors{};
2524 Id frag_depth{}; 2567 Id frag_depth{};
2525 Id frag_coord{}; 2568 Id frag_coord{};
diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.h b/src/video_core/renderer_vulkan/vk_shader_decompiler.h
index 10794be1c..f5dc14d9e 100644
--- a/src/video_core/renderer_vulkan/vk_shader_decompiler.h
+++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.h
@@ -102,9 +102,6 @@ struct Specialization final {
102 Maxwell::TessellationSpacing spacing{}; 102 Maxwell::TessellationSpacing spacing{};
103 bool clockwise{}; 103 bool clockwise{};
104 } tessellation; 104 } tessellation;
105
106 // Fragment specific
107 std::bitset<8> enabled_rendertargets;
108}; 105};
109// Old gcc versions don't consider this trivially copyable. 106// Old gcc versions don't consider this trivially copyable.
110// static_assert(std::is_trivially_copyable_v<Specialization>); 107// static_assert(std::is_trivially_copyable_v<Specialization>);
diff --git a/src/video_core/shader/ast.h b/src/video_core/shader/ast.h
index a2f0044ba..cca13bcde 100644
--- a/src/video_core/shader/ast.h
+++ b/src/video_core/shader/ast.h
@@ -65,8 +65,8 @@ public:
65 void DetachSegment(ASTNode start, ASTNode end); 65 void DetachSegment(ASTNode start, ASTNode end);
66 void Remove(ASTNode node); 66 void Remove(ASTNode node);
67 67
68 ASTNode first{}; 68 ASTNode first;
69 ASTNode last{}; 69 ASTNode last;
70}; 70};
71 71
72class ASTProgram { 72class ASTProgram {
@@ -299,9 +299,9 @@ private:
299 friend class ASTZipper; 299 friend class ASTZipper;
300 300
301 ASTData data; 301 ASTData data;
302 ASTNode parent{}; 302 ASTNode parent;
303 ASTNode next{}; 303 ASTNode next;
304 ASTNode previous{}; 304 ASTNode previous;
305 ASTZipper* manager{}; 305 ASTZipper* manager{};
306}; 306};
307 307
diff --git a/src/video_core/shader/const_buffer_locker.cpp b/src/video_core/shader/const_buffer_locker.cpp
index a4a0319eb..0638be8cb 100644
--- a/src/video_core/shader/const_buffer_locker.cpp
+++ b/src/video_core/shader/const_buffer_locker.cpp
@@ -66,6 +66,18 @@ std::optional<Tegra::Engines::SamplerDescriptor> ConstBufferLocker::ObtainBindle
66 return value; 66 return value;
67} 67}
68 68
69std::optional<u32> ConstBufferLocker::ObtainBoundBuffer() {
70 if (bound_buffer_saved) {
71 return bound_buffer;
72 }
73 if (!engine) {
74 return std::nullopt;
75 }
76 bound_buffer_saved = true;
77 bound_buffer = engine->GetBoundBuffer();
78 return bound_buffer;
79}
80
69void ConstBufferLocker::InsertKey(u32 buffer, u32 offset, u32 value) { 81void ConstBufferLocker::InsertKey(u32 buffer, u32 offset, u32 value) {
70 keys.insert_or_assign({buffer, offset}, value); 82 keys.insert_or_assign({buffer, offset}, value);
71} 83}
@@ -78,6 +90,11 @@ void ConstBufferLocker::InsertBindlessSampler(u32 buffer, u32 offset, SamplerDes
78 bindless_samplers.insert_or_assign({buffer, offset}, sampler); 90 bindless_samplers.insert_or_assign({buffer, offset}, sampler);
79} 91}
80 92
93void ConstBufferLocker::SetBoundBuffer(u32 buffer) {
94 bound_buffer_saved = true;
95 bound_buffer = buffer;
96}
97
81bool ConstBufferLocker::IsConsistent() const { 98bool ConstBufferLocker::IsConsistent() const {
82 if (!engine) { 99 if (!engine) {
83 return false; 100 return false;
diff --git a/src/video_core/shader/const_buffer_locker.h b/src/video_core/shader/const_buffer_locker.h
index d32e2d657..d3ea11087 100644
--- a/src/video_core/shader/const_buffer_locker.h
+++ b/src/video_core/shader/const_buffer_locker.h
@@ -10,6 +10,7 @@
10#include "common/hash.h" 10#include "common/hash.h"
11#include "video_core/engines/const_buffer_engine_interface.h" 11#include "video_core/engines/const_buffer_engine_interface.h"
12#include "video_core/engines/shader_type.h" 12#include "video_core/engines/shader_type.h"
13#include "video_core/guest_driver.h"
13 14
14namespace VideoCommon::Shader { 15namespace VideoCommon::Shader {
15 16
@@ -40,6 +41,8 @@ public:
40 41
41 std::optional<Tegra::Engines::SamplerDescriptor> ObtainBindlessSampler(u32 buffer, u32 offset); 42 std::optional<Tegra::Engines::SamplerDescriptor> ObtainBindlessSampler(u32 buffer, u32 offset);
42 43
44 std::optional<u32> ObtainBoundBuffer();
45
43 /// Inserts a key. 46 /// Inserts a key.
44 void InsertKey(u32 buffer, u32 offset, u32 value); 47 void InsertKey(u32 buffer, u32 offset, u32 value);
45 48
@@ -49,6 +52,9 @@ public:
49 /// Inserts a bindless sampler key. 52 /// Inserts a bindless sampler key.
50 void InsertBindlessSampler(u32 buffer, u32 offset, Tegra::Engines::SamplerDescriptor sampler); 53 void InsertBindlessSampler(u32 buffer, u32 offset, Tegra::Engines::SamplerDescriptor sampler);
51 54
55 /// Set the bound buffer for this locker.
56 void SetBoundBuffer(u32 buffer);
57
52 /// Checks keys and samplers against engine's current const buffers. Returns true if they are 58 /// Checks keys and samplers against engine's current const buffers. Returns true if they are
53 /// the same value, false otherwise; 59 /// the same value, false otherwise;
54 bool IsConsistent() const; 60 bool IsConsistent() const;
@@ -71,12 +77,27 @@ public:
71 return bindless_samplers; 77 return bindless_samplers;
72 } 78 }
73 79
80 /// Gets bound buffer used on this shader
81 u32 GetBoundBuffer() const {
82 return bound_buffer;
83 }
84
85 /// Obtains access to the guest driver's profile.
86 VideoCore::GuestDriverProfile* AccessGuestDriverProfile() const {
87 if (engine) {
88 return &engine->AccessGuestDriverProfile();
89 }
90 return nullptr;
91 }
92
74private: 93private:
75 const Tegra::Engines::ShaderType stage; 94 const Tegra::Engines::ShaderType stage;
76 Tegra::Engines::ConstBufferEngineInterface* engine = nullptr; 95 Tegra::Engines::ConstBufferEngineInterface* engine = nullptr;
77 KeyMap keys; 96 KeyMap keys;
78 BoundSamplerMap bound_samplers; 97 BoundSamplerMap bound_samplers;
79 BindlessSamplerMap bindless_samplers; 98 BindlessSamplerMap bindless_samplers;
99 bool bound_buffer_saved{};
100 u32 bound_buffer{};
80}; 101};
81 102
82} // namespace VideoCommon::Shader 103} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode.cpp b/src/video_core/shader/decode.cpp
index 22c3e5120..6b697ed5d 100644
--- a/src/video_core/shader/decode.cpp
+++ b/src/video_core/shader/decode.cpp
@@ -3,6 +3,7 @@
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <cstring> 5#include <cstring>
6#include <limits>
6#include <set> 7#include <set>
7 8
8#include <fmt/format.h> 9#include <fmt/format.h>
@@ -33,6 +34,52 @@ constexpr bool IsSchedInstruction(u32 offset, u32 main_offset) {
33 return (absolute_offset % SchedPeriod) == 0; 34 return (absolute_offset % SchedPeriod) == 0;
34} 35}
35 36
37void DeduceTextureHandlerSize(VideoCore::GuestDriverProfile* gpu_driver,
38 const std::list<Sampler>& used_samplers) {
39 if (gpu_driver == nullptr) {
40 LOG_CRITICAL(HW_GPU, "GPU driver profile has not been created yet");
41 return;
42 }
43 if (gpu_driver->TextureHandlerSizeKnown() || used_samplers.size() <= 1) {
44 return;
45 }
46 u32 count{};
47 std::vector<u32> bound_offsets;
48 for (const auto& sampler : used_samplers) {
49 if (sampler.IsBindless()) {
50 continue;
51 }
52 ++count;
53 bound_offsets.emplace_back(sampler.GetOffset());
54 }
55 if (count > 1) {
56 gpu_driver->DeduceTextureHandlerSize(std::move(bound_offsets));
57 }
58}
59
60std::optional<u32> TryDeduceSamplerSize(const Sampler& sampler_to_deduce,
61 VideoCore::GuestDriverProfile* gpu_driver,
62 const std::list<Sampler>& used_samplers) {
63 if (gpu_driver == nullptr) {
64 LOG_CRITICAL(HW_GPU, "GPU Driver profile has not been created yet");
65 return std::nullopt;
66 }
67 const u32 base_offset = sampler_to_deduce.GetOffset();
68 u32 max_offset{std::numeric_limits<u32>::max()};
69 for (const auto& sampler : used_samplers) {
70 if (sampler.IsBindless()) {
71 continue;
72 }
73 if (sampler.GetOffset() > base_offset) {
74 max_offset = std::min(sampler.GetOffset(), max_offset);
75 }
76 }
77 if (max_offset == std::numeric_limits<u32>::max()) {
78 return std::nullopt;
79 }
80 return ((max_offset - base_offset) * 4) / gpu_driver->GetTextureHandlerSize();
81}
82
36} // Anonymous namespace 83} // Anonymous namespace
37 84
38class ASTDecoder { 85class ASTDecoder {
@@ -315,4 +362,25 @@ u32 ShaderIR::DecodeInstr(NodeBlock& bb, u32 pc) {
315 return pc + 1; 362 return pc + 1;
316} 363}
317 364
365void ShaderIR::PostDecode() {
366 // Deduce texture handler size if needed
367 auto gpu_driver = locker.AccessGuestDriverProfile();
368 DeduceTextureHandlerSize(gpu_driver, used_samplers);
369 // Deduce Indexed Samplers
370 if (!uses_indexed_samplers) {
371 return;
372 }
373 for (auto& sampler : used_samplers) {
374 if (!sampler.IsIndexed()) {
375 continue;
376 }
377 if (const auto size = TryDeduceSamplerSize(sampler, gpu_driver, used_samplers)) {
378 sampler.SetSize(*size);
379 } else {
380 LOG_CRITICAL(HW_GPU, "Failed to deduce size of indexed sampler");
381 sampler.SetSize(1);
382 }
383 }
384}
385
318} // namespace VideoCommon::Shader 386} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/arithmetic.cpp b/src/video_core/shader/decode/arithmetic.cpp
index fcedd2af6..90240c765 100644
--- a/src/video_core/shader/decode/arithmetic.cpp
+++ b/src/video_core/shader/decode/arithmetic.cpp
@@ -21,7 +21,7 @@ u32 ShaderIR::DecodeArithmetic(NodeBlock& bb, u32 pc) {
21 21
22 Node op_a = GetRegister(instr.gpr8); 22 Node op_a = GetRegister(instr.gpr8);
23 23
24 Node op_b = [&]() -> Node { 24 Node op_b = [&] {
25 if (instr.is_b_imm) { 25 if (instr.is_b_imm) {
26 return GetImmediate19(instr); 26 return GetImmediate19(instr);
27 } else if (instr.is_b_gpr) { 27 } else if (instr.is_b_gpr) {
@@ -141,6 +141,15 @@ u32 ShaderIR::DecodeArithmetic(NodeBlock& bb, u32 pc) {
141 SetRegister(bb, instr.gpr0, value); 141 SetRegister(bb, instr.gpr0, value);
142 break; 142 break;
143 } 143 }
144 case OpCode::Id::FCMP_R: {
145 UNIMPLEMENTED_IF(instr.fcmp.ftz == 0);
146 Node op_c = GetRegister(instr.gpr39);
147 Node comp = GetPredicateComparisonFloat(instr.fcmp.cond, std::move(op_c), Immediate(0.0f));
148 SetRegister(
149 bb, instr.gpr0,
150 Operation(OperationCode::Select, std::move(comp), std::move(op_a), std::move(op_b)));
151 break;
152 }
144 case OpCode::Id::RRO_C: 153 case OpCode::Id::RRO_C:
145 case OpCode::Id::RRO_R: 154 case OpCode::Id::RRO_R:
146 case OpCode::Id::RRO_IMM: { 155 case OpCode::Id::RRO_IMM: {
diff --git a/src/video_core/shader/decode/arithmetic_integer.cpp b/src/video_core/shader/decode/arithmetic_integer.cpp
index 371fae127..21366869d 100644
--- a/src/video_core/shader/decode/arithmetic_integer.cpp
+++ b/src/video_core/shader/decode/arithmetic_integer.cpp
@@ -166,13 +166,13 @@ u32 ShaderIR::DecodeArithmeticInteger(NodeBlock& bb, u32 pc) {
166 const auto [op_rhs, test] = [&]() -> std::pair<Node, Node> { 166 const auto [op_rhs, test] = [&]() -> std::pair<Node, Node> {
167 switch (opcode->get().GetId()) { 167 switch (opcode->get().GetId()) {
168 case OpCode::Id::ICMP_CR: 168 case OpCode::Id::ICMP_CR:
169 return {GetConstBuffer(instr.cbuf34.index, instr.cbuf34.offset), 169 return {GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset()),
170 GetRegister(instr.gpr39)}; 170 GetRegister(instr.gpr39)};
171 case OpCode::Id::ICMP_R: 171 case OpCode::Id::ICMP_R:
172 return {GetRegister(instr.gpr20), GetRegister(instr.gpr39)}; 172 return {GetRegister(instr.gpr20), GetRegister(instr.gpr39)};
173 case OpCode::Id::ICMP_RC: 173 case OpCode::Id::ICMP_RC:
174 return {GetRegister(instr.gpr39), 174 return {GetRegister(instr.gpr39),
175 GetConstBuffer(instr.cbuf34.index, instr.cbuf34.offset)}; 175 GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset())};
176 case OpCode::Id::ICMP_IMM: 176 case OpCode::Id::ICMP_IMM:
177 return {Immediate(instr.alu.GetSignedImm20_20()), GetRegister(instr.gpr39)}; 177 return {Immediate(instr.alu.GetSignedImm20_20()), GetRegister(instr.gpr39)};
178 default: 178 default:
@@ -297,7 +297,7 @@ void ShaderIR::WriteLop3Instruction(NodeBlock& bb, Register dest, Node op_a, Nod
297 const Node one = Immediate(1); 297 const Node one = Immediate(1);
298 const Node two = Immediate(2); 298 const Node two = Immediate(2);
299 299
300 Node value{}; 300 Node value;
301 for (u32 i = 0; i < lop_iterations; ++i) { 301 for (u32 i = 0; i < lop_iterations; ++i) {
302 const Node shift_amount = Immediate(i); 302 const Node shift_amount = Immediate(i);
303 303
diff --git a/src/video_core/shader/decode/bfi.cpp b/src/video_core/shader/decode/bfi.cpp
index 8be1119df..70d1c055b 100644
--- a/src/video_core/shader/decode/bfi.cpp
+++ b/src/video_core/shader/decode/bfi.cpp
@@ -17,10 +17,13 @@ u32 ShaderIR::DecodeBfi(NodeBlock& bb, u32 pc) {
17 const Instruction instr = {program_code[pc]}; 17 const Instruction instr = {program_code[pc]};
18 const auto opcode = OpCode::Decode(instr); 18 const auto opcode = OpCode::Decode(instr);
19 19
20 const auto [base, packed_shift] = [&]() -> std::tuple<Node, Node> { 20 const auto [packed_shift, base] = [&]() -> std::pair<Node, Node> {
21 switch (opcode->get().GetId()) { 21 switch (opcode->get().GetId()) {
22 case OpCode::Id::BFI_RC:
23 return {GetRegister(instr.gpr39),
24 GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset())};
22 case OpCode::Id::BFI_IMM_R: 25 case OpCode::Id::BFI_IMM_R:
23 return {GetRegister(instr.gpr39), Immediate(instr.alu.GetSignedImm20_20())}; 26 return {Immediate(instr.alu.GetSignedImm20_20()), GetRegister(instr.gpr39)};
24 default: 27 default:
25 UNREACHABLE(); 28 UNREACHABLE();
26 return {Immediate(0), Immediate(0)}; 29 return {Immediate(0), Immediate(0)};
diff --git a/src/video_core/shader/decode/conversion.cpp b/src/video_core/shader/decode/conversion.cpp
index 0eeb75559..6ead42070 100644
--- a/src/video_core/shader/decode/conversion.cpp
+++ b/src/video_core/shader/decode/conversion.cpp
@@ -83,14 +83,14 @@ u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) {
83 83
84 const bool input_signed = instr.conversion.is_input_signed; 84 const bool input_signed = instr.conversion.is_input_signed;
85 85
86 if (instr.conversion.src_size == Register::Size::Byte) { 86 if (const u32 offset = static_cast<u32>(instr.conversion.int_src.selector); offset > 0) {
87 const u32 offset = static_cast<u32>(instr.conversion.int_src.selector) * 8; 87 ASSERT(instr.conversion.src_size == Register::Size::Byte ||
88 if (offset > 0) { 88 instr.conversion.src_size == Register::Size::Short);
89 value = SignedOperation(OperationCode::ILogicalShiftRight, input_signed, 89 if (instr.conversion.src_size == Register::Size::Short) {
90 std::move(value), Immediate(offset)); 90 ASSERT(offset == 0 || offset == 2);
91 } 91 }
92 } else { 92 value = SignedOperation(OperationCode::ILogicalShiftRight, input_signed,
93 UNIMPLEMENTED_IF(instr.conversion.int_src.selector != 0); 93 std::move(value), Immediate(offset * 8));
94 } 94 }
95 95
96 value = ConvertIntegerSize(value, instr.conversion.src_size, input_signed); 96 value = ConvertIntegerSize(value, instr.conversion.src_size, input_signed);
diff --git a/src/video_core/shader/decode/memory.cpp b/src/video_core/shader/decode/memory.cpp
index 7591a715f..b5fbc4d58 100644
--- a/src/video_core/shader/decode/memory.cpp
+++ b/src/video_core/shader/decode/memory.cpp
@@ -19,9 +19,12 @@ namespace VideoCommon::Shader {
19using Tegra::Shader::AtomicOp; 19using Tegra::Shader::AtomicOp;
20using Tegra::Shader::AtomicType; 20using Tegra::Shader::AtomicType;
21using Tegra::Shader::Attribute; 21using Tegra::Shader::Attribute;
22using Tegra::Shader::GlobalAtomicOp;
23using Tegra::Shader::GlobalAtomicType;
22using Tegra::Shader::Instruction; 24using Tegra::Shader::Instruction;
23using Tegra::Shader::OpCode; 25using Tegra::Shader::OpCode;
24using Tegra::Shader::Register; 26using Tegra::Shader::Register;
27using Tegra::Shader::StoreType;
25 28
26namespace { 29namespace {
27 30
@@ -61,6 +64,27 @@ u32 GetMemorySize(Tegra::Shader::UniformType uniform_type) {
61 } 64 }
62} 65}
63 66
67Node ExtractUnaligned(Node value, Node address, u32 mask, u32 size) {
68 Node offset = Operation(OperationCode::UBitwiseAnd, address, Immediate(mask));
69 offset = Operation(OperationCode::ULogicalShiftLeft, std::move(offset), Immediate(3));
70 return Operation(OperationCode::UBitfieldExtract, std::move(value), std::move(offset),
71 Immediate(size));
72}
73
74Node InsertUnaligned(Node dest, Node value, Node address, u32 mask, u32 size) {
75 Node offset = Operation(OperationCode::UBitwiseAnd, std::move(address), Immediate(mask));
76 offset = Operation(OperationCode::ULogicalShiftLeft, std::move(offset), Immediate(3));
77 return Operation(OperationCode::UBitfieldInsert, std::move(dest), std::move(value),
78 std::move(offset), Immediate(size));
79}
80
81Node Sign16Extend(Node value) {
82 Node sign = Operation(OperationCode::UBitwiseAnd, value, Immediate(1U << 15));
83 Node is_sign = Operation(OperationCode::LogicalUEqual, std::move(sign), Immediate(1U << 15));
84 Node extend = Operation(OperationCode::Select, is_sign, Immediate(0xFFFF0000), Immediate(0));
85 return Operation(OperationCode::UBitwiseOr, std::move(value), std::move(extend));
86}
87
64} // Anonymous namespace 88} // Anonymous namespace
65 89
66u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) { 90u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
@@ -136,26 +160,31 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
136 LOG_DEBUG(HW_GPU, "LD_L cache management mode: {}", static_cast<u64>(instr.ld_l.unknown)); 160 LOG_DEBUG(HW_GPU, "LD_L cache management mode: {}", static_cast<u64>(instr.ld_l.unknown));
137 [[fallthrough]]; 161 [[fallthrough]];
138 case OpCode::Id::LD_S: { 162 case OpCode::Id::LD_S: {
139 const auto GetMemory = [&](s32 offset) { 163 const auto GetAddress = [&](s32 offset) {
140 ASSERT(offset % 4 == 0); 164 ASSERT(offset % 4 == 0);
141 const Node immediate_offset = Immediate(static_cast<s32>(instr.smem_imm) + offset); 165 const Node immediate_offset = Immediate(static_cast<s32>(instr.smem_imm) + offset);
142 const Node address = Operation(OperationCode::IAdd, NO_PRECISE, GetRegister(instr.gpr8), 166 return Operation(OperationCode::IAdd, GetRegister(instr.gpr8), immediate_offset);
143 immediate_offset); 167 };
144 return opcode->get().GetId() == OpCode::Id::LD_S ? GetSharedMemory(address) 168 const auto GetMemory = [&](s32 offset) {
145 : GetLocalMemory(address); 169 return opcode->get().GetId() == OpCode::Id::LD_S ? GetSharedMemory(GetAddress(offset))
170 : GetLocalMemory(GetAddress(offset));
146 }; 171 };
147 172
148 switch (instr.ldst_sl.type.Value()) { 173 switch (instr.ldst_sl.type.Value()) {
149 case Tegra::Shader::StoreType::Bits32: 174 case StoreType::Signed16:
150 case Tegra::Shader::StoreType::Bits64: 175 SetRegister(bb, instr.gpr0,
151 case Tegra::Shader::StoreType::Bits128: { 176 Sign16Extend(ExtractUnaligned(GetMemory(0), GetAddress(0), 0b10, 16)));
152 const u32 count = [&]() { 177 break;
178 case StoreType::Bits32:
179 case StoreType::Bits64:
180 case StoreType::Bits128: {
181 const u32 count = [&] {
153 switch (instr.ldst_sl.type.Value()) { 182 switch (instr.ldst_sl.type.Value()) {
154 case Tegra::Shader::StoreType::Bits32: 183 case StoreType::Bits32:
155 return 1; 184 return 1;
156 case Tegra::Shader::StoreType::Bits64: 185 case StoreType::Bits64:
157 return 2; 186 return 2;
158 case Tegra::Shader::StoreType::Bits128: 187 case StoreType::Bits128:
159 return 4; 188 return 4;
160 default: 189 default:
161 UNREACHABLE(); 190 UNREACHABLE();
@@ -212,12 +241,7 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
212 // To handle unaligned loads get the bytes used to dereference global memory and extract 241 // To handle unaligned loads get the bytes used to dereference global memory and extract
213 // those bytes from the loaded u32. 242 // those bytes from the loaded u32.
214 if (IsUnaligned(type)) { 243 if (IsUnaligned(type)) {
215 Node mask = Immediate(GetUnalignedMask(type)); 244 gmem = ExtractUnaligned(gmem, real_address, GetUnalignedMask(type), size);
216 Node offset = Operation(OperationCode::UBitwiseAnd, real_address, std::move(mask));
217 offset = Operation(OperationCode::ULogicalShiftLeft, offset, Immediate(3));
218
219 gmem = Operation(OperationCode::UBitfieldExtract, std::move(gmem),
220 std::move(offset), Immediate(size));
221 } 245 }
222 246
223 SetTemporary(bb, i, gmem); 247 SetTemporary(bb, i, gmem);
@@ -269,21 +293,28 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
269 return Operation(OperationCode::IAdd, NO_PRECISE, GetRegister(instr.gpr8), immediate); 293 return Operation(OperationCode::IAdd, NO_PRECISE, GetRegister(instr.gpr8), immediate);
270 }; 294 };
271 295
272 const auto set_memory = opcode->get().GetId() == OpCode::Id::ST_L 296 const bool is_local = opcode->get().GetId() == OpCode::Id::ST_L;
273 ? &ShaderIR::SetLocalMemory 297 const auto set_memory = is_local ? &ShaderIR::SetLocalMemory : &ShaderIR::SetSharedMemory;
274 : &ShaderIR::SetSharedMemory; 298 const auto get_memory = is_local ? &ShaderIR::GetLocalMemory : &ShaderIR::GetSharedMemory;
275 299
276 switch (instr.ldst_sl.type.Value()) { 300 switch (instr.ldst_sl.type.Value()) {
277 case Tegra::Shader::StoreType::Bits128: 301 case StoreType::Bits128:
278 (this->*set_memory)(bb, GetAddress(12), GetRegister(instr.gpr0.Value() + 3)); 302 (this->*set_memory)(bb, GetAddress(12), GetRegister(instr.gpr0.Value() + 3));
279 (this->*set_memory)(bb, GetAddress(8), GetRegister(instr.gpr0.Value() + 2)); 303 (this->*set_memory)(bb, GetAddress(8), GetRegister(instr.gpr0.Value() + 2));
280 [[fallthrough]]; 304 [[fallthrough]];
281 case Tegra::Shader::StoreType::Bits64: 305 case StoreType::Bits64:
282 (this->*set_memory)(bb, GetAddress(4), GetRegister(instr.gpr0.Value() + 1)); 306 (this->*set_memory)(bb, GetAddress(4), GetRegister(instr.gpr0.Value() + 1));
283 [[fallthrough]]; 307 [[fallthrough]];
284 case Tegra::Shader::StoreType::Bits32: 308 case StoreType::Bits32:
285 (this->*set_memory)(bb, GetAddress(0), GetRegister(instr.gpr0)); 309 (this->*set_memory)(bb, GetAddress(0), GetRegister(instr.gpr0));
286 break; 310 break;
311 case StoreType::Signed16: {
312 Node address = GetAddress(0);
313 Node memory = (this->*get_memory)(address);
314 (this->*set_memory)(
315 bb, address, InsertUnaligned(memory, GetRegister(instr.gpr0), address, 0b10, 16));
316 break;
317 }
287 default: 318 default:
288 UNIMPLEMENTED_MSG("{} unhandled type: {}", opcode->get().GetName(), 319 UNIMPLEMENTED_MSG("{} unhandled type: {}", opcode->get().GetName(),
289 static_cast<u32>(instr.ldst_sl.type.Value())); 320 static_cast<u32>(instr.ldst_sl.type.Value()));
@@ -323,18 +354,32 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
323 Node value = GetRegister(instr.gpr0.Value() + i); 354 Node value = GetRegister(instr.gpr0.Value() + i);
324 355
325 if (IsUnaligned(type)) { 356 if (IsUnaligned(type)) {
326 Node mask = Immediate(GetUnalignedMask(type)); 357 const u32 mask = GetUnalignedMask(type);
327 Node offset = Operation(OperationCode::UBitwiseAnd, real_address, std::move(mask)); 358 value = InsertUnaligned(gmem, std::move(value), real_address, mask, size);
328 offset = Operation(OperationCode::ULogicalShiftLeft, offset, Immediate(3));
329
330 value = Operation(OperationCode::UBitfieldInsert, gmem, std::move(value), offset,
331 Immediate(size));
332 } 359 }
333 360
334 bb.push_back(Operation(OperationCode::Assign, gmem, value)); 361 bb.push_back(Operation(OperationCode::Assign, gmem, value));
335 } 362 }
336 break; 363 break;
337 } 364 }
365 case OpCode::Id::ATOM: {
366 UNIMPLEMENTED_IF_MSG(instr.atom.operation != GlobalAtomicOp::Add, "operation={}",
367 static_cast<int>(instr.atom.operation.Value()));
368 UNIMPLEMENTED_IF_MSG(instr.atom.type != GlobalAtomicType::S32, "type={}",
369 static_cast<int>(instr.atom.type.Value()));
370
371 const auto [real_address, base_address, descriptor] =
372 TrackGlobalMemory(bb, instr, true, true);
373 if (!real_address || !base_address) {
374 // Tracking failed, skip atomic.
375 break;
376 }
377
378 Node gmem = MakeNode<GmemNode>(real_address, base_address, descriptor);
379 Node value = Operation(OperationCode::AtomicAdd, std::move(gmem), GetRegister(instr.gpr20));
380 SetRegister(bb, instr.gpr0, std::move(value));
381 break;
382 }
338 case OpCode::Id::ATOMS: { 383 case OpCode::Id::ATOMS: {
339 UNIMPLEMENTED_IF_MSG(instr.atoms.operation != AtomicOp::Add, "operation={}", 384 UNIMPLEMENTED_IF_MSG(instr.atoms.operation != AtomicOp::Add, "operation={}",
340 static_cast<int>(instr.atoms.operation.Value())); 385 static_cast<int>(instr.atoms.operation.Value()));
@@ -348,7 +393,7 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
348 Node memory = GetSharedMemory(std::move(address)); 393 Node memory = GetSharedMemory(std::move(address));
349 Node data = GetRegister(instr.gpr20); 394 Node data = GetRegister(instr.gpr20);
350 395
351 Node value = Operation(OperationCode::UAtomicAdd, std::move(memory), std::move(data)); 396 Node value = Operation(OperationCode::AtomicAdd, std::move(memory), std::move(data));
352 SetRegister(bb, instr.gpr0, std::move(value)); 397 SetRegister(bb, instr.gpr0, std::move(value));
353 break; 398 break;
354 } 399 }
diff --git a/src/video_core/shader/decode/other.cpp b/src/video_core/shader/decode/other.cpp
index 7321698b2..4944e9d69 100644
--- a/src/video_core/shader/decode/other.cpp
+++ b/src/video_core/shader/decode/other.cpp
@@ -69,13 +69,16 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) {
69 case OpCode::Id::MOV_SYS: { 69 case OpCode::Id::MOV_SYS: {
70 const Node value = [this, instr] { 70 const Node value = [this, instr] {
71 switch (instr.sys20) { 71 switch (instr.sys20) {
72 case SystemVariable::LaneId:
73 LOG_WARNING(HW_GPU, "MOV_SYS instruction with LaneId is incomplete");
74 return Immediate(0U);
72 case SystemVariable::InvocationId: 75 case SystemVariable::InvocationId:
73 return Operation(OperationCode::InvocationId); 76 return Operation(OperationCode::InvocationId);
74 case SystemVariable::Ydirection: 77 case SystemVariable::Ydirection:
75 return Operation(OperationCode::YNegate); 78 return Operation(OperationCode::YNegate);
76 case SystemVariable::InvocationInfo: 79 case SystemVariable::InvocationInfo:
77 LOG_WARNING(HW_GPU, "MOV_SYS instruction with InvocationInfo is incomplete"); 80 LOG_WARNING(HW_GPU, "MOV_SYS instruction with InvocationInfo is incomplete");
78 return Immediate(0u); 81 return Immediate(0U);
79 case SystemVariable::Tid: { 82 case SystemVariable::Tid: {
80 Node value = Immediate(0); 83 Node value = Immediate(0);
81 value = BitfieldInsert(value, Operation(OperationCode::LocalInvocationIdX), 0, 9); 84 value = BitfieldInsert(value, Operation(OperationCode::LocalInvocationIdX), 0, 9);
@@ -188,7 +191,7 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) {
188 UNIMPLEMENTED_IF_MSG(cc != Tegra::Shader::ConditionCode::T, "SYNC condition code used: {}", 191 UNIMPLEMENTED_IF_MSG(cc != Tegra::Shader::ConditionCode::T, "SYNC condition code used: {}",
189 static_cast<u32>(cc)); 192 static_cast<u32>(cc));
190 193
191 if (disable_flow_stack) { 194 if (decompiled) {
192 break; 195 break;
193 } 196 }
194 197
@@ -200,7 +203,7 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) {
200 const Tegra::Shader::ConditionCode cc = instr.flow_condition_code; 203 const Tegra::Shader::ConditionCode cc = instr.flow_condition_code;
201 UNIMPLEMENTED_IF_MSG(cc != Tegra::Shader::ConditionCode::T, "BRK condition code used: {}", 204 UNIMPLEMENTED_IF_MSG(cc != Tegra::Shader::ConditionCode::T, "BRK condition code used: {}",
202 static_cast<u32>(cc)); 205 static_cast<u32>(cc));
203 if (disable_flow_stack) { 206 if (decompiled) {
204 break; 207 break;
205 } 208 }
206 209
diff --git a/src/video_core/shader/decode/shift.cpp b/src/video_core/shader/decode/shift.cpp
index d419e9c45..3b391d3e6 100644
--- a/src/video_core/shader/decode/shift.cpp
+++ b/src/video_core/shader/decode/shift.cpp
@@ -10,8 +10,80 @@
10 10
11namespace VideoCommon::Shader { 11namespace VideoCommon::Shader {
12 12
13using std::move;
13using Tegra::Shader::Instruction; 14using Tegra::Shader::Instruction;
14using Tegra::Shader::OpCode; 15using Tegra::Shader::OpCode;
16using Tegra::Shader::ShfType;
17using Tegra::Shader::ShfXmode;
18
19namespace {
20
21Node IsFull(Node shift) {
22 return Operation(OperationCode::LogicalIEqual, move(shift), Immediate(32));
23}
24
25Node Shift(OperationCode opcode, Node value, Node shift) {
26 Node is_full = Operation(OperationCode::LogicalIEqual, shift, Immediate(32));
27 Node shifted = Operation(opcode, move(value), shift);
28 return Operation(OperationCode::Select, IsFull(move(shift)), Immediate(0), move(shifted));
29}
30
31Node ClampShift(Node shift, s32 size = 32) {
32 shift = Operation(OperationCode::IMax, move(shift), Immediate(0));
33 return Operation(OperationCode::IMin, move(shift), Immediate(size));
34}
35
36Node WrapShift(Node shift, s32 size = 32) {
37 return Operation(OperationCode::UBitwiseAnd, move(shift), Immediate(size - 1));
38}
39
40Node ShiftRight(Node low, Node high, Node shift, Node low_shift, ShfType type) {
41 // These values are used when the shift value is less than 32
42 Node less_low = Shift(OperationCode::ILogicalShiftRight, low, shift);
43 Node less_high = Shift(OperationCode::ILogicalShiftLeft, high, low_shift);
44 Node less = Operation(OperationCode::IBitwiseOr, move(less_high), move(less_low));
45
46 if (type == ShfType::Bits32) {
47 // On 32 bit shifts we are either full (shifting 32) or shifting less than 32 bits
48 return Operation(OperationCode::Select, IsFull(move(shift)), move(high), move(less));
49 }
50
51 // And these when it's larger than or 32
52 const bool is_signed = type == ShfType::S64;
53 const auto opcode = SignedToUnsignedCode(OperationCode::IArithmeticShiftRight, is_signed);
54 Node reduced = Operation(OperationCode::IAdd, shift, Immediate(-32));
55 Node greater = Shift(opcode, high, move(reduced));
56
57 Node is_less = Operation(OperationCode::LogicalILessThan, shift, Immediate(32));
58 Node is_zero = Operation(OperationCode::LogicalIEqual, move(shift), Immediate(0));
59
60 Node value = Operation(OperationCode::Select, move(is_less), move(less), move(greater));
61 return Operation(OperationCode::Select, move(is_zero), move(high), move(value));
62}
63
64Node ShiftLeft(Node low, Node high, Node shift, Node low_shift, ShfType type) {
65 // These values are used when the shift value is less than 32
66 Node less_low = Operation(OperationCode::ILogicalShiftRight, low, low_shift);
67 Node less_high = Operation(OperationCode::ILogicalShiftLeft, high, shift);
68 Node less = Operation(OperationCode::IBitwiseOr, move(less_low), move(less_high));
69
70 if (type == ShfType::Bits32) {
71 // On 32 bit shifts we are either full (shifting 32) or shifting less than 32 bits
72 return Operation(OperationCode::Select, IsFull(move(shift)), move(low), move(less));
73 }
74
75 // And these when it's larger than or 32
76 Node reduced = Operation(OperationCode::IAdd, shift, Immediate(-32));
77 Node greater = Shift(OperationCode::ILogicalShiftLeft, move(low), move(reduced));
78
79 Node is_less = Operation(OperationCode::LogicalILessThan, shift, Immediate(32));
80 Node is_zero = Operation(OperationCode::LogicalIEqual, move(shift), Immediate(0));
81
82 Node value = Operation(OperationCode::Select, move(is_less), move(less), move(greater));
83 return Operation(OperationCode::Select, move(is_zero), move(high), move(value));
84}
85
86} // Anonymous namespace
15 87
16u32 ShaderIR::DecodeShift(NodeBlock& bb, u32 pc) { 88u32 ShaderIR::DecodeShift(NodeBlock& bb, u32 pc) {
17 const Instruction instr = {program_code[pc]}; 89 const Instruction instr = {program_code[pc]};
@@ -28,29 +100,48 @@ u32 ShaderIR::DecodeShift(NodeBlock& bb, u32 pc) {
28 } 100 }
29 }(); 101 }();
30 102
31 switch (opcode->get().GetId()) { 103 switch (const auto opid = opcode->get().GetId(); opid) {
32 case OpCode::Id::SHR_C: 104 case OpCode::Id::SHR_C:
33 case OpCode::Id::SHR_R: 105 case OpCode::Id::SHR_R:
34 case OpCode::Id::SHR_IMM: { 106 case OpCode::Id::SHR_IMM: {
35 if (instr.shr.wrap) { 107 op_b = instr.shr.wrap ? WrapShift(move(op_b)) : ClampShift(move(op_b));
36 op_b = Operation(OperationCode::UBitwiseAnd, std::move(op_b), Immediate(0x1f));
37 } else {
38 op_b = Operation(OperationCode::IMax, std::move(op_b), Immediate(0));
39 op_b = Operation(OperationCode::IMin, std::move(op_b), Immediate(31));
40 }
41 108
42 Node value = SignedOperation(OperationCode::IArithmeticShiftRight, instr.shift.is_signed, 109 Node value = SignedOperation(OperationCode::IArithmeticShiftRight, instr.shift.is_signed,
43 std::move(op_a), std::move(op_b)); 110 move(op_a), move(op_b));
44 SetInternalFlagsFromInteger(bb, value, instr.generates_cc); 111 SetInternalFlagsFromInteger(bb, value, instr.generates_cc);
45 SetRegister(bb, instr.gpr0, std::move(value)); 112 SetRegister(bb, instr.gpr0, move(value));
46 break; 113 break;
47 } 114 }
48 case OpCode::Id::SHL_C: 115 case OpCode::Id::SHL_C:
49 case OpCode::Id::SHL_R: 116 case OpCode::Id::SHL_R:
50 case OpCode::Id::SHL_IMM: { 117 case OpCode::Id::SHL_IMM: {
51 const Node value = Operation(OperationCode::ILogicalShiftLeft, op_a, op_b); 118 Node value = Operation(OperationCode::ILogicalShiftLeft, op_a, op_b);
52 SetInternalFlagsFromInteger(bb, value, instr.generates_cc); 119 SetInternalFlagsFromInteger(bb, value, instr.generates_cc);
53 SetRegister(bb, instr.gpr0, value); 120 SetRegister(bb, instr.gpr0, move(value));
121 break;
122 }
123 case OpCode::Id::SHF_RIGHT_R:
124 case OpCode::Id::SHF_RIGHT_IMM:
125 case OpCode::Id::SHF_LEFT_R:
126 case OpCode::Id::SHF_LEFT_IMM: {
127 UNIMPLEMENTED_IF(instr.generates_cc);
128 UNIMPLEMENTED_IF_MSG(instr.shf.xmode != ShfXmode::None, "xmode={}",
129 static_cast<int>(instr.shf.xmode.Value()));
130
131 if (instr.is_b_imm) {
132 op_b = Immediate(static_cast<u32>(instr.shf.immediate));
133 }
134 const s32 size = instr.shf.type == ShfType::Bits32 ? 32 : 64;
135 Node shift = instr.shf.wrap ? WrapShift(move(op_b), size) : ClampShift(move(op_b), size);
136
137 Node negated_shift = Operation(OperationCode::INegate, shift);
138 Node low_shift = Operation(OperationCode::IAdd, move(negated_shift), Immediate(32));
139
140 const bool is_right = opid == OpCode::Id::SHF_RIGHT_R || opid == OpCode::Id::SHF_RIGHT_IMM;
141 Node value = (is_right ? ShiftRight : ShiftLeft)(
142 move(op_a), GetRegister(instr.gpr39), move(shift), move(low_shift), instr.shf.type);
143
144 SetRegister(bb, instr.gpr0, move(value));
54 break; 145 break;
55 } 146 }
56 default: 147 default:
diff --git a/src/video_core/shader/decode/texture.cpp b/src/video_core/shader/decode/texture.cpp
index cd984f763..bee7d8cad 100644
--- a/src/video_core/shader/decode/texture.cpp
+++ b/src/video_core/shader/decode/texture.cpp
@@ -144,7 +144,8 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
144 Node4 values; 144 Node4 values;
145 for (u32 element = 0; element < values.size(); ++element) { 145 for (u32 element = 0; element < values.size(); ++element) {
146 auto coords_copy = coords; 146 auto coords_copy = coords;
147 MetaTexture meta{sampler, {}, depth_compare, aoffi, {}, {}, {}, {}, component, element}; 147 MetaTexture meta{sampler, {}, depth_compare, aoffi, {}, {},
148 {}, {}, component, element, {}};
148 values[element] = Operation(OperationCode::TextureGather, meta, std::move(coords_copy)); 149 values[element] = Operation(OperationCode::TextureGather, meta, std::move(coords_copy));
149 } 150 }
150 151
@@ -161,16 +162,16 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
161 case OpCode::Id::TXD: { 162 case OpCode::Id::TXD: {
162 UNIMPLEMENTED_IF_MSG(instr.txd.UsesMiscMode(TextureMiscMode::AOFFI), 163 UNIMPLEMENTED_IF_MSG(instr.txd.UsesMiscMode(TextureMiscMode::AOFFI),
163 "AOFFI is not implemented"); 164 "AOFFI is not implemented");
164 UNIMPLEMENTED_IF_MSG(instr.txd.is_array != 0, "TXD Array is not implemented");
165 165
166 const bool is_array = instr.txd.is_array != 0;
166 u64 base_reg = instr.gpr8.Value(); 167 u64 base_reg = instr.gpr8.Value();
167 const auto derivate_reg = instr.gpr20.Value(); 168 const auto derivate_reg = instr.gpr20.Value();
168 const auto texture_type = instr.txd.texture_type.Value(); 169 const auto texture_type = instr.txd.texture_type.Value();
169 const auto coord_count = GetCoordCount(texture_type); 170 const auto coord_count = GetCoordCount(texture_type);
170 171 Node index_var{};
171 const Sampler* sampler = is_bindless 172 const Sampler* sampler =
172 ? GetBindlessSampler(base_reg, {{texture_type, false, false}}) 173 is_bindless ? GetBindlessSampler(base_reg, index_var, {{texture_type, is_array, false}})
173 : GetSampler(instr.sampler, {{texture_type, false, false}}); 174 : GetSampler(instr.sampler, {{texture_type, is_array, false}});
174 Node4 values; 175 Node4 values;
175 if (sampler == nullptr) { 176 if (sampler == nullptr) {
176 for (u32 element = 0; element < values.size(); ++element) { 177 for (u32 element = 0; element < values.size(); ++element) {
@@ -179,6 +180,7 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
179 WriteTexInstructionFloat(bb, instr, values); 180 WriteTexInstructionFloat(bb, instr, values);
180 break; 181 break;
181 } 182 }
183
182 if (is_bindless) { 184 if (is_bindless) {
183 base_reg++; 185 base_reg++;
184 } 186 }
@@ -192,8 +194,15 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
192 derivates.push_back(GetRegister(derivate_reg + derivate + 1)); 194 derivates.push_back(GetRegister(derivate_reg + derivate + 1));
193 } 195 }
194 196
197 Node array_node = {};
198 if (is_array) {
199 const Node info_reg = GetRegister(base_reg + coord_count);
200 array_node = BitfieldExtract(info_reg, 0, 16);
201 }
202
195 for (u32 element = 0; element < values.size(); ++element) { 203 for (u32 element = 0; element < values.size(); ++element) {
196 MetaTexture meta{*sampler, {}, {}, {}, {}, derivates, {}, {}, {}, element}; 204 MetaTexture meta{*sampler, array_node, {}, {}, {}, derivates,
205 {}, {}, {}, element, index_var};
197 values[element] = Operation(OperationCode::TextureGradient, std::move(meta), coords); 206 values[element] = Operation(OperationCode::TextureGradient, std::move(meta), coords);
198 } 207 }
199 208
@@ -208,8 +217,9 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
208 // TODO: The new commits on the texture refactor, change the way samplers work. 217 // TODO: The new commits on the texture refactor, change the way samplers work.
209 // Sadly, not all texture instructions specify the type of texture their sampler 218 // Sadly, not all texture instructions specify the type of texture their sampler
210 // uses. This must be fixed at a later instance. 219 // uses. This must be fixed at a later instance.
220 Node index_var{};
211 const Sampler* sampler = 221 const Sampler* sampler =
212 is_bindless ? GetBindlessSampler(instr.gpr8) : GetSampler(instr.sampler); 222 is_bindless ? GetBindlessSampler(instr.gpr8, index_var) : GetSampler(instr.sampler);
213 223
214 if (sampler == nullptr) { 224 if (sampler == nullptr) {
215 u32 indexer = 0; 225 u32 indexer = 0;
@@ -233,7 +243,7 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
233 if (!instr.txq.IsComponentEnabled(element)) { 243 if (!instr.txq.IsComponentEnabled(element)) {
234 continue; 244 continue;
235 } 245 }
236 MetaTexture meta{*sampler, {}, {}, {}, {}, {}, {}, {}, {}, element}; 246 MetaTexture meta{*sampler, {}, {}, {}, {}, {}, {}, {}, {}, element, index_var};
237 const Node value = 247 const Node value =
238 Operation(OperationCode::TextureQueryDimensions, meta, 248 Operation(OperationCode::TextureQueryDimensions, meta,
239 GetRegister(instr.gpr8.Value() + (is_bindless ? 1 : 0))); 249 GetRegister(instr.gpr8.Value() + (is_bindless ? 1 : 0)));
@@ -259,8 +269,9 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
259 269
260 auto texture_type = instr.tmml.texture_type.Value(); 270 auto texture_type = instr.tmml.texture_type.Value();
261 const bool is_array = instr.tmml.array != 0; 271 const bool is_array = instr.tmml.array != 0;
272 Node index_var{};
262 const Sampler* sampler = 273 const Sampler* sampler =
263 is_bindless ? GetBindlessSampler(instr.gpr20) : GetSampler(instr.sampler); 274 is_bindless ? GetBindlessSampler(instr.gpr20, index_var) : GetSampler(instr.sampler);
264 275
265 if (sampler == nullptr) { 276 if (sampler == nullptr) {
266 u32 indexer = 0; 277 u32 indexer = 0;
@@ -302,7 +313,7 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
302 continue; 313 continue;
303 } 314 }
304 auto params = coords; 315 auto params = coords;
305 MetaTexture meta{*sampler, {}, {}, {}, {}, {}, {}, {}, {}, element}; 316 MetaTexture meta{*sampler, {}, {}, {}, {}, {}, {}, {}, {}, element, index_var};
306 const Node value = Operation(OperationCode::TextureQueryLod, meta, std::move(params)); 317 const Node value = Operation(OperationCode::TextureQueryLod, meta, std::move(params));
307 SetTemporary(bb, indexer++, value); 318 SetTemporary(bb, indexer++, value);
308 } 319 }
@@ -376,37 +387,65 @@ const Sampler* ShaderIR::GetSampler(const Tegra::Shader::Sampler& sampler,
376 // Otherwise create a new mapping for this sampler 387 // Otherwise create a new mapping for this sampler
377 const auto next_index = static_cast<u32>(used_samplers.size()); 388 const auto next_index = static_cast<u32>(used_samplers.size());
378 return &used_samplers.emplace_back(next_index, offset, info.type, info.is_array, info.is_shadow, 389 return &used_samplers.emplace_back(next_index, offset, info.type, info.is_array, info.is_shadow,
379 info.is_buffer); 390 info.is_buffer, false);
380} 391}
381 392
382const Sampler* ShaderIR::GetBindlessSampler(Tegra::Shader::Register reg, 393const Sampler* ShaderIR::GetBindlessSampler(Tegra::Shader::Register reg, Node& index_var,
383 std::optional<SamplerInfo> sampler_info) { 394 std::optional<SamplerInfo> sampler_info) {
384 const Node sampler_register = GetRegister(reg); 395 const Node sampler_register = GetRegister(reg);
385 const auto [base_sampler, buffer, offset] = 396 const auto [base_node, tracked_sampler_info] =
386 TrackCbuf(sampler_register, global_code, static_cast<s64>(global_code.size())); 397 TrackBindlessSampler(sampler_register, global_code, static_cast<s64>(global_code.size()));
387 ASSERT(base_sampler != nullptr); 398 ASSERT(base_node != nullptr);
388 if (base_sampler == nullptr) { 399 if (base_node == nullptr) {
389 return nullptr; 400 return nullptr;
390 } 401 }
391 402
392 const auto info = GetSamplerInfo(sampler_info, offset, buffer); 403 if (const auto bindless_sampler_info =
404 std::get_if<BindlessSamplerNode>(&*tracked_sampler_info)) {
405 const u32 buffer = bindless_sampler_info->GetIndex();
406 const u32 offset = bindless_sampler_info->GetOffset();
407 const auto info = GetSamplerInfo(sampler_info, offset, buffer);
408
409 // If this sampler has already been used, return the existing mapping.
410 const auto it =
411 std::find_if(used_samplers.begin(), used_samplers.end(),
412 [buffer = buffer, offset = offset](const Sampler& entry) {
413 return entry.GetBuffer() == buffer && entry.GetOffset() == offset;
414 });
415 if (it != used_samplers.end()) {
416 ASSERT(it->IsBindless() && it->GetType() == info.type &&
417 it->IsArray() == info.is_array && it->IsShadow() == info.is_shadow);
418 return &*it;
419 }
393 420
394 // If this sampler has already been used, return the existing mapping. 421 // Otherwise create a new mapping for this sampler
395 const auto it = 422 const auto next_index = static_cast<u32>(used_samplers.size());
396 std::find_if(used_samplers.begin(), used_samplers.end(), 423 return &used_samplers.emplace_back(next_index, offset, buffer, info.type, info.is_array,
397 [buffer = buffer, offset = offset](const Sampler& entry) { 424 info.is_shadow, info.is_buffer, false);
398 return entry.GetBuffer() == buffer && entry.GetOffset() == offset; 425 } else if (const auto array_sampler_info =
399 }); 426 std::get_if<ArraySamplerNode>(&*tracked_sampler_info)) {
400 if (it != used_samplers.end()) { 427 const u32 base_offset = array_sampler_info->GetBaseOffset() / 4;
401 ASSERT(it->IsBindless() && it->GetType() == info.type && it->IsArray() == info.is_array && 428 index_var = GetCustomVariable(array_sampler_info->GetIndexVar());
402 it->IsShadow() == info.is_shadow); 429 const auto info = GetSamplerInfo(sampler_info, base_offset);
403 return &*it; 430
404 } 431 // If this sampler has already been used, return the existing mapping.
432 const auto it = std::find_if(
433 used_samplers.begin(), used_samplers.end(),
434 [base_offset](const Sampler& entry) { return entry.GetOffset() == base_offset; });
435 if (it != used_samplers.end()) {
436 ASSERT(!it->IsBindless() && it->GetType() == info.type &&
437 it->IsArray() == info.is_array && it->IsShadow() == info.is_shadow &&
438 it->IsBuffer() == info.is_buffer && it->IsIndexed());
439 return &*it;
440 }
405 441
406 // Otherwise create a new mapping for this sampler 442 uses_indexed_samplers = true;
407 const auto next_index = static_cast<u32>(used_samplers.size()); 443 // Otherwise create a new mapping for this sampler
408 return &used_samplers.emplace_back(next_index, offset, buffer, info.type, info.is_array, 444 const auto next_index = static_cast<u32>(used_samplers.size());
409 info.is_shadow, info.is_buffer); 445 return &used_samplers.emplace_back(next_index, base_offset, info.type, info.is_array,
446 info.is_shadow, info.is_buffer, true);
447 }
448 return nullptr;
410} 449}
411 450
412void ShaderIR::WriteTexInstructionFloat(NodeBlock& bb, Instruction instr, const Node4& components) { 451void ShaderIR::WriteTexInstructionFloat(NodeBlock& bb, Instruction instr, const Node4& components) {
@@ -483,66 +522,53 @@ Node4 ShaderIR::GetTextureCode(Instruction instr, TextureType texture_type,
483 Node array, Node depth_compare, u32 bias_offset, 522 Node array, Node depth_compare, u32 bias_offset,
484 std::vector<Node> aoffi, 523 std::vector<Node> aoffi,
485 std::optional<Tegra::Shader::Register> bindless_reg) { 524 std::optional<Tegra::Shader::Register> bindless_reg) {
486 const auto is_array = static_cast<bool>(array); 525 const bool is_array = array != nullptr;
487 const auto is_shadow = static_cast<bool>(depth_compare); 526 const bool is_shadow = depth_compare != nullptr;
488 const bool is_bindless = bindless_reg.has_value(); 527 const bool is_bindless = bindless_reg.has_value();
489 528
490 UNIMPLEMENTED_IF_MSG((texture_type == TextureType::Texture3D && (is_array || is_shadow)) || 529 UNIMPLEMENTED_IF(texture_type == TextureType::TextureCube && is_array && is_shadow);
491 (texture_type == TextureType::TextureCube && is_array && is_shadow), 530 ASSERT_MSG(texture_type != TextureType::Texture3D || !is_array || !is_shadow,
492 "This method is not supported."); 531 "Illegal texture type");
493 532
494 const SamplerInfo info{texture_type, is_array, is_shadow, false}; 533 const SamplerInfo info{texture_type, is_array, is_shadow, false};
495 const Sampler* sampler = 534 Node index_var;
496 is_bindless ? GetBindlessSampler(*bindless_reg, info) : GetSampler(instr.sampler, info); 535 const Sampler* sampler = is_bindless ? GetBindlessSampler(*bindless_reg, index_var, info)
497 Node4 values; 536 : GetSampler(instr.sampler, info);
498 if (sampler == nullptr) { 537 if (!sampler) {
499 for (u32 element = 0; element < values.size(); ++element) { 538 return {Immediate(0), Immediate(0), Immediate(0), Immediate(0)};
500 values[element] = Immediate(0);
501 }
502 return values;
503 } 539 }
504 540
505 const bool lod_needed = process_mode == TextureProcessMode::LZ || 541 const bool lod_needed = process_mode == TextureProcessMode::LZ ||
506 process_mode == TextureProcessMode::LL || 542 process_mode == TextureProcessMode::LL ||
507 process_mode == TextureProcessMode::LLA; 543 process_mode == TextureProcessMode::LLA;
508 544 const OperationCode opcode = lod_needed ? OperationCode::TextureLod : OperationCode::Texture;
509 // LOD selection (either via bias or explicit textureLod) not supported in GL for
510 // sampler2DArrayShadow and samplerCubeArrayShadow.
511 const bool gl_lod_supported =
512 !((texture_type == Tegra::Shader::TextureType::Texture2D && is_array && is_shadow) ||
513 (texture_type == Tegra::Shader::TextureType::TextureCube && is_array && is_shadow));
514
515 const OperationCode read_method =
516 (lod_needed && gl_lod_supported) ? OperationCode::TextureLod : OperationCode::Texture;
517
518 UNIMPLEMENTED_IF(process_mode != TextureProcessMode::None && !gl_lod_supported);
519 545
520 Node bias; 546 Node bias;
521 Node lod; 547 Node lod;
522 if (process_mode != TextureProcessMode::None && gl_lod_supported) { 548 switch (process_mode) {
523 switch (process_mode) { 549 case TextureProcessMode::None:
524 case TextureProcessMode::LZ: 550 break;
525 lod = Immediate(0.0f); 551 case TextureProcessMode::LZ:
526 break; 552 lod = Immediate(0.0f);
527 case TextureProcessMode::LB: 553 break;
528 // If present, lod or bias are always stored in the register 554 case TextureProcessMode::LB:
529 // indexed by the gpr20 field with an offset depending on the 555 // If present, lod or bias are always stored in the register indexed by the gpr20 field with
530 // usage of the other registers 556 // an offset depending on the usage of the other registers.
531 bias = GetRegister(instr.gpr20.Value() + bias_offset); 557 bias = GetRegister(instr.gpr20.Value() + bias_offset);
532 break; 558 break;
533 case TextureProcessMode::LL: 559 case TextureProcessMode::LL:
534 lod = GetRegister(instr.gpr20.Value() + bias_offset); 560 lod = GetRegister(instr.gpr20.Value() + bias_offset);
535 break; 561 break;
536 default: 562 default:
537 UNIMPLEMENTED_MSG("Unimplemented process mode={}", static_cast<u32>(process_mode)); 563 UNIMPLEMENTED_MSG("Unimplemented process mode={}", static_cast<u32>(process_mode));
538 break; 564 break;
539 }
540 } 565 }
541 566
567 Node4 values;
542 for (u32 element = 0; element < values.size(); ++element) { 568 for (u32 element = 0; element < values.size(); ++element) {
543 auto copy_coords = coords; 569 MetaTexture meta{*sampler, array, depth_compare, aoffi, {}, {}, bias,
544 MetaTexture meta{*sampler, array, depth_compare, aoffi, {}, {}, bias, lod, {}, element}; 570 lod, {}, element, index_var};
545 values[element] = Operation(read_method, meta, std::move(copy_coords)); 571 values[element] = Operation(opcode, meta, coords);
546 } 572 }
547 573
548 return values; 574 return values;
@@ -589,7 +615,7 @@ Node4 ShaderIR::GetTexCode(Instruction instr, TextureType texture_type,
589 aoffi = GetAoffiCoordinates(GetRegister(parameter_register++), coord_count, false); 615 aoffi = GetAoffiCoordinates(GetRegister(parameter_register++), coord_count, false);
590 } 616 }
591 617
592 Node dc{}; 618 Node dc;
593 if (depth_compare) { 619 if (depth_compare) {
594 // Depth is always stored in the register signaled by gpr20 or in the next register if lod 620 // Depth is always stored in the register signaled by gpr20 or in the next register if lod
595 // or bias are used 621 // or bias are used
@@ -625,7 +651,7 @@ Node4 ShaderIR::GetTexsCode(Instruction instr, TextureType texture_type,
625 651
626 const Node array = is_array ? GetRegister(array_register) : nullptr; 652 const Node array = is_array ? GetRegister(array_register) : nullptr;
627 653
628 Node dc{}; 654 Node dc;
629 if (depth_compare) { 655 if (depth_compare) {
630 // Depth is always stored in the register signaled by gpr20 or in the next register if lod 656 // Depth is always stored in the register signaled by gpr20 or in the next register if lod
631 // or bias are used 657 // or bias are used
@@ -656,7 +682,8 @@ Node4 ShaderIR::GetTld4Code(Instruction instr, TextureType texture_type, bool de
656 u64 parameter_register = instr.gpr20.Value(); 682 u64 parameter_register = instr.gpr20.Value();
657 683
658 const SamplerInfo info{texture_type, is_array, depth_compare, false}; 684 const SamplerInfo info{texture_type, is_array, depth_compare, false};
659 const Sampler* sampler = is_bindless ? GetBindlessSampler(parameter_register++, info) 685 Node index_var{};
686 const Sampler* sampler = is_bindless ? GetBindlessSampler(parameter_register++, index_var, info)
660 : GetSampler(instr.sampler, info); 687 : GetSampler(instr.sampler, info);
661 Node4 values; 688 Node4 values;
662 if (sampler == nullptr) { 689 if (sampler == nullptr) {
@@ -685,7 +712,8 @@ Node4 ShaderIR::GetTld4Code(Instruction instr, TextureType texture_type, bool de
685 for (u32 element = 0; element < values.size(); ++element) { 712 for (u32 element = 0; element < values.size(); ++element) {
686 auto coords_copy = coords; 713 auto coords_copy = coords;
687 MetaTexture meta{ 714 MetaTexture meta{
688 *sampler, GetRegister(array_register), dc, aoffi, ptp, {}, {}, {}, component, element}; 715 *sampler, GetRegister(array_register), dc, aoffi, ptp, {}, {}, {}, component, element,
716 index_var};
689 values[element] = Operation(OperationCode::TextureGather, meta, std::move(coords_copy)); 717 values[element] = Operation(OperationCode::TextureGather, meta, std::move(coords_copy));
690 } 718 }
691 719
@@ -718,7 +746,7 @@ Node4 ShaderIR::GetTldCode(Tegra::Shader::Instruction instr) {
718 Node4 values; 746 Node4 values;
719 for (u32 element = 0; element < values.size(); ++element) { 747 for (u32 element = 0; element < values.size(); ++element) {
720 auto coords_copy = coords; 748 auto coords_copy = coords;
721 MetaTexture meta{sampler, array_register, {}, {}, {}, {}, {}, lod, {}, element}; 749 MetaTexture meta{sampler, array_register, {}, {}, {}, {}, {}, lod, {}, element, {}};
722 values[element] = Operation(OperationCode::TexelFetch, meta, std::move(coords_copy)); 750 values[element] = Operation(OperationCode::TexelFetch, meta, std::move(coords_copy));
723 } 751 }
724 752
@@ -768,7 +796,7 @@ Node4 ShaderIR::GetTldsCode(Instruction instr, TextureType texture_type, bool is
768 Node4 values; 796 Node4 values;
769 for (u32 element = 0; element < values.size(); ++element) { 797 for (u32 element = 0; element < values.size(); ++element) {
770 auto coords_copy = coords; 798 auto coords_copy = coords;
771 MetaTexture meta{sampler, array, {}, {}, {}, {}, {}, lod, {}, element}; 799 MetaTexture meta{sampler, array, {}, {}, {}, {}, {}, lod, {}, element, {}};
772 values[element] = Operation(OperationCode::TexelFetch, meta, std::move(coords_copy)); 800 values[element] = Operation(OperationCode::TexelFetch, meta, std::move(coords_copy));
773 } 801 }
774 return values; 802 return values;
diff --git a/src/video_core/shader/node.h b/src/video_core/shader/node.h
index 075c7d07c..a0a7b9111 100644
--- a/src/video_core/shader/node.h
+++ b/src/video_core/shader/node.h
@@ -162,7 +162,7 @@ enum class OperationCode {
162 AtomicImageXor, /// (MetaImage, int[N] coords) -> void 162 AtomicImageXor, /// (MetaImage, int[N] coords) -> void
163 AtomicImageExchange, /// (MetaImage, int[N] coords) -> void 163 AtomicImageExchange, /// (MetaImage, int[N] coords) -> void
164 164
165 UAtomicAdd, /// (smem, uint) -> uint 165 AtomicAdd, /// (memory, {u}int) -> {u}int
166 166
167 Branch, /// (uint branch_target) -> void 167 Branch, /// (uint branch_target) -> void
168 BranchIndirect, /// (uint branch_target) -> void 168 BranchIndirect, /// (uint branch_target) -> void
@@ -212,6 +212,7 @@ enum class MetaStackClass {
212class OperationNode; 212class OperationNode;
213class ConditionalNode; 213class ConditionalNode;
214class GprNode; 214class GprNode;
215class CustomVarNode;
215class ImmediateNode; 216class ImmediateNode;
216class InternalFlagNode; 217class InternalFlagNode;
217class PredicateNode; 218class PredicateNode;
@@ -223,26 +224,32 @@ class SmemNode;
223class GmemNode; 224class GmemNode;
224class CommentNode; 225class CommentNode;
225 226
226using NodeData = std::variant<OperationNode, ConditionalNode, GprNode, ImmediateNode, 227using NodeData = std::variant<OperationNode, ConditionalNode, GprNode, CustomVarNode, ImmediateNode,
227 InternalFlagNode, PredicateNode, AbufNode, PatchNode, CbufNode, 228 InternalFlagNode, PredicateNode, AbufNode, PatchNode, CbufNode,
228 LmemNode, SmemNode, GmemNode, CommentNode>; 229 LmemNode, SmemNode, GmemNode, CommentNode>;
229using Node = std::shared_ptr<NodeData>; 230using Node = std::shared_ptr<NodeData>;
230using Node4 = std::array<Node, 4>; 231using Node4 = std::array<Node, 4>;
231using NodeBlock = std::vector<Node>; 232using NodeBlock = std::vector<Node>;
232 233
234class BindlessSamplerNode;
235class ArraySamplerNode;
236
237using TrackSamplerData = std::variant<BindlessSamplerNode, ArraySamplerNode>;
238using TrackSampler = std::shared_ptr<TrackSamplerData>;
239
233class Sampler { 240class Sampler {
234public: 241public:
235 /// This constructor is for bound samplers 242 /// This constructor is for bound samplers
236 constexpr explicit Sampler(u32 index, u32 offset, Tegra::Shader::TextureType type, 243 constexpr explicit Sampler(u32 index, u32 offset, Tegra::Shader::TextureType type,
237 bool is_array, bool is_shadow, bool is_buffer) 244 bool is_array, bool is_shadow, bool is_buffer, bool is_indexed)
238 : index{index}, offset{offset}, type{type}, is_array{is_array}, is_shadow{is_shadow}, 245 : index{index}, offset{offset}, type{type}, is_array{is_array}, is_shadow{is_shadow},
239 is_buffer{is_buffer} {} 246 is_buffer{is_buffer}, is_indexed{is_indexed} {}
240 247
241 /// This constructor is for bindless samplers 248 /// This constructor is for bindless samplers
242 constexpr explicit Sampler(u32 index, u32 offset, u32 buffer, Tegra::Shader::TextureType type, 249 constexpr explicit Sampler(u32 index, u32 offset, u32 buffer, Tegra::Shader::TextureType type,
243 bool is_array, bool is_shadow, bool is_buffer) 250 bool is_array, bool is_shadow, bool is_buffer, bool is_indexed)
244 : index{index}, offset{offset}, buffer{buffer}, type{type}, is_array{is_array}, 251 : index{index}, offset{offset}, buffer{buffer}, type{type}, is_array{is_array},
245 is_shadow{is_shadow}, is_buffer{is_buffer}, is_bindless{true} {} 252 is_shadow{is_shadow}, is_buffer{is_buffer}, is_bindless{true}, is_indexed{is_indexed} {}
246 253
247 constexpr u32 GetIndex() const { 254 constexpr u32 GetIndex() const {
248 return index; 255 return index;
@@ -276,16 +283,72 @@ public:
276 return is_bindless; 283 return is_bindless;
277 } 284 }
278 285
286 constexpr bool IsIndexed() const {
287 return is_indexed;
288 }
289
290 constexpr u32 Size() const {
291 return size;
292 }
293
294 constexpr void SetSize(u32 new_size) {
295 size = new_size;
296 }
297
279private: 298private:
280 u32 index{}; ///< Emulated index given for the this sampler. 299 u32 index{}; ///< Emulated index given for the this sampler.
281 u32 offset{}; ///< Offset in the const buffer from where the sampler is being read. 300 u32 offset{}; ///< Offset in the const buffer from where the sampler is being read.
282 u32 buffer{}; ///< Buffer where the bindless sampler is being read (unused on bound samplers). 301 u32 buffer{}; ///< Buffer where the bindless sampler is being read (unused on bound samplers).
302 u32 size{}; ///< Size of the sampler if indexed.
283 303
284 Tegra::Shader::TextureType type{}; ///< The type used to sample this texture (Texture2D, etc) 304 Tegra::Shader::TextureType type{}; ///< The type used to sample this texture (Texture2D, etc)
285 bool is_array{}; ///< Whether the texture is being sampled as an array texture or not. 305 bool is_array{}; ///< Whether the texture is being sampled as an array texture or not.
286 bool is_shadow{}; ///< Whether the texture is being sampled as a depth texture or not. 306 bool is_shadow{}; ///< Whether the texture is being sampled as a depth texture or not.
287 bool is_buffer{}; ///< Whether the texture is a texture buffer without sampler. 307 bool is_buffer{}; ///< Whether the texture is a texture buffer without sampler.
288 bool is_bindless{}; ///< Whether this sampler belongs to a bindless texture or not. 308 bool is_bindless{}; ///< Whether this sampler belongs to a bindless texture or not.
309 bool is_indexed{}; ///< Whether this sampler is an indexed array of textures.
310};
311
312/// Represents a tracked bindless sampler into a direct const buffer
313class ArraySamplerNode final {
314public:
315 explicit ArraySamplerNode(u32 index, u32 base_offset, u32 bindless_var)
316 : index{index}, base_offset{base_offset}, bindless_var{bindless_var} {}
317
318 constexpr u32 GetIndex() const {
319 return index;
320 }
321
322 constexpr u32 GetBaseOffset() const {
323 return base_offset;
324 }
325
326 constexpr u32 GetIndexVar() const {
327 return bindless_var;
328 }
329
330private:
331 u32 index;
332 u32 base_offset;
333 u32 bindless_var;
334};
335
336/// Represents a tracked bindless sampler into a direct const buffer
337class BindlessSamplerNode final {
338public:
339 explicit BindlessSamplerNode(u32 index, u32 offset) : index{index}, offset{offset} {}
340
341 constexpr u32 GetIndex() const {
342 return index;
343 }
344
345 constexpr u32 GetOffset() const {
346 return offset;
347 }
348
349private:
350 u32 index;
351 u32 offset;
289}; 352};
290 353
291class Image final { 354class Image final {
@@ -380,8 +443,9 @@ struct MetaTexture {
380 std::vector<Node> derivates; 443 std::vector<Node> derivates;
381 Node bias; 444 Node bias;
382 Node lod; 445 Node lod;
383 Node component{}; 446 Node component;
384 u32 element{}; 447 u32 element{};
448 Node index;
385}; 449};
386 450
387struct MetaImage { 451struct MetaImage {
@@ -488,6 +552,19 @@ private:
488 Tegra::Shader::Register index{}; 552 Tegra::Shader::Register index{};
489}; 553};
490 554
555/// A custom variable
556class CustomVarNode final {
557public:
558 explicit constexpr CustomVarNode(u32 index) : index{index} {}
559
560 constexpr u32 GetIndex() const {
561 return index;
562 }
563
564private:
565 u32 index{};
566};
567
491/// A 32-bits value that represents an immediate value 568/// A 32-bits value that represents an immediate value
492class ImmediateNode final { 569class ImmediateNode final {
493public: 570public:
diff --git a/src/video_core/shader/node_helper.h b/src/video_core/shader/node_helper.h
index 0c2aa749b..11231bbea 100644
--- a/src/video_core/shader/node_helper.h
+++ b/src/video_core/shader/node_helper.h
@@ -45,6 +45,12 @@ Node MakeNode(Args&&... args) {
45 return std::make_shared<NodeData>(T(std::forward<Args>(args)...)); 45 return std::make_shared<NodeData>(T(std::forward<Args>(args)...));
46} 46}
47 47
48template <typename T, typename... Args>
49TrackSampler MakeTrackSampler(Args&&... args) {
50 static_assert(std::is_convertible_v<T, TrackSamplerData>);
51 return std::make_shared<TrackSamplerData>(T(std::forward<Args>(args)...));
52}
53
48template <typename... Args> 54template <typename... Args>
49Node Operation(OperationCode code, Args&&... args) { 55Node Operation(OperationCode code, Args&&... args) {
50 if constexpr (sizeof...(args) == 0) { 56 if constexpr (sizeof...(args) == 0) {
diff --git a/src/video_core/shader/shader_ir.cpp b/src/video_core/shader/shader_ir.cpp
index 31eecb3f4..3a5d280a9 100644
--- a/src/video_core/shader/shader_ir.cpp
+++ b/src/video_core/shader/shader_ir.cpp
@@ -27,6 +27,7 @@ ShaderIR::ShaderIR(const ProgramCode& program_code, u32 main_offset, CompilerSet
27 ConstBufferLocker& locker) 27 ConstBufferLocker& locker)
28 : program_code{program_code}, main_offset{main_offset}, settings{settings}, locker{locker} { 28 : program_code{program_code}, main_offset{main_offset}, settings{settings}, locker{locker} {
29 Decode(); 29 Decode();
30 PostDecode();
30} 31}
31 32
32ShaderIR::~ShaderIR() = default; 33ShaderIR::~ShaderIR() = default;
@@ -38,6 +39,10 @@ Node ShaderIR::GetRegister(Register reg) {
38 return MakeNode<GprNode>(reg); 39 return MakeNode<GprNode>(reg);
39} 40}
40 41
42Node ShaderIR::GetCustomVariable(u32 id) {
43 return MakeNode<CustomVarNode>(id);
44}
45
41Node ShaderIR::GetImmediate19(Instruction instr) { 46Node ShaderIR::GetImmediate19(Instruction instr) {
42 return Immediate(instr.alu.GetImm20_19()); 47 return Immediate(instr.alu.GetImm20_19());
43} 48}
@@ -452,4 +457,8 @@ std::size_t ShaderIR::DeclareAmend(Node new_amend) {
452 return id; 457 return id;
453} 458}
454 459
460u32 ShaderIR::NewCustomVariable() {
461 return num_custom_variables++;
462}
463
455} // namespace VideoCommon::Shader 464} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/shader_ir.h b/src/video_core/shader/shader_ir.h
index ba1db4c11..b0851c3be 100644
--- a/src/video_core/shader/shader_ir.h
+++ b/src/video_core/shader/shader_ir.h
@@ -180,6 +180,10 @@ public:
180 return amend_code[index]; 180 return amend_code[index];
181 } 181 }
182 182
183 u32 GetNumCustomVariables() const {
184 return num_custom_variables;
185 }
186
183private: 187private:
184 friend class ASTDecoder; 188 friend class ASTDecoder;
185 189
@@ -191,6 +195,7 @@ private:
191 }; 195 };
192 196
193 void Decode(); 197 void Decode();
198 void PostDecode();
194 199
195 NodeBlock DecodeRange(u32 begin, u32 end); 200 NodeBlock DecodeRange(u32 begin, u32 end);
196 void DecodeRangeInner(NodeBlock& bb, u32 begin, u32 end); 201 void DecodeRangeInner(NodeBlock& bb, u32 begin, u32 end);
@@ -235,6 +240,8 @@ private:
235 240
236 /// Generates a node for a passed register. 241 /// Generates a node for a passed register.
237 Node GetRegister(Tegra::Shader::Register reg); 242 Node GetRegister(Tegra::Shader::Register reg);
243 /// Generates a node for a custom variable
244 Node GetCustomVariable(u32 id);
238 /// Generates a node representing a 19-bit immediate value 245 /// Generates a node representing a 19-bit immediate value
239 Node GetImmediate19(Tegra::Shader::Instruction instr); 246 Node GetImmediate19(Tegra::Shader::Instruction instr);
240 /// Generates a node representing a 32-bit immediate value 247 /// Generates a node representing a 32-bit immediate value
@@ -321,7 +328,7 @@ private:
321 std::optional<SamplerInfo> sampler_info = std::nullopt); 328 std::optional<SamplerInfo> sampler_info = std::nullopt);
322 329
323 /// Accesses a texture sampler for a bindless texture. 330 /// Accesses a texture sampler for a bindless texture.
324 const Sampler* GetBindlessSampler(Tegra::Shader::Register reg, 331 const Sampler* GetBindlessSampler(Tegra::Shader::Register reg, Node& index_var,
325 std::optional<SamplerInfo> sampler_info = std::nullopt); 332 std::optional<SamplerInfo> sampler_info = std::nullopt);
326 333
327 /// Accesses an image. 334 /// Accesses an image.
@@ -387,6 +394,9 @@ private:
387 394
388 std::tuple<Node, u32, u32> TrackCbuf(Node tracked, const NodeBlock& code, s64 cursor) const; 395 std::tuple<Node, u32, u32> TrackCbuf(Node tracked, const NodeBlock& code, s64 cursor) const;
389 396
397 std::tuple<Node, TrackSampler> TrackBindlessSampler(Node tracked, const NodeBlock& code,
398 s64 cursor);
399
390 std::optional<u32> TrackImmediate(Node tracked, const NodeBlock& code, s64 cursor) const; 400 std::optional<u32> TrackImmediate(Node tracked, const NodeBlock& code, s64 cursor) const;
391 401
392 std::pair<Node, s64> TrackRegister(const GprNode* tracked, const NodeBlock& code, 402 std::pair<Node, s64> TrackRegister(const GprNode* tracked, const NodeBlock& code,
@@ -399,6 +409,8 @@ private:
399 /// Register new amending code and obtain the reference id. 409 /// Register new amending code and obtain the reference id.
400 std::size_t DeclareAmend(Node new_amend); 410 std::size_t DeclareAmend(Node new_amend);
401 411
412 u32 NewCustomVariable();
413
402 const ProgramCode& program_code; 414 const ProgramCode& program_code;
403 const u32 main_offset; 415 const u32 main_offset;
404 const CompilerSettings settings; 416 const CompilerSettings settings;
@@ -414,6 +426,7 @@ private:
414 NodeBlock global_code; 426 NodeBlock global_code;
415 ASTManager program_manager{true, true}; 427 ASTManager program_manager{true, true};
416 std::vector<Node> amend_code; 428 std::vector<Node> amend_code;
429 u32 num_custom_variables{};
417 430
418 std::set<u32> used_registers; 431 std::set<u32> used_registers;
419 std::set<Tegra::Shader::Pred> used_predicates; 432 std::set<Tegra::Shader::Pred> used_predicates;
@@ -431,6 +444,7 @@ private:
431 bool uses_instance_id{}; 444 bool uses_instance_id{};
432 bool uses_vertex_id{}; 445 bool uses_vertex_id{};
433 bool uses_warps{}; 446 bool uses_warps{};
447 bool uses_indexed_samplers{};
434 448
435 Tegra::Shader::Header header; 449 Tegra::Shader::Header header;
436}; 450};
diff --git a/src/video_core/shader/track.cpp b/src/video_core/shader/track.cpp
index 165c79330..face8c943 100644
--- a/src/video_core/shader/track.cpp
+++ b/src/video_core/shader/track.cpp
@@ -8,6 +8,7 @@
8 8
9#include "common/common_types.h" 9#include "common/common_types.h"
10#include "video_core/shader/node.h" 10#include "video_core/shader/node.h"
11#include "video_core/shader/node_helper.h"
11#include "video_core/shader/shader_ir.h" 12#include "video_core/shader/shader_ir.h"
12 13
13namespace VideoCommon::Shader { 14namespace VideoCommon::Shader {
@@ -35,8 +36,113 @@ std::pair<Node, s64> FindOperation(const NodeBlock& code, s64 cursor,
35 } 36 }
36 return {}; 37 return {};
37} 38}
39
40std::optional<std::pair<Node, Node>> DecoupleIndirectRead(const OperationNode& operation) {
41 if (operation.GetCode() != OperationCode::UAdd) {
42 return std::nullopt;
43 }
44 Node gpr;
45 Node offset;
46 ASSERT(operation.GetOperandsCount() == 2);
47 for (std::size_t i = 0; i < operation.GetOperandsCount(); i++) {
48 Node operand = operation[i];
49 if (std::holds_alternative<ImmediateNode>(*operand)) {
50 offset = operation[i];
51 } else if (std::holds_alternative<GprNode>(*operand)) {
52 gpr = operation[i];
53 }
54 }
55 if (offset && gpr) {
56 return std::make_pair(gpr, offset);
57 }
58 return std::nullopt;
59}
60
61bool AmendNodeCv(std::size_t amend_index, Node node) {
62 if (const auto operation = std::get_if<OperationNode>(&*node)) {
63 operation->SetAmendIndex(amend_index);
64 return true;
65 } else if (const auto conditional = std::get_if<ConditionalNode>(&*node)) {
66 conditional->SetAmendIndex(amend_index);
67 return true;
68 }
69 return false;
70}
71
38} // Anonymous namespace 72} // Anonymous namespace
39 73
74std::tuple<Node, TrackSampler> ShaderIR::TrackBindlessSampler(Node tracked, const NodeBlock& code,
75 s64 cursor) {
76 if (const auto cbuf = std::get_if<CbufNode>(&*tracked)) {
77 // Constant buffer found, test if it's an immediate
78 const auto offset = cbuf->GetOffset();
79 if (const auto immediate = std::get_if<ImmediateNode>(&*offset)) {
80 auto track =
81 MakeTrackSampler<BindlessSamplerNode>(cbuf->GetIndex(), immediate->GetValue());
82 return {tracked, track};
83 } else if (const auto operation = std::get_if<OperationNode>(&*offset)) {
84 auto bound_buffer = locker.ObtainBoundBuffer();
85 if (!bound_buffer) {
86 return {};
87 }
88 if (*bound_buffer != cbuf->GetIndex()) {
89 return {};
90 }
91 auto pair = DecoupleIndirectRead(*operation);
92 if (!pair) {
93 return {};
94 }
95 auto [gpr, base_offset] = *pair;
96 const auto offset_inm = std::get_if<ImmediateNode>(&*base_offset);
97 auto gpu_driver = locker.AccessGuestDriverProfile();
98 if (gpu_driver == nullptr) {
99 return {};
100 }
101 const u32 bindless_cv = NewCustomVariable();
102 const Node op = Operation(OperationCode::UDiv, NO_PRECISE, gpr,
103 Immediate(gpu_driver->GetTextureHandlerSize()));
104
105 const Node cv_node = GetCustomVariable(bindless_cv);
106 Node amend_op = Operation(OperationCode::Assign, cv_node, std::move(op));
107 const std::size_t amend_index = DeclareAmend(amend_op);
108 AmendNodeCv(amend_index, code[cursor]);
109 // TODO Implement Bindless Index custom variable
110 auto track = MakeTrackSampler<ArraySamplerNode>(cbuf->GetIndex(),
111 offset_inm->GetValue(), bindless_cv);
112 return {tracked, track};
113 }
114 return {};
115 }
116 if (const auto gpr = std::get_if<GprNode>(&*tracked)) {
117 if (gpr->GetIndex() == Tegra::Shader::Register::ZeroIndex) {
118 return {};
119 }
120 // Reduce the cursor in one to avoid infinite loops when the instruction sets the same
121 // register that it uses as operand
122 const auto [source, new_cursor] = TrackRegister(gpr, code, cursor - 1);
123 if (!source) {
124 return {};
125 }
126 return TrackBindlessSampler(source, code, new_cursor);
127 }
128 if (const auto operation = std::get_if<OperationNode>(&*tracked)) {
129 for (std::size_t i = operation->GetOperandsCount(); i > 0; --i) {
130 if (auto found = TrackBindlessSampler((*operation)[i - 1], code, cursor);
131 std::get<0>(found)) {
132 // Cbuf found in operand.
133 return found;
134 }
135 }
136 return {};
137 }
138 if (const auto conditional = std::get_if<ConditionalNode>(&*tracked)) {
139 const auto& conditional_code = conditional->GetCode();
140 return TrackBindlessSampler(tracked, conditional_code,
141 static_cast<s64>(conditional_code.size()));
142 }
143 return {};
144}
145
40std::tuple<Node, u32, u32> ShaderIR::TrackCbuf(Node tracked, const NodeBlock& code, 146std::tuple<Node, u32, u32> ShaderIR::TrackCbuf(Node tracked, const NodeBlock& code,
41 s64 cursor) const { 147 s64 cursor) const {
42 if (const auto cbuf = std::get_if<CbufNode>(&*tracked)) { 148 if (const auto cbuf = std::get_if<CbufNode>(&*tracked)) {
diff --git a/src/video_core/texture_cache/surface_base.cpp b/src/video_core/texture_cache/surface_base.cpp
index 829268b4c..84469b7ba 100644
--- a/src/video_core/texture_cache/surface_base.cpp
+++ b/src/video_core/texture_cache/surface_base.cpp
@@ -135,7 +135,7 @@ std::vector<CopyParams> SurfaceBaseImpl::BreakDownLayered(const SurfaceParams& i
135 for (u32 level = 0; level < mipmaps; level++) { 135 for (u32 level = 0; level < mipmaps; level++) {
136 const u32 width = SurfaceParams::IntersectWidth(params, in_params, level, level); 136 const u32 width = SurfaceParams::IntersectWidth(params, in_params, level, level);
137 const u32 height = SurfaceParams::IntersectHeight(params, in_params, level, level); 137 const u32 height = SurfaceParams::IntersectHeight(params, in_params, level, level);
138 result.emplace_back(width, height, layer, level); 138 result.emplace_back(0, 0, layer, 0, 0, layer, level, level, width, height, 1);
139 } 139 }
140 } 140 }
141 return result; 141 return result;
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index f4c015635..0d105d386 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -721,7 +721,6 @@ private:
721 std::pair<TSurface, TView> GetSurface(const GPUVAddr gpu_addr, const CacheAddr cache_addr, 721 std::pair<TSurface, TView> GetSurface(const GPUVAddr gpu_addr, const CacheAddr cache_addr,
722 const SurfaceParams& params, bool preserve_contents, 722 const SurfaceParams& params, bool preserve_contents,
723 bool is_render) { 723 bool is_render) {
724
725 // Step 1 724 // Step 1
726 // Check Level 1 Cache for a fast structural match. If candidate surface 725 // Check Level 1 Cache for a fast structural match. If candidate surface
727 // matches at certain level we are pretty much done. 726 // matches at certain level we are pretty much done.
@@ -733,14 +732,18 @@ private:
733 return RecycleSurface(overlaps, params, gpu_addr, preserve_contents, 732 return RecycleSurface(overlaps, params, gpu_addr, preserve_contents,
734 topological_result); 733 topological_result);
735 } 734 }
735
736 const auto struct_result = current_surface->MatchesStructure(params); 736 const auto struct_result = current_surface->MatchesStructure(params);
737 if (struct_result != MatchStructureResult::None && 737 if (struct_result != MatchStructureResult::None) {
738 (params.target != SurfaceTarget::Texture3D || 738 const auto& old_params = current_surface->GetSurfaceParams();
739 current_surface->MatchTarget(params.target))) { 739 const bool not_3d = params.target != SurfaceTarget::Texture3D &&
740 if (struct_result == MatchStructureResult::FullMatch) { 740 old_params.target != SurfaceTarget::Texture3D;
741 return ManageStructuralMatch(current_surface, params, is_render); 741 if (not_3d || current_surface->MatchTarget(params.target)) {
742 } else { 742 if (struct_result == MatchStructureResult::FullMatch) {
743 return RebuildSurface(current_surface, params, is_render); 743 return ManageStructuralMatch(current_surface, params, is_render);
744 } else {
745 return RebuildSurface(current_surface, params, is_render);
746 }
744 } 747 }
745 } 748 }
746 } 749 }
diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp
index 8e947394c..a5f81a8a0 100644
--- a/src/video_core/video_core.cpp
+++ b/src/video_core/video_core.cpp
@@ -3,19 +3,32 @@
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <memory> 5#include <memory>
6#include "common/logging/log.h"
6#include "core/core.h" 7#include "core/core.h"
7#include "core/settings.h" 8#include "core/settings.h"
8#include "video_core/gpu_asynch.h" 9#include "video_core/gpu_asynch.h"
9#include "video_core/gpu_synch.h" 10#include "video_core/gpu_synch.h"
10#include "video_core/renderer_base.h" 11#include "video_core/renderer_base.h"
11#include "video_core/renderer_opengl/renderer_opengl.h" 12#include "video_core/renderer_opengl/renderer_opengl.h"
13#ifdef HAS_VULKAN
14#include "video_core/renderer_vulkan/renderer_vulkan.h"
15#endif
12#include "video_core/video_core.h" 16#include "video_core/video_core.h"
13 17
14namespace VideoCore { 18namespace VideoCore {
15 19
16std::unique_ptr<RendererBase> CreateRenderer(Core::Frontend::EmuWindow& emu_window, 20std::unique_ptr<RendererBase> CreateRenderer(Core::Frontend::EmuWindow& emu_window,
17 Core::System& system) { 21 Core::System& system) {
18 return std::make_unique<OpenGL::RendererOpenGL>(emu_window, system); 22 switch (Settings::values.renderer_backend) {
23 case Settings::RendererBackend::OpenGL:
24 return std::make_unique<OpenGL::RendererOpenGL>(emu_window, system);
25#ifdef HAS_VULKAN
26 case Settings::RendererBackend::Vulkan:
27 return std::make_unique<Vulkan::RendererVulkan>(emu_window, system);
28#endif
29 default:
30 return nullptr;
31 }
19} 32}
20 33
21std::unique_ptr<Tegra::GPU> CreateGPU(Core::System& system) { 34std::unique_ptr<Tegra::GPU> CreateGPU(Core::System& system) {
diff --git a/src/web_service/telemetry_json.cpp b/src/web_service/telemetry_json.cpp
index 9156ce802..7538389bf 100644
--- a/src/web_service/telemetry_json.cpp
+++ b/src/web_service/telemetry_json.cpp
@@ -117,6 +117,7 @@ bool TelemetryJson::SubmitTestcase() {
117 impl->SerializeSection(Telemetry::FieldType::Session, "Session"); 117 impl->SerializeSection(Telemetry::FieldType::Session, "Session");
118 impl->SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback"); 118 impl->SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback");
119 impl->SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem"); 119 impl->SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem");
120 impl->SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig");
120 121
121 auto content = impl->TopSection().dump(); 122 auto content = impl->TopSection().dump();
122 Client client(impl->host, impl->username, impl->token); 123 Client client(impl->host, impl->username, impl->token);
diff --git a/src/web_service/web_backend.cpp b/src/web_service/web_backend.cpp
index 6683f459f..737ffe409 100644
--- a/src/web_service/web_backend.cpp
+++ b/src/web_service/web_backend.cpp
@@ -73,14 +73,12 @@ struct Client::Impl {
73 if (!parsedUrl.GetPort(&port)) { 73 if (!parsedUrl.GetPort(&port)) {
74 port = HTTP_PORT; 74 port = HTTP_PORT;
75 } 75 }
76 cli = std::make_unique<httplib::Client>(parsedUrl.m_Host.c_str(), port, 76 cli = std::make_unique<httplib::Client>(parsedUrl.m_Host.c_str(), port);
77 TIMEOUT_SECONDS);
78 } else if (parsedUrl.m_Scheme == "https") { 77 } else if (parsedUrl.m_Scheme == "https") {
79 if (!parsedUrl.GetPort(&port)) { 78 if (!parsedUrl.GetPort(&port)) {
80 port = HTTPS_PORT; 79 port = HTTPS_PORT;
81 } 80 }
82 cli = std::make_unique<httplib::SSLClient>(parsedUrl.m_Host.c_str(), port, 81 cli = std::make_unique<httplib::SSLClient>(parsedUrl.m_Host.c_str(), port);
83 TIMEOUT_SECONDS);
84 } else { 82 } else {
85 LOG_ERROR(WebService, "Bad URL scheme {}", parsedUrl.m_Scheme); 83 LOG_ERROR(WebService, "Bad URL scheme {}", parsedUrl.m_Scheme);
86 return Common::WebResult{Common::WebResult::Code::InvalidURL, "Bad URL scheme"}; 84 return Common::WebResult{Common::WebResult::Code::InvalidURL, "Bad URL scheme"};
@@ -90,6 +88,7 @@ struct Client::Impl {
90 LOG_ERROR(WebService, "Invalid URL {}", host + path); 88 LOG_ERROR(WebService, "Invalid URL {}", host + path);
91 return Common::WebResult{Common::WebResult::Code::InvalidURL, "Invalid URL"}; 89 return Common::WebResult{Common::WebResult::Code::InvalidURL, "Invalid URL"};
92 } 90 }
91 cli->set_timeout_sec(TIMEOUT_SECONDS);
93 92
94 httplib::Headers params; 93 httplib::Headers params;
95 if (!jwt.empty()) { 94 if (!jwt.empty()) {
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 11ae1e66e..b841e63fa 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -36,9 +36,6 @@ add_executable(yuzu
36 configuration/configure_filesystem.cpp 36 configuration/configure_filesystem.cpp
37 configuration/configure_filesystem.h 37 configuration/configure_filesystem.h
38 configuration/configure_filesystem.ui 38 configuration/configure_filesystem.ui
39 configuration/configure_gamelist.cpp
40 configuration/configure_gamelist.h
41 configuration/configure_gamelist.ui
42 configuration/configure_general.cpp 39 configuration/configure_general.cpp
43 configuration/configure_general.h 40 configuration/configure_general.h
44 configuration/configure_general.ui 41 configuration/configure_general.ui
@@ -75,6 +72,9 @@ add_executable(yuzu
75 configuration/configure_touchscreen_advanced.cpp 72 configuration/configure_touchscreen_advanced.cpp
76 configuration/configure_touchscreen_advanced.h 73 configuration/configure_touchscreen_advanced.h
77 configuration/configure_touchscreen_advanced.ui 74 configuration/configure_touchscreen_advanced.ui
75 configuration/configure_ui.cpp
76 configuration/configure_ui.h
77 configuration/configure_ui.ui
78 configuration/configure_web.cpp 78 configuration/configure_web.cpp
79 configuration/configure_web.h 79 configuration/configure_web.h
80 configuration/configure_web.ui 80 configuration/configure_web.ui
@@ -200,3 +200,8 @@ if (MSVC)
200 copy_yuzu_SDL_deps(yuzu) 200 copy_yuzu_SDL_deps(yuzu)
201 copy_yuzu_unicorn_deps(yuzu) 201 copy_yuzu_unicorn_deps(yuzu)
202endif() 202endif()
203
204if (ENABLE_VULKAN)
205 target_include_directories(yuzu PRIVATE ../../externals/Vulkan-Headers/include)
206 target_compile_definitions(yuzu PRIVATE HAS_VULKAN)
207endif()
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 7490fb718..55a37fffa 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -2,19 +2,30 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <glad/glad.h>
6
5#include <QApplication> 7#include <QApplication>
6#include <QHBoxLayout> 8#include <QHBoxLayout>
7#include <QKeyEvent> 9#include <QKeyEvent>
10#include <QMessageBox>
8#include <QOffscreenSurface> 11#include <QOffscreenSurface>
9#include <QOpenGLWindow> 12#include <QOpenGLWindow>
10#include <QPainter> 13#include <QPainter>
11#include <QScreen> 14#include <QScreen>
15#include <QStringList>
12#include <QWindow> 16#include <QWindow>
17#ifdef HAS_VULKAN
18#include <QVulkanWindow>
19#endif
20
13#include <fmt/format.h> 21#include <fmt/format.h>
22
23#include "common/assert.h"
14#include "common/microprofile.h" 24#include "common/microprofile.h"
15#include "common/scm_rev.h" 25#include "common/scm_rev.h"
16#include "core/core.h" 26#include "core/core.h"
17#include "core/frontend/framebuffer_layout.h" 27#include "core/frontend/framebuffer_layout.h"
28#include "core/frontend/scope_acquire_window_context.h"
18#include "core/settings.h" 29#include "core/settings.h"
19#include "input_common/keyboard.h" 30#include "input_common/keyboard.h"
20#include "input_common/main.h" 31#include "input_common/main.h"
@@ -114,19 +125,10 @@ private:
114 QOpenGLContext context; 125 QOpenGLContext context;
115}; 126};
116 127
117// This class overrides paintEvent and resizeEvent to prevent the GUI thread from stealing GL 128class GWidgetInternal : public QWindow {
118// context.
119// The corresponding functionality is handled in EmuThread instead
120class GGLWidgetInternal : public QOpenGLWindow {
121public: 129public:
122 GGLWidgetInternal(GRenderWindow* parent, QOpenGLContext* shared_context) 130 GWidgetInternal(GRenderWindow* parent) : parent(parent) {}
123 : QOpenGLWindow(shared_context), parent(parent) {} 131 virtual ~GWidgetInternal() = default;
124
125 void paintEvent(QPaintEvent* ev) override {
126 if (do_painting) {
127 QPainter painter(this);
128 }
129 }
130 132
131 void resizeEvent(QResizeEvent* ev) override { 133 void resizeEvent(QResizeEvent* ev) override {
132 parent->OnClientAreaResized(ev->size().width(), ev->size().height()); 134 parent->OnClientAreaResized(ev->size().width(), ev->size().height());
@@ -182,11 +184,47 @@ public:
182 do_painting = true; 184 do_painting = true;
183 } 185 }
184 186
187 std::pair<unsigned, unsigned> GetSize() const {
188 return std::make_pair(width(), height());
189 }
190
191protected:
192 bool IsPaintingEnabled() const {
193 return do_painting;
194 }
195
185private: 196private:
186 GRenderWindow* parent; 197 GRenderWindow* parent;
187 bool do_painting; 198 bool do_painting = false;
199};
200
201// This class overrides paintEvent and resizeEvent to prevent the GUI thread from stealing GL
202// context.
203// The corresponding functionality is handled in EmuThread instead
204class GGLWidgetInternal final : public GWidgetInternal, public QOpenGLWindow {
205public:
206 GGLWidgetInternal(GRenderWindow* parent, QOpenGLContext* shared_context)
207 : GWidgetInternal(parent), QOpenGLWindow(shared_context) {}
208 ~GGLWidgetInternal() override = default;
209
210 void paintEvent(QPaintEvent* ev) override {
211 if (IsPaintingEnabled()) {
212 QPainter painter(this);
213 }
214 }
188}; 215};
189 216
217#ifdef HAS_VULKAN
218class GVKWidgetInternal final : public GWidgetInternal {
219public:
220 GVKWidgetInternal(GRenderWindow* parent, QVulkanInstance* instance) : GWidgetInternal(parent) {
221 setSurfaceType(QSurface::SurfaceType::VulkanSurface);
222 setVulkanInstance(instance);
223 }
224 ~GVKWidgetInternal() override = default;
225};
226#endif
227
190GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread) 228GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread)
191 : QWidget(parent), emu_thread(emu_thread) { 229 : QWidget(parent), emu_thread(emu_thread) {
192 setWindowTitle(QStringLiteral("yuzu %1 | %2-%3") 230 setWindowTitle(QStringLiteral("yuzu %1 | %2-%3")
@@ -201,9 +239,15 @@ GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread)
201 239
202GRenderWindow::~GRenderWindow() { 240GRenderWindow::~GRenderWindow() {
203 InputCommon::Shutdown(); 241 InputCommon::Shutdown();
242
243 // Avoid an unordered destruction that generates a segfault
244 delete child;
204} 245}
205 246
206void GRenderWindow::moveContext() { 247void GRenderWindow::moveContext() {
248 if (!context) {
249 return;
250 }
207 DoneCurrent(); 251 DoneCurrent();
208 252
209 // If the thread started running, move the GL Context to the new thread. Otherwise, move it 253 // If the thread started running, move the GL Context to the new thread. Otherwise, move it
@@ -215,8 +259,9 @@ void GRenderWindow::moveContext() {
215} 259}
216 260
217void GRenderWindow::SwapBuffers() { 261void GRenderWindow::SwapBuffers() {
218 context->swapBuffers(child); 262 if (context) {
219 263 context->swapBuffers(child);
264 }
220 if (!first_frame) { 265 if (!first_frame) {
221 first_frame = true; 266 first_frame = true;
222 emit FirstFrameDisplayed(); 267 emit FirstFrameDisplayed();
@@ -224,15 +269,38 @@ void GRenderWindow::SwapBuffers() {
224} 269}
225 270
226void GRenderWindow::MakeCurrent() { 271void GRenderWindow::MakeCurrent() {
227 context->makeCurrent(child); 272 if (context) {
273 context->makeCurrent(child);
274 }
228} 275}
229 276
230void GRenderWindow::DoneCurrent() { 277void GRenderWindow::DoneCurrent() {
231 context->doneCurrent(); 278 if (context) {
279 context->doneCurrent();
280 }
232} 281}
233 282
234void GRenderWindow::PollEvents() {} 283void GRenderWindow::PollEvents() {}
235 284
285bool GRenderWindow::IsShown() const {
286 return !isMinimized();
287}
288
289void GRenderWindow::RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
290 void* surface) const {
291#ifdef HAS_VULKAN
292 const auto instance_proc_addr = vk_instance->getInstanceProcAddr("vkGetInstanceProcAddr");
293 const VkInstance instance_copy = vk_instance->vkInstance();
294 const VkSurfaceKHR surface_copy = vk_instance->surfaceForWindow(child);
295
296 std::memcpy(get_instance_proc_addr, &instance_proc_addr, sizeof(instance_proc_addr));
297 std::memcpy(instance, &instance_copy, sizeof(instance_copy));
298 std::memcpy(surface, &surface_copy, sizeof(surface_copy));
299#else
300 UNREACHABLE_MSG("Executing Vulkan code without compiling Vulkan");
301#endif
302}
303
236// On Qt 5.0+, this correctly gets the size of the framebuffer (pixels). 304// On Qt 5.0+, this correctly gets the size of the framebuffer (pixels).
237// 305//
238// Older versions get the window size (density independent pixels), 306// Older versions get the window size (density independent pixels),
@@ -241,10 +309,9 @@ void GRenderWindow::PollEvents() {}
241void GRenderWindow::OnFramebufferSizeChanged() { 309void GRenderWindow::OnFramebufferSizeChanged() {
242 // Screen changes potentially incur a change in screen DPI, hence we should update the 310 // Screen changes potentially incur a change in screen DPI, hence we should update the
243 // framebuffer size 311 // framebuffer size
244 const qreal pixel_ratio = GetWindowPixelRatio(); 312 const qreal pixelRatio{GetWindowPixelRatio()};
245 const u32 width = child->QPaintDevice::width() * pixel_ratio; 313 const auto size{child->GetSize()};
246 const u32 height = child->QPaintDevice::height() * pixel_ratio; 314 UpdateCurrentFramebufferLayout(size.first * pixelRatio, size.second * pixelRatio);
247 UpdateCurrentFramebufferLayout(width, height);
248} 315}
249 316
250void GRenderWindow::ForwardKeyPressEvent(QKeyEvent* event) { 317void GRenderWindow::ForwardKeyPressEvent(QKeyEvent* event) {
@@ -290,7 +357,7 @@ qreal GRenderWindow::GetWindowPixelRatio() const {
290} 357}
291 358
292std::pair<u32, u32> GRenderWindow::ScaleTouch(const QPointF pos) const { 359std::pair<u32, u32> GRenderWindow::ScaleTouch(const QPointF pos) const {
293 const qreal pixel_ratio = GetWindowPixelRatio(); 360 const qreal pixel_ratio{GetWindowPixelRatio()};
294 return {static_cast<u32>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})), 361 return {static_cast<u32>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})),
295 static_cast<u32>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))}; 362 static_cast<u32>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))};
296} 363}
@@ -356,50 +423,46 @@ std::unique_ptr<Core::Frontend::GraphicsContext> GRenderWindow::CreateSharedCont
356 return std::make_unique<GGLContext>(context.get()); 423 return std::make_unique<GGLContext>(context.get());
357} 424}
358 425
359void GRenderWindow::InitRenderTarget() { 426bool GRenderWindow::InitRenderTarget() {
360 shared_context.reset(); 427 shared_context.reset();
361 context.reset(); 428 context.reset();
362 429 if (child) {
363 delete child; 430 delete child;
364 child = nullptr; 431 }
365 432 if (container) {
366 delete container; 433 delete container;
367 container = nullptr; 434 }
368 435 if (layout()) {
369 delete layout(); 436 delete layout();
437 }
370 438
371 first_frame = false; 439 first_frame = false;
372 440
373 // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground, 441 switch (Settings::values.renderer_backend) {
374 // WA_DontShowOnScreen, WA_DeleteOnClose 442 case Settings::RendererBackend::OpenGL:
375 QSurfaceFormat fmt; 443 if (!InitializeOpenGL()) {
376 fmt.setVersion(4, 3); 444 return false;
377 fmt.setProfile(QSurfaceFormat::CompatibilityProfile); 445 }
378 fmt.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions); 446 break;
379 // TODO: expose a setting for buffer value (ie default/single/double/triple) 447 case Settings::RendererBackend::Vulkan:
380 fmt.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior); 448 if (!InitializeVulkan()) {
381 shared_context = std::make_unique<QOpenGLContext>(); 449 return false;
382 shared_context->setFormat(fmt); 450 }
383 shared_context->create(); 451 break;
384 context = std::make_unique<QOpenGLContext>(); 452 }
385 context->setShareContext(shared_context.get());
386 context->setFormat(fmt);
387 context->create();
388 fmt.setSwapInterval(0);
389 453
390 child = new GGLWidgetInternal(this, shared_context.get());
391 container = QWidget::createWindowContainer(child, this); 454 container = QWidget::createWindowContainer(child, this);
392
393 QBoxLayout* layout = new QHBoxLayout(this); 455 QBoxLayout* layout = new QHBoxLayout(this);
456
394 layout->addWidget(container); 457 layout->addWidget(container);
395 layout->setMargin(0); 458 layout->setMargin(0);
396 setLayout(layout); 459 setLayout(layout);
397 460
398 // Reset minimum size to avoid unwanted resizes when this function is called for a second time. 461 // Reset minimum required size to avoid resizing issues on the main window after restarting.
399 setMinimumSize(1, 1); 462 setMinimumSize(1, 1);
400 463
401 // Show causes the window to actually be created and the OpenGL context as well, but we don't 464 // Show causes the window to actually be created and the gl context as well, but we don't want
402 // want the widget to be shown yet, so immediately hide it. 465 // the widget to be shown yet, so immediately hide it.
403 show(); 466 show();
404 hide(); 467 hide();
405 468
@@ -410,9 +473,17 @@ void GRenderWindow::InitRenderTarget() {
410 OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); 473 OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
411 474
412 OnFramebufferSizeChanged(); 475 OnFramebufferSizeChanged();
413 NotifyClientAreaSizeChanged(std::pair<unsigned, unsigned>(child->width(), child->height())); 476 NotifyClientAreaSizeChanged(child->GetSize());
414 477
415 BackupGeometry(); 478 BackupGeometry();
479
480 if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) {
481 if (!LoadOpenGL()) {
482 return false;
483 }
484 }
485
486 return true;
416} 487}
417 488
418void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) { 489void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) {
@@ -441,6 +512,113 @@ void GRenderWindow::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal
441 setMinimumSize(minimal_size.first, minimal_size.second); 512 setMinimumSize(minimal_size.first, minimal_size.second);
442} 513}
443 514
515bool GRenderWindow::InitializeOpenGL() {
516 // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
517 // WA_DontShowOnScreen, WA_DeleteOnClose
518 QSurfaceFormat fmt;
519 fmt.setVersion(4, 3);
520 fmt.setProfile(QSurfaceFormat::CompatibilityProfile);
521 fmt.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions);
522 // TODO: expose a setting for buffer value (ie default/single/double/triple)
523 fmt.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
524 shared_context = std::make_unique<QOpenGLContext>();
525 shared_context->setFormat(fmt);
526 shared_context->create();
527 context = std::make_unique<QOpenGLContext>();
528 context->setShareContext(shared_context.get());
529 context->setFormat(fmt);
530 context->create();
531 fmt.setSwapInterval(false);
532
533 child = new GGLWidgetInternal(this, shared_context.get());
534 return true;
535}
536
537bool GRenderWindow::InitializeVulkan() {
538#ifdef HAS_VULKAN
539 vk_instance = std::make_unique<QVulkanInstance>();
540 vk_instance->setApiVersion(QVersionNumber(1, 1, 0));
541 vk_instance->setFlags(QVulkanInstance::Flag::NoDebugOutputRedirect);
542 if (Settings::values.renderer_debug) {
543 const auto supported_layers{vk_instance->supportedLayers()};
544 const bool found =
545 std::find_if(supported_layers.begin(), supported_layers.end(), [](const auto& layer) {
546 constexpr const char searched_layer[] = "VK_LAYER_LUNARG_standard_validation";
547 return layer.name == searched_layer;
548 });
549 if (found) {
550 vk_instance->setLayers(QByteArrayList() << "VK_LAYER_LUNARG_standard_validation");
551 vk_instance->setExtensions(QByteArrayList() << VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
552 }
553 }
554 if (!vk_instance->create()) {
555 QMessageBox::critical(
556 this, tr("Error while initializing Vulkan 1.1!"),
557 tr("Your OS doesn't seem to support Vulkan 1.1 instances, or you do not have the "
558 "latest graphics drivers."));
559 return false;
560 }
561
562 child = new GVKWidgetInternal(this, vk_instance.get());
563 return true;
564#else
565 QMessageBox::critical(this, tr("Vulkan not available!"),
566 tr("yuzu has not been compiled with Vulkan support."));
567 return false;
568#endif
569}
570
571bool GRenderWindow::LoadOpenGL() {
572 Core::Frontend::ScopeAcquireWindowContext acquire_context{*this};
573 if (!gladLoadGL()) {
574 QMessageBox::critical(this, tr("Error while initializing OpenGL 4.3!"),
575 tr("Your GPU may not support OpenGL 4.3, or you do not have the "
576 "latest graphics driver."));
577 return false;
578 }
579
580 QStringList unsupported_gl_extensions = GetUnsupportedGLExtensions();
581 if (!unsupported_gl_extensions.empty()) {
582 QMessageBox::critical(
583 this, tr("Error while initializing OpenGL!"),
584 tr("Your GPU may not support one or more required OpenGL extensions. Please ensure you "
585 "have the latest graphics driver.<br><br>Unsupported extensions:<br>") +
586 unsupported_gl_extensions.join(QStringLiteral("<br>")));
587 return false;
588 }
589 return true;
590}
591
592QStringList GRenderWindow::GetUnsupportedGLExtensions() const {
593 QStringList unsupported_ext;
594
595 if (!GLAD_GL_ARB_buffer_storage)
596 unsupported_ext.append(QStringLiteral("ARB_buffer_storage"));
597 if (!GLAD_GL_ARB_direct_state_access)
598 unsupported_ext.append(QStringLiteral("ARB_direct_state_access"));
599 if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev)
600 unsupported_ext.append(QStringLiteral("ARB_vertex_type_10f_11f_11f_rev"));
601 if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge)
602 unsupported_ext.append(QStringLiteral("ARB_texture_mirror_clamp_to_edge"));
603 if (!GLAD_GL_ARB_multi_bind)
604 unsupported_ext.append(QStringLiteral("ARB_multi_bind"));
605 if (!GLAD_GL_ARB_clip_control)
606 unsupported_ext.append(QStringLiteral("ARB_clip_control"));
607
608 // Extensions required to support some texture formats.
609 if (!GLAD_GL_EXT_texture_compression_s3tc)
610 unsupported_ext.append(QStringLiteral("EXT_texture_compression_s3tc"));
611 if (!GLAD_GL_ARB_texture_compression_rgtc)
612 unsupported_ext.append(QStringLiteral("ARB_texture_compression_rgtc"));
613 if (!GLAD_GL_ARB_depth_buffer_float)
614 unsupported_ext.append(QStringLiteral("ARB_depth_buffer_float"));
615
616 for (const QString& ext : unsupported_ext)
617 LOG_CRITICAL(Frontend, "Unsupported GL extension: {}", ext.toStdString());
618
619 return unsupported_ext;
620}
621
444void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) { 622void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) {
445 this->emu_thread = emu_thread; 623 this->emu_thread = emu_thread;
446 child->DisablePainting(); 624 child->DisablePainting();
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h
index 2fc64895f..71a2fa321 100644
--- a/src/yuzu/bootmanager.h
+++ b/src/yuzu/bootmanager.h
@@ -7,17 +7,28 @@
7#include <atomic> 7#include <atomic>
8#include <condition_variable> 8#include <condition_variable>
9#include <mutex> 9#include <mutex>
10
10#include <QImage> 11#include <QImage>
11#include <QThread> 12#include <QThread>
12#include <QWidget> 13#include <QWidget>
14
15#include "common/thread.h"
13#include "core/core.h" 16#include "core/core.h"
14#include "core/frontend/emu_window.h" 17#include "core/frontend/emu_window.h"
15 18
16class QKeyEvent; 19class QKeyEvent;
17class QScreen; 20class QScreen;
18class QTouchEvent; 21class QTouchEvent;
22class QStringList;
23class QSurface;
24class QOpenGLContext;
25#ifdef HAS_VULKAN
26class QVulkanInstance;
27#endif
19 28
29class GWidgetInternal;
20class GGLWidgetInternal; 30class GGLWidgetInternal;
31class GVKWidgetInternal;
21class GMainWindow; 32class GMainWindow;
22class GRenderWindow; 33class GRenderWindow;
23class QSurface; 34class QSurface;
@@ -123,6 +134,9 @@ public:
123 void MakeCurrent() override; 134 void MakeCurrent() override;
124 void DoneCurrent() override; 135 void DoneCurrent() override;
125 void PollEvents() override; 136 void PollEvents() override;
137 bool IsShown() const override;
138 void RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
139 void* surface) const override;
126 std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override; 140 std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
127 141
128 void ForwardKeyPressEvent(QKeyEvent* event); 142 void ForwardKeyPressEvent(QKeyEvent* event);
@@ -142,7 +156,7 @@ public:
142 156
143 void OnClientAreaResized(u32 width, u32 height); 157 void OnClientAreaResized(u32 width, u32 height);
144 158
145 void InitRenderTarget(); 159 bool InitRenderTarget();
146 160
147 void CaptureScreenshot(u32 res_scale, const QString& screenshot_path); 161 void CaptureScreenshot(u32 res_scale, const QString& screenshot_path);
148 162
@@ -165,10 +179,13 @@ private:
165 179
166 void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override; 180 void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override;
167 181
168 QWidget* container = nullptr; 182 bool InitializeOpenGL();
169 GGLWidgetInternal* child = nullptr; 183 bool InitializeVulkan();
184 bool LoadOpenGL();
185 QStringList GetUnsupportedGLExtensions() const;
170 186
171 QByteArray geometry; 187 QWidget* container = nullptr;
188 GWidgetInternal* child = nullptr;
172 189
173 EmuThread* emu_thread; 190 EmuThread* emu_thread;
174 // Context that backs the GGLWidgetInternal (and will be used by core to render) 191 // Context that backs the GGLWidgetInternal (and will be used by core to render)
@@ -177,9 +194,14 @@ private:
177 // current 194 // current
178 std::unique_ptr<QOpenGLContext> shared_context; 195 std::unique_ptr<QOpenGLContext> shared_context;
179 196
197#ifdef HAS_VULKAN
198 std::unique_ptr<QVulkanInstance> vk_instance;
199#endif
200
180 /// Temporary storage of the screenshot taken 201 /// Temporary storage of the screenshot taken
181 QImage screenshot_image; 202 QImage screenshot_image;
182 203
204 QByteArray geometry;
183 bool first_frame = false; 205 bool first_frame = false;
184 206
185protected: 207protected:
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index f92a4b3c3..6209fff75 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -10,6 +10,7 @@
10#include "core/hle/service/acc/profile_manager.h" 10#include "core/hle/service/acc/profile_manager.h"
11#include "core/hle/service/hid/controllers/npad.h" 11#include "core/hle/service/hid/controllers/npad.h"
12#include "input_common/main.h" 12#include "input_common/main.h"
13#include "input_common/udp/client.h"
13#include "yuzu/configuration/config.h" 14#include "yuzu/configuration/config.h"
14#include "yuzu/uisettings.h" 15#include "yuzu/uisettings.h"
15 16
@@ -429,6 +430,16 @@ void Config::ReadControlValues() {
429 QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01")) 430 QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"))
430 .toString() 431 .toString()
431 .toStdString(); 432 .toStdString();
433 Settings::values.udp_input_address =
434 ReadSetting(QStringLiteral("udp_input_address"),
435 QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR))
436 .toString()
437 .toStdString();
438 Settings::values.udp_input_port = static_cast<u16>(
439 ReadSetting(QStringLiteral("udp_input_port"), InputCommon::CemuhookUDP::DEFAULT_PORT)
440 .toInt());
441 Settings::values.udp_pad_index =
442 static_cast<u8>(ReadSetting(QStringLiteral("udp_pad_index"), 0).toUInt());
432 443
433 qt_config->endGroup(); 444 qt_config->endGroup();
434} 445}
@@ -613,8 +624,13 @@ void Config::ReadPathValues() {
613void Config::ReadRendererValues() { 624void Config::ReadRendererValues() {
614 qt_config->beginGroup(QStringLiteral("Renderer")); 625 qt_config->beginGroup(QStringLiteral("Renderer"));
615 626
627 Settings::values.renderer_backend =
628 static_cast<Settings::RendererBackend>(ReadSetting(QStringLiteral("backend"), 0).toInt());
629 Settings::values.renderer_debug = ReadSetting(QStringLiteral("debug"), false).toBool();
630 Settings::values.vulkan_device = ReadSetting(QStringLiteral("vulkan_device"), 0).toInt();
616 Settings::values.resolution_factor = 631 Settings::values.resolution_factor =
617 ReadSetting(QStringLiteral("resolution_factor"), 1.0).toFloat(); 632 ReadSetting(QStringLiteral("resolution_factor"), 1.0).toFloat();
633 Settings::values.aspect_ratio = ReadSetting(QStringLiteral("aspect_ratio"), 0).toInt();
618 Settings::values.use_frame_limit = 634 Settings::values.use_frame_limit =
619 ReadSetting(QStringLiteral("use_frame_limit"), true).toBool(); 635 ReadSetting(QStringLiteral("use_frame_limit"), true).toBool();
620 Settings::values.frame_limit = ReadSetting(QStringLiteral("frame_limit"), 100).toInt(); 636 Settings::values.frame_limit = ReadSetting(QStringLiteral("frame_limit"), 100).toInt();
@@ -727,7 +743,6 @@ void Config::ReadUIValues() {
727void Config::ReadUIGamelistValues() { 743void Config::ReadUIGamelistValues() {
728 qt_config->beginGroup(QStringLiteral("UIGameList")); 744 qt_config->beginGroup(QStringLiteral("UIGameList"));
729 745
730 UISettings::values.show_unknown = ReadSetting(QStringLiteral("show_unknown"), true).toBool();
731 UISettings::values.show_add_ons = ReadSetting(QStringLiteral("show_add_ons"), true).toBool(); 746 UISettings::values.show_add_ons = ReadSetting(QStringLiteral("show_add_ons"), true).toBool();
732 UISettings::values.icon_size = ReadSetting(QStringLiteral("icon_size"), 64).toUInt(); 747 UISettings::values.icon_size = ReadSetting(QStringLiteral("icon_size"), 64).toUInt();
733 UISettings::values.row_1_text_id = ReadSetting(QStringLiteral("row_1_text_id"), 3).toUInt(); 748 UISettings::values.row_1_text_id = ReadSetting(QStringLiteral("row_1_text_id"), 3).toUInt();
@@ -911,6 +926,12 @@ void Config::SaveControlValues() {
911 QString::fromStdString(Settings::values.motion_device), 926 QString::fromStdString(Settings::values.motion_device),
912 QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01")); 927 QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"));
913 WriteSetting(QStringLiteral("keyboard_enabled"), Settings::values.keyboard_enabled, false); 928 WriteSetting(QStringLiteral("keyboard_enabled"), Settings::values.keyboard_enabled, false);
929 WriteSetting(QStringLiteral("udp_input_address"),
930 QString::fromStdString(Settings::values.udp_input_address),
931 QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR));
932 WriteSetting(QStringLiteral("udp_input_port"), Settings::values.udp_input_port,
933 InputCommon::CemuhookUDP::DEFAULT_PORT);
934 WriteSetting(QStringLiteral("udp_pad_index"), Settings::values.udp_pad_index, 0);
914 935
915 qt_config->endGroup(); 936 qt_config->endGroup();
916} 937}
@@ -1039,8 +1060,12 @@ void Config::SavePathValues() {
1039void Config::SaveRendererValues() { 1060void Config::SaveRendererValues() {
1040 qt_config->beginGroup(QStringLiteral("Renderer")); 1061 qt_config->beginGroup(QStringLiteral("Renderer"));
1041 1062
1063 WriteSetting(QStringLiteral("backend"), static_cast<int>(Settings::values.renderer_backend), 0);
1064 WriteSetting(QStringLiteral("debug"), Settings::values.renderer_debug, false);
1065 WriteSetting(QStringLiteral("vulkan_device"), Settings::values.vulkan_device, 0);
1042 WriteSetting(QStringLiteral("resolution_factor"), 1066 WriteSetting(QStringLiteral("resolution_factor"),
1043 static_cast<double>(Settings::values.resolution_factor), 1.0); 1067 static_cast<double>(Settings::values.resolution_factor), 1.0);
1068 WriteSetting(QStringLiteral("aspect_ratio"), Settings::values.aspect_ratio, 0);
1044 WriteSetting(QStringLiteral("use_frame_limit"), Settings::values.use_frame_limit, true); 1069 WriteSetting(QStringLiteral("use_frame_limit"), Settings::values.use_frame_limit, true);
1045 WriteSetting(QStringLiteral("frame_limit"), Settings::values.frame_limit, 100); 1070 WriteSetting(QStringLiteral("frame_limit"), Settings::values.frame_limit, 100);
1046 WriteSetting(QStringLiteral("use_disk_shader_cache"), Settings::values.use_disk_shader_cache, 1071 WriteSetting(QStringLiteral("use_disk_shader_cache"), Settings::values.use_disk_shader_cache,
@@ -1135,7 +1160,6 @@ void Config::SaveUIValues() {
1135void Config::SaveUIGamelistValues() { 1160void Config::SaveUIGamelistValues() {
1136 qt_config->beginGroup(QStringLiteral("UIGameList")); 1161 qt_config->beginGroup(QStringLiteral("UIGameList"));
1137 1162
1138 WriteSetting(QStringLiteral("show_unknown"), UISettings::values.show_unknown, true);
1139 WriteSetting(QStringLiteral("show_add_ons"), UISettings::values.show_add_ons, true); 1163 WriteSetting(QStringLiteral("show_add_ons"), UISettings::values.show_add_ons, true);
1140 WriteSetting(QStringLiteral("icon_size"), UISettings::values.icon_size, 64); 1164 WriteSetting(QStringLiteral("icon_size"), UISettings::values.icon_size, 64);
1141 WriteSetting(QStringLiteral("row_1_text_id"), UISettings::values.row_1_text_id, 3); 1165 WriteSetting(QStringLiteral("row_1_text_id"), UISettings::values.row_1_text_id, 3);
diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui
index 372427ae2..67b990f1a 100644
--- a/src/yuzu/configuration/configure.ui
+++ b/src/yuzu/configuration/configure.ui
@@ -48,7 +48,7 @@
48 <string>General</string> 48 <string>General</string>
49 </attribute> 49 </attribute>
50 </widget> 50 </widget>
51 <widget class="ConfigureGameList" name="gameListTab"> 51 <widget class="ConfigureUi" name="uiTab">
52 <attribute name="title"> 52 <attribute name="title">
53 <string>Game List</string> 53 <string>Game List</string>
54 </attribute> 54 </attribute>
@@ -166,9 +166,9 @@
166 <container>1</container> 166 <container>1</container>
167 </customwidget> 167 </customwidget>
168 <customwidget> 168 <customwidget>
169 <class>ConfigureGameList</class> 169 <class>ConfigureUi</class>
170 <extends>QWidget</extends> 170 <extends>QWidget</extends>
171 <header>configuration/configure_gamelist.h</header> 171 <header>configuration/configure_ui.h</header>
172 <container>1</container> 172 <container>1</container>
173 </customwidget> 173 </customwidget>
174 <customwidget> 174 <customwidget>
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp
index 90c1f9459..9631059c7 100644
--- a/src/yuzu/configuration/configure_debug.cpp
+++ b/src/yuzu/configuration/configure_debug.cpp
@@ -36,6 +36,8 @@ void ConfigureDebug::SetConfiguration() {
36 ui->homebrew_args_edit->setText(QString::fromStdString(Settings::values.program_args)); 36 ui->homebrew_args_edit->setText(QString::fromStdString(Settings::values.program_args));
37 ui->reporting_services->setChecked(Settings::values.reporting_services); 37 ui->reporting_services->setChecked(Settings::values.reporting_services);
38 ui->quest_flag->setChecked(Settings::values.quest_flag); 38 ui->quest_flag->setChecked(Settings::values.quest_flag);
39 ui->enable_graphics_debugging->setEnabled(!Core::System::GetInstance().IsPoweredOn());
40 ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug);
39} 41}
40 42
41void ConfigureDebug::ApplyConfiguration() { 43void ConfigureDebug::ApplyConfiguration() {
@@ -46,6 +48,7 @@ void ConfigureDebug::ApplyConfiguration() {
46 Settings::values.program_args = ui->homebrew_args_edit->text().toStdString(); 48 Settings::values.program_args = ui->homebrew_args_edit->text().toStdString();
47 Settings::values.reporting_services = ui->reporting_services->isChecked(); 49 Settings::values.reporting_services = ui->reporting_services->isChecked();
48 Settings::values.quest_flag = ui->quest_flag->isChecked(); 50 Settings::values.quest_flag = ui->quest_flag->isChecked();
51 Settings::values.renderer_debug = ui->enable_graphics_debugging->isChecked();
49 Debugger::ToggleConsole(); 52 Debugger::ToggleConsole();
50 Log::Filter filter; 53 Log::Filter filter;
51 filter.ParseFilterString(Settings::values.log_filter); 54 filter.ParseFilterString(Settings::values.log_filter);
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui
index ce49569bb..e028c4c80 100644
--- a/src/yuzu/configuration/configure_debug.ui
+++ b/src/yuzu/configuration/configure_debug.ui
@@ -7,7 +7,7 @@
7 <x>0</x> 7 <x>0</x>
8 <y>0</y> 8 <y>0</y>
9 <width>400</width> 9 <width>400</width>
10 <height>474</height> 10 <height>467</height>
11 </rect> 11 </rect>
12 </property> 12 </property>
13 <property name="windowTitle"> 13 <property name="windowTitle">
@@ -103,6 +103,80 @@
103 </item> 103 </item>
104 </layout> 104 </layout>
105 </item> 105 </item>
106 </layout>
107 </widget>
108 </item>
109 <item>
110 <widget class="QGroupBox" name="groupBox_3">
111 <property name="title">
112 <string>Homebrew</string>
113 </property>
114 <layout class="QVBoxLayout" name="verticalLayout_5">
115 <item>
116 <layout class="QHBoxLayout" name="horizontalLayout_4">
117 <item>
118 <widget class="QLabel" name="label_3">
119 <property name="text">
120 <string>Arguments String</string>
121 </property>
122 </widget>
123 </item>
124 <item>
125 <widget class="QLineEdit" name="homebrew_args_edit"/>
126 </item>
127 </layout>
128 </item>
129 </layout>
130 </widget>
131 </item>
132 <item>
133 <widget class="QGroupBox" name="groupBox_4">
134 <property name="title">
135 <string>Graphics</string>
136 </property>
137 <layout class="QVBoxLayout" name="verticalLayout_6">
138 <item>
139 <widget class="QCheckBox" name="enable_graphics_debugging">
140 <property name="enabled">
141 <bool>true</bool>
142 </property>
143 <property name="whatsThis">
144 <string>When checked, the graphics API enters in a slower debugging mode</string>
145 </property>
146 <property name="text">
147 <string>Enable Graphics Debugging</string>
148 </property>
149 </widget>
150 </item>
151 </layout>
152 </widget>
153 </item>
154 <item>
155 <widget class="QGroupBox" name="groupBox_5">
156 <property name="title">
157 <string>Dump</string>
158 </property>
159 <layout class="QVBoxLayout" name="verticalLayout_6">
160 <item>
161 <widget class="QCheckBox" name="dump_decompressed_nso">
162 <property name="whatsThis">
163 <string>When checked, any NSO yuzu tries to load or patch will be copied decompressed to the yuzu/dump directory.</string>
164 </property>
165 <property name="text">
166 <string>Dump Decompressed NSOs</string>
167 </property>
168 </widget>
169 </item>
170 <item>
171 <widget class="QCheckBox" name="dump_exefs">
172 <property name="whatsThis">
173 <string>When checked, any game that yuzu loads will have its ExeFS dumped to the yuzu/dump directory.</string>
174 </property>
175 <property name="text">
176 <string>Dump ExeFS</string>
177 </property>
178 </widget>
179 </item>
106 <item> 180 <item>
107 <widget class="QCheckBox" name="reporting_services"> 181 <widget class="QCheckBox" name="reporting_services">
108 <property name="text"> 182 <property name="text">
@@ -129,11 +203,11 @@
129 </widget> 203 </widget>
130 </item> 204 </item>
131 <item> 205 <item>
132 <widget class="QGroupBox" name="groupBox_5"> 206 <widget class="QGroupBox" name="groupBox_6">
133 <property name="title"> 207 <property name="title">
134 <string>Advanced</string> 208 <string>Advanced</string>
135 </property> 209 </property>
136 <layout class="QVBoxLayout" name="verticalLayout"> 210 <layout class="QVBoxLayout" name="verticalLayout_7">
137 <item> 211 <item>
138 <widget class="QCheckBox" name="quest_flag"> 212 <widget class="QCheckBox" name="quest_flag">
139 <property name="text"> 213 <property name="text">
@@ -145,29 +219,6 @@
145 </widget> 219 </widget>
146 </item> 220 </item>
147 <item> 221 <item>
148 <widget class="QGroupBox" name="groupBox_3">
149 <property name="title">
150 <string>Homebrew</string>
151 </property>
152 <layout class="QVBoxLayout" name="verticalLayout_5">
153 <item>
154 <layout class="QHBoxLayout" name="horizontalLayout_4">
155 <item>
156 <widget class="QLabel" name="label_3">
157 <property name="text">
158 <string>Arguments String</string>
159 </property>
160 </widget>
161 </item>
162 <item>
163 <widget class="QLineEdit" name="homebrew_args_edit"/>
164 </item>
165 </layout>
166 </item>
167 </layout>
168 </widget>
169 </item>
170 <item>
171 <spacer name="verticalSpacer"> 222 <spacer name="verticalSpacer">
172 <property name="orientation"> 223 <property name="orientation">
173 <enum>Qt::Vertical</enum> 224 <enum>Qt::Vertical</enum>
@@ -185,6 +236,19 @@
185 </item> 236 </item>
186 </layout> 237 </layout>
187 </widget> 238 </widget>
239 <tabstops>
240 <tabstop>toggle_gdbstub</tabstop>
241 <tabstop>gdbport_spinbox</tabstop>
242 <tabstop>log_filter_edit</tabstop>
243 <tabstop>toggle_console</tabstop>
244 <tabstop>open_log_button</tabstop>
245 <tabstop>homebrew_args_edit</tabstop>
246 <tabstop>enable_graphics_debugging</tabstop>
247 <tabstop>dump_decompressed_nso</tabstop>
248 <tabstop>dump_exefs</tabstop>
249 <tabstop>reporting_services</tabstop>
250 <tabstop>quest_flag</tabstop>
251 </tabstops>
188 <resources/> 252 <resources/>
189 <connections> 253 <connections>
190 <connection> 254 <connection>
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index 8497eaa14..db3b19352 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -34,7 +34,7 @@ void ConfigureDialog::SetConfiguration() {}
34 34
35void ConfigureDialog::ApplyConfiguration() { 35void ConfigureDialog::ApplyConfiguration() {
36 ui->generalTab->ApplyConfiguration(); 36 ui->generalTab->ApplyConfiguration();
37 ui->gameListTab->ApplyConfiguration(); 37 ui->uiTab->ApplyConfiguration();
38 ui->systemTab->ApplyConfiguration(); 38 ui->systemTab->ApplyConfiguration();
39 ui->profileManagerTab->ApplyConfiguration(); 39 ui->profileManagerTab->ApplyConfiguration();
40 ui->filesystemTab->applyConfiguration(); 40 ui->filesystemTab->applyConfiguration();
@@ -74,7 +74,7 @@ Q_DECLARE_METATYPE(QList<QWidget*>);
74 74
75void ConfigureDialog::PopulateSelectionList() { 75void ConfigureDialog::PopulateSelectionList() {
76 const std::array<std::pair<QString, QList<QWidget*>>, 5> items{ 76 const std::array<std::pair<QString, QList<QWidget*>>, 5> items{
77 {{tr("General"), {ui->generalTab, ui->webTab, ui->debugTab, ui->gameListTab}}, 77 {{tr("General"), {ui->generalTab, ui->webTab, ui->debugTab, ui->uiTab}},
78 {tr("System"), {ui->systemTab, ui->profileManagerTab, ui->serviceTab, ui->filesystemTab}}, 78 {tr("System"), {ui->systemTab, ui->profileManagerTab, ui->serviceTab, ui->filesystemTab}},
79 {tr("Graphics"), {ui->graphicsTab}}, 79 {tr("Graphics"), {ui->graphicsTab}},
80 {tr("Audio"), {ui->audioTab}}, 80 {tr("Audio"), {ui->audioTab}},
@@ -108,7 +108,7 @@ void ConfigureDialog::UpdateVisibleTabs() {
108 {ui->audioTab, tr("Audio")}, 108 {ui->audioTab, tr("Audio")},
109 {ui->debugTab, tr("Debug")}, 109 {ui->debugTab, tr("Debug")},
110 {ui->webTab, tr("Web")}, 110 {ui->webTab, tr("Web")},
111 {ui->gameListTab, tr("Game List")}, 111 {ui->uiTab, tr("UI")},
112 {ui->filesystemTab, tr("Filesystem")}, 112 {ui->filesystemTab, tr("Filesystem")},
113 {ui->serviceTab, tr("Services")}, 113 {ui->serviceTab, tr("Services")},
114 }; 114 };
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp
index 34e1d7fea..5ef927114 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -15,11 +15,6 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
15 15
16 ui->setupUi(this); 16 ui->setupUi(this);
17 17
18 for (const auto& theme : UISettings::themes) {
19 ui->theme_combobox->addItem(QString::fromUtf8(theme.first),
20 QString::fromUtf8(theme.second));
21 }
22
23 SetConfiguration(); 18 SetConfiguration();
24 19
25 connect(ui->toggle_frame_limit, &QCheckBox::toggled, ui->frame_limit, &QSpinBox::setEnabled); 20 connect(ui->toggle_frame_limit, &QCheckBox::toggled, ui->frame_limit, &QSpinBox::setEnabled);
@@ -30,7 +25,6 @@ ConfigureGeneral::~ConfigureGeneral() = default;
30void ConfigureGeneral::SetConfiguration() { 25void ConfigureGeneral::SetConfiguration() {
31 ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing); 26 ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);
32 ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot); 27 ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot);
33 ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme));
34 ui->toggle_background_pause->setChecked(UISettings::values.pause_when_in_background); 28 ui->toggle_background_pause->setChecked(UISettings::values.pause_when_in_background);
35 29
36 ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit); 30 ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit);
@@ -41,8 +35,6 @@ void ConfigureGeneral::SetConfiguration() {
41void ConfigureGeneral::ApplyConfiguration() { 35void ConfigureGeneral::ApplyConfiguration() {
42 UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); 36 UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
43 UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked(); 37 UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked();
44 UISettings::values.theme =
45 ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString();
46 UISettings::values.pause_when_in_background = ui->toggle_background_pause->isChecked(); 38 UISettings::values.pause_when_in_background = ui->toggle_background_pause->isChecked();
47 39
48 Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked(); 40 Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked();
diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui
index 26b3486ff..857119bb3 100644
--- a/src/yuzu/configuration/configure_general.ui
+++ b/src/yuzu/configuration/configure_general.ui
@@ -65,39 +65,12 @@
65 </property> 65 </property>
66 </widget> 66 </widget>
67 </item> 67 </item>
68 <item>
69 <widget class="QCheckBox" name="toggle_background_pause">
70 <property name="text">
71 <string>Pause emulation when in background</string>
72 </property>
73 </widget>
74 </item>
75 </layout>
76 </item>
77 </layout>
78 </widget>
79 </item>
80 <item>
81 <widget class="QGroupBox" name="theme_group_box">
82 <property name="title">
83 <string>Theme</string>
84 </property>
85 <layout class="QHBoxLayout" name="theme_qhbox_layout">
86 <item>
87 <layout class="QVBoxLayout" name="theme_qvbox_layout">
88 <item> 68 <item>
89 <layout class="QHBoxLayout" name="theme_qhbox_layout_2"> 69 <widget class="QCheckBox" name="toggle_background_pause">
90 <item> 70 <property name="text">
91 <widget class="QLabel" name="theme_label"> 71 <string>Pause emulation when in background</string>
92 <property name="text"> 72 </property>
93 <string>Theme:</string> 73 </widget>
94 </property>
95 </widget>
96 </item>
97 <item>
98 <widget class="QComboBox" name="theme_combobox"/>
99 </item>
100 </layout>
101 </item> 74 </item>
102 </layout> 75 </layout>
103 </item> 76 </item>
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index 2c9e322c9..ea899c080 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -3,6 +3,13 @@
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <QColorDialog> 5#include <QColorDialog>
6#include <QComboBox>
7#ifdef HAS_VULKAN
8#include <QVulkanInstance>
9#endif
10
11#include "common/common_types.h"
12#include "common/logging/log.h"
6#include "core/core.h" 13#include "core/core.h"
7#include "core/settings.h" 14#include "core/settings.h"
8#include "ui_configure_graphics.h" 15#include "ui_configure_graphics.h"
@@ -51,10 +58,18 @@ Resolution FromResolutionFactor(float factor) {
51 58
52ConfigureGraphics::ConfigureGraphics(QWidget* parent) 59ConfigureGraphics::ConfigureGraphics(QWidget* parent)
53 : QWidget(parent), ui(new Ui::ConfigureGraphics) { 60 : QWidget(parent), ui(new Ui::ConfigureGraphics) {
61 vulkan_device = Settings::values.vulkan_device;
62 RetrieveVulkanDevices();
63
54 ui->setupUi(this); 64 ui->setupUi(this);
55 65
56 SetConfiguration(); 66 SetConfiguration();
57 67
68 connect(ui->api, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
69 [this] { UpdateDeviceComboBox(); });
70 connect(ui->device, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this,
71 [this](int device) { UpdateDeviceSelection(device); });
72
58 connect(ui->bg_button, &QPushButton::clicked, this, [this] { 73 connect(ui->bg_button, &QPushButton::clicked, this, [this] {
59 const QColor new_bg_color = QColorDialog::getColor(bg_color); 74 const QColor new_bg_color = QColorDialog::getColor(bg_color);
60 if (!new_bg_color.isValid()) { 75 if (!new_bg_color.isValid()) {
@@ -64,13 +79,25 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent)
64 }); 79 });
65} 80}
66 81
82void ConfigureGraphics::UpdateDeviceSelection(int device) {
83 if (device == -1) {
84 return;
85 }
86 if (GetCurrentGraphicsBackend() == Settings::RendererBackend::Vulkan) {
87 vulkan_device = device;
88 }
89}
90
67ConfigureGraphics::~ConfigureGraphics() = default; 91ConfigureGraphics::~ConfigureGraphics() = default;
68 92
69void ConfigureGraphics::SetConfiguration() { 93void ConfigureGraphics::SetConfiguration() {
70 const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn(); 94 const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn();
71 95
96 ui->api->setEnabled(runtime_lock);
97 ui->api->setCurrentIndex(static_cast<int>(Settings::values.renderer_backend));
72 ui->resolution_factor_combobox->setCurrentIndex( 98 ui->resolution_factor_combobox->setCurrentIndex(
73 static_cast<int>(FromResolutionFactor(Settings::values.resolution_factor))); 99 static_cast<int>(FromResolutionFactor(Settings::values.resolution_factor)));
100 ui->aspect_ratio_combobox->setCurrentIndex(Settings::values.aspect_ratio);
74 ui->use_disk_shader_cache->setEnabled(runtime_lock); 101 ui->use_disk_shader_cache->setEnabled(runtime_lock);
75 ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache); 102 ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache);
76 ui->use_accurate_gpu_emulation->setChecked(Settings::values.use_accurate_gpu_emulation); 103 ui->use_accurate_gpu_emulation->setChecked(Settings::values.use_accurate_gpu_emulation);
@@ -80,11 +107,15 @@ void ConfigureGraphics::SetConfiguration() {
80 ui->force_30fps_mode->setChecked(Settings::values.force_30fps_mode); 107 ui->force_30fps_mode->setChecked(Settings::values.force_30fps_mode);
81 UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green, 108 UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green,
82 Settings::values.bg_blue)); 109 Settings::values.bg_blue));
110 UpdateDeviceComboBox();
83} 111}
84 112
85void ConfigureGraphics::ApplyConfiguration() { 113void ConfigureGraphics::ApplyConfiguration() {
114 Settings::values.renderer_backend = GetCurrentGraphicsBackend();
115 Settings::values.vulkan_device = vulkan_device;
86 Settings::values.resolution_factor = 116 Settings::values.resolution_factor =
87 ToResolutionFactor(static_cast<Resolution>(ui->resolution_factor_combobox->currentIndex())); 117 ToResolutionFactor(static_cast<Resolution>(ui->resolution_factor_combobox->currentIndex()));
118 Settings::values.aspect_ratio = ui->aspect_ratio_combobox->currentIndex();
88 Settings::values.use_disk_shader_cache = ui->use_disk_shader_cache->isChecked(); 119 Settings::values.use_disk_shader_cache = ui->use_disk_shader_cache->isChecked();
89 Settings::values.use_accurate_gpu_emulation = ui->use_accurate_gpu_emulation->isChecked(); 120 Settings::values.use_accurate_gpu_emulation = ui->use_accurate_gpu_emulation->isChecked();
90 Settings::values.use_asynchronous_gpu_emulation = 121 Settings::values.use_asynchronous_gpu_emulation =
@@ -116,3 +147,68 @@ void ConfigureGraphics::UpdateBackgroundColorButton(QColor color) {
116 const QIcon color_icon(pixmap); 147 const QIcon color_icon(pixmap);
117 ui->bg_button->setIcon(color_icon); 148 ui->bg_button->setIcon(color_icon);
118} 149}
150
151void ConfigureGraphics::UpdateDeviceComboBox() {
152 ui->device->clear();
153
154 bool enabled = false;
155 switch (GetCurrentGraphicsBackend()) {
156 case Settings::RendererBackend::OpenGL:
157 ui->device->addItem(tr("OpenGL Graphics Device"));
158 enabled = false;
159 break;
160 case Settings::RendererBackend::Vulkan:
161 for (const auto device : vulkan_devices) {
162 ui->device->addItem(device);
163 }
164 ui->device->setCurrentIndex(vulkan_device);
165 enabled = !vulkan_devices.empty();
166 break;
167 }
168 ui->device->setEnabled(enabled && !Core::System::GetInstance().IsPoweredOn());
169}
170
171void ConfigureGraphics::RetrieveVulkanDevices() {
172#ifdef HAS_VULKAN
173 QVulkanInstance instance;
174 instance.setApiVersion(QVersionNumber(1, 1, 0));
175 if (!instance.create()) {
176 LOG_INFO(Frontend, "Vulkan 1.1 not available");
177 return;
178 }
179 const auto vkEnumeratePhysicalDevices{reinterpret_cast<PFN_vkEnumeratePhysicalDevices>(
180 instance.getInstanceProcAddr("vkEnumeratePhysicalDevices"))};
181 if (vkEnumeratePhysicalDevices == nullptr) {
182 LOG_INFO(Frontend, "Failed to get pointer to vkEnumeratePhysicalDevices");
183 return;
184 }
185 u32 physical_device_count;
186 if (vkEnumeratePhysicalDevices(instance.vkInstance(), &physical_device_count, nullptr) !=
187 VK_SUCCESS) {
188 LOG_INFO(Frontend, "Failed to get physical devices count");
189 return;
190 }
191 std::vector<VkPhysicalDevice> physical_devices(physical_device_count);
192 if (vkEnumeratePhysicalDevices(instance.vkInstance(), &physical_device_count,
193 physical_devices.data()) != VK_SUCCESS) {
194 LOG_INFO(Frontend, "Failed to get physical devices");
195 return;
196 }
197
198 const auto vkGetPhysicalDeviceProperties{reinterpret_cast<PFN_vkGetPhysicalDeviceProperties>(
199 instance.getInstanceProcAddr("vkGetPhysicalDeviceProperties"))};
200 if (vkGetPhysicalDeviceProperties == nullptr) {
201 LOG_INFO(Frontend, "Failed to get pointer to vkGetPhysicalDeviceProperties");
202 return;
203 }
204 for (const auto physical_device : physical_devices) {
205 VkPhysicalDeviceProperties properties;
206 vkGetPhysicalDeviceProperties(physical_device, &properties);
207 vulkan_devices.push_back(QString::fromUtf8(properties.deviceName));
208 }
209#endif
210}
211
212Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const {
213 return static_cast<Settings::RendererBackend>(ui->api->currentIndex());
214}
diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h
index fae28d98e..7e0596d9c 100644
--- a/src/yuzu/configuration/configure_graphics.h
+++ b/src/yuzu/configuration/configure_graphics.h
@@ -5,7 +5,10 @@
5#pragma once 5#pragma once
6 6
7#include <memory> 7#include <memory>
8#include <vector>
9#include <QString>
8#include <QWidget> 10#include <QWidget>
11#include "core/settings.h"
9 12
10namespace Ui { 13namespace Ui {
11class ConfigureGraphics; 14class ConfigureGraphics;
@@ -27,7 +30,16 @@ private:
27 void SetConfiguration(); 30 void SetConfiguration();
28 31
29 void UpdateBackgroundColorButton(QColor color); 32 void UpdateBackgroundColorButton(QColor color);
33 void UpdateDeviceComboBox();
34 void UpdateDeviceSelection(int device);
35
36 void RetrieveVulkanDevices();
37
38 Settings::RendererBackend GetCurrentGraphicsBackend() const;
30 39
31 std::unique_ptr<Ui::ConfigureGraphics> ui; 40 std::unique_ptr<Ui::ConfigureGraphics> ui;
32 QColor bg_color; 41 QColor bg_color;
42
43 std::vector<QString> vulkan_devices;
44 u32 vulkan_device{};
33}; 45};
diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui
index 0309ee300..db60426ab 100644
--- a/src/yuzu/configuration/configure_graphics.ui
+++ b/src/yuzu/configuration/configure_graphics.ui
@@ -7,21 +7,69 @@
7 <x>0</x> 7 <x>0</x>
8 <y>0</y> 8 <y>0</y>
9 <width>400</width> 9 <width>400</width>
10 <height>300</height> 10 <height>321</height>
11 </rect> 11 </rect>
12 </property> 12 </property>
13 <property name="windowTitle"> 13 <property name="windowTitle">
14 <string>Form</string> 14 <string>Form</string>
15 </property> 15 </property>
16 <layout class="QVBoxLayout" name="verticalLayout"> 16 <layout class="QVBoxLayout" name="verticalLayout_1">
17 <item> 17 <item>
18 <layout class="QVBoxLayout" name="verticalLayout_3"> 18 <layout class="QVBoxLayout" name="verticalLayout_2">
19 <item>
20 <widget class="QGroupBox" name="groupBox_2">
21 <property name="title">
22 <string>API Settings</string>
23 </property>
24 <layout class="QVBoxLayout" name="verticalLayout_3">
25 <item>
26 <layout class="QHBoxLayout" name="horizontalLayout_4">
27 <item>
28 <widget class="QLabel" name="label_2">
29 <property name="text">
30 <string>API:</string>
31 </property>
32 </widget>
33 </item>
34 <item>
35 <widget class="QComboBox" name="api">
36 <item>
37 <property name="text">
38 <string notr="true">OpenGL</string>
39 </property>
40 </item>
41 <item>
42 <property name="text">
43 <string notr="true">Vulkan</string>
44 </property>
45 </item>
46 </widget>
47 </item>
48 </layout>
49 </item>
50 <item>
51 <layout class="QHBoxLayout" name="horizontalLayout_5">
52 <item>
53 <widget class="QLabel" name="label_3">
54 <property name="text">
55 <string>Device:</string>
56 </property>
57 </widget>
58 </item>
59 <item>
60 <widget class="QComboBox" name="device"/>
61 </item>
62 </layout>
63 </item>
64 </layout>
65 </widget>
66 </item>
19 <item> 67 <item>
20 <widget class="QGroupBox" name="groupBox"> 68 <widget class="QGroupBox" name="groupBox">
21 <property name="title"> 69 <property name="title">
22 <string>Graphics</string> 70 <string>Graphics Settings</string>
23 </property> 71 </property>
24 <layout class="QVBoxLayout" name="verticalLayout_2"> 72 <layout class="QVBoxLayout" name="verticalLayout_4">
25 <item> 73 <item>
26 <widget class="QCheckBox" name="use_disk_shader_cache"> 74 <widget class="QCheckBox" name="use_disk_shader_cache">
27 <property name="text"> 75 <property name="text">
@@ -30,16 +78,16 @@
30 </widget> 78 </widget>
31 </item> 79 </item>
32 <item> 80 <item>
33 <widget class="QCheckBox" name="use_accurate_gpu_emulation"> 81 <widget class="QCheckBox" name="use_asynchronous_gpu_emulation">
34 <property name="text"> 82 <property name="text">
35 <string>Use accurate GPU emulation (slow)</string> 83 <string>Use asynchronous GPU emulation</string>
36 </property> 84 </property>
37 </widget> 85 </widget>
38 </item> 86 </item>
39 <item> 87 <item>
40 <widget class="QCheckBox" name="use_asynchronous_gpu_emulation"> 88 <widget class="QCheckBox" name="use_accurate_gpu_emulation">
41 <property name="text"> 89 <property name="text">
42 <string>Use asynchronous GPU emulation</string> 90 <string>Use accurate GPU emulation (slow)</string>
43 </property> 91 </property>
44 </widget> 92 </widget>
45 </item> 93 </item>
@@ -51,11 +99,11 @@
51 </widget> 99 </widget>
52 </item> 100 </item>
53 <item> 101 <item>
54 <layout class="QHBoxLayout" name="horizontalLayout"> 102 <layout class="QHBoxLayout" name="horizontalLayout_2">
55 <item> 103 <item>
56 <widget class="QLabel" name="label"> 104 <widget class="QLabel" name="label">
57 <property name="text"> 105 <property name="text">
58 <string>Internal Resolution</string> 106 <string>Internal Resolution:</string>
59 </property> 107 </property>
60 </widget> 108 </widget>
61 </item> 109 </item>
@@ -93,6 +141,41 @@
93 <item> 141 <item>
94 <layout class="QHBoxLayout" name="horizontalLayout_6"> 142 <layout class="QHBoxLayout" name="horizontalLayout_6">
95 <item> 143 <item>
144 <widget class="QLabel" name="ar_label">
145 <property name="text">
146 <string>Aspect Ratio:</string>
147 </property>
148 </widget>
149 </item>
150 <item>
151 <widget class="QComboBox" name="aspect_ratio_combobox">
152 <item>
153 <property name="text">
154 <string>Default (16:9)</string>
155 </property>
156 </item>
157 <item>
158 <property name="text">
159 <string>Force 4:3</string>
160 </property>
161 </item>
162 <item>
163 <property name="text">
164 <string>Force 21:9</string>
165 </property>
166 </item>
167 <item>
168 <property name="text">
169 <string>Stretch to Window</string>
170 </property>
171 </item>
172 </widget>
173 </item>
174 </layout>
175 </item>
176 <item>
177 <layout class="QHBoxLayout" name="horizontalLayout_3">
178 <item>
96 <widget class="QLabel" name="bg_label"> 179 <widget class="QLabel" name="bg_label">
97 <property name="text"> 180 <property name="text">
98 <string>Background Color:</string> 181 <string>Background Color:</string>
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index 67c9a7c6d..96dec50e2 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -236,6 +236,8 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
236 widget->setVisible(false); 236 widget->setVisible(false);
237 237
238 analog_map_stick = {ui->buttonLStickAnalog, ui->buttonRStickAnalog}; 238 analog_map_stick = {ui->buttonLStickAnalog, ui->buttonRStickAnalog};
239 analog_map_deadzone = {ui->sliderLStickDeadzone, ui->sliderRStickDeadzone};
240 analog_map_deadzone_label = {ui->labelLStickDeadzone, ui->labelRStickDeadzone};
239 241
240 for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { 242 for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
241 auto* const button = button_map[button_id]; 243 auto* const button = button_map[button_id];
@@ -326,6 +328,11 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
326 InputCommon::Polling::DeviceType::Analog); 328 InputCommon::Polling::DeviceType::Analog);
327 } 329 }
328 }); 330 });
331 connect(analog_map_deadzone[analog_id], &QSlider::valueChanged, [=] {
332 const float deadzone = analog_map_deadzone[analog_id]->value() / 100.0f;
333 analog_map_deadzone_label[analog_id]->setText(tr("Deadzone: %1").arg(deadzone));
334 analogs_param[analog_id].Set("deadzone", deadzone);
335 });
329 } 336 }
330 337
331 connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); }); 338 connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); });
@@ -484,7 +491,7 @@ void ConfigureInputPlayer::ClearAll() {
484 continue; 491 continue;
485 } 492 }
486 493
487 analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]); 494 analogs_param[analog_id].Clear();
488 } 495 }
489 } 496 }
490 497
@@ -508,6 +515,23 @@ void ConfigureInputPlayer::UpdateButtonLabels() {
508 AnalogToText(analogs_param[analog_id], analog_sub_buttons[sub_button_id])); 515 AnalogToText(analogs_param[analog_id], analog_sub_buttons[sub_button_id]));
509 } 516 }
510 analog_map_stick[analog_id]->setText(tr("Set Analog Stick")); 517 analog_map_stick[analog_id]->setText(tr("Set Analog Stick"));
518
519 auto& param = analogs_param[analog_id];
520 auto* const analog_deadzone_slider = analog_map_deadzone[analog_id];
521 auto* const analog_deadzone_label = analog_map_deadzone_label[analog_id];
522
523 if (param.Has("engine") && param.Get("engine", "") == "sdl") {
524 if (!param.Has("deadzone")) {
525 param.Set("deadzone", 0.1f);
526 }
527
528 analog_deadzone_slider->setValue(static_cast<int>(param.Get("deadzone", 0.1f) * 100));
529 analog_deadzone_slider->setVisible(true);
530 analog_deadzone_label->setVisible(true);
531 } else {
532 analog_deadzone_slider->setVisible(false);
533 analog_deadzone_label->setVisible(false);
534 }
511 } 535 }
512} 536}
513 537
diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h
index c66027651..045704e47 100644
--- a/src/yuzu/configuration/configure_input_player.h
+++ b/src/yuzu/configuration/configure_input_player.h
@@ -97,6 +97,8 @@ private:
97 /// Analog inputs are also represented each with a single button, used to configure with an 97 /// Analog inputs are also represented each with a single button, used to configure with an
98 /// actual analog stick 98 /// actual analog stick
99 std::array<QPushButton*, Settings::NativeAnalog::NumAnalogs> analog_map_stick; 99 std::array<QPushButton*, Settings::NativeAnalog::NumAnalogs> analog_map_stick;
100 std::array<QSlider*, Settings::NativeAnalog::NumAnalogs> analog_map_deadzone;
101 std::array<QLabel*, Settings::NativeAnalog::NumAnalogs> analog_map_deadzone_label;
100 102
101 static const std::array<std::string, ANALOG_SUB_BUTTONS_NUM> analog_sub_buttons; 103 static const std::array<std::string, ANALOG_SUB_BUTTONS_NUM> analog_sub_buttons;
102 104
diff --git a/src/yuzu/configuration/configure_input_player.ui b/src/yuzu/configuration/configure_input_player.ui
index 42db020be..1556481d0 100644
--- a/src/yuzu/configuration/configure_input_player.ui
+++ b/src/yuzu/configuration/configure_input_player.ui
@@ -170,6 +170,44 @@
170 </item> 170 </item>
171 </layout> 171 </layout>
172 </item> 172 </item>
173 <item row="4" column="0" colspan="2">
174 <layout class="QVBoxLayout" name="sliderRStickDeadzoneVerticalLayout">
175 <item>
176 <layout class="QHBoxLayout" name="sliderRStickDeadzoneHorizontalLayout">
177 <item>
178 <widget class="QLabel" name="labelRStickDeadzone">
179 <property name="text">
180 <string>Deadzone: 0</string>
181 </property>
182 <property name="alignment">
183 <enum>Qt::AlignHCenter</enum>
184 </property>
185 </widget>
186 </item>
187 </layout>
188 </item>
189 <item>
190 <widget class="QSlider" name="sliderRStickDeadzone">
191 <property name="orientation">
192 <enum>Qt::Horizontal</enum>
193 </property>
194 </widget>
195 </item>
196 </layout>
197 </item>
198 <item row="5" column="0">
199 <spacer name="RStick_verticalSpacer">
200 <property name="orientation">
201 <enum>Qt::Vertical</enum>
202 </property>
203 <property name="sizeHint" stdset="0">
204 <size>
205 <width>0</width>
206 <height>0</height>
207 </size>
208 </property>
209 </spacer>
210 </item>
173 </layout> 211 </layout>
174 </widget> 212 </widget>
175 </item> 213 </item>
@@ -745,6 +783,47 @@
745 </item> 783 </item>
746 </layout> 784 </layout>
747 </item> 785 </item>
786 <item row="5" column="1" colspan="2">
787 <layout class="QVBoxLayout" name="sliderLStickDeadzoneVerticalLayout">
788 <property name="sizeConstraint">
789 <enum>QLayout::SetDefaultConstraint</enum>
790 </property>
791 <item>
792 <layout class="QHBoxLayout" name="sliderLStickDeadzoneHorizontalLayout">
793 <item>
794 <widget class="QLabel" name="labelLStickDeadzone">
795 <property name="text">
796 <string>Deadzone: 0</string>
797 </property>
798 <property name="alignment">
799 <enum>Qt::AlignHCenter</enum>
800 </property>
801 </widget>
802 </item>
803 </layout>
804 </item>
805 <item>
806 <widget class="QSlider" name="sliderLStickDeadzone">
807 <property name="orientation">
808 <enum>Qt::Horizontal</enum>
809 </property>
810 </widget>
811 </item>
812 </layout>
813 </item>
814 <item row="6" column="1">
815 <spacer name="LStick_verticalSpacer">
816 <property name="orientation">
817 <enum>Qt::Vertical</enum>
818 </property>
819 <property name="sizeHint" stdset="0">
820 <size>
821 <width>0</width>
822 <height>0</height>
823 </size>
824 </property>
825 </spacer>
826 </item>
748 </layout> 827 </layout>
749 </widget> 828 </widget>
750 </item> 829 </item>
diff --git a/src/yuzu/configuration/configure_gamelist.cpp b/src/yuzu/configuration/configure_ui.cpp
index e43e84d39..94424ee44 100644
--- a/src/yuzu/configuration/configure_gamelist.cpp
+++ b/src/yuzu/configuration/configure_ui.cpp
@@ -7,8 +7,8 @@
7 7
8#include "common/common_types.h" 8#include "common/common_types.h"
9#include "core/settings.h" 9#include "core/settings.h"
10#include "ui_configure_gamelist.h" 10#include "ui_configure_ui.h"
11#include "yuzu/configuration/configure_gamelist.h" 11#include "yuzu/configuration/configure_ui.h"
12#include "yuzu/uisettings.h" 12#include "yuzu/uisettings.h"
13 13
14namespace { 14namespace {
@@ -26,36 +26,39 @@ constexpr std::array row_text_names{
26}; 26};
27} // Anonymous namespace 27} // Anonymous namespace
28 28
29ConfigureGameList::ConfigureGameList(QWidget* parent) 29ConfigureUi::ConfigureUi(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureUi) {
30 : QWidget(parent), ui(new Ui::ConfigureGameList) {
31 ui->setupUi(this); 30 ui->setupUi(this);
32 31
32 for (const auto& theme : UISettings::themes) {
33 ui->theme_combobox->addItem(QString::fromUtf8(theme.first),
34 QString::fromUtf8(theme.second));
35 }
36
33 InitializeIconSizeComboBox(); 37 InitializeIconSizeComboBox();
34 InitializeRowComboBoxes(); 38 InitializeRowComboBoxes();
35 39
36 SetConfiguration(); 40 SetConfiguration();
37 41
38 // Force game list reload if any of the relevant settings are changed. 42 // Force game list reload if any of the relevant settings are changed.
39 connect(ui->show_unknown, &QCheckBox::stateChanged, this,
40 &ConfigureGameList::RequestGameListUpdate);
41 connect(ui->icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, 43 connect(ui->icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
42 &ConfigureGameList::RequestGameListUpdate); 44 &ConfigureUi::RequestGameListUpdate);
43 connect(ui->row_1_text_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, 45 connect(ui->row_1_text_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
44 &ConfigureGameList::RequestGameListUpdate); 46 &ConfigureUi::RequestGameListUpdate);
45 connect(ui->row_2_text_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, 47 connect(ui->row_2_text_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
46 &ConfigureGameList::RequestGameListUpdate); 48 &ConfigureUi::RequestGameListUpdate);
47 49
48 // Update text ComboBoxes after user interaction. 50 // Update text ComboBoxes after user interaction.
49 connect(ui->row_1_text_combobox, QOverload<int>::of(&QComboBox::activated), 51 connect(ui->row_1_text_combobox, QOverload<int>::of(&QComboBox::activated),
50 [=]() { ConfigureGameList::UpdateSecondRowComboBox(); }); 52 [=]() { ConfigureUi::UpdateSecondRowComboBox(); });
51 connect(ui->row_2_text_combobox, QOverload<int>::of(&QComboBox::activated), 53 connect(ui->row_2_text_combobox, QOverload<int>::of(&QComboBox::activated),
52 [=]() { ConfigureGameList::UpdateFirstRowComboBox(); }); 54 [=]() { ConfigureUi::UpdateFirstRowComboBox(); });
53} 55}
54 56
55ConfigureGameList::~ConfigureGameList() = default; 57ConfigureUi::~ConfigureUi() = default;
56 58
57void ConfigureGameList::ApplyConfiguration() { 59void ConfigureUi::ApplyConfiguration() {
58 UISettings::values.show_unknown = ui->show_unknown->isChecked(); 60 UISettings::values.theme =
61 ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString();
59 UISettings::values.show_add_ons = ui->show_add_ons->isChecked(); 62 UISettings::values.show_add_ons = ui->show_add_ons->isChecked();
60 UISettings::values.icon_size = ui->icon_size_combobox->currentData().toUInt(); 63 UISettings::values.icon_size = ui->icon_size_combobox->currentData().toUInt();
61 UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt(); 64 UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt();
@@ -63,18 +66,18 @@ void ConfigureGameList::ApplyConfiguration() {
63 Settings::Apply(); 66 Settings::Apply();
64} 67}
65 68
66void ConfigureGameList::RequestGameListUpdate() { 69void ConfigureUi::RequestGameListUpdate() {
67 UISettings::values.is_game_list_reload_pending.exchange(true); 70 UISettings::values.is_game_list_reload_pending.exchange(true);
68} 71}
69 72
70void ConfigureGameList::SetConfiguration() { 73void ConfigureUi::SetConfiguration() {
71 ui->show_unknown->setChecked(UISettings::values.show_unknown); 74 ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme));
72 ui->show_add_ons->setChecked(UISettings::values.show_add_ons); 75 ui->show_add_ons->setChecked(UISettings::values.show_add_ons);
73 ui->icon_size_combobox->setCurrentIndex( 76 ui->icon_size_combobox->setCurrentIndex(
74 ui->icon_size_combobox->findData(UISettings::values.icon_size)); 77 ui->icon_size_combobox->findData(UISettings::values.icon_size));
75} 78}
76 79
77void ConfigureGameList::changeEvent(QEvent* event) { 80void ConfigureUi::changeEvent(QEvent* event) {
78 if (event->type() == QEvent::LanguageChange) { 81 if (event->type() == QEvent::LanguageChange) {
79 RetranslateUI(); 82 RetranslateUI();
80 } 83 }
@@ -82,7 +85,7 @@ void ConfigureGameList::changeEvent(QEvent* event) {
82 QWidget::changeEvent(event); 85 QWidget::changeEvent(event);
83} 86}
84 87
85void ConfigureGameList::RetranslateUI() { 88void ConfigureUi::RetranslateUI() {
86 ui->retranslateUi(this); 89 ui->retranslateUi(this);
87 90
88 for (int i = 0; i < ui->icon_size_combobox->count(); i++) { 91 for (int i = 0; i < ui->icon_size_combobox->count(); i++) {
@@ -97,18 +100,18 @@ void ConfigureGameList::RetranslateUI() {
97 } 100 }
98} 101}
99 102
100void ConfigureGameList::InitializeIconSizeComboBox() { 103void ConfigureUi::InitializeIconSizeComboBox() {
101 for (const auto& size : default_icon_sizes) { 104 for (const auto& size : default_icon_sizes) {
102 ui->icon_size_combobox->addItem(QString::fromUtf8(size.second), size.first); 105 ui->icon_size_combobox->addItem(QString::fromUtf8(size.second), size.first);
103 } 106 }
104} 107}
105 108
106void ConfigureGameList::InitializeRowComboBoxes() { 109void ConfigureUi::InitializeRowComboBoxes() {
107 UpdateFirstRowComboBox(true); 110 UpdateFirstRowComboBox(true);
108 UpdateSecondRowComboBox(true); 111 UpdateSecondRowComboBox(true);
109} 112}
110 113
111void ConfigureGameList::UpdateFirstRowComboBox(bool init) { 114void ConfigureUi::UpdateFirstRowComboBox(bool init) {
112 const int currentIndex = 115 const int currentIndex =
113 init ? UISettings::values.row_1_text_id 116 init ? UISettings::values.row_1_text_id
114 : ui->row_1_text_combobox->findData(ui->row_1_text_combobox->currentData()); 117 : ui->row_1_text_combobox->findData(ui->row_1_text_combobox->currentData());
@@ -127,7 +130,7 @@ void ConfigureGameList::UpdateFirstRowComboBox(bool init) {
127 ui->row_1_text_combobox->findData(ui->row_2_text_combobox->currentData())); 130 ui->row_1_text_combobox->findData(ui->row_2_text_combobox->currentData()));
128} 131}
129 132
130void ConfigureGameList::UpdateSecondRowComboBox(bool init) { 133void ConfigureUi::UpdateSecondRowComboBox(bool init) {
131 const int currentIndex = 134 const int currentIndex =
132 init ? UISettings::values.row_2_text_id 135 init ? UISettings::values.row_2_text_id
133 : ui->row_2_text_combobox->findData(ui->row_2_text_combobox->currentData()); 136 : ui->row_2_text_combobox->findData(ui->row_2_text_combobox->currentData());
diff --git a/src/yuzu/configuration/configure_gamelist.h b/src/yuzu/configuration/configure_ui.h
index ecd3fa174..d471afe99 100644
--- a/src/yuzu/configuration/configure_gamelist.h
+++ b/src/yuzu/configuration/configure_ui.h
@@ -8,15 +8,15 @@
8#include <QWidget> 8#include <QWidget>
9 9
10namespace Ui { 10namespace Ui {
11class ConfigureGameList; 11class ConfigureUi;
12} 12}
13 13
14class ConfigureGameList : public QWidget { 14class ConfigureUi : public QWidget {
15 Q_OBJECT 15 Q_OBJECT
16 16
17public: 17public:
18 explicit ConfigureGameList(QWidget* parent = nullptr); 18 explicit ConfigureUi(QWidget* parent = nullptr);
19 ~ConfigureGameList() override; 19 ~ConfigureUi() override;
20 20
21 void ApplyConfiguration(); 21 void ApplyConfiguration();
22 22
@@ -34,5 +34,5 @@ private:
34 void UpdateFirstRowComboBox(bool init = false); 34 void UpdateFirstRowComboBox(bool init = false);
35 void UpdateSecondRowComboBox(bool init = false); 35 void UpdateSecondRowComboBox(bool init = false);
36 36
37 std::unique_ptr<Ui::ConfigureGameList> ui; 37 std::unique_ptr<Ui::ConfigureUi> ui;
38}; 38};
diff --git a/src/yuzu/configuration/configure_gamelist.ui b/src/yuzu/configuration/configure_ui.ui
index 7a69377e7..bd5c5d3c2 100644
--- a/src/yuzu/configuration/configure_gamelist.ui
+++ b/src/yuzu/configuration/configure_ui.ui
@@ -1,7 +1,7 @@
1<?xml version="1.0" encoding="UTF-8"?> 1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0"> 2<ui version="4.0">
3 <class>ConfigureGameList</class> 3 <class>ConfigureUi</class>
4 <widget class="QWidget" name="ConfigureGameList"> 4 <widget class="QWidget" name="ConfigureUi">
5 <property name="geometry"> 5 <property name="geometry">
6 <rect> 6 <rect>
7 <x>0</x> 7 <x>0</x>
@@ -21,22 +21,22 @@
21 <property name="title"> 21 <property name="title">
22 <string>General</string> 22 <string>General</string>
23 </property> 23 </property>
24 <layout class="QHBoxLayout" name="GeneralHorizontalLayout"> 24 <layout class="QHBoxLayout" name="horizontalLayout">
25 <item> 25 <item>
26 <layout class="QVBoxLayout" name="GeneralVerticalLayout"> 26 <layout class="QVBoxLayout" name="verticalLayout">
27 <item> 27 <item>
28 <widget class="QCheckBox" name="show_unknown"> 28 <layout class="QHBoxLayout" name="horizontalLayout_3">
29 <property name="text"> 29 <item>
30 <string>Show files with type 'Unknown'</string> 30 <widget class="QLabel" name="theme_label">
31 </property> 31 <property name="text">
32 </widget> 32 <string>Theme:</string>
33 </item> 33 </property>
34 <item> 34 </widget>
35 <widget class="QCheckBox" name="show_add_ons"> 35 </item>
36 <property name="text"> 36 <item>
37 <string>Show Add-Ons Column</string> 37 <widget class="QComboBox" name="theme_combobox"/>
38 </property> 38 </item>
39 </widget> 39 </layout>
40 </item> 40 </item>
41 </layout> 41 </layout>
42 </item> 42 </item>
@@ -44,13 +44,20 @@
44 </widget> 44 </widget>
45 </item> 45 </item>
46 <item> 46 <item>
47 <widget class="QGroupBox" name="IconSizeGroupBox"> 47 <widget class="QGroupBox" name="GameListGroupBox">
48 <property name="title"> 48 <property name="title">
49 <string>Icon Size</string> 49 <string>Game List</string>
50 </property> 50 </property>
51 <layout class="QHBoxLayout" name="icon_size_qhbox_layout"> 51 <layout class="QHBoxLayout" name="GameListHorizontalLayout">
52 <item> 52 <item>
53 <layout class="QVBoxLayout" name="icon_size_qvbox_layout"> 53 <layout class="QVBoxLayout" name="GeneralVerticalLayout">
54 <item>
55 <widget class="QCheckBox" name="show_add_ons">
56 <property name="text">
57 <string>Show Add-Ons Column</string>
58 </property>
59 </widget>
60 </item>
54 <item> 61 <item>
55 <layout class="QHBoxLayout" name="icon_size_qhbox_layout_2"> 62 <layout class="QHBoxLayout" name="icon_size_qhbox_layout_2">
56 <item> 63 <item>
@@ -65,19 +72,6 @@
65 </item> 72 </item>
66 </layout> 73 </layout>
67 </item> 74 </item>
68 </layout>
69 </item>
70 </layout>
71 </widget>
72 </item>
73 <item>
74 <widget class="QGroupBox" name="RowGroupBox">
75 <property name="title">
76 <string>Row Text</string>
77 </property>
78 <layout class="QHBoxLayout" name="RowHorizontalLayout">
79 <item>
80 <layout class="QVBoxLayout" name="RowVerticalLayout">
81 <item> 75 <item>
82 <layout class="QHBoxLayout" name="row_1_qhbox_layout"> 76 <layout class="QHBoxLayout" name="row_1_qhbox_layout">
83 <item> 77 <item>
diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp
index 727bd8a94..3f1a94627 100644
--- a/src/yuzu/debugger/wait_tree.cpp
+++ b/src/yuzu/debugger/wait_tree.cpp
@@ -12,8 +12,8 @@
12#include "core/hle/kernel/process.h" 12#include "core/hle/kernel/process.h"
13#include "core/hle/kernel/readable_event.h" 13#include "core/hle/kernel/readable_event.h"
14#include "core/hle/kernel/scheduler.h" 14#include "core/hle/kernel/scheduler.h"
15#include "core/hle/kernel/synchronization_object.h"
15#include "core/hle/kernel/thread.h" 16#include "core/hle/kernel/thread.h"
16#include "core/hle/kernel/wait_object.h"
17#include "core/memory.h" 17#include "core/memory.h"
18 18
19WaitTreeItem::WaitTreeItem() = default; 19WaitTreeItem::WaitTreeItem() = default;
@@ -133,8 +133,9 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeCallstack::GetChildren() cons
133 return list; 133 return list;
134} 134}
135 135
136WaitTreeWaitObject::WaitTreeWaitObject(const Kernel::WaitObject& o) : object(o) {} 136WaitTreeSynchronizationObject::WaitTreeSynchronizationObject(const Kernel::SynchronizationObject& o)
137WaitTreeWaitObject::~WaitTreeWaitObject() = default; 137 : object(o) {}
138WaitTreeSynchronizationObject::~WaitTreeSynchronizationObject() = default;
138 139
139WaitTreeExpandableItem::WaitTreeExpandableItem() = default; 140WaitTreeExpandableItem::WaitTreeExpandableItem() = default;
140WaitTreeExpandableItem::~WaitTreeExpandableItem() = default; 141WaitTreeExpandableItem::~WaitTreeExpandableItem() = default;
@@ -143,25 +144,26 @@ bool WaitTreeExpandableItem::IsExpandable() const {
143 return true; 144 return true;
144} 145}
145 146
146QString WaitTreeWaitObject::GetText() const { 147QString WaitTreeSynchronizationObject::GetText() const {
147 return tr("[%1]%2 %3") 148 return tr("[%1]%2 %3")
148 .arg(object.GetObjectId()) 149 .arg(object.GetObjectId())
149 .arg(QString::fromStdString(object.GetTypeName()), 150 .arg(QString::fromStdString(object.GetTypeName()),
150 QString::fromStdString(object.GetName())); 151 QString::fromStdString(object.GetName()));
151} 152}
152 153
153std::unique_ptr<WaitTreeWaitObject> WaitTreeWaitObject::make(const Kernel::WaitObject& object) { 154std::unique_ptr<WaitTreeSynchronizationObject> WaitTreeSynchronizationObject::make(
155 const Kernel::SynchronizationObject& object) {
154 switch (object.GetHandleType()) { 156 switch (object.GetHandleType()) {
155 case Kernel::HandleType::ReadableEvent: 157 case Kernel::HandleType::ReadableEvent:
156 return std::make_unique<WaitTreeEvent>(static_cast<const Kernel::ReadableEvent&>(object)); 158 return std::make_unique<WaitTreeEvent>(static_cast<const Kernel::ReadableEvent&>(object));
157 case Kernel::HandleType::Thread: 159 case Kernel::HandleType::Thread:
158 return std::make_unique<WaitTreeThread>(static_cast<const Kernel::Thread&>(object)); 160 return std::make_unique<WaitTreeThread>(static_cast<const Kernel::Thread&>(object));
159 default: 161 default:
160 return std::make_unique<WaitTreeWaitObject>(object); 162 return std::make_unique<WaitTreeSynchronizationObject>(object);
161 } 163 }
162} 164}
163 165
164std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeWaitObject::GetChildren() const { 166std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeSynchronizationObject::GetChildren() const {
165 std::vector<std::unique_ptr<WaitTreeItem>> list; 167 std::vector<std::unique_ptr<WaitTreeItem>> list;
166 168
167 const auto& threads = object.GetWaitingThreads(); 169 const auto& threads = object.GetWaitingThreads();
@@ -173,8 +175,8 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeWaitObject::GetChildren() con
173 return list; 175 return list;
174} 176}
175 177
176WaitTreeObjectList::WaitTreeObjectList(const std::vector<std::shared_ptr<Kernel::WaitObject>>& list, 178WaitTreeObjectList::WaitTreeObjectList(
177 bool w_all) 179 const std::vector<std::shared_ptr<Kernel::SynchronizationObject>>& list, bool w_all)
178 : object_list(list), wait_all(w_all) {} 180 : object_list(list), wait_all(w_all) {}
179 181
180WaitTreeObjectList::~WaitTreeObjectList() = default; 182WaitTreeObjectList::~WaitTreeObjectList() = default;
@@ -188,11 +190,12 @@ QString WaitTreeObjectList::GetText() const {
188std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeObjectList::GetChildren() const { 190std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeObjectList::GetChildren() const {
189 std::vector<std::unique_ptr<WaitTreeItem>> list(object_list.size()); 191 std::vector<std::unique_ptr<WaitTreeItem>> list(object_list.size());
190 std::transform(object_list.begin(), object_list.end(), list.begin(), 192 std::transform(object_list.begin(), object_list.end(), list.begin(),
191 [](const auto& t) { return WaitTreeWaitObject::make(*t); }); 193 [](const auto& t) { return WaitTreeSynchronizationObject::make(*t); });
192 return list; 194 return list;
193} 195}
194 196
195WaitTreeThread::WaitTreeThread(const Kernel::Thread& thread) : WaitTreeWaitObject(thread) {} 197WaitTreeThread::WaitTreeThread(const Kernel::Thread& thread)
198 : WaitTreeSynchronizationObject(thread) {}
196WaitTreeThread::~WaitTreeThread() = default; 199WaitTreeThread::~WaitTreeThread() = default;
197 200
198QString WaitTreeThread::GetText() const { 201QString WaitTreeThread::GetText() const {
@@ -241,7 +244,8 @@ QString WaitTreeThread::GetText() const {
241 const QString pc_info = tr(" PC = 0x%1 LR = 0x%2") 244 const QString pc_info = tr(" PC = 0x%1 LR = 0x%2")
242 .arg(context.pc, 8, 16, QLatin1Char{'0'}) 245 .arg(context.pc, 8, 16, QLatin1Char{'0'})
243 .arg(context.cpu_registers[30], 8, 16, QLatin1Char{'0'}); 246 .arg(context.cpu_registers[30], 8, 16, QLatin1Char{'0'});
244 return QStringLiteral("%1%2 (%3) ").arg(WaitTreeWaitObject::GetText(), pc_info, status); 247 return QStringLiteral("%1%2 (%3) ")
248 .arg(WaitTreeSynchronizationObject::GetText(), pc_info, status);
245} 249}
246 250
247QColor WaitTreeThread::GetColor() const { 251QColor WaitTreeThread::GetColor() const {
@@ -273,7 +277,7 @@ QColor WaitTreeThread::GetColor() const {
273} 277}
274 278
275std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const { 279std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {
276 std::vector<std::unique_ptr<WaitTreeItem>> list(WaitTreeWaitObject::GetChildren()); 280 std::vector<std::unique_ptr<WaitTreeItem>> list(WaitTreeSynchronizationObject::GetChildren());
277 281
278 const auto& thread = static_cast<const Kernel::Thread&>(object); 282 const auto& thread = static_cast<const Kernel::Thread&>(object);
279 283
@@ -314,7 +318,7 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {
314 } 318 }
315 319
316 if (thread.GetStatus() == Kernel::ThreadStatus::WaitSynch) { 320 if (thread.GetStatus() == Kernel::ThreadStatus::WaitSynch) {
317 list.push_back(std::make_unique<WaitTreeObjectList>(thread.GetWaitObjects(), 321 list.push_back(std::make_unique<WaitTreeObjectList>(thread.GetSynchronizationObjects(),
318 thread.IsSleepingOnWait())); 322 thread.IsSleepingOnWait()));
319 } 323 }
320 324
@@ -323,7 +327,8 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {
323 return list; 327 return list;
324} 328}
325 329
326WaitTreeEvent::WaitTreeEvent(const Kernel::ReadableEvent& object) : WaitTreeWaitObject(object) {} 330WaitTreeEvent::WaitTreeEvent(const Kernel::ReadableEvent& object)
331 : WaitTreeSynchronizationObject(object) {}
327WaitTreeEvent::~WaitTreeEvent() = default; 332WaitTreeEvent::~WaitTreeEvent() = default;
328 333
329WaitTreeThreadList::WaitTreeThreadList(const std::vector<std::shared_ptr<Kernel::Thread>>& list) 334WaitTreeThreadList::WaitTreeThreadList(const std::vector<std::shared_ptr<Kernel::Thread>>& list)
diff --git a/src/yuzu/debugger/wait_tree.h b/src/yuzu/debugger/wait_tree.h
index 631274a5f..8e3bc4b24 100644
--- a/src/yuzu/debugger/wait_tree.h
+++ b/src/yuzu/debugger/wait_tree.h
@@ -19,7 +19,7 @@ class EmuThread;
19namespace Kernel { 19namespace Kernel {
20class HandleTable; 20class HandleTable;
21class ReadableEvent; 21class ReadableEvent;
22class WaitObject; 22class SynchronizationObject;
23class Thread; 23class Thread;
24} // namespace Kernel 24} // namespace Kernel
25 25
@@ -99,35 +99,37 @@ private:
99 const Kernel::Thread& thread; 99 const Kernel::Thread& thread;
100}; 100};
101 101
102class WaitTreeWaitObject : public WaitTreeExpandableItem { 102class WaitTreeSynchronizationObject : public WaitTreeExpandableItem {
103 Q_OBJECT 103 Q_OBJECT
104public: 104public:
105 explicit WaitTreeWaitObject(const Kernel::WaitObject& object); 105 explicit WaitTreeSynchronizationObject(const Kernel::SynchronizationObject& object);
106 ~WaitTreeWaitObject() override; 106 ~WaitTreeSynchronizationObject() override;
107 107
108 static std::unique_ptr<WaitTreeWaitObject> make(const Kernel::WaitObject& object); 108 static std::unique_ptr<WaitTreeSynchronizationObject> make(
109 const Kernel::SynchronizationObject& object);
109 QString GetText() const override; 110 QString GetText() const override;
110 std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override; 111 std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
111 112
112protected: 113protected:
113 const Kernel::WaitObject& object; 114 const Kernel::SynchronizationObject& object;
114}; 115};
115 116
116class WaitTreeObjectList : public WaitTreeExpandableItem { 117class WaitTreeObjectList : public WaitTreeExpandableItem {
117 Q_OBJECT 118 Q_OBJECT
118public: 119public:
119 WaitTreeObjectList(const std::vector<std::shared_ptr<Kernel::WaitObject>>& list, bool wait_all); 120 WaitTreeObjectList(const std::vector<std::shared_ptr<Kernel::SynchronizationObject>>& list,
121 bool wait_all);
120 ~WaitTreeObjectList() override; 122 ~WaitTreeObjectList() override;
121 123
122 QString GetText() const override; 124 QString GetText() const override;
123 std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override; 125 std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
124 126
125private: 127private:
126 const std::vector<std::shared_ptr<Kernel::WaitObject>>& object_list; 128 const std::vector<std::shared_ptr<Kernel::SynchronizationObject>>& object_list;
127 bool wait_all; 129 bool wait_all;
128}; 130};
129 131
130class WaitTreeThread : public WaitTreeWaitObject { 132class WaitTreeThread : public WaitTreeSynchronizationObject {
131 Q_OBJECT 133 Q_OBJECT
132public: 134public:
133 explicit WaitTreeThread(const Kernel::Thread& thread); 135 explicit WaitTreeThread(const Kernel::Thread& thread);
@@ -138,7 +140,7 @@ public:
138 std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override; 140 std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
139}; 141};
140 142
141class WaitTreeEvent : public WaitTreeWaitObject { 143class WaitTreeEvent : public WaitTreeSynchronizationObject {
142 Q_OBJECT 144 Q_OBJECT
143public: 145public:
144 explicit WaitTreeEvent(const Kernel::ReadableEvent& object); 146 explicit WaitTreeEvent(const Kernel::ReadableEvent& object);
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index 4c81ef12b..da2c27aa2 100644
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.cpp
@@ -298,8 +298,7 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
298 } 298 }
299 299
300 const auto file_type = loader->GetFileType(); 300 const auto file_type = loader->GetFileType();
301 if ((file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) && 301 if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) {
302 !UISettings::values.show_unknown) {
303 return true; 302 return true;
304 } 303 }
305 304
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index b5dd3e0d6..54ca2dc1d 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -454,7 +454,6 @@ void GMainWindow::InitializeWidgets() {
454 // Create status bar 454 // Create status bar
455 message_label = new QLabel(); 455 message_label = new QLabel();
456 // Configured separately for left alignment 456 // Configured separately for left alignment
457 message_label->setVisible(false);
458 message_label->setFrameStyle(QFrame::NoFrame); 457 message_label->setFrameStyle(QFrame::NoFrame);
459 message_label->setContentsMargins(4, 0, 4, 0); 458 message_label->setContentsMargins(4, 0, 4, 0);
460 message_label->setAlignment(Qt::AlignLeft); 459 message_label->setAlignment(Qt::AlignLeft);
@@ -476,8 +475,73 @@ void GMainWindow::InitializeWidgets() {
476 label->setVisible(false); 475 label->setVisible(false);
477 label->setFrameStyle(QFrame::NoFrame); 476 label->setFrameStyle(QFrame::NoFrame);
478 label->setContentsMargins(4, 0, 4, 0); 477 label->setContentsMargins(4, 0, 4, 0);
479 statusBar()->addPermanentWidget(label, 0); 478 statusBar()->addPermanentWidget(label);
480 } 479 }
480
481 // Setup Dock button
482 dock_status_button = new QPushButton();
483 dock_status_button->setObjectName(QStringLiteral("TogglableStatusBarButton"));
484 dock_status_button->setFocusPolicy(Qt::NoFocus);
485 connect(dock_status_button, &QPushButton::clicked, [&] {
486 Settings::values.use_docked_mode = !Settings::values.use_docked_mode;
487 dock_status_button->setChecked(Settings::values.use_docked_mode);
488 OnDockedModeChanged(!Settings::values.use_docked_mode, Settings::values.use_docked_mode);
489 });
490 dock_status_button->setText(tr("DOCK"));
491 dock_status_button->setCheckable(true);
492 dock_status_button->setChecked(Settings::values.use_docked_mode);
493 statusBar()->insertPermanentWidget(0, dock_status_button);
494
495 // Setup ASync button
496 async_status_button = new QPushButton();
497 async_status_button->setObjectName(QStringLiteral("TogglableStatusBarButton"));
498 async_status_button->setFocusPolicy(Qt::NoFocus);
499 connect(async_status_button, &QPushButton::clicked, [&] {
500 if (emulation_running) {
501 return;
502 }
503 Settings::values.use_asynchronous_gpu_emulation =
504 !Settings::values.use_asynchronous_gpu_emulation;
505 async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation);
506 Settings::Apply();
507 });
508 async_status_button->setText(tr("ASYNC"));
509 async_status_button->setCheckable(true);
510 async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation);
511 statusBar()->insertPermanentWidget(0, async_status_button);
512
513 // Setup Renderer API button
514 renderer_status_button = new QPushButton();
515 renderer_status_button->setObjectName(QStringLiteral("RendererStatusBarButton"));
516 renderer_status_button->setCheckable(true);
517 renderer_status_button->setFocusPolicy(Qt::NoFocus);
518 connect(renderer_status_button, &QPushButton::toggled, [=](bool checked) {
519 renderer_status_button->setText(checked ? tr("VULKAN") : tr("OPENGL"));
520 });
521 renderer_status_button->toggle();
522
523#ifndef HAS_VULKAN
524 renderer_status_button->setChecked(false);
525 renderer_status_button->setCheckable(false);
526 renderer_status_button->setDisabled(true);
527#else
528 renderer_status_button->setChecked(Settings::values.renderer_backend ==
529 Settings::RendererBackend::Vulkan);
530 connect(renderer_status_button, &QPushButton::clicked, [=] {
531 if (emulation_running) {
532 return;
533 }
534 if (renderer_status_button->isChecked()) {
535 Settings::values.renderer_backend = Settings::RendererBackend::Vulkan;
536 } else {
537 Settings::values.renderer_backend = Settings::RendererBackend::OpenGL;
538 }
539
540 Settings::Apply();
541 });
542#endif // HAS_VULKAN
543 statusBar()->insertPermanentWidget(0, renderer_status_button);
544
481 statusBar()->setVisible(true); 545 statusBar()->setVisible(true);
482 setStyleSheet(QStringLiteral("QStatusBar::item{border: none;}")); 546 setStyleSheet(QStringLiteral("QStatusBar::item{border: none;}"));
483} 547}
@@ -640,6 +704,7 @@ void GMainWindow::InitializeHotkeys() {
640 Settings::values.use_docked_mode = !Settings::values.use_docked_mode; 704 Settings::values.use_docked_mode = !Settings::values.use_docked_mode;
641 OnDockedModeChanged(!Settings::values.use_docked_mode, 705 OnDockedModeChanged(!Settings::values.use_docked_mode,
642 Settings::values.use_docked_mode); 706 Settings::values.use_docked_mode);
707 dock_status_button->setChecked(Settings::values.use_docked_mode);
643 }); 708 });
644} 709}
645 710
@@ -806,70 +871,12 @@ void GMainWindow::AllowOSSleep() {
806#endif 871#endif
807} 872}
808 873
809QStringList GMainWindow::GetUnsupportedGLExtensions() {
810 QStringList unsupported_ext;
811
812 if (!GLAD_GL_ARB_buffer_storage) {
813 unsupported_ext.append(QStringLiteral("ARB_buffer_storage"));
814 }
815 if (!GLAD_GL_ARB_direct_state_access) {
816 unsupported_ext.append(QStringLiteral("ARB_direct_state_access"));
817 }
818 if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev) {
819 unsupported_ext.append(QStringLiteral("ARB_vertex_type_10f_11f_11f_rev"));
820 }
821 if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge) {
822 unsupported_ext.append(QStringLiteral("ARB_texture_mirror_clamp_to_edge"));
823 }
824 if (!GLAD_GL_ARB_multi_bind) {
825 unsupported_ext.append(QStringLiteral("ARB_multi_bind"));
826 }
827 if (!GLAD_GL_ARB_clip_control) {
828 unsupported_ext.append(QStringLiteral("ARB_clip_control"));
829 }
830
831 // Extensions required to support some texture formats.
832 if (!GLAD_GL_EXT_texture_compression_s3tc) {
833 unsupported_ext.append(QStringLiteral("EXT_texture_compression_s3tc"));
834 }
835 if (!GLAD_GL_ARB_texture_compression_rgtc) {
836 unsupported_ext.append(QStringLiteral("ARB_texture_compression_rgtc"));
837 }
838 if (!GLAD_GL_ARB_depth_buffer_float) {
839 unsupported_ext.append(QStringLiteral("ARB_depth_buffer_float"));
840 }
841
842 for (const QString& ext : unsupported_ext) {
843 LOG_CRITICAL(Frontend, "Unsupported GL extension: {}", ext.toStdString());
844 }
845
846 return unsupported_ext;
847}
848
849bool GMainWindow::LoadROM(const QString& filename) { 874bool GMainWindow::LoadROM(const QString& filename) {
850 // Shutdown previous session if the emu thread is still active... 875 // Shutdown previous session if the emu thread is still active...
851 if (emu_thread != nullptr) 876 if (emu_thread != nullptr)
852 ShutdownGame(); 877 ShutdownGame();
853 878
854 render_window->InitRenderTarget(); 879 if (!render_window->InitRenderTarget()) {
855
856 {
857 Core::Frontend::ScopeAcquireWindowContext acquire_context{*render_window};
858 if (!gladLoadGL()) {
859 QMessageBox::critical(this, tr("Error while initializing OpenGL 4.3 Core!"),
860 tr("Your GPU may not support OpenGL 4.3, or you do not "
861 "have the latest graphics driver."));
862 return false;
863 }
864 }
865
866 const QStringList unsupported_gl_extensions = GetUnsupportedGLExtensions();
867 if (!unsupported_gl_extensions.empty()) {
868 QMessageBox::critical(this, tr("Error while initializing OpenGL Core!"),
869 tr("Your GPU may not support one or more required OpenGL"
870 "extensions. Please ensure you have the latest graphics "
871 "driver.<br><br>Unsupported extensions:<br>") +
872 unsupported_gl_extensions.join(QStringLiteral("<br>")));
873 return false; 880 return false;
874 } 881 }
875 882
@@ -980,7 +987,9 @@ void GMainWindow::BootGame(const QString& filename) {
980 // Create and start the emulation thread 987 // Create and start the emulation thread
981 emu_thread = std::make_unique<EmuThread>(render_window); 988 emu_thread = std::make_unique<EmuThread>(render_window);
982 emit EmulationStarting(emu_thread.get()); 989 emit EmulationStarting(emu_thread.get());
983 render_window->moveContext(); 990 if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) {
991 render_window->moveContext();
992 }
984 emu_thread->start(); 993 emu_thread->start();
985 994
986 connect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame); 995 connect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
@@ -1000,6 +1009,8 @@ void GMainWindow::BootGame(const QString& filename) {
1000 game_list_placeholder->hide(); 1009 game_list_placeholder->hide();
1001 } 1010 }
1002 status_bar_update_timer.start(2000); 1011 status_bar_update_timer.start(2000);
1012 async_status_button->setDisabled(true);
1013 renderer_status_button->setDisabled(true);
1003 1014
1004 const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID(); 1015 const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID();
1005 1016
@@ -1065,10 +1076,13 @@ void GMainWindow::ShutdownGame() {
1065 1076
1066 // Disable status bar updates 1077 // Disable status bar updates
1067 status_bar_update_timer.stop(); 1078 status_bar_update_timer.stop();
1068 message_label->setVisible(false);
1069 emu_speed_label->setVisible(false); 1079 emu_speed_label->setVisible(false);
1070 game_fps_label->setVisible(false); 1080 game_fps_label->setVisible(false);
1071 emu_frametime_label->setVisible(false); 1081 emu_frametime_label->setVisible(false);
1082 async_status_button->setEnabled(true);
1083#ifdef HAS_VULKAN
1084 renderer_status_button->setEnabled(true);
1085#endif
1072 1086
1073 emulation_running = false; 1087 emulation_running = false;
1074 1088
@@ -1836,6 +1850,13 @@ void GMainWindow::OnConfigure() {
1836 } 1850 }
1837 1851
1838 config->Save(); 1852 config->Save();
1853
1854 dock_status_button->setChecked(Settings::values.use_docked_mode);
1855 async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation);
1856#ifdef HAS_VULKAN
1857 renderer_status_button->setChecked(Settings::values.renderer_backend ==
1858 Settings::RendererBackend::Vulkan);
1859#endif
1839} 1860}
1840 1861
1841void GMainWindow::OnLoadAmiibo() { 1862void GMainWindow::OnLoadAmiibo() {
@@ -2028,7 +2049,6 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det
2028 if (emu_thread) { 2049 if (emu_thread) {
2029 emu_thread->SetRunning(true); 2050 emu_thread->SetRunning(true);
2030 message_label->setText(status_message); 2051 message_label->setText(status_message);
2031 message_label->setVisible(true);
2032 } 2052 }
2033 } 2053 }
2034} 2054}
@@ -2195,6 +2215,18 @@ void GMainWindow::closeEvent(QCloseEvent* event) {
2195 QWidget::closeEvent(event); 2215 QWidget::closeEvent(event);
2196} 2216}
2197 2217
2218void GMainWindow::keyPressEvent(QKeyEvent* event) {
2219 if (render_window) {
2220 render_window->ForwardKeyPressEvent(event);
2221 }
2222}
2223
2224void GMainWindow::keyReleaseEvent(QKeyEvent* event) {
2225 if (render_window) {
2226 render_window->ForwardKeyReleaseEvent(event);
2227 }
2228}
2229
2198static bool IsSingleFileDropEvent(QDropEvent* event) { 2230static bool IsSingleFileDropEvent(QDropEvent* event) {
2199 const QMimeData* mimeData = event->mimeData(); 2231 const QMimeData* mimeData = event->mimeData();
2200 return mimeData->hasUrls() && mimeData->urls().length() == 1; 2232 return mimeData->hasUrls() && mimeData->urls().length() == 1;
@@ -2227,18 +2259,6 @@ void GMainWindow::dragMoveEvent(QDragMoveEvent* event) {
2227 event->acceptProposedAction(); 2259 event->acceptProposedAction();
2228} 2260}
2229 2261
2230void GMainWindow::keyPressEvent(QKeyEvent* event) {
2231 if (render_window) {
2232 render_window->ForwardKeyPressEvent(event);
2233 }
2234}
2235
2236void GMainWindow::keyReleaseEvent(QKeyEvent* event) {
2237 if (render_window) {
2238 render_window->ForwardKeyReleaseEvent(event);
2239 }
2240}
2241
2242bool GMainWindow::ConfirmChangeGame() { 2262bool GMainWindow::ConfirmChangeGame() {
2243 if (emu_thread == nullptr) 2263 if (emu_thread == nullptr)
2244 return true; 2264 return true;
@@ -2290,8 +2310,16 @@ void GMainWindow::UpdateUITheme() {
2290 QStringList theme_paths(default_theme_paths); 2310 QStringList theme_paths(default_theme_paths);
2291 2311
2292 if (is_default_theme || current_theme.isEmpty()) { 2312 if (is_default_theme || current_theme.isEmpty()) {
2293 qApp->setStyleSheet({}); 2313 const QString theme_uri(QStringLiteral(":default/style.qss"));
2294 setStyleSheet({}); 2314 QFile f(theme_uri);
2315 if (f.open(QFile::ReadOnly | QFile::Text)) {
2316 QTextStream ts(&f);
2317 qApp->setStyleSheet(ts.readAll());
2318 setStyleSheet(ts.readAll());
2319 } else {
2320 qApp->setStyleSheet({});
2321 setStyleSheet({});
2322 }
2295 theme_paths.append(default_icons); 2323 theme_paths.append(default_icons);
2296 QIcon::setThemeName(default_icons); 2324 QIcon::setThemeName(default_icons);
2297 } else { 2325 } else {
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index a56f9a981..8eba2172c 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -27,6 +27,7 @@ class LoadingScreen;
27class MicroProfileDialog; 27class MicroProfileDialog;
28class ProfilerWidget; 28class ProfilerWidget;
29class QLabel; 29class QLabel;
30class QPushButton;
30class WaitTreeWidget; 31class WaitTreeWidget;
31enum class GameListOpenTarget; 32enum class GameListOpenTarget;
32class GameListPlaceholder; 33class GameListPlaceholder;
@@ -130,7 +131,6 @@ private:
130 void PreventOSSleep(); 131 void PreventOSSleep();
131 void AllowOSSleep(); 132 void AllowOSSleep();
132 133
133 QStringList GetUnsupportedGLExtensions();
134 bool LoadROM(const QString& filename); 134 bool LoadROM(const QString& filename);
135 void BootGame(const QString& filename); 135 void BootGame(const QString& filename);
136 void ShutdownGame(); 136 void ShutdownGame();
@@ -229,6 +229,9 @@ private:
229 QLabel* emu_speed_label = nullptr; 229 QLabel* emu_speed_label = nullptr;
230 QLabel* game_fps_label = nullptr; 230 QLabel* game_fps_label = nullptr;
231 QLabel* emu_frametime_label = nullptr; 231 QLabel* emu_frametime_label = nullptr;
232 QPushButton* async_status_button = nullptr;
233 QPushButton* renderer_status_button = nullptr;
234 QPushButton* dock_status_button = nullptr;
232 QTimer status_bar_update_timer; 235 QTimer status_bar_update_timer;
233 236
234 std::unique_ptr<Config> config; 237 std::unique_ptr<Config> config;
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index bc7725a01..a675ecf4d 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -89,7 +89,6 @@ struct Values {
89 int profile_index; 89 int profile_index;
90 90
91 // Game List 91 // Game List
92 bool show_unknown;
93 bool show_add_ons; 92 bool show_add_ons;
94 uint32_t icon_size; 93 uint32_t icon_size;
95 uint8_t row_1_text_id; 94 uint8_t row_1_text_id;
diff --git a/src/yuzu_cmd/CMakeLists.txt b/src/yuzu_cmd/CMakeLists.txt
index b5f06ab9e..a15719a0f 100644
--- a/src/yuzu_cmd/CMakeLists.txt
+++ b/src/yuzu_cmd/CMakeLists.txt
@@ -8,11 +8,22 @@ add_executable(yuzu-cmd
8 emu_window/emu_window_sdl2_gl.h 8 emu_window/emu_window_sdl2_gl.h
9 emu_window/emu_window_sdl2.cpp 9 emu_window/emu_window_sdl2.cpp
10 emu_window/emu_window_sdl2.h 10 emu_window/emu_window_sdl2.h
11 emu_window/emu_window_sdl2_gl.cpp
12 emu_window/emu_window_sdl2_gl.h
11 resource.h 13 resource.h
12 yuzu.cpp 14 yuzu.cpp
13 yuzu.rc 15 yuzu.rc
14) 16)
15 17
18if (ENABLE_VULKAN)
19 target_sources(yuzu-cmd PRIVATE
20 emu_window/emu_window_sdl2_vk.cpp
21 emu_window/emu_window_sdl2_vk.h)
22
23 target_include_directories(yuzu-cmd PRIVATE ../../externals/Vulkan-Headers/include)
24 target_compile_definitions(yuzu-cmd PRIVATE HAS_VULKAN)
25endif()
26
16create_target_directory_groups(yuzu-cmd) 27create_target_directory_groups(yuzu-cmd)
17 28
18target_link_libraries(yuzu-cmd PRIVATE common core input_common) 29target_link_libraries(yuzu-cmd PRIVATE common core input_common)
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index 1a812cb87..96f1ce3af 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -12,6 +12,7 @@
12#include "core/hle/service/acc/profile_manager.h" 12#include "core/hle/service/acc/profile_manager.h"
13#include "core/settings.h" 13#include "core/settings.h"
14#include "input_common/main.h" 14#include "input_common/main.h"
15#include "input_common/udp/client.h"
15#include "yuzu_cmd/config.h" 16#include "yuzu_cmd/config.h"
16#include "yuzu_cmd/default_ini.h" 17#include "yuzu_cmd/default_ini.h"
17 18
@@ -297,6 +298,10 @@ void Config::ReadValues() {
297 sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_x", 15); 298 sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_x", 15);
298 Settings::values.touchscreen.diameter_y = 299 Settings::values.touchscreen.diameter_y =
299 sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_y", 15); 300 sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_y", 15);
301 Settings::values.udp_input_address =
302 sdl2_config->Get("Controls", "udp_input_address", InputCommon::CemuhookUDP::DEFAULT_ADDR);
303 Settings::values.udp_input_port = static_cast<u16>(sdl2_config->GetInteger(
304 "Controls", "udp_input_port", InputCommon::CemuhookUDP::DEFAULT_PORT));
300 305
301 std::transform(keyboard_keys.begin(), keyboard_keys.end(), 306 std::transform(keyboard_keys.begin(), keyboard_keys.end(),
302 Settings::values.keyboard_keys.begin(), InputCommon::GenerateKeyboardParam); 307 Settings::values.keyboard_keys.begin(), InputCommon::GenerateKeyboardParam);
@@ -366,8 +371,16 @@ void Config::ReadValues() {
366 Settings::values.use_multi_core = sdl2_config->GetBoolean("Core", "use_multi_core", false); 371 Settings::values.use_multi_core = sdl2_config->GetBoolean("Core", "use_multi_core", false);
367 372
368 // Renderer 373 // Renderer
374 const int renderer_backend = sdl2_config->GetInteger(
375 "Renderer", "backend", static_cast<int>(Settings::RendererBackend::OpenGL));
376 Settings::values.renderer_backend = static_cast<Settings::RendererBackend>(renderer_backend);
377 Settings::values.renderer_debug = sdl2_config->GetBoolean("Renderer", "debug", false);
378 Settings::values.vulkan_device = sdl2_config->GetInteger("Renderer", "vulkan_device", 0);
379
369 Settings::values.resolution_factor = 380 Settings::values.resolution_factor =
370 static_cast<float>(sdl2_config->GetReal("Renderer", "resolution_factor", 1.0)); 381 static_cast<float>(sdl2_config->GetReal("Renderer", "resolution_factor", 1.0));
382 Settings::values.aspect_ratio =
383 static_cast<int>(sdl2_config->GetInteger("Renderer", "aspect_ratio", 0));
371 Settings::values.use_frame_limit = sdl2_config->GetBoolean("Renderer", "use_frame_limit", true); 384 Settings::values.use_frame_limit = sdl2_config->GetBoolean("Renderer", "use_frame_limit", true);
372 Settings::values.frame_limit = 385 Settings::values.frame_limit =
373 static_cast<u16>(sdl2_config->GetInteger("Renderer", "frame_limit", 100)); 386 static_cast<u16>(sdl2_config->GetInteger("Renderer", "frame_limit", 100));
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h
index 8d18a4a5a..8a2b658cd 100644
--- a/src/yuzu_cmd/default_ini.h
+++ b/src/yuzu_cmd/default_ini.h
@@ -69,18 +69,46 @@ rstick=
69# - "motion_emu" (default) for emulating motion input from mouse input. Required parameters: 69# - "motion_emu" (default) for emulating motion input from mouse input. Required parameters:
70# - "update_period": update period in milliseconds (default to 100) 70# - "update_period": update period in milliseconds (default to 100)
71# - "sensitivity": the coefficient converting mouse movement to tilting angle (default to 0.01) 71# - "sensitivity": the coefficient converting mouse movement to tilting angle (default to 0.01)
72# - "cemuhookudp" reads motion input from a udp server that uses cemuhook's udp protocol
72motion_device= 73motion_device=
73 74
74# for touch input, the following devices are available: 75# for touch input, the following devices are available:
75# - "emu_window" (default) for emulating touch input from mouse input to the emulation window. No parameters required 76# - "emu_window" (default) for emulating touch input from mouse input to the emulation window. No parameters required
77# - "cemuhookudp" reads touch input from a udp server that uses cemuhook's udp protocol
78# - "min_x", "min_y", "max_x", "max_y": defines the udp device's touch screen coordinate system
76touch_device= 79touch_device=
77 80
81# Most desktop operating systems do not expose a way to poll the motion state of the controllers
82# so as a way around it, cemuhook created a udp client/server protocol to broadcast the data directly
83# from a controller device to the client program. Citra has a client that can connect and read
84# from any cemuhook compatible motion program.
85
86# IPv4 address of the udp input server (Default "127.0.0.1")
87udp_input_address=
88
89# Port of the udp input server. (Default 26760)
90udp_input_port=
91
92# The pad to request data on. Should be between 0 (Pad 1) and 3 (Pad 4). (Default 0)
93udp_pad_index=
94
78[Core] 95[Core]
79# Whether to use multi-core for CPU emulation 96# Whether to use multi-core for CPU emulation
80# 0 (default): Disabled, 1: Enabled 97# 0 (default): Disabled, 1: Enabled
81use_multi_core= 98use_multi_core=
82 99
83[Renderer] 100[Renderer]
101# Which backend API to use.
102# 0 (default): OpenGL, 1: Vulkan
103backend =
104
105# Enable graphics API debugging mode.
106# 0 (default): Disabled, 1: Enabled
107debug =
108
109# Which Vulkan physical device to use (defaults to 0)
110vulkan_device =
111
84# Whether to use software or hardware rendering. 112# Whether to use software or hardware rendering.
85# 0: Software, 1 (default): Hardware 113# 0: Software, 1 (default): Hardware
86use_hw_renderer = 114use_hw_renderer =
@@ -94,6 +122,10 @@ use_shader_jit =
94# factor for the Switch resolution 122# factor for the Switch resolution
95resolution_factor = 123resolution_factor =
96 124
125# Aspect ratio
126# 0: Default (16:9), 1: Force 4:3, 2: Force 21:9, 3: Stretch to Window
127aspect_ratio =
128
97# Whether to enable V-Sync (caps the framerate at 60FPS) or not. 129# Whether to enable V-Sync (caps the framerate at 60FPS) or not.
98# 0 (default): Off, 1: On 130# 0 (default): Off, 1: On
99use_vsync = 131use_vsync =
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
index b1c512db1..e96139885 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
@@ -89,6 +89,10 @@ bool EmuWindow_SDL2::IsOpen() const {
89 return is_open; 89 return is_open;
90} 90}
91 91
92bool EmuWindow_SDL2::IsShown() const {
93 return is_shown;
94}
95
92void EmuWindow_SDL2::OnResize() { 96void EmuWindow_SDL2::OnResize() {
93 int width, height; 97 int width, height;
94 SDL_GetWindowSize(render_window, &width, &height); 98 SDL_GetWindowSize(render_window, &width, &height);
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.h b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
index eaa971f77..b38f56661 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
@@ -21,6 +21,9 @@ public:
21 /// Whether the window is still open, and a close request hasn't yet been sent 21 /// Whether the window is still open, and a close request hasn't yet been sent
22 bool IsOpen() const; 22 bool IsOpen() const;
23 23
24 /// Returns if window is shown (not minimized)
25 bool IsShown() const override;
26
24protected: 27protected:
25 /// Called by PollEvents when a key is pressed or released. 28 /// Called by PollEvents when a key is pressed or released.
26 void OnKeyEvent(int key, u8 state); 29 void OnKeyEvent(int key, u8 state);
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
index 6fde694a2..7ffa0ac09 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
@@ -9,6 +9,7 @@
9#include <SDL.h> 9#include <SDL.h>
10#include <fmt/format.h> 10#include <fmt/format.h>
11#include <glad/glad.h> 11#include <glad/glad.h>
12#include "common/assert.h"
12#include "common/logging/log.h" 13#include "common/logging/log.h"
13#include "common/scm_rev.h" 14#include "common/scm_rev.h"
14#include "common/string_util.h" 15#include "common/string_util.h"
@@ -151,6 +152,12 @@ void EmuWindow_SDL2_GL::DoneCurrent() {
151 SDL_GL_MakeCurrent(render_window, nullptr); 152 SDL_GL_MakeCurrent(render_window, nullptr);
152} 153}
153 154
155void EmuWindow_SDL2_GL::RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
156 void* surface) const {
157 // Should not have been called from OpenGL
158 UNREACHABLE();
159}
160
154std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_GL::CreateSharedContext() const { 161std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_GL::CreateSharedContext() const {
155 return std::make_unique<SDLGLContext>(); 162 return std::make_unique<SDLGLContext>();
156} 163}
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
index 630deba93..c753085a8 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
@@ -22,6 +22,10 @@ public:
22 /// Releases the GL context from the caller thread 22 /// Releases the GL context from the caller thread
23 void DoneCurrent() override; 23 void DoneCurrent() override;
24 24
25 /// Ignored in OpenGL
26 void RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
27 void* surface) const override;
28
25 std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override; 29 std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
26 30
27private: 31private:
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp
new file mode 100644
index 000000000..a203f0da9
--- /dev/null
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp
@@ -0,0 +1,162 @@
1// Copyright 2018 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include <string>
7#include <vector>
8#include <SDL.h>
9#include <SDL_vulkan.h>
10#include <fmt/format.h>
11#include <vulkan/vulkan.h>
12#include "common/assert.h"
13#include "common/logging/log.h"
14#include "common/scm_rev.h"
15#include "core/settings.h"
16#include "yuzu_cmd/emu_window/emu_window_sdl2_vk.h"
17
18EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(bool fullscreen) : EmuWindow_SDL2(fullscreen) {
19 if (SDL_Vulkan_LoadLibrary(nullptr) != 0) {
20 LOG_CRITICAL(Frontend, "SDL failed to load the Vulkan library: {}", SDL_GetError());
21 exit(EXIT_FAILURE);
22 }
23
24 vkGetInstanceProcAddr =
25 reinterpret_cast<PFN_vkGetInstanceProcAddr>(SDL_Vulkan_GetVkGetInstanceProcAddr());
26 if (vkGetInstanceProcAddr == nullptr) {
27 LOG_CRITICAL(Frontend, "Failed to retrieve Vulkan function pointer!");
28 exit(EXIT_FAILURE);
29 }
30
31 const std::string window_title = fmt::format("yuzu {} | {}-{} (Vulkan)", Common::g_build_name,
32 Common::g_scm_branch, Common::g_scm_desc);
33 render_window =
34 SDL_CreateWindow(window_title.c_str(),
35 SDL_WINDOWPOS_UNDEFINED, // x position
36 SDL_WINDOWPOS_UNDEFINED, // y position
37 Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height,
38 SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_VULKAN);
39
40 const bool use_standard_layers = UseStandardLayers(vkGetInstanceProcAddr);
41
42 u32 extra_ext_count{};
43 if (!SDL_Vulkan_GetInstanceExtensions(render_window, &extra_ext_count, NULL)) {
44 LOG_CRITICAL(Frontend, "Failed to query Vulkan extensions count from SDL! {}",
45 SDL_GetError());
46 exit(1);
47 }
48
49 auto extra_ext_names = std::make_unique<const char* []>(extra_ext_count);
50 if (!SDL_Vulkan_GetInstanceExtensions(render_window, &extra_ext_count, extra_ext_names.get())) {
51 LOG_CRITICAL(Frontend, "Failed to query Vulkan extensions from SDL! {}", SDL_GetError());
52 exit(1);
53 }
54 std::vector<const char*> enabled_extensions;
55 enabled_extensions.insert(enabled_extensions.begin(), extra_ext_names.get(),
56 extra_ext_names.get() + extra_ext_count);
57
58 std::vector<const char*> enabled_layers;
59 if (use_standard_layers) {
60 enabled_extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
61 enabled_layers.push_back("VK_LAYER_LUNARG_standard_validation");
62 }
63
64 VkApplicationInfo app_info{};
65 app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
66 app_info.apiVersion = VK_API_VERSION_1_1;
67 app_info.applicationVersion = VK_MAKE_VERSION(0, 1, 0);
68 app_info.pApplicationName = "yuzu-emu";
69 app_info.engineVersion = VK_MAKE_VERSION(0, 1, 0);
70 app_info.pEngineName = "yuzu-emu";
71
72 VkInstanceCreateInfo instance_ci{};
73 instance_ci.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
74 instance_ci.pApplicationInfo = &app_info;
75 instance_ci.enabledExtensionCount = static_cast<u32>(enabled_extensions.size());
76 instance_ci.ppEnabledExtensionNames = enabled_extensions.data();
77 if (Settings::values.renderer_debug) {
78 instance_ci.enabledLayerCount = static_cast<u32>(enabled_layers.size());
79 instance_ci.ppEnabledLayerNames = enabled_layers.data();
80 }
81
82 const auto vkCreateInstance =
83 reinterpret_cast<PFN_vkCreateInstance>(vkGetInstanceProcAddr(nullptr, "vkCreateInstance"));
84 if (vkCreateInstance == nullptr ||
85 vkCreateInstance(&instance_ci, nullptr, &vk_instance) != VK_SUCCESS) {
86 LOG_CRITICAL(Frontend, "Failed to create Vulkan instance!");
87 exit(EXIT_FAILURE);
88 }
89
90 vkDestroyInstance = reinterpret_cast<PFN_vkDestroyInstance>(
91 vkGetInstanceProcAddr(vk_instance, "vkDestroyInstance"));
92 if (vkDestroyInstance == nullptr) {
93 LOG_CRITICAL(Frontend, "Failed to retrieve Vulkan function pointer!");
94 exit(EXIT_FAILURE);
95 }
96
97 if (!SDL_Vulkan_CreateSurface(render_window, vk_instance, &vk_surface)) {
98 LOG_CRITICAL(Frontend, "Failed to create Vulkan surface! {}", SDL_GetError());
99 exit(EXIT_FAILURE);
100 }
101
102 OnResize();
103 OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
104 SDL_PumpEvents();
105 LOG_INFO(Frontend, "yuzu Version: {} | {}-{} (Vulkan)", Common::g_build_name,
106 Common::g_scm_branch, Common::g_scm_desc);
107}
108
109EmuWindow_SDL2_VK::~EmuWindow_SDL2_VK() {
110 vkDestroyInstance(vk_instance, nullptr);
111}
112
113void EmuWindow_SDL2_VK::SwapBuffers() {}
114
115void EmuWindow_SDL2_VK::MakeCurrent() {
116 // Unused on Vulkan
117}
118
119void EmuWindow_SDL2_VK::DoneCurrent() {
120 // Unused on Vulkan
121}
122
123void EmuWindow_SDL2_VK::RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
124 void* surface) const {
125 const auto instance_proc_addr = vkGetInstanceProcAddr;
126 std::memcpy(get_instance_proc_addr, &instance_proc_addr, sizeof(instance_proc_addr));
127 std::memcpy(instance, &vk_instance, sizeof(vk_instance));
128 std::memcpy(surface, &vk_surface, sizeof(vk_surface));
129}
130
131std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_VK::CreateSharedContext() const {
132 return nullptr;
133}
134
135bool EmuWindow_SDL2_VK::UseStandardLayers(PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr) const {
136 if (!Settings::values.renderer_debug) {
137 return false;
138 }
139
140 const auto vkEnumerateInstanceLayerProperties =
141 reinterpret_cast<PFN_vkEnumerateInstanceLayerProperties>(
142 vkGetInstanceProcAddr(nullptr, "vkEnumerateInstanceLayerProperties"));
143 if (vkEnumerateInstanceLayerProperties == nullptr) {
144 LOG_CRITICAL(Frontend, "Failed to retrieve Vulkan function pointer!");
145 return false;
146 }
147
148 u32 available_layers_count{};
149 if (vkEnumerateInstanceLayerProperties(&available_layers_count, nullptr) != VK_SUCCESS) {
150 LOG_CRITICAL(Frontend, "Failed to enumerate Vulkan validation layers!");
151 return false;
152 }
153 std::vector<VkLayerProperties> layers(available_layers_count);
154 if (vkEnumerateInstanceLayerProperties(&available_layers_count, layers.data()) != VK_SUCCESS) {
155 LOG_CRITICAL(Frontend, "Failed to enumerate Vulkan validation layers!");
156 return false;
157 }
158
159 return std::find_if(layers.begin(), layers.end(), [&](const auto& layer) {
160 return layer.layerName == std::string("VK_LAYER_LUNARG_standard_validation");
161 }) != layers.end();
162}
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h
new file mode 100644
index 000000000..2a7c06a24
--- /dev/null
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h
@@ -0,0 +1,39 @@
1// Copyright 2018 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <vulkan/vulkan.h>
8#include "core/frontend/emu_window.h"
9#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
10
11class EmuWindow_SDL2_VK final : public EmuWindow_SDL2 {
12public:
13 explicit EmuWindow_SDL2_VK(bool fullscreen);
14 ~EmuWindow_SDL2_VK();
15
16 /// Swap buffers to display the next frame
17 void SwapBuffers() override;
18
19 /// Makes the graphics context current for the caller thread
20 void MakeCurrent() override;
21
22 /// Releases the GL context from the caller thread
23 void DoneCurrent() override;
24
25 /// Retrieves Vulkan specific handlers from the window
26 void RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
27 void* surface) const override;
28
29 std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
30
31private:
32 bool UseStandardLayers(PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr) const;
33
34 VkInstance vk_instance{};
35 VkSurfaceKHR vk_surface{};
36
37 PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr{};
38 PFN_vkDestroyInstance vkDestroyInstance{};
39};
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index 3ee088a91..325795321 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -32,6 +32,9 @@
32#include "yuzu_cmd/config.h" 32#include "yuzu_cmd/config.h"
33#include "yuzu_cmd/emu_window/emu_window_sdl2.h" 33#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
34#include "yuzu_cmd/emu_window/emu_window_sdl2_gl.h" 34#include "yuzu_cmd/emu_window/emu_window_sdl2_gl.h"
35#ifdef HAS_VULKAN
36#include "yuzu_cmd/emu_window/emu_window_sdl2_vk.h"
37#endif
35 38
36#include "core/file_sys/registered_cache.h" 39#include "core/file_sys/registered_cache.h"
37 40
@@ -174,7 +177,20 @@ int main(int argc, char** argv) {
174 Settings::values.use_gdbstub = use_gdbstub; 177 Settings::values.use_gdbstub = use_gdbstub;
175 Settings::Apply(); 178 Settings::Apply();
176 179
177 std::unique_ptr<EmuWindow_SDL2> emu_window{std::make_unique<EmuWindow_SDL2_GL>(fullscreen)}; 180 std::unique_ptr<EmuWindow_SDL2> emu_window;
181 switch (Settings::values.renderer_backend) {
182 case Settings::RendererBackend::OpenGL:
183 emu_window = std::make_unique<EmuWindow_SDL2_GL>(fullscreen);
184 break;
185 case Settings::RendererBackend::Vulkan:
186#ifdef HAS_VULKAN
187 emu_window = std::make_unique<EmuWindow_SDL2_VK>(fullscreen);
188 break;
189#else
190 LOG_CRITICAL(Frontend, "Vulkan backend has not been compiled!");
191 return 1;
192#endif
193 }
178 194
179 if (!Settings::values.use_multi_core) { 195 if (!Settings::values.use_multi_core) {
180 // Single core mode must acquire OpenGL context for entire emulation session 196 // Single core mode must acquire OpenGL context for entire emulation session
diff --git a/src/yuzu_tester/config.cpp b/src/yuzu_tester/config.cpp
index 84ab4d687..0ac93b62a 100644
--- a/src/yuzu_tester/config.cpp
+++ b/src/yuzu_tester/config.cpp
@@ -118,6 +118,8 @@ void Config::ReadValues() {
118 // Renderer 118 // Renderer
119 Settings::values.resolution_factor = 119 Settings::values.resolution_factor =
120 static_cast<float>(sdl2_config->GetReal("Renderer", "resolution_factor", 1.0)); 120 static_cast<float>(sdl2_config->GetReal("Renderer", "resolution_factor", 1.0));
121 Settings::values.aspect_ratio =
122 static_cast<int>(sdl2_config->GetInteger("Renderer", "aspect_ratio", 0));
121 Settings::values.use_frame_limit = false; 123 Settings::values.use_frame_limit = false;
122 Settings::values.frame_limit = 100; 124 Settings::values.frame_limit = 100;
123 Settings::values.use_disk_shader_cache = 125 Settings::values.use_disk_shader_cache =
diff --git a/src/yuzu_tester/default_ini.h b/src/yuzu_tester/default_ini.h
index 9a3e86d68..8d93f7b88 100644
--- a/src/yuzu_tester/default_ini.h
+++ b/src/yuzu_tester/default_ini.h
@@ -26,6 +26,10 @@ use_shader_jit =
26# factor for the Switch resolution 26# factor for the Switch resolution
27resolution_factor = 27resolution_factor =
28 28
29# Aspect ratio
30# 0: Default (16:9), 1: Force 4:3, 2: Force 21:9, 3: Stretch to Window
31aspect_ratio =
32
29# Whether to enable V-Sync (caps the framerate at 60FPS) or not. 33# Whether to enable V-Sync (caps the framerate at 60FPS) or not.
30# 0 (default): Off, 1: On 34# 0 (default): Off, 1: On
31use_vsync = 35use_vsync =
diff --git a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp
index e7fe8decf..f2cc4a797 100644
--- a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp
+++ b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp
@@ -5,10 +5,15 @@
5#include <algorithm> 5#include <algorithm>
6#include <cstdlib> 6#include <cstdlib>
7#include <string> 7#include <string>
8
9#include <fmt/format.h>
10
8#define SDL_MAIN_HANDLED 11#define SDL_MAIN_HANDLED
9#include <SDL.h> 12#include <SDL.h>
10#include <fmt/format.h> 13
11#include <glad/glad.h> 14#include <glad/glad.h>
15
16#include "common/assert.h"
12#include "common/logging/log.h" 17#include "common/logging/log.h"
13#include "common/scm_rev.h" 18#include "common/scm_rev.h"
14#include "core/settings.h" 19#include "core/settings.h"
@@ -120,3 +125,11 @@ void EmuWindow_SDL2_Hide::MakeCurrent() {
120void EmuWindow_SDL2_Hide::DoneCurrent() { 125void EmuWindow_SDL2_Hide::DoneCurrent() {
121 SDL_GL_MakeCurrent(render_window, nullptr); 126 SDL_GL_MakeCurrent(render_window, nullptr);
122} 127}
128
129bool EmuWindow_SDL2_Hide::IsShown() const {
130 return false;
131}
132
133void EmuWindow_SDL2_Hide::RetrieveVulkanHandlers(void*, void*, void*) const {
134 UNREACHABLE();
135}
diff --git a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h
index 1a8953c75..c7fccc002 100644
--- a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h
+++ b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h
@@ -25,6 +25,13 @@ public:
25 /// Releases the GL context from the caller thread 25 /// Releases the GL context from the caller thread
26 void DoneCurrent() override; 26 void DoneCurrent() override;
27 27
28 /// Whether the screen is being shown or not.
29 bool IsShown() const override;
30
31 /// Retrieves Vulkan specific handlers from the window
32 void RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
33 void* surface) const override;
34
28 /// Whether the window is still open, and a close request hasn't yet been sent 35 /// Whether the window is still open, and a close request hasn't yet been sent
29 bool IsOpen() const; 36 bool IsOpen() const;
30 37