GS: Rewrite presentation interface and OSD

This commit is contained in:
Connor McLaughlin 2021-12-19 00:43:50 +10:00 committed by refractionpcsx2
parent 1348c8880e
commit 0c36647506
93 changed files with 4593 additions and 2583 deletions

View File

@ -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}

View File

@ -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

View File

@ -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()
{

View File

@ -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 ()

View File

@ -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.

View File

@ -103,7 +103,11 @@ protected:
#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<decltype(varname)>(wrap.EntryBitfield(CURRENT_SETTINGS_SECTION, textname, static_cast<int>(varname), static_cast<int>(varname)))

View File

@ -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

View File

@ -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};
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();
void LoadSave(SettingsWrapper& wrap);
int GetVsync() const;
#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
bool operator==(const GSOptions& right) const
{
return OpEqu(SynchronousMTGS) &&
OpEqu(VsyncQueueSize) &&
/// Sets user hack values to defaults when user hacks are not enabled.
void MaskUserHacks();
OpEqu(FrameSkipEnable) &&
OpEqu(FrameLimitEnable) &&
OpEqu(VsyncEnable) &&
/// Returns true if any of the hardware renderers are selected.
bool UseHardwareRenderer() const;
OpEqu(LimitScalar) &&
OpEqu(FramerateNTSC) &&
OpEqu(FrameratePAL) &&
/// 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(FramesToDraw) &&
OpEqu(FramesToSkip) &&
/// Returns false if any options need to be applied to the MTGS.
bool OptionsAreEqual(const GSOptions& right) const;
OpEqu(AspectRatio) &&
OpEqu(FMVAspectRatioSwitch) &&
OpEqu(Zoom) &&
OpEqu(StretchY) &&
OpEqu(OffsetX) &&
OpEqu(OffsetY);
}
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
{

View File

@ -19,7 +19,6 @@
#include <time.h>
#include <cmath>
#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!

View File

@ -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;

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "Frontend/D3D11HostDisplay.h"
#include "imgui.h"
#include "imgui_impl_dx11.h"
#include <array>
#include <dxgi1_5.h>
#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<ID3D11Texture2D> texture,
Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> 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<ID3D11Texture2D> m_texture;
Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> m_srv;
u32 m_width;
u32 m_height;
u32 m_layers;
u32 m_levels;
bool m_dynamic;
};
static Microsoft::WRL::ComPtr<ID3D11VertexShader> CreateVertexShader(ID3D11Device* device, const void* bytecode,
size_t bytecode_length)
{
Microsoft::WRL::ComPtr<ID3D11VertexShader> 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<ID3D11PixelShader> CreatePixelShader(ID3D11Device* device, const void* bytecode,
size_t bytecode_length)
{
Microsoft::WRL::ComPtr<ID3D11PixelShader> 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<bool>(m_device);
}
bool D3D11HostDisplay::HasRenderSurface() const
{
return static_cast<bool>(m_swap_chain);
}
std::unique_ptr<HostDisplayTexture> 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<ID3D11Texture2D> 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<ID3D11ShaderResourceView> srv;
hr = m_device->CreateShaderResourceView(texture.Get(), &srv_desc, srv.GetAddressOf());
if (FAILED(hr))
return {};
return std::make_unique<D3D11HostDisplayTexture>(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<D3D11HostDisplayTexture*>(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<char*>(sr.pData) + (y * sr.RowPitch) + (x * sizeof(u32));
const char* src_ptr = static_cast<const char*>(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<float>(desc.BufferDesc.RefreshRate.Numerator) /
static_cast<float>(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<IDXGIFactory> 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<u32>(adapter_info.adapter_names.size()); adapter_index++)
{
if (adapter_name == adapter_info.adapter_names[adapter_index])
break;
}
if (adapter_index == static_cast<u32>(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<IDXGIAdapter> 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<D3D_FEATURE_LEVEL, 3> 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<UINT>(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<ID3D11InfoQueue> 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<IDXGIDevice> 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<int>(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<IDXGIFactory5> 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<HWND>(m_window_info.window_handle);
RECT client_rc{};
GetClientRect(window_hwnd, &client_rc);
const u32 width = static_cast<u32>(client_rc.right - client_rc.left);
const u32 height = static_cast<u32>(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<IDXGIFactory> 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<IDXGIFactory2> 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<IDXGISwapChain1> swap_chain1;
hr = factory2->CreateSwapChainForCoreWindow(m_device.Get(), static_cast<IUnknown*>(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<ID3D11Texture2D> 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<float>(desc.BufferDesc.RefreshRate.Numerator) /
static_cast<float>(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(&current_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<UINT>(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<float, 4> 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<float>(m_window_info.surface_width), static_cast<float>(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<UINT>(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<IDXGIFactory> 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<IDXGIAdapter> current_adapter;
while (SUCCEEDED(dxgi_factory->EnumAdapters(static_cast<UINT>(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<int>(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<size_t>(name_length));
else
adapter_name.assign("(Unknown)");
}
else
{
adapter_name.assign("(Unknown)");
}
if (adapter_info.fullscreen_modes.empty())
{
ComPtr<IDXGIOutput> 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<DXGI_MODE_DESC> 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<float>(mode.RefreshRate.Numerator) / static_cast<float>(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());
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "HostDisplay.h"
#include "common/RedtapeWindows.h"
#include "common/WindowInfo.h"
#include <d3d11.h>
#include <dxgi.h>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include <wrl/client.h>
class D3D11HostDisplay final : public HostDisplay
{
public:
template <typename T>
using ComPtr = Microsoft::WRL::ComPtr<T>;
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<HostDisplayTexture> 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<ID3D11Device> m_device;
ComPtr<ID3D11DeviceContext> m_context;
ComPtr<IDXGIFactory> m_dxgi_factory;
ComPtr<IDXGISwapChain> m_swap_chain;
ComPtr<ID3D11RenderTargetView> 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;
};

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include <chrono>
#include <deque>
#include <mutex>
#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<u8> s_standard_font_data;
static std::vector<u8> s_fixed_font_data;
static std::vector<u8> 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<float>(EmuConfig.GS.OsdScale / 100.0));
ImGui::GetIO().DisplayFramebufferScale = ImVec2(display->GetWindowScale(), display->GetWindowScale());
ImGui::GetIO().DisplaySize.x = static_cast<float>(display->GetWindowWidth());
ImGui::GetIO().DisplaySize.y = static_cast<float>(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<float>(new_width), static_cast<float>(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<float>(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<std::vector<u8>> 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<std::vector<u8>> 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<std::vector<u8>> 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<u8>().swap(s_standard_font_data);
std::vector<u8>().swap(s_fixed_font_data);
std::vector<u8>().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<int>(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<int>(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<int>(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<OSDMessage> s_osd_active_messages;
static std::deque<OSDMessage> 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<std::mutex> 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<std::mutex> lock(s_osd_messages_lock);
s_osd_posted_messages.push_back(std::move(msg));
}
void Host::ClearOSDMessages()
{
{
std::unique_lock<std::mutex> 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<OSDMessage>::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<double>(now - msg.time).count();
const float time_remaining = static_cast<float>(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<u32>(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<float>::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<u32>(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();
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <array>
#include <tuple>
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<void*>(static_cast<uintptr_t>(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<HostDisplayTexture> 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<OpenGLHostDisplayTexture>(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<OpenGLHostDisplayTexture*>(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, &current_fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
if (mode != VsyncMode::Adaptive || !m_gl_context->SetSwapInterval(-1))
m_gl_context->SetSwapInterval(static_cast<s32>(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<bool>(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<s32>(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<u32>(new_window_width) &&
m_window_info.surface_height == static_cast<u32>(new_window_height))
{
return;
}
m_gl_context->ResizeSurface(static_cast<u32>(new_window_width), static_cast<u32>(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();
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <glad.h>
#include "HostDisplay.h"
#include "common/GL/Context.h"
#include "common/WindowInfo.h"
#include <memory>
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<HostDisplayTexture> 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<GL::Context> m_gl_context;
VsyncMode m_vsync_mode = VsyncMode::Off;
};

View File

@ -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);
}

View File

@ -19,6 +19,7 @@
#include "System/SysThreads.h"
#include "Gif.h"
#include "GS/GS.h"
#include <functional>
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<u32> pixels; // width * height
bool success = false;
};
// --------------------------------------------------------------------------------------
// SysMtgsThread
// --------------------------------------------------------------------------------------
@ -312,6 +322,8 @@ class SysMtgsThread : public SysThreadBase
typedef SysThreadBase _parent;
public:
using AsyncCallType = std::function<void()>;
// 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<unsigned int> m_ReadPos; // cur pos gs is reading from
@ -364,7 +376,7 @@ public:
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 @@ public:
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<u32>* 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

View File

@ -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<GSRenderer> 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 GSinitConfig()
{
static bool config_inited = false;
if (config_inited)
return;
config_inited = true;
theApp.SetConfigDir();
theApp.Init();
}
void GSshutdown()
{
gsopen_done = false;
if (s_gs)
{
s_gs->Destroy();
s_gs.reset();
}
if (g_gs_device)
{
g_gs_device->Destroy();
g_gs_device.reset();
}
delete s_gs;
s_gs = nullptr;
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;
s_gs->ResetDevice();
// Opengl requirement: It must be done before the Detach() of
// the context
delete s_gs->m_dev;
s_gs->m_dev = NULL;
}
int _GSopen(const WindowInfo& wi, const char* title, GSRendererType renderer, int threads = -1)
{
GSDevice* dev = NULL;
// Fresh start up or config file changed
if (renderer == GSRendererType::Undefined)
if (s_gs)
{
renderer = static_cast<GSRendererType>(theApp.GetConfigI("Renderer"));
if (renderer == GSRendererType::Default)
renderer = GSUtil::GetPreferredRenderer();
s_gs->Destroy();
s_gs.reset();
}
if (g_gs_device)
{
g_gs_device->Destroy();
g_gs_device.reset();
}
if (threads == -1)
Host::ReleaseHostDisplay();
}
static HostDisplay::RenderAPI GetAPIForRenderer(GSRendererType renderer)
{
switch (renderer)
{
threads = theApp.GetConfigI("extrathreads");
case GSRendererType::OGL:
#ifndef _WIN32
default:
#endif
return HostDisplay::RenderAPI::OpenGL;
#ifdef _WIN32
case GSRendererType::DX11:
case GSRendererType::SW:
default:
return HostDisplay::RenderAPI::D3D11;
#endif
}
}
static bool DoGSOpen(GSRendererType renderer, u8* basemem)
{
HostDisplay* display = Host::GetHostDisplay();
pxAssert(display);
s_render_api = Host::GetHostDisplay()->GetRenderAPI();
switch (display->GetRenderAPI())
{
#ifdef _WIN32
case HostDisplay::RenderAPI::D3D11:
g_gs_device = std::make_unique<GSDevice11>();
break;
#endif
case HostDisplay::RenderAPI::OpenGL:
case HostDisplay::RenderAPI::OpenGLES:
g_gs_device = std::make_unique<GSDeviceOGL>();
break;
default:
Console.Error("Unknown render API %u", static_cast<unsigned>(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<GSRendererNull>();
}
printf("Current Renderer: %s\n", renderer_name.c_str());
if (dev == NULL)
else if (renderer != GSRendererType::SW)
{
return -1;
s_gs = std::make_unique<GSRendererNew>();
}
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<GSRendererSW>(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))
display->SetVSync(EmuConfig.GetEffectiveVsyncMode());
return true;
}
bool GSreopen(bool recreate_display)
{
Console.WriteLn("Reopening GS with %s display", recreate_display ? "new" : "existing");
s_gs->Flush();
freezeData fd = {};
if (s_gs->Freeze(&fd, true) != 0)
{
// 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
GSclose();
return -1;
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);
}
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);
}
int GSopen2(const WindowInfo& wi, u32 flags)
{
static bool stored_toggle_state = false;
const bool toggle_state = !!(flags & 4);
GSRendererType current_renderer = static_cast<GSRendererType>(flags >> 24);
if (current_renderer == GSRendererType::NO_RENDERER)
current_renderer = theApp.GetCurrentRendererType();
if (current_renderer != GSRendererType::Undefined && stored_toggle_state != toggle_state)
std::unique_ptr<u8[]> fd_data = std::make_unique<u8[]>(fd.size);
fd.data = fd_data.get();
if (s_gs->Freeze(&fd, false) != 0)
{
// SW -> HW and HW -> SW (F9 Switch)
switch (current_renderer)
Console.Error("(GSreopen) Failed to freeze GS");
return false;
}
if (recreate_display)
{
g_gs_device->ResetAPIState();
if (Host::BeginPresentFrame(true))
Host::EndPresentFrame();
}
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 (recreate_display)
{
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<GSRendererType>(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;
}
gsopen_done = true;
if (s_gs->Defrost(&fd) != 0)
{
pxFailRel("(GSreopen) Failed to defrost");
return false;
}
return retval;
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();
if (!Host::AcquireHostDisplay(GetAPIForRenderer(renderer)))
{
Console.Error("Failed to acquire host display");
return false;
}
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)
{
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])
{
std::lock_guard<std::mutex> lock(s_gs->m_pGSsetTitle_Crit);
s.append(" | ").append(s_gs->m_GStitleInfoBuffer);
if (s.size() > length - 1)
{
s = s.substr(0, length - 1);
}
}
strcpy(dest, s.c_str());
}
void GSsetFrameSkip(int frameskip)
{
s_gs->SetFrameSkip(frameskip);
}
void GSsetVsync(int vsync)
void GSgetInternalResolution(int* width, int* height)
{
s_vsync = vsync;
if (s_gs)
GSRenderer* gs = s_gs.get();
if (!gs)
{
s_gs->SetVSync(s_vsync);
*width = 0;
*height = 0;
return;
}
const GSVector2i res(gs->GetInternalResolution());
*width = res.x;
*height = res.y;
}
void GSgetStats(std::string& info)
{
GSPerfMon& pm = s_gs->m_perfmon;
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 GSsetExclusive(int enabled)
void GSgetTitleStats(std::string& info)
{
s_exclusive = !!enabled;
int iwidth, iheight;
GSgetInternalResolution(&iwidth, &iheight);
if (s_gs)
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<int>(GSConfig.InterlaceMode)].name.c_str();
info = format("%s%s | %s | %dx%d", api_name, hw_sw_name, interlace_mode, iwidth, iheight);
}
void GSUpdateConfig(const Pcsx2Config::GSOptions& new_config)
{
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;
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.
}
#ifndef PCSX2_CORE
void GSResizeWindow(int width, int height)
void GSSwitchRenderer(GSRendererType new_renderer)
{
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 (new_renderer == GSRendererType::Auto)
new_renderer = GSUtil::GetPreferredRenderer();
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));
}
bool GSCheckForWindowResize(int* new_width, int* new_height)
void GSResetAPIState()
{
if (!s_gs_window_resized.load())
if (!g_gs_device)
return;
g_gs_device->ResetAPIState();
}
void GSRestoreAPIState()
{
if (!g_gs_device)
return;
g_gs_device->RestoreAPIState();
}
bool GSSaveSnapshotToMemory(u32 width, u32 height, std::vector<u32>* pixels)
{
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<u32>(GSRendererType::Auto), "Automatic", ""));
#ifdef _WIN32
m_gs_renderers.push_back(GSSetting(static_cast<u32>(GSRendererType::DX1011_HW), "Direct3D 11", ""));
m_gs_renderers.push_back(GSSetting(static_cast<u32>(GSRendererType::OGL_HW), "OpenGL", ""));
m_gs_renderers.push_back(GSSetting(static_cast<u32>(GSRendererType::OGL_SW), "Software", ""));
#else // Linux
m_gs_renderers.push_back(GSSetting(static_cast<u32>(GSRendererType::OGL_HW), "OpenGL", ""));
m_gs_renderers.push_back(GSSetting(static_cast<u32>(GSRendererType::OGL_SW), "Software", ""));
m_gs_renderers.push_back(GSSetting(static_cast<u32>(GSRendererType::DX11), "Direct3D 11", ""));
#endif
m_gs_renderers.push_back(GSSetting(static_cast<u32>(GSRendererType::OGL), "OpenGL", ""));
m_gs_renderers.push_back(GSSetting(static_cast<u32>(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<u32>(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<u32>(BiFiltering::Forced), "Bilinear", "Forced"));
m_gs_bifilter.push_back(GSSetting(static_cast<u32>(BiFiltering::PS2), "Bilinear", "PS2"));
m_gs_trifilter.push_back(GSSetting(static_cast<u32>(TriFiltering::None), "None", "Default"));
m_gs_trifilter.push_back(GSSetting(static_cast<u32>(TriFiltering::Off), "None", "Default"));
m_gs_trifilter.push_back(GSSetting(static_cast<u32>(TriFiltering::PS2), "Trilinear", ""));
m_gs_trifilter.push_back(GSSetting(static_cast<u32>(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<u32>(AccBlendLevel::None), "Minimum", "Fastest"));
m_gs_acc_blend_level.push_back(GSSetting(static_cast<u32>(AccBlendLevel::Minimum), "Minimum", "Fastest"));
m_gs_acc_blend_level.push_back(GSSetting(static_cast<u32>(AccBlendLevel::Basic), "Basic", "Recommended"));
m_gs_acc_blend_level.push_back(GSSetting(static_cast<u32>(AccBlendLevel::Medium), "Medium", ""));
m_gs_acc_blend_level.push_back(GSSetting(static_cast<u32>(AccBlendLevel::High), "High", ""));
m_gs_acc_blend_level.push_back(GSSetting(static_cast<u32>(AccBlendLevel::Full), "Full", "Very Slow"));
m_gs_acc_blend_level.push_back(GSSetting(static_cast<u32>(AccBlendLevel::Ultra), "Ultra", "Ultra Slow"));
m_gs_acc_blend_level_d3d11.push_back(GSSetting(static_cast<u32>(AccBlendLevel::None), "Minimum", "Fastest"));
m_gs_acc_blend_level_d3d11.push_back(GSSetting(static_cast<u32>(AccBlendLevel::Minimum), "Minimum", "Fastest"));
m_gs_acc_blend_level_d3d11.push_back(GSSetting(static_cast<u32>(AccBlendLevel::Basic), "Basic", "Recommended"));
m_gs_acc_blend_level_d3d11.push_back(GSSetting(static_cast<u32>(AccBlendLevel::Medium), "Medium", "Debug"));
m_gs_acc_blend_level_d3d11.push_back(GSSetting(static_cast<u32>(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<s8>(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<s8>(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<int>(GSRendererType::Default));
m_default_configuration["Renderer"] = std::to_string(static_cast<int>(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<s8>(TriFiltering::None));
m_default_configuration["UserHacks_TriFilter"] = std::to_string(static_cast<s8>(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<bool>(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;
}

View File

@ -18,7 +18,7 @@
#include "common/WindowInfo.h"
#include "Window/GSSetting.h"
#include "SaveState.h"
#include "Host.h"
#include "pcsx2/Config.h"
#include <map>
@ -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,
};
extern Pcsx2Config::GSOptions GSConfig;
enum class TriFiltering : u8
{
None,
PS2,
Forced,
};
struct HostKeyEvent;
class HostDisplay;
enum class HWMipmapLevel : int
{
Automatic = -1,
Off,
Basic,
Full
};
enum class CRCHackLevel : s8
{
Automatic = -1,
None,
Minimum,
Partial,
Full,
Aggressive
};
enum class AccBlendLevel : u8
{
None,
Basic,
Medium,
High,
Full,
Ultra,
};
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<u32>* pixels);
class GSApp
{
std::string m_section;
std::map<std::string, std::string> m_default_configuration;
std::map<std::string, std::string> m_configuration_map;
GSRendererType m_current_renderer_type;
public:
std::string m_ini;
@ -154,12 +103,14 @@ public:
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 @@ public:
bool GetConfigB(const char* entry);
std::string GetConfigS(const char* entry);
void SetCurrentRendererType(GSRendererType type);
GSRendererType GetCurrentRendererType() const;
void SetConfigDir();
std::vector<GSSetting> m_gs_renderers;
@ -206,5 +154,3 @@ struct GSErrorGlVertexArrayTooSmall : GSError
};
extern GSApp theApp;
extern bool gsopen_done;

View File

@ -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;

View File

@ -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,

View File

@ -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);

View File

@ -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);

View File

@ -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<CRCHackLevel>("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<true, true>() : SetPrimHandlers<true, false>();
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();
}

View File

@ -155,8 +155,6 @@ protected:
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 @@ protected:
GIFRegTEX0 GetTex0Layer(u32 lod);
public:
GSDevice* m_dev;
GIFPath m_path[4];
GIFRegPRIM* PRIM;
GSPrivRegSet* m_regs;
@ -272,7 +269,13 @@ public:
template<int index> 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<u8*>(m_regs); }
void SetRegsMem(u8* basemem) { m_regs = reinterpret_cast<GSPrivRegSet*>(basemem); }
void SetFrameSkip(int skip);
void SetRegsMem(u8* basemem);
};

View File

@ -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<std::string> 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

View File

@ -42,7 +42,6 @@ public:
static bool CheckSSE();
static CRCHackLevel GetRecommendedCRCHackLevel(GSRendererType type);
static GSRendererType GetPreferredRenderer();
static std::vector<std::string> GetAdapterList(GSRendererType renderer);
};
#ifdef _WIN32

View File

@ -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<GSDevice> 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)

View File

@ -21,12 +21,13 @@
#include "GSVertex.h"
#include "GS/GSAlignedClass.h"
#include "GS/GSExtra.h"
#include "GSOsdManager.h"
#include <array>
#ifdef _WIN32
#include <dxgi.h>
#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 @@ protected:
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 @@ protected:
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 @@ public:
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 @@ public:
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 @@ public:
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<GSDevice> g_gs_device;

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "GSOsdManager.h"
#include "GS/GS.h"
#include "Host.h"
#if __GNUC__ > 5 || (__GNUC__ == 5 && __GNUC_MINOR__ >= 4)
#include <codecvt>
#include <locale>
#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<std::codecvt_utf8<unsigned int>, unsigned int> conv;
#else
std::wstring_convert<std::codecvt_utf8<char32_t>, 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<std::codecvt_utf8<unsigned int>, unsigned int> conv;
#else
std::wstring_convert<std::codecvt_utf8<char32_t>, 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<std::codecvt_utf8<unsigned int>, unsigned int> conv;
#else
std::wstring_convert<std::codecvt_utf8<char32_t>, 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<float> 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<float> 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;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "PrecompiledHeader.h"
#include "GS/GSVector.h"
#include "GSVertex.h"
#include "GSTexture.h"
#include "ft2build.h"
#include FT_FREETYPE_H
#include <map>
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<char32_t, glyph_info> m_char_info;
std::map<std::pair<char32_t, char32_t>, 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<log_info> m_log;
std::map<std::u32string, std::u32string> 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<u8> resource_data_buffer;
};

View File

@ -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 <X11/keysym.h>
#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)
void GSRenderer::Destroy()
{
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()
{
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<int>(GSConfig.InterlaceMode) - 1) & 1);
int mode = (static_cast<int>(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<double>(width);
const double f_height = static_cast<double>(height);
const double clientAr = f_width / f_height;
static constexpr std::array<float, static_cast<size_t>(AspectRatioType::MaxCount)> ars = { {4.0f / 3.0f, 4.0f / 3.0f, 16.0f / 9.0f} };
return ars[static_cast<u32>(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<float>(window_width);
const float f_height = static_cast<float>(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;
double target_x, target_y;
if (target_width > f_width)
target_x = -((target_width - f_width) * 0.5);
if (GSConfig.IntegerScaling)
{
// make target width/height an integer multiple of the texture width/height
const float t_width = static_cast<double>(texture_width);
const float t_height = static_cast<double>(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;
}
}
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<int>(std::floor(target_x)),
static_cast<int>(std::floor(target_y)),
static_cast<int>(std::round(target_x + target_width)),
static_cast<int>(std::round(target_y + target_height)));
GSVector4 ret(target_x, target_y, target_x + target_width, target_y + target_height);
if (flip_y)
{
const float height = ret.w - ret.y;
ret.y = static_cast<float>(window_height) - ret.w;
ret.w = ret.y + height;
}
return ret;
}
void GSRenderer::SetVSync(int vsync)
{
m_vsync = vsync;
if (m_dev)
m_dev->SetVSync(m_vsync);
}
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();
}
m_dev->AgePool();
// osd
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<int>(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<int>(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
{
// Satisfy PCSX2's request for title info: minimal verbosity due to more external title text
s = format("%dx%d | %s", GetInternalResolution().x, GetInternalResolution().y, theApp.m_gs_interlace[m_interlace].name.c_str());
}
#endif
if (m_capture.IsCapturing())
{
s += " | Recording...";
}
// 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<std::mutex> 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)
g_gs_device->AgePool();
const bool blank_frame = !Merge(field ? 1 : 0);
const bool skip_frame = m_frameskip;
if (blank_frame || skip_frame)
{
g_gs_device->ResetAPIState();
if (Host::BeginPresentFrame(skip_frame))
Host::EndPresentFrame();
g_gs_device->RestoreAPIState();
return;
}
// present
if ((m_perfmon.GetFrame() & 0x1f) == 0)
m_perfmon.Update();
// 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];
g_gs_device->ResetAPIState();
if (Host::BeginPresentFrame(false))
{
GSTexture* current = g_gs_device->GetCurrent();
if (current)
{
HostDisplay* const display = g_gs_device->GetDisplay();
const GSVector4 draw_rect(CalculateDrawRect(display->GetWindowWidth(), display->GetWindowHeight(),
current->GetWidth(), current->GetHeight(), display->GetDisplayAlignment(), display->UsesLowerLeftOrigin()));
m_dev->Present(ComputeDrawRectangle(window_size.z, window_size.w), m_shader);
static constexpr ShaderConvert s_shader[5] = {ShaderConvert::COPY, ShaderConvert::SCANLINE,
ShaderConvert::DIAGONAL_FILTER, ShaderConvert::TRIANGULAR_FILTER,
ShaderConvert::COMPLEX_FILTER}; // FIXME
g_gs_device->StretchRect(current, nullptr, draw_rect, s_shader[GSConfig.TVShader], GSConfig.LinearPresent);
}
Host::EndPresentFrame();
}
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<GSInterlaceMode>((static_cast<int>(GSConfig.InterlaceMode) + static_cast<int>(GSInterlaceMode::Count) + step) % static_cast<int>(GSInterlaceMode::Count));
theApp.SetConfig("interlace", static_cast<int>(GSConfig.InterlaceMode));
printf("GS: Set deinterlace mode to %d (%s).\n", static_cast<int>(GSConfig.InterlaceMode), theApp.m_gs_interlace.at(static_cast<int>(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<u32>* 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<u32>(draw_rect.z - draw_rect.x);
u32 draw_height = static_cast<u32>(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;
}

View File

@ -17,6 +17,7 @@
#include "GS/GSState.h"
#include "GS/GSCapture.h"
#include <memory>
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 @@ public:
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 @@ public:
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<u32>* pixels);
};

View File

@ -33,7 +33,6 @@ public:
DepthStencil,
Texture,
Offscreen,
Backbuffer,
SparseRenderTarget,
SparseDepthStencil,
};
@ -41,7 +40,6 @@ public:
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 @@ public:
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;

View File

@ -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<BiFiltering>(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;

View File

@ -26,8 +26,6 @@ class GSState;
class alignas(32) GSVertexTrace : public GSAlignedClass<32>
{
BiFiltering m_force_filter;
public:
struct Vertex
{

View File

@ -49,30 +49,6 @@ namespace D3D
return factory;
}
std::vector<std::string> GetAdapterList(IDXGIFactory2* factory)
{
ASSERT(factory);
UINT index = 0;
wil::com_ptr_nothrow<IDXGIAdapter1> adapter;
std::vector<std::string> 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<IDXGIAdapter1> GetAdapterFromIndex(IDXGIFactory2* factory, int index)
{
ASSERT(factory);

View File

@ -25,9 +25,6 @@ namespace D3D
// create a dxgi factory
wil::com_ptr_nothrow<IDXGIFactory2> CreateFactory(bool debug);
// get a list of adapters
std::vector<std::string> 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

View File

@ -20,6 +20,7 @@
#include "GS/GSExtra.h"
#include "GS/GSUtil.h"
#include "Host.h"
#include "HostDisplay.h"
#include <fstream>
#include <sstream>
#include <VersionHelpers.h>
@ -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");
D3D_FEATURE_LEVEL level;
auto factory = D3D::CreateFactory(enable_debugging);
if (!factory)
if (display->GetRenderAPI() != HostDisplay::RenderAPI::D3D11)
{
fprintf(stderr, "Render API is incompatible with D3D11\n");
return false;
}
m_dev = static_cast<ID3D11Device*>(display->GetRenderDevice());
m_ctx = static_cast<ID3D11DeviceContext*>(display->GetRenderContext());
level = m_dev->GetFeatureLevel();
bool nvidia_vendor = false;
{
if (auto dxgi_device = m_dev.try_query<IDXGIDevice>())
{
wil::com_ptr_nothrow<IDXGIAdapter> 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);
}
}
if (!SetFeatureLevel(m_dev->GetFeatureLevel(), true))
return false;
// select adapter
auto adapter = D3D::GetAdapterFromIndex(
factory.get(), theApp.GetConfigI("adapter_index")
);
DXGI_ADAPTER_DESC1 adapter_desc = {};
if (SUCCEEDED(adapter->GetDesc1(&adapter_desc)))
{
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);
}
// device creation
{
u32 flags = D3D11_CREATE_DEVICE_SINGLETHREADED;
if(enable_debugging)
flags |= D3D11_CREATE_DEVICE_DEBUG;
constexpr std::array<D3D_FEATURE_LEVEL, 3> supported_levels = {
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
};
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<ID3D11InfoQueue>())
{
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))
{
fprintf(stderr, "D3D: adapter doesn't have a sufficient feature level\n");
return false;
}
// 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<HWND>(wi.window_handle),
&swapchain_description, nullptr, nullptr, m_swapchain.put());
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<std::string> 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<ID3D11RasterizerState> 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<GSTexture>(
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<ID3D11Texture2D> 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<UINT>(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);
void GSDevice11::Flip()
{
m_swapchain->Present(m_vsync, 0);
const CD3D11_VIEWPORT vp(m_hack_topleft_offset, m_hack_topleft_offset,
static_cast<float>(m_state.viewport.x), static_cast<float>(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);
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<int>(ShaderConvert::RGBA8_TO_FLOAT32)]
|| ps == m_convert.ps[static_cast<int>(ShaderConvert::RGBA8_TO_FLOAT24)]
@ -740,11 +640,22 @@ void GSDevice11::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture*
BeginScene();
const GSVector2i ds = dTex->GetSize();
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<int>(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;

View File

@ -119,7 +119,6 @@ private:
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 @@ private:
wil::com_ptr_nothrow<ID3D11SamplerState> m_palette_ss;
std::unordered_map<u32, wil::com_ptr_nothrow<ID3D11DepthStencilState>> m_om_dss;
std::unordered_map<u32, wil::com_ptr_nothrow<ID3D11BlendState>> m_om_bs;
wil::com_ptr_nothrow<ID3D11RasterizerState> m_rs;
GSHWDrawConfig::VSConstantBuffer m_vs_cb_cache;
GSHWDrawConfig::PSConstantBuffer m_ps_cb_cache;
std::unique_ptr<GSTexture> m_font;
std::unique_ptr<GSTexture11> m_download_tex;
std::string m_tfx_source;
@ -240,10 +239,10 @@ public:
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();

View File

@ -44,6 +44,11 @@ GSTexture11::GSTexture11(wil::com_ptr_nothrow<ID3D11Texture2D> texture, GSTextur
m_max_layer = m_desc.MipLevels;
}
void* GSTexture11::GetNativeHandle() const
{
return static_cast<ID3D11ShaderResourceView*>(*const_cast<GSTexture11*>(this));
}
bool GSTexture11::Update(const GSVector4i& r, const void* data, int pitch, int layer)
{
if (layer >= m_max_layer)

View File

@ -36,6 +36,8 @@ class GSTexture11 : public GSTexture
public:
explicit GSTexture11(wil::com_ptr_nothrow<ID3D11Texture2D> 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();

View File

@ -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)

View File

@ -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);
}
}
}

View File

@ -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<int, 8> tri_normal_indices = {{0, 1, 2, 1, 2, 3}};
alignas(16) static constexpr std::array<int, 8> 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<true>(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;

View File

@ -26,7 +26,6 @@ private:
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 @@ protected:
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 @@ public:
GSVector4 RealignTargetTextureCoordinate(const GSTextureCache::Source* tex);
GSVector4i ComputeBoundingBox(const GSVector2& rtscale, const GSVector2i& rtsize);
void MergeSprite(GSTextureCache::Source* tex);
GSVector2 GetTextureScaleFactor();
GSVector2 GetTextureScaleFactor() override;
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();
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; }

View File

@ -18,12 +18,8 @@
#include "GS/GSGL.h"
GSRendererNew::GSRendererNew()
: GSRendererHW()
{
if (theApp.GetConfigB("UserHacks"))
UserHacks_tri_filter = static_cast<TriFiltering>(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<AccBlendLevel>(theApp.GetConfigI("accurate_blending_unit"));
else
m_sw_blending = static_cast<AccBlendLevel>(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<u8>(m_sw_blending) >= static_cast<u8>(AccBlendLevel::Basic))
if (static_cast<u8>(GSConfig.AccurateBlendingUnit) >= static_cast<u8>(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<u8>(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);
}

View File

@ -31,8 +31,6 @@ private:
PRIM_OVERLAP m_prim_overlap;
std::vector<size_t> m_drawlist;
TriFiltering UserHacks_tri_filter;
GSHWDrawConfig m_conf;
private:
@ -49,7 +47,6 @@ public:
GSRendererNew();
~GSRendererNew() override {}
bool CreateDevice(GSDevice* dev, const WindowInfo& wi) override;
void DrawPrims(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* tex) override;
PRIM_OVERLAP PrimitiveOverlap();

View File

@ -49,9 +49,6 @@ GSTextureCache::GSTextureCache(GSRenderer* r)
}
m_paltex = theApp.GetConfigB("paltex");
m_crc_hack_level = theApp.GetConfigT<CRCHackLevel>("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]));
}
}

View File

@ -219,7 +219,6 @@ protected:
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;

View File

@ -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);

View File

@ -29,7 +29,4 @@ private:
public:
GSDeviceNull() {}
bool Create(const WindowInfo& wi);
bool Reset(int w, int h);
};

View File

@ -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;
}

View File

@ -37,4 +37,5 @@ public:
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;
};

View File

@ -17,6 +17,7 @@
#include "GLLoader.h"
#include "GS/GS.h"
#include <unordered_set>
#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

View File

@ -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;

View File

@ -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;

View File

@ -20,6 +20,7 @@
#include "GLState.h"
#include "GS/GSUtil.h"
#include "Host.h"
#include "HostDisplay.h"
#include <fstream>
#include <sstream>
@ -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<TriFiltering>(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<GSTexture>(
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;
// 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)
{
m_gl_context->SetSwapInterval(vsync);
}
void GSDeviceOGL::Flip()
{
m_gl_context->SwapBuffers();
if (GLLoader::in_replayer)
{
glQueryCounter(m_profiler.timer(), GL_TIMESTAMP);
m_profiler.last_query++;
}
return true;
}
void GSDeviceOGL::ResetAPIState()
{
if (GLState::point_size)
glDisable(GL_PROGRAM_POINT_SIZE);
if (GLState::line_width != 1.0f)
glLineWidth(1.0f);
}
void GSDeviceOGL::RestoreAPIState()
{
glBindVertexArray(m_vertex_array_object);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, GLState::fbo);
glViewportIndexedf(0, 0, 0, static_cast<float>(GLState::viewport.x), static_cast<float>(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<float>(GLState::bf) / 128.0f;
glBlendColor(bf, bf, bf, bf);
if (GLState::blend)
{
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<GSTextureOGL*>(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);
OMSetFBO(m_fbo);
OMAttachRt(T);
// 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);
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<int>(ShaderConvert::RGBA8_TO_FLOAT32)]
|| ps == m_convert.ps[static_cast<int>(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<GSTextureOGL*>(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<int>(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<u32>(count) * sizeof(GSVertexPT1));
count = m_osd.GeneratePrimitives(reinterpret_cast<GSVertexPT1*>(res.pointer), count);
m_vertex.start = res.index_aligned;
m_vertex.count = count;
m_vertex_stream_buffer->Unmap(static_cast<u32>(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<GSTextureOGL*>(rt);
GSTextureOGL* DS = static_cast<GSTextureOGL*>(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)

View File

@ -213,12 +213,9 @@ private:
// Increment this constant whenever shaders change, to invalidate user's program binary cache.
static constexpr u32 SHADER_VERSION = 1;
std::unique_ptr<GL::Context> 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 @@ private:
GSHWDrawConfig::VSConstantBuffer m_vs_cb_cache;
GSHWDrawConfig::PSConstantBuffer m_ps_cb_cache;
std::unique_ptr<GSTexture> m_font;
AlignedBuffer<u8, 32> m_download_buffer;
GSTexture* CreateSurface(GSTexture::Type type, int w, int h, GSTexture::Format format) final;
@ -311,7 +307,6 @@ private:
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 @@ public:
// 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();

View File

@ -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<void*>(static_cast<uintptr_t>(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);

View File

@ -63,6 +63,8 @@ public:
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 @@ public:
bool Save(const std::string& fn) final;
GSMap Read(const GSVector4i& r, AlignedBuffer<u8, 32>& 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; }

View File

@ -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;
}

View File

@ -79,17 +79,16 @@ protected:
std::atomic<u32> m_fzb_pages[512]; // u16 frame/zbuf pages interleaved
std::atomic<u16> 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<GSRasterizerData>& 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 @@ protected:
public:
GSRendererSW(int threads);
virtual ~GSRendererSW();
~GSRendererSW() override;
};

View File

@ -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;

View File

@ -33,4 +33,5 @@ public:
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;
};

View File

@ -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

View File

@ -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<wxBoxSizer> tab_box(wxVERTICAL);
CheckboxPrereq monitor_check(m_ui.addCheckBox(tab_box.inner, "Enable Monitor", "osd_monitor_enabled", IDC_OSD_MONITOR));
PaddedBoxSizer<wxStaticBoxSizer> font_box(wxVERTICAL, this, "Font");
PaddedBoxSizer<wxStaticBoxSizer> 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<wxStaticBoxSizer> 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<wxStaticBoxSizer> 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<int>(theApp.m_gs_renderers.size()) || index >= 0);
const GSRendererType type = static_cast<GSRendererType>(
theApp.m_gs_renderers[index].value
);
return type;
const GSRendererType type = static_cast<GSRendererType>(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<int>(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;
const GSRendererType renderer = GetSelectedRendererType();
const std::string current_adapter(theApp.GetConfigS("Adapter"));
std::vector<std::string> 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<int>(adapters.size() + 1) || explicitly_selected_default)
new_sel = 0;
for (std::string& adapter : adapters)
HostDisplay::AdapterAndModeList list;
switch (renderer)
{
if (adapter == current)
new_sel = m_adapter_arr_string.Count();
m_adapter_arr_string.Add(fromUTF8(adapter));
#ifdef _WIN32
case GSRendererType::DX11:
list = D3D11HostDisplay::StaticGetAdapterAndModeList();
break;
#endif
default:
break;
}
m_adapter_select->Set(m_adapter_arr_string);
m_adapter_select->SetSelection(new_sel);
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)
{
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->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<int>(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();

View File

@ -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;

View File

@ -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<std::string> 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, ...);

131
pcsx2/HostDisplay.cpp Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "HostDisplay.h"
#include "common/Assertions.h"
#include "common/Console.h"
#include "common/StringUtil.h"
#include <cerrno>
#include <cmath>
#include <cstring>
#include <chrono>
#include <thread>
#include <vector>
HostDisplayTexture::~HostDisplayTexture() = default;
HostDisplay::~HostDisplay() = default;
const char* HostDisplay::RenderAPIToString(RenderAPI api)
{
static const char* names[] = {"None", "D3D11", "Vulkan", "OpenGL", "OpenGLES"};
return (static_cast<u32>(api) >= std::size(names)) ? names[0] : names[static_cast<u32>(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<u32> owidth = StringUtil::FromChars<u32>(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<u32> oheight = StringUtil::FromChars<u32>(mode.substr(sep1, sep2 - sep1));
sep2++;
while (sep2 < mode.length() && std::isspace(mode[sep2]))
sep2++;
if (oheight.has_value() && sep2 < mode.length())
{
std::optional<float> orefresh_rate = StringUtil::FromChars<float>(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> HostDisplay::CreateDisplayForAPI(RenderAPI api)
{
switch (api)
{
#ifdef _WIN32
case HostDisplay::RenderAPI::D3D11:
return std::make_unique<D3D11HostDisplay>();
#endif
case HostDisplay::RenderAPI::OpenGL:
case HostDisplay::RenderAPI::OpenGLES:
return std::make_unique<OpenGLHostDisplay>();
default:
Console.Error("Unknown render API %u", static_cast<unsigned>(api));
return {};
}
}

172
pcsx2/HostDisplay.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "common/Pcsx2Defs.h"
#include "common/WindowInfo.h"
#include <memory>
#include <string>
#include <string_view>
#include <tuple>
#include <vector>
#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<std::string> adapter_names;
std::vector<std::string> 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<HostDisplay> 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<s32>(m_window_info.surface_width); }
__fi s32 GetWindowHeight() const { return static_cast<s32>(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<HostDisplayTexture> 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

View File

@ -19,15 +19,19 @@
#include <list>
#include <wx/datetime.h>
#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<u32>* pixels)
{
bool result = false;
RunOnGSThread([width, height, pixels, &result]() {
result = GSSaveSnapshotToMemory(width, height, pixels);
});
WaitGS(false, false, false);
return result;
}

View File

@ -29,11 +29,10 @@ struct Component_FileMcd;
#include "System.h"
#include "Config.h"
#include "Host.h"
#include "svnrev.h"
#include "gui/ConsoleLogger.h"
#include <wx/ffile.h>
#include <map>
@ -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;

View File

@ -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};
wrap.EnumEntry("AspectRatio", AspectRatio, AspectRatioNames, AspectRatio);
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);
// These are loaded from GSWindow in wx.
SettingsWrapEnumEx(AspectRatio, "AspectRatio", AspectRatioNames);
SettingsWrapEnumEx(FMVAspectRatioSwitch, "FMVAspectRatioSwitch", FMVAspectRatioSwitchNames);
SettingsWrapEntry(Zoom);
SettingsWrapEntry(StretchY);
SettingsWrapEntry(OffsetX);
SettingsWrapEntry(OffsetY);
#endif
#ifndef PCSX2_CORE
if (wrap.IsLoading())
ReloadIniSettings();
#else
LoadSaveIniSettings(wrap);
#endif
}
int Pcsx2Config::GSOptions::GetVsync() const
#ifdef PCSX2_CORE
void Pcsx2Config::GSOptions::LoadSaveIniSettings(SettingsWrapper& wrap)
{
if (EmuConfig.LimiterMode == LimiterModeType::Turbo || !FrameLimitEnable)
return 0;
SettingsWrapSection("EmuCore/GS");
// D3D only support a boolean state. OpenGL waits a number of vsync
// interrupt (negative value for late vsync).
switch (VsyncEnable)
#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<double>(theApp.GetConfigI(#var))
#define GSSettingIntEnumEx(var, name) var = static_cast<decltype(var)>(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
}
void Pcsx2Config::GSOptions::MaskUserHacks()
{
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
}
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()

View File

@ -22,6 +22,7 @@
#include "GS.h" // GSosdlog
#include "gui/App.h" // GetRGBA
#include "gui/ConsoleLogger.h"
#include "Host.h"
#include <fmt/core.h>
@ -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)

View File

@ -104,8 +104,10 @@ void SysCoreThread::OnStart()
void SysCoreThread::OnSuspendInThread()
{
TearDownSystems(static_cast<SystemsMask>(-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<SystemsMask>(-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();

View File

@ -526,6 +526,7 @@ public:
void OpenGsPanel();
void CloseGsPanel();
void OnGsFrameClosed(wxWindowID id);
void OnGsFrameDestroyed(wxWindowID id);
void OnMainFrameClosed(wxWindowID id);
// --------------------------------------------------------------------------

View File

@ -567,9 +567,6 @@ void AppCoreThread::ApplySettings(const Pcsx2Config& src)
{
_parent::ApplySettings(fixup);
}
if (m_ExecMode >= ExecMode_Paused)
GSsetVsync(EmuConfig.GS.GetVsync());
}
// --------------------------------------------------------------------------------------

View File

@ -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 <atomic>
#include <mutex>
#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<HostDisplay> 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();
}

12
pcsx2/gui/AppHost.h Normal file
View File

@ -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

View File

@ -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);

View File

@ -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 <wx/textfile.h>
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));
}

View File

@ -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);

View File

@ -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<GSRendererType>(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;

View File

@ -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<decltype(get_dpi_for_window)>(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<float>(dpi) / 96.0f) : 1.0f;
}
#endif
std::optional<WindowInfo> GSPanel::GetWindowInfo()
{
WindowInfo ret;
const wxSize gs_vp_size(GetClientSize());
ret.surface_scale = static_cast<float>(GetContentScaleFactor());
ret.surface_width = static_cast<u32>(gs_vp_size.GetWidth());
ret.surface_height = static_cast<u32>(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<WindowInfo> GSPanel::GetWindowInfo()
return std::nullopt;
}
const wxSize gs_vp_size(GetClientSize());
ret.surface_scale = static_cast<float>(GetContentScaleFactor());
ret.surface_width = static_cast<u32>(gs_vp_size.GetWidth());
ret.surface_height = static_cast<u32>(gs_vp_size.GetHeight());
#ifdef __WXGTK__
// GTK seems to not scale coordinates?
if (ret.type == WindowInfo::Type::X11)
{
ret.surface_width = static_cast<u32>(ret.surface_width * ret.surface_scale);
ret.surface_height = static_cast<u32>(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;

View File

@ -61,7 +61,6 @@ public:
protected:
void AppStatusEvent_OnSettingsApplied();
void OnCloseWindow( wxCloseEvent& evt );
void OnResize(wxSizeEvent& event);
void OnMouseEvent( wxMouseEvent& evt );
void OnHideMouseTimeout( wxTimerEvent& evt );
@ -118,6 +117,7 @@ public:
protected:
void OnCloseWindow( wxCloseEvent& evt );
void OnDestroyWindow( wxWindowDestroyEvent& evt );
void OnMove( wxMoveEvent& evt );
void OnResize( wxSizeEvent& evt );
void OnFocus( wxFocusEvent& evt );

View File

@ -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;
}

View File

@ -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;

View File

@ -47,8 +47,6 @@
using namespace Dialogs;
extern std::atomic_bool init_gspanel;
void MainEmuFrame::Menu_SysSettings_Click(wxCommandEvent& event)
{
AppOpenModalDialog<SysConfigDialog>(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)
{
GetMTGS().Freeze(FreezeAction::Size, sstate);
fP.data = new u8[fP.size];
GetMTGS().Freeze(FreezeAction::Save, sstate);
GetMTGS().Suspend(true);
}
GSconfigure();
if (is_frame_init)
// 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::Load, sstate);
delete[] fP.data;
ScopedCoreThreadPause pauser(static_cast<SystemsMask>(System_USB | System_PAD));
wxGetApp().SysApplySettings();
}
if (need_shutdown)
GetMTGS().Suspend(true);
init_gspanel = true;
paused_core.AllowResume();
else
{
wxGetApp().SysApplySettings();
}
// 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)

View File

@ -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

View File

@ -35,13 +35,13 @@
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\xbyak;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\freetype\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\xz\xz\src\liblzma\api;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\baseclasses;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\zlib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\libpng;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\glad\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\cubeb\cubeb\include;$(SolutionDir)3rdparty\cubeb\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\imgui\imgui;$(SolutionDir)3rdparty\imgui\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<ExceptionHandling>Async</ExceptionHandling>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>PrecompiledHeader.h</PrecompiledHeaderFile>
@ -303,6 +303,9 @@
<ClCompile Include="DEV9\Win32\DEV9WinConfig.cpp" />
<ClCompile Include="DEV9\net.cpp" />
<ClCompile Include="DEV9\Win32\tap-win32.cpp" />
<ClCompile Include="Frontend\D3D11HostDisplay.cpp" />
<ClCompile Include="Frontend\ImGuiManager.cpp" />
<ClCompile Include="Frontend\OpenGLHostDisplay.cpp" />
<ClCompile Include="GameDatabase.cpp" />
<ClCompile Include="Gif_Logger.cpp" />
<ClCompile Include="Gif_Unit.cpp" />
@ -332,6 +335,7 @@
<ClCompile Include="gui\wxAppWithHelpers.cpp" />
<ClCompile Include="gui\wxSettingsInterface.cpp" />
<ClCompile Include="Host.cpp" />
<ClCompile Include="HostDisplay.cpp" />
<ClCompile Include="IopGte.cpp" />
<ClCompile Include="PINE.cpp" />
<ClCompile Include="FW.cpp" />
@ -483,7 +487,6 @@
<ClCompile Include="GS\GSLocalMemory.cpp" />
<ClCompile Include="GS\GSLzma.cpp" />
<ClCompile Include="GS\GSPerfMon.cpp" />
<ClCompile Include="GS\Renderers\Common\GSOsdManager.cpp" />
<ClCompile Include="GS\GSPng.cpp" />
<ClCompile Include="GS\GSRingHeap.cpp" />
<ClCompile Include="GS\Renderers\SW\GSRasterizer.cpp" />
@ -742,6 +745,9 @@
<ClInclude Include="DEV9\smap.h" />
<ClInclude Include="DEV9\Win32\pcap_io_win32_funcs.h" />
<ClInclude Include="DEV9\Win32\tap.h" />
<ClInclude Include="Frontend\D3D11HostDisplay.h" />
<ClInclude Include="Frontend\ImGuiManager.h" />
<ClInclude Include="Frontend\OpenGLHostDisplay.h" />
<ClInclude Include="GameDatabase.h" />
<ClInclude Include="Gif_Unit.h" />
<ClInclude Include="GS\Renderers\DX11\D3D.h" />
@ -768,6 +774,7 @@
<ClInclude Include="gui\wxAppWithHelpers.h" />
<ClInclude Include="gui\wxSettingsInterface.h" />
<ClInclude Include="Host.h" />
<ClInclude Include="HostDisplay.h" />
<ClInclude Include="IopGte.h" />
<ClInclude Include="PINE.h" />
<ClInclude Include="FW.h" />
@ -843,7 +850,6 @@
<ClInclude Include="GS\GSLocalMemory.h" />
<ClInclude Include="GS\GSLzma.h" />
<ClInclude Include="GS\GSPerfMon.h" />
<ClInclude Include="GS\Renderers\Common\GSOsdManager.h" />
<ClInclude Include="GS\GSPng.h" />
<ClInclude Include="GS\GSRingHeap.h" />
<ClInclude Include="GS\Renderers\SW\GSRasterizer.h" />
@ -1107,9 +1113,6 @@
<ProjectReference Include="$(SolutionDir)3rdparty\fmt\fmt.vcxproj">
<Project>{449ad25e-424a-4714-babc-68706cdcc33b}</Project>
</ProjectReference>
<ProjectReference Include="$(SolutionDir)3rdparty\freetype\builds\windows\freetype.vcxproj">
<Project>{78b079bd-9fc7-4b9e-b4a6-96da0f00248b}</Project>
</ProjectReference>
<ProjectReference Include="$(SolutionDir)3rdparty\libjpeg\libjpeg.vcxproj">
<Project>{bc236261-77e8-4567-8d09-45cd02965eb6}</Project>
</ProjectReference>
@ -1158,6 +1161,9 @@
<ProjectReference Include="$(SolutionDir)3rdparty\rapidyaml\ryml.vcxproj">
<Project>{de9653b6-17dd-356a-9ee0-28a731772587}</Project>
</ProjectReference>
<ProjectReference Include="$(SolutionDir)3rdparty\imgui\imgui.vcxproj">
<Project>{88fb34ec-845e-4f21-a552-f1573b9ed167}</Project>
</ProjectReference>
<ProjectReference Include="..\common\common.vcxproj">
<Project>{4639972e-424e-4e13-8b07-ca403c481346}</Project>
</ProjectReference>

View File

@ -1583,9 +1583,6 @@
<ClCompile Include="GS\Renderers\Common\GSFunctionMap.cpp">
<Filter>System\Ps2\GS\Renderers\Common</Filter>
</ClCompile>
<ClCompile Include="GS\Renderers\Common\GSOsdManager.cpp">
<Filter>System\Ps2\GS\Renderers\Common</Filter>
</ClCompile>
<ClCompile Include="GS\GSCapture.cpp">
<Filter>System\Ps2\GS\Window</Filter>
</ClCompile>
@ -1649,6 +1646,21 @@
<ClCompile Include="SPU2\SndOut_Cubeb.cpp">
<Filter>System\Ps2\SPU2</Filter>
</ClCompile>
<ClCompile Include="HostDisplay.cpp">
<Filter>Host</Filter>
</ClCompile>
<ClCompile Include="Frontend\OpenGLHostDisplay.cpp">
<Filter>Host</Filter>
</ClCompile>
<ClCompile Include="Frontend\D3D11HostDisplay.cpp">
<Filter>Host</Filter>
</ClCompile>
<ClCompile Include="gui\AppHost.cpp">
<Filter>AppHost</Filter>
</ClCompile>
<ClCompile Include="Frontend\ImGuiManager.cpp">
<Filter>Host</Filter>
</ClCompile>
<ClCompile Include="Host.cpp">
<Filter>Host</Filter>
</ClCompile>
@ -2689,9 +2701,6 @@
<ClInclude Include="GS\Renderers\Common\GSFunctionMap.h">
<Filter>System\Ps2\GS\Renderers\Common</Filter>
</ClInclude>
<ClInclude Include="GS\Renderers\Common\GSOsdManager.h">
<Filter>System\Ps2\GS\Renderers\Common</Filter>
</ClInclude>
<ClInclude Include="GS\GSCapture.h">
<Filter>System\Ps2\GS\Window</Filter>
</ClInclude>
@ -2755,6 +2764,21 @@
<ClInclude Include="Host.h">
<Filter>Host</Filter>
</ClInclude>
<ClInclude Include="HostDisplay.h">
<Filter>Host</Filter>
</ClInclude>
<ClInclude Include="Frontend\OpenGLHostDisplay.h">
<Filter>Host</Filter>
</ClInclude>
<ClInclude Include="Frontend\D3D11HostDisplay.h">
<Filter>Host</Filter>
</ClInclude>
<ClInclude Include="gui\AppHost.h">
<Filter>AppHost\Include</Filter>
</ClInclude>
<ClInclude Include="Frontend\ImGuiManager.h">
<Filter>Host</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="windows\wxResources.rc">