From 0c3664750671306bbae15a6159e1ddf8c808808f Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Sun, 19 Dec 2021 00:43:50 +1000 Subject: [PATCH] GS: Rewrite presentation interface and OSD --- PCSX2_suite.sln | 28 - bin/resources/shaders/dx11/convert.fx | 9 - bin/resources/shaders/opengl/convert.glsl | 7 - cmake/FindHarfBuzz.cmake | 181 ----- cmake/SearchForStuff.cmake | 8 - common/SettingsWrapper.h | 4 + pcsx2/CMakeLists.txt | 50 +- pcsx2/Config.h | 206 ++++- pcsx2/Counters.cpp | 76 +- pcsx2/Counters.h | 1 + pcsx2/Frontend/D3D11HostDisplay.cpp | 754 ++++++++++++++++++ pcsx2/Frontend/D3D11HostDisplay.h | 98 +++ pcsx2/Frontend/ImGuiManager.cpp | 614 ++++++++++++++ pcsx2/Frontend/ImGuiManager.h | 38 + pcsx2/Frontend/OpenGLHostDisplay.cpp | 369 +++++++++ pcsx2/Frontend/OpenGLHostDisplay.h | 76 ++ pcsx2/GS.cpp | 55 +- pcsx2/GS.h | 25 +- pcsx2/GS/GS.cpp | 685 +++++++++------- pcsx2/GS/GS.h | 102 +-- pcsx2/GS/GSCapture.cpp | 12 +- pcsx2/GS/GSDrawingContext.cpp | 2 +- pcsx2/GS/GSExtra.h | 1 + pcsx2/GS/GSLocalMemory.cpp | 11 +- pcsx2/GS/GSState.cpp | 28 +- pcsx2/GS/GSState.h | 11 +- pcsx2/GS/GSUtil.cpp | 18 +- pcsx2/GS/GSUtil.h | 1 - pcsx2/GS/Renderers/Common/GSDevice.cpp | 45 +- pcsx2/GS/Renderers/Common/GSDevice.h | 27 +- pcsx2/GS/Renderers/Common/GSOsdManager.cpp | 502 ------------ pcsx2/GS/Renderers/Common/GSOsdManager.h | 98 --- pcsx2/GS/Renderers/Common/GSRenderer.cpp | 398 +++++---- pcsx2/GS/Renderers/Common/GSRenderer.h | 23 +- pcsx2/GS/Renderers/Common/GSTexture.h | 5 +- pcsx2/GS/Renderers/Common/GSVertexTrace.cpp | 3 +- pcsx2/GS/Renderers/Common/GSVertexTrace.h | 2 - pcsx2/GS/Renderers/DX11/D3D.cpp | 24 - pcsx2/GS/Renderers/DX11/D3D.h | 3 - pcsx2/GS/Renderers/DX11/GSDevice11.cpp | 290 ++----- pcsx2/GS/Renderers/DX11/GSDevice11.h | 11 +- pcsx2/GS/Renderers/DX11/GSTexture11.cpp | 5 + pcsx2/GS/Renderers/DX11/GSTexture11.h | 2 + pcsx2/GS/Renderers/DX11/GSTextureFX11.cpp | 2 +- pcsx2/GS/Renderers/HW/GSHwHack.cpp | 6 +- pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 65 +- pcsx2/GS/Renderers/HW/GSRendererHW.h | 36 +- pcsx2/GS/Renderers/HW/GSRendererNew.cpp | 93 +-- pcsx2/GS/Renderers/HW/GSRendererNew.h | 3 - pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 73 +- pcsx2/GS/Renderers/HW/GSTextureCache.h | 1 - pcsx2/GS/Renderers/Null/GSDeviceNull.cpp | 15 - pcsx2/GS/Renderers/Null/GSDeviceNull.h | 3 - pcsx2/GS/Renderers/Null/GSTextureNull.cpp | 5 + pcsx2/GS/Renderers/Null/GSTextureNull.h | 1 + pcsx2/GS/Renderers/OpenGL/GLLoader.cpp | 49 +- pcsx2/GS/Renderers/OpenGL/GLLoader.h | 2 +- pcsx2/GS/Renderers/OpenGL/GLState.cpp | 18 +- pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp | 269 +++---- pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.h | 13 +- pcsx2/GS/Renderers/OpenGL/GSTextureOGL.cpp | 23 +- pcsx2/GS/Renderers/OpenGL/GSTextureOGL.h | 3 +- pcsx2/GS/Renderers/SW/GSRendererSW.cpp | 18 +- pcsx2/GS/Renderers/SW/GSRendererSW.h | 17 +- pcsx2/GS/Renderers/SW/GSTextureSW.cpp | 5 + pcsx2/GS/Renderers/SW/GSTextureSW.h | 1 + pcsx2/GS/Window/GSSetting.cpp | 6 - pcsx2/GS/Window/GSwxDialog.cpp | 103 +-- pcsx2/GS/Window/GSwxDialog.h | 1 - pcsx2/Host.h | 15 +- pcsx2/HostDisplay.cpp | 131 +++ pcsx2/HostDisplay.h | 172 ++++ pcsx2/MTGS.cpp | 188 +++-- pcsx2/MemoryCardFile.cpp | 5 +- pcsx2/Pcsx2Config.cpp | 356 ++++++++- .../Utilities/InputRecordingLogger.cpp | 3 +- pcsx2/System/SysCoreThread.cpp | 19 +- pcsx2/gui/App.h | 1 + pcsx2/gui/AppCoreThread.cpp | 3 - pcsx2/gui/AppHost.cpp | 118 +++ pcsx2/gui/AppHost.h | 12 + pcsx2/gui/AppMain.cpp | 56 +- pcsx2/gui/ConsoleLogger.cpp | 9 +- pcsx2/gui/ConsoleLogger.h | 1 - pcsx2/gui/Dialogs/GSDumpDialog.cpp | 14 +- pcsx2/gui/FrameForGS.cpp | 105 ++- pcsx2/gui/GSFrame.h | 2 +- pcsx2/gui/GlobalCommands.cpp | 54 +- pcsx2/gui/MainFrame.cpp | 4 +- pcsx2/gui/MainMenuClicks.cpp | 41 +- pcsx2/gui/Panels/GameFixesPanel.cpp | 4 + pcsx2/pcsx2.vcxproj | 18 +- pcsx2/pcsx2.vcxproj.filters | 36 +- 93 files changed, 4558 insertions(+), 2548 deletions(-) delete mode 100644 cmake/FindHarfBuzz.cmake create mode 100644 pcsx2/Frontend/D3D11HostDisplay.cpp create mode 100644 pcsx2/Frontend/D3D11HostDisplay.h create mode 100644 pcsx2/Frontend/ImGuiManager.cpp create mode 100644 pcsx2/Frontend/ImGuiManager.h create mode 100644 pcsx2/Frontend/OpenGLHostDisplay.cpp create mode 100644 pcsx2/Frontend/OpenGLHostDisplay.h delete mode 100644 pcsx2/GS/Renderers/Common/GSOsdManager.cpp delete mode 100644 pcsx2/GS/Renderers/Common/GSOsdManager.h create mode 100644 pcsx2/HostDisplay.cpp create mode 100644 pcsx2/HostDisplay.h create mode 100644 pcsx2/gui/AppHost.h diff --git a/PCSX2_suite.sln b/PCSX2_suite.sln index 7eb89572f53be..5e43b47ea150a 100644 --- a/PCSX2_suite.sln +++ b/PCSX2_suite.sln @@ -15,7 +15,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pcsx2", "pcsx2\pcsx2.vcxpro {12728250-16EC-4DC6-94D7-E21DD88947F8} = {12728250-16EC-4DC6-94D7-E21DD88947F8} {D6973076-9317-4EF2-A0B8-B7A18AC0713E} = {D6973076-9317-4EF2-A0B8-B7A18AC0713E} {27F17499-A372-4408-8AFA-4F9F4584FBD3} = {27F17499-A372-4408-8AFA-4F9F4584FBD3} - {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B} = {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SoundTouch", "3rdparty\soundtouch\SoundTouch.vcxproj", "{E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}" @@ -40,8 +39,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pthreads4w", "3rdparty\pthr EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "baseclasses", "3rdparty\baseclasses\baseclasses.vcxproj", "{27F17499-A372-4408-8AFA-4F9F4584FBD3}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "freetype", "3rdparty\freetype\builds\windows\freetype.vcxproj", "{78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "liblzma", "3rdparty\xz\liblzma.vcxproj", "{12728250-16EC-4DC6-94D7-E21DD88947F8}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fmt", "3rdparty\fmt\fmt.vcxproj", "{449AD25E-424A-4714-BABC-68706CDCC33B}" @@ -367,30 +364,6 @@ Global {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Release|Win32.Build.0 = Release|Win32 {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Release|x64.ActiveCfg = Release|x64 {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Release|x64.Build.0 = Release|x64 - {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Debug AVX2|Win32.ActiveCfg = Debug|Win32 - {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Debug AVX2|Win32.Build.0 = Debug|Win32 - {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Debug AVX2|x64.ActiveCfg = Debug|x64 - {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Debug AVX2|x64.Build.0 = Debug|x64 - {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Debug|Win32.ActiveCfg = Debug|Win32 - {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Debug|Win32.Build.0 = Debug|Win32 - {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Debug|x64.ActiveCfg = Debug|x64 - {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Debug|x64.Build.0 = Debug|x64 - {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Devel AVX2|Win32.ActiveCfg = Devel|Win32 - {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Devel AVX2|Win32.Build.0 = Devel|Win32 - {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Devel AVX2|x64.ActiveCfg = Devel|x64 - {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Devel AVX2|x64.Build.0 = Devel|x64 - {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Devel|Win32.ActiveCfg = Devel|Win32 - {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Devel|Win32.Build.0 = Devel|Win32 - {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Devel|x64.ActiveCfg = Devel|x64 - {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Devel|x64.Build.0 = Devel|x64 - {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Release AVX2|Win32.ActiveCfg = Release|Win32 - {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Release AVX2|Win32.Build.0 = Release|Win32 - {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Release AVX2|x64.ActiveCfg = Release|x64 - {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Release AVX2|x64.Build.0 = Release|x64 - {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Release|Win32.ActiveCfg = Release|Win32 - {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Release|Win32.Build.0 = Release|Win32 - {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Release|x64.ActiveCfg = Release|x64 - {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Release|x64.Build.0 = Release|x64 {12728250-16EC-4DC6-94D7-E21DD88947F8}.Debug AVX2|Win32.ActiveCfg = Debug|Win32 {12728250-16EC-4DC6-94D7-E21DD88947F8}.Debug AVX2|Win32.Build.0 = Debug|Win32 {12728250-16EC-4DC6-94D7-E21DD88947F8}.Debug AVX2|x64.ActiveCfg = Debug|x64 @@ -671,7 +644,6 @@ Global {D6973076-9317-4EF2-A0B8-B7A18AC0713E} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38} {0FAE817D-9A32-4830-857E-81DA57246E16} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38} {27F17499-A372-4408-8AFA-4F9F4584FBD3} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38} - {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38} {12728250-16EC-4DC6-94D7-E21DD88947F8} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38} {449AD25E-424A-4714-BABC-68706CDCC33B} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38} {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38} diff --git a/bin/resources/shaders/dx11/convert.fx b/bin/resources/shaders/dx11/convert.fx index 11ba1f55d9d1e..842e8a539e620 100644 --- a/bin/resources/shaders/dx11/convert.fx +++ b/bin/resources/shaders/dx11/convert.fx @@ -367,13 +367,4 @@ PS_OUTPUT ps_yuv(PS_INPUT input) return output; } -PS_OUTPUT ps_osd(PS_INPUT input) -{ - PS_OUTPUT output; - - output.c = input.c * float4(1.0, 1.0, 1.0, sample_c(input.t).a); - - return output; -} - #endif diff --git a/bin/resources/shaders/opengl/convert.glsl b/bin/resources/shaders/opengl/convert.glsl index 66baf59c86748..24f9f8af1415e 100644 --- a/bin/resources/shaders/opengl/convert.glsl +++ b/bin/resources/shaders/opengl/convert.glsl @@ -272,13 +272,6 @@ void ps_convert_rgba_8i() } #endif -#ifdef ps_osd -void ps_osd() -{ - SV_Target0 = PSin_c * vec4(1.0, 1.0, 1.0, sample_c().r); -} -#endif - #ifdef ps_filter_transparency void ps_filter_transparency() { diff --git a/cmake/FindHarfBuzz.cmake b/cmake/FindHarfBuzz.cmake deleted file mode 100644 index 8433780016cdf..0000000000000 --- a/cmake/FindHarfBuzz.cmake +++ /dev/null @@ -1,181 +0,0 @@ - -# Copyright (c) 2012, Intel Corporation -# Copyright (c) 2019 Sony Interactive Entertainment Inc. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# * Neither the name of Intel Corporation nor the names of its contributors may -# be used to endorse or promote products derived from this software without -# specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. -# -# Try to find Harfbuzz include and library directories. -# -# After successful discovery, this will set for inclusion where needed: -# HarfBuzz_INCLUDE_DIRS - containg the HarfBuzz headers -# HarfBuzz_LIBRARIES - containg the HarfBuzz library - -#[=======================================================================[.rst: -FindHarfBuzz --------------- - -Find HarfBuzz headers and libraries. - -Imported Targets -^^^^^^^^^^^^^^^^ - -``HarfBuzz::HarfBuzz`` - The HarfBuzz library, if found. - -``HarfBuzz::ICU`` - The HarfBuzz ICU library, if found. - -Result Variables -^^^^^^^^^^^^^^^^ - -This will define the following variables in your project: - -``HarfBuzz_FOUND`` - true if (the requested version of) HarfBuzz is available. -``HarfBuzz_VERSION`` - the version of HarfBuzz. -``HarfBuzz_LIBRARIES`` - the libraries to link against to use HarfBuzz. -``HarfBuzz_INCLUDE_DIRS`` - where to find the HarfBuzz headers. -``HarfBuzz_COMPILE_OPTIONS`` - this should be passed to target_compile_options(), if the - target is not used for linking - -#]=======================================================================] - -find_package(PkgConfig QUIET) -pkg_check_modules(PC_HARFBUZZ QUIET harfbuzz) -set(HarfBuzz_COMPILE_OPTIONS ${PC_HARFBUZZ_CFLAGS_OTHER}) -set(HarfBuzz_VERSION ${PC_HARFBUZZ_CFLAGS_VERSION}) - -find_path(HarfBuzz_INCLUDE_DIR - NAMES hb.h - HINTS ${PC_HARFBUZZ_INCLUDEDIR} ${PC_HARFBUZZ_INCLUDE_DIRS} - PATH_SUFFIXES harfbuzz -) - -find_library(HarfBuzz_LIBRARY - NAMES ${HarfBuzz_NAMES} harfbuzz - HINTS ${PC_HARFBUZZ_LIBDIR} ${PC_HARFBUZZ_LIBRARY_DIRS} -) - -if (HarfBuzz_INCLUDE_DIR AND NOT HarfBuzz_VERSION) - if (EXISTS "${HarfBuzz_INCLUDE_DIR}/hb-version.h") - file(READ "${HarfBuzz_INCLUDE_DIR}/hb-version.h" _harfbuzz_version_content) - - string(REGEX MATCH "#define +HB_VERSION_STRING +\"([0-9]+\\.[0-9]+\\.[0-9]+)\"" _dummy "${_harfbuzz_version_content}") - set(HarfBuzz_VERSION "${CMAKE_MATCH_1}") - endif () -endif () - -if ("${HarfBuzz_FIND_VERSION}" VERSION_GREATER "${HarfBuzz_VERSION}") - message(FATAL_ERROR "Required version (" ${HarfBuzz_FIND_VERSION} ") is higher than found version (" ${HarfBuzz_VERSION} ")") -endif () - -# Find components -if (HarfBuzz_INCLUDE_DIR AND HarfBuzz_LIBRARY) - set(_HarfBuzz_REQUIRED_LIBS_FOUND ON) - set(HarfBuzz_LIBS_FOUND "HarfBuzz (required): ${HarfBuzz_LIBRARY}") -else () - set(_HarfBuzz_REQUIRED_LIBS_FOUND OFF) - set(HarfBuzz_LIBS_NOT_FOUND "HarfBuzz (required)") -endif () - -if ("ICU" IN_LIST HarfBuzz_FIND_COMPONENTS) - pkg_check_modules(PC_HARFBUZZ_ICU QUIET harfbuzz-icu) - set(HarfBuzz_ICU_COMPILE_OPTIONS ${PC_HARFBUZZ_ICU_CFLAGS_OTHER}) - - find_library(HarfBuzz_ICU_LIBRARY - NAMES ${HarfBuzz_ICU_NAMES} harfbuzz-icu - HINTS ${PC_HARFBUZZ_ICU_LIBDIR} ${PC_HARFBUZZ_ICU_LIBRARY_DIRS} - ) - - if (HarfBuzz_ICU_LIBRARY) - if (HarfBuzz_FIND_REQUIRED_ICU) - list(APPEND HarfBuzz_LIBS_FOUND "ICU (required): ${HarfBuzz_ICU_LIBRARY}") - else () - list(APPEND HarfBuzz_LIBS_FOUND "ICU (optional): ${HarfBuzz_ICU_LIBRARY}") - endif () - else () - if (HarfBuzz_FIND_REQUIRED_ICU) - set(_HarfBuzz_REQUIRED_LIBS_FOUND OFF) - list(APPEND HarfBuzz_LIBS_NOT_FOUND "ICU (required)") - else () - list(APPEND HarfBuzz_LIBS_NOT_FOUND "ICU (optional)") - endif () - endif () -endif () - -if (NOT HarfBuzz_FIND_QUIETLY) - if (HarfBuzz_LIBS_FOUND) - message(STATUS "Found the following HarfBuzz libraries:") - foreach (found ${HarfBuzz_LIBS_FOUND}) - message(STATUS " ${found}") - endforeach () - endif () - if (HarfBuzz_LIBS_NOT_FOUND) - message(STATUS "The following HarfBuzz libraries were not found:") - foreach (found ${HarfBuzz_LIBS_NOT_FOUND}) - message(STATUS " ${found}") - endforeach () - endif () -endif () - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(HarfBuzz - FOUND_VAR HarfBuzz_FOUND - REQUIRED_VARS HarfBuzz_INCLUDE_DIR HarfBuzz_LIBRARY _HarfBuzz_REQUIRED_LIBS_FOUND - VERSION_VAR HarfBuzz_VERSION -) - -if (HarfBuzz_LIBRARY AND NOT TARGET HarfBuzz::HarfBuzz) - add_library(HarfBuzz::HarfBuzz UNKNOWN IMPORTED GLOBAL) - set_target_properties(HarfBuzz::HarfBuzz PROPERTIES - IMPORTED_LOCATION "${HarfBuzz_LIBRARY}" - INTERFACE_COMPILE_OPTIONS "${HarfBuzz_COMPILE_OPTIONS}" - INTERFACE_INCLUDE_DIRECTORIES "${HarfBuzz_INCLUDE_DIR}" - ) -endif () - -if (HarfBuzz_ICU_LIBRARY AND NOT TARGET HarfBuzz::ICU) - add_library(HarfBuzz::ICU UNKNOWN IMPORTED GLOBAL) - set_target_properties(HarfBuzz::ICU PROPERTIES - IMPORTED_LOCATION "${HarfBuzz_ICU_LIBRARY}" - INTERFACE_COMPILE_OPTIONS "${HarfBuzz_ICU_COMPILE_OPTIONS}" - INTERFACE_INCLUDE_DIRECTORIES "${HarfBuzz_INCLUDE_DIR}" - ) -endif () - -mark_as_advanced( - HarfBuzz_INCLUDE_DIR - HarfBuzz_LIBRARY - HarfBuzz_ICU_LIBRARY -) - -if (HarfBuzz_FOUND) - set(HarfBuzz_LIBRARIES ${HarfBuzz_LIBRARY} ${HarfBuzz_ICU_LIBRARY}) - set(HarfBuzz_INCLUDE_DIRS ${HarfBuzz_INCLUDE_DIR}) -endif () diff --git a/cmake/SearchForStuff.cmake b/cmake/SearchForStuff.cmake index 8a1b1c9943119..b0d6cd50fa301 100644 --- a/cmake/SearchForStuff.cmake +++ b/cmake/SearchForStuff.cmake @@ -11,7 +11,6 @@ if (WIN32) add_subdirectory(3rdparty/libjpeg EXCLUDE_FROM_ALL) add_subdirectory(3rdparty/libsamplerate EXCLUDE_FROM_ALL) add_subdirectory(3rdparty/baseclasses EXCLUDE_FROM_ALL) - add_subdirectory(3rdparty/freetype EXCLUDE_FROM_ALL) add_subdirectory(3rdparty/pthreads4w EXCLUDE_FROM_ALL) add_subdirectory(3rdparty/soundtouch EXCLUDE_FROM_ALL) add_subdirectory(3rdparty/wil EXCLUDE_FROM_ALL) @@ -26,7 +25,6 @@ else() find_package(PCAP REQUIRED) find_package(LibXml2 REQUIRED) make_imported_target_if_missing(LibXml2::LibXml2 LibXml2) - find_package(Freetype REQUIRED) # GS OSD find_package(Gettext) # translation tool find_package(LibLZMA REQUIRED) make_imported_target_if_missing(LibLZMA::LibLZMA LIBLZMA) @@ -168,12 +166,6 @@ else() if(WAYLAND_API) find_package(Wayland REQUIRED) endif() - - #---------------------------------------- - # Use system include - #---------------------------------------- - find_package(HarfBuzz) - endif(WIN32) # Require threads on all OSes. diff --git a/common/SettingsWrapper.h b/common/SettingsWrapper.h index 55e6954e47ccb..fa85d46befcae 100644 --- a/common/SettingsWrapper.h +++ b/common/SettingsWrapper.h @@ -103,7 +103,11 @@ class SettingsSaveWrapper final : public SettingsWrapper #define SettingsWrapSection(section) const char* CURRENT_SETTINGS_SECTION = section; #define SettingsWrapEntry(var) wrap.Entry(CURRENT_SETTINGS_SECTION, #var, var, var) +#define SettingsWrapEntryEx(var, name) wrap.Entry(CURRENT_SETTINGS_SECTION, name, var, var) #define SettingsWrapBitfield(varname) varname = wrap.EntryBitfield(CURRENT_SETTINGS_SECTION, #varname, varname, varname) #define SettingsWrapBitBool(varname) varname = wrap.EntryBitBool(CURRENT_SETTINGS_SECTION, #varname, !!varname, varname) #define SettingsWrapBitfieldEx(varname, textname) varname = wrap.EntryBitfield(CURRENT_SETTINGS_SECTION, textname, varname, varname) #define SettingsWrapBitBoolEx(varname, textname) varname = wrap.EntryBitBool(CURRENT_SETTINGS_SECTION, textname, !!varname, varname) +#define SettingsWrapEnumEx(varname, textname, names) wrap.EnumEntry(CURRENT_SETTINGS_SECTION, textname, varname, names, varname) +#define SettingsWrapIntEnumEx(varname, textname) varname = static_cast(wrap.EntryBitfield(CURRENT_SETTINGS_SECTION, textname, static_cast(varname), static_cast(varname))) + diff --git a/pcsx2/CMakeLists.txt b/pcsx2/CMakeLists.txt index 9742d9700d146..c0e8502a960f8 100644 --- a/pcsx2/CMakeLists.txt +++ b/pcsx2/CMakeLists.txt @@ -109,6 +109,8 @@ set(pcsx2Sources Gif_Unit.cpp GS.cpp GSState.cpp + Host.cpp + HostDisplay.cpp Hw.cpp HwRead.cpp HwWrite.cpp @@ -187,6 +189,7 @@ set(pcsx2Headers GS.h Hardware.h Host.h + HostDisplay.h Hw.h IopBios.h IopCommon.h @@ -607,7 +610,6 @@ set(pcsx2GSSources GS/Renderers/Common/GSDevice.cpp GS/Renderers/Common/GSDirtyRect.cpp GS/Renderers/Common/GSFunctionMap.cpp - GS/Renderers/Common/GSOsdManager.cpp GS/Renderers/Common/GSRenderer.cpp GS/Renderers/Common/GSTexture.cpp GS/Renderers/Common/GSVertexTrace.cpp @@ -672,7 +674,6 @@ set(pcsx2GSHeaders GS/Renderers/Common/GSDirtyRect.h GS/Renderers/Common/GSFastList.h GS/Renderers/Common/GSFunctionMap.h - GS/Renderers/Common/GSOsdManager.h GS/Renderers/Common/GSRenderer.h GS/Renderers/Common/GSTexture.h GS/Renderers/Common/GSVertex.h @@ -872,6 +873,27 @@ set(pcsx2DebugToolsHeaders DebugTools/DisVUops.h DebugTools/BiosDebugData.h) +# Frontend sources +set(pcsx2FrontendSources + Frontend/ImGuiManager.cpp + Frontend/OpenGLHostDisplay.cpp +) + +# Frontend headers +set(pcsx2FrontendHeaders + Frontend/ImGuiManager.h + Frontend/OpenGLHostDisplay.h +) + +if(WIN32) + list(APPEND pcsx2FrontendSources + Frontend/D3D11HostDisplay.cpp + ) + list(APPEND pcsx2FrontendHeaders + Frontend/D3D11HostDisplay.h + ) +endif() + # gui sources set(pcsx2GuiSources gui/AppAssert.cpp @@ -952,6 +974,7 @@ set(pcsx2GuiHeaders gui/AppConfig.h gui/AppCoreThread.h gui/AppEventListeners.h + gui/AppHost.h gui/AppForwardDefs.h gui/App.h gui/ApplyState.h @@ -1296,19 +1319,27 @@ target_sources(PCSX2 PRIVATE ${pcsx2GSSources} ${pcsx2DebugToolsSources} ${pcsx2DebugToolsHeaders} - ${pcsx2GuiSources} - ${pcsx2GuiResources} - ${pcsx2GuiHeaders} + ${pcsx2FrontendSources} + ${pcsx2FrontendHeaders} ${pcsx2ps2Sources} ${pcsx2ps2Headers} - ${pcsx2RecordingSources} - ${pcsx2RecordingVirtualPadResources} - ${pcsx2RecordingHeaders} ${pcsx2SystemSources} ${pcsx2SystemHeaders} ${pcsx2UtilitiesSources} ${pcsx2UtilitiesHeaders}) +# gui sources when not doing a qt build +if (NOT PCSX2_CORE) + target_sources(PCSX2 PRIVATE + ${pcsx2GuiSources} + ${pcsx2GuiResources} + ${pcsx2GuiHeaders} + ${pcsx2RecordingSources} + ${pcsx2RecordingVirtualPadResources} + ${pcsx2RecordingHeaders} + ) +endif() + # platform sources # Linux if(Linux) @@ -1368,6 +1399,7 @@ endif() target_link_libraries(PCSX2_FLAGS INTERFACE common glad + imgui fmt::fmt ryml chdr-static @@ -1376,7 +1408,6 @@ target_link_libraries(PCSX2_FLAGS INTERFACE PkgConfig::SOUNDTOUCH PkgConfig::SAMPLERATE PNG::PNG - Freetype::Freetype LibLZMA::LibLZMA ${LIBC_LIBRARIES} ) @@ -1410,7 +1441,6 @@ elseif(APPLE) else() target_link_libraries(PCSX2_FLAGS INTERFACE GTK::gtk - HarfBuzz::HarfBuzz OpenGL::GL PCAP::PCAP LibXml2::LibXml2 diff --git a/pcsx2/Config.h b/pcsx2/Config.h index 69b3ba59910b8..b3e82b3d50d0a 100644 --- a/pcsx2/Config.h +++ b/pcsx2/Config.h @@ -32,6 +32,7 @@ enum GamefixId Fix_FpuMultiply = GamefixId_FIRST, Fix_FpuNegDiv, Fix_GoemonTlbMiss, + Fix_SoftwareRendererFMV, Fix_SkipMpeg, Fix_OPHFlag, Fix_EETiming, @@ -97,6 +98,73 @@ enum class LimiterModeType : u8 Nominal, Turbo, Slomo, + Unlimited, +}; + +enum class GSRendererType : s8 +{ + Auto = -1, + DX11 = 3, + Null = 11, + OGL = 12, + SW = 13, +}; + +enum class GSInterlaceMode : u8 +{ + Off, + WeaveTFF, + WeaveBFF, + BobTFF, + BobBFF, + BlendTFF, + BlendBFF, + Automatic, + Count +}; + +// Ordering was done to keep compatibility with older ini file. +enum class BiFiltering : u8 +{ + Nearest, + Forced, + PS2, + Forced_But_Sprite, +}; + +enum class TriFiltering : u8 +{ + Off, + PS2, + Forced, +}; + +enum class HWMipmapLevel : s8 +{ + Automatic = -1, + Off, + Basic, + Full +}; + +enum class CRCHackLevel : s8 +{ + Automatic = -1, + Off, + Minimum, + Partial, + Full, + Aggressive +}; + +enum class AccBlendLevel : u8 +{ + Minimum, + Basic, + Medium, + High, + Full, + Ultra, }; // Template function for casting enumerations to their underlying type @@ -318,6 +386,61 @@ struct Pcsx2Config // ------------------------------------------------------------------------ struct GSOptions { + static const char* AspectRatioNames[]; + static const char* FMVAspectRatioSwitchNames[]; + + static const char* GetRendererName(GSRendererType type); + + union + { + u64 bitset; + + struct + { + bool + IntegerScaling : 1, + LinearPresent : 1, + UseDebugDevice : 1, + UseBlitSwapChain : 1, + DisableShaderCache : 1, + OsdShowMessages : 1, + OsdShowSpeed : 1, + OsdShowFPS : 1, + OsdShowCPU : 1, + OsdShowResolution : 1, + OsdShowGSStats : 1; + + bool + HWDisableReadbacks : 1, + AccurateDATE : 1, + GPUPaletteConversion : 1, + ConservativeFramebuffer : 1, + AutoFlushSW : 1, + PreloadFrameWithGSData : 1, + WrapGSMem : 1, + Mipmap : 1, + AA1 : 1, + UserHacks : 1, + UserHacks_AlignSpriteX : 1, + UserHacks_AutoFlush : 1, + UserHacks_CPUFBConversion : 1, + UserHacks_DisableDepthSupport : 1, + UserHacks_DisablePartialInvalidation : 1, + UserHacks_DisableSafeFeatures : 1, + UserHacks_MergePPSprite : 1, + UserHacks_WildHack : 1, + UserHacks_TextureInsideRt : 1, + FXAA : 1, + ShadeBoost : 1, + ShaderFX : 1, + DumpGSData : 1, + SaveRT : 1, + SaveFrame : 1, + SaveTexture : 1, + SaveDepth : 1; + }; + }; + int VsyncQueueSize{2}; // forces the MTGS to execute tags/tasks in fully blocking/synchronous @@ -337,45 +460,73 @@ struct Pcsx2Config AspectRatioType AspectRatio{AspectRatioType::R4_3}; FMVAspectRatioSwitchType FMVAspectRatioSwitch{FMVAspectRatioSwitchType::Off}; + GSInterlaceMode InterlaceMode{GSInterlaceMode::Automatic}; double Zoom{100.0}; double StretchY{100.0}; double OffsetX{0.0}; double OffsetY{0.0}; - void LoadSave(SettingsWrapper& wrap); - - int GetVsync() const; + double OsdScale{100.0}; + + GSRendererType Renderer{GSRendererType::Auto}; + uint UpscaleMultiplier{1}; + + HWMipmapLevel HWMipmap{HWMipmapLevel::Automatic}; + AccBlendLevel AccurateBlendingUnit{AccBlendLevel::Basic}; + CRCHackLevel CRCHack{CRCHackLevel::Automatic}; + BiFiltering TextureFiltering{BiFiltering::PS2}; + int Dithering{2}; + int MaxAnisotropy{0}; + int SWExtraThreads{2}; + int SWExtraThreadsHeight{4}; + int TVShader{0}; + int SkipDraw{0}; + int SkipDrawOffset{0}; + + int UserHacks_HalfBottomOverride{-1}; + int UserHacks_HalfPixelOffset{0}; + int UserHacks_RoundSprite{0}; + int UserHacks_TCOffsetX{0}; + int UserHacks_TCOffsetY{0}; + TriFiltering UserHacks_TriFilter{TriFiltering::Off}; + + int ShadeBoost_Brightness{50}; + int ShadeBoost_Contrast{50}; + int ShadeBoost_Saturation{50}; + int SaveN{0}; + int SaveL{5000}; + std::string Adapter; + std::string ShaderFX_Conf; + std::string ShaderFX_GLSL; + + GSOptions(); - bool operator==(const GSOptions& right) const - { - return OpEqu(SynchronousMTGS) && - OpEqu(VsyncQueueSize) && + void LoadSave(SettingsWrapper& wrap); - OpEqu(FrameSkipEnable) && - OpEqu(FrameLimitEnable) && - OpEqu(VsyncEnable) && +#ifndef PCSX2_CORE + /// Because some GS settings are stored in a separate INI in wx, we need a way to reload them. + /// This is because the SettingsWrapper is only created on full save/load. + void ReloadIniSettings(); +#else + void LoadSaveIniSettings(SettingsWrapper& wrap); +#endif - OpEqu(LimitScalar) && - OpEqu(FramerateNTSC) && - OpEqu(FrameratePAL) && + /// Sets user hack values to defaults when user hacks are not enabled. + void MaskUserHacks(); - OpEqu(FramesToDraw) && - OpEqu(FramesToSkip) && + /// Returns true if any of the hardware renderers are selected. + bool UseHardwareRenderer() const; - OpEqu(AspectRatio) && - OpEqu(FMVAspectRatioSwitch) && + /// Returns false if the compared to the old settings, we need to reopen GS. + /// (i.e. renderer change, swap chain mode change, etc.) + bool RestartOptionsAreEqual(const GSOptions& right) const; - OpEqu(Zoom) && - OpEqu(StretchY) && - OpEqu(OffsetX) && - OpEqu(OffsetY); - } + /// Returns false if any options need to be applied to the MTGS. + bool OptionsAreEqual(const GSOptions& right) const; - bool operator!=(const GSOptions& right) const - { - return !this->operator==(right); - } + bool operator==(const GSOptions& right) const; + bool operator!=(const GSOptions& right) const; }; struct SPU2Options @@ -463,6 +614,7 @@ struct Pcsx2Config FpuMulHack : 1, // Tales of Destiny hangs. FpuNegDivHack : 1, // Gundam games messed up camera-view. GoemonTlbHack : 1, // Gomeon tlb miss hack. The game need to access unmapped virtual address. Instead to handle it as exception, tlb are preloaded at startup + SoftwareRendererFMVHack : 1, // Switches to software renderer for FMVs SkipMPEGHack : 1, // Skips MPEG videos (Katamari and other games need this) OPHFlagHack : 1, // Bleach Blade Battlers EETimingHack : 1, // General purpose timing hack. @@ -680,6 +832,8 @@ struct Pcsx2Config bool MultitapEnabled(uint port) const; + VsyncMode GetEffectiveVsyncMode() const; + bool operator==(const Pcsx2Config& right) const; bool operator!=(const Pcsx2Config& right) const { diff --git a/pcsx2/Counters.cpp b/pcsx2/Counters.cpp index b643319561763..eb781f7c6bb8d 100644 --- a/pcsx2/Counters.cpp +++ b/pcsx2/Counters.cpp @@ -19,7 +19,6 @@ #include #include -#include "gui/App.h" #include "Common.h" #include "R3000A.h" #include "Counters.h" @@ -28,10 +27,17 @@ #include "GS.h" #include "VUmicro.h" #include "PerformanceMetrics.h" +#include "Patch.h" #include "ps2/HwInternal.h" #include "Sio.h" +#ifndef PCSX2_CORE +#include "gui/App.h" +#else +#include "VMManager.h" +#endif + #ifndef DISABLE_RECORDING # include "Recording/InputRecordingControls.h" #endif @@ -283,6 +289,12 @@ const char* ReportVideoMode() } } +const char* ReportInterlaceMode() +{ + const u64& smode2 = *(u64*)PS2GS_BASE(GS_SMODE2); + return (smode2 & 1) ? ((smode2 & 2) ? "Interlaced (Frame)" : "Interlaced (Field)") : "Progressive"; +} + double GetVerticalFrequency() { // Note about NTSC/PAL "double strike" modes: @@ -416,10 +428,68 @@ void frameLimitReset() m_iStart = GetCPUTicks(); } +// FMV switch stuff +extern uint eecount_on_last_vdec; +extern bool FMVstarted; +extern bool EnableFMV; +static bool s_last_fmv_state = false; + +static __fi void DoFMVSwitch() +{ + bool new_fmv_state = s_last_fmv_state; + if (EnableFMV) + { + DevCon.WriteLn("FMV started"); + new_fmv_state = true; + EnableFMV = false; + } + else if (FMVstarted) + { + const int diff = cpuRegs.cycle - eecount_on_last_vdec; + if (diff > 60000000) + { + DevCon.WriteLn("FMV ended"); + new_fmv_state = false; + FMVstarted = false; + } + } + + if (new_fmv_state == s_last_fmv_state) + return; + + s_last_fmv_state = new_fmv_state; + + switch (EmuConfig.GS.FMVAspectRatioSwitch) + { + case FMVAspectRatioSwitchType::Off: + break; + case FMVAspectRatioSwitchType::R4_3: + EmuConfig.CurrentAspectRatio = new_fmv_state ? AspectRatioType::R4_3 : EmuConfig.GS.AspectRatio; + break; + case FMVAspectRatioSwitchType::R16_9: + EmuConfig.CurrentAspectRatio = new_fmv_state ? AspectRatioType::R16_9 : EmuConfig.GS.AspectRatio; + break; + default: + break; + } + + if (EmuConfig.Gamefixes.SoftwareRendererFMVHack && EmuConfig.GS.UseHardwareRenderer()) + { + // we don't use the sw toggle here, because it'll change back to auto if set to sw + GetMTGS().SwitchRenderer(new_fmv_state ? GSRendererType::SW : EmuConfig.GS.Renderer, false); + } +} + // Convenience function to update UI thread and set patches. static __fi void frameLimitUpdateCore() { + DoFMVSwitch(); + +#ifndef PCSX2_CORE GetCoreThread().VsyncInThread(); +#else + VMManager::Internal::VSyncOnCPUThread(); +#endif Cpu->CheckExecutionState(); } @@ -429,7 +499,7 @@ static __fi void frameLimitUpdateCore() static __fi void frameLimit() { // Framelimiter off in settings? Framelimiter go brrr. - if (!EmuConfig.GS.FrameLimitEnable) + if (EmuConfig.GS.LimitScalar == 0.0) { frameLimitUpdateCore(); return; @@ -482,6 +552,8 @@ static __fi void VSyncStart(u32 sCycle) } #endif + PerformanceMetrics::Update(); + frameLimit(); // limit FPS gsPostVsyncStart(); // MUST be after framelimit; doing so before causes funk with frame times! diff --git a/pcsx2/Counters.h b/pcsx2/Counters.h index 6580dab2c0667..f34d2d03a5b84 100644 --- a/pcsx2/Counters.h +++ b/pcsx2/Counters.h @@ -127,6 +127,7 @@ struct SyncCounter #define MODE_HBLANK 0x1 //Set for the remaining ~1/6 of 1 Scanline extern const char* ReportVideoMode(); +extern const char* ReportInterlaceMode(); extern Counter counters[4]; extern SyncCounter hsyncCounter; extern SyncCounter vsyncCounter; diff --git a/pcsx2/Frontend/D3D11HostDisplay.cpp b/pcsx2/Frontend/D3D11HostDisplay.cpp new file mode 100644 index 0000000000000..2d1cbf94b6a5e --- /dev/null +++ b/pcsx2/Frontend/D3D11HostDisplay.cpp @@ -0,0 +1,754 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2021 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "Frontend/D3D11HostDisplay.h" + +#include "imgui.h" +#include "imgui_impl_dx11.h" +#include +#include + +#include "common/Assertions.h" +#include "common/Console.h" +#include "common/StringUtil.h" + +#include "Config.h" + +#pragma comment(lib, "d3d11.lib") +#pragma comment(lib, "dxgi.lib") + +class D3D11HostDisplayTexture : public HostDisplayTexture +{ +public: + D3D11HostDisplayTexture(Microsoft::WRL::ComPtr texture, + Microsoft::WRL::ComPtr srv, u32 width, u32 height, u32 layers, + u32 levels, bool dynamic) + : m_texture(std::move(texture)) + , m_srv(std::move(srv)) + , m_width(width) + , m_height(height) + , m_layers(layers) + , m_levels(levels) + , m_dynamic(dynamic) + { + } + ~D3D11HostDisplayTexture() override = default; + + void* GetHandle() const override { return m_srv.Get(); } + u32 GetWidth() const override { return m_width; } + u32 GetHeight() const override { return m_height; } + u32 GetLayers() const override { return m_layers; } + u32 GetLevels() const override { return m_levels; } + + __fi ID3D11Texture2D* GetD3DTexture() const { return m_texture.Get(); } + __fi ID3D11ShaderResourceView* GetD3DSRV() const { return m_srv.Get(); } + __fi ID3D11ShaderResourceView* const* GetD3DSRVArray() const { return m_srv.GetAddressOf(); } + __fi bool IsDynamic() const { return m_dynamic; } + +private: + Microsoft::WRL::ComPtr m_texture; + Microsoft::WRL::ComPtr m_srv; + u32 m_width; + u32 m_height; + u32 m_layers; + u32 m_levels; + bool m_dynamic; +}; + +static Microsoft::WRL::ComPtr CreateVertexShader(ID3D11Device* device, const void* bytecode, + size_t bytecode_length) +{ + Microsoft::WRL::ComPtr shader; + const HRESULT hr = device->CreateVertexShader(bytecode, bytecode_length, nullptr, shader.GetAddressOf()); + if (FAILED(hr)) + { + Console.Error("Failed to create vertex shader: 0x%08X", hr); + return {}; + } + + return shader; +} + +static Microsoft::WRL::ComPtr CreatePixelShader(ID3D11Device* device, const void* bytecode, + size_t bytecode_length) +{ + Microsoft::WRL::ComPtr shader; + const HRESULT hr = device->CreatePixelShader(bytecode, bytecode_length, nullptr, shader.GetAddressOf()); + if (FAILED(hr)) + { + Console.Error("Failed to create pixel shader: 0x%08X", hr); + return {}; + } + + return shader; +} + +D3D11HostDisplay::D3D11HostDisplay() = default; + +D3D11HostDisplay::~D3D11HostDisplay() +{ + pxAssertMsg(!m_context, "Context should have been destroyed by now"); + pxAssertMsg(!m_swap_chain, "Swap chain should have been destroyed by now"); +} + +HostDisplay::RenderAPI D3D11HostDisplay::GetRenderAPI() const +{ + return HostDisplay::RenderAPI::D3D11; +} + +void* D3D11HostDisplay::GetRenderDevice() const +{ + return m_device.Get(); +} + +void* D3D11HostDisplay::GetRenderContext() const +{ + return m_context.Get(); +} + +void* D3D11HostDisplay::GetRenderSurface() const +{ + return m_swap_chain.Get(); +} + +bool D3D11HostDisplay::HasRenderDevice() const +{ + return static_cast(m_device); +} + +bool D3D11HostDisplay::HasRenderSurface() const +{ + return static_cast(m_swap_chain); +} + +std::unique_ptr D3D11HostDisplay::CreateTexture(u32 width, u32 height, u32 layers, u32 levels, + const void* data, u32 data_stride, bool dynamic /* = false */) +{ + const CD3D11_TEXTURE2D_DESC desc(DXGI_FORMAT_R8G8B8A8_UNORM, width, height, layers, levels, + D3D11_BIND_SHADER_RESOURCE, dynamic ? D3D11_USAGE_DYNAMIC : D3D11_USAGE_DEFAULT, + dynamic ? D3D11_CPU_ACCESS_WRITE : 0, 1, 0, 0); + const D3D11_SUBRESOURCE_DATA srd{data, data_stride, data_stride * height}; + ComPtr texture; + HRESULT hr = m_device->CreateTexture2D(&desc, data ? &srd : nullptr, texture.GetAddressOf()); + if (FAILED(hr)) + return {}; + + const CD3D11_SHADER_RESOURCE_VIEW_DESC srv_desc(D3D11_SRV_DIMENSION_TEXTURE2D, DXGI_FORMAT_R8G8B8A8_UNORM, 0, 1, 0, + 1); + ComPtr srv; + hr = m_device->CreateShaderResourceView(texture.Get(), &srv_desc, srv.GetAddressOf()); + if (FAILED(hr)) + return {}; + + return std::make_unique(std::move(texture), std::move(srv), width, height, layers, levels, dynamic); +} + +void D3D11HostDisplay::UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, + const void* texture_data, u32 texture_data_stride) +{ + D3D11HostDisplayTexture* d3d11_texture = static_cast(texture); + if (!d3d11_texture->IsDynamic()) + { + const CD3D11_BOX dst_box(x, y, 0, x + width, y + height, 1); + m_context->UpdateSubresource(d3d11_texture->GetD3DTexture(), 0, &dst_box, texture_data, texture_data_stride, + texture_data_stride * height); + } + else + { + D3D11_MAPPED_SUBRESOURCE sr; + HRESULT hr = m_context->Map(d3d11_texture->GetD3DTexture(), 0, D3D11_MAP_WRITE_DISCARD, 0, &sr); + pxAssertMsg(SUCCEEDED(hr), "Failed to map dynamic host display texture"); + + char* dst_ptr = static_cast(sr.pData) + (y * sr.RowPitch) + (x * sizeof(u32)); + const char* src_ptr = static_cast(texture_data); + if (sr.RowPitch == texture_data_stride) + { + std::memcpy(dst_ptr, src_ptr, texture_data_stride * height); + } + else + { + for (u32 row = 0; row < height; row++) + { + std::memcpy(dst_ptr, src_ptr, width * sizeof(u32)); + src_ptr += texture_data_stride; + dst_ptr += sr.RowPitch; + } + } + + m_context->Unmap(d3d11_texture->GetD3DTexture(), 0); + } +} + +bool D3D11HostDisplay::GetHostRefreshRate(float* refresh_rate) +{ + if (m_swap_chain && IsFullscreen()) + { + DXGI_SWAP_CHAIN_DESC desc; + if (SUCCEEDED(m_swap_chain->GetDesc(&desc)) && desc.BufferDesc.RefreshRate.Numerator > 0 && + desc.BufferDesc.RefreshRate.Denominator > 0) + { + DevCon.WriteLn("using fs rr: %u %u", desc.BufferDesc.RefreshRate.Numerator, + desc.BufferDesc.RefreshRate.Denominator); + *refresh_rate = static_cast(desc.BufferDesc.RefreshRate.Numerator) / + static_cast(desc.BufferDesc.RefreshRate.Denominator); + return true; + } + } + + return HostDisplay::GetHostRefreshRate(refresh_rate); +} + +void D3D11HostDisplay::SetVSync(VsyncMode mode) +{ + m_vsync = mode; +} + +bool D3D11HostDisplay::CreateRenderDevice(const WindowInfo& wi, std::string_view adapter_name, bool debug_device) +{ + UINT create_flags = 0; + if (debug_device) + create_flags |= D3D11_CREATE_DEVICE_DEBUG; + + ComPtr temp_dxgi_factory; +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + HRESULT hr = CreateDXGIFactory(IID_PPV_ARGS(temp_dxgi_factory.GetAddressOf())); +#else + HRESULT hr = CreateDXGIFactory2(0, IID_PPV_ARGS(temp_dxgi_factory.GetAddressOf())); +#endif + if (FAILED(hr)) + { + Console.Error("Failed to create DXGI factory: 0x%08X", hr); + return false; + } + + u32 adapter_index; + if (!adapter_name.empty()) + { + AdapterAndModeList adapter_info(GetAdapterAndModeList(temp_dxgi_factory.Get())); + for (adapter_index = 0; adapter_index < static_cast(adapter_info.adapter_names.size()); adapter_index++) + { + if (adapter_name == adapter_info.adapter_names[adapter_index]) + break; + } + if (adapter_index == static_cast(adapter_info.adapter_names.size())) + { + Console.Warning("Could not find adapter '%s', using first (%s)", std::string(adapter_name).c_str(), + adapter_info.adapter_names[0].c_str()); + adapter_index = 0; + } + } + else + { + Console.WriteLn("No adapter selected, using first."); + adapter_index = 0; + } + + ComPtr dxgi_adapter; + hr = temp_dxgi_factory->EnumAdapters(adapter_index, dxgi_adapter.GetAddressOf()); + if (FAILED(hr)) + Console.Warning("Failed to enumerate adapter %u, using default", adapter_index); + + static constexpr std::array requested_feature_levels = { + {D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0}}; + + hr = + D3D11CreateDevice(dxgi_adapter.Get(), dxgi_adapter ? D3D_DRIVER_TYPE_UNKNOWN : D3D_DRIVER_TYPE_HARDWARE, nullptr, + create_flags, requested_feature_levels.data(), static_cast(requested_feature_levels.size()), + D3D11_SDK_VERSION, m_device.GetAddressOf(), nullptr, m_context.GetAddressOf()); + + // we re-grab these later, see below + dxgi_adapter.Reset(); + temp_dxgi_factory.Reset(); + + if (FAILED(hr)) + { + Console.Error("Failed to create D3D device: 0x%08X", hr); + return false; + } + + if (debug_device && IsDebuggerPresent()) + { + ComPtr info; + hr = m_device.As(&info); + if (SUCCEEDED(hr)) + { + info->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_ERROR, TRUE); + info->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_WARNING, TRUE); + } + } + + // we need the specific factory for the device, otherwise MakeWindowAssociation() is flaky. + ComPtr dxgi_device; + if (FAILED(m_device.As(&dxgi_device)) || FAILED(dxgi_device->GetParent(IID_PPV_ARGS(dxgi_adapter.GetAddressOf()))) || + FAILED(dxgi_adapter->GetParent(IID_PPV_ARGS(m_dxgi_factory.GetAddressOf())))) + { + Console.Warning("Failed to get parent adapter/device/factory"); + return false; + } + + DXGI_ADAPTER_DESC adapter_desc; + if (SUCCEEDED(dxgi_adapter->GetDesc(&adapter_desc))) + { + char adapter_name_buffer[128]; + const int name_length = + WideCharToMultiByte(CP_UTF8, 0, adapter_desc.Description, static_cast(std::wcslen(adapter_desc.Description)), + adapter_name_buffer, sizeof(adapter_name_buffer), 0, nullptr); + if (name_length >= 0) + { + adapter_name_buffer[name_length] = 0; + Console.WriteLn("D3D Adapter: %s", adapter_name_buffer); + } + } + + m_allow_tearing_supported = false; + ComPtr dxgi_factory5; + hr = m_dxgi_factory.As(&dxgi_factory5); + if (SUCCEEDED(hr)) + { + BOOL allow_tearing_supported = false; + hr = dxgi_factory5->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allow_tearing_supported, + sizeof(allow_tearing_supported)); + if (SUCCEEDED(hr)) + m_allow_tearing_supported = (allow_tearing_supported == TRUE); + } + + m_window_info = wi; + return true; +} + +bool D3D11HostDisplay::InitializeRenderDevice(std::string_view shader_cache_directory, bool debug_device) +{ + if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain(nullptr)) + return false; + + return true; +} + +void D3D11HostDisplay::DestroyRenderDevice() +{ + DestroyRenderSurface(); + m_context.Reset(); + m_device.Reset(); +} + +bool D3D11HostDisplay::MakeRenderContextCurrent() +{ + return true; +} + +bool D3D11HostDisplay::DoneRenderContextCurrent() +{ + return true; +} + +bool D3D11HostDisplay::CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode) +{ + HRESULT hr; + +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + if (m_window_info.type != WindowInfo::Type::Win32) + return false; + + m_using_flip_model_swap_chain = !EmuConfig.GS.UseBlitSwapChain || fullscreen_mode; + + const HWND window_hwnd = reinterpret_cast(m_window_info.window_handle); + RECT client_rc{}; + GetClientRect(window_hwnd, &client_rc); + const u32 width = static_cast(client_rc.right - client_rc.left); + const u32 height = static_cast(client_rc.bottom - client_rc.top); + + DXGI_SWAP_CHAIN_DESC swap_chain_desc = {}; + swap_chain_desc.BufferDesc.Width = width; + swap_chain_desc.BufferDesc.Height = height; + swap_chain_desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + swap_chain_desc.SampleDesc.Count = 1; + swap_chain_desc.BufferCount = 3; + swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + swap_chain_desc.OutputWindow = window_hwnd; + swap_chain_desc.Windowed = TRUE; + swap_chain_desc.SwapEffect = m_using_flip_model_swap_chain ? DXGI_SWAP_EFFECT_FLIP_DISCARD : DXGI_SWAP_EFFECT_DISCARD; + + m_using_allow_tearing = (m_allow_tearing_supported && m_using_flip_model_swap_chain && !fullscreen_mode); + if (m_using_allow_tearing) + swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; + + if (fullscreen_mode) + { + swap_chain_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; + swap_chain_desc.Windowed = FALSE; + swap_chain_desc.BufferDesc = *fullscreen_mode; + } + + Console.WriteLn("Creating a %dx%d %s %s swap chain", swap_chain_desc.BufferDesc.Width, + swap_chain_desc.BufferDesc.Height, m_using_flip_model_swap_chain ? "flip-discard" : "discard", + swap_chain_desc.Windowed ? "windowed" : "full-screen"); + + hr = m_dxgi_factory->CreateSwapChain(m_device.Get(), &swap_chain_desc, m_swap_chain.GetAddressOf()); + if (FAILED(hr) && m_using_flip_model_swap_chain) + { + Console.Warning("Failed to create a flip-discard swap chain, trying discard."); + swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; + swap_chain_desc.Flags = 0; + m_using_flip_model_swap_chain = false; + m_using_allow_tearing = false; + + hr = m_dxgi_factory->CreateSwapChain(m_device.Get(), &swap_chain_desc, m_swap_chain.GetAddressOf()); + if (FAILED(hr)) + { + Console.Error("CreateSwapChain failed: 0x%08X", hr); + return false; + } + } + + ComPtr dxgi_factory; + hr = m_swap_chain->GetParent(IID_PPV_ARGS(dxgi_factory.GetAddressOf())); + if (SUCCEEDED(hr)) + { + hr = dxgi_factory->MakeWindowAssociation(swap_chain_desc.OutputWindow, DXGI_MWA_NO_WINDOW_CHANGES); + if (FAILED(hr)) + Console.Warning("MakeWindowAssociation() to disable ALT+ENTER failed"); + } +#else + if (m_window_info.type != WindowInfo::Type::WinRT) + return false; + + ComPtr factory2; + hr = m_dxgi_factory.As(&factory2); + if (FAILED(hr)) + { + Console.Error("Failed to get DXGI factory: %08X", hr); + return false; + } + + DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {}; + swap_chain_desc.Width = m_window_info.surface_width; + swap_chain_desc.Height = m_window_info.surface_height; + swap_chain_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + swap_chain_desc.SampleDesc.Count = 1; + swap_chain_desc.BufferCount = 3; + swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + swap_chain_desc.SwapEffect = m_using_flip_model_swap_chain ? DXGI_SWAP_EFFECT_FLIP_DISCARD : DXGI_SWAP_EFFECT_DISCARD; + + m_using_allow_tearing = (m_allow_tearing_supported && m_using_flip_model_swap_chain && !fullscreen_mode); + if (m_using_allow_tearing) + swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; + + ComPtr swap_chain1; + hr = factory2->CreateSwapChainForCoreWindow(m_device.Get(), static_cast(m_window_info.window_handle), + &swap_chain_desc, nullptr, swap_chain1.GetAddressOf()); + if (FAILED(hr)) + { + Console.Error("CreateSwapChainForCoreWindow failed: 0x%08X", hr); + return false; + } + + m_swap_chain = swap_chain1; +#endif + + return CreateSwapChainRTV(); +} + +bool D3D11HostDisplay::CreateSwapChainRTV() +{ + ComPtr backbuffer; + HRESULT hr = m_swap_chain->GetBuffer(0, IID_PPV_ARGS(backbuffer.GetAddressOf())); + if (FAILED(hr)) + { + Console.Error("GetBuffer for RTV failed: 0x%08X", hr); + return false; + } + + D3D11_TEXTURE2D_DESC backbuffer_desc; + backbuffer->GetDesc(&backbuffer_desc); + + CD3D11_RENDER_TARGET_VIEW_DESC rtv_desc(D3D11_RTV_DIMENSION_TEXTURE2D, backbuffer_desc.Format, 0, 0, + backbuffer_desc.ArraySize); + hr = m_device->CreateRenderTargetView(backbuffer.Get(), &rtv_desc, m_swap_chain_rtv.GetAddressOf()); + if (FAILED(hr)) + { + Console.Error("CreateRenderTargetView for swap chain failed: 0x%08X", hr); + return false; + } + + m_window_info.surface_width = backbuffer_desc.Width; + m_window_info.surface_height = backbuffer_desc.Height; + DevCon.WriteLn("Swap chain buffer size: %ux%u", m_window_info.surface_width, m_window_info.surface_height); + + if (m_window_info.type == WindowInfo::Type::Win32) + { + BOOL fullscreen = FALSE; + DXGI_SWAP_CHAIN_DESC desc; + if (SUCCEEDED(m_swap_chain->GetFullscreenState(&fullscreen, nullptr)) && fullscreen && + SUCCEEDED(m_swap_chain->GetDesc(&desc))) + { + m_window_info.surface_refresh_rate = static_cast(desc.BufferDesc.RefreshRate.Numerator) / + static_cast(desc.BufferDesc.RefreshRate.Denominator); + } + else + { + m_window_info.surface_refresh_rate = 0.0f; + } + } + + return true; +} + +bool D3D11HostDisplay::ChangeRenderWindow(const WindowInfo& new_wi) +{ + DestroyRenderSurface(); + + m_window_info = new_wi; + return CreateSwapChain(nullptr); +} + +void D3D11HostDisplay::DestroyRenderSurface() +{ + if (IsFullscreen()) + SetFullscreen(false, 0, 0, 0.0f); + + m_swap_chain_rtv.Reset(); + m_swap_chain.Reset(); +} + +void D3D11HostDisplay::ResizeRenderWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) +{ + if (!m_swap_chain) + return; + + m_window_info.surface_scale = new_window_scale; + + if (m_window_info.surface_width == new_window_width && m_window_info.surface_height == new_window_height) + return; + + m_swap_chain_rtv.Reset(); + + HRESULT hr = m_swap_chain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, + m_using_allow_tearing ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0); + if (FAILED(hr)) + Console.Error("ResizeBuffers() failed: 0x%08X", hr); + + if (!CreateSwapChainRTV()) + pxFailRel("Failed to recreate swap chain RTV after resize"); +} + +bool D3D11HostDisplay::SupportsFullscreen() const +{ + return true; +} + +bool D3D11HostDisplay::IsFullscreen() +{ + BOOL is_fullscreen = FALSE; + return (m_swap_chain && SUCCEEDED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) && is_fullscreen); +} + +bool D3D11HostDisplay::SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) +{ + if (!m_swap_chain) + return false; + + BOOL is_fullscreen = FALSE; + HRESULT hr = m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr); + if (!fullscreen) + { + // leaving fullscreen + if (is_fullscreen) + return SUCCEEDED(m_swap_chain->SetFullscreenState(FALSE, nullptr)); + else + return true; + } + + IDXGIOutput* output; + if (FAILED(hr = m_swap_chain->GetContainingOutput(&output))) + return false; + + DXGI_SWAP_CHAIN_DESC current_desc; + hr = m_swap_chain->GetDesc(¤t_desc); + if (FAILED(hr)) + return false; + + DXGI_MODE_DESC new_mode = current_desc.BufferDesc; + new_mode.Width = width; + new_mode.Height = height; + new_mode.RefreshRate.Numerator = static_cast(std::floor(refresh_rate * 1000.0f)); + new_mode.RefreshRate.Denominator = 1000u; + + DXGI_MODE_DESC closest_mode; + if (FAILED(hr = output->FindClosestMatchingMode(&new_mode, &closest_mode, nullptr)) || + new_mode.Format != current_desc.BufferDesc.Format) + { + Console.Error("Failed to find closest matching mode, hr=%08X", hr); + return false; + } + + if (new_mode.Width == current_desc.BufferDesc.Width && new_mode.Height == current_desc.BufferDesc.Height && + new_mode.RefreshRate.Numerator == current_desc.BufferDesc.RefreshRate.Numerator && + new_mode.RefreshRate.Denominator == current_desc.BufferDesc.RefreshRate.Denominator) + { + DevCon.WriteLn("Fullscreen mode already set"); + return true; + } + + m_swap_chain_rtv.Reset(); + m_swap_chain.Reset(); + + if (!CreateSwapChain(&closest_mode)) + { + Console.Error("Failed to create a fullscreen swap chain"); + if (!CreateSwapChain(nullptr)) + pxFailRel("Failed to recreate windowed swap chain"); + + return false; + } + + return true; +} + +bool D3D11HostDisplay::CreateImGuiContext() +{ + return ImGui_ImplDX11_Init(m_device.Get(), m_context.Get()); +} + +void D3D11HostDisplay::DestroyImGuiContext() +{ + ImGui_ImplDX11_Shutdown(); +} + +bool D3D11HostDisplay::UpdateImGuiFontTexture() +{ + ImGui_ImplDX11_CreateFontsTexture(); + return true; +} + +bool D3D11HostDisplay::BeginPresent(bool frame_skip) +{ + if (frame_skip || !m_swap_chain) + { + ImGui::EndFrame(); + return false; + } + + static constexpr std::array clear_color = {}; + m_context->ClearRenderTargetView(m_swap_chain_rtv.Get(), clear_color.data()); + m_context->OMSetRenderTargets(1, m_swap_chain_rtv.GetAddressOf(), nullptr); + + const CD3D11_VIEWPORT vp(0.0f, 0.0f, static_cast(m_window_info.surface_width), static_cast(m_window_info.surface_height)); + const CD3D11_RECT scissor(0, 0, m_window_info.surface_width, m_window_info.surface_height); + m_context->RSSetViewports(1, &vp); + m_context->RSSetScissorRects(1, &scissor); + return true; +} + +void D3D11HostDisplay::EndPresent() +{ + ImGui::Render(); + ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData()); + + const UINT vsync_rate = static_cast(m_vsync != VsyncMode::Off); + if (vsync_rate == 0 && m_using_allow_tearing) + m_swap_chain->Present(0, DXGI_PRESENT_ALLOW_TEARING); + else + m_swap_chain->Present(vsync_rate, 0); +} + +HostDisplay::AdapterAndModeList D3D11HostDisplay::StaticGetAdapterAndModeList() +{ + ComPtr dxgi_factory; +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + HRESULT hr = CreateDXGIFactory(IID_PPV_ARGS(dxgi_factory.GetAddressOf())); +#else + HRESULT hr = CreateDXGIFactory2(0, IID_PPV_ARGS(dxgi_factory.GetAddressOf())); +#endif + if (FAILED(hr)) + return {}; + + return GetAdapterAndModeList(dxgi_factory.Get()); +} + +HostDisplay::AdapterAndModeList D3D11HostDisplay::GetAdapterAndModeList(IDXGIFactory* dxgi_factory) +{ + AdapterAndModeList adapter_info; + ComPtr current_adapter; + while (SUCCEEDED(dxgi_factory->EnumAdapters(static_cast(adapter_info.adapter_names.size()), + current_adapter.ReleaseAndGetAddressOf()))) + { + DXGI_ADAPTER_DESC adapter_desc; + std::string adapter_name; + if (SUCCEEDED(current_adapter->GetDesc(&adapter_desc))) + { + char adapter_name_buffer[128]; + const int name_length = WideCharToMultiByte(CP_UTF8, 0, adapter_desc.Description, + static_cast(std::wcslen(adapter_desc.Description)), + adapter_name_buffer, sizeof(adapter_name_buffer), 0, nullptr); + if (name_length >= 0) + adapter_name.assign(adapter_name_buffer, static_cast(name_length)); + else + adapter_name.assign("(Unknown)"); + } + else + { + adapter_name.assign("(Unknown)"); + } + + if (adapter_info.fullscreen_modes.empty()) + { + ComPtr output; + if (SUCCEEDED(current_adapter->EnumOutputs(0, &output))) + { + UINT num_modes = 0; + if (SUCCEEDED(output->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, 0, &num_modes, nullptr))) + { + std::vector modes(num_modes); + if (SUCCEEDED(output->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, 0, &num_modes, modes.data()))) + { + for (const DXGI_MODE_DESC& mode : modes) + { + adapter_info.fullscreen_modes.push_back(GetFullscreenModeString( + mode.Width, mode.Height, + static_cast(mode.RefreshRate.Numerator) / static_cast(mode.RefreshRate.Denominator))); + } + } + } + } + } + + // handle duplicate adapter names + if (std::any_of(adapter_info.adapter_names.begin(), adapter_info.adapter_names.end(), + [&adapter_name](const std::string& other) { return (adapter_name == other); })) + { + std::string original_adapter_name = std::move(adapter_name); + + u32 current_extra = 2; + do + { + adapter_name = StringUtil::StdStringFromFormat("%s (%u)", original_adapter_name.c_str(), current_extra); + current_extra++; + } while (std::any_of(adapter_info.adapter_names.begin(), adapter_info.adapter_names.end(), + [&adapter_name](const std::string& other) { return (adapter_name == other); })); + } + + adapter_info.adapter_names.push_back(std::move(adapter_name)); + } + + return adapter_info; +} + +HostDisplay::AdapterAndModeList D3D11HostDisplay::GetAdapterAndModeList() +{ + return GetAdapterAndModeList(m_dxgi_factory.Get()); +} + diff --git a/pcsx2/Frontend/D3D11HostDisplay.h b/pcsx2/Frontend/D3D11HostDisplay.h new file mode 100644 index 0000000000000..441cb72fa5c51 --- /dev/null +++ b/pcsx2/Frontend/D3D11HostDisplay.h @@ -0,0 +1,98 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2021 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once +#include "HostDisplay.h" +#include "common/RedtapeWindows.h" +#include "common/WindowInfo.h" +#include +#include +#include +#include +#include +#include +#include + +class D3D11HostDisplay final : public HostDisplay +{ +public: + template + using ComPtr = Microsoft::WRL::ComPtr; + + D3D11HostDisplay(); + ~D3D11HostDisplay(); + + RenderAPI GetRenderAPI() const override; + void* GetRenderDevice() const override; + void* GetRenderContext() const override; + void* GetRenderSurface() const override; + + bool HasRenderDevice() const override; + bool HasRenderSurface() const override; + + bool CreateRenderDevice(const WindowInfo& wi, std::string_view adapter_name, bool debug_device) override; + bool InitializeRenderDevice(std::string_view shader_cache_directory, bool debug_device) override; + void DestroyRenderDevice() override; + + bool MakeRenderContextCurrent() override; + bool DoneRenderContextCurrent() override; + + bool ChangeRenderWindow(const WindowInfo& new_wi) override; + void ResizeRenderWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override; + bool SupportsFullscreen() const override; + bool IsFullscreen() override; + bool SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override; + AdapterAndModeList GetAdapterAndModeList() override; + void DestroyRenderSurface() override; + + std::unique_ptr CreateTexture(u32 width, u32 height, u32 layers, u32 levels, + const void* data, u32 data_stride, bool dynamic = false) override; + void UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* texture_data, + u32 texture_data_stride) override; + + bool GetHostRefreshRate(float* refresh_rate) override; + + void SetVSync(VsyncMode mode) override; + + bool BeginPresent(bool frame_skip) override; + void EndPresent() override; + + static AdapterAndModeList StaticGetAdapterAndModeList(); + +protected: + static constexpr u32 DISPLAY_CONSTANT_BUFFER_SIZE = 16; + + static AdapterAndModeList GetAdapterAndModeList(IDXGIFactory* dxgi_factory); + + bool CreateImGuiContext() override; + void DestroyImGuiContext() override; + bool UpdateImGuiFontTexture() override; + + bool CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode); + bool CreateSwapChainRTV(); + + ComPtr m_device; + ComPtr m_context; + + ComPtr m_dxgi_factory; + ComPtr m_swap_chain; + ComPtr m_swap_chain_rtv; + + VsyncMode m_vsync = VsyncMode::Off; + bool m_allow_tearing_supported = false; + bool m_using_flip_model_swap_chain = true; + bool m_using_allow_tearing = false; +}; + diff --git a/pcsx2/Frontend/ImGuiManager.cpp b/pcsx2/Frontend/ImGuiManager.cpp new file mode 100644 index 0000000000000..ccfa366dd308b --- /dev/null +++ b/pcsx2/Frontend/ImGuiManager.cpp @@ -0,0 +1,614 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2021 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include +#include +#include + +#include "common/StringHelpers.h" +#include "common/StringUtil.h" +#include "imgui.h" + +#include "Config.h" +#include "Counters.h" +#include "Frontend/ImGuiManager.h" +#include "GS.h" +#include "GS/GS.h" +#include "Host.h" +#include "HostDisplay.h" +#include "IconsFontAwesome5.h" +#include "PerformanceMetrics.h" + +#ifdef PCSX2_CORE +#include "VMManager.h" +#endif + +static void SetImGuiStyle(); +static bool LoadFontData(); +static void UnloadFontData(); +static bool AddImGuiFonts(); + +static float s_global_scale = 1.0f; + +static ImFont* s_standard_font; +static ImFont* s_fixed_font; + +static std::vector s_standard_font_data; +static std::vector s_fixed_font_data; +static std::vector s_icon_font_data; + +bool ImGuiManager::Initialize() +{ + if (!LoadFontData()) + { + pxFailRel("Failed to load font data"); + return false; + } + + HostDisplay* display = Host::GetHostDisplay(); + + ImGui::CreateContext(); + ImGui::GetIO().IniFilename = nullptr; + + s_global_scale = std::max(1.0f, display->GetWindowScale() * static_cast(EmuConfig.GS.OsdScale / 100.0)); + + ImGui::GetIO().DisplayFramebufferScale = ImVec2(display->GetWindowScale(), display->GetWindowScale()); + ImGui::GetIO().DisplaySize.x = static_cast(display->GetWindowWidth()); + ImGui::GetIO().DisplaySize.y = static_cast(display->GetWindowHeight()); + ImGui::GetStyle() = ImGuiStyle(); + ImGui::GetStyle().WindowMinSize = ImVec2(1.0f, 1.0f); + SetImGuiStyle(); + ImGui::GetStyle().ScaleAllSizes(s_global_scale); + + if (!display->CreateImGuiContext()) + { + pxFailRel("Failed to create ImGui device context"); + ImGui::DestroyContext(); + UnloadFontData(); + return false; + } + + if (!AddImGuiFonts() || !display->UpdateImGuiFontTexture()) + { + pxFailRel("Failed to create ImGui font text"); + display->DestroyImGuiContext(); + ImGui::DestroyContext(); + UnloadFontData(); + return false; + } + + NewFrame(); + return true; +} + +void ImGuiManager::Shutdown() +{ + HostDisplay* display = Host::GetHostDisplay(); + if (display) + display->DestroyImGuiContext(); + if (ImGui::GetCurrentContext()) + ImGui::DestroyContext(); + UnloadFontData(); +} + +void ImGuiManager::WindowResized() +{ + HostDisplay* display = Host::GetHostDisplay(); + + const u32 new_width = display ? display->GetWindowWidth() : 0; + const u32 new_height = display ? display->GetWindowHeight() : 0; + const float new_scale = (display ? display->GetWindowScale() : 1.0f); + + ImGui::GetIO().DisplaySize = ImVec2(static_cast(new_width), static_cast(new_height)); + ImGui::GetIO().DisplayFramebufferScale = ImVec2(new_scale, new_scale); + + UpdateScale(); +} + +void ImGuiManager::UpdateScale() +{ + const float scale = + std::max(ImGui::GetIO().DisplayFramebufferScale.x * static_cast(EmuConfig.GS.OsdScale / 100.0), 1.0f); + if (scale == s_global_scale) + return; + + // This is assumed to be called mid-frame. + ImGui::EndFrame(); + + s_global_scale = scale; + + HostDisplay* display = Host::GetHostDisplay(); + + ImGui::GetStyle() = ImGuiStyle(); + ImGui::GetStyle().WindowMinSize = ImVec2(1.0f, 1.0f); + SetImGuiStyle(); + ImGui::GetStyle().ScaleAllSizes(scale); + + if (!AddImGuiFonts() || !display->UpdateImGuiFontTexture()) + pxFailRel("Failed to create ImGui font text"); + + if (!display->UpdateImGuiFontTexture()) + pxFailRel("Failed to recreate font texture after scale+resize"); + + NewFrame(); +} + +void ImGuiManager::NewFrame() +{ + ImGui::NewFrame(); +} + +void SetImGuiStyle() +{ + ImGuiStyle* style = &ImGui::GetStyle(); + ImVec4* colors = style->Colors; + + colors[ImGuiCol_Text] = ImVec4(0.95f, 0.96f, 0.98f, 1.00f); + colors[ImGuiCol_TextDisabled] = ImVec4(0.36f, 0.42f, 0.47f, 1.00f); + colors[ImGuiCol_WindowBg] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f); + colors[ImGuiCol_ChildBg] = ImVec4(0.15f, 0.18f, 0.22f, 1.00f); + colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f); + colors[ImGuiCol_Border] = ImVec4(0.08f, 0.10f, 0.12f, 1.00f); + colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_FrameBg] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.12f, 0.20f, 0.28f, 1.00f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.09f, 0.12f, 0.14f, 1.00f); + colors[ImGuiCol_TitleBg] = ImVec4(0.09f, 0.12f, 0.14f, 0.65f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.08f, 0.10f, 0.12f, 1.00f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f); + colors[ImGuiCol_MenuBarBg] = ImVec4(0.15f, 0.18f, 0.22f, 1.00f); + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.39f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.18f, 0.22f, 0.25f, 1.00f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.09f, 0.21f, 0.31f, 1.00f); + colors[ImGuiCol_CheckMark] = ImVec4(0.28f, 0.56f, 1.00f, 1.00f); + colors[ImGuiCol_SliderGrab] = ImVec4(0.28f, 0.56f, 1.00f, 1.00f); + colors[ImGuiCol_SliderGrabActive] = ImVec4(0.37f, 0.61f, 1.00f, 1.00f); + colors[ImGuiCol_Button] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.33f, 0.38f, 0.46f, 1.00f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.27f, 0.32f, 0.38f, 1.00f); + colors[ImGuiCol_Header] = ImVec4(0.20f, 0.25f, 0.29f, 0.55f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.33f, 0.38f, 0.46f, 1.00f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.27f, 0.32f, 0.38f, 1.00f); + colors[ImGuiCol_Separator] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f); + colors[ImGuiCol_SeparatorHovered] = ImVec4(0.33f, 0.38f, 0.46f, 1.00f); + colors[ImGuiCol_SeparatorActive] = ImVec4(0.27f, 0.32f, 0.38f, 1.00f); + colors[ImGuiCol_ResizeGrip] = ImVec4(0.26f, 0.59f, 0.98f, 0.25f); + colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.33f, 0.38f, 0.46f, 1.00f); + colors[ImGuiCol_ResizeGripActive] = ImVec4(0.27f, 0.32f, 0.38f, 1.00f); + colors[ImGuiCol_Tab] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f); + colors[ImGuiCol_TabHovered] = ImVec4(0.33f, 0.38f, 0.46f, 1.00f); + colors[ImGuiCol_TabActive] = ImVec4(0.27f, 0.32f, 0.38f, 1.00f); + colors[ImGuiCol_TabUnfocused] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f); + colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f); + colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f); + colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); + colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); + colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f); + colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); + colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); + colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); + colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f); +} + +bool LoadFontData() +{ + if (s_standard_font_data.empty()) + { + std::optional> font_data = Host::ReadResourceFile("fonts/Roboto-Regular.ttf"); + if (!font_data.has_value()) + return false; + + s_standard_font_data = std::move(font_data.value()); + } + + if (s_fixed_font_data.empty()) + { + std::optional> font_data = Host::ReadResourceFile("fonts/RobotoMono-Medium.ttf"); + if (!font_data.has_value()) + return false; + + s_fixed_font_data = std::move(font_data.value()); + } + + if (s_icon_font_data.empty()) + { + std::optional> font_data = Host::ReadResourceFile("fonts/fa-solid-900.ttf"); + if (!font_data.has_value()) + return false; + + s_icon_font_data = std::move(font_data.value()); + } + + return true; +} + +void UnloadFontData() +{ + std::vector().swap(s_standard_font_data); + std::vector().swap(s_fixed_font_data); + std::vector().swap(s_icon_font_data); +} + +static ImFont* AddTextFont(float size /*= 15.0f*/) +{ + static const ImWchar default_ranges[] = { + // Basic Latin + Latin Supplement + Central European diacritics + 0x0020, + 0x017F, + + // Cyrillic + Cyrillic Supplement + 0x0400, + 0x052F, + + // Cyrillic Extended-A + 0x2DE0, + 0x2DFF, + + // Cyrillic Extended-B + 0xA640, + 0xA69F, + + 0, + }; + + ImFontConfig cfg; + cfg.FontDataOwnedByAtlas = false; + return ImGui::GetIO().Fonts->AddFontFromMemoryTTF( + s_standard_font_data.data(), static_cast(s_standard_font_data.size()), size, &cfg, default_ranges); +} + +static ImFont* AddFixedFont(float size) +{ + ImFontConfig cfg; + cfg.FontDataOwnedByAtlas = false; + return ImGui::GetIO().Fonts->AddFontFromMemoryTTF(s_fixed_font_data.data(), + static_cast(s_fixed_font_data.size()), size, &cfg, nullptr); +} + +static bool AddIconFonts(float size) +{ + static const ImWchar range_fa[] = {ICON_MIN_FA, ICON_MAX_FA, 0}; + + ImFontConfig cfg; + cfg.MergeMode = true; + cfg.PixelSnapH = true; + cfg.GlyphMinAdvanceX = size * 0.75f; + cfg.GlyphMaxAdvanceX = size * 0.75f; + cfg.FontDataOwnedByAtlas = false; + + return (ImGui::GetIO().Fonts->AddFontFromMemoryTTF(s_icon_font_data.data(), static_cast(s_icon_font_data.size()), + size * 0.75f, &cfg, range_fa) != nullptr); +} + +bool AddImGuiFonts() +{ + const float standard_font_size = std::ceil(15.0f * s_global_scale); + + ImGuiIO& io = ImGui::GetIO(); + io.Fonts->Clear(); + + s_standard_font = AddTextFont(standard_font_size); + if (!s_standard_font || !AddIconFonts(standard_font_size)) + return false; + + s_fixed_font = AddFixedFont(standard_font_size); + if (!s_fixed_font) + return false; + + return io.Fonts->Build(); +} + +struct OSDMessage +{ + std::string key; + std::string text; + std::chrono::steady_clock::time_point time; + float duration; +}; + +static std::deque s_osd_active_messages; +static std::deque s_osd_posted_messages; +static std::mutex s_osd_messages_lock; + +void Host::AddOSDMessage(std::string message, float duration /*= 2.0f*/) +{ + AddKeyedOSDMessage(std::string(), std::move(message), duration); +} + +void Host::AddKeyedOSDMessage(std::string key, std::string message, float duration /* = 2.0f */) +{ + OSDMessage msg; + msg.key = std::move(key); + msg.text = std::move(message); + msg.duration = duration; + msg.time = std::chrono::steady_clock::now(); + + std::unique_lock lock(s_osd_messages_lock); + s_osd_posted_messages.push_back(std::move(msg)); +} + +void Host::AddFormattedOSDMessage(float duration, const char* format, ...) +{ + std::va_list ap; + va_start(ap, format); + std::string ret = StringUtil::StdStringFromFormatV(format, ap); + va_end(ap); + return AddKeyedOSDMessage(std::string(), std::move(ret), duration); +} + +void Host::AddKeyedFormattedOSDMessage(std::string key, float duration, const char* format, ...) +{ + std::va_list ap; + va_start(ap, format); + std::string ret = StringUtil::StdStringFromFormatV(format, ap); + va_end(ap); + return AddKeyedOSDMessage(std::move(key), std::move(ret), duration); +} + +void Host::RemoveKeyedOSDMessage(std::string key) +{ + OSDMessage msg; + msg.key = std::move(key); + msg.duration = 0.0f; + msg.time = std::chrono::steady_clock::now(); + + std::unique_lock lock(s_osd_messages_lock); + s_osd_posted_messages.push_back(std::move(msg)); +} + +void Host::ClearOSDMessages() +{ + { + std::unique_lock lock(s_osd_messages_lock); + s_osd_posted_messages.clear(); + } + + s_osd_active_messages.clear(); +} + +static void AcquirePendingOSDMessages() +{ + std::atomic_thread_fence(std::memory_order_consume); + if (s_osd_posted_messages.empty()) + return; + + std::unique_lock lock(s_osd_messages_lock); + for (;;) + { + if (s_osd_posted_messages.empty()) + break; + + if (GSConfig.OsdShowMessages) + { + OSDMessage& new_msg = s_osd_posted_messages.front(); + std::deque::iterator iter; + if (!new_msg.key.empty() && (iter = std::find_if(s_osd_active_messages.begin(), s_osd_active_messages.end(), + [&new_msg](const OSDMessage& other) { + return new_msg.key == other.key; + })) != s_osd_active_messages.end()) + { + iter->text = std::move(new_msg.text); + iter->duration = new_msg.duration; + iter->time = new_msg.time; + } + else + { + s_osd_active_messages.push_back(std::move(new_msg)); + } + } + + s_osd_posted_messages.pop_front(); + + static constexpr size_t MAX_ACTIVE_OSD_MESSAGES = 512; + if (s_osd_active_messages.size() > MAX_ACTIVE_OSD_MESSAGES) + s_osd_active_messages.pop_front(); + } +} + +static void DrawOSDMessages() +{ + ImFont* const font = ImGui::GetFont(); + const float scale = s_global_scale; + const float spacing = std::ceil(5.0f * scale); + const float margin = std::ceil(10.0f * scale); + const float padding = std::ceil(8.0f * scale); + const float rounding = std::ceil(5.0f * scale); + const float max_width = ImGui::GetIO().DisplaySize.x - (margin + padding) * 2.0f; + float position_x = margin; + float position_y = margin; + + const auto now = std::chrono::steady_clock::now(); + + auto iter = s_osd_active_messages.begin(); + while (iter != s_osd_active_messages.end()) + { + const OSDMessage& msg = *iter; + const double time = std::chrono::duration(now - msg.time).count(); + const float time_remaining = static_cast(msg.duration - time); + if (time_remaining <= 0.0f) + { + iter = s_osd_active_messages.erase(iter); + continue; + } + + ++iter; + + const float opacity = std::min(time_remaining, 1.0f); + const u32 alpha = static_cast(opacity * 255.0f); + + if (position_y >= ImGui::GetIO().DisplaySize.y) + break; + + const ImVec2 pos(position_x, position_y); + const ImVec2 text_size( + font->CalcTextSizeA(font->FontSize, max_width, max_width, msg.text.c_str(), msg.text.c_str() + msg.text.length())); + const ImVec2 size(text_size.x + padding * 2.0f, text_size.y + padding * 2.0f); + const ImVec4 text_rect(pos.x + padding, pos.y + padding, pos.x + size.x - padding, pos.y + size.y - padding); + + ImDrawList* dl = ImGui::GetBackgroundDrawList(); + dl->AddRectFilled(pos, ImVec2(pos.x + size.x, pos.y + size.y), IM_COL32(0x21, 0x21, 0x21, alpha), rounding); + dl->AddRect(pos, ImVec2(pos.x + size.x, pos.y + size.y), IM_COL32(0x48, 0x48, 0x48, alpha), rounding); + dl->AddText(font, font->FontSize, ImVec2(text_rect.x, text_rect.y), IM_COL32(0xff, 0xff, 0xff, alpha), + msg.text.c_str(), msg.text.c_str() + msg.text.length(), max_width, &text_rect); + position_y += size.y + spacing; + } +} + +static void DrawPerformanceOverlay() +{ + const float scale = s_global_scale; + const float shadow_offset = std::ceil(1.0f * scale); + const float margin = std::ceil(10.0f * scale); + const float spacing = std::ceil(5.0f * scale); + float position_y = margin; + + ImDrawList* dl = ImGui::GetBackgroundDrawList(); + FastFormatAscii text; + ImVec2 text_size; + bool first = true; + +#define DRAW_LINE(font, text, color) \ + do \ + { \ + text_size = \ + font->CalcTextSizeA(font->FontSize, std::numeric_limits::max(), -1.0f, (text), nullptr, nullptr); \ + dl->AddText( \ + font, font->FontSize, \ + ImVec2(ImGui::GetIO().DisplaySize.x - margin - text_size.x + shadow_offset, position_y + shadow_offset), \ + IM_COL32(0, 0, 0, 100), (text)); \ + dl->AddText(font, font->FontSize, ImVec2(ImGui::GetIO().DisplaySize.x - margin - text_size.x, position_y), color, \ + (text)); \ + position_y += text_size.y + spacing; \ + } while (0) + +#ifdef PCSX2_CORE + const bool paused = (VMManager::GetState() == VMState::Paused); +#else + constexpr bool paused = false; +#endif + + if (!paused) + { + const float speed = PerformanceMetrics::GetSpeed(); + if (GSConfig.OsdShowFPS) + { + text.Write("%.2f", PerformanceMetrics::GetFPS()); + first = false; + } + if (GSConfig.OsdShowSpeed) + { + text.Write("%s%u%%", first ? "" : " | ", static_cast(std::round(speed))); + + // We read the main config here, since MTGS doesn't get updated with speed toggles. + if (EmuConfig.GS.LimitScalar == 0.0) + text.Write(" (Max)"); + else + text.Write(" (%.0f%%)", EmuConfig.GS.LimitScalar * 100.0); + + first = false; + } + if (!text.IsEmpty()) + { + ImU32 color; + if (speed < 95.0f) + color = IM_COL32(255, 100, 100, 255); + else if (speed > 105.0f) + color = IM_COL32(100, 255, 100, 255); + else + color = IM_COL32(255, 255, 255, 255); + + DRAW_LINE(s_fixed_font, text.c_str(), color); + } + + if (GSConfig.OsdShowGSStats) + { + std::string gs_stats; + GSgetStats(gs_stats); + DRAW_LINE(s_fixed_font, gs_stats.c_str(), IM_COL32(255, 255, 255, 255)); + } + + if (GSConfig.OsdShowResolution) + { + int width, height; + GSgetInternalResolution(&width, &height); + + text.Clear(); + text.Write("%dx%d %s %s", width, height, ReportVideoMode(), ReportInterlaceMode()); + DRAW_LINE(s_fixed_font, text.c_str(), IM_COL32(255, 255, 255, 255)); + } + + if (GSConfig.OsdShowCPU) + { + text.Clear(); + text.Write("%.2fms (%.2fms worst)", PerformanceMetrics::GetAverageFrameTime(), + PerformanceMetrics::GetWorstFrameTime()); + DRAW_LINE(s_fixed_font, text.c_str(), IM_COL32(255, 255, 255, 255)); + + text.Clear(); + if (EmuConfig.Speedhacks.EECycleRate != 0 || EmuConfig.Speedhacks.EECycleSkip != 0) + { + text.Write("EE[%d/%d]: %.1f%% (%.2fms)", EmuConfig.Speedhacks.EECycleRate, EmuConfig.Speedhacks.EECycleSkip, + PerformanceMetrics::GetCPUThreadUsage(), + PerformanceMetrics::GetCPUThreadAverageTime()); + } + else + { + text.Write("EE: %.1f%% (%.2fms)", PerformanceMetrics::GetCPUThreadUsage(), + PerformanceMetrics::GetCPUThreadAverageTime()); + } + DRAW_LINE(s_fixed_font, text.c_str(), IM_COL32(255, 255, 255, 255)); + + text.Clear(); + text.Write("GS: %.1f%% (%.2fms)", PerformanceMetrics::GetGSThreadUsage(), + PerformanceMetrics::GetGSThreadAverageTime()); + DRAW_LINE(s_fixed_font, text.c_str(), IM_COL32(255, 255, 255, 255)); + + if (THREAD_VU1) + { + text.Clear(); + text.Write("VU: %.1f%% (%.2fms)", PerformanceMetrics::GetVUThreadUsage(), + PerformanceMetrics::GetVUThreadAverageTime()); + DRAW_LINE(s_fixed_font, text.c_str(), IM_COL32(255, 255, 255, 255)); + } + } + + const bool is_normal_speed = (EmuConfig.GS.LimitScalar == EmuConfig.Framerate.NominalScalar); + if (!is_normal_speed) + { + const bool is_slowmo = (EmuConfig.GS.LimitScalar < EmuConfig.Framerate.NominalScalar); + DRAW_LINE(s_standard_font, is_slowmo ? ICON_FA_FORWARD : ICON_FA_FAST_FORWARD, IM_COL32(255, 255, 255, 255)); + } + } + else + { + DRAW_LINE(s_standard_font, ICON_FA_PAUSE, IM_COL32(255, 255, 255, 255)); + } + +#undef DRAW_LINE +} + +void ImGuiManager::RenderOSD() +{ + DrawPerformanceOverlay(); + + AcquirePendingOSDMessages(); + DrawOSDMessages(); +} + diff --git a/pcsx2/Frontend/ImGuiManager.h b/pcsx2/Frontend/ImGuiManager.h new file mode 100644 index 0000000000000..8b8cbadd2fecd --- /dev/null +++ b/pcsx2/Frontend/ImGuiManager.h @@ -0,0 +1,38 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2021 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once + +namespace ImGuiManager +{ + /// Initializes ImGui, creates fonts, etc. + bool Initialize(); + + /// Frees all ImGui resources. + void Shutdown(); + + /// Updates internal state when the window is size. + void WindowResized(); + + /// Updates scaling of the on-screen elements. + void UpdateScale(); + + /// Call at the beginning of the frame to set up ImGui state. + void NewFrame(); + + /// Renders any on-screen display elements. + void RenderOSD(); +} // namespace ImGuiManager + diff --git a/pcsx2/Frontend/OpenGLHostDisplay.cpp b/pcsx2/Frontend/OpenGLHostDisplay.cpp new file mode 100644 index 0000000000000..45434d0986469 --- /dev/null +++ b/pcsx2/Frontend/OpenGLHostDisplay.cpp @@ -0,0 +1,369 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2021 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "OpenGLHostDisplay.h" +#include "common/Assertions.h" +#include "common/Console.h" +#include "common/ScopedGuard.h" +#include "common/StringUtil.h" +#include "common/GL/Program.h" +#include "imgui.h" +#include "imgui_impl_opengl3.h" +#include +#include + +class OpenGLHostDisplayTexture : public HostDisplayTexture +{ +public: + OpenGLHostDisplayTexture(GLuint texture, u32 width, u32 height, u32 layers, u32 levels) + : m_texture(texture) + , m_width(width) + , m_height(height) + , m_layers(layers) + , m_levels(levels) + { + } + ~OpenGLHostDisplayTexture() override = default; + + void* GetHandle() const override { return reinterpret_cast(static_cast(m_texture)); } + u32 GetWidth() const override { return m_width; } + u32 GetHeight() const override { return m_height; } + u32 GetLayers() const override { return m_layers; } + u32 GetLevels() const override { return m_levels; } + + GLuint GetGLID() const { return m_texture; } + +private: + GLuint m_texture; + u32 m_width; + u32 m_height; + u32 m_layers; + u32 m_levels; +}; + +OpenGLHostDisplay::OpenGLHostDisplay() = default; + +OpenGLHostDisplay::~OpenGLHostDisplay() +{ + pxAssertMsg(!m_gl_context, "Context should have been destroyed by now"); +} + +HostDisplay::RenderAPI OpenGLHostDisplay::GetRenderAPI() const +{ + return m_gl_context->IsGLES() ? RenderAPI::OpenGLES : RenderAPI::OpenGL; +} + +void* OpenGLHostDisplay::GetRenderDevice() const +{ + return nullptr; +} + +void* OpenGLHostDisplay::GetRenderContext() const +{ + return m_gl_context.get(); +} + +void* OpenGLHostDisplay::GetRenderSurface() const +{ + return nullptr; +} + +std::unique_ptr OpenGLHostDisplay::CreateTexture(u32 width, u32 height, u32 layers, u32 levels, + const void* data, u32 data_stride, bool dynamic /* = false */) +{ + // clear error + glGetError(); + + GLuint id; + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_2D, id); + + if ((GLAD_GL_ARB_texture_storage || GLAD_GL_ES_VERSION_3_0) && !data) + glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height); + else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + + GLenum error = glGetError(); + if (error != GL_NO_ERROR) + { + Console.Error("Failed to create texture: 0x%X", error); + glDeleteTextures(1, &id); + return nullptr; + } + + return std::make_unique(id, width, height, layers, levels); +} + +void OpenGLHostDisplay::UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, + const void* texture_data, u32 texture_data_stride) +{ + OpenGLHostDisplayTexture* tex = static_cast(texture); + + GLint alignment; + if (texture_data_stride & 1) + alignment = 1; + else if (texture_data_stride & 2) + alignment = 2; + else + alignment = 4; + + GLint old_texture_binding = 0, old_alignment = 0, old_row_length = 0; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &old_texture_binding); + glBindTexture(GL_TEXTURE_2D, tex->GetGLID()); + + glGetIntegerv(GL_UNPACK_ALIGNMENT, &old_alignment); + glPixelStorei(GL_UNPACK_ALIGNMENT, alignment); + + glGetIntegerv(GL_UNPACK_ROW_LENGTH, &old_row_length); + glPixelStorei(GL_UNPACK_ROW_LENGTH, texture_data_stride / sizeof(u32)); + + glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, GL_RGBA8, GL_UNSIGNED_BYTE, texture_data); + + glPixelStorei(GL_UNPACK_ROW_LENGTH, old_row_length); + + glPixelStorei(GL_UNPACK_ALIGNMENT, old_alignment); + glBindTexture(GL_TEXTURE_2D, old_texture_binding); +} + +void OpenGLHostDisplay::SetVSync(VsyncMode mode) +{ + if (m_gl_context->GetWindowInfo().type == WindowInfo::Type::Surfaceless) + return; + + // Window framebuffer has to be bound to call SetSwapInterval. + GLint current_fbo = 0; + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤t_fbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + + if (mode != VsyncMode::Adaptive || !m_gl_context->SetSwapInterval(-1)) + m_gl_context->SetSwapInterval(static_cast(mode != VsyncMode::Off)); + + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo); +} + +const char* OpenGLHostDisplay::GetGLSLVersionString() const +{ + if (GetRenderAPI() == RenderAPI::OpenGLES) + { + if (GLAD_GL_ES_VERSION_3_0) + return "#version 300 es"; + else + return "#version 100"; + } + else + { + if (GLAD_GL_VERSION_3_3) + return "#version 330"; + else + return "#version 130"; + } +} + +std::string OpenGLHostDisplay::GetGLSLVersionHeader() const +{ + std::string header = GetGLSLVersionString(); + header += "\n\n"; + if (GetRenderAPI() == RenderAPI::OpenGLES) + { + header += "precision highp float;\n"; + header += "precision highp int;\n\n"; + } + + return header; +} + +bool OpenGLHostDisplay::HasRenderDevice() const +{ + return static_cast(m_gl_context); +} + +bool OpenGLHostDisplay::HasRenderSurface() const +{ + return m_window_info.type != WindowInfo::Type::Surfaceless; +} + +bool OpenGLHostDisplay::CreateRenderDevice(const WindowInfo& wi, std::string_view adapter_name, bool debug_device) +{ + m_gl_context = GL::Context::Create(wi); + if (!m_gl_context) + { + Console.Error("Failed to create any GL context"); + m_gl_context.reset(); + return false; + } + + m_window_info = m_gl_context->GetWindowInfo(); + return true; +} + +bool OpenGLHostDisplay::InitializeRenderDevice(std::string_view shader_cache_directory, bool debug_device) +{ + // Start with vsync off. + m_gl_context->SetSwapInterval(0); + GL::Program::ResetLastProgram(); + return true; +} + +bool OpenGLHostDisplay::MakeRenderContextCurrent() +{ + if (!m_gl_context->MakeCurrent()) + { + Console.Error("Failed to make GL context current"); + return false; + } + + return true; +} + +bool OpenGLHostDisplay::DoneRenderContextCurrent() +{ + return m_gl_context->DoneCurrent(); +} + +void OpenGLHostDisplay::DestroyRenderDevice() +{ + if (!m_gl_context) + return; + + m_gl_context->DoneCurrent(); + m_gl_context.reset(); +} + +bool OpenGLHostDisplay::ChangeRenderWindow(const WindowInfo& new_wi) +{ + pxAssert(m_gl_context); + + if (!m_gl_context->ChangeSurface(new_wi)) + { + Console.Error("Failed to change surface"); + return false; + } + + m_window_info = m_gl_context->GetWindowInfo(); + + if (new_wi.type != WindowInfo::Type::Surfaceless) + { + // reset vsync rate, since it (usually) gets lost + if (m_vsync_mode != VsyncMode::Adaptive || !m_gl_context->SetSwapInterval(-1)) + m_gl_context->SetSwapInterval(static_cast(m_vsync_mode != VsyncMode::Off)); + } + + return true; +} + +void OpenGLHostDisplay::ResizeRenderWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) +{ + if (!m_gl_context) + return; + + m_window_info.surface_scale = new_window_scale; + if (m_window_info.surface_width == static_cast(new_window_width) && + m_window_info.surface_height == static_cast(new_window_height)) + { + return; + } + + m_gl_context->ResizeSurface(static_cast(new_window_width), static_cast(new_window_height)); + m_window_info = m_gl_context->GetWindowInfo(); +} + +bool OpenGLHostDisplay::SupportsFullscreen() const +{ + return false; +} + +bool OpenGLHostDisplay::IsFullscreen() +{ + return false; +} + +bool OpenGLHostDisplay::SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) +{ + return false; +} + +HostDisplay::AdapterAndModeList OpenGLHostDisplay::GetAdapterAndModeList() +{ + AdapterAndModeList aml; + + if (m_gl_context) + { + for (const GL::Context::FullscreenModeInfo& fmi : m_gl_context->EnumerateFullscreenModes()) + aml.fullscreen_modes.push_back(GetFullscreenModeString(fmi.width, fmi.height, fmi.refresh_rate)); + } + + return aml; +} + +void OpenGLHostDisplay::DestroyRenderSurface() +{ + if (!m_gl_context) + return; + + m_window_info = {}; + if (!m_gl_context->ChangeSurface(m_window_info)) + Console.Error("Failed to switch to surfaceless"); +} + +bool OpenGLHostDisplay::CreateImGuiContext() +{ + return ImGui_ImplOpenGL3_Init(GetGLSLVersionString()); +} + +void OpenGLHostDisplay::DestroyImGuiContext() +{ + ImGui_ImplOpenGL3_Shutdown(); +} + +bool OpenGLHostDisplay::UpdateImGuiFontTexture() +{ + ImGui_ImplOpenGL3_DestroyFontsTexture(); + return ImGui_ImplOpenGL3_CreateFontsTexture(); +} + +bool OpenGLHostDisplay::BeginPresent(bool frame_skip) +{ + if (frame_skip || m_window_info.type == WindowInfo::Type::Surfaceless) + { + ImGui::EndFrame(); + return false; + } + + glDisable(GL_SCISSOR_TEST); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + glViewport(0, 0, m_window_info.surface_width, m_window_info.surface_height); + + return true; +} + +void OpenGLHostDisplay::EndPresent() +{ + // clear out pipeline bindings, since imgui doesn't use them + glBindProgramPipeline(0); + glDisable(GL_SCISSOR_TEST); + glDisable(GL_STENCIL_TEST); + glActiveTexture(GL_TEXTURE0); + + ImGui::Render(); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + GL::Program::ResetLastProgram(); + + m_gl_context->SwapBuffers(); +} + diff --git a/pcsx2/Frontend/OpenGLHostDisplay.h b/pcsx2/Frontend/OpenGLHostDisplay.h new file mode 100644 index 0000000000000..b50c0a6569cdf --- /dev/null +++ b/pcsx2/Frontend/OpenGLHostDisplay.h @@ -0,0 +1,76 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2021 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once + +#include + +#include "HostDisplay.h" +#include "common/GL/Context.h" +#include "common/WindowInfo.h" +#include + +class OpenGLHostDisplay final : public HostDisplay +{ +public: + OpenGLHostDisplay(); + ~OpenGLHostDisplay(); + + RenderAPI GetRenderAPI() const override; + void* GetRenderDevice() const override; + void* GetRenderContext() const override; + void* GetRenderSurface() const override; + + bool HasRenderDevice() const override; + bool HasRenderSurface() const override; + + bool CreateRenderDevice(const WindowInfo& wi, std::string_view adapter_name, bool debug_device) override; + bool InitializeRenderDevice(std::string_view shader_cache_directory, bool debug_device) override; + void DestroyRenderDevice() override; + + bool MakeRenderContextCurrent() override; + bool DoneRenderContextCurrent() override; + + bool ChangeRenderWindow(const WindowInfo& new_wi) override; + void ResizeRenderWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override; + bool SupportsFullscreen() const override; + bool IsFullscreen() override; + bool SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override; + AdapterAndModeList GetAdapterAndModeList() override; + void DestroyRenderSurface() override; + + std::unique_ptr CreateTexture(u32 width, u32 height, u32 layers, u32 levels, const void* data, + u32 data_stride, bool dynamic) override; + void UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* texture_data, + u32 texture_data_stride) override; + + void SetVSync(VsyncMode mode) override; + + bool BeginPresent(bool frame_skip) override; + void EndPresent() override; + +protected: + const char* GetGLSLVersionString() const; + std::string GetGLSLVersionHeader() const; + + bool CreateImGuiContext() override; + void DestroyImGuiContext() override; + bool UpdateImGuiFontTexture() override; + + std::unique_ptr m_gl_context; + + VsyncMode m_vsync_mode = VsyncMode::Off; +}; + diff --git a/pcsx2/GS.cpp b/pcsx2/GS.cpp index bf44627b9a4c4..4e1b8bc6039c2 100644 --- a/pcsx2/GS.cpp +++ b/pcsx2/GS.cpp @@ -47,20 +47,31 @@ void gsReset() void gsUpdateFrequency(Pcsx2Config& config) { - switch (EmuConfig.LimiterMode) + if (config.GS.FrameLimitEnable) { - case LimiterModeType::Nominal: - config.GS.LimitScalar = EmuConfig.Framerate.NominalScalar; - break; - case LimiterModeType::Slomo: - config.GS.LimitScalar = EmuConfig.Framerate.SlomoScalar; - break; - case LimiterModeType::Turbo: - config.GS.LimitScalar = EmuConfig.Framerate.TurboScalar; - break; - default: - pxAssert("Unknown framelimiter mode!"); + switch (config.LimiterMode) + { + case LimiterModeType::Nominal: + config.GS.LimitScalar = config.Framerate.NominalScalar; + break; + case LimiterModeType::Slomo: + config.GS.LimitScalar = config.Framerate.SlomoScalar; + break; + case LimiterModeType::Turbo: + config.GS.LimitScalar = config.Framerate.TurboScalar; + break; + case LimiterModeType::Unlimited: + config.GS.LimitScalar = 0.0; + break; + default: + pxAssert("Unknown framelimiter mode!"); + } + } + else + { + config.GS.LimitScalar = 0.0; } + UpdateVSyncRate(); } @@ -369,34 +380,34 @@ void gsIrq() { // This function does not regulate frame limiting, meaning it does no stalling. Stalling // functions are performed by the EE, which itself uses thread sleep logic to avoid spin // waiting as much as possible (maximizes CPU resource availability for the GS). +static bool s_isSkippingCurrentFrame = false; __fi void gsFrameSkip() { static int consec_skipped = 0; static int consec_drawn = 0; - static bool isSkipping = false; if( !EmuConfig.GS.FrameSkipEnable ) { - if( isSkipping ) + if( s_isSkippingCurrentFrame ) { // Frameskipping disabled on-the-fly .. make sure the GS is restored to non-skip // behavior. GSsetFrameSkip( false ); - isSkipping = false; + s_isSkippingCurrentFrame = false; } return; } - GSsetFrameSkip( isSkipping ); + GSsetFrameSkip( s_isSkippingCurrentFrame ); - if( isSkipping ) + if( s_isSkippingCurrentFrame ) { ++consec_skipped; if( consec_skipped >= EmuConfig.GS.FramesToSkip ) { consec_skipped = 0; - isSkipping = false; + s_isSkippingCurrentFrame = false; } } else @@ -405,11 +416,16 @@ __fi void gsFrameSkip() if( consec_drawn >= EmuConfig.GS.FramesToDraw ) { consec_drawn = 0; - isSkipping = true; + s_isSkippingCurrentFrame = true; } } } +extern bool gsIsSkippingCurrentFrame() +{ + return s_isSkippingCurrentFrame; +} + //These are done at VSync Start. Drawing is done when VSync is off, then output the screen when Vsync is on //The GS needs to be told at the start of a vsync else it loses half of its picture (could be responsible for some halfscreen issues) //We got away with it before i think due to our awful GS timing, but now we have it right (ish) @@ -436,3 +452,4 @@ void SaveStateBase::gsFreeze() FreezeMem(PS2MEM_GS, 0x2000); Freeze(gsVideoMode); } + diff --git a/pcsx2/GS.h b/pcsx2/GS.h index 9235d4642788a..4120282285290 100644 --- a/pcsx2/GS.h +++ b/pcsx2/GS.h @@ -19,6 +19,7 @@ #include "System/SysThreads.h" #include "Gif.h" #include "GS/GS.h" +#include extern double GetVerticalFrequency(); alignas(16) extern u8 g_RealGSMem[Ps2MemSize::GSregs]; @@ -295,6 +296,7 @@ enum MTGS_RingCommand GS_RINGTYPE_MTVU_GSPACKET, GS_RINGTYPE_INIT_READ_FIFO1, GS_RINGTYPE_INIT_READ_FIFO2, + GS_RINGTYPE_ASYNC_CALL, }; @@ -304,6 +306,14 @@ struct MTGS_FreezeData s32 retval; // value returned from the call, valid only after an mtgsWaitGS() }; +struct MTGS_MemoryScreenshotData +{ + u32 width = 0; + u32 height = 0; + std::vector pixels; // width * height + bool success = false; +}; + // -------------------------------------------------------------------------------------- // SysMtgsThread // -------------------------------------------------------------------------------------- @@ -312,6 +322,8 @@ class SysMtgsThread : public SysThreadBase typedef SysThreadBase _parent; public: + using AsyncCallType = std::function; + // note: when m_ReadPos == m_WritePos, the fifo is empty // Threading info: m_ReadPos is updated by the MTGS thread. m_WritePos is updated by the EE thread std::atomic m_ReadPos; // cur pos gs is reading from @@ -364,7 +376,7 @@ class SysMtgsThread : public SysThreadBase void PrepDataPacket( GIF_PATH pathidx, u32 size ); void SendDataPacket(); void SendGameCRC( u32 crc ); - void WaitForOpen(); + bool WaitForOpen(); void Freeze( FreezeAction mode, MTGS_FreezeData& data ); void SendSimpleGSPacket( MTGS_RingCommand type, u32 offset, u32 size, GIF_PATH path ); @@ -377,6 +389,16 @@ class SysMtgsThread : public SysThreadBase bool IsGSOpened() const { return m_Opened; } + void RunOnGSThread(AsyncCallType func); + void ApplySettings(); + void ResizeDisplayWindow(int width, int height, float scale); + void UpdateDisplayWindow(); + void SetVSync(VsyncMode mode); + void SwitchRenderer(GSRendererType renderer, bool display_message = true); + void SetSoftwareRendering(bool software, bool display_message = true); + void ToggleSoftwareRendering(); + bool SaveMemorySnapshot(u32 width, u32 height, std::vector* pixels); + protected: void OpenGS(); void CloseGS(); @@ -413,6 +435,7 @@ extern void gsSetVideoMode(GS_VideoMode mode); extern void gsResetFrameSkip(); extern void gsPostVsyncStart(); extern void gsFrameSkip(); +extern bool gsIsSkippingCurrentFrame(); extern void gsUpdateFrequency(Pcsx2Config& config); // Some functions shared by both the GS and MTGS diff --git a/pcsx2/GS/GS.cpp b/pcsx2/GS/GS.cpp index dfd6250d55cd8..e1c6803ef2820 100644 --- a/pcsx2/GS/GS.cpp +++ b/pcsx2/GS/GS.cpp @@ -14,7 +14,10 @@ */ #include "PrecompiledHeader.h" +#ifndef PCSX2_CORE +// NOTE: The include order matters - GS.h includes windows.h #include "GS/Window/GSwxDialog.h" +#endif #include "GS.h" #include "GSUtil.h" #include "GSExtra.h" @@ -26,7 +29,15 @@ #include "GSLzma.h" #include "common/pxStreams.h" +#include "common/pxStreams.h" +#include "common/Console.h" +#include "common/StringUtil.h" #include "pcsx2/Config.h" +#include "pcsx2/Host.h" +#include "pcsx2/HostDisplay.h" +#ifdef PCSX2_CORE +#include "pcsx2/HostSettings.h" +#endif #ifdef _WIN32 @@ -45,29 +56,10 @@ static HRESULT s_hr = E_FAIL; // debug obscure compiler errors --govanify #undef None -static GSRenderer* s_gs = NULL; -static u8* s_basemem = NULL; -static int s_vsync = 0; -static bool s_exclusive = true; -static std::string s_renderer_name; -bool gsopen_done = false; // crash guard for GSgetTitleInfo2 and GSKeyEvent (replace with lock?) +Pcsx2Config::GSOptions GSConfig; -#ifndef PCSX2_CORE -static std::atomic_bool s_gs_window_resized{false}; -static std::mutex s_gs_window_resized_lock; -static int s_new_gs_window_width = 0; -static int s_new_gs_window_height = 0; -#endif - -void GSsetBaseMem(u8* mem) -{ - s_basemem = mem; - - if (s_gs) - { - s_gs->SetRegsMem(s_basemem); - } -} +static std::unique_ptr s_gs; +static HostDisplay::RenderAPI s_render_api; int GSinit() { @@ -80,8 +72,8 @@ int GSinit() // can crash if the CPU does not support the instruction set. // Initialise it here instead - it's not ideal since we have to strip the // const type qualifier from all the affected variables. - theApp.SetConfigDir(); - theApp.Init(); + GSinitConfig(); + GSUtil::Init(); @@ -98,14 +90,31 @@ int GSinit() return 0; } -void GSshutdown() +void GSinitConfig() { - gsopen_done = false; + static bool config_inited = false; + if (config_inited) + return; + + config_inited = true; + theApp.SetConfigDir(); + theApp.Init(); +} - delete s_gs; - s_gs = nullptr; +void GSshutdown() +{ + if (s_gs) + { + s_gs->Destroy(); + s_gs.reset(); + } + if (g_gs_device) + { + g_gs_device->Destroy(); + g_gs_device.reset(); + } - theApp.SetCurrentRendererType(GSRendererType::Undefined); + Host::ReleaseHostDisplay(); #ifdef _WIN32 if (SUCCEEDED(s_hr)) @@ -119,190 +128,180 @@ void GSshutdown() void GSclose() { - gsopen_done = false; - -#ifndef PCSX2_CORE - // Make sure we don't have any leftover resize events from our last open. - s_gs_window_resized.store(false); -#endif - - if (s_gs == NULL) - return; + if (s_gs) + { + s_gs->Destroy(); + s_gs.reset(); + } + if (g_gs_device) + { + g_gs_device->Destroy(); + g_gs_device.reset(); + } - s_gs->ResetDevice(); + Host::ReleaseHostDisplay(); +} - // Opengl requirement: It must be done before the Detach() of - // the context - delete s_gs->m_dev; +static HostDisplay::RenderAPI GetAPIForRenderer(GSRendererType renderer) +{ + switch (renderer) + { + case GSRendererType::OGL: +#ifndef _WIN32 + default: +#endif + return HostDisplay::RenderAPI::OpenGL; - s_gs->m_dev = NULL; +#ifdef _WIN32 + case GSRendererType::DX11: + case GSRendererType::SW: + default: + return HostDisplay::RenderAPI::D3D11; +#endif + } } -int _GSopen(const WindowInfo& wi, const char* title, GSRendererType renderer, int threads = -1) +static bool DoGSOpen(GSRendererType renderer, u8* basemem) { - GSDevice* dev = NULL; + HostDisplay* display = Host::GetHostDisplay(); + pxAssert(display); - // Fresh start up or config file changed - if (renderer == GSRendererType::Undefined) - { - renderer = static_cast(theApp.GetConfigI("Renderer")); - if (renderer == GSRendererType::Default) - renderer = GSUtil::GetPreferredRenderer(); - } + s_render_api = Host::GetHostDisplay()->GetRenderAPI(); - if (threads == -1) + switch (display->GetRenderAPI()) { - threads = theApp.GetConfigI("extrathreads"); +#ifdef _WIN32 + case HostDisplay::RenderAPI::D3D11: + g_gs_device = std::make_unique(); + break; +#endif + + case HostDisplay::RenderAPI::OpenGL: + case HostDisplay::RenderAPI::OpenGLES: + g_gs_device = std::make_unique(); + break; + + default: + Console.Error("Unknown render API %u", static_cast(display->GetRenderAPI())); + return false; } try { - if (theApp.GetCurrentRendererType() != renderer) + if (!g_gs_device->Create(display)) { - // Emulator has made a render change request, which requires a completely - // new s_gs -- if the emu doesn't save/restore the GS state across this - // GSopen call then they'll get corrupted graphics, but that's not my problem. - - delete s_gs; - - s_gs = NULL; - - theApp.SetCurrentRendererType(renderer); + g_gs_device->Destroy(); + g_gs_device.reset(); + return false; } - std::string renderer_name; - switch (renderer) + if (renderer == GSRendererType::Null) { - default: -#ifdef _WIN32 - case GSRendererType::DX1011_HW: - dev = new GSDevice11(); - s_renderer_name = "D3D11"; - renderer_name = "Direct3D 11"; - break; -#endif - case GSRendererType::OGL_HW: - dev = new GSDeviceOGL(); - s_renderer_name = "OGL"; - renderer_name = "OpenGL"; - break; - case GSRendererType::OGL_SW: - dev = new GSDeviceOGL(); - s_renderer_name = "SW"; - renderer_name = "Software"; - break; - case GSRendererType::Null: - dev = new GSDeviceNull(); - s_renderer_name = "NULL"; - renderer_name = "Null"; - break; + s_gs = std::make_unique(); } - - printf("Current Renderer: %s\n", renderer_name.c_str()); - - if (dev == NULL) + else if (renderer != GSRendererType::SW) { - return -1; + s_gs = std::make_unique(); } - - if (s_gs == NULL) + else { - switch (renderer) - { - default: - case GSRendererType::DX1011_HW: - case GSRendererType::OGL_HW: - s_gs = (GSRenderer*)new GSRendererNew(); - break; - case GSRendererType::OGL_SW: - s_gs = new GSRendererSW(threads); - break; - case GSRendererType::Null: - s_gs = new GSRendererNull(); - break; - } - if (s_gs == NULL) - return -1; + const int threads = theApp.GetConfigI("extrathreads"); + s_gs = std::make_unique(threads); } } catch (std::exception& ex) { - printf("GS error: Exception caught in GSopen: %s", ex.what()); - return -1; + Host::ReportFormattedErrorAsync("GS", "GS error: Exception caught in GSopen: %s", ex.what()); + s_gs.reset(); + g_gs_device->Destroy(); + g_gs_device.reset(); + return false; } - s_gs->SetRegsMem(s_basemem); - s_gs->SetVSync(s_vsync); + s_gs->SetRegsMem(basemem); - if (!s_gs->CreateDevice(dev, wi)) - { - // This probably means the user has DX11 configured with a video card that is only DX9 - // compliant. Cound mean drivr issues of some sort also, but to be sure, that's the most - // common cause of device creation errors. :) --air + display->SetVSync(EmuConfig.GetEffectiveVsyncMode()); + return true; +} + +bool GSreopen(bool recreate_display) +{ + Console.WriteLn("Reopening GS with %s display", recreate_display ? "new" : "existing"); - GSclose(); + s_gs->Flush(); - return -1; + freezeData fd = {}; + if (s_gs->Freeze(&fd, true) != 0) + { + Console.Error("(GSreopen) Failed to get GS freeze size"); + return false; } - return 0; -} - -void GSosdLog(const char* utf8, u32 color) -{ - if (s_gs && s_gs->m_dev) - s_gs->m_dev->m_osd.Log(utf8); -} + std::unique_ptr fd_data = std::make_unique(fd.size); + fd.data = fd_data.get(); + if (s_gs->Freeze(&fd, false) != 0) + { + Console.Error("(GSreopen) Failed to freeze GS"); + return false; + } -void GSosdMonitor(const char* key, const char* value, u32 color) -{ - if (s_gs && s_gs->m_dev) - s_gs->m_dev->m_osd.Monitor(key, value); -} + if (recreate_display) + { + g_gs_device->ResetAPIState(); + if (Host::BeginPresentFrame(true)) + Host::EndPresentFrame(); + } -int GSopen2(const WindowInfo& wi, u32 flags) -{ - static bool stored_toggle_state = false; - const bool toggle_state = !!(flags & 4); - GSRendererType current_renderer = static_cast(flags >> 24); - if (current_renderer == GSRendererType::NO_RENDERER) - current_renderer = theApp.GetCurrentRendererType(); + u8* basemem = s_gs->GetRegsMem(); + const u32 gamecrc = s_gs->GetGameCRC(); + const int gamecrc_options = s_gs->GetGameCRCOptions(); + s_gs->Destroy(); + s_gs.reset(); + g_gs_device->Destroy(); + g_gs_device.reset(); - if (current_renderer != GSRendererType::Undefined && stored_toggle_state != toggle_state) + if (recreate_display) { - // SW -> HW and HW -> SW (F9 Switch) - switch (current_renderer) + Host::ReleaseHostDisplay(); + if (!Host::AcquireHostDisplay(GetAPIForRenderer(GSConfig.Renderer))) { -#ifdef _WIN32 - case GSRendererType::DX1011_HW: - current_renderer = GSRendererType::OGL_SW; - break; -#endif - case GSRendererType::OGL_SW: - { - const auto config_renderer = static_cast(theApp.GetConfigI("Renderer")); - - if (current_renderer == config_renderer) - current_renderer = GSUtil::GetPreferredRenderer(); - else - current_renderer = config_renderer; - } - break; - case GSRendererType::OGL_HW: - current_renderer = GSRendererType::OGL_SW; - break; - default: - current_renderer = GSRendererType::OGL_SW; - break; + pxFailRel("(GSreopen) Failed to reacquire host display"); + return false; } } - stored_toggle_state = toggle_state; - int retval = _GSopen(wi, "", current_renderer); + if (!DoGSOpen(GSConfig.Renderer, basemem)) + { + pxFailRel("(GSreopen) Failed to recreate GS"); + return false; + } + + if (s_gs->Defrost(&fd) != 0) + { + pxFailRel("(GSreopen) Failed to defrost"); + return false; + } + + s_gs->SetGameCRC(gamecrc, gamecrc_options); + return true; +} + +bool GSopen(const Pcsx2Config::GSOptions& config, GSRendererType renderer, u8* basemem) +{ + if (renderer == GSRendererType::Auto) + renderer = GSUtil::GetPreferredRenderer(); + + GSConfig = config; + GSConfig.Renderer = renderer; + GSConfig.MaskUserHacks(); - gsopen_done = true; + if (!Host::AcquireHostDisplay(GetAPIForRenderer(renderer))) + { + Console.Error("Failed to acquire host display"); + return false; + } - return retval; + return DoGSOpen(renderer, basemem); } void GSreset() @@ -444,7 +443,7 @@ void GSgifTransfer3(u8* mem, u32 size) } } -void GSvsync(int field) +void GSvsync(u32 field) { try { @@ -488,20 +487,6 @@ u32 GSmakeSnapshot(char* path) } } -void GSkeyEvent(const HostKeyEvent& e) -{ - try - { - if (gsopen_done) - { - s_gs->KeyEvent(e); - } - } - catch (GSRecoverableError) - { - } -} - int GSfreeze(FreezeAction mode, freezeData* data) { try @@ -526,6 +511,20 @@ int GSfreeze(FreezeAction mode, freezeData* data) return 0; } +#ifndef PCSX2_CORE + +void GSkeyEvent(const HostKeyEvent& e) +{ + try + { + if (s_gs) + s_gs->KeyEvent(e); + } + catch (GSRecoverableError) + { + } +} + void GSconfigure() { try @@ -540,7 +539,7 @@ void GSconfigure() { theApp.ReloadConfig(); // Force a reload of the gs state - theApp.SetCurrentRendererType(GSRendererType::Undefined); + //theApp.SetCurrentRendererType(GSRendererType::Undefined); } } catch (GSRecoverableError) @@ -556,6 +555,8 @@ int GStest() return 0; } +#endif + void pt(const char* str) { struct tm* current; @@ -606,72 +607,203 @@ void GSsetGameCRC(u32 crc, int options) s_gs->SetGameCRC(crc, options); } -void GSgetTitleInfo2(char* dest, size_t length) +void GSsetFrameSkip(int frameskip) +{ + s_gs->SetFrameSkip(frameskip); +} + +void GSgetInternalResolution(int* width, int* height) { - std::string s; - s.append(s_renderer_name); - // TODO: this gets called from a different thread concurrently with GSOpen (on linux) - if (gsopen_done && s_gs != NULL && s_gs->m_GStitleInfoBuffer[0]) + GSRenderer* gs = s_gs.get(); + if (!gs) { - std::lock_guard lock(s_gs->m_pGSsetTitle_Crit); + *width = 0; + *height = 0; + return; + } - s.append(" | ").append(s_gs->m_GStitleInfoBuffer); + const GSVector2i res(gs->GetInternalResolution()); + *width = res.x; + *height = res.y; +} - if (s.size() > length - 1) - { - s = s.substr(0, length - 1); - } - } +void GSgetStats(std::string& info) +{ + GSPerfMon& pm = s_gs->m_perfmon; - strcpy(dest, s.c_str()); + const char* api_name = HostDisplay::RenderAPIToString(s_render_api); + + if (GSConfig.Renderer == GSRendererType::SW) + { + int sum = 0; + for (int i = 0; i < 16; i++) + sum += pm.CPU(GSPerfMon::WorkerDraw0 + i); + + const double fps = 1000.0f / pm.Get(GSPerfMon::Frame); + const double fillrate = pm.Get(GSPerfMon::Fillrate); + info = format("%d S | %d P | %d D | %.2f U | %.2f D | %.2f mpps | %d%% WCPU", + (int)pm.Get(GSPerfMon::SyncPoint), + (int)pm.Get(GSPerfMon::Prim), + (int)pm.Get(GSPerfMon::Draw), + pm.Get(GSPerfMon::Swizzle) / 1024, + pm.Get(GSPerfMon::Unswizzle) / 1024, + fps * fillrate / (1024 * 1024), + sum); + } + else if (GSConfig.Renderer == GSRendererType::Null) + { + info = format("%s Null", api_name); + } + else + { + info = format("%d S | %d P | %d D | %.2f U | %.2f D", + (int)pm.Get(GSPerfMon::SyncPoint), + (int)pm.Get(GSPerfMon::Prim), + (int)pm.Get(GSPerfMon::Draw), + pm.Get(GSPerfMon::Swizzle) / 1024, + pm.Get(GSPerfMon::Unswizzle) / 1024); + } } -void GSsetFrameSkip(int frameskip) +void GSgetTitleStats(std::string& info) { - s_gs->SetFrameSkip(frameskip); + int iwidth, iheight; + GSgetInternalResolution(&iwidth, &iheight); + + const char* api_name = HostDisplay::RenderAPIToString(s_render_api); + const char* hw_sw_name = (GSConfig.Renderer == GSRendererType::Null) ? " Null" : (GSConfig.UseHardwareRenderer() ? " HW" : " SW"); + const char* interlace_mode = theApp.m_gs_interlace[static_cast(GSConfig.InterlaceMode)].name.c_str(); + + info = format("%s%s | %s | %dx%d", api_name, hw_sw_name, interlace_mode, iwidth, iheight); } -void GSsetVsync(int vsync) +void GSUpdateConfig(const Pcsx2Config::GSOptions& new_config) { - s_vsync = vsync; + Pcsx2Config::GSOptions old_config(std::move(GSConfig)); + GSConfig = new_config; + GSConfig.Renderer = (GSConfig.Renderer == GSRendererType::Auto) ? GSUtil::GetPreferredRenderer() : GSConfig.Renderer; + GSConfig.MaskUserHacks(); + if (!s_gs) + return; - if (s_gs) + HostDisplay* display = Host::GetHostDisplay(); + + // Handle OSD scale changes by pushing a window resize through. + if (new_config.OsdScale != old_config.OsdScale) + Host::ResizeHostDisplay(display->GetWindowWidth(), display->GetWindowHeight(), display->GetWindowScale()); + + // Options which need a full teardown/recreate. + if (!GSConfig.RestartOptionsAreEqual(old_config)) { - s_gs->SetVSync(s_vsync); + HostDisplay::RenderAPI existing_api = Host::GetHostDisplay()->GetRenderAPI(); + if (existing_api == HostDisplay::RenderAPI::OpenGLES) + existing_api = HostDisplay::RenderAPI::OpenGL; + + const bool do_full_restart = ( + existing_api != GetAPIForRenderer(GSConfig.Renderer) || + GSConfig.Adapter != old_config.Adapter || + GSConfig.UseDebugDevice != old_config.UseDebugDevice || + GSConfig.UseBlitSwapChain != old_config.UseBlitSwapChain || + GSConfig.DisableShaderCache != old_config.DisableShaderCache + ); + GSreopen(do_full_restart); + return; } + + // Options which aren't using the global struct yet, so we need to recreate all GS objects. + if ( + GSConfig.GPUPaletteConversion != old_config.GPUPaletteConversion || + GSConfig.ConservativeFramebuffer != old_config.ConservativeFramebuffer || + GSConfig.AutoFlushSW != old_config.AutoFlushSW || + GSConfig.PreloadFrameWithGSData != old_config.PreloadFrameWithGSData || + GSConfig.WrapGSMem != old_config.WrapGSMem || + GSConfig.Mipmap != old_config.Mipmap || + GSConfig.AA1 != old_config.AA1 || + GSConfig.UserHacks_AlignSpriteX != old_config.UserHacks_AlignSpriteX || + GSConfig.UserHacks_AutoFlush != old_config.UserHacks_AutoFlush || + GSConfig.UserHacks_CPUFBConversion != old_config.UserHacks_CPUFBConversion || + GSConfig.UserHacks_DisableDepthSupport != old_config.UserHacks_DisableDepthSupport || + GSConfig.UserHacks_DisablePartialInvalidation != old_config.UserHacks_DisablePartialInvalidation || + GSConfig.UserHacks_DisableSafeFeatures != old_config.UserHacks_DisableSafeFeatures || + GSConfig.UserHacks_MergePPSprite != old_config.UserHacks_MergePPSprite || + GSConfig.UserHacks_WildHack != old_config.UserHacks_WildHack || + GSConfig.UserHacks_TextureInsideRt != old_config.UserHacks_TextureInsideRt || + GSConfig.DumpGSData != old_config.DumpGSData || + GSConfig.SaveRT != old_config.SaveRT || + GSConfig.SaveFrame != old_config.SaveFrame || + GSConfig.SaveTexture != old_config.SaveTexture || + GSConfig.SaveDepth != old_config.SaveDepth || + + GSConfig.UpscaleMultiplier != old_config.UpscaleMultiplier || + GSConfig.HWMipmap != old_config.HWMipmap || + GSConfig.CRCHack != old_config.CRCHack || + GSConfig.MaxAnisotropy != old_config.MaxAnisotropy || + GSConfig.SWExtraThreads != old_config.SWExtraThreads || + GSConfig.SWExtraThreadsHeight != old_config.SWExtraThreadsHeight || + + GSConfig.UserHacks_HalfBottomOverride != old_config.UserHacks_HalfBottomOverride || + GSConfig.UserHacks_HalfPixelOffset != old_config.UserHacks_HalfPixelOffset || + GSConfig.UserHacks_RoundSprite != old_config.UserHacks_RoundSprite || + GSConfig.UserHacks_TCOffsetX != old_config.UserHacks_TCOffsetX || + GSConfig.UserHacks_TCOffsetY != old_config.UserHacks_TCOffsetY || + + GSConfig.ShadeBoost_Brightness != old_config.ShadeBoost_Brightness || + GSConfig.ShadeBoost_Contrast != old_config.ShadeBoost_Contrast || + GSConfig.ShadeBoost_Saturation != old_config.ShadeBoost_Saturation || + GSConfig.SaveN != old_config.SaveN || + GSConfig.SaveL != old_config.SaveL || + + GSConfig.ShaderFX_Conf != old_config.ShaderFX_Conf || + GSConfig.ShaderFX_GLSL != old_config.ShaderFX_GLSL) + { + GSreopen(false); + return; + } + + // This is where we would do finer-grained checks in the future. + // For example, flushing the texture cache when mipmap settings change. } -void GSsetExclusive(int enabled) +void GSSwitchRenderer(GSRendererType new_renderer) { - s_exclusive = !!enabled; + if (new_renderer == GSRendererType::Auto) + new_renderer = GSUtil::GetPreferredRenderer(); - if (s_gs) - { - s_gs->SetVSync(s_vsync); - } + if (!s_gs || GSConfig.Renderer == new_renderer) + return; + + HostDisplay::RenderAPI existing_api = Host::GetHostDisplay()->GetRenderAPI(); + if (existing_api == HostDisplay::RenderAPI::OpenGLES) + existing_api = HostDisplay::RenderAPI::OpenGL; + + const bool is_software_switch = (new_renderer == GSRendererType::SW || GSConfig.Renderer == GSRendererType::SW); + GSConfig.Renderer = new_renderer; + GSreopen(!is_software_switch && existing_api != GetAPIForRenderer(new_renderer)); } -#ifndef PCSX2_CORE -void GSResizeWindow(int width, int height) +void GSResetAPIState() +{ + if (!g_gs_device) + return; + + g_gs_device->ResetAPIState(); +} + +void GSRestoreAPIState() { - std::unique_lock lock(s_gs_window_resized_lock); - s_new_gs_window_width = width; - s_new_gs_window_height = height; - s_gs_window_resized.store(true); + if (!g_gs_device) + return; + + g_gs_device->RestoreAPIState(); } -bool GSCheckForWindowResize(int* new_width, int* new_height) +bool GSSaveSnapshotToMemory(u32 width, u32 height, std::vector* pixels) { - if (!s_gs_window_resized.load()) + if (!s_gs) return false; - std::unique_lock lock(s_gs_window_resized_lock); - *new_width = s_new_gs_window_width; - *new_height = s_new_gs_window_height; - s_gs_window_resized.store(false); - return true; + return s_gs->SaveSnapshotToMemory(width, height, pixels); } -#endif std::string format(const char* fmt, ...) { @@ -885,6 +1017,10 @@ void fifo_free(void* ptr, size_t size, size_t repeat) size_t GSApp::GetIniString(const char* lpAppName, const char* lpKeyName, const char* lpDefault, char* lpReturnedString, size_t nSize, const char* lpFileName) { +#ifdef PCSX2_CORE + std::string ret(Host::GetStringSettingValue("EmuCore/GS", lpKeyName, lpDefault)); + return StringUtil::Strlcpy(lpReturnedString, ret, nSize); +#else BuildConfigurationMap(lpFileName); std::string key(lpKeyName); @@ -899,10 +1035,12 @@ size_t GSApp::GetIniString(const char* lpAppName, const char* lpKeyName, const c strcpy(lpReturnedString, value.c_str()); return 0; +#endif } bool GSApp::WriteIniString(const char* lpAppName, const char* lpKeyName, const char* pString, const char* lpFileName) { +#ifndef PCSX2_CORE BuildConfigurationMap(lpFileName); std::string key(lpKeyName); @@ -931,10 +1069,12 @@ bool GSApp::WriteIniString(const char* lpAppName, const char* lpKeyName, const c fprintf(f, "%s = %s\n", entry.first.c_str(), entry.second.c_str()); } fclose(f); +#endif return false; } +#ifndef PCSX2_CORE int GSApp::GetIniInt(const char* lpAppName, const char* lpKeyName, int nDefault, const char* lpFileName) { BuildConfigurationMap(lpFileName); @@ -949,6 +1089,7 @@ int GSApp::GetIniInt(const char* lpAppName, const char* lpKeyName, int nDefault, else return atoi(value.c_str()); } +#endif GSApp theApp; @@ -969,20 +1110,16 @@ void GSApp::Init() return; is_initialised = true; - m_current_renderer_type = GSRendererType::Undefined; - m_section = "Settings"; + m_gs_renderers.push_back(GSSetting(static_cast(GSRendererType::Auto), "Automatic", "")); #ifdef _WIN32 - m_gs_renderers.push_back(GSSetting(static_cast(GSRendererType::DX1011_HW), "Direct3D 11", "")); - m_gs_renderers.push_back(GSSetting(static_cast(GSRendererType::OGL_HW), "OpenGL", "")); - m_gs_renderers.push_back(GSSetting(static_cast(GSRendererType::OGL_SW), "Software", "")); -#else // Linux - m_gs_renderers.push_back(GSSetting(static_cast(GSRendererType::OGL_HW), "OpenGL", "")); - m_gs_renderers.push_back(GSSetting(static_cast(GSRendererType::OGL_SW), "Software", "")); + m_gs_renderers.push_back(GSSetting(static_cast(GSRendererType::DX11), "Direct3D 11", "")); #endif + m_gs_renderers.push_back(GSSetting(static_cast(GSRendererType::OGL), "OpenGL", "")); + m_gs_renderers.push_back(GSSetting(static_cast(GSRendererType::SW), "Software", "")); - // The null renderer goes third, it has use for benchmarking purposes in a release build + // The null renderer goes last, it has use for benchmarking purposes in a release build m_gs_renderers.push_back(GSSetting(static_cast(GSRendererType::Null), "Null", "")); m_gs_interlace.push_back(GSSetting(0, "None", "")); @@ -1018,7 +1155,7 @@ void GSApp::Init() m_gs_bifilter.push_back(GSSetting(static_cast(BiFiltering::Forced), "Bilinear", "Forced")); m_gs_bifilter.push_back(GSSetting(static_cast(BiFiltering::PS2), "Bilinear", "PS2")); - m_gs_trifilter.push_back(GSSetting(static_cast(TriFiltering::None), "None", "Default")); + m_gs_trifilter.push_back(GSSetting(static_cast(TriFiltering::Off), "None", "Default")); m_gs_trifilter.push_back(GSSetting(static_cast(TriFiltering::PS2), "Trilinear", "")); m_gs_trifilter.push_back(GSSetting(static_cast(TriFiltering::Forced), "Trilinear", "Ultra/Slow")); @@ -1044,7 +1181,7 @@ void GSApp::Init() m_gs_crc_level = { GSSetting(CRCHackLevel::Automatic, "Automatic", "Default"), - GSSetting(CRCHackLevel::None, "None", "Debug"), + GSSetting(CRCHackLevel::Off, "None", "Debug"), GSSetting(CRCHackLevel::Minimum, "Minimum", "Debug"), #ifdef _DEBUG GSSetting(CRCHackLevel::Partial, "Partial", "OpenGL"), @@ -1053,14 +1190,14 @@ void GSApp::Init() GSSetting(CRCHackLevel::Aggressive, "Aggressive", ""), }; - m_gs_acc_blend_level.push_back(GSSetting(static_cast(AccBlendLevel::None), "Minimum", "Fastest")); + m_gs_acc_blend_level.push_back(GSSetting(static_cast(AccBlendLevel::Minimum), "Minimum", "Fastest")); m_gs_acc_blend_level.push_back(GSSetting(static_cast(AccBlendLevel::Basic), "Basic", "Recommended")); m_gs_acc_blend_level.push_back(GSSetting(static_cast(AccBlendLevel::Medium), "Medium", "")); m_gs_acc_blend_level.push_back(GSSetting(static_cast(AccBlendLevel::High), "High", "")); m_gs_acc_blend_level.push_back(GSSetting(static_cast(AccBlendLevel::Full), "Full", "Very Slow")); m_gs_acc_blend_level.push_back(GSSetting(static_cast(AccBlendLevel::Ultra), "Ultra", "Ultra Slow")); - m_gs_acc_blend_level_d3d11.push_back(GSSetting(static_cast(AccBlendLevel::None), "Minimum", "Fastest")); + m_gs_acc_blend_level_d3d11.push_back(GSSetting(static_cast(AccBlendLevel::Minimum), "Minimum", "Fastest")); m_gs_acc_blend_level_d3d11.push_back(GSSetting(static_cast(AccBlendLevel::Basic), "Basic", "Recommended")); m_gs_acc_blend_level_d3d11.push_back(GSSetting(static_cast(AccBlendLevel::Medium), "Medium", "Debug")); m_gs_acc_blend_level_d3d11.push_back(GSSetting(static_cast(AccBlendLevel::High), "High", "Debug")); @@ -1075,9 +1212,9 @@ void GSApp::Init() // Avoid to clutter the ini file with useless options #ifdef _WIN32 // Per OS option. + m_default_configuration["Adapter"] = ""; m_default_configuration["CaptureFileName"] = ""; m_default_configuration["CaptureVideoCodecDisplayName"] = ""; - m_default_configuration["debug_d3d"] = "0"; m_default_configuration["dx_break_on_severity"] = "0"; // D3D Blending option m_default_configuration["accurate_blending_unit_d3d11"] = "1"; @@ -1097,7 +1234,6 @@ void GSApp::Init() m_default_configuration["CaptureWidth"] = "640"; m_default_configuration["crc_hack_level"] = std::to_string(static_cast(CRCHackLevel::Automatic)); m_default_configuration["CrcHacksExclusions"] = ""; - m_default_configuration["debug_opengl"] = "0"; m_default_configuration["disable_hw_gl_draw"] = "0"; m_default_configuration["disable_shader_cache"] = "0"; m_default_configuration["dithering_ps2"] = "2"; @@ -1105,7 +1241,10 @@ void GSApp::Init() m_default_configuration["extrathreads"] = "2"; m_default_configuration["extrathreads_height"] = "4"; m_default_configuration["filter"] = std::to_string(static_cast(BiFiltering::PS2)); + m_default_configuration["FMVSoftwareRendererSwitch"] = "0"; m_default_configuration["fxaa"] = "0"; + m_default_configuration["HWDisableReadbacks"] = "0"; + m_default_configuration["IntegerScaling"] = "0"; m_default_configuration["interlace"] = "7"; m_default_configuration["conservative_framebuffer"] = "1"; m_default_configuration["linear_present"] = "1"; @@ -1115,20 +1254,13 @@ void GSApp::Init() m_default_configuration["ModeHeight"] = "480"; m_default_configuration["ModeWidth"] = "640"; m_default_configuration["NTSC_Saturation"] = "1"; -#ifdef _WIN32 - m_default_configuration["osd_fontname"] = "C:\\Windows\\Fonts\\my_favorite_font_e_g_tahoma.ttf"; -#else - m_default_configuration["osd_fontname"] = "/usr/share/fonts/truetype/my_favorite_font_e_g_DejaVu Sans.ttf"; -#endif - m_default_configuration["osd_color_r"] = "0"; - m_default_configuration["osd_color_g"] = "160"; - m_default_configuration["osd_color_b"] = "255"; - m_default_configuration["osd_color_opacity"] = "100"; - m_default_configuration["osd_fontsize"] = "25"; - m_default_configuration["osd_log_enabled"] = "1"; - m_default_configuration["osd_log_timeout"] = "4"; - m_default_configuration["osd_monitor_enabled"] = "0"; - m_default_configuration["osd_max_log_messages"] = "2"; + m_default_configuration["OsdShowMessages"] = "1"; + m_default_configuration["OsdShowSpeed"] = "0"; + m_default_configuration["OsdShowFPS"] = "0"; + m_default_configuration["OsdShowCPU"] = "0"; + m_default_configuration["OsdShowResolution"] = "0"; + m_default_configuration["OsdShowGSStats"] = "0"; + m_default_configuration["OsdScale"] = "100"; m_default_configuration["override_geometry_shader"] = "-1"; m_default_configuration["override_GL_ARB_copy_image"] = "-1"; m_default_configuration["override_GL_ARB_clear_texture"] = "-1"; @@ -1146,7 +1278,7 @@ void GSApp::Init() m_default_configuration["paltex"] = "0"; m_default_configuration["png_compression_level"] = std::to_string(Z_BEST_SPEED); m_default_configuration["preload_frame_with_gs_data"] = "0"; - m_default_configuration["Renderer"] = std::to_string(static_cast(GSRendererType::Default)); + m_default_configuration["Renderer"] = std::to_string(static_cast(GSRendererType::Auto)); m_default_configuration["resx"] = "1024"; m_default_configuration["resy"] = "1024"; m_default_configuration["save"] = "0"; @@ -1162,8 +1294,13 @@ void GSApp::Init() m_default_configuration["shaderfx"] = "0"; m_default_configuration["shaderfx_conf"] = "shaders/GS_FX_Settings.ini"; m_default_configuration["shaderfx_glsl"] = "shaders/GS.fx"; + m_default_configuration["skip_duplicate_frames"] = "0"; + m_default_configuration["threaded_presentation"] = "0"; + m_default_configuration["throttle_present_rate"] = "0"; m_default_configuration["TVShader"] = "0"; m_default_configuration["upscale_multiplier"] = "1"; + m_default_configuration["UseBlitSwapChain"] = "0"; + m_default_configuration["UseDebugDevice"] = "0"; m_default_configuration["UserHacks"] = "0"; m_default_configuration["UserHacks_align_sprite_X"] = "0"; m_default_configuration["UserHacks_AutoFlush"] = "0"; @@ -1180,13 +1317,14 @@ void GSApp::Init() m_default_configuration["UserHacks_TCOffsetX"] = "0"; m_default_configuration["UserHacks_TCOffsetY"] = "0"; m_default_configuration["UserHacks_TextureInsideRt"] = "0"; - m_default_configuration["UserHacks_TriFilter"] = std::to_string(static_cast(TriFiltering::None)); + m_default_configuration["UserHacks_TriFilter"] = std::to_string(static_cast(TriFiltering::Off)); m_default_configuration["UserHacks_WildHack"] = "0"; m_default_configuration["wrap_gs_mem"] = "0"; m_default_configuration["vsync"] = "0"; // clang-format on } +#ifndef PCSX2_CORE void GSApp::ReloadConfig() { if (m_configuration_map.empty()) @@ -1245,6 +1383,7 @@ void GSApp::BuildConfigurationMap(const char* lpFileName) m_configuration_map[key] = value; } } +#endif void GSApp::SetConfigDir() { @@ -1284,18 +1423,40 @@ int GSApp::GetConfigI(const char* entry) if (def != m_default_configuration.end()) { +#ifndef PCSX2_CORE return GetIniInt(m_section.c_str(), entry, std::stoi(def->second), m_ini.c_str()); +#else + return Host::GetIntSettingValue("EmuCore/GS", entry, std::stoi(def->second)); +#endif } else { fprintf(stderr, "Option %s doesn't have a default value\n", entry); +#ifndef PCSX2_CORE return GetIniInt(m_section.c_str(), entry, 0, m_ini.c_str()); +#else + return Host::GetIntSettingValue("EmuCore/GS", entry, 0); +#endif } } bool GSApp::GetConfigB(const char* entry) { +#ifndef PCSX2_CORE return !!GetConfigI(entry); +#else + auto def = m_default_configuration.find(entry); + + if (def != m_default_configuration.end()) + { + return Host::GetBoolSettingValue("EmuCore/GS", entry, StringUtil::FromChars(def->second).value_or(false)); + } + else + { + fprintf(stderr, "Option %s doesn't have a default value\n", entry); + return Host::GetBoolSettingValue("EmuCore/GS", entry, false); + } +#endif } void GSApp::SetConfig(const char* entry, int value) @@ -1306,13 +1467,3 @@ void GSApp::SetConfig(const char* entry, int value) SetConfig(entry, buff); } - -void GSApp::SetCurrentRendererType(GSRendererType type) -{ - m_current_renderer_type = type; -} - -GSRendererType GSApp::GetCurrentRendererType() const -{ - return m_current_renderer_type; -} diff --git a/pcsx2/GS/GS.h b/pcsx2/GS/GS.h index 79c880609c37d..2207524cee676 100644 --- a/pcsx2/GS/GS.h +++ b/pcsx2/GS/GS.h @@ -18,7 +18,7 @@ #include "common/WindowInfo.h" #include "Window/GSSetting.h" #include "SaveState.h" -#include "Host.h" +#include "pcsx2/Config.h" #include @@ -27,18 +27,6 @@ #undef None #endif -enum class GSRendererType : int8_t -{ - Undefined = -1, - NO_RENDERER = 0, - DX1011_HW = 3, - Null = 11, - OGL_HW = 12, - OGL_SW = 13, - - Default = Undefined -}; - // ST_WRITE is defined in libc, avoid this enum stateType { @@ -58,59 +46,18 @@ enum class GSVideoMode : u8 HDTV_1080I }; -// Ordering was done to keep compatibility with older ini file. -enum class BiFiltering : u8 -{ - Nearest, - Forced, - PS2, - Forced_But_Sprite, -}; - -enum class TriFiltering : u8 -{ - None, - PS2, - Forced, -}; - -enum class HWMipmapLevel : int -{ - Automatic = -1, - Off, - Basic, - Full -}; - -enum class CRCHackLevel : s8 -{ - Automatic = -1, - None, - Minimum, - Partial, - Full, - Aggressive -}; +extern Pcsx2Config::GSOptions GSConfig; -enum class AccBlendLevel : u8 -{ - None, - Basic, - Medium, - High, - Full, - Ultra, -}; +struct HostKeyEvent; +class HostDisplay; -void GSsetBaseMem(u8* mem); int GSinit(); +void GSinitConfig(); void GSshutdown(); -void GSclose(); -int _GSopen(const WindowInfo& wi, const char* title, GSRendererType renderer, int threads); -void GSosdLog(const char* utf8, u32 color); -void GSosdMonitor(const char* key, const char* value, u32 color); -int GSopen2(const WindowInfo & wi, u32 flags); +bool GSopen(const Pcsx2Config::GSOptions& config, GSRendererType renderer, u8* basemem); +bool GSreopen(bool recreate_display); void GSreset(); +void GSclose(); void GSgifSoftReset(u32 mask); void GSwriteCSR(u32 csr); void GSinitReadFIFO(u8* mem); @@ -121,32 +68,34 @@ void GSgifTransfer(const u8* mem, u32 size); void GSgifTransfer1(u8* mem, u32 addr); void GSgifTransfer2(u8* mem, u32 size); void GSgifTransfer3(u8* mem, u32 size); -void GSvsync(int field); +void GSvsync(u32 field); u32 GSmakeSnapshot(char* path); -void GSkeyEvent(const HostKeyEvent& e); int GSfreeze(FreezeAction mode, freezeData* data); +#ifndef PCSX2_CORE +void GSkeyEvent(const HostKeyEvent& e); void GSconfigure(); int GStest(); +#endif bool GSsetupRecording(std::string& filename); void GSendRecording(); void GSsetGameCRC(u32 crc, int options); -void GSgetTitleInfo2(char* dest, size_t length); void GSsetFrameSkip(int frameskip); -void GSsetVsync(int vsync); -void GSsetExclusive(int enabled); -#ifndef PCSX2_CORE -// Needed for window resizing in wx. Can be safely called from the UI thread. -void GSResizeWindow(int width, int height); -bool GSCheckForWindowResize(int* new_width, int* new_height); -#endif +void GSgetInternalResolution(int* width, int* height); +void GSgetStats(std::string& info); +void GSgetTitleStats(std::string& info); + +void GSUpdateConfig(const Pcsx2Config::GSOptions& new_config); +void GSSwitchRenderer(GSRendererType new_renderer); +void GSResetAPIState(); +void GSRestoreAPIState(); +bool GSSaveSnapshotToMemory(u32 width, u32 height, std::vector* pixels); class GSApp { std::string m_section; std::map m_default_configuration; std::map m_configuration_map; - GSRendererType m_current_renderer_type; public: std::string m_ini; @@ -154,12 +103,14 @@ class GSApp void Init(); +#ifndef PCSX2_CORE void BuildConfigurationMap(const char* lpFileName); void ReloadConfig(); + int GetIniInt(const char* lpAppName, const char* lpKeyName, int nDefault, const char* lpFileName); +#endif size_t GetIniString(const char* lpAppName, const char* lpKeyName, const char* lpDefault, char* lpReturnedString, size_t nSize, const char* lpFileName); bool WriteIniString(const char* lpAppName, const char* lpKeyName, const char* pString, const char* lpFileName); - int GetIniInt(const char* lpAppName, const char* lpKeyName, int nDefault, const char* lpFileName); void SetConfig(const char* entry, const char* value); void SetConfig(const char* entry, int value); @@ -173,9 +124,6 @@ class GSApp bool GetConfigB(const char* entry); std::string GetConfigS(const char* entry); - void SetCurrentRendererType(GSRendererType type); - GSRendererType GetCurrentRendererType() const; - void SetConfigDir(); std::vector m_gs_renderers; @@ -206,5 +154,3 @@ struct GSErrorGlVertexArrayTooSmall : GSError }; extern GSApp theApp; - -extern bool gsopen_done; diff --git a/pcsx2/GS/GSCapture.cpp b/pcsx2/GS/GSCapture.cpp index c97867bd839b0..153e247675244 100644 --- a/pcsx2/GS/GSCapture.cpp +++ b/pcsx2/GS/GSCapture.cpp @@ -397,11 +397,6 @@ GSCapture::GSCapture() : m_capturing(false), m_frame(0) , m_out_dir("/tmp/GS_Capture") // FIXME Later add an option { - m_out_dir = theApp.GetConfigS("capture_out_dir"); - m_threads = theApp.GetConfigI("capture_threads"); -#if defined(__unix__) - m_compression_level = theApp.GetConfigI("png_compression_level"); -#endif } GSCapture::~GSCapture() @@ -418,6 +413,13 @@ bool GSCapture::BeginCapture(float fps, GSVector2i recommendedResolution, float EndCapture(); + // reload settings because they may have changed + m_out_dir = theApp.GetConfigS("capture_out_dir"); + m_threads = theApp.GetConfigI("capture_threads"); +#if defined(__unix__) + m_compression_level = theApp.GetConfigI("png_compression_level"); +#endif + #ifdef _WIN32 GSCaptureDlg dlg; diff --git a/pcsx2/GS/GSDrawingContext.cpp b/pcsx2/GS/GSDrawingContext.cpp index 19d8146afe5d0..8a446af8706ec 100644 --- a/pcsx2/GS/GSDrawingContext.cpp +++ b/pcsx2/GS/GSDrawingContext.cpp @@ -120,7 +120,7 @@ GIFRegTEX0 GSDrawingContext::GetSizeFixedTEX0(const GSVector4& st, bool linear, th = extend(uv.y, th); } - if ((theApp.GetCurrentRendererType() == GSRendererType::OGL_SW) && ((int)TEX0.TW != tw || (int)TEX0.TH != th)) + if (GSConfig.Renderer == GSRendererType::SW && ((int)TEX0.TW != tw || (int)TEX0.TH != th)) { GL_DBG("FixedTEX0 %05x %d %d tw %d=>%d th %d=>%d st (%.0f,%.0f,%.0f,%.0f) uvmax %d,%d wm %d,%d (%d,%d,%d,%d)", (int)TEX0.TBP0, (int)TEX0.TBW, (int)TEX0.PSM, diff --git a/pcsx2/GS/GSExtra.h b/pcsx2/GS/GSExtra.h index 42a0ab071cdb9..0899b9c10a070 100644 --- a/pcsx2/GS/GSExtra.h +++ b/pcsx2/GS/GSExtra.h @@ -18,6 +18,7 @@ #include "GSVector.h" #ifdef _WIN32 +#include "common/RedtapeWindows.h" inline std::string convert_utf16_to_utf8(const std::wstring& utf16_string) { const int size = WideCharToMultiByte(CP_UTF8, 0, utf16_string.c_str(), utf16_string.size(), nullptr, 0, nullptr, nullptr); diff --git a/pcsx2/GS/GSLocalMemory.cpp b/pcsx2/GS/GSLocalMemory.cpp index 8bf476e1e91ad..33e2bb7919f66 100644 --- a/pcsx2/GS/GSLocalMemory.cpp +++ b/pcsx2/GS/GSLocalMemory.cpp @@ -62,14 +62,9 @@ GSLocalMemory::GSLocalMemory() : m_clut(this) { m_use_fifo_alloc = theApp.GetConfigB("UserHacks") && theApp.GetConfigB("wrap_gs_mem"); - switch (theApp.GetCurrentRendererType()) - { - case GSRendererType::OGL_SW: - m_use_fifo_alloc = true; - break; - default: - break; - } + + if (!GSConfig.UseHardwareRenderer()) + m_use_fifo_alloc = true; if (m_use_fifo_alloc) m_vm8 = (u8*)fifo_alloc(m_vmsize, 4); diff --git a/pcsx2/GS/GSState.cpp b/pcsx2/GS/GSState.cpp index d3847927f204e..b443036edddec 100644 --- a/pcsx2/GS/GSState.cpp +++ b/pcsx2/GS/GSState.cpp @@ -31,7 +31,6 @@ GSState::GSState() , m_q(1.0f) , m_scanmask_used(false) , m_vt(this) - , m_dev(nullptr) , m_regs(NULL) , m_crc(0) , m_options(0) @@ -46,15 +45,11 @@ GSState::GSState() { m_userhacks_auto_flush = theApp.GetConfigB("UserHacks_AutoFlush"); m_userhacks_wildhack = theApp.GetConfigB("UserHacks_WildHack"); - m_userhacks_skipdraw = theApp.GetConfigI("UserHacks_SkipDraw"); - m_userhacks_skipdraw_offset = theApp.GetConfigI("UserHacks_SkipDraw_Offset"); } else { m_userhacks_auto_flush = false; m_userhacks_wildhack = false; - m_userhacks_skipdraw = 0; - m_userhacks_skipdraw_offset = 0; } s_n = 0; @@ -76,7 +71,7 @@ GSState::GSState() m_crc_hack_level = theApp.GetConfigT("crc_hack_level"); if (m_crc_hack_level == CRCHackLevel::Automatic) - m_crc_hack_level = GSUtil::GetRecommendedCRCHackLevel(theApp.GetCurrentRendererType()); + m_crc_hack_level = GSUtil::GetRecommendedCRCHackLevel(GSConfig.Renderer); memset(&m_v, 0, sizeof(m_v)); memset(&m_vertex, 0, sizeof(m_vertex)); @@ -152,13 +147,6 @@ GSState::~GSState() _aligned_free(m_index.buff); } -void GSState::SetRegsMem(u8* basemem) -{ - ASSERT(basemem); - - m_regs = (GSPrivRegSet*)basemem; -} - void GSState::SetFrameSkip(int skip) { if (m_frameskip == skip) @@ -269,9 +257,7 @@ void GSState::ResetHandlers() m_fpGIFPackedRegHandlers[GIF_REG_NOP] = &GSState::GIFPackedRegHandlerNOP; // swap first/last indices when the provoking vertex is the first (D3D/Vulkan) - const GSRendererType renderer = theApp.GetCurrentRendererType(); - const bool is_hardware_renderer = (renderer == GSRendererType::DX1011_HW || renderer == GSRendererType::OGL_HW); - const bool index_swap = is_hardware_renderer && m_dev && !m_dev->Features().provoking_vertex_last; + const bool index_swap = GSConfig.UseHardwareRenderer() && !g_gs_device->Features().provoking_vertex_last; if (m_userhacks_auto_flush) index_swap ? SetPrimHandlers() : SetPrimHandlers(); else @@ -728,9 +714,7 @@ __forceinline void GSState::ApplyPRIM(u32 prim) if (GSUtil::GetPrimClass(m_env.PRIM.PRIM) == GSUtil::GetPrimClass(prim & 7)) // NOTE: assume strips/fans are converted to lists { u32 prim_mask = 0x7f8; - const bool is_hardware_renderer = - ((theApp.GetCurrentRendererType() == GSRendererType::OGL_HW) || (theApp.GetCurrentRendererType() == GSRendererType::DX1011_HW)); - if (is_hardware_renderer && GSUtil::GetPrimClass(prim & 7) == GS_TRIANGLE_CLASS) + if (GSConfig.UseHardwareRenderer() && GSUtil::GetPrimClass(prim & 7) == GS_TRIANGLE_CLASS) prim_mask &= ~0x80; // Mask out AA1. if (m_env.PRMODECONT.AC == 1 && (m_env.PRIM.U32[0] ^ prim) & prim_mask) // all fields except PRIM @@ -1067,9 +1051,7 @@ void GSState::GIFRegHandlerPRMODE(const GIFReg* RESTRICT r) if (!m_env.PRMODECONT.AC) { u32 prim_mask = 0x7f8; - const bool is_hardware_renderer = - ((theApp.GetCurrentRendererType() == GSRendererType::OGL_HW) || (theApp.GetCurrentRendererType() == GSRendererType::DX1011_HW)); - if (is_hardware_renderer && GSUtil::GetPrimClass(m_env.PRIM.PRIM) == GS_TRIANGLE_CLASS) + if (GSConfig.UseHardwareRenderer() && GSUtil::GetPrimClass(m_env.PRIM.PRIM) == GS_TRIANGLE_CLASS) prim_mask &= ~0x80; // Mask out AA1. if ((m_env.PRIM.U32[0] ^ r->PRMODE.U32[0]) & prim_mask) @@ -2235,7 +2217,7 @@ void GSState::SetGameCRC(u32 crc, int options) { m_crc = crc; m_options = options; - m_game = CRC::Lookup(m_crc_hack_level != CRCHackLevel::None ? crc : 0); + m_game = CRC::Lookup(m_crc_hack_level != CRCHackLevel::Off ? crc : 0); SetupCrcHack(); } diff --git a/pcsx2/GS/GSState.h b/pcsx2/GS/GSState.h index 161d04ed8aa8e..ceb482187c760 100644 --- a/pcsx2/GS/GSState.h +++ b/pcsx2/GS/GSState.h @@ -155,8 +155,6 @@ class GSState : public GSAlignedClass<32> GetSkipCount m_gsc; int m_skip; int m_skip_offset; - int m_userhacks_skipdraw; - int m_userhacks_skipdraw_offset; bool m_userhacks_auto_flush; GSVertex m_v; @@ -208,7 +206,6 @@ class GSState : public GSAlignedClass<32> GIFRegTEX0 GetTex0Layer(u32 lod); public: - GSDevice* m_dev; GIFPath m_path[4]; GIFRegPRIM* PRIM; GSPrivRegSet* m_regs; @@ -272,7 +269,13 @@ class GSState : public GSAlignedClass<32> template void Transfer(const u8* mem, u32 size); int Freeze(freezeData* fd, bool sizeonly); int Defrost(const freezeData* fd); + + u32 GetGameCRC() const { return m_crc; } + int GetGameCRCOptions() const { return m_options; } virtual void SetGameCRC(u32 crc, int options); + + u8* GetRegsMem() const { return reinterpret_cast(m_regs); } + void SetRegsMem(u8* basemem) { m_regs = reinterpret_cast(basemem); } + void SetFrameSkip(int skip); - void SetRegsMem(u8* basemem); }; diff --git a/pcsx2/GS/GSUtil.cpp b/pcsx2/GS/GSUtil.cpp index f7f64368ae439..b5ed799972be3 100644 --- a/pcsx2/GS/GSUtil.cpp +++ b/pcsx2/GS/GSUtil.cpp @@ -185,28 +185,16 @@ bool GSUtil::CheckSSE() CRCHackLevel GSUtil::GetRecommendedCRCHackLevel(GSRendererType type) { - return type == GSRendererType::OGL_HW ? CRCHackLevel::Partial : CRCHackLevel::Full; + return type == GSRendererType::OGL ? CRCHackLevel::Partial : CRCHackLevel::Full; } GSRendererType GSUtil::GetPreferredRenderer() { #ifdef _WIN32 if (D3D::ShouldPreferD3D()) - return GSRendererType::DX1011_HW; + return GSRendererType::DX11; #endif - return GSRendererType::OGL_HW; -} - -std::vector GSUtil::GetAdapterList(GSRendererType renderer) -{ -#ifdef _WIN32 - if (renderer == GSRendererType::DX1011_HW) - { - auto factory = D3D::CreateFactory(false); - return D3D::GetAdapterList(factory.get()); - } -#endif - return {}; + return GSRendererType::OGL; } #ifdef _WIN32 diff --git a/pcsx2/GS/GSUtil.h b/pcsx2/GS/GSUtil.h index d284f7afb6595..df893a3146e8d 100644 --- a/pcsx2/GS/GSUtil.h +++ b/pcsx2/GS/GSUtil.h @@ -42,7 +42,6 @@ class GSUtil static bool CheckSSE(); static CRCHackLevel GetRecommendedCRCHackLevel(GSRendererType type); static GSRendererType GetPreferredRenderer(); - static std::vector GetAdapterList(GSRendererType renderer); }; #ifdef _WIN32 diff --git a/pcsx2/GS/Renderers/Common/GSDevice.cpp b/pcsx2/GS/Renderers/Common/GSDevice.cpp index 6be23618726eb..2a49f3d8e7ee0 100644 --- a/pcsx2/GS/Renderers/Common/GSDevice.cpp +++ b/pcsx2/GS/Renderers/Common/GSDevice.cpp @@ -41,17 +41,16 @@ const char* shaderName(ShaderConvert value) case ShaderConvert::RGB5A1_TO_FLOAT16: return "ps_convert_rgb5a1_float16"; case ShaderConvert::RGBA_TO_8I: return "ps_convert_rgba_8i"; case ShaderConvert::YUV: return "ps_yuv"; - case ShaderConvert::OSD: return "ps_osd"; default: ASSERT(0); return "ShaderConvertUnknownShader"; } } +std::unique_ptr g_gs_device; + GSDevice::GSDevice() - : m_vsync(false) - , m_rbswapped(false) - , m_backbuffer(NULL) + : m_rbswapped(false) , m_merge(NULL) , m_weavebob(NULL) , m_blend(NULL) @@ -61,73 +60,47 @@ GSDevice::GSDevice() { memset(&m_vertex, 0, sizeof(m_vertex)); memset(&m_index, 0, sizeof(m_index)); - m_linear_present = theApp.GetConfigB("linear_present"); } GSDevice::~GSDevice() { PurgePool(); - delete m_backbuffer; + delete m_merge; delete m_weavebob; delete m_blend; delete m_target_tmp; } -bool GSDevice::Create(const WindowInfo& wi) +bool GSDevice::Create(HostDisplay* display) { + m_display = display; return true; } -bool GSDevice::Reset(int w, int h) +void GSDevice::Destroy() { PurgePool(); - delete m_backbuffer; delete m_merge; delete m_weavebob; delete m_blend; delete m_target_tmp; - m_backbuffer = nullptr; m_merge = nullptr; m_weavebob = nullptr; m_blend = nullptr; m_target_tmp = nullptr; m_current = nullptr; // current is special, points to other textures, no need to delete - return true; } -void GSDevice::Present(const GSVector4i& r, int shader) +void GSDevice::ResetAPIState() { - GL_PUSH("Present"); - -#ifndef PCSX2_CORE - int new_width, new_height; - if (GSCheckForWindowResize(&new_width, &new_height) && !Reset(new_width, new_height)) - return; -#endif - - // FIXME is it mandatory, it could be slow - ClearRenderTarget(m_backbuffer, 0); - - if (m_current) - { - static constexpr ShaderConvert s_shader[5] = {ShaderConvert::COPY, ShaderConvert::SCANLINE, - ShaderConvert::DIAGONAL_FILTER, ShaderConvert::TRIANGULAR_FILTER, - ShaderConvert::COMPLEX_FILTER}; // FIXME - - Present(m_current, m_backbuffer, GSVector4(r), s_shader[shader]); - RenderOsd(m_backbuffer); - } - - Flip(); } -void GSDevice::Present(GSTexture* sTex, GSTexture* dTex, const GSVector4& dRect, ShaderConvert shader) +void GSDevice::RestoreAPIState() { - StretchRect(sTex, dTex, dRect, shader, m_linear_present); } GSTexture* GSDevice::FetchSurface(GSTexture::Type type, int w, int h, GSTexture::Format format) diff --git a/pcsx2/GS/Renderers/Common/GSDevice.h b/pcsx2/GS/Renderers/Common/GSDevice.h index c04adf91871f6..4aacfcb187340 100644 --- a/pcsx2/GS/Renderers/Common/GSDevice.h +++ b/pcsx2/GS/Renderers/Common/GSDevice.h @@ -21,12 +21,13 @@ #include "GSVertex.h" #include "GS/GSAlignedClass.h" #include "GS/GSExtra.h" -#include "GSOsdManager.h" #include #ifdef _WIN32 #include #endif +class HostDisplay; + enum class ShaderConvert { COPY = 0, @@ -48,7 +49,6 @@ enum class ShaderConvert RGB5A1_TO_FLOAT16, RGBA_TO_8I = 17, YUV, - OSD, Count }; @@ -526,9 +526,8 @@ class GSDevice : public GSAlignedClass<32> static const int m_NO_BLEND = 0; static const int m_MERGE_BLEND = m_blendMap.size() - 1; - int m_vsync; bool m_rbswapped; - GSTexture* m_backbuffer; + HostDisplay* m_display; GSTexture* m_merge; GSTexture* m_weavebob; GSTexture* m_blend; @@ -557,11 +556,11 @@ class GSDevice : public GSAlignedClass<32> virtual u16 ConvertBlendEnum(u16 generic) = 0; // Convert blend factors/ops from the generic enum to DX11/OGl specific. public: - GSOsdManager m_osd; - GSDevice(); virtual ~GSDevice(); + __fi HostDisplay* GetDisplay() const { return m_display; } + void Recycle(GSTexture* t); enum @@ -571,14 +570,11 @@ class GSDevice : public GSAlignedClass<32> DontCare }; - virtual bool Create(const WindowInfo& wi); - virtual bool Reset(int w, int h); - virtual bool IsLost(bool update = false) { return false; } - virtual void Present(const GSVector4i& r, int shader); - virtual void Present(GSTexture* sTex, GSTexture* dTex, const GSVector4& dRect, ShaderConvert shader = ShaderConvert::COPY); - virtual void Flip() {} + virtual bool Create(HostDisplay* display); + virtual void Destroy(); - virtual void SetVSync(int vsync) { m_vsync = vsync; } + virtual void ResetAPIState(); + virtual void RestoreAPIState(); virtual void BeginScene() {} virtual void EndScene(); @@ -626,7 +622,6 @@ class GSDevice : public GSAlignedClass<32> void FXAA(); void ShadeBoost(); void ExternalFX(); - virtual void RenderOsd(GSTexture* dt) {}; bool ResizeTexture(GSTexture** t, GSTexture::Type type, int w, int h); bool ResizeTexture(GSTexture** t, int w, int h); @@ -634,8 +629,6 @@ class GSDevice : public GSAlignedClass<32> bool ResizeTarget(GSTexture** t); bool IsRBSwapped() { return m_rbswapped; } - int GetBackbufferWidth() const { return m_backbuffer ? m_backbuffer->GetWidth() : 0; } - int GetBackbufferHeight() const { return m_backbuffer ? m_backbuffer->GetHeight() : 0; } void AgePool(); void PurgePool(); @@ -673,3 +666,5 @@ struct GSAdapter // TODO #endif }; + +extern std::unique_ptr g_gs_device; \ No newline at end of file diff --git a/pcsx2/GS/Renderers/Common/GSOsdManager.cpp b/pcsx2/GS/Renderers/Common/GSOsdManager.cpp deleted file mode 100644 index 4ed0c6093bd3e..0000000000000 --- a/pcsx2/GS/Renderers/Common/GSOsdManager.cpp +++ /dev/null @@ -1,502 +0,0 @@ -/* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2021 PCSX2 Dev Team - * - * PCSX2 is free software: you can redistribute it and/or modify it under the terms - * of the GNU Lesser General Public License as published by the Free Software Found- - * ation, either version 3 of the License, or (at your option) any later version. - * - * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with PCSX2. - * If not, see . - */ - -#include "PrecompiledHeader.h" -#include "GSOsdManager.h" -#include "GS/GS.h" -#include "Host.h" - -#if __GNUC__ > 5 || (__GNUC__ == 5 && __GNUC_MINOR__ >= 4) -#include -#include -#endif - -void GSOsdManager::LoadFont() -{ - FT_Error error = FT_New_Face(m_library, theApp.GetConfigS("osd_fontname").c_str(), 0, &m_face); - if (error) - { - FT_Error error_load_res = 1; - auto buffer = Host::ReadResourceFile("fonts/Roboto-Regular.ttf"); - if (buffer.has_value()) - { - resource_data_buffer = std::move(*buffer); - error_load_res = FT_New_Memory_Face(m_library, resource_data_buffer.data(), resource_data_buffer.size(), 0, &m_face); - } - - if (error_load_res) - { - m_face = NULL; - fprintf(stderr, "Failed to init freetype face from external and internal resource\n"); - if (error == FT_Err_Unknown_File_Format) - fprintf(stderr, "\tFreetype unknown file format for external file\n"); - return; - } - } - - LoadSize(); -} - -void GSOsdManager::LoadSize() -{ - if (!m_face) - return; - - FT_Error error = FT_Set_Pixel_Sizes(m_face, 0, m_size); - if (error) - { - fprintf(stderr, "Failed to init the face size\n"); - return; - } - - /* This is not exact, I'm sure there's some convoluted way to determine these - * from FreeType but they don't make it easy. */ - m_atlas_w = m_size * 96; // random guess - m_atlas_h = m_size + 10; // another random guess -} - -GSOsdManager::GSOsdManager() - : m_atlas_h(0) - , m_atlas_w(0) - , m_max_width(0) - , m_onscreen_messages(0) - , m_texture_dirty(true) -{ - m_monitor_enabled = theApp.GetConfigB("osd_monitor_enabled"); - m_log_enabled = theApp.GetConfigB("osd_log_enabled"); - m_size = std::clamp(theApp.GetConfigI("osd_fontsize"), 1, 100); - m_opacity = std::clamp(theApp.GetConfigI("osd_color_opacity"), 0, 100); - m_log_timeout = std::clamp(theApp.GetConfigI("osd_log_timeout"), 2, 10); - m_max_onscreen_messages = std::clamp(theApp.GetConfigI("osd_max_log_messages"), 1, 20); - - const int r = std::clamp(theApp.GetConfigI("osd_color_r"), 0, 255); - const int g = std::clamp(theApp.GetConfigI("osd_color_g"), 0, 255); - const int b = std::clamp(theApp.GetConfigI("osd_color_b"), 0, 255); - - m_color = r | (g << 8) | (b << 16) | (255 << 24); - - if (FT_Init_FreeType(&m_library)) - { - m_face = NULL; - fprintf(stderr, "Failed to init the freetype library\n"); - return; - } - - LoadFont(); - - /* The space character's width is used in GeneratePrimitives() */ - AddGlyph(' '); -} - -GSOsdManager::~GSOsdManager() -{ - FT_Done_FreeType(m_library); -} - -GSVector2i GSOsdManager::get_texture_font_size() -{ - return GSVector2i(m_atlas_w, m_atlas_h); -} - -void GSOsdManager::upload_texture_atlas(GSTexture* t) -{ - if (!m_face) - return; - - if (m_char_info.size() > 96) // we only reserved space for this many glyphs - fprintf(stderr, "More than 96 glyphs needed for OSD"); - - // This can be sped up a bit by only uploading new glyphs - int x = 0; - for (auto& pair : m_char_info) - { - if (FT_Load_Char(m_face, pair.first, FT_LOAD_RENDER)) - { - fprintf(stderr, "failed to load char U%d\n", (int)pair.first); - continue; - } - - // Size of char - pair.second.ax = m_face->glyph->advance.x >> 6; - pair.second.ay = m_face->glyph->advance.y >> 6; - - pair.second.bw = m_face->glyph->bitmap.width; - pair.second.bh = m_face->glyph->bitmap.rows; - - pair.second.bl = m_face->glyph->bitmap_left; - pair.second.bt = m_face->glyph->bitmap_top; - - GSVector4i r(x, 0, x + pair.second.bw, pair.second.bh); - if (r.width()) - t->Update(r, m_face->glyph->bitmap.buffer, m_face->glyph->bitmap.pitch); - - if (r.width() > m_max_width) - m_max_width = r.width(); - - pair.second.tx = (float)x / m_atlas_w; - pair.second.ty = (float)pair.second.bh / m_atlas_h; - pair.second.tw = (float)pair.second.bw / m_atlas_w; - - x += pair.second.bw; - } - - m_texture_dirty = false; -} - -#if __GNUC__ < 5 || (__GNUC__ == 5 && __GNUC_MINOR__ < 4) -/* This is dumb in that it doesn't check for malformed UTF8. This function - * is not expected to operate on user input, but only on compiled in strings */ -void dumb_utf8_to_utf32(const char* utf8, char32_t* utf32, unsigned size) -{ - while (*utf8 && --size) - { - if ((*utf8 & 0xF1) == 0xF0) - { - *utf32++ = (utf8[0] & 0x07) << 18 | (utf8[1] & 0x3F) << 12 | (utf8[2] & 0x3F) << 6 | utf8[3] & 0x3F; - utf8 += 4; - } - else if ((*utf8 & 0xF0) == 0xE0) - { - *utf32++ = (utf8[0] & 0x0F) << 12 | (utf8[1] & 0x3F) << 6 | utf8[2] & 0x3F; - utf8 += 3; - } - else if ((*utf8 & 0xE0) == 0xC0) - { - *utf32++ = (utf8[0] & 0x1F) << 6 | utf8[1] & 0x3F; - utf8 += 2; - } - else if ((*utf8 & 0x80) == 0x00) - { - *utf32++ = utf8[0] & 0x7F; - utf8 += 1; - } - } - - if (size) - *utf32 = *utf8; // Copy NUL char -} -#endif - -void GSOsdManager::AddGlyph(char32_t codepoint) -{ - if (!m_face) - return; - if (m_char_info.count(codepoint) == 0) - { - m_texture_dirty = true; - m_char_info[codepoint]; // add it - if (FT_HAS_KERNING(m_face)) - { - FT_UInt new_glyph = FT_Get_Char_Index(m_face, codepoint); - for (auto pair : m_char_info) - { - FT_Vector delta; - - FT_UInt glyph_index = FT_Get_Char_Index(m_face, pair.first); - FT_Get_Kerning(m_face, glyph_index, new_glyph, FT_KERNING_DEFAULT, &delta); - m_kern_info[std::make_pair(pair.first, codepoint)] = delta.x >> 6; - } - } - } -} - -void GSOsdManager::Log(const char* utf8) -{ - if (!m_log_enabled) - return; - -#if __GNUC__ < 5 || (__GNUC__ == 5 && __GNUC_MINOR__ < 4) - char32_t buffer[256]; - dumb_utf8_to_utf32(utf8, buffer, std::size(buffer)); - for (char32_t* c = buffer; *c; ++c) - AddGlyph(*c); -#else -#if _MSC_VER == 1900 - std::wstring_convert, unsigned int> conv; -#else - std::wstring_convert, char32_t> conv; -#endif - std::u32string buffer = conv.from_bytes(utf8); - for (auto const& c : buffer) - AddGlyph(c); -#endif - m_onscreen_messages++; - m_log.push_back(log_info{buffer, std::chrono::system_clock::time_point()}); -} - -void GSOsdManager::Monitor(const char* key, const char* value) -{ - if (!m_monitor_enabled) - return; - - if (value && *value) - { -#if __GNUC__ < 5 || (__GNUC__ == 5 && __GNUC_MINOR__ < 4) - char32_t buffer[256], vbuffer[256]; - dumb_utf8_to_utf32(key, buffer, std::size(buffer)); - dumb_utf8_to_utf32(value, vbuffer, std::size(vbuffer)); - for (char32_t* c = buffer; *c; ++c) - AddGlyph(*c); - for (char32_t* c = vbuffer; *c; ++c) - AddGlyph(*c); -#else -#if _MSC_VER == 1900 - std::wstring_convert, unsigned int> conv; -#else - std::wstring_convert, char32_t> conv; -#endif - std::u32string buffer = conv.from_bytes(key); - std::u32string vbuffer = conv.from_bytes(value); - for (auto const& c : buffer) - AddGlyph(c); - for (auto const& c : vbuffer) - AddGlyph(c); -#endif - m_monitor[buffer] = vbuffer; - } - else - { -#if __GNUC__ < 5 || (__GNUC__ == 5 && __GNUC_MINOR__ < 4) - char32_t buffer[256]; - dumb_utf8_to_utf32(key, buffer, std::size(buffer)); -#else -#if _MSC_VER == 1900 - std::wstring_convert, unsigned int> conv; -#else - std::wstring_convert, char32_t> conv; -#endif - std::u32string buffer = conv.from_bytes(key); -#endif - m_monitor.erase(buffer); - } -} - -void GSOsdManager::RenderGlyph(GSVertexPT1* dst, const glyph_info g, float x, float y, u32 color) -{ - float x2 = x + g.bl * (2.0f / m_real_size.x); - float y2 = -y - g.bt * (2.0f / m_real_size.y); - float w = g.bw * (2.0f / m_real_size.x); - float h = g.bh * (2.0f / m_real_size.y); - - dst->p = GSVector4(x2 , -y2 , 0.0f, 1.0f); - dst->t = GSVector2(g.tx , 0.0f); - dst->c = color; - ++dst; - dst->p = GSVector4(x2 + w, -y2 , 0.0f, 1.0f); - dst->t = GSVector2(g.tx + g.tw, 0.0f); - dst->c = color; - ++dst; - dst->p = GSVector4(x2 , -y2 - h, 0.0f, 1.0f); - dst->t = GSVector2(g.tx , g.ty); - dst->c = color; - ++dst; - dst->p = GSVector4(x2 + w, -y2 , 0.0f, 1.0f); - dst->t = GSVector2(g.tx + g.tw, 0.0f); - dst->c = color; - ++dst; - dst->p = GSVector4(x2 , -y2 - h, 0.0f, 1.0f); - dst->t = GSVector2(g.tx , g.ty); - dst->c = color; - ++dst; - dst->p = GSVector4(x2 + w, -y2 - h, 0.0f, 1.0f); - dst->t = GSVector2(g.tx + g.tw, g.ty); - dst->c = color; - ++dst; -} - -void GSOsdManager::RenderString(GSVertexPT1* dst, const std::u32string msg, float x, float y, u32 color) -{ - char32_t p = 0; - for (const auto& c : msg) - { - if (p) - { - x += m_kern_info[std::make_pair(p, c)] * (2.0f / m_real_size.x); - } - - RenderGlyph(dst, m_char_info[c], x, y, color); - - /* Advance the cursor to the start of the next character */ - x += m_char_info[c].ax * (2.0f / m_real_size.x); - y += m_char_info[c].ay * (2.0f / m_real_size.y); - - dst += 6; - - p = c; - } -} - -size_t GSOsdManager::Size() -{ - size_t sum = 0; - - if (m_log_enabled) - { - float offset = 0; - - for (auto it = m_log.begin(); it != m_log.end(); ++it) - { - float y = 1 - ((m_size + 2) * (it - m_log.begin() + 1)) * (2.0f / m_real_size.y); - if (y + offset < -1) - break; - - std::chrono::duration elapsed; - if (it->OnScreen.time_since_epoch().count() == 0) - { - elapsed = std::chrono::seconds(0); - } - else - { - elapsed = std::chrono::system_clock::now() - it->OnScreen; - if (elapsed > std::chrono::seconds(m_log_timeout) || m_onscreen_messages > m_max_onscreen_messages) - { - continue; - } - } - - float ratio = (elapsed - std::chrono::seconds(m_log_timeout / 2)).count() / std::chrono::seconds(m_log_timeout / 2).count(); - ratio = ratio > 1.0f ? 1.0f : ratio < 0.0f ? 0.0f : ratio; - - y += offset += ((m_size + 2) * (2.0f / m_real_size.y)) * ratio; - sum += it->msg.size(); - } - } - - if (m_monitor_enabled) - { - for (const auto& pair : m_monitor) - { - sum += pair.first.size(); - sum += pair.second.size(); - } - } - - return sum * 6; -} - -float GSOsdManager::StringSize(const std::u32string msg) -{ - char32_t p = 0; - float x = 0.0; - - for (auto c : msg) - { - if (p) - { - x += m_kern_info[std::make_pair(p, c)] * (2.0f / m_real_size.x); - } - - /* Advance the cursor to the start of the next character */ - x += m_char_info[c].ax * (2.0f / m_real_size.x); - - p = c; - } - - return x; -} - -size_t GSOsdManager::GeneratePrimitives(GSVertexPT1* dst, size_t count) -{ - size_t drawn = 0; - float opacity = m_opacity * 0.01f; - - if (m_log_enabled) - { - float offset = 0; - - for (auto it = m_log.begin(); it != m_log.end();) - { - float x = -1 + 8 * (2.0f / m_real_size.x); - float y = 1 - ((m_size + 2) * (it - m_log.begin() + 1)) * (2.0f / m_real_size.y); - - if (y + offset < -1) - break; - - if (it->OnScreen.time_since_epoch().count() == 0) - it->OnScreen = std::chrono::system_clock::now(); - - std::chrono::duration elapsed = std::chrono::system_clock::now() - it->OnScreen; - if (elapsed > std::chrono::seconds(m_log_timeout) || m_onscreen_messages > m_max_onscreen_messages) - { - m_onscreen_messages--; - it = m_log.erase(it); - continue; - } - - if (it->msg.size() * 6 > count - drawn) - break; - - float ratio = (elapsed - std::chrono::seconds(m_log_timeout / 2)).count() / std::chrono::seconds(m_log_timeout / 2).count(); - ratio = ratio > 1.0f ? 1.0f : ratio < 0.0f ? 0.0f : ratio; - - y += offset += ((m_size + 2) * (2.0f / m_real_size.y)) * ratio; - u32 color = m_color; - ((u8*)&color)[3] = (u8)(((u8*)&color)[3] * (1.0f - ratio) * opacity); - RenderString(dst, it->msg, x, y, color); - dst += it->msg.size() * 6; - drawn += it->msg.size() * 6; - ++it; - } - } - - if (m_monitor_enabled) - { - // pair.first is the key and second is the value and color - - // Since the monitor is right justified, but we render from left to right - // we need to find the longest string - float first_max = 0.0, second_max = 0.0; - for (const auto& pair : m_monitor) - { - float first_len = StringSize(pair.first); - float second_len = StringSize(pair.second); - - first_max = first_max < first_len ? first_len : first_max; - second_max = second_max < second_len ? second_len : second_max; - } - - size_t line = 1; - for (const auto& pair : m_monitor) - { - if ((pair.first.size() + pair.second.size()) * 6 > count - drawn) - break; - - // Calculate where to start rendering from by taking the right most position 1.0 - // and subtracting (going left) 8 scaled pixels for a margin, then subtracting - // the size of the longest key and subtracting a scaled space and finally - // subtracting the longest value - float x = 1.0f - 8 * (2.0f / m_real_size.x) - first_max - m_char_info[' '].ax * (2.0f / m_real_size.x) - second_max; - float y = -1.0f + ((m_size + 2) * (2.0f / m_real_size.y)) * line++; - - u32 color = m_color; - ((u8*)&color)[3] = (u8)(((u8*)&color)[3] * opacity); - - // Render the key - RenderString(dst, pair.first, x, y, color); - dst += pair.first.size() * 6; - drawn += pair.first.size() * 6; - - // Calculate the position for the value - x = 1.0f - 8 * (2.0f / m_real_size.x) - second_max; - - // Render the value - RenderString(dst, pair.second, x, y, color); - dst += pair.second.size() * 6; - drawn += pair.second.size() * 6; - } - } - - return drawn; -} diff --git a/pcsx2/GS/Renderers/Common/GSOsdManager.h b/pcsx2/GS/Renderers/Common/GSOsdManager.h deleted file mode 100644 index 15fbed1fe0f67..0000000000000 --- a/pcsx2/GS/Renderers/Common/GSOsdManager.h +++ /dev/null @@ -1,98 +0,0 @@ -/* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2021 PCSX2 Dev Team - * - * PCSX2 is free software: you can redistribute it and/or modify it under the terms - * of the GNU Lesser General Public License as published by the Free Software Found- - * ation, either version 3 of the License, or (at your option) any later version. - * - * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with PCSX2. - * If not, see . - */ - -#pragma once - -#include "PrecompiledHeader.h" -#include "GS/GSVector.h" -#include "GSVertex.h" -#include "GSTexture.h" -#include "ft2build.h" -#include FT_FREETYPE_H -#include - -class GSOsdManager -{ - struct glyph_info - { - s32 ax; // advance.x - s32 ay; // advance.y - - u32 bw; // bitmap.width; - u32 bh; // bitmap.rows; - - s32 bl; // bitmap_left; - s32 bt; // bitmap_top; - - float tx; // x offset of glyph - float ty; // y offset of glyph - float tw; // nomalized glyph width - }; - - std::map m_char_info; - std::map, FT_Pos> m_kern_info; - - FT_Library m_library; - FT_Face m_face; - FT_UInt m_size; - - u32 m_atlas_h; - u32 m_atlas_w; - s32 m_max_width; - s32 m_onscreen_messages; - - struct log_info - { - std::u32string msg; - std::chrono::system_clock::time_point OnScreen; - }; - std::vector m_log; - - std::map m_monitor; - - void AddGlyph(char32_t codepoint); - void RenderGlyph(GSVertexPT1* dst, const glyph_info g, float x, float y, u32 color); - void RenderString(GSVertexPT1* dst, const std::u32string msg, float x, float y, u32 color); - float StringSize(const std::u32string msg); - - bool m_log_enabled; - int m_log_timeout; - bool m_monitor_enabled; - int m_opacity; - u32 m_color; - int m_max_onscreen_messages; - -public: - GSOsdManager(); - ~GSOsdManager(); - - void LoadFont(); - void LoadSize(); - - GSVector2i get_texture_font_size(); - - bool m_texture_dirty; - void upload_texture_atlas(GSTexture* t); - - void Log(const char* utf8); - void Monitor(const char* key, const char* value); - - GSVector2i m_real_size; - size_t Size(); - size_t GeneratePrimitives(GSVertexPT1* dst, size_t count); - -private: - std::vector resource_data_buffer; -}; diff --git a/pcsx2/GS/Renderers/Common/GSRenderer.cpp b/pcsx2/GS/Renderers/Common/GSRenderer.cpp index 9f8e60aa1ff40..c600a55d9fce0 100644 --- a/pcsx2/GS/Renderers/Common/GSRenderer.cpp +++ b/pcsx2/GS/Renderers/Common/GSRenderer.cpp @@ -15,68 +15,32 @@ #include "PrecompiledHeader.h" #include "GSRenderer.h" -#include "gui/AppConfig.h" #include "GS/GSGL.h" +#include "Host.h" +#include "HostDisplay.h" +#include "PerformanceMetrics.h" +#include "pcsx2/Config.h" +#include "common/StringUtil.h" #if defined(__unix__) #include #endif -const unsigned int s_interlace_nb = 8; -const unsigned int s_post_shader_nb = 5; const unsigned int s_mipmap_nb = 3; GSRenderer::GSRenderer() - : m_shader(0) - , m_shift_key(false) + : m_shift_key(false) , m_control_key(false) , m_texture_shuffle(false) , m_real_size(0, 0) { - m_GStitleInfoBuffer[0] = 0; - - m_interlace = theApp.GetConfigI("interlace") % s_interlace_nb; - m_shader = theApp.GetConfigI("TVShader") % s_post_shader_nb; - m_vsync = theApp.GetConfigI("vsync"); - m_aa1 = theApp.GetConfigB("aa1"); - m_fxaa = theApp.GetConfigB("fxaa"); - m_shaderfx = theApp.GetConfigB("shaderfx"); - m_shadeboost = theApp.GetConfigB("ShadeBoost"); - m_dithering = theApp.GetConfigI("dithering_ps2"); // 0 off, 1 auto, 2 auto no scale } GSRenderer::~GSRenderer() { - /*if(m_dev) - { - m_dev->Reset(1, 1, GSDevice::Windowed); - }*/ - - delete m_dev; -} - -bool GSRenderer::CreateDevice(GSDevice* dev, const WindowInfo& wi) -{ - ASSERT(dev); - ASSERT(!m_dev); - - if (!dev->Create(wi)) - { - return false; - } - - m_dev = dev; - m_dev->SetVSync(m_vsync); - - // reset handlers to pick up index swap if needed - ResetHandlers(); - - return true; } -void GSRenderer::ResetDevice() +void GSRenderer::Destroy() { - if (m_dev) - m_dev->Reset(1, 1); } bool GSRenderer::Merge(int field) @@ -265,37 +229,37 @@ bool GSRenderer::Merge(int field) GSVector4 c = GSVector4((int)m_regs->BGCOLOR.R, (int)m_regs->BGCOLOR.G, (int)m_regs->BGCOLOR.B, (int)m_regs->PMODE.ALP) / 255; - m_dev->Merge(tex, src_hw, dst, fs, m_regs->PMODE, m_regs->EXTBUF, c); + g_gs_device->Merge(tex, src_hw, dst, fs, m_regs->PMODE, m_regs->EXTBUF, c); - if (m_regs->SMODE2.INT && m_interlace > 0) + if (m_regs->SMODE2.INT && GSConfig.InterlaceMode != GSInterlaceMode::Off) { - if (m_interlace == 7 && m_regs->SMODE2.FFMD) // Auto interlace enabled / Odd frame interlace setting + if (GSConfig.InterlaceMode == GSInterlaceMode::Automatic && m_regs->SMODE2.FFMD) // Auto interlace enabled / Odd frame interlace setting { int field2 = 0; int mode = 2; - m_dev->Interlace(ds, field ^ field2, mode, tex[1] ? tex[1]->GetScale().y : tex[0]->GetScale().y); + g_gs_device->Interlace(ds, field ^ field2, mode, tex[1] ? tex[1]->GetScale().y : tex[0]->GetScale().y); } else { - int field2 = 1 - ((m_interlace - 1) & 1); - int mode = (m_interlace - 1) >> 1; - m_dev->Interlace(ds, field ^ field2, mode, tex[1] ? tex[1]->GetScale().y : tex[0]->GetScale().y); + int field2 = 1 - ((static_cast(GSConfig.InterlaceMode) - 1) & 1); + int mode = (static_cast(GSConfig.InterlaceMode) - 1) >> 1; + g_gs_device->Interlace(ds, field ^ field2, mode, tex[1] ? tex[1]->GetScale().y : tex[0]->GetScale().y); } } - if (m_shadeboost) + if (GSConfig.ShadeBoost) { - m_dev->ShadeBoost(); + g_gs_device->ShadeBoost(); } - if (m_shaderfx) + if (GSConfig.ShaderFX) { - m_dev->ExternalFX(); + g_gs_device->ExternalFX(); } - if (m_fxaa) + if (GSConfig.FXAA) { - m_dev->FXAA(); + g_gs_device->FXAA(); } } @@ -307,64 +271,118 @@ GSVector2i GSRenderer::GetInternalResolution() return m_real_size; } -GSVector4i GSRenderer::ComputeDrawRectangle(int width, int height) const +static float GetCurrentAspectRatioFloat() { - const double f_width = static_cast(width); - const double f_height = static_cast(height); - const double clientAr = f_width / f_height; + static constexpr std::array(AspectRatioType::MaxCount)> ars = { {4.0f / 3.0f, 4.0f / 3.0f, 16.0f / 9.0f} }; + return ars[static_cast(GSConfig.AspectRatio)]; +} - double targetAr = clientAr; +static GSVector4 CalculateDrawRect(s32 window_width, s32 window_height, s32 texture_width, s32 texture_height, HostDisplay::Alignment alignment, bool flip_y) +{ + const float f_width = static_cast(window_width); + const float f_height = static_cast(window_height); + const float clientAr = f_width / f_height; + float targetAr = clientAr; if (EmuConfig.CurrentAspectRatio == AspectRatioType::R4_3) - targetAr = 4.0 / 3.0; + targetAr = 4.0f / 3.0f; else if (EmuConfig.CurrentAspectRatio == AspectRatioType::R16_9) - targetAr = 16.0 / 9.0; + targetAr = 16.0f / 9.0f; const double arr = targetAr / clientAr; - double target_width = f_width; - double target_height = f_height; + float target_width = f_width; + float target_height = f_height; if (arr < 1) - target_width = std::floor(f_width * arr + 0.5); + target_width = std::floor(f_width * arr + 0.5f); else if (arr > 1) - target_height = std::floor(f_height / arr + 0.5); + target_height = std::floor(f_height / arr + 0.5f); - float zoom = EmuConfig.GS.Zoom / 100.0; + float zoom = GSConfig.Zoom / 100.0; if (zoom == 0) //auto zoom in untill black-bars are gone (while keeping the aspect ratio). zoom = std::max((float)arr, (float)(1.0 / arr)); target_width *= zoom; - target_height *= zoom * EmuConfig.GS.StretchY / 100.0; + target_height *= zoom * GSConfig.StretchY / 100.0f; + + if (GSConfig.IntegerScaling) + { + // make target width/height an integer multiple of the texture width/height + const float t_width = static_cast(texture_width); + const float t_height = static_cast(texture_height); + + float scale; + if ((t_width / t_height) >= 1.0) + scale = target_width / t_width; + else + scale = target_height / t_height; + + if (scale > 1.0) + { + const float adjust = std::floor(scale) / scale; + target_width = target_width * adjust; + target_height = target_height * adjust; + } + } - double target_x, target_y; - if (target_width > f_width) - target_x = -((target_width - f_width) * 0.5); + float target_x, target_y; + if (target_width >= f_width) + { + target_x = -((target_width - f_width) * 0.5f); + } else - target_x = (f_width - target_width) * 0.5; - if (target_height > f_height) - target_y = -((target_height - f_height) * 0.5); + { + switch (alignment) + { + case HostDisplay::Alignment::Center: + target_x = (f_width - target_width) * 0.5f; + break; + case HostDisplay::Alignment::RightOrBottom: + target_x = (f_width - target_width); + break; + case HostDisplay::Alignment::LeftOrTop: + default: + target_x = 0.0f; + break; + } + } + if (target_height >= f_height) + { + target_y = -((target_height - f_height) * 0.5f); + } else - target_y = (f_height - target_height) * 0.5; + { + switch (alignment) + { + case HostDisplay::Alignment::Center: + target_y = (f_height - target_height) * 0.5f; + break; + case HostDisplay::Alignment::RightOrBottom: + target_y = (f_height - target_height); + break; + case HostDisplay::Alignment::LeftOrTop: + default: + target_y = 0.0f; + break; + } + } - const double unit = .01 * std::min(target_x, target_y); - target_x += unit * EmuConfig.GS.OffsetX; - target_y += unit * EmuConfig.GS.OffsetY; + const float unit = .01f * std::min(target_x, target_y); + target_x += unit * GSConfig.OffsetX; + target_y += unit * GSConfig.OffsetY; - return GSVector4i( - static_cast(std::floor(target_x)), - static_cast(std::floor(target_y)), - static_cast(std::round(target_x + target_width)), - static_cast(std::round(target_y + target_height))); -} + GSVector4 ret(target_x, target_y, target_x + target_width, target_y + target_height); -void GSRenderer::SetVSync(int vsync) -{ - m_vsync = vsync; + if (flip_y) + { + const float height = ret.w - ret.y; + ret.y = static_cast(window_height) - ret.w; + ret.w = ret.y + height; + } - if (m_dev) - m_dev->SetVSync(m_vsync); + return ret; } -void GSRenderer::VSync(int field) +void GSRenderer::VSync(u32 field) { GSPerfMonAutoTimer pmat(&m_perfmon); @@ -377,104 +395,43 @@ void GSRenderer::VSync(int field) m_regs->Dump(root_sw + format("%05d_f%lld_gs_reg.txt", s_n, m_perfmon.GetFrame())); } - if (!m_dev->IsLost(true)) - { - if (!Merge(field ? 1 : 0)) - { - return; - } - } - else - { - ResetDevice(); - } + g_gs_device->AgePool(); - m_dev->AgePool(); + const bool blank_frame = !Merge(field ? 1 : 0); + const bool skip_frame = m_frameskip; - // osd + if (blank_frame || skip_frame) + { + g_gs_device->ResetAPIState(); + if (Host::BeginPresentFrame(skip_frame)) + Host::EndPresentFrame(); + g_gs_device->RestoreAPIState(); + return; + } if ((m_perfmon.GetFrame() & 0x1f) == 0) - { m_perfmon.Update(); - std::string s; - -#ifdef GSTITLEINFO_API_FORCE_VERBOSE - { - const double fps = 1000.0f / m_perfmon.Get(GSPerfMon::Frame); - //GS owns the window's title, be verbose. - static const char* aspect_ratio_names[static_cast(AspectRatioType::MaxCount)] = { "Stretch", "4:3", "16:9" }; - - std::string s2 = m_regs->SMODE2.INT ? (std::string("Interlaced ") + (m_regs->SMODE2.FFMD ? "(frame)" : "(field)")) : "Progressive"; - - s = format( - "%lld | %d x %d | %.2f fps (%d%%) | %s - %s | %s | %d S/%d P/%d D | %d%% CPU | %.2f | %.2f", - m_perfmon.GetFrame(), GetInternalResolution().x, GetInternalResolution().y, fps, (int)(100.0 * fps / GetTvRefreshRate()), - s2.c_str(), - theApp.m_gs_interlace[m_interlace].name.c_str(), - aspect_ratio_names[static_cast(EmuConfig.GS.AspectRatio)], - (int)m_perfmon.Get(GSPerfMon::SyncPoint), - (int)m_perfmon.Get(GSPerfMon::Prim), - (int)m_perfmon.Get(GSPerfMon::Draw), - m_perfmon.CPU(), - m_perfmon.Get(GSPerfMon::Swizzle) / 1024, - m_perfmon.Get(GSPerfMon::Unswizzle) / 1024); - - double fillrate = m_perfmon.Get(GSPerfMon::Fillrate); - - if (fillrate > 0) - { - s += format(" | %.2f mpps", fps * fillrate / (1024 * 1024)); - - int sum = 0; - - for (int i = 0; i < 16; i++) - { - sum += m_perfmon.CPU(GSPerfMon::WorkerDraw0 + i); - } - - s += format(" | %d%% CPU", sum); - } - } -#else + g_gs_device->ResetAPIState(); + if (Host::BeginPresentFrame(false)) + { + GSTexture* current = g_gs_device->GetCurrent(); + if (current) { - // Satisfy PCSX2's request for title info: minimal verbosity due to more external title text + HostDisplay* const display = g_gs_device->GetDisplay(); + const GSVector4 draw_rect(CalculateDrawRect(display->GetWindowWidth(), display->GetWindowHeight(), + current->GetWidth(), current->GetHeight(), display->GetDisplayAlignment(), display->UsesLowerLeftOrigin())); - s = format("%dx%d | %s", GetInternalResolution().x, GetInternalResolution().y, theApp.m_gs_interlace[m_interlace].name.c_str()); - } -#endif + static constexpr ShaderConvert s_shader[5] = {ShaderConvert::COPY, ShaderConvert::SCANLINE, + ShaderConvert::DIAGONAL_FILTER, ShaderConvert::TRIANGULAR_FILTER, + ShaderConvert::COMPLEX_FILTER}; // FIXME - if (m_capture.IsCapturing()) - { - s += " | Recording..."; + g_gs_device->StretchRect(current, nullptr, draw_rect, s_shader[GSConfig.TVShader], GSConfig.LinearPresent); } - // note: do not use TryEnterCriticalSection. It is unnecessary code complication in - // an area that absolutely does not matter (even if it were 100 times slower, it wouldn't - // be noticeable). Besides, these locks are extremely short -- overhead of conditional - // is way more expensive than just waiting for the CriticalSection in 1 of 10,000,000 tries. --air - - std::lock_guard lock(m_pGSsetTitle_Crit); - - strncpy(m_GStitleInfoBuffer, s.c_str(), std::size(m_GStitleInfoBuffer) - 1); - - m_GStitleInfoBuffer[sizeof(m_GStitleInfoBuffer) - 1] = 0; // make sure null terminated even if text overflows - } - - if (m_frameskip) - { - return; + Host::EndPresentFrame(); } - - // present - - // This will scale the OSD to the window's size. - // Will maintiain the font size no matter what size the window is. - GSVector4i window_size(0, 0, m_dev->GetBackbufferWidth(), m_dev->GetBackbufferHeight()); - m_dev->m_osd.m_real_size.x = window_size.v[2]; - m_dev->m_osd.m_real_size.y = window_size.v[3]; - - m_dev->Present(ComputeDrawRectangle(window_size.z, window_size.w), m_shader); + g_gs_device->RestoreAPIState(); // snapshot @@ -495,7 +452,7 @@ void GSRenderer::VSync(int field) delete[] fd.data; } - if (GSTexture* t = m_dev->GetCurrent()) + if (GSTexture* t = g_gs_device->GetCurrent()) { t->Save(m_snapshot + ".png"); } @@ -512,21 +469,21 @@ void GSRenderer::VSync(int field) if (m_capture.IsCapturing()) { - if (GSTexture* current = m_dev->GetCurrent()) + if (GSTexture* current = g_gs_device->GetCurrent()) { GSVector2i size = m_capture.GetSize(); bool res; GSTexture::GSMap m; if (size == current->GetSize()) - res = m_dev->DownloadTexture(current, GSVector4i(0, 0, size.x, size.y), m); + res = g_gs_device->DownloadTexture(current, GSVector4i(0, 0, size.x, size.y), m); else - res = m_dev->DownloadTextureConvert(current, GSVector4(0, 0, 1, 1), size, GSTexture::Format::Color, ShaderConvert::COPY, m, true); + res = g_gs_device->DownloadTextureConvert(current, GSVector4(0, 0, 1, 1), size, GSTexture::Format::Color, ShaderConvert::COPY, m, true); if (res) { - m_capture.DeliverFrame(m.bits, m.pitch, !m_dev->IsRBSwapped()); - m_dev->DownloadTextureComplete(); + m_capture.DeliverFrame(m.bits, m.pitch, !g_gs_device->IsRBSwapped()); + g_gs_device->DownloadTextureComplete(); } } } @@ -569,10 +526,7 @@ bool GSRenderer::MakeSnapshot(const std::string& path) bool GSRenderer::BeginCapture(std::string& filename) { - GSVector4i disp = ComputeDrawRectangle(m_dev->GetBackbufferWidth(), m_dev->GetBackbufferHeight()); - float aspect = (float)disp.width() / std::max(1, disp.height()); - - return m_capture.BeginCapture(GetTvRefreshRate(), GetInternalResolution(), aspect, filename); + return m_capture.BeginCapture(GetTvRefreshRate(), GetInternalResolution(), GetCurrentAspectRatioFloat(), filename); } void GSRenderer::EndCapture() @@ -618,34 +572,24 @@ void GSRenderer::KeyEvent(const HostKeyEvent& e) switch (e.key) { case VK_F5: - m_interlace = (m_interlace + s_interlace_nb + step) % s_interlace_nb; - theApp.SetConfig("interlace", m_interlace); - printf("GS: Set deinterlace mode to %d (%s).\n", m_interlace, theApp.m_gs_interlace.at(m_interlace).name.c_str()); + GSConfig.InterlaceMode = static_cast((static_cast(GSConfig.InterlaceMode) + static_cast(GSInterlaceMode::Count) + step) % static_cast(GSInterlaceMode::Count)); + theApp.SetConfig("interlace", static_cast(GSConfig.InterlaceMode)); + printf("GS: Set deinterlace mode to %d (%s).\n", static_cast(GSConfig.InterlaceMode), theApp.m_gs_interlace.at(static_cast(GSConfig.InterlaceMode)).name.c_str()); return; case VK_DELETE: - m_aa1 = !m_aa1; - theApp.SetConfig("aa1", m_aa1); - printf("GS: (Software) Edge anti-aliasing is now %s.\n", m_aa1 ? "enabled" : "disabled"); + GSConfig.AA1 = !GSConfig.AA1; + theApp.SetConfig("aa1", GSConfig.AA1); + printf("GS: (Software) Edge anti-aliasing is now %s.\n", GSConfig.AA1 ? "enabled" : "disabled"); return; case VK_INSERT: m_mipmap = (m_mipmap + s_mipmap_nb + step) % s_mipmap_nb; theApp.SetConfig("mipmap_hw", m_mipmap); printf("GS: Mipmapping is now %s.\n", theApp.m_gs_hack.at(m_mipmap).name.c_str()); return; - case VK_PRIOR: - m_fxaa = !m_fxaa; - theApp.SetConfig("fxaa", m_fxaa); - printf("GS: FXAA anti-aliasing is now %s.\n", m_fxaa ? "enabled" : "disabled"); - return; - case VK_HOME: - m_shaderfx = !m_shaderfx; - theApp.SetConfig("shaderfx", m_shaderfx); - printf("GS: External post-processing is now %s.\n", m_shaderfx ? "enabled" : "disabled"); - return; case VK_NEXT: // As requested by Prafull, to be removed later char dither_msg[3][16] = {"disabled", "auto", "auto unscaled"}; - m_dithering = (m_dithering + 1) % 3; - printf("GS: Dithering is now %s.\n", dither_msg[m_dithering]); + GSConfig.Dithering = (GSConfig.Dithering + 1) % 3; + printf("GS: Dithering is now %s.\n", dither_msg[GSConfig.Dithering]); return; } } @@ -654,5 +598,47 @@ void GSRenderer::KeyEvent(const HostKeyEvent& e) void GSRenderer::PurgePool() { - m_dev->PurgePool(); + g_gs_device->PurgePool(); +} + +bool GSRenderer::SaveSnapshotToMemory(u32 width, u32 height, std::vector* pixels) +{ + GSTexture* const current = g_gs_device->GetCurrent(); + if (!current) + return false; + + GSVector4 draw_rect(CalculateDrawRect(width, height, current->GetWidth(), current->GetHeight(), + HostDisplay::Alignment::LeftOrTop, false)); + u32 draw_width = static_cast(draw_rect.z - draw_rect.x); + u32 draw_height = static_cast(draw_rect.w - draw_rect.y); + if (draw_width > width) + { + draw_width = width; + draw_rect.left = 0; + draw_rect.right = width; + } + if (draw_height > height) + { + draw_height = height; + draw_rect.top = 0; + draw_rect.bottom = height; + } + + GSTexture::GSMap map; + const bool result = g_gs_device->DownloadTextureConvert( + current, GSVector4(0.0f, 0.0f, 1.0f, 1.0f), + GSVector2i(draw_width, draw_height), GSTexture::Format::Color, + ShaderConvert::COPY, map, true); + if (result) + { + const u32 pad_x = (width - draw_width) / 2; + const u32 pad_y = (height - draw_height) / 2; + pixels->resize(width * height, 0); + StringUtil::StrideMemCpy(pixels->data() + pad_y * width + pad_x, width * sizeof(u32), + map.bits, map.pitch, draw_width * sizeof(u32), draw_height); + + g_gs_device->DownloadTextureComplete(); + } + + return result; } diff --git a/pcsx2/GS/Renderers/Common/GSRenderer.h b/pcsx2/GS/Renderers/Common/GSRenderer.h index 9451e72992272..ba953db97e977 100644 --- a/pcsx2/GS/Renderers/Common/GSRenderer.h +++ b/pcsx2/GS/Renderers/Common/GSRenderer.h @@ -17,6 +17,7 @@ #include "GS/GSState.h" #include "GS/GSCapture.h" +#include struct HostKeyEvent; @@ -24,7 +25,6 @@ class GSRenderer : public GSState { GSCapture m_capture; std::string m_snapshot; - int m_shader; bool Merge(int field); @@ -32,13 +32,6 @@ class GSRenderer : public GSState bool m_control_key; protected: - int m_dithering; - int m_interlace; - int m_vsync; - bool m_aa1; - bool m_shaderfx; - bool m_fxaa; - bool m_shadeboost; bool m_texture_shuffle; GSVector2i m_real_size; @@ -49,9 +42,9 @@ class GSRenderer : public GSState GSRenderer(); virtual ~GSRenderer(); - virtual bool CreateDevice(GSDevice* dev, const WindowInfo& wi); - virtual void ResetDevice(); - virtual void VSync(int field); + virtual void Destroy(); + + virtual void VSync(u32 field); virtual bool MakeSnapshot(const std::string& path); virtual void KeyEvent(const HostKeyEvent& e); virtual bool CanUpscale() { return false; } @@ -59,17 +52,11 @@ class GSRenderer : public GSState virtual GSVector2i GetCustomResolution() { return GSVector2i(0, 0); } virtual GSVector2 GetTextureScaleFactor() { return { 1.0f, 1.0f }; } GSVector2i GetInternalResolution(); - void SetVSync(int vsync); virtual bool BeginCapture(std::string& filename); virtual void EndCapture(); void PurgePool(); - GSVector4i ComputeDrawRectangle(int width, int height) const; - -public: - std::mutex m_pGSsetTitle_Crit; - - char m_GStitleInfoBuffer[128]; + bool SaveSnapshotToMemory(u32 width, u32 height, std::vector* pixels); }; diff --git a/pcsx2/GS/Renderers/Common/GSTexture.h b/pcsx2/GS/Renderers/Common/GSTexture.h index b66899f433888..bcfc2625c7c0d 100644 --- a/pcsx2/GS/Renderers/Common/GSTexture.h +++ b/pcsx2/GS/Renderers/Common/GSTexture.h @@ -33,7 +33,6 @@ class GSTexture DepthStencil, Texture, Offscreen, - Backbuffer, SparseRenderTarget, SparseDepthStencil, }; @@ -41,7 +40,6 @@ class GSTexture enum class Format { Invalid = 0, ///< Used for initialization - Backbuffer, ///< For displaying to the screen Color, ///< Standard (RGBA8) color texture FloatColor, ///< Float-based color texture for colclip emulation (RGBA32F) DepthStencil, ///< Depth stencil texture @@ -70,6 +68,9 @@ class GSTexture return false; } + // Returns the native handle of a texture. + virtual void* GetNativeHandle() const = 0; + virtual bool Update(const GSVector4i& r, const void* data, int pitch, int layer = 0) = 0; virtual bool Map(GSMap& m, const GSVector4i* r = NULL, int layer = 0) = 0; virtual void Unmap() = 0; diff --git a/pcsx2/GS/Renderers/Common/GSVertexTrace.cpp b/pcsx2/GS/Renderers/Common/GSVertexTrace.cpp index 2eb2686055255..5c0dd56d91908 100644 --- a/pcsx2/GS/Renderers/Common/GSVertexTrace.cpp +++ b/pcsx2/GS/Renderers/Common/GSVertexTrace.cpp @@ -24,7 +24,6 @@ CONSTINIT const GSVector4 GSVertexTrace::s_minmax = GSVector4::cxpr(FLT_MAX, -FL GSVertexTrace::GSVertexTrace(const GSState* state) : m_accurate_stq(false), m_state(state), m_primclass(GS_INVALID_CLASS) { - m_force_filter = static_cast(theApp.GetConfigI("filter")); memset(&m_alpha, 0, sizeof(m_alpha)); #define InitUpdate3(P, IIP, TME, FST, COLOR) \ @@ -126,7 +125,7 @@ void GSVertexTrace::Update(const void* vertex, const u32* index, int v_count, in } } - switch (m_force_filter) + switch (GSConfig.TextureFiltering) { case BiFiltering::Nearest: m_filter.opt_linear = 0; diff --git a/pcsx2/GS/Renderers/Common/GSVertexTrace.h b/pcsx2/GS/Renderers/Common/GSVertexTrace.h index 750a23a023070..e026025424f16 100644 --- a/pcsx2/GS/Renderers/Common/GSVertexTrace.h +++ b/pcsx2/GS/Renderers/Common/GSVertexTrace.h @@ -26,8 +26,6 @@ class GSState; class alignas(32) GSVertexTrace : public GSAlignedClass<32> { - BiFiltering m_force_filter; - public: struct Vertex { diff --git a/pcsx2/GS/Renderers/DX11/D3D.cpp b/pcsx2/GS/Renderers/DX11/D3D.cpp index 09bffd82d3504..01301b8a3c8cb 100644 --- a/pcsx2/GS/Renderers/DX11/D3D.cpp +++ b/pcsx2/GS/Renderers/DX11/D3D.cpp @@ -49,30 +49,6 @@ namespace D3D return factory; } - std::vector GetAdapterList(IDXGIFactory2* factory) - { - ASSERT(factory); - - UINT index = 0; - wil::com_ptr_nothrow adapter; - - std::vector adapter_list; - while (factory->EnumAdapters1(index, adapter.put()) != DXGI_ERROR_NOT_FOUND) - { - DXGI_ADAPTER_DESC1 desc; - - std::string name; - if (SUCCEEDED(adapter->GetDesc1(&desc))) - name = convert_utf16_to_utf8(desc.Description); - - adapter_list.push_back(std::move(name)); - - index++; - } - - return adapter_list; - } - wil::com_ptr_nothrow GetAdapterFromIndex(IDXGIFactory2* factory, int index) { ASSERT(factory); diff --git a/pcsx2/GS/Renderers/DX11/D3D.h b/pcsx2/GS/Renderers/DX11/D3D.h index f9a2b508f7561..dee2e177ff1af 100644 --- a/pcsx2/GS/Renderers/DX11/D3D.h +++ b/pcsx2/GS/Renderers/DX11/D3D.h @@ -25,9 +25,6 @@ namespace D3D // create a dxgi factory wil::com_ptr_nothrow CreateFactory(bool debug); - // get a list of adapters - std::vector GetAdapterList(IDXGIFactory2* factory); - // get an adapter based on position // assuming no one removes/moves it, it should always have the same id // however in the event that the adapter is not found due to the above, use the default diff --git a/pcsx2/GS/Renderers/DX11/GSDevice11.cpp b/pcsx2/GS/Renderers/DX11/GSDevice11.cpp index 74d1fb69a3d85..e91f3b17d7104 100644 --- a/pcsx2/GS/Renderers/DX11/GSDevice11.cpp +++ b/pcsx2/GS/Renderers/DX11/GSDevice11.cpp @@ -20,6 +20,7 @@ #include "GS/GSExtra.h" #include "GS/GSUtil.h" #include "Host.h" +#include "HostDisplay.h" #include #include #include @@ -79,12 +80,10 @@ bool GSDevice11::SetFeatureLevel(D3D_FEATURE_LEVEL level, bool compat_mode) return true; } -bool GSDevice11::Create(const WindowInfo& wi) +bool GSDevice11::Create(HostDisplay* display) { - if (!__super::Create(wi)) - { + if (!__super::Create(display)) return false; - } D3D11_BUFFER_DESC bd; D3D11_SAMPLER_DESC sd; @@ -92,131 +91,43 @@ bool GSDevice11::Create(const WindowInfo& wi) D3D11_RASTERIZER_DESC rd; D3D11_BLEND_DESC bsd; - const bool enable_debugging = theApp.GetConfigB("debug_d3d"); - - auto factory = D3D::CreateFactory(enable_debugging); - if (!factory) - return false; + D3D_FEATURE_LEVEL level; - // select adapter - auto adapter = D3D::GetAdapterFromIndex( - factory.get(), theApp.GetConfigI("adapter_index") - ); - - DXGI_ADAPTER_DESC1 adapter_desc = {}; - if (SUCCEEDED(adapter->GetDesc1(&adapter_desc))) + if (display->GetRenderAPI() != HostDisplay::RenderAPI::D3D11) { - std::string adapter_name = convert_utf16_to_utf8( - adapter_desc.Description - ); - - fprintf(stderr, "Selected DXGI Adapter\n" - "\tName: %s\n" - "\tVendor: %x\n", adapter_name.c_str(), adapter_desc.VendorId); + fprintf(stderr, "Render API is incompatible with D3D11\n"); + return false; } - // device creation - { - u32 flags = D3D11_CREATE_DEVICE_SINGLETHREADED; - - if(enable_debugging) - flags |= D3D11_CREATE_DEVICE_DEBUG; - - constexpr std::array supported_levels = { - D3D_FEATURE_LEVEL_11_0, - D3D_FEATURE_LEVEL_10_1, - D3D_FEATURE_LEVEL_10_0, - }; + m_dev = static_cast(display->GetRenderDevice()); + m_ctx = static_cast(display->GetRenderContext()); + level = m_dev->GetFeatureLevel(); - D3D_FEATURE_LEVEL feature_level; - HRESULT result = D3D11CreateDevice( - adapter.get(), D3D_DRIVER_TYPE_UNKNOWN, nullptr, flags, - supported_levels.data(), supported_levels.size(), - D3D11_SDK_VERSION, m_dev.put(), &feature_level, m_ctx.put() - ); - - // if a debug device is requested but not supported, fallback to non-debug device - if (FAILED(result) && enable_debugging) - { - fprintf(stderr, "D3D: failed to create debug device, trying without debugging\n"); - // clear the debug flag - flags = D3D11_CREATE_DEVICE_SINGLETHREADED; - - result = D3D11CreateDevice( - adapter.get(), D3D_DRIVER_TYPE_UNKNOWN, nullptr, flags, - supported_levels.data(), supported_levels.size(), - D3D11_SDK_VERSION, m_dev.put(), &feature_level, m_ctx.put() - ); - } - - if (FAILED(result)) - { - fprintf(stderr, "D3D: unable to create D3D11 device (reason %x)\n" - "ensure that your gpu supports our minimum requirements:\n" - "https://github.com/PCSX2/pcsx2#system-requirements\n", result); - return false; - } - - if (enable_debugging) - { - if (auto info_queue = m_dev.try_query()) - { - const int break_on = theApp.GetConfigI("dx_break_on_severity"); - - info_queue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_CORRUPTION, break_on & (1 << 0)); - info_queue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_ERROR, break_on & (1 << 1)); - info_queue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_WARNING, break_on & (1 << 2)); - info_queue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_INFO, break_on & (1 << 3)); - } - fprintf(stderr, "D3D: debugging enabled\n"); - } - - if (!SetFeatureLevel(feature_level, true)) + bool nvidia_vendor = false; + { + if (auto dxgi_device = m_dev.try_query()) { - fprintf(stderr, "D3D: adapter doesn't have a sufficient feature level\n"); - return false; + wil::com_ptr_nothrow dxgi_adapter; + DXGI_ADAPTER_DESC adapter_desc; + if (SUCCEEDED(dxgi_device->GetAdapter(dxgi_adapter.put())) && SUCCEEDED(dxgi_adapter->GetDesc(&adapter_desc))) + nvidia_vendor = (adapter_desc.VendorId == 0x10DE); } - - // Set maximum texture size limit based on supported feature level. - if (feature_level >= D3D_FEATURE_LEVEL_11_0) - m_d3d_texsize = D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION; - else - m_d3d_texsize = D3D10_REQ_TEXTURE2D_U_OR_V_DIMENSION; } - // swapchain creation - { - DXGI_SWAP_CHAIN_DESC1 swapchain_description = {}; - - // let the runtime get window size - swapchain_description.Width = 0; - swapchain_description.Height = 0; - - swapchain_description.BufferCount = 2; - swapchain_description.Format = DXGI_FORMAT_R8G8B8A8_UNORM; - swapchain_description.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; - swapchain_description.SampleDesc.Count = 1; - swapchain_description.SampleDesc.Quality = 0; - - // TODO: update swap effect - swapchain_description.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; - - const HRESULT result = factory->CreateSwapChainForHwnd( - m_dev.get(), reinterpret_cast(wi.window_handle), - &swapchain_description, nullptr, nullptr, m_swapchain.put()); + if (!SetFeatureLevel(m_dev->GetFeatureLevel(), true)) + return false; - if (FAILED(result)) - { - fprintf(stderr, "D3D: Failed to create swapchain (reason: %x)\n", result); - return false; - } - } + // Set maximum texture size limit based on supported feature level. + if (level >= D3D_FEATURE_LEVEL_11_0) + m_d3d_texsize = D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION; + else + m_d3d_texsize = D3D10_REQ_TEXTURE2D_U_OR_V_DIMENSION; { // HACK: check nVIDIA // Note: It can cause issues on several games such as SOTC, Fatal Frame, plus it adds border offset. - bool disable_safe_features = theApp.GetConfigB("UserHacks") && theApp.GetConfigB("UserHacks_Disable_Safe_Features"); - m_hack_topleft_offset = (m_upscale_multiplier != 1 && D3D::IsNvidia(adapter.get()) && !disable_safe_features) ? -0.01f : 0.0f; + const bool disable_safe_features = theApp.GetConfigB("UserHacks") && theApp.GetConfigB("UserHacks_Disable_Safe_Features"); + m_hack_topleft_offset = (m_upscale_multiplier != 1 && nvidia_vendor && !disable_safe_features) ? -0.01f : 0.0f; } std::optional shader = Host::ReadResourceFileToString("shaders/dx11/tfx.fx"); @@ -362,10 +273,8 @@ bool GSDevice11::Create(const WindowInfo& wi) rd.MultisampleEnable = true; rd.AntialiasedLineEnable = false; - wil::com_ptr_nothrow rs; - m_dev->CreateRasterizerState(&rd, rs.put()); - - m_ctx->RSSetState(rs.get()); + m_dev->CreateRasterizerState(&rd, m_rs.put()); + m_ctx->RSSetState(m_rs.get()); // @@ -388,10 +297,6 @@ bool GSDevice11::Create(const WindowInfo& wi) // - Reset(wi.surface_width, wi.surface_height); - - // - CreateTextureFX(); // @@ -419,48 +324,48 @@ bool GSDevice11::Create(const WindowInfo& wi) m_dev->CreateBlendState(&blend, m_date.bs.put()); - const GSVector2i tex_font = m_osd.get_texture_font_size(); - - m_font = std::unique_ptr( - CreateSurface(GSTexture::Type::Texture, tex_font.x, tex_font.y, GSTexture::Format::UNorm8)); - return true; } -bool GSDevice11::Reset(int w, int h) +void GSDevice11::ResetAPIState() { - if (!__super::Reset(w, h)) - return false; - - if (m_swapchain) - { - DXGI_SWAP_CHAIN_DESC scd; - - memset(&scd, 0, sizeof(scd)); - - m_swapchain->GetDesc(&scd); - m_swapchain->ResizeBuffers(scd.BufferCount, w, h, scd.BufferDesc.Format, 0); - - wil::com_ptr_nothrow backbuffer; - if (FAILED(m_swapchain->GetBuffer(0, IID_PPV_ARGS(backbuffer.put())))) - { - return false; - } - - m_backbuffer = new GSTexture11(std::move(backbuffer), GSTexture::Format::Backbuffer); - } - - return true; + // Clear out the GS, since the imgui draw doesn't get rid of it. + m_ctx->GSSetShader(nullptr, nullptr, 0); } -void GSDevice11::SetVSync(int vsync) +void GSDevice11::RestoreAPIState() { - m_vsync = vsync ? 1 : 0; -} + const UINT vb_stride = static_cast(m_state.vb_stride); + const UINT vb_offset = 0; + m_ctx->IASetVertexBuffers(0, 1, &m_state.vb, &vb_stride, &vb_offset); + m_ctx->IASetIndexBuffer(m_state.ib, DXGI_FORMAT_R32_UINT, 0); + m_ctx->IASetInputLayout(m_state.layout); + m_ctx->IASetPrimitiveTopology(m_state.topology); + m_ctx->VSSetShader(m_state.vs, nullptr, 0); + m_ctx->VSSetConstantBuffers(0, 1, &m_state.vs_cb); + m_ctx->GSSetShader(m_state.gs, nullptr, 0); + m_ctx->GSSetConstantBuffers(0, 1, &m_state.gs_cb); + m_ctx->PSSetShader(m_state.ps, nullptr, 0); + m_ctx->PSSetConstantBuffers(0, 1, &m_state.ps_cb); + + const CD3D11_VIEWPORT vp(m_hack_topleft_offset, m_hack_topleft_offset, + static_cast(m_state.viewport.x), static_cast(m_state.viewport.y), + 0.0f, 1.0f); + m_ctx->RSSetViewports(1, &vp); + m_ctx->RSSetScissorRects(1, m_state.scissor); + m_ctx->RSSetState(m_rs.get()); + + m_ctx->OMSetDepthStencilState(m_state.dss, m_state.sref); + + const float blend_factors[4] = { m_state.bf, m_state.bf, m_state.bf, m_state.bf }; + m_ctx->OMSetBlendState(m_state.bs, blend_factors, 0xFFFFFFFFu); -void GSDevice11::Flip() -{ - m_swapchain->Present(m_vsync, 0); + PSUpdateShaderState(); + + if (m_state.rt_view) + m_ctx->OMSetRenderTargets(1, &m_state.rt_view, m_state.dsv); + else + m_ctx->OMSetRenderTargets(0, nullptr, m_state.dsv); } void GSDevice11::BeforeDraw() @@ -583,7 +488,6 @@ GSTexture* GSDevice11::CreateSurface(GSTexture::Type type, int w, int h, GSTextu case GSTexture::Format::UInt32: dxformat = DXGI_FORMAT_R32_UINT; break; case GSTexture::Format::Int32: dxformat = DXGI_FORMAT_R32_SINT; break; case GSTexture::Format::Invalid: - case GSTexture::Format::Backbuffer: ASSERT(0); dxformat = DXGI_FORMAT_UNKNOWN; } @@ -727,11 +631,7 @@ void GSDevice11::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* void GSDevice11::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ID3D11PixelShader* ps, ID3D11Buffer* ps_cb, ID3D11BlendState* bs, bool linear) { - if (!sTex || !dTex) - { - ASSERT(0); - return; - } + ASSERT(sTex); const bool draw_in_depth = ps == m_convert.ps[static_cast(ShaderConvert::RGBA8_TO_FLOAT32)] || ps == m_convert.ps[static_cast(ShaderConvert::RGBA8_TO_FLOAT24)] @@ -740,11 +640,22 @@ void GSDevice11::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* BeginScene(); - const GSVector2i ds = dTex->GetSize(); - - // om + GSVector2i ds; + if (dTex) + { + ds = dTex->GetSize(); + if (draw_in_depth) + OMSetRenderTargets(nullptr, dTex); + else + OMSetRenderTargets(dTex, nullptr); + } + else + { + ds = GSVector2i(m_display->GetWindowWidth(), m_display->GetWindowHeight()); + } + // om if (draw_in_depth) OMSetDepthStencilState(m_convert.dss_write.get(), 0); else @@ -752,10 +663,7 @@ void GSDevice11::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* OMSetBlendState(bs, 0); - if (draw_in_depth) - OMSetRenderTargets(nullptr, dTex); - else - OMSetRenderTargets(dTex, nullptr); + // ia @@ -805,48 +713,6 @@ void GSDevice11::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* PSSetShaderResources(nullptr, nullptr); } -void GSDevice11::RenderOsd(GSTexture* dt) -{ - BeginScene(); - - // om - OMSetDepthStencilState(m_convert.dss.get(), 0); - OMSetBlendState(m_merge.bs.get(), 0); - OMSetRenderTargets(dt, nullptr); - - if (m_osd.m_texture_dirty) - { - m_osd.upload_texture_atlas(m_font.get()); - } - - // ps - PSSetShaderResource(0, m_font.get()); - PSSetSamplerState(m_convert.pt.get(), nullptr); - PSSetShader(m_convert.ps[static_cast(ShaderConvert::OSD)].get(), nullptr); - - // ia - IASetInputLayout(m_convert.il.get()); - IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); - - // Note scaling could also be done in shader (require gl3/dx10) - size_t count = m_osd.Size(); - void* dst = nullptr; - - IAMapVertexBuffer(&dst, sizeof(GSVertexPT1), count); - count = m_osd.GeneratePrimitives((GSVertexPT1*)dst, count); - IAUnmapVertexBuffer(); - - // vs - VSSetShader(m_convert.vs.get(), nullptr); - - // gs - GSSetShader(nullptr, nullptr); - - DrawPrimitive(); - - EndScene(); -} - void GSDevice11::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, GSVector4* dRect, const GSRegPMODE& PMODE, const GSRegEXTBUF& EXTBUF, const GSVector4& c) { const bool slbg = PMODE.SLBG; diff --git a/pcsx2/GS/Renderers/DX11/GSDevice11.h b/pcsx2/GS/Renderers/DX11/GSDevice11.h index 61b4fb8ed2e51..a1837b741b8ea 100644 --- a/pcsx2/GS/Renderers/DX11/GSDevice11.h +++ b/pcsx2/GS/Renderers/DX11/GSDevice11.h @@ -119,7 +119,6 @@ class GSDevice11 final : public GSDevice void DoFXAA(GSTexture* sTex, GSTexture* dTex) final; void DoShadeBoost(GSTexture* sTex, GSTexture* dTex) final; void DoExternalFX(GSTexture* sTex, GSTexture* dTex) final; - void RenderOsd(GSTexture* dt); void BeforeDraw(); void AfterDraw(); @@ -217,11 +216,11 @@ class GSDevice11 final : public GSDevice wil::com_ptr_nothrow m_palette_ss; std::unordered_map> m_om_dss; std::unordered_map> m_om_bs; + wil::com_ptr_nothrow m_rs; GSHWDrawConfig::VSConstantBuffer m_vs_cb_cache; GSHWDrawConfig::PSConstantBuffer m_ps_cb_cache; - std::unique_ptr m_font; std::unique_ptr m_download_tex; std::string m_tfx_source; @@ -240,10 +239,10 @@ class GSDevice11 final : public GSDevice bool SetFeatureLevel(D3D_FEATURE_LEVEL level, bool compat_mode); void GetFeatureLevel(D3D_FEATURE_LEVEL& level) const { level = m_shader.level; } - bool Create(const WindowInfo& wi); - bool Reset(int w, int h); - void Flip(); - void SetVSync(int vsync) final; + bool Create(HostDisplay* display); + + void ResetAPIState() override; + void RestoreAPIState() override; void DrawPrimitive(); void DrawIndexedPrimitive(); diff --git a/pcsx2/GS/Renderers/DX11/GSTexture11.cpp b/pcsx2/GS/Renderers/DX11/GSTexture11.cpp index 9c6a1797d8185..c057c5db277d5 100644 --- a/pcsx2/GS/Renderers/DX11/GSTexture11.cpp +++ b/pcsx2/GS/Renderers/DX11/GSTexture11.cpp @@ -44,6 +44,11 @@ GSTexture11::GSTexture11(wil::com_ptr_nothrow texture, GSTextur m_max_layer = m_desc.MipLevels; } +void* GSTexture11::GetNativeHandle() const +{ + return static_cast(*const_cast(this)); +} + bool GSTexture11::Update(const GSVector4i& r, const void* data, int pitch, int layer) { if (layer >= m_max_layer) diff --git a/pcsx2/GS/Renderers/DX11/GSTexture11.h b/pcsx2/GS/Renderers/DX11/GSTexture11.h index f3afe2f0b0c32..af73bd2e37fdd 100644 --- a/pcsx2/GS/Renderers/DX11/GSTexture11.h +++ b/pcsx2/GS/Renderers/DX11/GSTexture11.h @@ -36,6 +36,8 @@ class GSTexture11 : public GSTexture public: explicit GSTexture11(wil::com_ptr_nothrow texture, GSTexture::Format format); + void* GetNativeHandle() const override; + bool Update(const GSVector4i& r, const void* data, int pitch, int layer = 0); bool Map(GSMap& m, const GSVector4i* r = NULL, int layer = 0); void Unmap(); diff --git a/pcsx2/GS/Renderers/DX11/GSTextureFX11.cpp b/pcsx2/GS/Renderers/DX11/GSTextureFX11.cpp index 76c277b50f3f8..66b9e7088993e 100644 --- a/pcsx2/GS/Renderers/DX11/GSTextureFX11.cpp +++ b/pcsx2/GS/Renderers/DX11/GSTextureFX11.cpp @@ -225,7 +225,7 @@ void GSDevice11::SetupPS(PSSelector sel, const GSHWDrawConfig::PSConstantBuffer* memset(&sd, 0, sizeof(sd)); - const int anisotropy = theApp.GetConfigI("MaxAnisotropy"); + const int anisotropy = GSConfig.MaxAnisotropy; if (anisotropy && ssel.aniso) sd.Filter = D3D11_FILTER_ANISOTROPIC; else if (ssel.biln) diff --git a/pcsx2/GS/Renderers/HW/GSHwHack.cpp b/pcsx2/GS/Renderers/HW/GSHwHack.cpp index 2fec5c3730243..3041202814009 100644 --- a/pcsx2/GS/Renderers/HW/GSHwHack.cpp +++ b/pcsx2/GS/Renderers/HW/GSHwHack.cpp @@ -1121,7 +1121,7 @@ bool GSState::IsBadFrame() return false; } - if (m_skip == 0 && (m_userhacks_skipdraw > 0)) + if (m_skip == 0 && (GSConfig.SkipDraw > 0)) { if (fi.TME) { @@ -1129,8 +1129,8 @@ bool GSState::IsBadFrame() // General, often problematic post processing if (GSLocalMemory::m_psm[fi.TPSM].depth || GSUtil::HasSharedBits(fi.FBP, fi.FPSM, fi.TBP0, fi.TPSM)) { - m_skip_offset = m_userhacks_skipdraw_offset; - m_skip = std::max(m_userhacks_skipdraw, m_skip_offset); + m_skip_offset = GSConfig.SkipDrawOffset; + m_skip = std::max(GSConfig.SkipDraw, m_skip_offset); } } } diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index 29251ae61c9aa..2787001904aec 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -18,11 +18,11 @@ #include "GS/GSGL.h" GSRendererHW::GSRendererHW() - : m_width(default_rt_size.x) + : GSRenderer() + , m_width(default_rt_size.x) , m_height(default_rt_size.y) , m_custom_width(1024) , m_custom_height(1024) - , m_reset(false) , m_userhacks_ts_half_bottom(-1) , m_tc(new GSTextureCache(this)) , m_src(nullptr) @@ -30,12 +30,12 @@ GSRendererHW::GSRendererHW() , m_userhacks_tcoffset_x(0) , m_userhacks_tcoffset_y(0) , m_channel_shuffle(false) + , m_reset(false) , m_lod(GSVector2i(0, 0)) { m_mipmap = theApp.GetConfigI("mipmap_hw"); m_upscale_multiplier = std::max(0, theApp.GetConfigI("upscale_multiplier")); m_conservative_framebuffer = theApp.GetConfigB("conservative_framebuffer"); - m_accurate_date = theApp.GetConfigB("accurate_date"); if (theApp.GetConfigB("UserHacks")) { @@ -45,7 +45,6 @@ GSRendererHW::GSRendererHW() m_userHacks_merge_sprite = theApp.GetConfigB("UserHacks_merge_pp_sprite"); m_userhacks_ts_half_bottom = theApp.GetConfigI("UserHacks_Half_Bottom_Override"); m_userhacks_round_sprite_offset = theApp.GetConfigI("UserHacks_round_sprite_offset"); - m_userHacks_HPO = theApp.GetConfigI("UserHacks_HalfPixelOffset"); m_userhacks_tcoffset_x = theApp.GetConfigI("UserHacks_TCOffsetX") / -1000.0f; m_userhacks_tcoffset_y = theApp.GetConfigI("UserHacks_TCOffsetY") / -1000.0f; m_userhacks_tcoffset = m_userhacks_tcoffset_x < 0.0f || m_userhacks_tcoffset_y < 0.0f; @@ -58,7 +57,6 @@ GSRendererHW::GSRendererHW() m_userHacks_merge_sprite = false; m_userhacks_ts_half_bottom = -1; m_userhacks_round_sprite_offset = 0; - m_userHacks_HPO = 0; } if (!m_upscale_multiplier) // Custom Resolution @@ -278,7 +276,7 @@ void GSRendererHW::Reset() GSRenderer::Reset(); } -void GSRendererHW::VSync(int field) +void GSRendererHW::VSync(u32 field) { //Check if the frame buffer width or display width has changed SetScaling(); @@ -295,19 +293,12 @@ void GSRendererHW::VSync(int field) m_tc->IncAge(); m_tc->PrintMemoryUsage(); - m_dev->PrintMemoryUsage(); + g_gs_device->PrintMemoryUsage(); m_skip = 0; m_skip_offset = 0; } -void GSRendererHW::ResetDevice() -{ - m_tc->RemoveAll(); - - GSRenderer::ResetDevice(); -} - GSTexture* GSRendererHW::GetOutput(int i, int& y_offset) { const GSRegDISPFB& DISPFB = m_regs->DISP[i].DISPFB; @@ -393,7 +384,7 @@ void GSRendererHW::Lines2Sprites() alignas(16) static constexpr std::array tri_normal_indices = {{0, 1, 2, 1, 2, 3}}; alignas(16) static constexpr std::array tri_swapped_indices = {{0, 1, 2, 1, 2, 3}}; - const bool index_swap = !m_dev->Features().provoking_vertex_last; + const bool index_swap = !g_gs_device->Features().provoking_vertex_last; const int* tri_indices = index_swap ? tri_swapped_indices.data() : tri_normal_indices.data(); const GSVector4i indices_low(GSVector4i::load(tri_indices)); const GSVector4i indices_high(GSVector4i::loadl(tri_indices + 4)); @@ -641,7 +632,7 @@ void GSRendererHW::ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba) GSVector4 GSRendererHW::RealignTargetTextureCoordinate(const GSTextureCache::Source* tex) { - if (m_userHacks_HPO <= 1 || GetUpscaleMultiplier() == 1) + if (GSConfig.UserHacks_HalfPixelOffset <= 1 || GetUpscaleMultiplier() == 1) return GSVector4(0.0f); const GSVertex* v = &m_vertex.buff[0]; @@ -656,7 +647,7 @@ GSVector4 GSRendererHW::RealignTargetTextureCoordinate(const GSTextureCache::Sou if (PRIM->FST) { - if (m_userHacks_HPO == 3) + if (GSConfig.UserHacks_HalfPixelOffset == 3) { if (!linear && t_position == 8) { @@ -1202,7 +1193,7 @@ void GSRendererHW::RoundSpriteOffset() void GSRendererHW::Draw() { - if (m_dev->IsLost() || IsBadFrame()) + if (IsBadFrame()) { GL_INS("Warning skipping a draw call (%d)", s_n); return; @@ -1508,11 +1499,11 @@ void GSRendererHW::Draw() { const bool is_rt = t == rt; t->m_texture = is_rt ? - m_dev->CreateSparseRenderTarget(new_w, new_h, tex->GetFormat()) : - m_dev->CreateSparseDepthStencil(new_w, new_h, tex->GetFormat()); + g_gs_device->CreateSparseRenderTarget(new_w, new_h, tex->GetFormat()) : + g_gs_device->CreateSparseDepthStencil(new_w, new_h, tex->GetFormat()); const GSVector4i r{ 0, 0, w, h }; - m_dev->CopyRect(tex, t->m_texture, r); - m_dev->Recycle(tex); + g_gs_device->CopyRect(tex, t->m_texture, r); + g_gs_device->Recycle(tex); t->m_texture->SetScale(up_s); (is_rt ? rt_tex : ds_tex) = t->m_texture; } @@ -1832,11 +1823,11 @@ void GSRendererHW::OI_DoubleHalfClear(GSTexture* rt, GSTexture* ds) { // Only pure clear are supported for depth ASSERT(color == 0); - m_dev->ClearDepth(t); + g_gs_device->ClearDepth(t); } else { - m_dev->ClearRenderTarget(t, color); + g_gs_device->ClearRenderTarget(t, color); } } } @@ -1949,15 +1940,15 @@ bool GSRendererHW::OI_BlitFMV(GSTextureCache::Target* _rt, GSTextureCache::Sourc // Do the blit. With a Copy mess to avoid issue with limited API (dx) // m_dev->StretchRect(tex->m_texture, sRect, tex->m_texture, dRect); const GSVector4i r_full(0, 0, tw, th); - if (GSTexture* rt = m_dev->CreateRenderTarget(tw, th, GSTexture::Format::Color)) + if (GSTexture* rt = g_gs_device->CreateRenderTarget(tw, th, GSTexture::Format::Color)) { - m_dev->CopyRect(tex->m_texture, rt, r_full); + g_gs_device->CopyRect(tex->m_texture, rt, r_full); - m_dev->StretchRect(tex->m_texture, sRect, rt, dRect); + g_gs_device->StretchRect(tex->m_texture, sRect, rt, dRect); - m_dev->CopyRect(rt, tex->m_texture, r_full); + g_gs_device->CopyRect(rt, tex->m_texture, r_full); - m_dev->Recycle(rt); + g_gs_device->Recycle(rt); } // Copy back the texture into the GS mem. I don't know why but it will be @@ -2077,9 +2068,9 @@ bool GSRendererHW::OI_FFXII(GSTexture* rt, GSTexture* ds, GSTextureCache::Source // normally, this step would copy the video onto screen with 512 texture mapped horizontal lines, // but we use the stored video data to create a new texture, and replace the lines with two triangles - m_dev->Recycle(t->m_texture); + g_gs_device->Recycle(t->m_texture); - t->m_texture = m_dev->CreateTexture(512, 512, GSTexture::Format::Color); + t->m_texture = g_gs_device->CreateTexture(512, 512, GSTexture::Format::Color); t->m_texture->Update(GSVector4i(0, 0, 448, lines), video, 448 * 4); @@ -2120,7 +2111,7 @@ bool GSRendererHW::OI_FFX(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* GL_INS("OI_FFX ZB clear"); if (ds) ds->Commit(); // Don't bother to save few MB for a single game - m_dev->ClearDepth(ds); + g_gs_device->ClearDepth(ds); } return true; @@ -2172,7 +2163,7 @@ bool GSRendererHW::OI_RozenMaidenGebetGarden(GSTexture* rt, GSTexture* ds, GSTex { GL_INS("OI_RozenMaidenGebetGarden FB clear"); tmp_rt->m_texture->Commit(); // Don't bother to save few MB for a single game - m_dev->ClearRenderTarget(tmp_rt->m_texture, 0); + g_gs_device->ClearRenderTarget(tmp_rt->m_texture, 0); } return false; @@ -2191,7 +2182,7 @@ bool GSRendererHW::OI_RozenMaidenGebetGarden(GSTexture* rt, GSTexture* ds, GSTex { GL_INS("OI_RozenMaidenGebetGarden ZB clear"); tmp_ds->m_texture->Commit(); // Don't bother to save few MB for a single game - m_dev->ClearDepth(tmp_ds->m_texture); + g_gs_device->ClearDepth(tmp_ds->m_texture); } return false; @@ -2232,7 +2223,7 @@ bool GSRendererHW::OI_SonicUnleashed(GSTexture* rt, GSTexture* ds, GSTextureCach const GSVector4 sRect(0, 0, 1, 1); const GSVector4 dRect(0, 0, size.x, size.y); - m_dev->StretchRect(src->m_texture, sRect, rt, dRect, true, true, true, false); + g_gs_device->StretchRect(src->m_texture, sRect, rt, dRect, true, true, true, false); return false; } @@ -2308,7 +2299,7 @@ bool GSRendererHW::OI_SuperManReturns(GSTexture* rt, GSTexture* ds, GSTextureCac // Do a direct write if (rt) rt->Commit(); // Don't bother to save few MB for a single game - m_dev->ClearRenderTarget(rt, GSVector4(m_vt.m_min.c)); + g_gs_device->ClearRenderTarget(rt, GSVector4(m_vt.m_min.c)); m_tc->InvalidateVideoMemType(GSTextureCache::DepthStencil, ctx->FRAME.Block()); GL_INS("OI_SuperManReturns"); @@ -2346,7 +2337,7 @@ bool GSRendererHW::OI_ArTonelico2(GSTexture* rt, GSTexture* ds, GSTextureCache:: GL_INS("OI_ArTonelico2"); if (ds) ds->Commit(); // Don't bother to save few MB for a single game - m_dev->ClearDepth(ds); + g_gs_device->ClearDepth(ds); } return true; diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.h b/pcsx2/GS/Renderers/HW/GSRendererHW.h index 0b87977d1f671..688a8771a2b46 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.h +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.h @@ -26,7 +26,6 @@ class GSRendererHW : public GSRenderer int m_height; int m_custom_width; int m_custom_height; - bool m_reset; int m_upscale_multiplier; int m_userhacks_ts_half_bottom; @@ -151,29 +150,26 @@ class GSRendererHW : public GSRenderer virtual void DrawPrims(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* tex) = 0; int m_userhacks_round_sprite_offset; - int m_userHacks_HPO; bool m_userHacks_enabled_unscale_ptln; bool m_userhacks_tcoffset; float m_userhacks_tcoffset_x; float m_userhacks_tcoffset_y; - bool m_accurate_date; - AccBlendLevel m_sw_blending; - bool m_channel_shuffle; + bool m_reset; GSVector2i m_lod; // Min & Max level of detail void CustomResolutionScaling(); public: GSRendererHW(); - virtual ~GSRendererHW(); + virtual ~GSRendererHW() override; - void SetGameCRC(u32 crc, int options); - bool CanUpscale(); - int GetUpscaleMultiplier(); - GSVector2i GetCustomResolution(); + void SetGameCRC(u32 crc, int options) override; + bool CanUpscale() override; + int GetUpscaleMultiplier() override; + GSVector2i GetCustomResolution() override; void SetScaling(); void Lines2Sprites(); void EmulateAtst(GSVector4& FogColor_AREF, u8& atst, const bool pass_2); @@ -181,16 +177,16 @@ class GSRendererHW : public GSRenderer GSVector4 RealignTargetTextureCoordinate(const GSTextureCache::Source* tex); GSVector4i ComputeBoundingBox(const GSVector2& rtscale, const GSVector2i& rtsize); void MergeSprite(GSTextureCache::Source* tex); - GSVector2 GetTextureScaleFactor(); - - void Reset(); - void VSync(int field); - void ResetDevice(); - GSTexture* GetOutput(int i, int& y_offset); - GSTexture* GetFeedbackOutput(); - void InvalidateVideoMem(const GIFRegBITBLTBUF& BITBLTBUF, const GSVector4i& r); - void InvalidateLocalMem(const GIFRegBITBLTBUF& BITBLTBUF, const GSVector4i& r, bool clut = false); - void Draw(); + GSVector2 GetTextureScaleFactor() override; + + void Reset() override; + void VSync(u32 field) override; + + GSTexture* GetOutput(int i, int& y_offset) override; + GSTexture* GetFeedbackOutput() override; + void InvalidateVideoMem(const GIFRegBITBLTBUF& BITBLTBUF, const GSVector4i& r) override; + void InvalidateLocalMem(const GIFRegBITBLTBUF& BITBLTBUF, const GSVector4i& r, bool clut = false) override; + void Draw() override; // Called by the texture cache to know if current texture is useful virtual bool IsDummyTexture() const { return false; } diff --git a/pcsx2/GS/Renderers/HW/GSRendererNew.cpp b/pcsx2/GS/Renderers/HW/GSRendererNew.cpp index 6abd590655d76..ab6b4a9328872 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererNew.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererNew.cpp @@ -18,12 +18,8 @@ #include "GS/GSGL.h" GSRendererNew::GSRendererNew() + : GSRendererHW() { - if (theApp.GetConfigB("UserHacks")) - UserHacks_tri_filter = static_cast(theApp.GetConfigI("UserHacks_TriFilter")); - else - UserHacks_tri_filter = TriFiltering::None; - // Hope nothing requires too many draw calls. m_drawlist.reserve(2048); @@ -33,19 +29,6 @@ GSRendererNew::GSRendererNew() ResetStates(); } -bool GSRendererNew::CreateDevice(GSDevice* dev, const WindowInfo& wi) -{ - if (!GSRendererHW::CreateDevice(dev, wi)) - return false; - - if (dev->Features().texture_barrier) - m_sw_blending = static_cast(theApp.GetConfigI("accurate_blending_unit")); - else - m_sw_blending = static_cast(theApp.GetConfigI("accurate_blending_unit_d3d11")); - - return true; -} - void GSRendererNew::SetupIA(const float& sx, const float& sy) { GL_PUSH("IA"); @@ -56,7 +39,7 @@ void GSRendererNew::SetupIA(const float& sx, const float& sy) m_vertex.buff[i].UV &= 0x3FEF3FEF; } const bool unscale_pt_ln = m_userHacks_enabled_unscale_ptln && (GetUpscaleMultiplier() != 1); - const GSDevice::FeatureSupport features = m_dev->Features(); + const GSDevice::FeatureSupport features = g_gs_device->Features(); switch (m_vt.m_primclass) { @@ -117,7 +100,7 @@ void GSRendererNew::SetupIA(const float& sx, const float& sy) // the extra validation cost of the extra stage. // // Note: keep Geometry Shader in the replayer to ease debug. - if (m_dev->Features().geometry_shader && !m_vt.m_accurate_stq && (m_vertex.next > 32 || GLLoader::in_replayer)) // <=> 16 sprites (based on Shadow Hearts) + if (g_gs_device->Features().geometry_shader && !m_vt.m_accurate_stq && (m_vertex.next > 32 || GLLoader::in_replayer)) // <=> 16 sprites (based on Shadow Hearts) { m_conf.gs.expand = true; @@ -200,9 +183,9 @@ void GSRendererNew::EmulateTextureShuffleAndFbmask() // m_texture_shuffle = false; bool enable_fbmask_emulation = false; - if (m_dev->Features().texture_barrier) + if (g_gs_device->Features().texture_barrier) { - enable_fbmask_emulation = m_sw_blending != AccBlendLevel::None; + enable_fbmask_emulation = GSConfig.AccurateBlendingUnit != AccBlendLevel::Minimum; } else { @@ -211,7 +194,7 @@ void GSRendererNew::EmulateTextureShuffleAndFbmask() // 1. D3D sucks. // 2. FB copy is slow, especially on triangle primitives which is unplayable with some games. // 3. SW blending isn't implemented yet. - switch (m_sw_blending) + switch (GSConfig.AccurateBlendingUnit) { case AccBlendLevel::Ultra: case AccBlendLevel::Full: @@ -231,7 +214,7 @@ void GSRendererNew::EmulateTextureShuffleAndFbmask() // Also exclude fbmask emulation on texture shuffle just in case, it is probably safe tho. enable_fbmask_emulation = (!m_texture_shuffle && (m_vt.m_primclass != GS_TRIANGLE_CLASS) && (m_context->FRAME.FBMSK != 0x80000000)); break; - case AccBlendLevel::None: + case AccBlendLevel::Minimum: break; } } @@ -249,7 +232,7 @@ void GSRendererNew::EmulateTextureShuffleAndFbmask() // If date is enabled you need to test the green channel instead of the // alpha channel. Only enable this code in DATE mode to reduce the number // of shader. - m_conf.ps.write_rg = !write_ba && m_dev->Features().texture_barrier && m_context->TEST.DATE; + m_conf.ps.write_rg = !write_ba && g_gs_device->Features().texture_barrier && m_context->TEST.DATE; m_conf.ps.read_ba = read_ba; @@ -303,7 +286,7 @@ void GSRendererNew::EmulateTextureShuffleAndFbmask() m_conf.cb_ps.FbMask.a = ba_mask; // No blending so hit unsafe path. - if (!PRIM->ABE || !m_dev->Features().texture_barrier) + if (!PRIM->ABE || !g_gs_device->Features().texture_barrier) { GL_INS("FBMASK Unsafe SW emulated fb_mask:%x on tex shuffle", fbmask); m_conf.require_one_barrier = true; @@ -354,7 +337,7 @@ void GSRendererNew::EmulateTextureShuffleAndFbmask() have been invalidated before subsequent Draws are executed. */ // No blending so hit unsafe path. - if (!PRIM->ABE || !(~ff_fbmask & ~zero_fbmask & 0x7) || !m_dev->Features().texture_barrier) + if (!PRIM->ABE || !(~ff_fbmask & ~zero_fbmask & 0x7) || !g_gs_device->Features().texture_barrier) { GL_INS("FBMASK Unsafe SW emulated fb_mask:%x on %d bits format", m_context->FRAME.FBMSK, (GSLocalMemory::m_psm[m_context->FRAME.PSM].fmt == 2) ? 16 : 32); @@ -504,7 +487,7 @@ void GSRendererNew::EmulateChannelShuffle(GSTexture** rt, const GSTextureCache:: if (m_channel_shuffle) { m_conf.raw_tex = tex->m_from_target; - if (m_dev->Features().texture_barrier) + if (g_gs_device->Features().texture_barrier) m_conf.require_one_barrier = true; // Replace current draw with a fullscreen sprite @@ -545,7 +528,7 @@ void GSRendererNew::EmulateBlending(bool& DATE_GL42, bool& DATE_GL45) // Compute the blending equation to detect special case const GIFRegALPHA& ALPHA = m_context->ALPHA; u8 blend_index = u8(((ALPHA.A * 3 + ALPHA.B) * 3 + ALPHA.C) * 3 + ALPHA.D); - const int blend_flag = m_dev->GetBlendFlags(blend_index); + const int blend_flag = g_gs_device->GetBlendFlags(blend_index); // Do the multiplication in shader for blending accumulation: Cs*As + Cd or Cs*Af + Cd bool accumulation_blend = !!(blend_flag & BLEND_ACCU); @@ -569,9 +552,9 @@ void GSRendererNew::EmulateBlending(bool& DATE_GL42, bool& DATE_GL45) // Warning no break on purpose // Note: the [[fallthrough]] attribute tell compilers not to complain about not having breaks. bool sw_blending = false; - if (m_dev->Features().texture_barrier) + if (g_gs_device->Features().texture_barrier) { - switch (m_sw_blending) + switch (GSConfig.AccurateBlendingUnit) { case AccBlendLevel::Ultra: sw_blending |= true; @@ -591,18 +574,18 @@ void GSRendererNew::EmulateBlending(bool& DATE_GL42, bool& DATE_GL45) case AccBlendLevel::Basic: sw_blending |= impossible_or_free_blend; [[fallthrough]]; - case AccBlendLevel::None: + case AccBlendLevel::Minimum: /*sw_blending |= accumulation_blend*/; } } else { - if (static_cast(m_sw_blending) >= static_cast(AccBlendLevel::Basic)) + if (static_cast(GSConfig.AccurateBlendingUnit) >= static_cast(AccBlendLevel::Basic)) sw_blending |= accumulation_blend || blend_non_recursive; } // Do not run BLEND MIX if sw blending is already present, it's less accurate - if (m_sw_blending != AccBlendLevel::None) + if (GSConfig.AccurateBlendingUnit != AccBlendLevel::Minimum) { blend_mix &= !sw_blending; sw_blending |= blend_mix; @@ -615,7 +598,7 @@ void GSRendererNew::EmulateBlending(bool& DATE_GL42, bool& DATE_GL45) // fixes shadows in Superman shadows of Apokolips. const bool sw_fbmask_colclip = !m_conf.require_one_barrier && m_conf.ps.fbmask; bool free_colclip = false; - if (m_dev->Features().texture_barrier) + if (g_gs_device->Features().texture_barrier) free_colclip = m_prim_overlap == PRIM_OVERLAP_NO || blend_non_recursive || sw_fbmask_colclip; else free_colclip = blend_non_recursive; @@ -637,7 +620,7 @@ void GSRendererNew::EmulateBlending(bool& DATE_GL42, bool& DATE_GL45) m_conf.ps.hdr = 1; sw_blending = true; // Enable sw blending for the HDR algo } - else if (sw_blending && m_dev->Features().texture_barrier) + else if (sw_blending && g_gs_device->Features().texture_barrier) { // A slow algo that could requires several passes (barely used) GL_INS("COLCLIP SW mode ENABLED"); @@ -658,7 +641,7 @@ void GSRendererNew::EmulateBlending(bool& DATE_GL42, bool& DATE_GL45) if (sw_blending) { GL_INS("PABE mode ENABLED"); - if (m_dev->Features().texture_barrier) + if (g_gs_device->Features().texture_barrier) { // Disable hw/sw blend and do pure sw blend with reading the framebuffer. accumulation_blend = false; @@ -753,7 +736,7 @@ void GSRendererNew::EmulateBlending(bool& DATE_GL42, bool& DATE_GL45) m_conf.require_full_barrier |= !blend_non_recursive; // Only BLEND_NO_REC should hit this code path for now - if (!m_dev->Features().texture_barrier) + if (!g_gs_device->Features().texture_barrier) ASSERT(blend_non_recursive); } @@ -795,7 +778,7 @@ void GSRendererNew::EmulateTextureSampler(const GSTextureCache::Source* tex) bool bilinear = m_vt.IsLinear(); int trilinear = 0; bool trilinear_auto = false; - switch (UserHacks_tri_filter) + switch (GSConfig.UserHacks_TriFilter) { case TriFiltering::Forced: trilinear = static_cast(GS_MIN_FILTER::Linear_Mipmap_Linear); @@ -810,7 +793,7 @@ void GSRendererNew::EmulateTextureSampler(const GSTextureCache::Source* tex) } break; - case TriFiltering::None: + case TriFiltering::Off: default: break; } @@ -936,7 +919,7 @@ void GSRendererNew::EmulateTextureSampler(const GSTextureCache::Source* tex) m_conf.ps.tcc = m_context->TEX0.TCC; m_conf.ps.ltf = bilinear && shader_emulated_sampler; - m_conf.ps.point_sampler = m_dev->Features().broken_point_sampler && (!bilinear || shader_emulated_sampler); + m_conf.ps.point_sampler = g_gs_device->Features().broken_point_sampler && (!bilinear || shader_emulated_sampler); const int w = tex->m_texture->GetWidth(); const int h = tex->m_texture->GetHeight(); @@ -1200,7 +1183,7 @@ void GSRendererNew::DrawPrims(GSTexture* rt, GSTexture* ds, GSTextureCache::Sour m_conf.cb_vs.texture_offset = GSVector2(0, 0); m_conf.ps.scanmsk = m_env.SCANMSK.MSK; - ASSERT(m_dev != NULL); + ASSERT(g_gs_device != NULL); // HLE implementation of the channel selection effect // @@ -1213,13 +1196,13 @@ void GSRendererNew::DrawPrims(GSTexture* rt, GSTexture* ds, GSTextureCache::Sour MergeSprite(tex); // Always check if primitive overlap as it is used in plenty of effects. - if (m_dev->Features().texture_barrier) + if (g_gs_device->Features().texture_barrier) m_prim_overlap = PrimitiveOverlap(); else m_prim_overlap = PRIM_OVERLAP_UNKNOW; // Prim overlap check is useless without texture barrier // Detect framebuffer read that will need special handling - if (m_dev->Features().texture_barrier && (m_context->FRAME.Block() == m_context->TEX0.TBP0) && PRIM->TME && m_sw_blending != AccBlendLevel::None) + if (g_gs_device->Features().texture_barrier && (m_context->FRAME.Block() == m_context->TEX0.TBP0) && PRIM->TME && GSConfig.AccurateBlendingUnit != AccBlendLevel::Minimum) { if ((m_context->FRAME.FBMSK == 0x00FFFFFF) && (m_vt.m_primclass == GS_TRIANGLE_CLASS)) { @@ -1248,7 +1231,7 @@ void GSRendererNew::DrawPrims(GSTexture* rt, GSTexture* ds, GSTextureCache::Sour // It is way too complex to emulate texture shuffle with DATE. So just use // the slow but accurate algo GL_PERF("DATE: With %s", m_texture_shuffle ? "texture shuffle" : "no prim overlap"); - if (m_dev->Features().texture_barrier) + if (g_gs_device->Features().texture_barrier) { m_conf.require_full_barrier = true; DATE_GL45 = true; @@ -1276,21 +1259,21 @@ void GSRendererNew::DrawPrims(GSTexture* rt, GSTexture* ds, GSTextureCache::Sour // texture barrier will split the draw call into n draw call. It is very efficient for // few primitive draws. Otherwise it sucks. GL_PERF("DATE: Slow with alpha %d-%d", GetAlphaMinMax().min, GetAlphaMinMax().max); - if (m_dev->Features().texture_barrier) + if (g_gs_device->Features().texture_barrier) { m_conf.require_full_barrier = true; DATE_GL45 = true; } } - else if (m_accurate_date) + else if (GSConfig.AccurateDATE) { // Note: Fast level (DATE_one) was removed as it's less accurate. GL_PERF("DATE: Full AD with alpha %d-%d", GetAlphaMinMax().min, GetAlphaMinMax().max); - if (m_dev->Features().image_load_store) + if (g_gs_device->Features().image_load_store) { DATE_GL42 = true; } - else if (m_dev->Features().texture_barrier) + else if (g_gs_device->Features().texture_barrier) { m_conf.require_full_barrier = true; DATE_GL45 = true; @@ -1371,7 +1354,7 @@ void GSRendererNew::DrawPrims(GSTexture* rt, GSTexture* ds, GSTextureCache::Sour //The resulting shifted output aligns better with common blending / corona / blurring effects, //but introduces a few bad pixels on the edges. - if (rt && rt->LikelyOffset && m_userHacks_HPO == 1) + if (rt && rt->LikelyOffset && GSConfig.UserHacks_HalfPixelOffset == 1) { ox2 *= rt->OffsetHack_modx; oy2 *= rt->OffsetHack_mody; @@ -1392,7 +1375,7 @@ void GSRendererNew::DrawPrims(GSTexture* rt, GSTexture* ds, GSTextureCache::Sour } else if (DATE_one) { - if (m_dev->Features().texture_barrier) + if (g_gs_device->Features().texture_barrier) { m_conf.require_one_barrier = true; m_conf.ps.date = 5 + m_context->TEST.DATM; @@ -1409,13 +1392,13 @@ void GSRendererNew::DrawPrims(GSTexture* rt, GSTexture* ds, GSTextureCache::Sour } m_conf.ps.fba = m_context->FBA.FBA; - m_conf.ps.dither = m_dithering > 0 && m_conf.ps.dfmt == 2 && m_env.DTHE.DTHE; + m_conf.ps.dither = GSConfig.Dithering > 0 && m_conf.ps.dfmt == 2 && m_env.DTHE.DTHE; if (m_conf.ps.dither) { GL_DBG("DITHERING mode ENABLED (%d)", m_dithering); - m_conf.ps.dither = m_dithering; + m_conf.ps.dither = GSConfig.Dithering; m_conf.cb_ps.DitherMatrix[0] = GSVector4(m_env.DIMX.DM00, m_env.DIMX.DM01, m_env.DIMX.DM02, m_env.DIMX.DM03); m_conf.cb_ps.DitherMatrix[1] = GSVector4(m_env.DIMX.DM10, m_env.DIMX.DM11, m_env.DIMX.DM12, m_env.DIMX.DM13); m_conf.cb_ps.DitherMatrix[2] = GSVector4(m_env.DIMX.DM20, m_env.DIMX.DM21, m_env.DIMX.DM22, m_env.DIMX.DM23); @@ -1619,12 +1602,12 @@ void GSRendererNew::DrawPrims(GSTexture* rt, GSTexture* ds, GSTextureCache::Sour m_conf.rt = rt; m_conf.ds = ds; - m_dev->RenderHW(m_conf); + g_gs_device->RenderHW(m_conf); } bool GSRendererNew::IsDummyTexture() const { // Texture is actually the frame buffer. Stencil emulation to compute shadow (Jak series/tri-ace game) // Will hit the "m_ps_sel.tex_is_fb = 1" path in the draw - return m_dev->Features().texture_barrier && (m_context->FRAME.Block() == m_context->TEX0.TBP0) && PRIM->TME && m_sw_blending != AccBlendLevel::None && m_vt.m_primclass == GS_TRIANGLE_CLASS && (m_context->FRAME.FBMSK == 0x00FFFFFF); + return g_gs_device->Features().texture_barrier && (m_context->FRAME.Block() == m_context->TEX0.TBP0) && PRIM->TME && GSConfig.AccurateBlendingUnit != AccBlendLevel::Minimum && m_vt.m_primclass == GS_TRIANGLE_CLASS && (m_context->FRAME.FBMSK == 0x00FFFFFF); } diff --git a/pcsx2/GS/Renderers/HW/GSRendererNew.h b/pcsx2/GS/Renderers/HW/GSRendererNew.h index d54df3b96d83f..ce4954db3e033 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererNew.h +++ b/pcsx2/GS/Renderers/HW/GSRendererNew.h @@ -31,8 +31,6 @@ class GSRendererNew final : public GSRendererHW PRIM_OVERLAP m_prim_overlap; std::vector m_drawlist; - TriFiltering UserHacks_tri_filter; - GSHWDrawConfig m_conf; private: @@ -49,7 +47,6 @@ class GSRendererNew final : public GSRendererHW GSRendererNew(); ~GSRendererNew() override {} - bool CreateDevice(GSDevice* dev, const WindowInfo& wi) override; void DrawPrims(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* tex) override; PRIM_OVERLAP PrimitiveOverlap(); diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index 9ce956e79ec8c..ced88e4a16e2b 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -49,9 +49,6 @@ GSTextureCache::GSTextureCache(GSRenderer* r) } m_paltex = theApp.GetConfigB("paltex"); - m_crc_hack_level = theApp.GetConfigT("crc_hack_level"); - if (m_crc_hack_level == CRCHackLevel::Automatic) - m_crc_hack_level = GSUtil::GetRecommendedCRCHackLevel(theApp.GetCurrentRendererType()); // In theory 4MB is enough but 9MB is safer for overflow (8MB // isn't enough in custom resolution) @@ -191,7 +188,7 @@ GSTextureCache::Source* GSTextureCache::LookupDepthSource(const GIFRegTEX0& TEX0 GL_CACHE("TC depth: ERROR miss (0x%x, %s)", TEX0.TBP0, psm_str(psm)); // Possible ? In this case we could call LookupSource // Or just put a basic texture - // src->m_texture = m_renderer->m_dev->CreateTexture(tw, th); + // src->m_texture = g_gs_device->CreateTexture(tw, th); // In all cases rendering will be broken // // Note: might worth to check previous frame @@ -593,7 +590,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(const GIFRegTEX0& TEX0, int GL_CACHE("TC: Lookup Target(Color) %dx%d, hit Depth (0x%x, %s was %s)", new_w, new_h, bp, psm_str(TEX0.PSM), psm_str(dst_match->m_TEX0.PSM)); shader = (fmt_16_bits) ? ShaderConvert::FLOAT16_TO_RGB5A1 : ShaderConvert::FLOAT32_TO_RGBA8; } - m_renderer->m_dev->StretchRect(dst_match->m_texture, sRect, dst->m_texture, dRect, shader, false); + g_gs_device->StretchRect(dst_match->m_texture, sRect, dst->m_texture, dRect, shader, false); } } @@ -632,8 +629,8 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(const GIFRegTEX0& TEX0, int { #ifdef ENABLE_OGL_DEBUG switch (type) { - case RenderTarget: m_renderer->m_dev->ClearRenderTarget(dst->m_texture, 0); break; - case DepthStencil: m_renderer->m_dev->ClearDepth(dst->m_texture); break; + case RenderTarget: g_gs_device->ClearRenderTarget(dst->m_texture, 0); break; + case DepthStencil: g_gs_device->ClearDepth(dst->m_texture); break; default: break; } #endif @@ -738,7 +735,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(const GIFRegTEX0& TEX0, int dst = CreateTarget(TEX0, w, h, RenderTarget); ScaleTexture(dst->m_texture); - m_renderer->m_dev->ClearRenderTarget(dst->m_texture, 0); // new frame buffers after reset should be cleared, don't display memory garbage + g_gs_device->ClearRenderTarget(dst->m_texture, 0); // new frame buffers after reset should be cleared, don't display memory garbage if (m_preload_frame) { @@ -857,7 +854,7 @@ void GSTextureCache::InvalidateVideoMem(const GSOffset& off, const GSVector4i& r GL_CACHE("TC: Clear Sub Target(%s) %d (0x%x)", to_string(type), t->m_texture ? t->m_texture->GetID() : 0, t->m_TEX0.TBP0); - m_renderer->m_dev->ClearRenderTarget(t->m_texture, 0); + g_gs_device->ClearRenderTarget(t->m_texture, 0); } } } @@ -1037,6 +1034,12 @@ void GSTextureCache::InvalidateLocalMem(const GSOffset& off, const GSVector4i& r u32 psm = off.psm(); //u32 bw = off->bw; + if (GSConfig.HWDisableReadbacks) + { + Console.Error("Skipping readback of %ux%u @ %u,%u", r.width(), r.height(), r.left, r.top); + return; + } + // No depth handling please. if (psm == PSM_PSMZ32 || psm == PSM_PSMZ24 || psm == PSM_PSMZ16 || psm == PSM_PSMZ16S) { @@ -1294,10 +1297,10 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con int h = (int)(scale.y * th); GSTexture* sTex = dst->m_texture; - GSTexture* dTex = m_renderer->m_dev->CreateRenderTarget(w, h, GSTexture::Format::Color); + GSTexture* dTex = g_gs_device->CreateRenderTarget(w, h, GSTexture::Format::Color); GSVector4i area(x, y, x + w, y + h); - m_renderer->m_dev->CopyRect(sTex, dTex, area); + g_gs_device->CopyRect(sTex, dTex, area); // Keep a trace of origin of the texture src->m_texture = dTex; @@ -1326,7 +1329,7 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con // So it could be tricky to put in the middle of the DrawPrims // Texture is created to keep code compatibility - GSTexture* dTex = m_renderer->m_dev->CreateRenderTarget(tw, th, GSTexture::Format::Color); + GSTexture* dTex = g_gs_device->CreateRenderTarget(tw, th, GSTexture::Format::Color); // Keep a trace of origin of the texture src->m_texture = dTex; @@ -1401,7 +1404,7 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con //ASSERT(dst->m_TEX0.TBW > TEX0.TBW); // otherwise scale.x need to be reduced to make the larger texture fit (TODO) - //src->m_texture = m_renderer->m_dev->CreateRenderTarget(dstsize.x, dstsize.y, false); + //src->m_texture = g_gs_device->CreateRenderTarget(dstsize.x, dstsize.y, false); //GSVector4 size = GSVector4(dstsize).xyxy(); //GSVector4 scale = GSVector4(dst->m_texture->GetScale()).xyxy(); @@ -1429,7 +1432,7 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con // GSVector4 sRect = GSVector4(GSVector4i(sx, sy).xyxy() + br) * scale / size; // GSVector4 dRect = GSVector4(GSVector4i(dx, dy).xyxy() + br) * scale; - // m_renderer->m_dev->StretchRect(dst->m_texture, sRect, src->m_texture, dRect); + // g_gs_device->StretchRect(dst->m_texture, sRect, src->m_texture, dRect); // // TODO: this is quite a lot of StretchRect, do it with one Draw // } @@ -1497,7 +1500,7 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con // Don't be fooled by the name. 'dst' is the old target (hence the input) // 'src' is the new texture cache entry (hence the output) GSTexture* sTex = dst->m_texture; - GSTexture* dTex = m_renderer->m_dev->CreateRenderTarget(w, h, GSTexture::Format::Color); + GSTexture* dTex = g_gs_device->CreateRenderTarget(w, h, GSTexture::Format::Color); src->m_texture = dTex; // GH: by default (m_paltex == 0) GS converts texture to the 32 bit format @@ -1541,11 +1544,11 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con // which is arbitrary set to 1280 (biggest RT used by GS). h/w are based on the input texture // so the only reliable way to find the real size of the target is to use the TBW value. float real_width = dst->m_TEX0.TBW * 64u * dst->m_texture->GetScale().x; - m_renderer->m_dev->CopyRect(sTex, dTex, GSVector4i((int)(real_width / 2.0f), 0, (int)real_width, h)); + g_gs_device->CopyRect(sTex, dTex, GSVector4i((int)(real_width / 2.0f), 0, (int)real_width, h)); } else { - m_renderer->m_dev->CopyRect(sTex, dTex, GSVector4i(0, 0, w, h)); // <= likely wrong dstsize.x could be bigger than w + g_gs_device->CopyRect(sTex, dTex, GSVector4i(0, 0, w, h)); // <= likely wrong dstsize.x could be bigger than w } } else @@ -1559,7 +1562,7 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con sRect.x = sRect.z / 2.0f; } - m_renderer->m_dev->StretchRect(sTex, sRect, dTex, dRect, shader, linear); + g_gs_device->StretchRect(sTex, sRect, dTex, dRect, shader, linear); } if (src->m_texture) @@ -1602,12 +1605,12 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con { if (m_paltex && psm.pal > 0) { - src->m_texture = m_renderer->m_dev->CreateTexture(tw, th, GSTexture::Format::UNorm8); + src->m_texture = g_gs_device->CreateTexture(tw, th, GSTexture::Format::UNorm8); AttachPaletteToSource(src, psm.pal, true); } else { - src->m_texture = m_renderer->m_dev->CreateTexture(tw, th, GSTexture::Format::Color); + src->m_texture = g_gs_device->CreateTexture(tw, th, GSTexture::Format::Color); if (psm.pal > 0) { AttachPaletteToSource(src, psm.pal, false); @@ -1634,13 +1637,13 @@ GSTextureCache::Target* GSTextureCache::CreateTarget(const GIFRegTEX0& TEX0, int if (type == RenderTarget) { - t->m_texture = m_renderer->m_dev->CreateSparseRenderTarget(w, h, GSTexture::Format::Color); + t->m_texture = g_gs_device->CreateSparseRenderTarget(w, h, GSTexture::Format::Color); t->m_used = true; // FIXME } else if (type == DepthStencil) { - t->m_texture = m_renderer->m_dev->CreateSparseDepthStencil(w, h, GSTexture::Format::DepthStencil); + t->m_texture = g_gs_device->CreateSparseDepthStencil(w, h, GSTexture::Format::DepthStencil); } m_dst[type].push_front(t); @@ -1699,9 +1702,9 @@ void GSTextureCache::Read(Target* t, const GSVector4i& r) GSTexture::GSMap m; if (t->m_texture->GetScale() == GSVector2(1, 1) && ps_shader == ShaderConvert::COPY) - res = m_renderer->m_dev->DownloadTexture(t->m_texture, r, m); + res = g_gs_device->DownloadTexture(t->m_texture, r, m); else - res = m_renderer->m_dev->DownloadTextureConvert(t->m_texture, src, GSVector2i(r.width(), r.height()), fmt, ps_shader, m, false); + res = g_gs_device->DownloadTextureConvert(t->m_texture, src, GSVector2i(r.width(), r.height()), fmt, ps_shader, m, false); if (res) { @@ -1728,7 +1731,7 @@ void GSTextureCache::Read(Target* t, const GSVector4i& r) ASSERT(0); } - m_renderer->m_dev->DownloadTextureComplete(); + g_gs_device->DownloadTextureComplete(); } } @@ -1737,11 +1740,11 @@ void GSTextureCache::Read(Source* t, const GSVector4i& r) const GIFRegTEX0& TEX0 = t->m_TEX0; GSTexture::GSMap m; - if (m_renderer->m_dev->DownloadTexture(t->m_texture, r, m)) + if (g_gs_device->DownloadTexture(t->m_texture, r, m)) { GSOffset off = m_renderer->m_mem.GetOffset(TEX0.TBP0, TEX0.TBW, TEX0.PSM); m_renderer->m_mem.WritePixel32(m.bits, m.pitch, off, r); - m_renderer->m_dev->DownloadTextureComplete(); + g_gs_device->DownloadTextureComplete(); } } @@ -1796,7 +1799,7 @@ GSTextureCache::Surface::~Surface() // Shared textures are pointers copy. Therefore no allocation // to recycle. if (!m_shared_texture) - m_renderer->m_dev->Recycle(m_texture); + g_gs_device->Recycle(m_texture); } void GSTextureCache::Surface::UpdateAge() @@ -2144,7 +2147,7 @@ void GSTextureCache::Target::Update() // could be a gs transfer bug too due to unaligned-page transfer. // // So the quick and dirty solution is just to clean the depth buffer. - m_renderer->m_dev->ClearDepth(m_texture); + g_gs_device->ClearDepth(m_texture); return; } @@ -2157,7 +2160,7 @@ void GSTextureCache::Target::Update() TEXA.TA0 = 0; TEXA.TA1 = 0x80; - GSTexture* t = m_renderer->m_dev->CreateTexture(w, h, GSTexture::Format::Color); + GSTexture* t = g_gs_device->CreateTexture(w, h, GSTexture::Format::Color); GSOffset off = m_renderer->m_mem.GetOffset(m_TEX0.TBP0, m_TEX0.TBW, m_TEX0.PSM); @@ -2185,17 +2188,17 @@ void GSTextureCache::Target::Update() { GL_INS("ERROR: Update RenderTarget 0x%x bw:%d (%d,%d => %d,%d)", m_TEX0.TBP0, m_TEX0.TBW, r.x, r.y, r.z, r.w); - m_renderer->m_dev->StretchRect(t, m_texture, GSVector4(r) * GSVector4(m_texture->GetScale()).xyxy()); + g_gs_device->StretchRect(t, m_texture, GSVector4(r) * GSVector4(m_texture->GetScale()).xyxy()); } else if (m_type == DepthStencil) { GL_INS("ERROR: Update DepthStencil 0x%x", m_TEX0.TBP0); // FIXME linear or not? - m_renderer->m_dev->StretchRect(t, m_texture, GSVector4(r) * GSVector4(m_texture->GetScale()).xyxy(), ShaderConvert::RGBA8_TO_FLOAT32); + g_gs_device->StretchRect(t, m_texture, GSVector4(r) * GSVector4(m_texture->GetScale()).xyxy(), ShaderConvert::RGBA8_TO_FLOAT32); } - m_renderer->m_dev->Recycle(t); + g_gs_device->Recycle(t); } void GSTextureCache::Target::UpdateValidity(const GSVector4i& rect) @@ -2294,7 +2297,7 @@ GSTextureCache::Palette::Palette(const GSRenderer* renderer, u16 pal, bool need_ GSTextureCache::Palette::~Palette() { - m_renderer->m_dev->Recycle(m_tex_palette); + g_gs_device->Recycle(m_tex_palette); _aligned_free(m_clut); } @@ -2317,7 +2320,7 @@ void GSTextureCache::Palette::InitializeTexture() // sampling such texture are always normalized by 255. // This is because indexes are stored as normalized values of an RGBA texture (e.g. index 15 will be read as (15/255), // and therefore will read texel 15/255 * texture size). - m_tex_palette = m_renderer->m_dev->CreateTexture(256, 1, GSTexture::Format::Color); + m_tex_palette = g_gs_device->CreateTexture(256, 1, GSTexture::Format::Color); m_tex_palette->Update(GSVector4i(0, 0, m_pal, 1), m_clut, m_pal * sizeof(m_clut[0])); } } diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.h b/pcsx2/GS/Renderers/HW/GSTextureCache.h index 4be841b2d06b2..c37c9eb1d22e3 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.h +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.h @@ -219,7 +219,6 @@ class GSTextureCache u8* m_temp; bool m_can_convert_depth; bool m_cpu_fb_conversion; - CRCHackLevel m_crc_hack_level; static bool m_disable_partial_invalidation; bool m_texture_inside_rt; static bool m_wrap_gs_mem; diff --git a/pcsx2/GS/Renderers/Null/GSDeviceNull.cpp b/pcsx2/GS/Renderers/Null/GSDeviceNull.cpp index 54921f892ca6a..4f68658ddd065 100644 --- a/pcsx2/GS/Renderers/Null/GSDeviceNull.cpp +++ b/pcsx2/GS/Renderers/Null/GSDeviceNull.cpp @@ -16,21 +16,6 @@ #include "PrecompiledHeader.h" #include "GSDeviceNull.h" -bool GSDeviceNull::Create(const WindowInfo& wi) -{ - if (!GSDevice::Create(wi)) - return false; - - Reset(1, 1); - - return true; -} - -bool GSDeviceNull::Reset(int w, int h) -{ - return GSDevice::Reset(w, h); -} - GSTexture* GSDeviceNull::CreateSurface(GSTexture::Type type, int w, int h, GSTexture::Format format) { return new GSTextureNull(type, w, h, format); diff --git a/pcsx2/GS/Renderers/Null/GSDeviceNull.h b/pcsx2/GS/Renderers/Null/GSDeviceNull.h index 78da505828a6c..d1ff6f8aa67d9 100644 --- a/pcsx2/GS/Renderers/Null/GSDeviceNull.h +++ b/pcsx2/GS/Renderers/Null/GSDeviceNull.h @@ -29,7 +29,4 @@ class GSDeviceNull : public GSDevice public: GSDeviceNull() {} - - bool Create(const WindowInfo& wi); - bool Reset(int w, int h); }; diff --git a/pcsx2/GS/Renderers/Null/GSTextureNull.cpp b/pcsx2/GS/Renderers/Null/GSTextureNull.cpp index 75d6499b550af..ba1e2cd2294a3 100644 --- a/pcsx2/GS/Renderers/Null/GSTextureNull.cpp +++ b/pcsx2/GS/Renderers/Null/GSTextureNull.cpp @@ -28,3 +28,8 @@ GSTextureNull::GSTextureNull(Type type, int w, int h, GSTexture::Format format) m_desc.h = h; m_desc.format = format; } + +void* GSTextureNull::GetNativeHandle() const +{ + return nullptr; +} diff --git a/pcsx2/GS/Renderers/Null/GSTextureNull.h b/pcsx2/GS/Renderers/Null/GSTextureNull.h index 2ad202962c56f..6eeb50b3927f9 100644 --- a/pcsx2/GS/Renderers/Null/GSTextureNull.h +++ b/pcsx2/GS/Renderers/Null/GSTextureNull.h @@ -37,4 +37,5 @@ class GSTextureNull final : public GSTexture bool Map(GSMap& m, const GSVector4i* r = NULL, int layer = 0) override { return false; } void Unmap() override {} bool Save(const std::string& fn) override { return false; } + void* GetNativeHandle() const override; }; diff --git a/pcsx2/GS/Renderers/OpenGL/GLLoader.cpp b/pcsx2/GS/Renderers/OpenGL/GLLoader.cpp index f88fbd179eb82..f7665b24e6183 100644 --- a/pcsx2/GS/Renderers/OpenGL/GLLoader.cpp +++ b/pcsx2/GS/Renderers/OpenGL/GLLoader.cpp @@ -17,6 +17,7 @@ #include "GLLoader.h" #include "GS/GS.h" #include +#include "Host.h" namespace GLExtension { @@ -161,15 +162,15 @@ namespace GLLoader bool found_GL_ARB_get_texture_sub_image = false; #endif - static void mandatory(const std::string& ext) + static bool mandatory(const std::string& ext) { if (!GLExtension::Has(ext)) { - fprintf(stderr, "ERROR: %s is NOT SUPPORTED\n", ext.c_str()); - throw GSRecoverableError(); + Host::ReportFormattedErrorAsync("GS", "ERROR: %s is NOT SUPPORTED\n", ext.c_str()); + return false; } - return; + return true; } static bool optional(const std::string& name) @@ -198,7 +199,7 @@ namespace GLLoader return found; } - void check_gl_version(int major, int minor) + bool check_gl_version(int major, int minor) { const char* vendor = (const char*)glGetString(GL_VENDOR); if (strstr(vendor, "Advanced Micro Devices") || strstr(vendor, "ATI Technologies Inc.") || strstr(vendor, "ATI")) @@ -226,12 +227,14 @@ namespace GLLoader glGetIntegerv(GL_MINOR_VERSION, &minor_gl); if ((major_gl < major) || (major_gl == major && minor_gl < minor)) { - fprintf(stderr, "OpenGL %d.%d is not supported. Only OpenGL %d.%d\n was found", major, minor, major_gl, minor_gl); - throw GSRecoverableError(); + Host::ReportFormattedErrorAsync("GS", "OpenGL %d.%d is not supported. Only OpenGL %d.%d\n was found", major, minor, major_gl, minor_gl); + return false; } + + return true; } - void check_gl_supported_extension() + bool check_gl_supported_extension() { int max_ext = 0; glGetIntegerv(GL_NUM_EXTENSIONS, &max_ext); @@ -243,24 +246,27 @@ namespace GLLoader } // Mandatory for both renderer + bool ok = true; { // GL4.1 - mandatory("GL_ARB_separate_shader_objects"); + ok = ok && mandatory("GL_ARB_separate_shader_objects"); // GL4.2 - mandatory("GL_ARB_shading_language_420pack"); - mandatory("GL_ARB_texture_storage"); + ok = ok && mandatory("GL_ARB_shading_language_420pack"); + ok = ok && mandatory("GL_ARB_texture_storage"); // GL4.3 - mandatory("GL_KHR_debug"); + ok = ok && mandatory("GL_KHR_debug"); // GL4.4 - mandatory("GL_ARB_buffer_storage"); + ok = ok && mandatory("GL_ARB_buffer_storage"); } // Only for HW renderer - if (theApp.GetCurrentRendererType() == GSRendererType::OGL_HW) + if (GSConfig.UseHardwareRenderer()) { - mandatory("GL_ARB_copy_image"); - mandatory("GL_ARB_clip_control"); + ok = ok && mandatory("GL_ARB_copy_image"); + ok = ok && mandatory("GL_ARB_clip_control"); } + if (!ok) + return false; // Extra { @@ -319,6 +325,8 @@ namespace GLLoader Emulate_DSA::Init(); } #endif + + return true; } bool is_sparse2_compatible(const char* name, GLenum internal_fmt, int x_max, int y_max) @@ -385,11 +393,13 @@ namespace GLLoader fprintf_once(stdout, "INFO: sparse depth texture is %s\n", found_compatible_sparse_depth ? "available" : "NOT SUPPORTED"); } - void check_gl_requirements() + bool check_gl_requirements() { - check_gl_version(3, 3); + if (!check_gl_version(3, 3)) + return false; - check_gl_supported_extension(); + if (!check_gl_supported_extension()) + return false; // Bonus for sparse texture check_sparse_compatibility(); @@ -397,5 +407,6 @@ namespace GLLoader fprintf_once(stdout, "\n"); s_first_load = false; + return true; } } // namespace GLLoader diff --git a/pcsx2/GS/Renderers/OpenGL/GLLoader.h b/pcsx2/GS/Renderers/OpenGL/GLLoader.h index cb42eb34c2283..64c726c1339c3 100644 --- a/pcsx2/GS/Renderers/OpenGL/GLLoader.h +++ b/pcsx2/GS/Renderers/OpenGL/GLLoader.h @@ -30,7 +30,7 @@ namespace GLExtension namespace GLLoader { - void check_gl_requirements(); + bool check_gl_requirements(); extern bool vendor_id_amd; extern bool vendor_id_nvidia; diff --git a/pcsx2/GS/Renderers/OpenGL/GLState.cpp b/pcsx2/GS/Renderers/OpenGL/GLState.cpp index 2e6659590e3fb..c7d9d668da4f2 100644 --- a/pcsx2/GS/Renderers/OpenGL/GLState.cpp +++ b/pcsx2/GS/Renderers/OpenGL/GLState.cpp @@ -52,23 +52,23 @@ namespace GLState void Clear() { fbo = 0; - viewport = GSVector2i(0, 0); - scissor = GSVector4i(0, 0, 0, 0); + viewport = GSVector2i(1, 1); + scissor = GSVector4i(0, 0, 1, 1); blend = false; - eq_RGB = 0; - f_sRGB = 0; - f_dRGB = 0; + eq_RGB = GL_FUNC_ADD; + f_sRGB = GL_ONE; + f_dRGB = GL_ZERO; bf = 0; wrgba = 0xF; depth = false; - depth_func = 0; - depth_mask = true; + depth_func = GL_LESS; + depth_mask = false; stencil = false; - stencil_func = 0; - stencil_pass = 0xFFFF; // Note 0 is valid (GL_ZERO) + stencil_func = GL_ALWAYS; + stencil_pass = GL_KEEP; ps_ss = 0; diff --git a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp index a624f7ff31517..9491ee1ec1c30 100644 --- a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp +++ b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp @@ -20,6 +20,7 @@ #include "GLState.h" #include "GS/GSUtil.h" #include "Host.h" +#include "HostDisplay.h" #include #include @@ -40,7 +41,6 @@ static constexpr u32 INDEX_BUFFER_SIZE = 16 * 1024 * 1024; static constexpr u32 VERTEX_UNIFORM_BUFFER_SIZE = 8 * 1024 * 1024; static constexpr u32 FRAGMENT_UNIFORM_BUFFER_SIZE = 8 * 1024 * 1024; -bool GSDeviceOGL::m_debug_gl_call = false; int GSDeviceOGL::m_shader_inst = 0; int GSDeviceOGL::m_shader_reg = 0; FILE* GSDeviceOGL::m_debug_gl_file = NULL; @@ -61,25 +61,15 @@ GSDeviceOGL::GSDeviceOGL() memset(&m_shadeboost, 0, sizeof(m_shadeboost)); memset(&m_om_dss, 0, sizeof(m_om_dss)); memset(&m_profiler, 0, sizeof(m_profiler)); - GLState::Clear(); m_mipmap = theApp.GetConfigI("mipmap"); m_upscale_multiplier = std::max(1, theApp.GetConfigI("upscale_multiplier")); - if (theApp.GetConfigB("UserHacks")) - m_filter = static_cast(theApp.GetConfigI("UserHacks_TriFilter")); - else - m_filter = TriFiltering::None; // Reset the debug file #ifdef ENABLE_OGL_DEBUG - if (theApp.GetCurrentRendererType() == GSRendererType::OGL_SW) - m_debug_gl_file = fopen("GS_opengl_debug_sw.txt", "w"); - else - m_debug_gl_file = fopen("GS_opengl_debug_hw.txt", "w"); + m_debug_gl_file = fopen("GS_opengl_debug.txt", "w"); #endif - m_debug_gl_call = theApp.GetConfigB("debug_opengl"); - m_disable_hw_gl_draw = theApp.GetConfigB("disable_hw_gl_draw"); } @@ -215,29 +205,23 @@ GSTexture* GSDeviceOGL::CreateSurface(GSTexture::Type type, int w, int h, GSText GL_PUSH("Create surface"); // A wrapper to call GSTextureOGL, with the different kind of parameters. - GSTextureOGL* t = new GSTextureOGL(type, w, h, fmt, m_fbo_read, m_mipmap > 1 || m_filter != TriFiltering::None); + GSTextureOGL* t = new GSTextureOGL(type, w, h, fmt, m_fbo_read, m_mipmap > 1 || GSConfig.UserHacks_TriFilter != TriFiltering::Off); return t; } -bool GSDeviceOGL::Create(const WindowInfo& wi) +bool GSDeviceOGL::Create(HostDisplay* display) { - m_gl_context = GL::Context::Create(wi, GL::Context::GetAllVersionsList()); - if (!m_gl_context || !m_gl_context->MakeCurrent()) + if (!GSDevice::Create(display)) + return false; + + if (display->GetRenderAPI() != HostDisplay::RenderAPI::OpenGL) return false; // Check openGL requirement as soon as possible so we can switch to another // renderer/device - try - { - GLLoader::check_gl_requirements(); - } - catch (std::exception& ex) - { - printf("GS error: Exception caught in GSDeviceOGL::Create: %s", ex.what()); - m_gl_context->DoneCurrent(); + if (!GLLoader::check_gl_requirements()) return false; - } if (!theApp.GetConfigB("disable_shader_cache")) { @@ -268,16 +252,22 @@ bool GSDeviceOGL::Create(const WindowInfo& wi) { auto shader = Host::ReadResourceFileToString("shaders/opengl/common_header.glsl"); if (!shader.has_value()) + { + Host::ReportErrorAsync("GS", "Failed to read shaders/opengl/common_header.glsl."); return false; + } m_shader_common_header = std::move(*shader); } + // because of fbo bindings below... + GLState::Clear(); + // **************************************************************** // Debug helper // **************************************************************** #ifdef ENABLE_OGL_DEBUG - if (theApp.GetConfigB("debug_opengl")) + if (GSConfig.UseDebugDevice) { glDebugMessageCallback((GLDEBUGPROC)DebugOutputToFile, NULL); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB); @@ -334,7 +324,7 @@ bool GSDeviceOGL::Create(const WindowInfo& wi) glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &m_uniform_buffer_alignment); if (!m_vertex_stream_buffer || !m_index_stream_buffer || !m_vertex_uniform_stream_buffer || !m_fragment_uniform_stream_buffer) { - Console.Error("Failed to create vertex/index/uniform streaming buffers"); + Host::ReportErrorAsync("GS", "Failed to create vertex/index/uniform streaming buffers"); return false; } @@ -356,6 +346,10 @@ bool GSDeviceOGL::Create(const WindowInfo& wi) glVertexAttribPointer(7, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(GSVertex), (const GLvoid*)(28)); } + // must be done after va is created + GLState::Clear(); + RestoreAPIState(); + // **************************************************************** // Pre Generate the different sampler object // **************************************************************** @@ -377,7 +371,10 @@ bool GSDeviceOGL::Create(const WindowInfo& wi) // these all share the same vertex shader const auto shader = Host::ReadResourceFileToString("shaders/opengl/convert.glsl"); if (!shader.has_value()) + { + Host::ReportErrorAsync("GS", "Failed to read shaders/opengl/convert.glsl."); return false; + } m_convert.vs = GetShaderSource("vs_main", GL_VERTEX_SHADER, m_shader_common_header, *shader, {}); @@ -417,7 +414,10 @@ bool GSDeviceOGL::Create(const WindowInfo& wi) const auto shader = Host::ReadResourceFileToString("shaders/opengl/merge.glsl"); if (!shader.has_value()) + { + Host::ReportErrorAsync("GS", "Failed to read shaders/opengl/merge.glsl."); return false; + } for (size_t i = 0; i < std::size(m_merge_obj.ps); i++) { @@ -437,7 +437,10 @@ bool GSDeviceOGL::Create(const WindowInfo& wi) const auto shader = Host::ReadResourceFileToString("shaders/opengl/interlace.glsl"); if (!shader.has_value()) + { + Host::ReportErrorAsync("GS", "Failed to read shaders/opengl/interlace.glsl."); return false; + } for (size_t i = 0; i < std::size(m_interlace.ps); i++) { @@ -465,7 +468,10 @@ bool GSDeviceOGL::Create(const WindowInfo& wi) const auto shader = Host::ReadResourceFileToString("shaders/opengl/shadeboost.glsl"); if (!shader.has_value()) + { + Host::ReportErrorAsync("GS", "Failed to read shaders/opengl/shadeboost.glsl."); return false; + } const std::string ps(GetShaderSource("ps_main", GL_FRAGMENT_SHADER, m_shader_common_header, *shader, shade_macro)); if (!m_shader_cache.GetProgram(&m_shadeboost.ps, m_convert.vs, {}, ps)) @@ -563,23 +569,6 @@ bool GSDeviceOGL::Create(const WindowInfo& wi) fprintf(stdout, "Available VRAM/RAM:%lldMB for textures\n", GLState::available_vram >> 20u); - // **************************************************************** - // Texture Font (OSD) - // **************************************************************** - const GSVector2i tex_font = m_osd.get_texture_font_size(); - - m_font = std::unique_ptr( - new GSTextureOGL(GSTexture::Type::Texture, tex_font.x, tex_font.y, GSTexture::Format::UNorm8, m_fbo_read, false) - ); - - // **************************************************************** - // Finish window setup and backbuffer - // **************************************************************** - if (!GSDevice::Create(wi)) - return false; - - Reset(wi.surface_width, wi.surface_height); - // Basic to ensure structures are correctly packed static_assert(sizeof(VSSelector) == 4, "Wrong VSSelector size"); static_assert(sizeof(PSSelector) == 8, "Wrong PSSelector size"); @@ -597,7 +586,10 @@ bool GSDeviceOGL::CreateTextureFX() auto vertex_shader = Host::ReadResourceFileToString("shaders/opengl/tfx_vgs.glsl"); auto fragment_shader = Host::ReadResourceFileToString("shaders/opengl/tfx_fs.glsl"); if (!vertex_shader.has_value() || !fragment_shader.has_value()) + { + Host::ReportErrorAsync("GS", "Failed to read shaders/opengl/tfx_{vgs,fs}.glsl."); return false; + } m_shader_tfx_vgs = std::move(*vertex_shader); m_shader_tfx_fs = std::move(*fragment_shader); @@ -615,37 +607,75 @@ bool GSDeviceOGL::CreateTextureFX() m_om_dss[key] = CreateDepthStencil(OMDepthStencilSelector(key)); } - return true; -} - -bool GSDeviceOGL::Reset(int w, int h) -{ - if (!GSDevice::Reset(w, h)) - return false; + if (GLLoader::in_replayer) + { + glQueryCounter(m_profiler.timer(), GL_TIMESTAMP); + m_profiler.last_query++; + } - // Opengl allocate the backbuffer with the window. The render is done in the backbuffer when - // there isn't any FBO. Only a dummy texture is created to easily detect when the rendering is done - // in the backbuffer - m_gl_context->ResizeSurface(w, h); - m_backbuffer = new GSTextureOGL(GSTexture::Type::Backbuffer, m_gl_context->GetSurfaceWidth(), - m_gl_context->GetSurfaceHeight(), GSTexture::Format::Backbuffer, m_fbo_read, false); return true; } -void GSDeviceOGL::SetVSync(int vsync) +void GSDeviceOGL::ResetAPIState() { - m_gl_context->SetSwapInterval(vsync); + if (GLState::point_size) + glDisable(GL_PROGRAM_POINT_SIZE); + if (GLState::line_width != 1.0f) + glLineWidth(1.0f); } -void GSDeviceOGL::Flip() +void GSDeviceOGL::RestoreAPIState() { - m_gl_context->SwapBuffers(); + glBindVertexArray(m_vertex_array_object); - if (GLLoader::in_replayer) + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, GLState::fbo); + + glViewportIndexedf(0, 0, 0, static_cast(GLState::viewport.x), static_cast(GLState::viewport.y)); + glScissorIndexed(0, GLState::scissor.x, GLState::scissor.y, GLState::scissor.width(), GLState::scissor.height()); + + glBlendEquationSeparate(GLState::eq_RGB, GL_FUNC_ADD); + glBlendFuncSeparate(GLState::f_sRGB, GLState::f_dRGB, GL_ONE, GL_ZERO); + + const float bf = static_cast(GLState::bf) / 128.0f; + glBlendColor(bf, bf, bf, bf); + + if (GLState::blend) { - glQueryCounter(m_profiler.timer(), GL_TIMESTAMP); - m_profiler.last_query++; + glEnable(GL_BLEND); + } + else + { + glDisable(GL_BLEND); + } + + const OMColorMaskSelector msel{ GLState::wrgba }; + glColorMask(msel.wr, msel.wg, msel.wb, msel.wa); + + GLState::depth ? glEnable(GL_DEPTH_TEST) : glDisable(GL_DEPTH_TEST); + glDepthFunc(GLState::depth_func); + glDepthMask(GLState::depth_mask); + + if (GLState::stencil) + { + glEnable(GL_STENCIL_TEST); } + else + { + glDisable(GL_STENCIL_TEST); + } + + glStencilFunc(GLState::stencil_func, 1, 1); + glStencilOp(GL_KEEP, GL_KEEP, GLState::stencil_pass); + + glBindSampler(0, GLState::ps_ss); + + for (GLuint i = 0; i < sizeof(GLState::tex_unit) / sizeof(GLState::tex_unit[0]); i++) + glBindTextureUnit(i, GLState::tex_unit[i]); + + if (GLState::point_size) + glEnable(GL_PROGRAM_POINT_SIZE); + if (GLState::line_width != 1.0f) + glLineWidth(GLState::line_width); } void GSDeviceOGL::DrawPrimitive() @@ -680,7 +710,7 @@ void GSDeviceOGL::ClearRenderTarget(GSTexture* t, const GSVector4& c) return; GSTextureOGL* T = static_cast(t); - if (T->HasBeenCleaned() && !T->IsBackbuffer()) + if (T->HasBeenCleaned()) return; // Performance note: potentially T->Clear() could be used. Main purpose of @@ -698,21 +728,10 @@ void GSDeviceOGL::ClearRenderTarget(GSTexture* t, const GSVector4& c) const u32 old_color_mask = GLState::wrgba; OMSetColorMaskState(); - if (T->IsBackbuffer()) - { - OMSetFBO(0); - - // glDrawBuffer(GL_BACK); // this is the default when there is no FB - // 0 will select the first drawbuffer ie GL_BACK - glClearBufferfv(GL_COLOR, 0, c.v); - } - else - { - OMSetFBO(m_fbo); - OMAttachRt(T); + OMSetFBO(m_fbo); + OMAttachRt(T); - glClearBufferfv(GL_COLOR, 0, c.v); - } + glClearBufferfv(GL_COLOR, 0, c.v); OMSetColorMaskState(OMColorMaskSelector(old_color_mask)); @@ -1167,11 +1186,7 @@ void GSDeviceOGL::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture void GSDeviceOGL::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, const GL::Program& ps, int bs, OMColorMaskSelector cms, bool linear) { - if (!sTex || !dTex) - { - ASSERT(0); - return; - } + ASSERT(sTex); const bool draw_in_depth = ps == m_convert.ps[static_cast(ShaderConvert::RGBA8_TO_FLOAT32)] || ps == m_convert.ps[static_cast(ShaderConvert::RGBA8_TO_FLOAT24)] @@ -1182,15 +1197,27 @@ void GSDeviceOGL::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture // instead to emulate it with shader // see https://www.opengl.org/wiki/Framebuffer#Blitting - GL_PUSH("StretchRect from %d to %d", sTex->GetID(), dTex->GetID()); - // ************************************ // Init // ************************************ BeginScene(); - GSVector2i ds = dTex->GetSize(); + GSVector2i ds; + if (dTex) + { + GL_PUSH("StretchRect from %d to %d", sTex->GetID(), dTex->GetID()); + ds = dTex->GetSize(); + dTex->CommitRegion(GSVector2i((int)dRect.z + 1, (int)dRect.w + 1)); + if (draw_in_depth) + OMSetRenderTargets(NULL, dTex); + else + OMSetRenderTargets(dTex, NULL); + } + else + { + ds = GSVector2i(m_display->GetWindowWidth(), m_display->GetWindowHeight()); + } ps.Bind(); @@ -1203,11 +1230,6 @@ void GSDeviceOGL::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture else OMSetDepthStencilState(m_convert.dss); - if (draw_in_depth) - OMSetRenderTargets(NULL, dTex); - else - OMSetRenderTargets(dTex, NULL); - OMSetBlendState((u8)bs); OMSetColorMaskState(cms); @@ -1222,7 +1244,7 @@ void GSDeviceOGL::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture // 2/ in case some GS code expect thing in dx order. // Only flipping the backbuffer is transparent (I hope)... GSVector4 flip_sr = sRect; - if (static_cast(dTex)->IsBackbuffer()) + if (!dTex) { flip_sr.y = sRect.w; flip_sr.w = sRect.y; @@ -1238,7 +1260,6 @@ void GSDeviceOGL::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture // ************************************ // Draw // ************************************ - dTex->CommitRegion(GSVector2i((int)dRect.z + 1, (int)dRect.w + 1)); DrawStretchRect(flip_sr, dRect, ds); // ************************************ @@ -1276,39 +1297,6 @@ void GSDeviceOGL::DrawStretchRect(const GSVector4& sRect, const GSVector4& dRect DrawPrimitive(); } -void GSDeviceOGL::RenderOsd(GSTexture* dt) -{ - BeginScene(); - - m_convert.ps[static_cast(ShaderConvert::OSD)].Bind(); - - OMSetDepthStencilState(m_convert.dss); - OMSetBlendState((u8)GSDeviceOGL::m_MERGE_BLEND); - OMSetRenderTargets(dt, NULL); - - if (m_osd.m_texture_dirty) - { - m_osd.upload_texture_atlas(m_font.get()); - } - - PSSetShaderResource(0, m_font.get()); - PSSetSamplerState(m_convert.pt); - - IASetPrimitiveTopology(GL_TRIANGLES); - - // Note scaling could also be done in shader (require gl3/dx10) - size_t count = m_osd.Size(); - auto res = m_vertex_stream_buffer->Map(sizeof(GSVertexPT1), static_cast(count) * sizeof(GSVertexPT1)); - count = m_osd.GeneratePrimitives(reinterpret_cast(res.pointer), count); - m_vertex.start = res.index_aligned; - m_vertex.count = count; - m_vertex_stream_buffer->Unmap(static_cast(count) * sizeof(GSVertexPT1)); - - DrawPrimitive(); - - EndScene(); -} - void GSDeviceOGL::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, GSVector4* dRect, const GSRegPMODE& PMODE, const GSRegEXTBUF& EXTBUF, const GSVector4& c) { GL_PUSH("DoMerge"); @@ -1705,30 +1693,21 @@ void GSDeviceOGL::OMSetRenderTargets(GSTexture* rt, GSTexture* ds, const GSVecto GSTextureOGL* RT = static_cast(rt); GSTextureOGL* DS = static_cast(ds); - if (rt == NULL || !RT->IsBackbuffer()) + OMSetFBO(m_fbo); + if (rt) { - OMSetFBO(m_fbo); - if (rt) - { - OMAttachRt(RT); - } - else - { - OMAttachRt(); - } - - // Note: it must be done after OMSetFBO - if (ds) - OMAttachDs(DS); - else - OMAttachDs(); + OMAttachRt(RT); } else { - // Render in the backbuffer - OMSetFBO(0); + OMAttachRt(); } + // Note: it must be done after OMSetFBO + if (ds) + OMAttachDs(DS); + else + OMAttachDs(); const GSVector2i size = rt ? rt->GetSize() : ds ? ds->GetSize() : GLState::viewport; if (GLState::viewport != size) diff --git a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.h b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.h index 8e844797d2989..f10e8931901f0 100644 --- a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.h +++ b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.h @@ -213,12 +213,9 @@ class GSDeviceOGL final : public GSDevice // Increment this constant whenever shaders change, to invalidate user's program binary cache. static constexpr u32 SHADER_VERSION = 1; - std::unique_ptr m_gl_context; int m_mipmap; int m_upscale_multiplier; - TriFiltering m_filter; - static bool m_debug_gl_call; static FILE* m_debug_gl_file; bool m_disable_hw_gl_draw; @@ -301,7 +298,6 @@ class GSDeviceOGL final : public GSDevice GSHWDrawConfig::VSConstantBuffer m_vs_cb_cache; GSHWDrawConfig::PSConstantBuffer m_ps_cb_cache; - std::unique_ptr m_font; AlignedBuffer m_download_buffer; GSTexture* CreateSurface(GSTexture::Type type, int w, int h, GSTexture::Format format) final; @@ -311,7 +307,6 @@ class GSDeviceOGL final : public GSDevice void DoFXAA(GSTexture* sTex, GSTexture* dTex) final; void DoShadeBoost(GSTexture* sTex, GSTexture* dTex) final; void DoExternalFX(GSTexture* sTex, GSTexture* dTex) final; - void RenderOsd(GSTexture* dt) final; void OMAttachRt(GSTextureOGL* rt = NULL); void OMAttachDs(GSTextureOGL* ds = NULL); @@ -330,10 +325,10 @@ class GSDeviceOGL final : public GSDevice // Used by OpenGL, so the same calling convention is required. static void APIENTRY DebugOutputToFile(GLenum gl_source, GLenum gl_type, GLuint id, GLenum gl_severity, GLsizei gl_length, const GLchar* gl_message, const void* userParam); - bool Create(const WindowInfo& wi) override; - bool Reset(int w, int h) override; - void Flip() override; - void SetVSync(int vsync) override; + bool Create(HostDisplay* display) override; + + void ResetAPIState() override; + void RestoreAPIState() override; void DrawPrimitive(); void DrawIndexedPrimitive(); diff --git a/pcsx2/GS/Renderers/OpenGL/GSTextureOGL.cpp b/pcsx2/GS/Renderers/OpenGL/GSTextureOGL.cpp index db1599e5c7f7b..3df8de2377fd1 100644 --- a/pcsx2/GS/Renderers/OpenGL/GSTextureOGL.cpp +++ b/pcsx2/GS/Renderers/OpenGL/GSTextureOGL.cpp @@ -98,7 +98,7 @@ namespace PboPool m_map = NULL; m_offset = 0; - for (GLsync fence : m_fence) + for (GLsync& fence : m_fence) { if (fence != 0) { @@ -238,13 +238,6 @@ GSTextureOGL::GSTextureOGL(Type type, int w, int h, Format format, GLuint fbo_re m_int_shift = 3; // 4 bytes for depth + 4 bytes for stencil by texels break; - // Backbuffer - case Format::Backbuffer: - m_int_format = 0; - m_int_type = 0; - m_int_shift = 2; // 4 bytes by texels - break; - case Format::Invalid: m_int_format = 0; m_int_type = 0; @@ -254,8 +247,6 @@ GSTextureOGL::GSTextureOGL(Type type, int w, int h, Format format, GLuint fbo_re switch (m_type) { - case Type::Backbuffer: - return; // backbuffer isn't a real texture case Type::Texture: // Only 32 bits input texture will be supported for mipmap m_max_layer = mipmap && m_format == Format::Color ? (int)log2(std::max(w, h)) : 1; @@ -279,7 +270,6 @@ GSTextureOGL::GSTextureOGL(Type type, int w, int h, Format format, GLuint fbo_re case Format::Color: case Format::UInt32: case Format::Int32: - case Format::Backbuffer: m_sparse &= GLLoader::found_compatible_GL_ARB_sparse_texture2; SetGpuPageSize(GSVector2i(127, 127)); break; @@ -357,6 +347,11 @@ GSTextureOGL::~GSTextureOGL() GLState::available_vram += m_mem_usage; } +void* GSTextureOGL::GetNativeHandle() const +{ + return reinterpret_cast(static_cast(m_texture_id)); +} + void GSTextureOGL::Clear(const void* data) { glClearTexImage(m_texture_id, GL_TEX_LEVEL_0, m_int_format, m_int_type, data); @@ -587,11 +582,7 @@ bool GSTextureOGL::Save(const std::string& fn) GSPng::Format fmt = GSPng::RGB_PNG; #endif - if (IsBackbuffer()) - { - glReadPixels(0, 0, m_committed_size.x, m_committed_size.y, GL_RGBA, GL_UNSIGNED_BYTE, image.get()); - } - else if (IsDss()) + if (IsDss()) { glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo_read); diff --git a/pcsx2/GS/Renderers/OpenGL/GSTextureOGL.h b/pcsx2/GS/Renderers/OpenGL/GSTextureOGL.h index 23faa68c0459c..38cb86ff56c29 100644 --- a/pcsx2/GS/Renderers/OpenGL/GSTextureOGL.h +++ b/pcsx2/GS/Renderers/OpenGL/GSTextureOGL.h @@ -63,6 +63,8 @@ class GSTextureOGL final : public GSTexture explicit GSTextureOGL(Type type, int w, int h, Format format, GLuint fbo_read, bool mipmap); virtual ~GSTextureOGL(); + void* GetNativeHandle() const override; + bool Update(const GSVector4i& r, const void* data, int pitch, int layer = 0) final; bool Map(GSMap& m, const GSVector4i* r = NULL, int layer = 0) final; void Unmap() final; @@ -70,7 +72,6 @@ class GSTextureOGL final : public GSTexture bool Save(const std::string& fn) final; GSMap Read(const GSVector4i& r, AlignedBuffer& buffer); - bool IsBackbuffer() { return (m_type == Type::Backbuffer); } bool IsDss() { return (m_type == Type::DepthStencil || m_type == Type::SparseDepthStencil); } u32 GetID() final { return m_texture_id; } diff --git a/pcsx2/GS/Renderers/SW/GSRendererSW.cpp b/pcsx2/GS/Renderers/SW/GSRendererSW.cpp index 108be0c1fa60f..65d4eda374b3e 100644 --- a/pcsx2/GS/Renderers/SW/GSRendererSW.cpp +++ b/pcsx2/GS/Renderers/SW/GSRendererSW.cpp @@ -27,7 +27,7 @@ CONSTINIT const GSVector8 GSRendererSW::m_pos_scale2 = GSVector8::cxpr(1.0f / 16 #endif GSRendererSW::GSRendererSW(int threads) - : m_fzb(NULL) + : GSRenderer(), m_fzb(NULL) { m_nativeres = true; // ignore ini, sw is always native @@ -91,7 +91,7 @@ void GSRendererSW::Reset() GSRenderer::Reset(); } -void GSRendererSW::VSync(int field) +void GSRendererSW::VSync(u32 field) { Sync(0); // IncAge might delete a cached texture in use @@ -135,16 +135,6 @@ void GSRendererSW::VSync(int field) // if((m_perfmon.GetFrame() & 255) == 0) m_rl->PrintStats(); } -void GSRendererSW::ResetDevice() -{ - for (GSTexture*& tex : m_texture) - { - delete tex; - - tex = NULL; - } -} - GSTexture* GSRendererSW::GetOutput(int i, int& y_offset) { Sync(1); @@ -156,7 +146,7 @@ GSTexture* GSRendererSW::GetOutput(int i, int& y_offset) // TODO: round up bottom - if (m_dev->ResizeTexture(&m_texture[i], w, h)) + if (g_gs_device->ResizeTexture(&m_texture[i], w, h)) { static int pitch = 1024 * 4; @@ -1300,7 +1290,7 @@ bool GSRendererSW::GetScanlineGlobalData(SharedData* data) gd.sel.pabe = 1; } - if (m_aa1 && PRIM->AA1 && (primclass == GS_LINE_CLASS || primclass == GS_TRIANGLE_CLASS)) + if (GSConfig.AA1 && PRIM->AA1 && (primclass == GS_LINE_CLASS || primclass == GS_TRIANGLE_CLASS)) { gd.sel.aa1 = 1; } diff --git a/pcsx2/GS/Renderers/SW/GSRendererSW.h b/pcsx2/GS/Renderers/SW/GSRendererSW.h index 6295b235de66d..d85ac70bf6605 100644 --- a/pcsx2/GS/Renderers/SW/GSRendererSW.h +++ b/pcsx2/GS/Renderers/SW/GSRendererSW.h @@ -79,17 +79,16 @@ class GSRendererSW : public GSRenderer std::atomic m_fzb_pages[512]; // u16 frame/zbuf pages interleaved std::atomic m_tex_pages[512]; - void Reset(); - void VSync(int field); - void ResetDevice(); - GSTexture* GetOutput(int i, int& y_offset); - GSTexture* GetFeedbackOutput(); + void Reset() override; + void VSync(u32 field) override; + GSTexture* GetOutput(int i, int& y_offset) override; + GSTexture* GetFeedbackOutput() override; - void Draw(); + void Draw() override; void Queue(GSRingHeap::SharedPtr& item); void Sync(int reason); - void InvalidateVideoMem(const GIFRegBITBLTBUF& BITBLTBUF, const GSVector4i& r); - void InvalidateLocalMem(const GIFRegBITBLTBUF& BITBLTBUF, const GSVector4i& r, bool clut = false); + void InvalidateVideoMem(const GIFRegBITBLTBUF& BITBLTBUF, const GSVector4i& r) override; + void InvalidateLocalMem(const GIFRegBITBLTBUF& BITBLTBUF, const GSVector4i& r, bool clut = false) override; void UsePages(const GSOffset::PageLooper& pages, const int type); void ReleasePages(const GSOffset::PageLooper& pages, const int type); @@ -101,5 +100,5 @@ class GSRendererSW : public GSRenderer public: GSRendererSW(int threads); - virtual ~GSRendererSW(); + ~GSRendererSW() override; }; diff --git a/pcsx2/GS/Renderers/SW/GSTextureSW.cpp b/pcsx2/GS/Renderers/SW/GSTextureSW.cpp index 36a95e6d2523f..d713ddf41d73c 100644 --- a/pcsx2/GS/Renderers/SW/GSTextureSW.cpp +++ b/pcsx2/GS/Renderers/SW/GSTextureSW.cpp @@ -32,6 +32,11 @@ GSTextureSW::~GSTextureSW() _aligned_free(m_data); } +void* GSTextureSW::GetNativeHandle() const +{ + return nullptr; +} + bool GSTextureSW::Update(const GSVector4i& r, const void* data, int pitch, int layer) { GSMap m; diff --git a/pcsx2/GS/Renderers/SW/GSTextureSW.h b/pcsx2/GS/Renderers/SW/GSTextureSW.h index 96d4ecd008d89..a90616728e978 100644 --- a/pcsx2/GS/Renderers/SW/GSTextureSW.h +++ b/pcsx2/GS/Renderers/SW/GSTextureSW.h @@ -33,4 +33,5 @@ class GSTextureSW final : public GSTexture bool Map(GSMap& m, const GSVector4i* r = NULL, int layer = 0) override; void Unmap() override; bool Save(const std::string& fn) override; + void* GetNativeHandle() const override; }; diff --git a/pcsx2/GS/Window/GSSetting.cpp b/pcsx2/GS/Window/GSSetting.cpp index 4e810e2da0e9b..49a4e8e729dff 100644 --- a/pcsx2/GS/Window/GSSetting.cpp +++ b/pcsx2/GS/Window/GSSetting.cpp @@ -85,8 +85,6 @@ const char* dialog_message(int ID, bool* updateText) " 0500 0500, fixes Persona 3 minimap, helps Haunting Ground."); case IDC_OSD_LOG: return cvtString("Prints log messages from the Function keys onscreen."); - case IDC_OSD_MONITOR: - return cvtString("Continuously prints/overlays the FPS counter and the EE ('CPU-usage') ,\nGS ('GPU-usage') and VU(if the MTVU speedhack is enabled) percentages onscreen."); case IDC_PALTEX: return cvtString("Enabled: GPU converts colormap-textures.\n" "Disabled: CPU converts colormap-textures.\n\n" @@ -167,10 +165,6 @@ const char* dialog_message(int ID, bool* updateText) case IDC_SPARSE_TEXTURE: return cvtString("Allows to reduce VRAM usage on the GPU.\n\n" "Note: Feature is currently experimental and works only on Nvidia GPUs."); - case IDC_OSD_MAX_LOG_EDIT: - case IDC_OSD_MAX_LOG: - return cvtString("Sets the maximum number of log messages on the screen or in the buffer at the same time.\n\n" - "The maximum number of messages visible on the screen at the same time also depends on the character size."); case IDC_LINEAR_PRESENT: return cvtString("Use bilinear filtering when Upscaling/Downscaling the image to the screen. Disable it if you want a sharper/pixelated output."); // Exclusive for Hardware Renderer diff --git a/pcsx2/GS/Window/GSwxDialog.cpp b/pcsx2/GS/Window/GSwxDialog.cpp index dd32ca5c09fad..5a8b1293ca575 100644 --- a/pcsx2/GS/Window/GSwxDialog.cpp +++ b/pcsx2/GS/Window/GSwxDialog.cpp @@ -17,6 +17,11 @@ #include "GSwxDialog.h" #include "gui/AppConfig.h" #include "GS/GSUtil.h" +#include "HostDisplay.h" + +#ifdef _WIN32 +#include "Frontend/D3D11HostDisplay.h" +#endif using namespace GSSettingsDialog; @@ -317,7 +322,7 @@ RendererTab::RendererTab(wxWindow* parent) void RendererTab::UpdateBlendMode(GSRendererType renderer) { #ifdef _WIN32 - if (renderer == GSRendererType::DX1011_HW) + if (renderer == GSRendererType::DX11) { m_blend_mode_d3d11.first ->Show(); m_blend_mode_d3d11.second->Show(); @@ -510,30 +515,25 @@ OSDTab::OSDTab(wxWindow* parent) const int space = wxSizerFlags().Border().GetBorderInPixels(); PaddedBoxSizer tab_box(wxVERTICAL); - CheckboxPrereq monitor_check(m_ui.addCheckBox(tab_box.inner, "Enable Monitor", "osd_monitor_enabled", IDC_OSD_MONITOR)); - - PaddedBoxSizer font_box(wxVERTICAL, this, "Font"); + PaddedBoxSizer font_box(wxVERTICAL, this, "Visuals"); auto* font_grid = new wxFlexGridSizer(2, space, space); font_grid->AddGrowableCol(1); - m_ui.addSpinAndLabel(font_grid, "Size:", "osd_fontsize", 1, 100, 25, -1, monitor_check); - - m_ui.addSliderAndLabel(font_grid, "Red:", "osd_color_r", 0, 255, 0, -1, monitor_check); - m_ui.addSliderAndLabel(font_grid, "Green:", "osd_color_g", 0, 255, 0, -1, monitor_check); - m_ui.addSliderAndLabel(font_grid, "Blue:", "osd_color_b", 0, 255, 0, -1, monitor_check); - m_ui.addSliderAndLabel(font_grid, "Opacity:", "osd_color_opacity", 0, 100, 100, -1, monitor_check); + m_ui.addSliderAndLabel(font_grid, "Scale:", "OsdScale", 50, 300, 100, -1); font_box->Add(font_grid, wxSizerFlags().Expand()); tab_box->Add(font_box.outer, wxSizerFlags().Expand()); - CheckboxPrereq log_check(m_ui.addCheckBox(tab_box.inner, "Enable Log", "osd_log_enabled", IDC_OSD_LOG)); - PaddedBoxSizer log_box(wxVERTICAL, this, "Log Messages"); auto* log_grid = new wxFlexGridSizer(2, space, space); log_grid->AddGrowableCol(1); - m_ui.addSpinAndLabel(log_grid, "Timeout (seconds):", "osd_log_timeout", 2, 10, 4, -1, log_check); - m_ui.addSpinAndLabel(log_grid, "Max On-Screen Messages:", "osd_max_log_messages", 1, 10, 2, IDC_OSD_MAX_LOG, log_check); + m_ui.addCheckBox(log_grid, "Show Messages", "OsdShowMessages", -1); + m_ui.addCheckBox(log_grid, "Show Speed", "OsdShowSpeed", -1); + m_ui.addCheckBox(log_grid, "Show FPS", "OsdShowFPS", -1); + m_ui.addCheckBox(log_grid, "Show CPU Usage", "OsdShowCPU", -1); + m_ui.addCheckBox(log_grid, "Show Resolution", "OsdShowResolution", -1); + m_ui.addCheckBox(log_grid, "Show Statistics", "OsdShowGSStats", -1); log_box->Add(log_grid, wxSizerFlags().Expand()); tab_box->Add(log_box.outer, wxSizerFlags().Expand()); @@ -554,11 +554,9 @@ DebugTab::DebugTab(wxWindow* parent) { PaddedBoxSizer debug_box(wxVERTICAL, this, "Debug"); auto* debug_check_box = new wxWrapSizer(wxHORIZONTAL); + m_ui.addCheckBox(debug_check_box, "Use Blit Swap Chain", "UseBlitSwapChain"); m_ui.addCheckBox(debug_check_box, "Disable Shader Cache", "disable_shader_cache"); - m_ui.addCheckBox(debug_check_box, "Print GL error", "debug_opengl"); -#ifdef _WIN32 - m_ui.addCheckBox(debug_check_box, "D3D Debug Layer", "debug_d3d"); -#endif + m_ui.addCheckBox(debug_check_box, "Use Debug Device", "UseDebugDevice"); m_ui.addCheckBox(debug_check_box, "Dump GS data", "dump"); auto* debug_save_check_box = new wxWrapSizer(wxHORIZONTAL); @@ -667,43 +665,47 @@ void Dialog::OnRendererChange(wxCommandEvent&) GSRendererType Dialog::GetSelectedRendererType() { - int index = m_renderer_select->GetSelection(); + const int index = m_renderer_select->GetSelection(); // there is no currently selected renderer or the combo box has more entries than the renderer list or the current selection is negative // make sure you haven't made a mistake initializing everything ASSERT(index < static_cast(theApp.m_gs_renderers.size()) || index >= 0); - const GSRendererType type = static_cast( - theApp.m_gs_renderers[index].value - ); - - return type; + const GSRendererType type = static_cast(theApp.m_gs_renderers[index].value); + return (type == GSRendererType::Auto) ? GSUtil::GetPreferredRenderer() : type; } void Dialog::RendererChange() { - GSRendererType renderer = GetSelectedRendererType(); - std::string current; - int current_sel = m_adapter_select->GetSelection(); - if (current_sel >= 0 && current_sel < static_cast(m_adapter_arr_string.Count())) - current = m_adapter_arr_string[current_sel].ToUTF8(); - bool explicitly_selected_default = m_adapter_arr_string.Count() > 1 && current_sel == 0; - - std::vector adapters = GSUtil::GetAdapterList(renderer); - - m_adapter_arr_string.Clear(); - m_adapter_arr_string.Add(_("Default Adapter")); - int new_sel = theApp.GetConfigI("adapter_index") + 1; - if (new_sel < 0 || new_sel >= static_cast(adapters.size() + 1) || explicitly_selected_default) - new_sel = 0; - for (std::string& adapter : adapters) + const GSRendererType renderer = GetSelectedRendererType(); + const std::string current_adapter(theApp.GetConfigS("Adapter")); + + HostDisplay::AdapterAndModeList list; + switch (renderer) + { +#ifdef _WIN32 + case GSRendererType::DX11: + list = D3D11HostDisplay::StaticGetAdapterAndModeList(); + break; +#endif + default: + break; + } + + m_adapter_select->Clear(); + m_adapter_select->Insert(_("Default Adapter"), 0); + if (current_adapter.empty()) + m_adapter_select->SetSelection(0); + + for (const std::string& name : list.adapter_names) { - if (adapter == current) - new_sel = m_adapter_arr_string.Count(); - m_adapter_arr_string.Add(fromUTF8(adapter)); + m_adapter_select->Insert(fromUTF8(name), m_adapter_select->GetCount()); + if (current_adapter == name) + m_adapter_select->SetSelection(m_adapter_select->GetCount() - 1); } - m_adapter_select->Set(m_adapter_arr_string); - m_adapter_select->SetSelection(new_sel); + + m_adapter_select->Enable(!list.adapter_names.empty()); + #ifdef _WIN32 m_renderer_panel->UpdateBlendMode(renderer); @@ -714,9 +716,8 @@ void Dialog::RendererChange() void Dialog::Load() { m_ui.Load(); - GSRendererType renderer = GSRendererType(theApp.GetConfigI("Renderer")); - if (renderer == GSRendererType::Undefined) - renderer = GSUtil::GetPreferredRenderer(); + + const GSRendererType renderer = GSRendererType(theApp.GetConfigI("Renderer")); m_renderer_select->SetSelection(get_config_index(theApp.m_gs_renderers, static_cast(renderer))); RendererChange(); @@ -735,7 +736,7 @@ void Dialog::Save() // only save the adapter when it makes sense to // prevents changing the adapter, switching to another renderer and saving if (m_adapter_select->GetCount() > 1) // First option is system default - theApp.SetConfig("adapter_index", m_adapter_select->GetSelection() - 1); + theApp.SetConfig("Adapter", m_adapter_select->GetStringSelection().c_str()); m_hacks_panel->Save(); m_renderer_panel->Save(); @@ -762,14 +763,14 @@ void Dialog::Update() else { // cross-tab dependencies yay - const bool is_hw = renderer == GSRendererType::OGL_HW || renderer == GSRendererType::DX1011_HW; + const bool is_hw = renderer == GSRendererType::OGL || renderer == GSRendererType::DX11; const bool is_upscale = m_renderer_panel->m_internal_resolution->GetSelection() != 0; m_hacks_panel->m_is_native_res = !is_hw || !is_upscale; m_hacks_panel->m_is_hardware = is_hw; - m_hacks_panel->m_is_ogl_hw = renderer == GSRendererType::OGL_HW; + m_hacks_panel->m_is_ogl_hw = renderer == GSRendererType::OGL; m_renderer_panel->m_is_hardware = is_hw; m_renderer_panel->m_is_native_res = !is_hw || !is_upscale; - m_debug_panel->m_is_ogl_hw = renderer == GSRendererType::OGL_HW; + m_debug_panel->m_is_ogl_hw = renderer == GSRendererType::OGL; m_ui.Update(); m_hacks_panel->DoUpdate(); diff --git a/pcsx2/GS/Window/GSwxDialog.h b/pcsx2/GS/Window/GSwxDialog.h index 572ed8f081e52..d4f4905241534 100644 --- a/pcsx2/GS/Window/GSwxDialog.h +++ b/pcsx2/GS/Window/GSwxDialog.h @@ -187,7 +187,6 @@ namespace GSSettingsDialog wxChoice* m_renderer_select; wxChoice* m_adapter_select; wxChoice* m_bifilter_select; - wxArrayString m_adapter_arr_string; RendererTab* m_renderer_panel; HacksTab* m_hacks_panel; DebugTab* m_debug_panel; diff --git a/pcsx2/Host.h b/pcsx2/Host.h index 46fa17eeacd98..33028e4a1211f 100644 --- a/pcsx2/Host.h +++ b/pcsx2/Host.h @@ -28,7 +28,12 @@ struct HostKeyEvent { NoEvent = 0, KeyPressed = 1, - KeyReleased = 2 + KeyReleased = 2, + MousePressed = 3, + MouseReleased = 4, + MouseWheelDown = 5, + MouseWheelUp = 6, + MouseMove = 7, }; Type type; @@ -44,6 +49,14 @@ namespace Host /// Reads a resource file file from the resources directory as a string. std::optional ReadResourceFileToString(const char* filename); + /// Adds OSD messages, duration is in seconds. + void AddOSDMessage(std::string message, float duration = 2.0f); + void AddKeyedOSDMessage(std::string key, std::string message, float duration = 2.0f); + void AddFormattedOSDMessage(float duration, const char* format, ...); + void AddKeyedFormattedOSDMessage(std::string key, float duration, const char* format, ...); + void RemoveKeyedOSDMessage(std::string key); + void ClearOSDMessages(); + /// Displays an asynchronous error on the UI thread, i.e. doesn't block the caller. void ReportErrorAsync(const std::string_view& title, const std::string_view& message); void ReportFormattedErrorAsync(const std::string_view& title, const char* format, ...); diff --git a/pcsx2/HostDisplay.cpp b/pcsx2/HostDisplay.cpp new file mode 100644 index 0000000000000..726274d2d44ca --- /dev/null +++ b/pcsx2/HostDisplay.cpp @@ -0,0 +1,131 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2021 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "HostDisplay.h" +#include "common/Assertions.h" +#include "common/Console.h" +#include "common/StringUtil.h" +#include +#include +#include +#include +#include +#include + +HostDisplayTexture::~HostDisplayTexture() = default; + +HostDisplay::~HostDisplay() = default; + +const char* HostDisplay::RenderAPIToString(RenderAPI api) +{ + static const char* names[] = {"None", "D3D11", "Vulkan", "OpenGL", "OpenGLES"}; + return (static_cast(api) >= std::size(names)) ? names[0] : names[static_cast(api)]; +} + +bool HostDisplay::UsesLowerLeftOrigin() const +{ + const RenderAPI api = GetRenderAPI(); + return (api == RenderAPI::OpenGL || api == RenderAPI::OpenGLES); +} + +bool HostDisplay::GetHostRefreshRate(float* refresh_rate) +{ + if (m_window_info.surface_refresh_rate > 0.0f) + { + *refresh_rate = m_window_info.surface_refresh_rate; + return true; + } + + return WindowInfo::QueryRefreshRateForWindow(m_window_info, refresh_rate); +} + +bool HostDisplay::ParseFullscreenMode(const std::string_view& mode, u32* width, u32* height, float* refresh_rate) +{ + if (!mode.empty()) + { + std::string_view::size_type sep1 = mode.find('x'); + if (sep1 != std::string_view::npos) + { + std::optional owidth = StringUtil::FromChars(mode.substr(0, sep1)); + sep1++; + + while (sep1 < mode.length() && std::isspace(mode[sep1])) + sep1++; + + if (owidth.has_value() && sep1 < mode.length()) + { + std::string_view::size_type sep2 = mode.find('@', sep1); + if (sep2 != std::string_view::npos) + { + std::optional oheight = StringUtil::FromChars(mode.substr(sep1, sep2 - sep1)); + sep2++; + + while (sep2 < mode.length() && std::isspace(mode[sep2])) + sep2++; + + if (oheight.has_value() && sep2 < mode.length()) + { + std::optional orefresh_rate = StringUtil::FromChars(mode.substr(sep2)); + if (orefresh_rate.has_value()) + { + *width = owidth.value(); + *height = oheight.value(); + *refresh_rate = orefresh_rate.value(); + return true; + } + } + } + } + } + } + + *width = 0; + *height = 0; + *refresh_rate = 0; + return false; +} + +std::string HostDisplay::GetFullscreenModeString(u32 width, u32 height, float refresh_rate) +{ + return StringUtil::StdStringFromFormat("%u x %u @ %f hz", width, height, refresh_rate); +} + +#include "Frontend/OpenGLHostDisplay.h" + +#ifdef _WIN32 +#include "Frontend/D3D11HostDisplay.h" +#endif + +std::unique_ptr HostDisplay::CreateDisplayForAPI(RenderAPI api) +{ + switch (api) + { +#ifdef _WIN32 + case HostDisplay::RenderAPI::D3D11: + return std::make_unique(); +#endif + + case HostDisplay::RenderAPI::OpenGL: + case HostDisplay::RenderAPI::OpenGLES: + return std::make_unique(); + + default: + Console.Error("Unknown render API %u", static_cast(api)); + return {}; + } +} + diff --git a/pcsx2/HostDisplay.h b/pcsx2/HostDisplay.h new file mode 100644 index 0000000000000..d6559c062605a --- /dev/null +++ b/pcsx2/HostDisplay.h @@ -0,0 +1,172 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2021 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once + +#include "common/Pcsx2Defs.h" +#include "common/WindowInfo.h" + +#include +#include +#include +#include +#include + +#include "Host.h" +#include "Config.h" + +/// An abstracted RGBA8 texture. +class HostDisplayTexture +{ +public: + virtual ~HostDisplayTexture(); + + virtual void* GetHandle() const = 0; + virtual u32 GetWidth() const = 0; + virtual u32 GetHeight() const = 0; + virtual u32 GetLayers() const = 0; + virtual u32 GetLevels() const = 0; +}; + +/// Interface to the frontend's renderer. +class HostDisplay +{ +public: + enum class RenderAPI + { + None, + D3D11, + Vulkan, + OpenGL, + OpenGLES + }; + + enum class Alignment + { + LeftOrTop, + Center, + RightOrBottom + }; + + struct AdapterAndModeList + { + std::vector adapter_names; + std::vector fullscreen_modes; + }; + + virtual ~HostDisplay(); + + /// Returns a string representing the specified API. + static const char* RenderAPIToString(RenderAPI api); + + /// Creates a display for the specified API. + static std::unique_ptr CreateDisplayForAPI(RenderAPI api); + + /// Parses a fullscreen mode into its components (width * height @ refresh hz) + static bool ParseFullscreenMode(const std::string_view& mode, u32* width, u32* height, float* refresh_rate); + + /// Converts a fullscreen mode to a string. + static std::string GetFullscreenModeString(u32 width, u32 height, float refresh_rate); + + __fi const WindowInfo& GetWindowInfo() const { return m_window_info; } + __fi s32 GetWindowWidth() const { return static_cast(m_window_info.surface_width); } + __fi s32 GetWindowHeight() const { return static_cast(m_window_info.surface_height); } + __fi float GetWindowScale() const { return m_window_info.surface_scale; } + + /// Changes the alignment for this display (screen positioning). + __fi Alignment GetDisplayAlignment() const { return m_display_alignment; } + __fi void SetDisplayAlignment(Alignment alignment) { m_display_alignment = alignment; } + + virtual RenderAPI GetRenderAPI() const = 0; + virtual void* GetRenderDevice() const = 0; + virtual void* GetRenderContext() const = 0; + virtual void* GetRenderSurface() const = 0; + + virtual bool HasRenderDevice() const = 0; + virtual bool HasRenderSurface() const = 0; + + virtual bool CreateRenderDevice(const WindowInfo& wi, std::string_view adapter_name, bool debug_device) = 0; + virtual bool InitializeRenderDevice(std::string_view shader_cache_directory, bool debug_device) = 0; + virtual bool MakeRenderContextCurrent() = 0; + virtual bool DoneRenderContextCurrent() = 0; + virtual void DestroyRenderDevice() = 0; + virtual void DestroyRenderSurface() = 0; + virtual bool ChangeRenderWindow(const WindowInfo& wi) = 0; + virtual bool SupportsFullscreen() const = 0; + virtual bool IsFullscreen() = 0; + virtual bool SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) = 0; + virtual AdapterAndModeList GetAdapterAndModeList() = 0; + + /// Call when the window size changes externally to recreate any resources. + virtual void ResizeRenderWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) = 0; + + /// Creates an abstracted RGBA8 texture. If dynamic, the texture can be updated with UpdateTexture() below. + virtual std::unique_ptr CreateTexture(u32 width, u32 height, u32 layers, u32 levels, const void* data, + u32 data_stride, bool dynamic = false) = 0; + virtual void UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* data, + u32 data_stride) = 0; + + /// Returns false if the window was completely occluded. If frame_skip is set, the frame won't be + /// displayed, but the GPU command queue will still be flushed. + virtual bool BeginPresent(bool frame_skip) = 0; + + /// Presents the frame to the display, and renders OSD elements. + virtual void EndPresent() = 0; + + /// Changes vsync mode for this display. + virtual void SetVSync(VsyncMode mode) = 0; + + /// ImGui context management, usually called by derived classes. + virtual bool CreateImGuiContext() = 0; + virtual void DestroyImGuiContext() = 0; + virtual bool UpdateImGuiFontTexture() = 0; + + /// Returns the effective refresh rate of this display. + virtual bool GetHostRefreshRate(float* refresh_rate); + + /// Returns true if it's an OpenGL-based renderer. + bool UsesLowerLeftOrigin() const; + +protected: + WindowInfo m_window_info; + Alignment m_display_alignment = Alignment::Center; +}; + +namespace Host +{ + /// Creates the host display. This may create a new window. The API used depends on the current configuration. + HostDisplay* AcquireHostDisplay(HostDisplay::RenderAPI api); + + /// Destroys the host display. This may close the display window. + void ReleaseHostDisplay(); + + /// Returns a pointer to the current host display abstraction. Assumes AcquireHostDisplay() has been caled. + HostDisplay* GetHostDisplay(); + + /// Returns false if the window was completely occluded. If frame_skip is set, the frame won't be + /// displayed, but the GPU command queue will still be flushed. + bool BeginPresentFrame(bool frame_skip); + + /// Presents the frame to the display, and renders OSD elements. + void EndPresentFrame(); + + /// Called on the MTGS thread when a resize request is received. + void ResizeHostDisplay(u32 new_window_width, u32 new_window_height, float new_window_scale); + + /// Called on the MTGS thread when a request to update the display is received. + /// This could be a fullscreen transition, for example. + void UpdateHostDisplay(); +} // namespace Host + diff --git a/pcsx2/MTGS.cpp b/pcsx2/MTGS.cpp index 79c4451236e04..bbd0348fe41c5 100644 --- a/pcsx2/MTGS.cpp +++ b/pcsx2/MTGS.cpp @@ -19,15 +19,19 @@ #include #include +#include "common/StringUtil.h" + #include "GS.h" #include "Gif_Unit.h" #include "MTVU.h" #include "Elfheader.h" -#include "PerformanceMetrics.h" -#include "gui/Dialogs/ModalPopups.h" -#include "common/WindowInfo.h" -extern WindowInfo g_gs_window_info; +#include "Host.h" +#include "HostDisplay.h" + +#ifndef PCSX2_CORE +#include "gui/Dialogs/ModalPopups.h" +#endif // Uncomment this to enable profiling of the GS RingBufferCopy function. //#define PCSX2_GSRING_SAMPLING_STATS @@ -48,8 +52,6 @@ using namespace Threading; // ===================================================================================================== alignas(32) MTGS_BufferedData RingBuffer; -extern bool renderswitch; -std::atomic_bool init_gspanel = true; #ifdef RINGBUF_DEBUG_STACK @@ -68,42 +70,6 @@ SysMtgsThread::SysMtgsThread() // All other state vars are initialized by OnStart(). } -typedef void (SysMtgsThread::*FnPtr_MtgsThreadMethod)(); - -class SysExecEvent_InvokeMtgsThreadMethod : public SysExecEvent -{ -protected: - FnPtr_MtgsThreadMethod m_method; - bool m_IsCritical; - -public: - wxString GetEventName() const { return L"MtgsThreadMethod"; } - virtual ~SysExecEvent_InvokeMtgsThreadMethod() = default; - SysExecEvent_InvokeMtgsThreadMethod* Clone() const { return new SysExecEvent_InvokeMtgsThreadMethod(*this); } - - bool AllowCancelOnExit() const { return false; } - bool IsCriticalEvent() const { return m_IsCritical; } - - SysExecEvent_InvokeMtgsThreadMethod(FnPtr_MtgsThreadMethod method, bool critical = false) - { - m_method = method; - m_IsCritical = critical; - } - - SysExecEvent_InvokeMtgsThreadMethod& Critical() - { - m_IsCritical = true; - return *this; - } - -protected: - void InvokeEvent() - { - if (m_method) - (mtgsThread.*m_method)(); - } -}; - void SysMtgsThread::OnStart() { m_Opened = false; @@ -135,7 +101,6 @@ SysMtgsThread::~SysMtgsThread() void SysMtgsThread::OnResumeReady() { - PerformanceMetrics::Reset(); m_sem_OpenDone.Reset(); } @@ -235,19 +200,17 @@ void SysMtgsThread::OpenGS() if (m_Opened) return; - if (init_gspanel) - sApp.OpenGsPanel(); - memcpy(RingBuffer.Regs, PS2MEM_GS, sizeof(PS2MEM_GS)); - GSsetBaseMem(RingBuffer.Regs); - - pxAssertMsg((GSopen2(g_gs_window_info, 1 | (renderswitch ? 4 : 0)) == 0), "GS failed to open!"); - - GSsetVsync(EmuConfig.GS.GetVsync()); - m_Opened = true; + m_Opened = GSopen(EmuConfig.GS, EmuConfig.GS.Renderer, RingBuffer.Regs); m_sem_OpenDone.Post(); + if (!m_Opened) + { + Console.Error("GS failed to open"); + return; + } + GSsetGameCRC(ElfCRC, 0); } @@ -310,7 +273,7 @@ void SysMtgsThread::ExecuteTaskInThread() // is very optimized (only 1 instruction test in most cases), so no point in trying // to avoid it. - m_sem_event.WaitWithoutYield(); + m_sem_event.Wait(); StateCheckInThread(); busy.Acquire(); @@ -480,14 +443,20 @@ void SysMtgsThread::ExecuteTaskInThread() if (m_VsyncSignalListener.exchange(false)) m_sem_Vsync.Post(); - PerformanceMetrics::Update(); - // Do not StateCheckInThread() here // Otherwise we could pause while there's still data in the queue // Which could make the MTVU thread wait forever for it to empty } break; + case GS_RINGTYPE_ASYNC_CALL: + { + AsyncCallType* const func = (AsyncCallType*)tag.pointer; + (*func)(); + delete func; + } + break; + case GS_RINGTYPE_FRAMESKIP: MTGS_LOG("(MTGS Packet Read) ringtype=Frameskip"); _gs_ResetFrameskip(); @@ -586,12 +555,14 @@ void SysMtgsThread::ExecuteTaskInThread() void SysMtgsThread::CloseGS() { - if (!m_Opened || GSDump::isRunning) + if (!m_Opened) return; +#ifndef PCSX2_CORE + if (GSDump::isRunning) + return; +#endif m_Opened = false; GSclose(); - if (init_gspanel) - sApp.CloseGsPanel(); } void SysMtgsThread::OnSuspendInThread() @@ -893,10 +864,10 @@ void SysMtgsThread::SendGameCRC(u32 crc) SendSimplePacket(GS_RINGTYPE_CRC, crc, 0, 0); } -void SysMtgsThread::WaitForOpen() +bool SysMtgsThread::WaitForOpen() { if (m_Opened) - return; + return true; Resume(); // Two-phase timeout on MTGS opening, so that possible errors are handled @@ -904,6 +875,7 @@ void SysMtgsThread::WaitForOpen() // another 12 seconds if no errors occurred (this might seem long, but sometimes our // GS can be very stubborned, especially in debug mode builds). +#ifndef PCSX2_CORE if (!m_sem_OpenDone.Wait(wxTimeSpan(0, 0, 2, 0))) { RethrowException(); @@ -917,6 +889,16 @@ void SysMtgsThread::WaitForOpen() } RethrowException(); + return m_Opened; +#else + if (!m_sem_OpenDone.Wait(wxTimeSpan(0, 0, 12, 0)) || !m_Opened) + { + Suspend(false); + return false; + } + + return true; +#endif } void SysMtgsThread::Freeze(FreezeAction mode, MTGS_FreezeData& data) @@ -932,3 +914,89 @@ void SysMtgsThread::Freeze(FreezeAction mode, MTGS_FreezeData& data) WaitForOpen(); WaitGS(); } + +void SysMtgsThread::RunOnGSThread(AsyncCallType func) +{ + SendPointerPacket(GS_RINGTYPE_ASYNC_CALL, 0, new AsyncCallType(std::move(func))); +} + +void SysMtgsThread::ApplySettings() +{ + pxAssertRel(IsOpen(), "MTGS is running"); + + RunOnGSThread([opts = EmuConfig.GS]() { + GSUpdateConfig(opts); + }); +} + +void SysMtgsThread::ResizeDisplayWindow(int width, int height, float scale) +{ + pxAssertRel(IsOpen(), "MTGS is running"); + RunOnGSThread([width, height, scale]() { + GSResetAPIState(); + Host::ResizeHostDisplay(width, height, scale); + GSRestoreAPIState(); + }); +} + +void SysMtgsThread::UpdateDisplayWindow() +{ + pxAssertRel(IsOpen(), "MTGS is running"); + RunOnGSThread([]() { + GSResetAPIState(); + Host::UpdateHostDisplay(); + GSRestoreAPIState(); + }); +} + +void SysMtgsThread::SetVSync(VsyncMode mode) +{ + pxAssertRel(IsOpen(), "MTGS is running"); + + RunOnGSThread([mode]() { + Host::GetHostDisplay()->SetVSync(mode); + }); +} + +void SysMtgsThread::SwitchRenderer(GSRendererType renderer, bool display_message /* = true */) +{ + pxAssertRel(IsOpen(), "MTGS is running"); + + if (display_message) + { + Host::AddKeyedFormattedOSDMessage("SwitchRenderer", 10.0f, "Switching to %s renderer...", + Pcsx2Config::GSOptions::GetRendererName(renderer)); + } + + RunOnGSThread([renderer]() { + GSSwitchRenderer(renderer); + }); +} + +void SysMtgsThread::SetSoftwareRendering(bool software, bool display_message /* = true */) +{ + // for hardware, use the chosen api in the base config, or auto if base is set to sw + GSRendererType new_renderer; + if (!software) + new_renderer = EmuConfig.GS.UseHardwareRenderer() ? EmuConfig.GS.Renderer : GSRendererType::Auto; + else + new_renderer = GSRendererType::SW; + + SwitchRenderer(new_renderer, display_message); +} + +void SysMtgsThread::ToggleSoftwareRendering() +{ + // reading from the GS thread.. but should be okay here + SetSoftwareRendering(GSConfig.Renderer != GSRendererType::SW); +} + +bool SysMtgsThread::SaveMemorySnapshot(u32 width, u32 height, std::vector* pixels) +{ + bool result = false; + RunOnGSThread([width, height, pixels, &result]() { + result = GSSaveSnapshotToMemory(width, height, pixels); + }); + WaitGS(false, false, false); + return result; +} diff --git a/pcsx2/MemoryCardFile.cpp b/pcsx2/MemoryCardFile.cpp index 8e2ab86c6cdc1..a3e1d0237f279 100644 --- a/pcsx2/MemoryCardFile.cpp +++ b/pcsx2/MemoryCardFile.cpp @@ -29,11 +29,10 @@ struct Component_FileMcd; #include "System.h" #include "Config.h" +#include "Host.h" #include "svnrev.h" -#include "gui/ConsoleLogger.h" - #include #include @@ -526,7 +525,7 @@ s32 FileMemoryCard::Save(uint slot, const u8* src, u32 adr, int size) { wxString name, ext; wxFileName::SplitPath(m_file[slot].GetName(), NULL, NULL, &name, &ext); - OSDlog(Color_StrongYellow, true, "Memory Card %s written.", (const char*)(name + "." + ext).c_str()); + Host::AddOSDMessage(StringUtil::StdStringFromFormat("Memory Card %s written.", (const char*)(name + "." + ext).c_str()), 10.0f); last = std::chrono::system_clock::now(); } return 1; diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp index eba991e7f01f3..69ff91dd35bfe 100644 --- a/pcsx2/Pcsx2Config.cpp +++ b/pcsx2/Pcsx2Config.cpp @@ -22,11 +22,13 @@ #include "common/StringUtil.h" #include "Config.h" #include "GS.h" +#include "HostDisplay.h" #include "CDVD/CDVDaccess.h" #include "MemoryCardFile.h" #ifndef PCSX2_CORE #include "gui/AppConfig.h" +#include "GS/GS.h" #endif namespace EmuFolders @@ -245,6 +247,152 @@ void Pcsx2Config::CpuOptions::LoadSave(SettingsWrapper& wrap) Recompiler.LoadSave(wrap); } +const char* Pcsx2Config::GSOptions::AspectRatioNames[] = { + "Stretch", + "4:3", + "16:9", + nullptr}; + +const char* Pcsx2Config::GSOptions::FMVAspectRatioSwitchNames[] = { + "Off", + "4:3", + "16:9", + nullptr}; + +const char* Pcsx2Config::GSOptions::GetRendererName(GSRendererType type) +{ + switch (type) + { + case GSRendererType::Auto: return "Auto"; + case GSRendererType::DX11: return "Direct3D 11"; + case GSRendererType::OGL: return "OpenGL"; + case GSRendererType::SW: return "Software"; + case GSRendererType::Null: return "Null"; + default: return ""; + } +} + +Pcsx2Config::GSOptions::GSOptions() +{ + bitset = 0; + + IntegerScaling = false; + LinearPresent = true; + UseDebugDevice = false; + UseBlitSwapChain = false; + DisableShaderCache = false; + OsdShowMessages = true; + OsdShowSpeed = false; + OsdShowFPS = false; + OsdShowCPU = false; + OsdShowResolution = false; + OsdShowGSStats = false; + + HWDisableReadbacks = false; + AccurateDATE = true; + GPUPaletteConversion = false; + ConservativeFramebuffer = true; + AutoFlushSW = true; + PreloadFrameWithGSData = false; + WrapGSMem = false; + UserHacks = false; + UserHacks_AlignSpriteX = false; + UserHacks_AutoFlush = false; + UserHacks_CPUFBConversion = false; + UserHacks_DisableDepthSupport = false; + UserHacks_DisablePartialInvalidation = false; + UserHacks_DisableSafeFeatures = false; + UserHacks_MergePPSprite = false; + UserHacks_WildHack = false; + + ShaderFX_Conf = "shaders/GS_FX_Settings.ini"; + ShaderFX_GLSL = "shaders/GS.fx"; +} + +bool Pcsx2Config::GSOptions::operator==(const GSOptions& right) const +{ + return ( + OpEqu(SynchronousMTGS) && + OpEqu(VsyncQueueSize) && + + OpEqu(FrameSkipEnable) && + OpEqu(FrameLimitEnable) && + OpEqu(VsyncEnable) && + + OpEqu(FramesToDraw) && + OpEqu(FramesToSkip) && + + OpEqu(LimitScalar) && + OpEqu(FramerateNTSC) && + OpEqu(FrameratePAL) && + + OpEqu(AspectRatio) && + OpEqu(FMVAspectRatioSwitch) && + + OptionsAreEqual(right) + ); +} + +bool Pcsx2Config::GSOptions::OptionsAreEqual(const GSOptions& right) const +{ + return ( + OpEqu(bitset) && + + OpEqu(InterlaceMode) && + + OpEqu(Zoom) && + OpEqu(StretchY) && + OpEqu(OffsetX) && + OpEqu(OffsetY) && + OpEqu(OsdScale) && + + OpEqu(Renderer) && + OpEqu(UpscaleMultiplier) && + + OpEqu(HWMipmap) && + OpEqu(AccurateBlendingUnit) && + OpEqu(CRCHack) && + OpEqu(TextureFiltering) && + OpEqu(Dithering) && + OpEqu(MaxAnisotropy) && + OpEqu(SWExtraThreads) && + OpEqu(SWExtraThreadsHeight) && + OpEqu(TVShader) && + OpEqu(SkipDraw) && + OpEqu(SkipDrawOffset) && + + OpEqu(UserHacks_HalfBottomOverride) && + OpEqu(UserHacks_HalfPixelOffset) && + OpEqu(UserHacks_RoundSprite) && + OpEqu(UserHacks_TCOffsetX) && + OpEqu(UserHacks_TCOffsetY) && + OpEqu(UserHacks_TriFilter) && + + OpEqu(ShadeBoost_Brightness) && + OpEqu(ShadeBoost_Contrast) && + OpEqu(ShadeBoost_Saturation) && + OpEqu(SaveN) && + OpEqu(SaveL) && + OpEqu(Adapter) && + OpEqu(ShaderFX_Conf) && + OpEqu(ShaderFX_GLSL)); +} + +bool Pcsx2Config::GSOptions::operator!=(const GSOptions& right) const +{ + return !operator==(right); +} + +bool Pcsx2Config::GSOptions::RestartOptionsAreEqual(const GSOptions& right) const +{ + return + OpEqu(Renderer) && + OpEqu(Adapter) && + OpEqu(UseDebugDevice) && + OpEqu(UseBlitSwapChain) && + OpEqu(DisableShaderCache); +} + void Pcsx2Config::GSOptions::LoadSave(SettingsWrapper& wrap) { SettingsWrapSection("EmuCore/GS"); @@ -258,7 +406,7 @@ void Pcsx2Config::GSOptions::LoadSave(SettingsWrapper& wrap) SettingsWrapEntry(FrameSkipEnable); wrap.EnumEntry(CURRENT_SETTINGS_SECTION, "VsyncEnable", VsyncEnable, NULL, VsyncEnable); - SettingsWrapEntry(LimitScalar); + // LimitScalar is set at runtime. SettingsWrapEntry(FramerateNTSC); SettingsWrapEntry(FrameratePAL); @@ -266,48 +414,181 @@ void Pcsx2Config::GSOptions::LoadSave(SettingsWrapper& wrap) SettingsWrapEntry(FramesToSkip); #ifdef PCSX2_CORE - static const char* AspectRatioNames[] = - { - "Stretch", - "4:3", - "16:9", - // WARNING: array must be NULL terminated to compute it size - NULL}; + // These are loaded from GSWindow in wx. + SettingsWrapEnumEx(AspectRatio, "AspectRatio", AspectRatioNames); + SettingsWrapEnumEx(FMVAspectRatioSwitch, "FMVAspectRatioSwitch", FMVAspectRatioSwitchNames); + SettingsWrapEntry(Zoom); + SettingsWrapEntry(StretchY); + SettingsWrapEntry(OffsetX); + SettingsWrapEntry(OffsetY); +#endif - wrap.EnumEntry("AspectRatio", AspectRatio, AspectRatioNames, AspectRatio); +#ifndef PCSX2_CORE + if (wrap.IsLoading()) + ReloadIniSettings(); +#else + LoadSaveIniSettings(wrap); +#endif +} - static const char* FMVAspectRatioSwitchNames[] = - { - "Off", - "4:3", - "16:9", - // WARNING: array must be NULL terminated to compute it size - NULL}; - wrap.EnumEntry("FMVAspectRatioSwitch", FMVAspectRatioSwitch, FMVAspectRatioSwitchNames, FMVAspectRatioSwitch); +#ifdef PCSX2_CORE +void Pcsx2Config::GSOptions::LoadSaveIniSettings(SettingsWrapper& wrap) +{ + SettingsWrapSection("EmuCore/GS"); - SettingsWrapEntry(Zoom); +#define GSSettingInt(var) SettingsWrapBitfield(var) +#define GSSettingIntEx(var, name) SettingsWrapBitfieldEx(var, name) +#define GSSettingBool(var) SettingsWrapBitBool(var) +#define GSSettingBoolEx(var, name) SettingsWrapBitBoolEx(var, name) +#define GSSettingFloat(var) SettingsWrapBitfield(var) +#define GSSettingIntEnumEx(var, name) SettingsWrapIntEnumEx(var, name) +#define GSSettingString(var) SettingsWrapEntry(var) +#define GSSettingStringEx(var, name) SettingsWrapEntryEx(var, name) +#else +void Pcsx2Config::GSOptions::ReloadIniSettings() +{ + // ensure theApp is loaded. + GSinitConfig(); + +#define GSSettingInt(var) var = theApp.GetConfigI(#var) +#define GSSettingIntEx(var, name) var = theApp.GetConfigI(name) +#define GSSettingBool(var) var = theApp.GetConfigB(#var) +#define GSSettingBoolEx(var, name) var = theApp.GetConfigB(name) +#define GSSettingFloat(var) var = static_cast(theApp.GetConfigI(#var)) +#define GSSettingIntEnumEx(var, name) var = static_cast(theApp.GetConfigI(name)) +#define GSSettingString(var) var = theApp.GetConfigS(#var) +#define GSSettingStringEx(var, name) var = theApp.GetConfigS(name) #endif + + // Unfortunately, because code in the GS still reads the setting by key instead of + // using these variables, we need to use the old names. Maybe post 2.0 we can change this. + GSSettingBool(IntegerScaling); + GSSettingBoolEx(LinearPresent, "linear_present"); + GSSettingBool(UseDebugDevice); + GSSettingBool(UseBlitSwapChain); + GSSettingBoolEx(DisableShaderCache, "disable_shader_cache"); + GSSettingBool(OsdShowMessages); + GSSettingBool(OsdShowSpeed); + GSSettingBool(OsdShowFPS); + GSSettingBool(OsdShowCPU); + GSSettingBool(OsdShowResolution); + GSSettingBool(OsdShowGSStats); + + GSSettingBool(HWDisableReadbacks); + GSSettingBoolEx(AccurateDATE, "accurate_date"); + GSSettingBoolEx(GPUPaletteConversion, "paltex"); + GSSettingBoolEx(ConservativeFramebuffer, "conservative_framebuffer"); + GSSettingBoolEx(AutoFlushSW, "autoflush_sw"); + GSSettingBoolEx(PreloadFrameWithGSData, "preload_frame_with_gs_data"); + GSSettingBoolEx(WrapGSMem, "wrap_gs_mem"); + GSSettingBoolEx(Mipmap, "mipmap"); + GSSettingBoolEx(AA1, "aa1"); + GSSettingBoolEx(UserHacks, "UserHacks"); + GSSettingBoolEx(UserHacks_AlignSpriteX, "UserHacks_align_sprite_X"); + GSSettingBoolEx(UserHacks_AutoFlush, "UserHacks_AutoFlush"); + GSSettingBoolEx(UserHacks_CPUFBConversion, "UserHacks_CPU_FB_Conversion"); + GSSettingBoolEx(UserHacks_DisableDepthSupport, "UserHacks_DisableDepthSupport"); + GSSettingBoolEx(UserHacks_DisablePartialInvalidation, "UserHacks_DisablePartialInvalidation"); + GSSettingBoolEx(UserHacks_DisableSafeFeatures, "UserHacks_Disable_Safe_Features"); + GSSettingBoolEx(UserHacks_MergePPSprite, "UserHacks_merge_pp_sprite"); + GSSettingBoolEx(UserHacks_WildHack, "UserHacks_WildHack"); + GSSettingBoolEx(UserHacks_TextureInsideRt, "UserHacks_TextureInsideRt"); + GSSettingBoolEx(FXAA, "fxaa"); + GSSettingBool(ShadeBoost); + GSSettingBoolEx(ShaderFX, "shaderfx"); + GSSettingBoolEx(DumpGSData, "dump"); + GSSettingBoolEx(SaveRT, "save"); + GSSettingBoolEx(SaveFrame, "savef"); + GSSettingBoolEx(SaveTexture, "savet"); + GSSettingBoolEx(SaveDepth, "savez"); + + GSSettingIntEnumEx(InterlaceMode, "interlace"); + + GSSettingFloat(OsdScale); + + GSSettingIntEnumEx(Renderer, "Renderer"); + GSSettingIntEx(UpscaleMultiplier, "upscale_multiplier"); + + GSSettingIntEnumEx(HWMipmap, "mipmap_hw"); + GSSettingIntEnumEx(AccurateBlendingUnit, "accurate_blending_unit"); + GSSettingIntEnumEx(CRCHack, "crc_hack_level"); + GSSettingIntEnumEx(TextureFiltering, "filter"); + GSSettingIntEx(Dithering, "dithering_ps2"); + GSSettingIntEx(MaxAnisotropy, "MaxAnisotropy"); + GSSettingIntEx(SWExtraThreads, "extrathreads"); + GSSettingIntEx(SWExtraThreadsHeight, "extrathreads_height"); + GSSettingIntEx(TVShader, "TVShader"); + GSSettingIntEx(SkipDraw, "UserHacks_SkipDraw"); + GSSettingIntEx(SkipDrawOffset, "UserHacks_SkipDraw_Offset"); + + GSSettingIntEx(UserHacks_HalfBottomOverride, "UserHacks_Half_Bottom_Override"); + GSSettingIntEx(UserHacks_HalfPixelOffset, "UserHacks_HalfPixelOffset"); + GSSettingIntEx(UserHacks_RoundSprite, "UserHacks_round_sprite_offset"); + GSSettingIntEx(UserHacks_TCOffsetX, "UserHacks_TCOffsetX"); + GSSettingIntEx(UserHacks_TCOffsetY, "UserHacks_TCOffsetY"); + GSSettingIntEnumEx(UserHacks_TriFilter, "UserHacks_TriFilter"); + + GSSettingInt(ShadeBoost_Brightness); + GSSettingInt(ShadeBoost_Contrast); + GSSettingInt(ShadeBoost_Saturation); + GSSettingIntEx(SaveN, "saven"); + GSSettingIntEx(SaveL, "savel"); + + GSSettingString(Adapter); + GSSettingStringEx(ShaderFX_Conf, "shaderfx_conf"); + GSSettingStringEx(ShaderFX_GLSL, "shaderfx_glsl"); + +#undef GSSettingInt +#undef GSSettingIntEx +#undef GSSettingBool +#undef GSSettingBoolEx +#undef GSSettingFloat +#undef GSSettingEnumEx +#undef GSSettingIntEnumEx +#undef GSSettingString +#undef GSSettingStringEx } -int Pcsx2Config::GSOptions::GetVsync() const +void Pcsx2Config::GSOptions::MaskUserHacks() { - if (EmuConfig.LimiterMode == LimiterModeType::Turbo || !FrameLimitEnable) - return 0; + if (UserHacks) + return; + + UserHacks_AlignSpriteX = false; + UserHacks_MergePPSprite = false; + UserHacks_DisableSafeFeatures = false; + UserHacks_HalfBottomOverride = -1; + UserHacks_HalfPixelOffset = 0; + UserHacks_RoundSprite = 0; + PreloadFrameWithGSData = false; + UserHacks_DisablePartialInvalidation = false; + UserHacks_DisableDepthSupport = false; + UserHacks_CPUFBConversion = false; + UserHacks_TextureInsideRt = false; + UserHacks_TCOffsetX = 0; + UserHacks_TCOffsetY = 0; + + // in wx, we put trilinear filtering behind user hacks, but not in qt. +#ifndef PCSX2_CORE + UserHacks_TriFilter = TriFiltering::Off; +#endif +} - // D3D only support a boolean state. OpenGL waits a number of vsync - // interrupt (negative value for late vsync). - switch (VsyncEnable) +bool Pcsx2Config::GSOptions::UseHardwareRenderer() const +{ + return (Renderer == GSRendererType::DX11 || Renderer == GSRendererType::OGL); +} + +VsyncMode Pcsx2Config::GetEffectiveVsyncMode() const +{ + if (GS.LimitScalar != 1.0) { - case VsyncMode::Adaptive: - return -1; - case VsyncMode::Off: - return 0; - case VsyncMode::On: - return 1; - - default: - return 0; + Console.WriteLn("Vsync is OFF"); + return VsyncMode::Off; } + + Console.WriteLn("Vsync is %s", GS.VsyncEnable == VsyncMode::Off ? "OFF" : (GS.VsyncEnable == VsyncMode::Adaptive ? "ADAPTIVE" : "ON")); + return GS.VsyncEnable; } Pcsx2Config::SPU2Options::SPU2Options() @@ -348,6 +629,7 @@ static const char* const tbl_GamefixNames[] = "FpuMul", "FpuNegDiv", "GoemonTlb", + "SoftwareRendererFMV", "SkipMPEG", "OPHFlag", "EETiming", @@ -422,6 +704,9 @@ void Pcsx2Config::GamefixOptions::Set(GamefixId id, bool enabled) case Fix_EETiming: EETimingHack = enabled; break; + case Fix_SoftwareRendererFMV: + SoftwareRendererFMVHack = enabled; + break; case Fix_SkipMpeg: SkipMPEGHack = enabled; break; @@ -471,6 +756,8 @@ bool Pcsx2Config::GamefixOptions::Get(GamefixId id) const return XgKickHack; case Fix_EETiming: return EETimingHack; + case Fix_SoftwareRendererFMV: + return SoftwareRendererFMVHack; case Fix_SkipMpeg: return SkipMPEGHack; case Fix_OPHFlag: @@ -505,6 +792,7 @@ void Pcsx2Config::GamefixOptions::LoadSave(SettingsWrapper& wrap) SettingsWrapBitBool(FpuNegDivHack); SettingsWrapBitBool(XgKickHack); SettingsWrapBitBool(EETimingHack); + SettingsWrapBitBool(SoftwareRendererFMVHack); SettingsWrapBitBool(SkipMPEGHack); SettingsWrapBitBool(OPHFlagHack); SettingsWrapBitBool(DMABusyHack); @@ -765,6 +1053,8 @@ void Pcsx2Config::CopyConfig(const Pcsx2Config& cfg) #ifdef __WXMSW__ McdCompressNTFS = cfg.McdCompressNTFS; #endif + + LimiterMode = cfg.LimiterMode; } void EmuFolders::SetDefaults() diff --git a/pcsx2/Recording/Utilities/InputRecordingLogger.cpp b/pcsx2/Recording/Utilities/InputRecordingLogger.cpp index 58e72cda5f8c6..62dfdda395937 100644 --- a/pcsx2/Recording/Utilities/InputRecordingLogger.cpp +++ b/pcsx2/Recording/Utilities/InputRecordingLogger.cpp @@ -22,6 +22,7 @@ #include "GS.h" // GSosdlog #include "gui/App.h" // GetRGBA #include "gui/ConsoleLogger.h" +#include "Host.h" #include @@ -35,7 +36,7 @@ namespace inputRec recordingConLog(fmt::format("[REC]: {}\n", log)); // NOTE - Color is not currently used for OSD logs - GSosdLog(log.c_str(), wxGetApp().GetProgramLog()->GetRGBA(ConsoleColors::Color_StrongMagenta)); + Host::AddOSDMessage(log, 15.0f); } void consoleLog(const std::string& log) diff --git a/pcsx2/System/SysCoreThread.cpp b/pcsx2/System/SysCoreThread.cpp index 59dbaa394653c..c436b6c0e9253 100644 --- a/pcsx2/System/SysCoreThread.cpp +++ b/pcsx2/System/SysCoreThread.cpp @@ -104,8 +104,10 @@ void SysCoreThread::OnStart() void SysCoreThread::OnSuspendInThread() { - TearDownSystems(static_cast(-1)); // All systems - GetMTGS().Suspend(); + // We deliberately don't tear down GS here, because the state isn't saved. + // Which means when it reopens, you'll lose everything in VRAM. Anything + // which needs GS to be torn down should manually save its state. + TearDownSystems(static_cast(-1 & ~System_GS)); // All systems } void SysCoreThread::Start() @@ -183,7 +185,19 @@ void SysCoreThread::ApplySettings(const Pcsx2Config& src) m_resetProfilers = (src.Profiler != EmuConfig.Profiler); m_resetVsyncTimers = (src.GS != EmuConfig.GS); + const bool gs_settings_changed = !src.GS.OptionsAreEqual(EmuConfig.GS); + EmuConfig.CopyConfig(src); + + // handle GS setting changes + if (GetMTGS().IsOpen() && gs_settings_changed) + { + // if by change we reopen the GS, the window handles will invalidate. + // so, we should block here until GS has finished reinitializing, if needed. + Console.WriteLn("Applying GS settings..."); + GetMTGS().ApplySettings(); + GetMTGS().WaitGS(); + } } // -------------------------------------------------------------------------------------- @@ -326,6 +340,7 @@ void SysCoreThread::TearDownSystems(SystemsMask systemsToTearDown) void SysCoreThread::OnResumeInThread(SystemsMask systemsToReinstate) { PerformanceMetrics::SetCPUThreadTimer(Common::ThreadCPUTimer::GetForCallingThread()); + PerformanceMetrics::Reset(); GetMTGS().WaitForOpen(); if (systemsToReinstate & System_DEV9) DEV9open(); diff --git a/pcsx2/gui/App.h b/pcsx2/gui/App.h index 2333c1a860afa..50110f4f633ef 100644 --- a/pcsx2/gui/App.h +++ b/pcsx2/gui/App.h @@ -526,6 +526,7 @@ class Pcsx2App : public wxAppWithHelpers void OpenGsPanel(); void CloseGsPanel(); void OnGsFrameClosed(wxWindowID id); + void OnGsFrameDestroyed(wxWindowID id); void OnMainFrameClosed(wxWindowID id); // -------------------------------------------------------------------------- diff --git a/pcsx2/gui/AppCoreThread.cpp b/pcsx2/gui/AppCoreThread.cpp index af517c4d3770e..44177e01d139d 100644 --- a/pcsx2/gui/AppCoreThread.cpp +++ b/pcsx2/gui/AppCoreThread.cpp @@ -567,9 +567,6 @@ void AppCoreThread::ApplySettings(const Pcsx2Config& src) { _parent::ApplySettings(fixup); } - - if (m_ExecMode >= ExecMode_Paused) - GSsetVsync(EmuConfig.GS.GetVsync()); } // -------------------------------------------------------------------------------------- diff --git a/pcsx2/gui/AppHost.cpp b/pcsx2/gui/AppHost.cpp index 2755670140d01..86e36e0e6faef 100644 --- a/pcsx2/gui/AppHost.cpp +++ b/pcsx2/gui/AppHost.cpp @@ -18,9 +18,26 @@ #include "common/Console.h" #include "common/FileSystem.h" #include "common/Path.h" +#include "common/StringUtil.h" #include "Host.h" #include "HostSettings.h" +#include "HostDisplay.h" +#include "GS/GS.h" + +#include "common/Assertions.h" +#include "Frontend/ImGuiManager.h" +#include "Frontend/OpenGLHostDisplay.h" +#ifdef _WIN32 +#include "Frontend/D3D11HostDisplay.h" +#endif + +#include "gui/App.h" +#include "gui/AppHost.h" +#include "gui/pxEvents.h" + +#include +#include #include "gui/App.h" #include "gui/AppConfig.h" @@ -89,3 +106,104 @@ void Host::ReportErrorAsync(const std::string_view& title, const std::string_vie MsgButtons().OK())); } +static std::unique_ptr s_host_display; + +HostDisplay* Host::AcquireHostDisplay(HostDisplay::RenderAPI api) +{ + sApp.OpenGsPanel(); + + // can't go anywhere if we don't have a window to render into! + if (g_gs_window_info.type == WindowInfo::Type::Surfaceless) + return nullptr; + + s_host_display = HostDisplay::CreateDisplayForAPI(api); + if (!s_host_display) + return nullptr; + + if (!s_host_display->CreateRenderDevice(g_gs_window_info, GSConfig.Adapter, GSConfig.UseDebugDevice) || + !s_host_display->InitializeRenderDevice(StringUtil::wxStringToUTF8String(EmuFolders::Cache.ToString()), GSConfig.UseDebugDevice) || + !ImGuiManager::Initialize()) + { + s_host_display->DestroyRenderDevice(); + s_host_display.reset(); + return nullptr; + } + + return s_host_display.get(); +} + +void Host::ReleaseHostDisplay() +{ + ImGuiManager::Shutdown(); + + if (s_host_display) + { + s_host_display->DestroyRenderDevice(); + s_host_display.reset(); + } + + sApp.CloseGsPanel(); +} + +HostDisplay* Host::GetHostDisplay() +{ + return s_host_display.get(); +} + +bool Host::BeginPresentFrame(bool frame_skip) +{ + CheckForGSWindowResize(); + return s_host_display->BeginPresent(frame_skip); +} + +void Host::EndPresentFrame() +{ + ImGuiManager::RenderOSD(); + s_host_display->EndPresent(); + ImGuiManager::NewFrame(); +} + +void Host::UpdateHostDisplay() +{ + // not used for wx +} + +void Host::ResizeHostDisplay(u32 new_window_width, u32 new_window_height, float new_window_scale) +{ + // not used for wx (except for osd scale changes) + ImGuiManager::WindowResized(); +} + +static std::atomic_bool s_gs_window_resized{false}; +static std::mutex s_gs_window_resized_lock; +static int s_new_gs_window_width = 0; +static int s_new_gs_window_height = 0; + +void Host::GSWindowResized(int width, int height) +{ + std::unique_lock lock(s_gs_window_resized_lock); + s_new_gs_window_width = width; + s_new_gs_window_height = height; + s_gs_window_resized.store(true); +} + +void Host::CheckForGSWindowResize() +{ + if (!s_gs_window_resized.load()) + return; + + int width, height; + { + std::unique_lock lock(s_gs_window_resized_lock); + width = s_new_gs_window_width; + height = s_new_gs_window_height; + s_gs_window_resized.store(false); + } + + if (!s_host_display) + return; + + s_host_display->ResizeRenderWindow(width, height, s_host_display ? s_host_display->GetWindowScale() : 1.0f); + ImGuiManager::WindowResized(); +} + diff --git a/pcsx2/gui/AppHost.h b/pcsx2/gui/AppHost.h new file mode 100644 index 0000000000000..283e3a416f8dc --- /dev/null +++ b/pcsx2/gui/AppHost.h @@ -0,0 +1,12 @@ +#pragma once +#include "Host.h" + +namespace Host +{ + // UI thread + void GSWindowResized(int width, int height); + + // MTGS thread + void CheckForGSWindowResize(); +} // namespace Host + diff --git a/pcsx2/gui/AppMain.cpp b/pcsx2/gui/AppMain.cpp index 1e3a904e3599c..5ea5a43840354 100644 --- a/pcsx2/gui/AppMain.cpp +++ b/pcsx2/gui/AppMain.cpp @@ -361,47 +361,12 @@ wxAppTraits* Pcsx2App::CreateTraits() // LogicalVsync - Event received from the AppCoreThread (EEcore) for each vsync, // roughly 50/60 times a second when frame limiting is enabled, and up to 10,000 // times a second if not (ok, not quite, but you get the idea... I hope.) -extern uint eecount_on_last_vdec; -extern bool FMVstarted; -extern bool EnableFMV; -extern bool renderswitch; - void Pcsx2App::LogicalVsync() { if( AppRpc_TryInvokeAsync( &Pcsx2App::LogicalVsync ) ) return; if( !SysHasValidState() ) return; - // Update / Calculate framerate! - - if (EmuConfig.GS.FMVAspectRatioSwitch != FMVAspectRatioSwitchType::Off) { - if (EnableFMV) { - DevCon.Warning("FMV on"); - - switch (EmuConfig.GS.FMVAspectRatioSwitch) - { - case FMVAspectRatioSwitchType::R4_3: - EmuConfig.CurrentAspectRatio = AspectRatioType::R4_3; - break; - case FMVAspectRatioSwitchType::R16_9: - EmuConfig.CurrentAspectRatio = AspectRatioType::R16_9; - break; - default: - break; - } - EnableFMV = false; - } - - if (FMVstarted) { - int diff = cpuRegs.cycle - eecount_on_last_vdec; - if (diff > 60000000 ) { - DevCon.Warning("FMV off"); - EmuConfig.CurrentAspectRatio = EmuConfig.GS.AspectRatio; - FMVstarted = false; - } - } - } - if( (wxGetApp().GetGsFramePtr() != NULL) ) PADupdate(0); @@ -763,8 +728,6 @@ void Pcsx2App::OpenGsPanel() gsFrame->SetSize( oldsize ); } - pxAssertDev( !gsopen_done, "GS must be closed prior to opening a new Gs Panel!" ); - gsFrame->ShowFullScreen(g_Conf->GSWindow.IsFullscreen); wxApp::ProcessPendingEvents(); @@ -784,10 +747,19 @@ void Pcsx2App::OpenGsPanel() #endif } + void Pcsx2App::CloseGsPanel() { if (AppRpc_TryInvoke(&Pcsx2App::CloseGsPanel)) return; + + GSFrame* gsFrame = GetGsFramePtr(); + if (gsFrame) + { + // we unreference the window first, that way it doesn't try to suspend on close and deadlock + OnGsFrameDestroyed(gsFrame->GetId()); + gsFrame->Destroy(); + } } void Pcsx2App::OnGsFrameClosed(wxWindowID id) @@ -803,6 +775,16 @@ void Pcsx2App::OnGsFrameClosed(wxWindowID id) // right now there's no way to resume from suspend without GUI. PrepForExit(); } +} + +void Pcsx2App::OnGsFrameDestroyed(wxWindowID id) +{ + if ((m_id_GsFrame == wxID_ANY) || (m_id_GsFrame != id)) + return; + + m_id_GsFrame = wxID_ANY; + g_gs_window_info = {}; + #ifndef DISABLE_RECORDING // Disable recording controls that only make sense if the game is running sMainFrame.enableRecordingMenuItem(MenuId_Recording_FrameAdvance, false); diff --git a/pcsx2/gui/ConsoleLogger.cpp b/pcsx2/gui/ConsoleLogger.cpp index eb27a9f9c6960..0e96b6d14f9f2 100644 --- a/pcsx2/gui/ConsoleLogger.cpp +++ b/pcsx2/gui/ConsoleLogger.cpp @@ -18,13 +18,13 @@ #include "MainFrame.h" #include "ConsoleLogger.h" #include "MSWstuff.h" +#include "Host.h" #include "common/Console.h" #include "common/IniInterface.h" #include "common/SafeArray.inl" #include "Dialogs/LogOptionsDialog.h" #include "DebugTools/Debug.h" - #include wxDECLARE_EVENT(pxEvt_SetTitleText, wxCommandEvent); @@ -1266,13 +1266,8 @@ void Pcsx2App::DisableWindowLogging() const void OSDlog(ConsoleColors color, bool console, const std::string& str) { - GSosdLog(str.c_str(), wxGetApp().GetProgramLog()->GetRGBA(color)); + Host::AddOSDMessage(str, 15.0f); if (console) Console.WriteLn(color, str.c_str()); } - -void OSDmonitor(ConsoleColors color, const std::string key, const std::string value) { - GSosdMonitor(key.c_str(), value.c_str(), wxGetApp().GetProgramLog()->GetRGBA(color)); -} - diff --git a/pcsx2/gui/ConsoleLogger.h b/pcsx2/gui/ConsoleLogger.h index b0544a0bd9095..17df62b86c23f 100644 --- a/pcsx2/gui/ConsoleLogger.h +++ b/pcsx2/gui/ConsoleLogger.h @@ -244,5 +244,4 @@ void OSDlog(ConsoleColors color, bool console, const std::string& format, Args . OSDlog(color, console, buf.data()); } -void OSDmonitor(ConsoleColors color, const std::string key, const std::string value); diff --git a/pcsx2/gui/Dialogs/GSDumpDialog.cpp b/pcsx2/gui/Dialogs/GSDumpDialog.cpp index 9a644145585c7..bd20d838be9e4 100644 --- a/pcsx2/gui/Dialogs/GSDumpDialog.cpp +++ b/pcsx2/gui/Dialogs/GSDumpDialog.cpp @@ -24,6 +24,7 @@ #include "common/EmbeddedImage.h" #include "gui/Resources/NoIcon.h" #include "GS.h" +#include "HostDisplay.h" #include "PathDefs.h" #include "gui/AppConfig.h" @@ -199,6 +200,7 @@ void Dialogs::GSDumpDialog::CloseDump(wxCommandEvent& event) m_gif_packet->DeleteAllItems(); m_debug_mode->SetValue(false); m_run->Enable(); + m_settings->Enable(); } // -------------------------------------------------------------------------------------- @@ -219,6 +221,7 @@ void Dialogs::GSDumpDialog::RunDump(wxCommandEvent& event) return; } m_run->Disable(); + m_settings->Disable(); m_debug_mode->Enable(); m_thread->m_renderer = m_renderer_overrides->GetSelection(); m_thread->Start(); @@ -278,6 +281,9 @@ void Dialogs::GSDumpDialog::ToVSync(wxCommandEvent& event) void Dialogs::GSDumpDialog::OpenSettings(wxCommandEvent& event) { GSconfigure(); + + // config has to be reloaded here, otherwise it won't apply when we restart + g_Conf->EmuOptions.GS.ReloadIniSettings(); } void Dialogs::GSDumpDialog::ToStart(wxCommandEvent& event) @@ -654,10 +660,8 @@ void Dialogs::GSDumpDialog::ProcessDumpEvent(const GSData& event, char* regs) case VSync: { GSvsync((*((int*)(regs + 4096)) & 0x2000) > 0 ? (u8)1 : (u8)0); + PerformanceMetrics::Update(); g_FrameCount++; - Pcsx2App* app = (Pcsx2App*)wxApp::GetInstance(); - if (app) - PerformanceMetrics::Update(); break; } case ReadFIFO2: @@ -788,8 +792,7 @@ void Dialogs::GSDumpDialog::GSThread::ExecuteTaskInThread() g_FrameCount = 0; } - GSsetBaseMem((u8*)regs); - if (GSopen2(g_gs_window_info, (renderer_override<<24)) != 0) + if (!GSopen(g_Conf->EmuOptions.GS, static_cast(renderer_override), (u8*)regs)) { OnStop(); return; @@ -801,7 +804,6 @@ void Dialogs::GSDumpDialog::GSThread::ExecuteTaskInThread() GSDump::isRunning = false; GSvsync(1); GSreset(); - GSsetBaseMem((u8*)regs); GSfreeze(FreezeAction::Load, &fd); size_t i = 0; diff --git a/pcsx2/gui/FrameForGS.cpp b/pcsx2/gui/FrameForGS.cpp index 3d1638ca2b49c..18a3fd514c0e8 100644 --- a/pcsx2/gui/FrameForGS.cpp +++ b/pcsx2/gui/FrameForGS.cpp @@ -17,6 +17,7 @@ #include "App.h" #include "GSFrame.h" #include "AppAccelerators.h" +#include "AppHost.h" #include "AppSaveStates.h" #include "Counters.h" #include "GS.h" @@ -25,6 +26,7 @@ #include "MSWstuff.h" #include "PAD/Gamepad.h" #include "PerformanceMetrics.h" +#include "common/StringUtil.h" #include "gui/Dialogs/ModalPopups.h" @@ -59,8 +61,6 @@ static const KeyAcceleratorCode FULLSCREEN_TOGGLE_ACCELERATOR_GSPANEL=KeyAcceleratorCode( WXK_RETURN ).Alt(); -extern std::atomic_bool init_gspanel; - void GSPanel::InitDefaultAccelerators() { // Note: these override GlobalAccels ( Pcsx2App::InitDefaultGlobalAccelerators() ) @@ -211,7 +211,6 @@ GSPanel::GSPanel( wxWindow* parent ) m_CursorShown = false; } - Bind(wxEVT_CLOSE_WINDOW, &GSPanel::OnCloseWindow, this); Bind(wxEVT_SIZE, &GSPanel::OnResize, this); Bind(wxEVT_KEY_UP, &GSPanel::OnKeyDownOrUp, this); Bind(wxEVT_KEY_DOWN, &GSPanel::OnKeyDownOrUp, this); @@ -259,13 +258,40 @@ void GSPanel::DoShowMouse() m_HideMouseTimer.Start( 1750, true ); } +#ifdef _WIN32 +static float GetDpiScaleForWxWindow(wxWindow* window) +{ + static UINT(WINAPI * get_dpi_for_window)(HWND hwnd); + if (!get_dpi_for_window) + { + HMODULE mod = GetModuleHandle(L"user32.dll"); + if (mod) + get_dpi_for_window = reinterpret_cast(GetProcAddress(mod, "GetDpiForWindow")); + } + if (!get_dpi_for_window) + return 1.0f; + + // less than 100% scaling seems unlikely. + const UINT dpi = get_dpi_for_window(window->GetHandle()); + return (dpi > 0) ? std::max(1.0f, static_cast(dpi) / 96.0f) : 1.0f; +} +#endif + std::optional GSPanel::GetWindowInfo() { WindowInfo ret; + const wxSize gs_vp_size(GetClientSize()); + ret.surface_scale = static_cast(GetContentScaleFactor()); + ret.surface_width = static_cast(gs_vp_size.GetWidth()); + ret.surface_height = static_cast(gs_vp_size.GetHeight()); + #if defined(_WIN32) ret.type = WindowInfo::Type::Win32; ret.window_handle = GetHandle(); + + // Windows DPI internally uses the higher pixel count, so work out by how much. + ret.surface_scale = GetDpiScaleForWxWindow(this); #elif defined(__WXGTK__) GtkWidget* child_window = GTK_WIDGET(GetHandle()); @@ -320,20 +346,6 @@ std::optional GSPanel::GetWindowInfo() return std::nullopt; } - const wxSize gs_vp_size(GetClientSize()); - ret.surface_scale = static_cast(GetContentScaleFactor()); - ret.surface_width = static_cast(gs_vp_size.GetWidth()); - ret.surface_height = static_cast(gs_vp_size.GetHeight()); - -#ifdef __WXGTK__ - // GTK seems to not scale coordinates? - if (ret.type == WindowInfo::Type::X11) - { - ret.surface_width = static_cast(ret.surface_width * ret.surface_scale); - ret.surface_height = static_cast(ret.surface_height * ret.surface_scale); - } -#endif - return ret; } @@ -362,18 +374,7 @@ void GSPanel::OnResize(wxSizeEvent& event) g_gs_window_info.surface_height = height; g_gs_window_info.surface_scale = scale; - GSResizeWindow(width, height); -} - -void GSPanel::OnCloseWindow(wxCloseEvent& evt) -{ - // CoreThread pausing calls MTGS suspend which calls GSPanel close on - // the main thread leading to event starvation. This prevents regenerating - // a frame handle when the user closes the window, which prevents this race - // condition. -- govanify - init_gspanel = false; - CoreThread.Suspend(); - evt.Skip(); // and close it. + Host::GSWindowResized(width, height); } void GSPanel::OnMouseEvent( wxMouseEvent& evt ) @@ -722,10 +723,24 @@ GSFrame::GSFrame( const wxString& title) void GSFrame::OnCloseWindow(wxCloseEvent& evt) { - // see GSPanel::OnCloseWindow - init_gspanel = false; + // if a gs dump is running, it cleans up the window once it's hidden. + if (GSDump::isRunning) + { + Hide(); + return; + } + + // but under normal operation, we want to suspend the core thread, which will hide us + // (except if hide-on-escape is enabled, in which case we want to force hide ourself) sApp.OnGsFrameClosed( GetId() ); - Hide(); // and don't close it. + if (!IsShown()) + Hide(); +} + +void GSFrame::OnDestroyWindow(wxWindowDestroyEvent& evt) +{ + sApp.OnGsFrameDestroyed(GetId()); + evt.Skip(); } bool GSFrame::ShowFullScreen(bool show, bool updateConfig) @@ -838,12 +853,6 @@ void GSFrame::AppStatusEvent_OnSettingsApplied() if (!IsFullScreen() && !IsMaximized()) SetClientSize(g_Conf->GSWindow.WindowSize); Refresh(); - - if( g_Conf->GSWindow.CloseOnEsc ) - { - if (IsShown() && !gsopen_done) - Show( false ); - } } GSPanel* GSFrame::GetViewport() @@ -866,19 +875,9 @@ void GSFrame::OnUpdateTitle( wxTimerEvent& evt ) cpuUsage.Write(L" | GS: %3.0f%%", PerformanceMetrics::GetGSThreadUsage()); if (THREAD_VU1) - { cpuUsage.Write(L" | VU: %3.0f%%", PerformanceMetrics::GetVUThreadUsage()); - OSDmonitor(Color_StrongGreen, "VU:", std::to_string(lround(PerformanceMetrics::GetVUThreadUsage())).c_str()); - } - - OSDmonitor(Color_StrongGreen, "EE:", std::to_string(lround(PerformanceMetrics::GetCPUThreadUsage())).c_str()); - OSDmonitor(Color_StrongGreen, "GS:", std::to_string(lround(PerformanceMetrics::GetGSThreadUsage())).c_str()); } - std::ostringstream out; - out << std::fixed << std::setprecision(2) << PerformanceMetrics::GetFPS(); - OSDmonitor(Color_StrongGreen, "FPS:", out.str()); - #ifdef __linux__ // Important Linux note: When the title is set in fullscreen the window is redrawn. Unfortunately // an intermediate white screen appears too which leads to a very annoying flickering. @@ -887,10 +886,6 @@ void GSFrame::OnUpdateTitle( wxTimerEvent& evt ) AppConfig::UiTemplateOptions& templates = g_Conf->Templates; - char gsDest[128]; - gsDest[0] = 0; // No need to set whole array to NULL. - GSgetTitleInfo2( gsDest, sizeof(gsDest) ); - wxString limiterStr = templates.LimiterUnlimited; if( g_Conf->EmuOptions.GS.FrameLimitEnable ) @@ -900,6 +895,7 @@ void GSFrame::OnUpdateTitle( wxTimerEvent& evt ) case LimiterModeType::Nominal: limiterStr = templates.LimiterNormal; break; case LimiterModeType::Turbo: limiterStr = templates.LimiterTurbo; break; case LimiterModeType::Slomo: limiterStr = templates.LimiterSlowmo; break; + case LimiterModeType::Unlimited: limiterStr = templates.LimiterUnlimited; break; } } @@ -922,6 +918,9 @@ void GSFrame::OnUpdateTitle( wxTimerEvent& evt ) wxString title = templates.TitleTemplate; #endif + std::string gsStats; + GSgetTitleStats(gsStats); + title.Replace(L"${slot}", pxsFmt(L"%d", States_GetCurrentSlot())); title.Replace(L"${limiter}", limiterStr); title.Replace(L"${speed}", pxsFmt(L"%3d%%", lround(PerformanceMetrics::GetSpeed()))); @@ -929,7 +928,7 @@ void GSFrame::OnUpdateTitle( wxTimerEvent& evt ) title.Replace(L"${cpuusage}", cpuUsage); title.Replace(L"${omodef}", omodef); title.Replace(L"${omodei}", omodei); - title.Replace(L"${gsdx}", fromUTF8(gsDest)); + title.Replace(L"${gsdx}", StringUtil::UTF8StringToWxString(gsStats)); title.Replace(L"${videomode}", ReportVideoMode()); if (CoreThread.IsPaused() && !GSDump::isRunning) title = templates.Paused + title; diff --git a/pcsx2/gui/GSFrame.h b/pcsx2/gui/GSFrame.h index c7d87a6899d8e..592b66a8bab02 100644 --- a/pcsx2/gui/GSFrame.h +++ b/pcsx2/gui/GSFrame.h @@ -61,7 +61,6 @@ class GSPanel : public wxWindow protected: void AppStatusEvent_OnSettingsApplied(); - void OnCloseWindow( wxCloseEvent& evt ); void OnResize(wxSizeEvent& event); void OnMouseEvent( wxMouseEvent& evt ); void OnHideMouseTimeout( wxTimerEvent& evt ); @@ -118,6 +117,7 @@ class GSFrame : public wxFrame protected: void OnCloseWindow( wxCloseEvent& evt ); + void OnDestroyWindow( wxWindowDestroyEvent& evt ); void OnMove( wxMoveEvent& evt ); void OnResize( wxSizeEvent& evt ); void OnFocus( wxFocusEvent& evt ); diff --git a/pcsx2/gui/GlobalCommands.cpp b/pcsx2/gui/GlobalCommands.cpp index 12f3d762dd7ee..0c8e9e95fe097 100644 --- a/pcsx2/gui/GlobalCommands.cpp +++ b/pcsx2/gui/GlobalCommands.cpp @@ -35,9 +35,6 @@ #include "SPU2/spu2.h" #include "gui/Dialogs/ModalPopups.h" -// renderswitch - tells GS to go into dx9 sw if "renderswitch" is set. -bool renderswitch = false; - static bool g_Pcsx2Recording = false; // true if recording video and sound @@ -92,15 +89,15 @@ namespace Implementations if (!g_Conf->EmuOptions.GS.FrameLimitEnable) { g_Conf->EmuOptions.GS.FrameLimitEnable = true; - EmuConfig.LimiterMode = LimiterModeType::Turbo; + g_Conf->EmuOptions.LimiterMode = LimiterModeType::Turbo; OSDlog(Color_StrongRed, true, "(FrameLimiter) Turbo + FrameLimit ENABLED."); g_Conf->EmuOptions.GS.FrameSkipEnable = !!EmuConfig.Framerate.SkipOnTurbo; } - else if (EmuConfig.LimiterMode == LimiterModeType::Turbo) + else if (g_Conf->EmuOptions.LimiterMode == LimiterModeType::Turbo) { - EmuConfig.LimiterMode = LimiterModeType::Nominal; + g_Conf->EmuOptions.LimiterMode = LimiterModeType::Nominal; - if (EmuConfig.Framerate.SkipOnLimit) + if (g_Conf->EmuOptions.Framerate.SkipOnLimit) { OSDlog(Color_StrongRed, true, "(FrameLimiter) Turbo DISABLED. Frameskip ENABLED"); g_Conf->EmuOptions.GS.FrameSkipEnable = true; @@ -113,9 +110,9 @@ namespace Implementations } else { - EmuConfig.LimiterMode = LimiterModeType::Turbo; + g_Conf->EmuOptions.LimiterMode = LimiterModeType::Turbo; - if (EmuConfig.Framerate.SkipOnTurbo) + if (g_Conf->EmuOptions.Framerate.SkipOnTurbo) { OSDlog(Color_StrongRed, true, "(FrameLimiter) Turbo + Frameskip ENABLED."); g_Conf->EmuOptions.GS.FrameSkipEnable = true; @@ -127,8 +124,6 @@ namespace Implementations } } - gsUpdateFrequency(g_Conf->EmuOptions); - pauser.AllowResume(); } @@ -141,20 +136,18 @@ namespace Implementations // out a better consistency approach... -air ScopedCoreThreadPause pauser; - if (EmuConfig.LimiterMode == LimiterModeType::Slomo) + if (g_Conf->EmuOptions.LimiterMode == LimiterModeType::Slomo) { - EmuConfig.LimiterMode = LimiterModeType::Nominal; + g_Conf->EmuOptions.LimiterMode = LimiterModeType::Nominal; OSDlog(Color_StrongRed, true, "(FrameLimiter) SlowMotion DISABLED."); } else { - EmuConfig.LimiterMode = LimiterModeType::Slomo; + g_Conf->EmuOptions.LimiterMode = LimiterModeType::Slomo; OSDlog(Color_StrongRed, true, "(FrameLimiter) SlowMotion ENABLED."); g_Conf->EmuOptions.GS.FrameLimitEnable = true; } - gsUpdateFrequency(g_Conf->EmuOptions); - pauser.AllowResume(); } @@ -165,7 +158,7 @@ namespace Implementations OSDlog(Color_StrongRed, true, "(FrameLimiter) %s.", g_Conf->EmuOptions.GS.FrameLimitEnable ? "ENABLED" : "DISABLED"); // Turbo/Slowmo don't make sense when framelimiter is toggled - EmuConfig.LimiterMode = LimiterModeType::Nominal; + g_Conf->EmuOptions.LimiterMode = LimiterModeType::Nominal; pauser.AllowResume(); } @@ -196,15 +189,22 @@ namespace Implementations // saved until shutdown, but it matches the behavior pre-settings-move. g_Conf->EmuOptions.GS.AspectRatio = art; + // Prevent GS reopening for the setting change. + EmuConfig.GS.AspectRatio = art; + OSDlog(Color_StrongBlue, true, "(GSwindow) Aspect ratio: %s", arts); } + // NOTE: The settings below are super janky and race the GS thread when updating. + // But because they don't go through the proper settings update procedure, it's necessary to avoid reopening GS. void SetOffset(float x, float y) { - EmuConfig.GS.OffsetX = x; - EmuConfig.GS.OffsetY = y; g_Conf->EmuOptions.GS.OffsetX = x; g_Conf->EmuOptions.GS.OffsetY = y; + EmuConfig.GS.OffsetX = x; + EmuConfig.GS.OffsetY = y; + GSConfig.OffsetX = x; + GSConfig.OffsetY = y; OSDlog(Color_StrongBlue, true, "(GSwindow) Offset: x=%f, y=%f", x, y); } @@ -237,8 +237,9 @@ namespace Implementations { if (zoom <= 0) return; - EmuConfig.GS.StretchY = zoom; g_Conf->EmuOptions.GS.StretchY = zoom; + EmuConfig.GS.StretchY = zoom; + GSConfig.StretchY = zoom; OSDlog(Color_StrongBlue, true, "(GSwindow) Vertical stretch: %f", zoom); } @@ -259,8 +260,9 @@ namespace Implementations { if (zoom < 0) return; - EmuConfig.GS.Zoom = zoom; g_Conf->EmuOptions.GS.Zoom = zoom; + EmuConfig.GS.Zoom = zoom; + GSConfig.Zoom = zoom; if (zoom == 0) OSDlog(Color_StrongBlue, true, "(GSwindow) Zoom: 0 (auto, no black bars)"); @@ -387,15 +389,7 @@ namespace Implementations { reentrant = true; ScopedCoreThreadPause paused_core; - freezeData fP = {0, nullptr}; - MTGS_FreezeData sstate = {&fP, 0}; - GetMTGS().Freeze(FreezeAction::Size, sstate); - fP.data = new u8[fP.size]; - GetMTGS().Freeze(FreezeAction::Save, sstate); - GetMTGS().Suspend(true); - renderswitch = !renderswitch; - GetMTGS().Freeze(FreezeAction::Load, sstate); - delete[] fP.data; + GetMTGS().ToggleSoftwareRendering(); paused_core.AllowResume(); reentrant = false; } diff --git a/pcsx2/gui/MainFrame.cpp b/pcsx2/gui/MainFrame.cpp index fea7390583d86..3856d00439bc8 100644 --- a/pcsx2/gui/MainFrame.cpp +++ b/pcsx2/gui/MainFrame.cpp @@ -36,8 +36,6 @@ #include "Recording/InputRecordingControls.h" #endif -extern std::atomic_bool init_gspanel; - // ------------------------------------------------------------------------ wxMenu* MainEmuFrame::MakeStatesSubMenu(int baseid, int loadBackupId) const { @@ -164,7 +162,7 @@ void MainEmuFrame::OnCloseWindow(wxCloseEvent& evt) { // the main thread is busy suspending everything, so let's not try to call it // when closing the emulator - init_gspanel = false; + //init_gspanel = false; if (IsBeingDeleted()) return; diff --git a/pcsx2/gui/MainMenuClicks.cpp b/pcsx2/gui/MainMenuClicks.cpp index 33a19f165662e..b1ce3c984fb44 100644 --- a/pcsx2/gui/MainMenuClicks.cpp +++ b/pcsx2/gui/MainMenuClicks.cpp @@ -47,8 +47,6 @@ using namespace Dialogs; -extern std::atomic_bool init_gspanel; - void MainEmuFrame::Menu_SysSettings_Click(wxCommandEvent& event) { AppOpenModalDialog(wxEmptyString, this); @@ -88,29 +86,30 @@ void MainEmuFrame::Menu_PADSettings_Click(wxCommandEvent& event) void MainEmuFrame::Menu_GSSettings_Click(wxCommandEvent& event) { - ScopedCoreThreadPause paused_core; - bool is_frame_init = !(wxGetApp().GetGsFramePtr() == nullptr); - bool need_shutdown = GetMTGS().IsClosed(); - init_gspanel = false; - freezeData fP = {0, nullptr}; - MTGS_FreezeData sstate = {&fP, 0}; - if (is_frame_init) + GSconfigure(); + + // this is a bit of an ugly hack, but so is the whole of the threading nonsense. + // we need to tear down USB/PAD before we apply settings, because the window handle + // will change on renderer change. but we can't do that in ApplySettings() because + // that happens on the UI thread instead of the core thread.... + GSFrame* gs_frame = wxGetApp().GetGsFramePtr(); + const bool gs_frame_open = gs_frame && gs_frame->IsShown(); + const Pcsx2Config::GSOptions old_options(g_Conf->EmuOptions.GS); + g_Conf->EmuOptions.GS.ReloadIniSettings(); + if (!g_Conf->EmuOptions.GS.RestartOptionsAreEqual(old_options)) { - GetMTGS().Freeze(FreezeAction::Size, sstate); - fP.data = new u8[fP.size]; - GetMTGS().Freeze(FreezeAction::Save, sstate); - GetMTGS().Suspend(true); + ScopedCoreThreadPause pauser(static_cast(System_USB | System_PAD)); + wxGetApp().SysApplySettings(); } - GSconfigure(); - if (is_frame_init) + else { - GetMTGS().Freeze(FreezeAction::Load, sstate); - delete[] fP.data; + wxGetApp().SysApplySettings(); } - if (need_shutdown) - GetMTGS().Suspend(true); - init_gspanel = true; - paused_core.AllowResume(); + + // re-hide the GS window after changing renderers if we were paused + gs_frame = wxGetApp().GetGsFramePtr(); + if (!gs_frame_open && gs_frame && gs_frame->IsShown()) + gs_frame->Hide(); } void MainEmuFrame::Menu_WindowSettings_Click(wxCommandEvent& event) diff --git a/pcsx2/gui/Panels/GameFixesPanel.cpp b/pcsx2/gui/Panels/GameFixesPanel.cpp index 43fc2e293216a..19c211e1c1975 100644 --- a/pcsx2/gui/Panels/GameFixesPanel.cpp +++ b/pcsx2/gui/Panels/GameFixesPanel.cpp @@ -48,6 +48,10 @@ Panels::GameFixesPanel::GameFixesPanel( wxWindow* parent ) _("Preload TLB hack to avoid tlb miss on Goemon"), wxEmptyString }, + { + _("Switch to Software renderer for FMVs"), + wxEmptyString + }, { _("Skip MPEG hack - Skips videos/FMVs in games to avoid game hanging/freezes."), wxEmptyString diff --git a/pcsx2/pcsx2.vcxproj b/pcsx2/pcsx2.vcxproj index feb9c04a47e74..d306f55c822c3 100644 --- a/pcsx2/pcsx2.vcxproj +++ b/pcsx2/pcsx2.vcxproj @@ -35,13 +35,13 @@ $(SolutionDir)3rdparty\xbyak;%(AdditionalIncludeDirectories) - $(SolutionDir)3rdparty\freetype\include;%(AdditionalIncludeDirectories) $(SolutionDir)3rdparty\xz\xz\src\liblzma\api;%(AdditionalIncludeDirectories) $(SolutionDir)3rdparty\baseclasses;%(AdditionalIncludeDirectories) $(SolutionDir)3rdparty\zlib;%(AdditionalIncludeDirectories) $(SolutionDir)3rdparty\libpng;%(AdditionalIncludeDirectories) $(SolutionDir)3rdparty\glad\include;%(AdditionalIncludeDirectories) $(SolutionDir)3rdparty\cubeb\cubeb\include;$(SolutionDir)3rdparty\cubeb\include;%(AdditionalIncludeDirectories) + $(SolutionDir)3rdparty\imgui\imgui;$(SolutionDir)3rdparty\imgui\include;%(AdditionalIncludeDirectories) Async Use PrecompiledHeader.h @@ -303,6 +303,9 @@ + + + @@ -332,6 +335,7 @@ + @@ -483,7 +487,6 @@ - @@ -742,6 +745,9 @@ + + + @@ -768,6 +774,7 @@ + @@ -843,7 +850,6 @@ - @@ -1107,9 +1113,6 @@ {449ad25e-424a-4714-babc-68706cdcc33b} - - {78b079bd-9fc7-4b9e-b4a6-96da0f00248b} - {bc236261-77e8-4567-8d09-45cd02965eb6} @@ -1158,6 +1161,9 @@ {de9653b6-17dd-356a-9ee0-28a731772587} + + {88fb34ec-845e-4f21-a552-f1573b9ed167} + {4639972e-424e-4e13-8b07-ca403c481346} diff --git a/pcsx2/pcsx2.vcxproj.filters b/pcsx2/pcsx2.vcxproj.filters index 12172c78f505e..ab77ed390bc91 100644 --- a/pcsx2/pcsx2.vcxproj.filters +++ b/pcsx2/pcsx2.vcxproj.filters @@ -1583,9 +1583,6 @@ System\Ps2\GS\Renderers\Common - - System\Ps2\GS\Renderers\Common - System\Ps2\GS\Window @@ -1649,6 +1646,21 @@ System\Ps2\SPU2 + + Host + + + Host + + + Host + + + AppHost + + + Host + Host @@ -2689,9 +2701,6 @@ System\Ps2\GS\Renderers\Common - - System\Ps2\GS\Renderers\Common - System\Ps2\GS\Window @@ -2755,6 +2764,21 @@ Host + + Host + + + Host + + + Host + + + AppHost\Include + + + Host +