Compare commits
@@ -39,8 +39,8 @@
|
||||
'Debugger':
|
||||
- 'pcsx2/DebugTools/*'
|
||||
- 'pcsx2/DebugTools/**/*'
|
||||
- 'pcsx2/gui/Debugger/*'
|
||||
- 'pcsx2/gui/Debugger/**/*'
|
||||
- 'pcsx2-qt/Debugger/*'
|
||||
- 'pcsx2-qt/Debugger/**/*'
|
||||
'IPC':
|
||||
- 'pcsx2/IPC*'
|
||||
- 'pcsx2/**/IPC*'
|
||||
|
||||
@@ -32,11 +32,11 @@ on:
|
||||
qt_binary_url:
|
||||
required: false
|
||||
type: string
|
||||
default: https://github.com/PCSX2/pcsx2-windows-dependencies/releases/download/2022-11-20/qt-6.4.0-x64.7z
|
||||
default: https://github.com/PCSX2/pcsx2-windows-dependencies/releases/download/2023-04-25/qt-6.5.0-x64.7z
|
||||
qt_dir:
|
||||
required: false
|
||||
type: string
|
||||
default: 3rdparty\qt\6.4.0\msvc2022_64
|
||||
default: 3rdparty\qt\6.5.0\msvc2022_64
|
||||
cheats_url:
|
||||
required: false
|
||||
type: string
|
||||
|
||||
@@ -537,6 +537,7 @@
|
||||
030000009b2800003200000000000000,Raphnet GC and N64 Adapter,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:+a5,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:+a2,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Windows,
|
||||
030000009b2800006000000000000000,Raphnet GC and N64 Adapter,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:+a5,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:+a2,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Windows,
|
||||
030000009b2800001800000000000000,Raphnet Jaguar Adapter,a:b2,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b0,righttrigger:b10,start:b3,x:b11,y:b12,platform:Windows,
|
||||
030000009b2800006300000000000000,Raphnet N64 Adapter,a:b0,b:b1,start:b3,lefttrigger:b2,dpup:b10,dpleft:b12,dpdown:b11,dpright:b13,leftx:a0,lefty:a1,-rightx:b8,+rightx:b9,-righty:b6,+righty:b7,leftshoulder:b4,rightshoulder:b5,platform:Windows,
|
||||
030000009b2800000200000000000000,Raphnet NES Adapter,a:b7,b:b6,back:b5,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,start:b4,platform:Windows,
|
||||
030000009b2800004400000000000000,Raphnet PS1 and PS2 Adapter,a:b1,b:b2,back:b5,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b9,rightx:a3,righty:a4,start:b4,x:b0,y:b3,platform:Windows,
|
||||
030000009b2800004300000000000000,Raphnet Saturn,a:b0,b:b1,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b8,x:b3,y:b4,platform:Windows,
|
||||
@@ -663,6 +664,7 @@
|
||||
03000000c61100001000000000000000,Tencent Xianyou Gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,x:b3,y:b4,platform:Windows,
|
||||
03000000790000002601000000000000,TGZ,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b3,y:b0,platform:Windows,
|
||||
03000000591c00002400000000000000,THEC64 Joystick,a:b0,b:b1,back:b6,leftshoulder:b4,leftx:a0,lefty:a4,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Windows,
|
||||
03000000591c00002600000000000000,THEGamepad,a:b2,b:b1,back:b6,leftx:a0,lefty:a1,start:b7,x:b3,y:b0,platform:Windows,
|
||||
030000004f04000015b3000000000000,Thrustmaster Dual Analog 4,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows,
|
||||
030000004f04000023b3000000000000,Thrustmaster Dual Trigger PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000004f0400000ed0000000000000,ThrustMaster eSwap Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
@@ -983,6 +985,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
|
||||
03000000457500002211000000010000,SZMY Power PC Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
03000000790000001c18000003100000,TGZ Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
|
||||
03000000591c00002400000021000000,THEC64 Joystick,a:b0,b:b1,back:b6,leftshoulder:b4,leftx:a0,lefty:a4,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Mac OS X,
|
||||
03000000591c00002600000021000000,THEGamepad,a:b2,b:b1,back:b6,dpdown:+a4,dpleft:-a0,dpright:+a0,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b0,platform:Mac OS X,
|
||||
030000004f04000015b3000000000000,Thrustmaster Dual Analog 3.2,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Mac OS X,
|
||||
030000004f0400000ed0000000020000,ThrustMaster eSwap Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
030000004f04000000b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Mac OS X,
|
||||
@@ -1332,7 +1335,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
|
||||
050000007e0500003003000001000000,Nintendo Wii U Pro Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Linux,
|
||||
030000000d0500000308000010010000,Nostromo n45 Dual Analog,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,platform:Linux,
|
||||
050000007e0500001920000001000000,NSO N64 Controller,+rightx:b8,+righty:b7,-rightx:b3,-righty:b2,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,righttrigger:b10,start:b9,platform:Linux,
|
||||
050000007e0500001920000001800000,NSO N64 Controller,a:b0,b:b1,guide:b11,misc1:b12,start:b6,leftshoulder:b3,rightshoulder:b4,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,-rightx:b9,+rightx:b10,-righty:b7,+righty:b8,lefttrigger:b2,righttrigger:b5,platform:Linux,
|
||||
050000007e0500001920000001800000,NSO N64 Controller,+rightx:b10,+righty:b8,-rightx:b9,-righty:b7,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b3,lefttrigger:b2,leftx:a0,lefty:a1,misc1:b12,rightshoulder:b4,righttrigger:b5,start:b6,platform:Linux,
|
||||
050000007e0500001720000001000000,NSO SNES Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,platform:Linux,
|
||||
03000000550900001072000011010000,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b8,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,
|
||||
03000000550900001472000011010000,NVIDIA Controller v01.04,a:b0,b:b1,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Linux,
|
||||
|
||||
@@ -128,11 +128,12 @@ ProcessedVertex load_vertex(uint index)
|
||||
#endif
|
||||
|
||||
vec2 i_st = rvtx.ST;
|
||||
vec4 i_c = vec4(uvec4(rvtx.RGBA & 0xFFu, (rvtx.RGBA >> 8) & 0xFFu, (rvtx.RGBA >> 16) & 0xFFu, rvtx.RGBA >> 24));
|
||||
vec4 i_c = vec4(uvec4(bitfieldExtract(rvtx.RGBA, 0, 8), bitfieldExtract(rvtx.RGBA, 8, 8),
|
||||
bitfieldExtract(rvtx.RGBA, 16, 8), bitfieldExtract(rvtx.RGBA, 24, 8)));
|
||||
float i_q = rvtx.Q;
|
||||
uvec2 i_p = uvec2(rvtx.XY & 0xFFFFu, rvtx.XY >> 16);
|
||||
uvec2 i_p = uvec2(bitfieldExtract(rvtx.XY, 0, 16), bitfieldExtract(rvtx.XY, 16, 16));
|
||||
uint i_z = rvtx.Z;
|
||||
uvec2 i_uv = uvec2(rvtx.UV & 0xFFFFu, rvtx.UV >> 16);
|
||||
uvec2 i_uv = uvec2(bitfieldExtract(rvtx.UV, 0, 16), bitfieldExtract(rvtx.UV, 16, 16));
|
||||
vec4 i_f = unpackUnorm4x8(rvtx.FOG);
|
||||
|
||||
ProcessedVertex vtx;
|
||||
|
||||
@@ -113,11 +113,12 @@ ProcessedVertex load_vertex(uint index)
|
||||
RawVertex rvtx = vertex_buffer[gl_BaseVertexARB + index];
|
||||
|
||||
vec2 a_st = rvtx.ST;
|
||||
uvec4 a_c = uvec4(rvtx.RGBA & 0xFFu, (rvtx.RGBA >> 8) & 0xFFu, (rvtx.RGBA >> 16) & 0xFFu, rvtx.RGBA >> 24);
|
||||
uvec4 a_c = uvec4(bitfieldExtract(rvtx.RGBA, 0, 8), bitfieldExtract(rvtx.RGBA, 8, 8),
|
||||
bitfieldExtract(rvtx.RGBA, 16, 8), bitfieldExtract(rvtx.RGBA, 24, 8));
|
||||
float a_q = rvtx.Q;
|
||||
uvec2 a_p = uvec2(rvtx.XY & 0xFFFFu, rvtx.XY >> 16);
|
||||
uvec2 a_p = uvec2(bitfieldExtract(rvtx.XY, 0, 16), bitfieldExtract(rvtx.XY, 16, 16));
|
||||
uint a_z = rvtx.Z;
|
||||
uvec2 a_uv = uvec2(rvtx.UV & 0xFFFFu, rvtx.UV >> 16);
|
||||
uvec2 a_uv = uvec2(bitfieldExtract(rvtx.UV, 0, 16), bitfieldExtract(rvtx.UV, 16, 16));
|
||||
vec4 a_f = unpackUnorm4x8(rvtx.FOG);
|
||||
|
||||
ProcessedVertex vtx;
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
set(wx_sdl_c_code "
|
||||
#include <wx/setup.h>
|
||||
|
||||
#if (wxUSE_LIBSDL == 0)
|
||||
#error cmake_WX_SDL
|
||||
#endif
|
||||
|
||||
int main()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
")
|
||||
|
||||
set(gcc7_mmx_code "
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <xmmintrin.h>
|
||||
#include <emmintrin.h>
|
||||
|
||||
class alignas(16) GSVector4i
|
||||
{
|
||||
public:
|
||||
__m128i m;
|
||||
|
||||
explicit GSVector4i(__m128i m)
|
||||
{
|
||||
this->m = m;
|
||||
}
|
||||
|
||||
static void storel(void* p, const GSVector4i& v)
|
||||
{
|
||||
_mm_storel_epi64((__m128i*)p, v.m);
|
||||
}
|
||||
|
||||
static GSVector4i loadl(const void* p)
|
||||
{
|
||||
return GSVector4i(_mm_loadl_epi64((__m128i*)p));
|
||||
}
|
||||
|
||||
bool eq(const GSVector4i& v) const
|
||||
{
|
||||
return _mm_movemask_epi8(_mm_cmpeq_epi32(m, v.m)) == 0xffff;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
union GIFRegTRXPOS
|
||||
{
|
||||
unsigned long long u64;
|
||||
void operator = (const GSVector4i& v) {GSVector4i::storel(this, v);}
|
||||
bool operator != (const union GIFRegTRXPOS& r) const {return !((GSVector4i)r).eq(*this);}
|
||||
operator GSVector4i() const {return GSVector4i::loadl(this);}
|
||||
};
|
||||
|
||||
extern GIFRegTRXPOS TRXPOS;
|
||||
GIFRegTRXPOS TRXPOS = {};
|
||||
|
||||
void GIFRegHandlerTRXPOS(const GIFRegTRXPOS& p)
|
||||
{
|
||||
if(p != TRXPOS)
|
||||
{
|
||||
printf(\"foo\");
|
||||
}
|
||||
|
||||
TRXPOS = (GSVector4i)p;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
GIFRegTRXPOS r = {};
|
||||
GIFRegHandlerTRXPOS(r);
|
||||
|
||||
uint16_t fpu[16] = {0};
|
||||
__asm__ __volatile__(\"fstenv %0\" : \"=m\"(fpu));
|
||||
|
||||
bool ok = fpu[4] == 0xFFFF;
|
||||
|
||||
if (!ok) {
|
||||
printf(\"Wrong MMX state !\");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
")
|
||||
|
||||
function(GCC7_BUG)
|
||||
# try_run doesn't work when cross-compiling is enabled. It is completely silly in our case
|
||||
# as i386 binaries are 100% fine on x64.
|
||||
set(OLD_CMAKE_CROSSCOMPILING ${CMAKE_CROSSCOMPILING})
|
||||
set(CMAKE_CROSSCOMPILING 0)
|
||||
|
||||
set(IN "${CMAKE_BINARY_DIR}/gcc7_mmx.cpp")
|
||||
file(WRITE "${IN}" "${gcc7_mmx_code}")
|
||||
|
||||
enable_language(CXX)
|
||||
|
||||
try_run(
|
||||
run_result
|
||||
compile_result_unused
|
||||
"${CMAKE_BINARY_DIR}"
|
||||
"${IN}"
|
||||
CMAKE_FLAGS "-DCOMPILE_DEFINITIONS:STRING=-msse -msse2 -O2 -m32 -march=i686"
|
||||
)
|
||||
|
||||
if (${run_result})
|
||||
message(FATAL_ERROR "GCC 7.0/7.1 generates invalid code => https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80799\n"
|
||||
"You can either backport the fix or swith to another version of GCC.")
|
||||
endif()
|
||||
|
||||
set(CMAKE_CROSSCOMPILING ${OLD_CMAKE_CROSSCOMPILING})
|
||||
|
||||
endfunction()
|
||||
@@ -52,9 +52,6 @@ if(MSVC AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
|
||||
set(USE_CLANG TRUE)
|
||||
message(STATUS "Building with Clang/LLVM.")
|
||||
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Intel")
|
||||
set(USE_ICC TRUE)
|
||||
message(STATUS "Building with Intel's ICC.")
|
||||
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
set(USE_GCC TRUE)
|
||||
message(STATUS "Building with GNU GCC")
|
||||
@@ -106,21 +103,13 @@ include(TargetArch)
|
||||
target_architecture(PCSX2_TARGET_ARCHITECTURES)
|
||||
if(${PCSX2_TARGET_ARCHITECTURES} MATCHES "x86_64")
|
||||
message(STATUS "Compiling a ${PCSX2_TARGET_ARCHITECTURES} build on a ${CMAKE_HOST_SYSTEM_PROCESSOR} host.")
|
||||
else()
|
||||
message(FATAL_ERROR "Unsupported architecture: ${PCSX2_TARGET_ARCHITECTURES}")
|
||||
endif()
|
||||
|
||||
if(${PCSX2_TARGET_ARCHITECTURES} MATCHES "x86_64")
|
||||
# x86_64 requires -fPIC
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
if(NOT DEFINED ARCH_FLAG AND NOT MSVC)
|
||||
if (DISABLE_ADVANCE_SIMD)
|
||||
if (USE_ICC)
|
||||
set(ARCH_FLAG "-msse2 -msse4.1")
|
||||
else()
|
||||
set(ARCH_FLAG "-msse -msse2 -msse4.1 -mfxsr")
|
||||
endif()
|
||||
set(ARCH_FLAG "-msse -msse2 -msse4.1 -mfxsr")
|
||||
else()
|
||||
#set(ARCH_FLAG "-march=native -fabi-version=6")
|
||||
set(ARCH_FLAG "-march=native")
|
||||
@@ -132,9 +121,6 @@ if(${PCSX2_TARGET_ARCHITECTURES} MATCHES "x86_64")
|
||||
set(_ARCH_64 1)
|
||||
set(_M_X86 1)
|
||||
else()
|
||||
# All but i386 requires -fPIC
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
message(FATAL_ERROR "Unsupported architecture: ${PCSX2_TARGET_ARCHITECTURES}")
|
||||
endif()
|
||||
string(REPLACE " " ";" ARCH_FLAG_LIST "${ARCH_FLAG}")
|
||||
@@ -220,24 +206,13 @@ endif()
|
||||
if (MSVC)
|
||||
set(DEFAULT_WARNINGS)
|
||||
else()
|
||||
set(DEFAULT_WARNINGS -Wall -Wextra -Wno-attributes -Wno-unused-function -Wno-unused-parameter -Wno-missing-field-initializers -Wno-format -Wno-format-security)
|
||||
if (NOT USE_ICC)
|
||||
list(APPEND DEFAULT_WARNINGS -Wno-unused-value)
|
||||
endif()
|
||||
set(DEFAULT_WARNINGS -Wall -Wextra -Wno-attributes -Wno-unused-function -Wno-unused-parameter -Wno-missing-field-initializers -Wno-format -Wno-format-security -Wno-unused-value)
|
||||
endif()
|
||||
|
||||
if (USE_GCC)
|
||||
list(APPEND DEFAULT_WARNINGS -Wno-stringop-truncation -Wno-stringop-overflow -Wno-maybe-uninitialized )
|
||||
endif()
|
||||
|
||||
|
||||
# -Wstrict-aliasing=n: to fix one day aliasing issue. n=1/2/3
|
||||
if (USE_ICC)
|
||||
set(AGGRESSIVE_WARNING -Wstrict-aliasing)
|
||||
elseif(NOT MSVC)
|
||||
set(AGGRESSIVE_WARNING -Wstrict-aliasing -Wstrict-overflow=1)
|
||||
endif()
|
||||
|
||||
if (USE_PGO_GENERATE OR USE_PGO_OPTIMIZE)
|
||||
add_compile_options("-fprofile-dir=${CMAKE_SOURCE_DIR}/profile")
|
||||
endif()
|
||||
@@ -264,7 +239,7 @@ if(USE_CLANG AND TIMETRACE)
|
||||
add_compile_options(-ftime-trace)
|
||||
endif()
|
||||
|
||||
set(PCSX2_WARNINGS ${DEFAULT_WARNINGS} ${AGGRESSIVE_WARNING})
|
||||
set(PCSX2_WARNINGS ${DEFAULT_WARNINGS})
|
||||
|
||||
if(DISABLE_BUILD_DATE)
|
||||
message(STATUS "Disabling the inclusion of the binary compile date.")
|
||||
|
||||
@@ -12,21 +12,12 @@ find_path(VTUNE_INCLUDE_DIRS NAMES jitprofiling.h PATHS
|
||||
/opt/intel/vtune_amplifier_xe_2016/include
|
||||
)
|
||||
|
||||
if(${PCSX2_TARGET_ARCHITECTURES} MATCHES "i386")
|
||||
find_library(VTUNE_LIBRARIES NAMES libjitprofiling.a PATHS
|
||||
/opt/intel/oneapi/vtune/latest/lib32
|
||||
/opt/intel/vtune_amplifier_xe_2018/lib32
|
||||
/opt/intel/vtune_amplifier_xe_2017/lib32
|
||||
/opt/intel/vtune_amplifier_xe_2016/lib32
|
||||
)
|
||||
else()
|
||||
find_library(VTUNE_LIBRARIES NAMES libjitprofiling.a PATHS
|
||||
/opt/intel/oneapi/vtune/latest/lib64
|
||||
/opt/intel/vtune_amplifier_xe_2018/lib64
|
||||
/opt/intel/vtune_amplifier_xe_2017/lib64
|
||||
/opt/intel/vtune_amplifier_xe_2016/lib64
|
||||
)
|
||||
endif()
|
||||
find_library(VTUNE_LIBRARIES NAMES libjitprofiling.a PATHS
|
||||
/opt/intel/oneapi/vtune/latest/lib64
|
||||
/opt/intel/vtune_amplifier_xe_2018/lib64
|
||||
/opt/intel/vtune_amplifier_xe_2017/lib64
|
||||
/opt/intel/vtune_amplifier_xe_2016/lib64
|
||||
)
|
||||
|
||||
# handle the QUIETLY and REQUIRED arguments and set VTUNE_FOUND to TRUE if
|
||||
# all listed variables are TRUE
|
||||
@@ -41,4 +32,3 @@ if(VTUNE_LIBRARIES AND NOT TARGET Vtune::Vtune)
|
||||
endif()
|
||||
|
||||
mark_as_advanced(VTUNE_FOUND VTUNE_INCLUDE_DIRS VTUNE_LIBRARIES)
|
||||
|
||||
|
||||
@@ -102,18 +102,7 @@ if(ENABLE_TESTS)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
#----------------------------------------
|
||||
# Check correctness of the parameter
|
||||
# Note: wxWidgets_INCLUDE_DIRS must be defined
|
||||
#----------------------------------------
|
||||
include(ApiValidation)
|
||||
|
||||
# Blacklist bad GCC
|
||||
if(GCC_VERSION VERSION_EQUAL "7.0" OR GCC_VERSION VERSION_EQUAL "7.1")
|
||||
GCC7_BUG()
|
||||
endif()
|
||||
|
||||
if((GCC_VERSION VERSION_EQUAL "9.0" OR GCC_VERSION VERSION_GREATER "9.0") AND GCC_VERSION LESS "9.2")
|
||||
if(GCC_VERSION VERSION_GREATER_EQUAL "9.0" AND GCC_VERSION VERSION_LESS "9.2")
|
||||
message(WARNING "
|
||||
It looks like you are compiling with 9.0.x or 9.1.x. Using these versions is not recommended,
|
||||
as there is a bug known to cause the compiler to segfault while compiling. See patch
|
||||
@@ -134,7 +123,7 @@ find_optional_system_library(libzip 3rdparty/libzip 1.8.0)
|
||||
if(QT_BUILD)
|
||||
# Default to bundled Qt6 for Windows.
|
||||
if(WIN32 AND NOT DEFINED Qt6_DIR)
|
||||
set(Qt6_DIR ${CMAKE_SOURCE_DIR}/3rdparty/qt/6.4.0/msvc2022_64/lib/cmake/Qt6)
|
||||
set(Qt6_DIR ${CMAKE_SOURCE_DIR}/3rdparty/qt/6.5.0/msvc2022_64/lib/cmake/Qt6)
|
||||
endif()
|
||||
|
||||
# Find the Qt components that we need.
|
||||
|
||||
@@ -15,197 +15,211 @@
|
||||
|
||||
#include "common/Perf.h"
|
||||
#include "common/Pcsx2Defs.h"
|
||||
#ifdef __unix__
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include "common/Assertions.h"
|
||||
#include "common/StringUtil.h"
|
||||
|
||||
#ifdef ENABLE_VTUNE
|
||||
#include "jitprofiling.h"
|
||||
#endif
|
||||
|
||||
#include <string> // std::string
|
||||
#include <cstring> // strncpy
|
||||
#include <algorithm> // std::remove_if
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
|
||||
#ifdef __linux__
|
||||
#include <atomic>
|
||||
#include <ctime>
|
||||
#include <mutex>
|
||||
#include <elf.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/syscall.h>
|
||||
#endif
|
||||
|
||||
//#define ProfileWithPerf
|
||||
#define MERGE_BLOCK_RESULT
|
||||
//#define ProfileWithPerfJitDump
|
||||
|
||||
#ifdef ENABLE_VTUNE
|
||||
#ifdef _WIN32
|
||||
#if defined(ENABLE_VTUNE) && defined(_WIN32)
|
||||
#pragma comment(lib, "jitprofiling.lib")
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace Perf
|
||||
{
|
||||
// Warning object aren't thread safe
|
||||
InfoVector any("");
|
||||
InfoVector ee("EE");
|
||||
InfoVector iop("IOP");
|
||||
InfoVector vu("VU");
|
||||
InfoVector vif("VIF");
|
||||
Group any("");
|
||||
Group ee("EE");
|
||||
Group iop("IOP");
|
||||
Group vu0("VU0");
|
||||
Group vu1("VU1");
|
||||
Group vif("VIF");
|
||||
|
||||
// Perf is only supported on linux
|
||||
#if defined(__linux__) && (defined(ProfileWithPerf) || defined(ENABLE_VTUNE))
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Implementation of the Info object
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Info::Info(uptr x86, u32 size, const char* symbol)
|
||||
: m_x86(x86)
|
||||
, m_size(size)
|
||||
, m_dynamic(false)
|
||||
#if defined(__linux__) && defined(ProfileWithPerf)
|
||||
static std::FILE* s_map_file = nullptr;
|
||||
static bool s_map_file_opened = false;
|
||||
static std::mutex s_mutex;
|
||||
static void RegisterMethod(const void* ptr, size_t size, const char* symbol)
|
||||
{
|
||||
strncpy(m_symbol, symbol, sizeof(m_symbol));
|
||||
}
|
||||
std::unique_lock lock(s_mutex);
|
||||
|
||||
Info::Info(uptr x86, u32 size, const char* symbol, u32 pc)
|
||||
: m_x86(x86)
|
||||
, m_size(size)
|
||||
, m_dynamic(true)
|
||||
{
|
||||
snprintf(m_symbol, sizeof(m_symbol), "%s_0x%08x", symbol, pc);
|
||||
}
|
||||
|
||||
void Info::Print(FILE* fp)
|
||||
{
|
||||
fprintf(fp, "%x %x %s\n", m_x86, m_size, m_symbol);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Implementation of the InfoVector object
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
InfoVector::InfoVector(const char* prefix)
|
||||
{
|
||||
strncpy(m_prefix, prefix, sizeof(m_prefix));
|
||||
#ifdef ENABLE_VTUNE
|
||||
m_vtune_id = iJIT_GetNewMethodID();
|
||||
#else
|
||||
m_vtune_id = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void InfoVector::print(FILE* fp)
|
||||
{
|
||||
for (auto&& it : m_v)
|
||||
it.Print(fp);
|
||||
}
|
||||
|
||||
void InfoVector::map(uptr x86, u32 size, const char* symbol)
|
||||
{
|
||||
// This function is typically used for dispatcher and recompiler.
|
||||
// Dispatchers are on a page and must always be kept.
|
||||
// Recompilers are much bigger (TODO check VIF) and are only
|
||||
// useful when MERGE_BLOCK_RESULT is defined
|
||||
#if defined(ENABLE_VTUNE) || !defined(MERGE_BLOCK_RESULT)
|
||||
u32 max_code_size = 16 * _1kb;
|
||||
#else
|
||||
u32 max_code_size = _1gb;
|
||||
#endif
|
||||
|
||||
if (size < max_code_size)
|
||||
if (!s_map_file)
|
||||
{
|
||||
m_v.emplace_back(x86, size, symbol);
|
||||
if (s_map_file_opened)
|
||||
return;
|
||||
|
||||
#ifdef ENABLE_VTUNE
|
||||
std::string name = std::string(symbol);
|
||||
|
||||
iJIT_Method_Load ml;
|
||||
|
||||
memset(&ml, 0, sizeof(ml));
|
||||
|
||||
ml.method_id = iJIT_GetNewMethodID();
|
||||
ml.method_name = (char*)name.c_str();
|
||||
ml.method_load_address = (void*)x86;
|
||||
ml.method_size = size;
|
||||
|
||||
iJIT_NotifyEvent(iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED, &ml);
|
||||
|
||||
//fprintf(stderr, "mapF %s: %p size %dKB\n", ml.method_name, ml.method_load_address, ml.method_size / 1024u);
|
||||
#endif
|
||||
char file[256];
|
||||
snprintf(file, std::size(file), "/tmp/perf-%d.map", getpid());
|
||||
s_map_file = std::fopen(file, "wb");
|
||||
s_map_file_opened = true;
|
||||
if (!s_map_file)
|
||||
return;
|
||||
}
|
||||
|
||||
std::fprintf(s_map_file, "%" PRIx64 " %zx %s\n", static_cast<u64>(reinterpret_cast<uintptr_t>(ptr)), size, symbol);
|
||||
std::fflush(s_map_file);
|
||||
}
|
||||
#elif defined(__linux__) && defined(ProfileWithPerfJitDump)
|
||||
enum : u32
|
||||
{
|
||||
JIT_CODE_LOAD = 0,
|
||||
JIT_CODE_MOVE = 1,
|
||||
JIT_CODE_DEBUG_INFO = 2,
|
||||
JIT_CODE_CLOSE = 3,
|
||||
JIT_CODE_UNWINDING_INFO = 4
|
||||
};
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct JITDUMP_HEADER
|
||||
{
|
||||
u32 magic = 0x4A695444; // JiTD
|
||||
u32 version = 1;
|
||||
u32 header_size = sizeof(JITDUMP_HEADER);
|
||||
u32 elf_mach;
|
||||
u32 pad1 = 0;
|
||||
u32 pid;
|
||||
u64 timestamp;
|
||||
u64 flags = 0;
|
||||
};
|
||||
struct JITDUMP_RECORD_HEADER
|
||||
{
|
||||
u32 id;
|
||||
u32 total_size;
|
||||
u64 timestamp;
|
||||
};
|
||||
struct JITDUMP_CODE_LOAD
|
||||
{
|
||||
JITDUMP_RECORD_HEADER header;
|
||||
u32 pid;
|
||||
u32 tid;
|
||||
u64 vma;
|
||||
u64 code_addr;
|
||||
u64 code_size;
|
||||
u64 code_index;
|
||||
// name
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
static u64 JitDumpTimestamp()
|
||||
{
|
||||
struct timespec ts = {};
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (static_cast<u64>(ts.tv_sec) * 1000000000ULL) + static_cast<u64>(ts.tv_nsec);
|
||||
}
|
||||
|
||||
void InfoVector::map(uptr x86, u32 size, u32 pc)
|
||||
static FILE* s_jitdump_file = nullptr;
|
||||
static bool s_jitdump_file_opened = false;
|
||||
static std::mutex s_jitdump_mutex;
|
||||
static u32 s_jitdump_record_id;
|
||||
|
||||
static void RegisterMethod(const void* ptr, size_t size, const char* symbol)
|
||||
{
|
||||
#ifndef MERGE_BLOCK_RESULT
|
||||
m_v.emplace_back(x86, size, m_prefix, pc);
|
||||
#endif
|
||||
const u32 namelen = std::strlen(symbol) + 1;
|
||||
|
||||
#ifdef ENABLE_VTUNE
|
||||
iJIT_Method_Load_V2 ml;
|
||||
std::unique_lock lock(s_jitdump_mutex);
|
||||
if (!s_jitdump_file)
|
||||
{
|
||||
if (!s_jitdump_file_opened)
|
||||
{
|
||||
char file[256];
|
||||
snprintf(file, std::size(file), "jit-%d.dump", getpid());
|
||||
s_jitdump_file = fopen(file, "w+b");
|
||||
s_jitdump_file_opened = true;
|
||||
if (!s_jitdump_file)
|
||||
return;
|
||||
}
|
||||
|
||||
memset(&ml, 0, sizeof(ml));
|
||||
void* perf_marker = mmap(nullptr, 4096, PROT_READ | PROT_EXEC, MAP_PRIVATE, fileno(s_jitdump_file), 0);
|
||||
pxAssertRel(perf_marker != MAP_FAILED, "Map perf marker");
|
||||
|
||||
#ifdef MERGE_BLOCK_RESULT
|
||||
ml.method_id = m_vtune_id;
|
||||
ml.method_name = m_prefix;
|
||||
#else
|
||||
std::string name = std::string(m_prefix) + "_" + std::to_string(pc);
|
||||
JITDUMP_HEADER jh = {};
|
||||
jh.elf_mach = EM_X86_64;
|
||||
jh.pid = getpid();
|
||||
jh.timestamp = JitDumpTimestamp();
|
||||
std::fwrite(&jh, sizeof(jh), 1, s_jitdump_file);
|
||||
}
|
||||
|
||||
JITDUMP_CODE_LOAD cl = {};
|
||||
cl.header.id = JIT_CODE_LOAD;
|
||||
cl.header.total_size = sizeof(cl) + namelen + static_cast<u32>(size);
|
||||
cl.header.timestamp = JitDumpTimestamp();
|
||||
cl.pid = getpid();
|
||||
cl.tid = syscall(SYS_gettid);
|
||||
cl.vma = 0;
|
||||
cl.code_addr = static_cast<u64>(reinterpret_cast<uintptr_t>(ptr));
|
||||
cl.code_size = static_cast<u64>(size);
|
||||
cl.code_index = s_jitdump_record_id++;
|
||||
std::fwrite(&cl, sizeof(cl), 1, s_jitdump_file);
|
||||
std::fwrite(symbol, namelen, 1, s_jitdump_file);
|
||||
std::fwrite(ptr, size, 1, s_jitdump_file);
|
||||
std::fflush(s_jitdump_file);
|
||||
}
|
||||
#elif defined(ENABLE_VTUNE)
|
||||
static void RegisterMethod(const void* ptr, size_t size, const char* symbol)
|
||||
{
|
||||
iJIT_Method_Load_V2 ml = {};
|
||||
ml.method_id = iJIT_GetNewMethodID();
|
||||
ml.method_name = (char*)name.c_str();
|
||||
#endif
|
||||
ml.method_load_address = (void*)x86;
|
||||
ml.method_size = size;
|
||||
|
||||
ml.method_name = const_cast<char*>(symbol);
|
||||
ml.method_load_address = ptr;
|
||||
ml.method_size = static_cast<unsigned int>(size);
|
||||
iJIT_NotifyEvent(iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED_V2, &ml);
|
||||
|
||||
//fprintf(stderr, "mapB %s: %p size %d\n", ml.method_name, ml.method_load_address, ml.method_size);
|
||||
#endif
|
||||
}
|
||||
|
||||
void InfoVector::reset()
|
||||
{
|
||||
auto dynamic = std::remove_if(m_v.begin(), m_v.end(), [](Info i) { return i.m_dynamic; });
|
||||
m_v.erase(dynamic, m_v.end());
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Global function
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void dump()
|
||||
{
|
||||
char file[256];
|
||||
snprintf(file, 250, "/tmp/perf-%d.map", getpid());
|
||||
FILE* fp = fopen(file, "w");
|
||||
|
||||
any.print(fp);
|
||||
ee.print(fp);
|
||||
iop.print(fp);
|
||||
vu.print(fp);
|
||||
|
||||
if (fp)
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
void dump_and_reset()
|
||||
{
|
||||
dump();
|
||||
|
||||
any.reset();
|
||||
ee.reset();
|
||||
iop.reset();
|
||||
vu.reset();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Dummy implementation
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
InfoVector::InfoVector(const char* prefix)
|
||||
: m_vtune_id(0)
|
||||
static void RegisterMethod(const void* ptr, size_t size, const char* method)
|
||||
{
|
||||
}
|
||||
void InfoVector::map(uptr x86, u32 size, const char* symbol) {}
|
||||
void InfoVector::map(uptr x86, u32 size, u32 pc) {}
|
||||
void InfoVector::reset() {}
|
||||
#endif
|
||||
|
||||
void dump() {}
|
||||
void dump_and_reset() {}
|
||||
#if (defined(__linux__) && (defined(ProfileWithPerf) || defined(ProfileWithPerfJitDump))) || defined(ENABLE_VTUNE)
|
||||
void Group::Register(const void* ptr, size_t size, const char* symbol)
|
||||
{
|
||||
char full_symbol[128];
|
||||
if (HasPrefix())
|
||||
std::snprintf(full_symbol, std::size(full_symbol), "%s_%s", m_prefix, symbol);
|
||||
else
|
||||
StringUtil::Strlcpy(full_symbol, symbol, std::size(full_symbol));
|
||||
RegisterMethod(ptr, size, full_symbol);
|
||||
}
|
||||
|
||||
void Group::RegisterPC(const void* ptr, size_t size, u32 pc)
|
||||
{
|
||||
char full_symbol[128];
|
||||
if (HasPrefix())
|
||||
std::snprintf(full_symbol, std::size(full_symbol), "%s_%08X", m_prefix, pc);
|
||||
else
|
||||
std::snprintf(full_symbol, std::size(full_symbol), "%08X", pc);
|
||||
RegisterMethod(ptr, size, full_symbol);
|
||||
}
|
||||
|
||||
void Group::RegisterKey(const void* ptr, size_t size, const char* prefix, u64 key)
|
||||
{
|
||||
char full_symbol[128];
|
||||
if (HasPrefix())
|
||||
std::snprintf(full_symbol, std::size(full_symbol), "%s_%s%016" PRIX64, m_prefix, prefix, key);
|
||||
else
|
||||
std::snprintf(full_symbol, std::size(full_symbol), "%s%016" PRIX64, prefix, key);
|
||||
RegisterMethod(ptr, size, full_symbol);
|
||||
}
|
||||
#else
|
||||
void Group::Register(const void* ptr, size_t size, const char* symbol) {}
|
||||
void Group::RegisterPC(const void* ptr, size_t size, u32 pc) {}
|
||||
void Group::RegisterKey(const void* ptr, size_t size, const char* prefix, u64 key) {}
|
||||
#endif
|
||||
} // namespace Perf
|
||||
|
||||
@@ -21,42 +21,23 @@
|
||||
|
||||
namespace Perf
|
||||
{
|
||||
|
||||
struct Info
|
||||
class Group
|
||||
{
|
||||
uptr m_x86;
|
||||
u32 m_size;
|
||||
char m_symbol[20];
|
||||
// The idea is to keep static zones that are set only
|
||||
// once.
|
||||
bool m_dynamic;
|
||||
|
||||
Info(uptr x86, u32 size, const char* symbol);
|
||||
Info(uptr x86, u32 size, const char* symbol, u32 pc);
|
||||
void Print(FILE* fp);
|
||||
};
|
||||
|
||||
class InfoVector
|
||||
{
|
||||
std::vector<Info> m_v;
|
||||
char m_prefix[20];
|
||||
unsigned int m_vtune_id;
|
||||
const char* m_prefix;
|
||||
|
||||
public:
|
||||
InfoVector(const char* prefix);
|
||||
constexpr Group(const char* prefix) : m_prefix(prefix) {}
|
||||
bool HasPrefix() const { return (m_prefix && m_prefix[0]); }
|
||||
|
||||
void print(FILE* fp);
|
||||
void map(uptr x86, u32 size, const char* symbol);
|
||||
void map(uptr x86, u32 size, u32 pc);
|
||||
void reset();
|
||||
void Register(const void* ptr, size_t size, const char* symbol);
|
||||
void RegisterPC(const void* ptr, size_t size, u32 pc);
|
||||
void RegisterKey(const void* ptr, size_t size, const char* prefix, u64 key);
|
||||
};
|
||||
|
||||
void dump();
|
||||
void dump_and_reset();
|
||||
|
||||
extern InfoVector any;
|
||||
extern InfoVector ee;
|
||||
extern InfoVector iop;
|
||||
extern InfoVector vu;
|
||||
extern InfoVector vif;
|
||||
extern Group any;
|
||||
extern Group ee;
|
||||
extern Group iop;
|
||||
extern Group vu0;
|
||||
extern Group vu1;
|
||||
extern Group vif;
|
||||
} // namespace Perf
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
|
||||
#include "fmt/format.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "common/RedtapeWindows.h"
|
||||
#else
|
||||
@@ -70,8 +72,7 @@ namespace Vulkan
|
||||
|
||||
Context::~Context() = default;
|
||||
|
||||
VkInstance Context::CreateVulkanInstance(
|
||||
const WindowInfo* wi, bool enable_debug_utils, bool enable_validation_layer)
|
||||
VkInstance Context::CreateVulkanInstance(const WindowInfo& wi, bool enable_debug_utils, bool enable_validation_layer)
|
||||
{
|
||||
ExtensionList enabled_extensions;
|
||||
if (!SelectInstanceExtensions(&enabled_extensions, wi, enable_debug_utils))
|
||||
@@ -118,7 +119,7 @@ namespace Vulkan
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool Context::SelectInstanceExtensions(ExtensionList* extension_list, const WindowInfo* wi, bool enable_debug_utils)
|
||||
bool Context::SelectInstanceExtensions(ExtensionList* extension_list, const WindowInfo& wi, bool enable_debug_utils)
|
||||
{
|
||||
u32 extension_count = 0;
|
||||
VkResult res = vkEnumerateInstanceExtensionProperties(nullptr, &extension_count, nullptr);
|
||||
@@ -155,34 +156,32 @@ namespace Vulkan
|
||||
};
|
||||
|
||||
// Common extensions
|
||||
if (wi && wi->type != WindowInfo::Type::Surfaceless && !SupportsExtension(VK_KHR_SURFACE_EXTENSION_NAME, true))
|
||||
if (wi.type != WindowInfo::Type::Surfaceless && !SupportsExtension(VK_KHR_SURFACE_EXTENSION_NAME, true))
|
||||
return false;
|
||||
|
||||
#if defined(VK_USE_PLATFORM_WIN32_KHR)
|
||||
if (wi && wi->type == WindowInfo::Type::Win32 && !SupportsExtension(VK_KHR_WIN32_SURFACE_EXTENSION_NAME, true))
|
||||
if (wi.type == WindowInfo::Type::Win32 && !SupportsExtension(VK_KHR_WIN32_SURFACE_EXTENSION_NAME, true))
|
||||
return false;
|
||||
#endif
|
||||
#if defined(VK_USE_PLATFORM_XLIB_KHR)
|
||||
if (wi && wi->type == WindowInfo::Type::X11 && !SupportsExtension(VK_KHR_XLIB_SURFACE_EXTENSION_NAME, true))
|
||||
if (wi.type == WindowInfo::Type::X11 && !SupportsExtension(VK_KHR_XLIB_SURFACE_EXTENSION_NAME, true))
|
||||
return false;
|
||||
#endif
|
||||
#if defined(VK_USE_PLATFORM_WAYLAND_KHR)
|
||||
if (wi && wi->type == WindowInfo::Type::Wayland &&
|
||||
!SupportsExtension(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME, true))
|
||||
if (wi.type == WindowInfo::Type::Wayland && !SupportsExtension(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME, true))
|
||||
return false;
|
||||
#endif
|
||||
#if defined(VK_USE_PLATFORM_ANDROID_KHR)
|
||||
if (wi && wi->type == WindowInfo::Type::Android &&
|
||||
!SupportsExtension(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, true))
|
||||
if (wi.type == WindowInfo::Type::Android && !SupportsExtension(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, true))
|
||||
return false;
|
||||
#endif
|
||||
#if defined(VK_USE_PLATFORM_METAL_EXT)
|
||||
if (wi && wi->type == WindowInfo::Type::MacOS && !SupportsExtension(VK_EXT_METAL_SURFACE_EXTENSION_NAME, true))
|
||||
if (wi.type == WindowInfo::Type::MacOS && !SupportsExtension(VK_EXT_METAL_SURFACE_EXTENSION_NAME, true))
|
||||
return false;
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
if (wi && wi->type == WindowInfo::Type::Display && !SupportsExtension(VK_KHR_DISPLAY_EXTENSION_NAME, true))
|
||||
if (wi.type == WindowInfo::Type::Display && !SupportsExtension(VK_KHR_DISPLAY_EXTENSION_NAME, true))
|
||||
return false;
|
||||
#endif
|
||||
|
||||
@@ -195,170 +194,67 @@ namespace Vulkan
|
||||
|
||||
Context::GPUList Context::EnumerateGPUs(VkInstance instance)
|
||||
{
|
||||
GPUList gpus;
|
||||
|
||||
u32 gpu_count = 0;
|
||||
VkResult res = vkEnumeratePhysicalDevices(instance, &gpu_count, nullptr);
|
||||
if ((res != VK_SUCCESS && res != VK_INCOMPLETE) || gpu_count == 0)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkEnumeratePhysicalDevices (1) failed: ");
|
||||
return {};
|
||||
return gpus;
|
||||
}
|
||||
|
||||
GPUList gpus;
|
||||
gpus.resize(gpu_count);
|
||||
|
||||
res = vkEnumeratePhysicalDevices(instance, &gpu_count, gpus.data());
|
||||
std::vector<VkPhysicalDevice> physical_devices(gpu_count);
|
||||
res = vkEnumeratePhysicalDevices(instance, &gpu_count, physical_devices.data());
|
||||
if (res == VK_INCOMPLETE)
|
||||
{
|
||||
Console.Warning("First vkEnumeratePhysicalDevices() call returned %zu devices, but second returned %u", gpus.size(), gpu_count);
|
||||
Console.Warning("First vkEnumeratePhysicalDevices() call returned %zu devices, but second returned %u",
|
||||
physical_devices.size(), gpu_count);
|
||||
}
|
||||
else if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkEnumeratePhysicalDevices (2) failed: ");
|
||||
return {};
|
||||
return gpus;
|
||||
}
|
||||
|
||||
// Maybe we lost a GPU?
|
||||
if (gpu_count < gpus.size())
|
||||
gpus.resize(gpu_count);
|
||||
if (gpu_count < physical_devices.size())
|
||||
physical_devices.resize(gpu_count);
|
||||
|
||||
return gpus;
|
||||
}
|
||||
|
||||
Context::GPUNameList Context::EnumerateGPUNames(VkInstance instance)
|
||||
{
|
||||
u32 gpu_count = 0;
|
||||
VkResult res = vkEnumeratePhysicalDevices(instance, &gpu_count, nullptr);
|
||||
if (res != VK_SUCCESS || gpu_count == 0)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkEnumeratePhysicalDevices failed: ");
|
||||
return {};
|
||||
}
|
||||
|
||||
GPUList gpus;
|
||||
gpus.resize(gpu_count);
|
||||
|
||||
res = vkEnumeratePhysicalDevices(instance, &gpu_count, gpus.data());
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkEnumeratePhysicalDevices failed: ");
|
||||
return {};
|
||||
}
|
||||
|
||||
GPUNameList gpu_names;
|
||||
gpu_names.reserve(gpu_count);
|
||||
for (u32 i = 0; i < gpu_count; i++)
|
||||
gpus.reserve(physical_devices.size());
|
||||
for (VkPhysicalDevice device : physical_devices)
|
||||
{
|
||||
VkPhysicalDeviceProperties props = {};
|
||||
vkGetPhysicalDeviceProperties(gpus[i], &props);
|
||||
vkGetPhysicalDeviceProperties(device, &props);
|
||||
|
||||
std::string gpu_name(props.deviceName);
|
||||
std::string gpu_name = props.deviceName;
|
||||
|
||||
// handle duplicate adapter names
|
||||
if (std::any_of(gpu_names.begin(), gpu_names.end(),
|
||||
[&gpu_name](const std::string& other) { return (gpu_name == other); }))
|
||||
if (std::any_of(gpus.begin(), gpus.end(),
|
||||
[&gpu_name](const auto& other) { return (gpu_name == other.second); }))
|
||||
{
|
||||
std::string original_adapter_name = std::move(gpu_name);
|
||||
|
||||
u32 current_extra = 2;
|
||||
do
|
||||
{
|
||||
gpu_name = StringUtil::StdStringFromFormat("%s (%u)", original_adapter_name.c_str(), current_extra);
|
||||
gpu_name = fmt::format("{} ({})", original_adapter_name, current_extra);
|
||||
current_extra++;
|
||||
} while (std::any_of(gpu_names.begin(), gpu_names.end(),
|
||||
[&gpu_name](const std::string& other) { return (gpu_name == other); }));
|
||||
} while (std::any_of(gpus.begin(), gpus.end(),
|
||||
[&gpu_name](const auto& other) { return (gpu_name == other.second); }));
|
||||
}
|
||||
|
||||
gpu_names.push_back(std::move(gpu_name));
|
||||
gpus.emplace_back(device, std::move(gpu_name));
|
||||
}
|
||||
|
||||
return gpu_names;
|
||||
return gpus;
|
||||
}
|
||||
|
||||
bool Context::Create(std::string_view gpu_name, const WindowInfo* wi, std::unique_ptr<SwapChain>* out_swap_chain,
|
||||
VkPresentModeKHR preferred_present_mode, bool threaded_presentation, bool enable_debug_utils,
|
||||
bool enable_validation_layer)
|
||||
bool Context::Create(VkInstance instance, VkSurfaceKHR surface, VkPhysicalDevice physical_device,
|
||||
bool threaded_presentation, bool enable_debug_utils, bool enable_validation_layer)
|
||||
{
|
||||
pxAssertMsg(!g_vulkan_context, "Has no current context");
|
||||
|
||||
if (!Vulkan::LoadVulkanLibrary())
|
||||
{
|
||||
Console.Error("Failed to load Vulkan library");
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool enable_surface = (wi && wi->type != WindowInfo::Type::Surfaceless);
|
||||
VkInstance instance = CreateVulkanInstance(wi, enable_debug_utils, enable_validation_layer);
|
||||
if (instance == VK_NULL_HANDLE)
|
||||
{
|
||||
if (enable_debug_utils || enable_validation_layer)
|
||||
{
|
||||
// Try again without the validation layer.
|
||||
enable_debug_utils = false;
|
||||
enable_validation_layer = false;
|
||||
instance = CreateVulkanInstance(wi, enable_debug_utils, enable_validation_layer);
|
||||
if (instance == VK_NULL_HANDLE)
|
||||
{
|
||||
Vulkan::UnloadVulkanLibrary();
|
||||
return false;
|
||||
}
|
||||
|
||||
Console.Error("Vulkan validation/debug layers requested but are unavailable. Creating non-debug device.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!Vulkan::LoadVulkanInstanceFunctions(instance))
|
||||
{
|
||||
Console.Error("Failed to load Vulkan instance functions");
|
||||
vkDestroyInstance(instance, nullptr);
|
||||
Vulkan::UnloadVulkanLibrary();
|
||||
return false;
|
||||
}
|
||||
|
||||
GPUList gpus = EnumerateGPUs(instance);
|
||||
if (gpus.empty())
|
||||
{
|
||||
vkDestroyInstance(instance, nullptr);
|
||||
Vulkan::UnloadVulkanLibrary();
|
||||
return false;
|
||||
}
|
||||
|
||||
u32 gpu_index = 0;
|
||||
GPUNameList gpu_names = EnumerateGPUNames(instance);
|
||||
if (!gpu_name.empty())
|
||||
{
|
||||
for (; gpu_index < static_cast<u32>(gpu_names.size()); gpu_index++)
|
||||
{
|
||||
Console.WriteLn("GPU %u: %s", static_cast<u32>(gpu_index), gpu_names[gpu_index].c_str());
|
||||
if (gpu_names[gpu_index] == gpu_name)
|
||||
break;
|
||||
}
|
||||
|
||||
if (gpu_index == static_cast<u32>(gpu_names.size()))
|
||||
{
|
||||
Console.Warning("Requested GPU '%s' not found, using first (%s)", std::string(gpu_name).c_str(),
|
||||
gpu_names[0].c_str());
|
||||
gpu_index = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLn("No GPU requested, using first (%s)", gpu_names[0].c_str());
|
||||
}
|
||||
|
||||
VkSurfaceKHR surface = VK_NULL_HANDLE;
|
||||
WindowInfo wi_copy;
|
||||
if (wi)
|
||||
wi_copy = *wi;
|
||||
|
||||
if (enable_surface &&
|
||||
(surface = SwapChain::CreateVulkanSurface(instance, gpus[gpu_index], &wi_copy)) == VK_NULL_HANDLE)
|
||||
{
|
||||
vkDestroyInstance(instance, nullptr);
|
||||
Vulkan::UnloadVulkanLibrary();
|
||||
return false;
|
||||
}
|
||||
|
||||
g_vulkan_context.reset(new Context(instance, gpus[gpu_index]));
|
||||
g_vulkan_context.reset(new Context(instance, physical_device));
|
||||
|
||||
if (enable_debug_utils)
|
||||
g_vulkan_context->EnableDebugUtils();
|
||||
@@ -367,8 +263,7 @@ namespace Vulkan
|
||||
if (!g_vulkan_context->CreateDevice(surface, enable_validation_layer, nullptr, 0, nullptr, 0, nullptr) ||
|
||||
!g_vulkan_context->CreateAllocator() || !g_vulkan_context->CreateGlobalDescriptorPool() ||
|
||||
!g_vulkan_context->CreateCommandBuffers() || !g_vulkan_context->CreateTextureStreamBuffer() ||
|
||||
!g_vulkan_context->InitSpinResources() ||
|
||||
(enable_surface && (*out_swap_chain = SwapChain::Create(wi_copy, surface, preferred_present_mode)) == nullptr))
|
||||
!g_vulkan_context->InitSpinResources())
|
||||
{
|
||||
// Since we are destroying the instance, we're also responsible for destroying the surface.
|
||||
if (surface != VK_NULL_HANDLE)
|
||||
@@ -468,10 +363,11 @@ namespace Vulkan
|
||||
SupportsExtension(VK_EXT_CALIBRATED_TIMESTAMPS_EXTENSION_NAME, false);
|
||||
m_optional_extensions.vk_ext_line_rasterization =
|
||||
SupportsExtension(VK_EXT_LINE_RASTERIZATION_EXTENSION_NAME, false);
|
||||
m_optional_extensions.vk_ext_rasterization_order_attachment_access =
|
||||
SupportsExtension(VK_EXT_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_EXTENSION_NAME, false) ||
|
||||
SupportsExtension(VK_ARM_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_EXTENSION_NAME, false);
|
||||
m_optional_extensions.vk_khr_driver_properties =
|
||||
SupportsExtension(VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME, false);
|
||||
m_optional_extensions.vk_arm_rasterization_order_attachment_access =
|
||||
SupportsExtension(VK_ARM_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_EXTENSION_NAME, false);
|
||||
m_optional_extensions.vk_khr_fragment_shader_barycentric =
|
||||
SupportsExtension(VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME, false);
|
||||
m_optional_extensions.vk_khr_shader_draw_parameters =
|
||||
@@ -490,7 +386,6 @@ namespace Vulkan
|
||||
|
||||
// Enable the features we use.
|
||||
m_device_features.dualSrcBlend = available_features.dualSrcBlend;
|
||||
m_device_features.geometryShader = available_features.geometryShader;
|
||||
m_device_features.largePoints = available_features.largePoints;
|
||||
m_device_features.wideLines = available_features.wideLines;
|
||||
m_device_features.fragmentStoresAndAtomics = available_features.fragmentStoresAndAtomics;
|
||||
@@ -664,8 +559,8 @@ namespace Vulkan
|
||||
// provoking vertex
|
||||
VkPhysicalDeviceProvokingVertexFeaturesEXT provoking_vertex_feature = {
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_FEATURES_EXT};
|
||||
VkPhysicalDeviceRasterizationOrderAttachmentAccessFeaturesARM rasterization_order_access_feature = {
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_FEATURES_ARM};
|
||||
VkPhysicalDeviceRasterizationOrderAttachmentAccessFeaturesEXT rasterization_order_access_feature = {
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_FEATURES_EXT};
|
||||
VkPhysicalDeviceLineRasterizationFeaturesEXT line_rasterization_feature = {
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINE_RASTERIZATION_FEATURES_EXT};
|
||||
|
||||
@@ -679,7 +574,7 @@ namespace Vulkan
|
||||
line_rasterization_feature.bresenhamLines = VK_TRUE;
|
||||
Util::AddPointerToChain(&device_info, &line_rasterization_feature);
|
||||
}
|
||||
if (m_optional_extensions.vk_arm_rasterization_order_attachment_access)
|
||||
if (m_optional_extensions.vk_ext_rasterization_order_attachment_access)
|
||||
{
|
||||
rasterization_order_access_feature.rasterizationOrderColorAttachmentAccess = VK_TRUE;
|
||||
Util::AddPointerToChain(&device_info, &rasterization_order_access_feature);
|
||||
@@ -745,15 +640,15 @@ namespace Vulkan
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_FEATURES_EXT};
|
||||
VkPhysicalDeviceLineRasterizationFeaturesEXT line_rasterization_feature = {
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINE_RASTERIZATION_FEATURES_EXT};
|
||||
VkPhysicalDeviceRasterizationOrderAttachmentAccessFeaturesARM rasterization_order_access_feature = {
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_FEATURES_ARM};
|
||||
VkPhysicalDeviceRasterizationOrderAttachmentAccessFeaturesEXT rasterization_order_access_feature = {
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_FEATURES_EXT};
|
||||
|
||||
// add in optional feature structs
|
||||
if (m_optional_extensions.vk_ext_provoking_vertex)
|
||||
Util::AddPointerToChain(&features2, &provoking_vertex_features);
|
||||
if (m_optional_extensions.vk_ext_line_rasterization)
|
||||
Util::AddPointerToChain(&features2, &line_rasterization_feature);
|
||||
if (m_optional_extensions.vk_arm_rasterization_order_attachment_access)
|
||||
if (m_optional_extensions.vk_ext_rasterization_order_attachment_access)
|
||||
Util::AddPointerToChain(&features2, &rasterization_order_access_feature);
|
||||
|
||||
// query
|
||||
@@ -761,7 +656,7 @@ namespace Vulkan
|
||||
|
||||
// confirm we actually support it
|
||||
m_optional_extensions.vk_ext_provoking_vertex &= (provoking_vertex_features.provokingVertexLast == VK_TRUE);
|
||||
m_optional_extensions.vk_arm_rasterization_order_attachment_access &= (rasterization_order_access_feature.rasterizationOrderColorAttachmentAccess == VK_TRUE);
|
||||
m_optional_extensions.vk_ext_rasterization_order_attachment_access &= (rasterization_order_access_feature.rasterizationOrderColorAttachmentAccess == VK_TRUE);
|
||||
m_optional_extensions.vk_ext_line_rasterization &= (line_rasterization_feature.bresenhamLines == VK_TRUE);
|
||||
|
||||
VkPhysicalDeviceProperties2 properties2 = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2};
|
||||
@@ -817,8 +712,8 @@ namespace Vulkan
|
||||
m_optional_extensions.vk_ext_line_rasterization ? "supported" : "NOT supported");
|
||||
Console.WriteLn("VK_EXT_calibrated_timestamps is %s",
|
||||
m_optional_extensions.vk_ext_calibrated_timestamps ? "supported" : "NOT supported");
|
||||
Console.WriteLn("VK_ARM_rasterization_order_attachment_access is %s",
|
||||
m_optional_extensions.vk_arm_rasterization_order_attachment_access ? "supported" : "NOT supported");
|
||||
Console.WriteLn("VK_EXT_rasterization_order_attachment_access is %s",
|
||||
m_optional_extensions.vk_ext_rasterization_order_attachment_access ? "supported" : "NOT supported");
|
||||
}
|
||||
|
||||
bool Context::CreateAllocator()
|
||||
@@ -1666,7 +1561,7 @@ namespace Vulkan
|
||||
input_reference.layout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
input_reference_ptr = &input_reference;
|
||||
|
||||
if (!g_vulkan_context->GetOptionalExtensions().vk_arm_rasterization_order_attachment_access)
|
||||
if (!g_vulkan_context->GetOptionalExtensions().vk_ext_rasterization_order_attachment_access)
|
||||
{
|
||||
// don't need the framebuffer-local dependency when we have rasterization order attachment access
|
||||
subpass_dependency.srcSubpass = 0;
|
||||
@@ -1699,7 +1594,9 @@ namespace Vulkan
|
||||
}
|
||||
|
||||
const VkSubpassDescriptionFlags subpass_flags =
|
||||
(key.color_feedback_loop && g_vulkan_context->GetOptionalExtensions().vk_arm_rasterization_order_attachment_access) ? VK_SUBPASS_DESCRIPTION_RASTERIZATION_ORDER_ATTACHMENT_COLOR_ACCESS_BIT_ARM : 0;
|
||||
(key.color_feedback_loop && g_vulkan_context->GetOptionalExtensions().vk_ext_rasterization_order_attachment_access) ?
|
||||
VK_SUBPASS_DESCRIPTION_RASTERIZATION_ORDER_ATTACHMENT_COLOR_ACCESS_BIT_EXT :
|
||||
0;
|
||||
const VkSubpassDescription subpass = {subpass_flags, VK_PIPELINE_BIND_POINT_GRAPHICS, input_reference_ptr ? 1u : 0u,
|
||||
input_reference_ptr ? input_reference_ptr : nullptr, color_reference_ptr ? 1u : 0u,
|
||||
color_reference_ptr ? color_reference_ptr : nullptr, nullptr, depth_reference_ptr, 0, nullptr};
|
||||
|
||||
@@ -53,8 +53,8 @@ namespace Vulkan
|
||||
bool vk_ext_memory_budget : 1;
|
||||
bool vk_ext_calibrated_timestamps : 1;
|
||||
bool vk_ext_line_rasterization : 1;
|
||||
bool vk_ext_rasterization_order_attachment_access : 1;
|
||||
bool vk_khr_driver_properties : 1;
|
||||
bool vk_arm_rasterization_order_attachment_access : 1;
|
||||
bool vk_khr_fragment_shader_barycentric : 1;
|
||||
bool vk_khr_shader_draw_parameters : 1;
|
||||
};
|
||||
@@ -63,18 +63,15 @@ namespace Vulkan
|
||||
|
||||
// Helper method to create a Vulkan instance.
|
||||
static VkInstance CreateVulkanInstance(
|
||||
const WindowInfo* wi, bool enable_debug_utils, bool enable_validation_layer);
|
||||
const WindowInfo& wi, bool enable_debug_utils, bool enable_validation_layer);
|
||||
|
||||
// Returns a list of Vulkan-compatible GPUs.
|
||||
using GPUList = std::vector<VkPhysicalDevice>;
|
||||
using GPUNameList = std::vector<std::string>;
|
||||
using GPUList = std::vector<std::pair<VkPhysicalDevice, std::string>>;
|
||||
static GPUList EnumerateGPUs(VkInstance instance);
|
||||
static GPUNameList EnumerateGPUNames(VkInstance instance);
|
||||
|
||||
// Creates a new context and sets it up as global.
|
||||
static bool Create(std::string_view gpu_name, const WindowInfo* wi, std::unique_ptr<SwapChain>* out_swap_chain,
|
||||
VkPresentModeKHR preferred_present_mode, bool threaded_presentation, bool enable_debug_utils,
|
||||
bool enable_validation_layer);
|
||||
static bool Create(VkInstance instance, VkSurfaceKHR surface, VkPhysicalDevice physical_device,
|
||||
bool threaded_presentation, bool enable_debug_utils, bool enable_validation_layer);
|
||||
|
||||
// Destroys context.
|
||||
static void Destroy();
|
||||
@@ -267,7 +264,7 @@ namespace Vulkan
|
||||
|
||||
using ExtensionList = std::vector<const char*>;
|
||||
static bool SelectInstanceExtensions(
|
||||
ExtensionList* extension_list, const WindowInfo* wi, bool enable_debug_utils);
|
||||
ExtensionList* extension_list, const WindowInfo& wi, bool enable_debug_utils);
|
||||
bool SelectDeviceExtensions(ExtensionList* extension_list, bool enable_surface);
|
||||
bool SelectDeviceFeatures(const VkPhysicalDeviceFeatures* required_features);
|
||||
bool CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer, const char** required_device_extensions,
|
||||
|
||||
@@ -545,9 +545,7 @@ namespace Vulkan
|
||||
// Determine the dimensions of the swap chain. Values of -1 indicate the size we specify here
|
||||
// determines window size?
|
||||
VkExtent2D size = surface_capabilities.currentExtent;
|
||||
#ifndef ANDROID
|
||||
if (size.width == UINT32_MAX)
|
||||
#endif
|
||||
{
|
||||
size.width = m_window_info.surface_width;
|
||||
size.height = m_window_info.surface_height;
|
||||
@@ -562,9 +560,17 @@ namespace Vulkan
|
||||
if (!(surface_capabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR))
|
||||
transform = surface_capabilities.currentTransform;
|
||||
|
||||
VkCompositeAlphaFlagBitsKHR alpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
|
||||
if (!(surface_capabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR))
|
||||
{
|
||||
// If we only support pre-multiplied/post-multiplied... :/
|
||||
if (surface_capabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR)
|
||||
alpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
|
||||
}
|
||||
|
||||
// Select swap chain flags, we only need a colour attachment
|
||||
VkImageUsageFlags image_usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
||||
if (!(surface_capabilities.supportedUsageFlags & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT))
|
||||
VkImageUsageFlags image_usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
if ((surface_capabilities.supportedUsageFlags & image_usage) != image_usage)
|
||||
{
|
||||
Console.Error("Vulkan: Swap chain does not support usage as color attachment");
|
||||
return false;
|
||||
@@ -577,7 +583,7 @@ namespace Vulkan
|
||||
// Now we can actually create the swap chain
|
||||
VkSwapchainCreateInfoKHR swap_chain_info = {VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, nullptr, 0, m_surface,
|
||||
image_count, m_surface_format.format, m_surface_format.colorSpace, size, 1u, image_usage,
|
||||
VK_SHARING_MODE_EXCLUSIVE, 0, nullptr, transform, VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, m_present_mode,
|
||||
VK_SHARING_MODE_EXCLUSIVE, 0, nullptr, transform, alpha, m_present_mode,
|
||||
VK_TRUE, old_swap_chain};
|
||||
std::array<uint32_t, 2> indices = {{
|
||||
g_vulkan_context->GetGraphicsQueueFamilyIndex(),
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Label="UserMacros">
|
||||
<BinaryOutputDir>$(SolutionDir)bin\</BinaryOutputDir>
|
||||
<PCSX2QTDIRDefault Condition="'$(Platform)'=='x64'">$(SolutionDir)3rdparty\qt\6.4.0\msvc2022_64\</PCSX2QTDIRDefault>
|
||||
<PCSX2QTDIRDefault Condition="'$(Platform)'=='ARM64'">$(SolutionDir)3rdparty\qt\6.4.0\msvc2022_arm64\</PCSX2QTDIRDefault>
|
||||
<PCSX2QTDIRDefault Condition="'$(Platform)'=='x64'">$(SolutionDir)3rdparty\qt\6.5.0\msvc2022_64\</PCSX2QTDIRDefault>
|
||||
<PCSX2QTDIRDefault Condition="'$(Platform)'=='ARM64'">$(SolutionDir)3rdparty\qt\6.5.0\msvc2022_arm64\</PCSX2QTDIRDefault>
|
||||
<PCSX2QTDIR Condition="Exists('$(PCSX2QTDIRDefault)') And ('$(PCSX2QTDIR)'=='' Or !Exists('$(PCSX2QTDIR)'))">$(PCSX2QTDIRDefault)</PCSX2QTDIR>
|
||||
<PCSX2QTDIR Condition="Exists('$(PCSX2QTDIR)') And !HasTrailingSlash('$(PCSX2QTDIR)')">$(PCSX2QTDIR)\</PCSX2QTDIR>
|
||||
<PCSX2QTDIRHost>$(SolutionDir)3rdparty\qt\6.4.0\msvc2022_64\</PCSX2QTDIRHost>
|
||||
<PCSX2QTDIRHost>$(SolutionDir)3rdparty\qt\6.5.0\msvc2022_64\</PCSX2QTDIRHost>
|
||||
<QtDirValid>false</QtDirValid>
|
||||
<QtDirValid Condition="Exists('$(PCSX2QTDIR)')">true</QtDirValid>
|
||||
<QtIncludeDir>$(PCSX2QTDIR)include\</QtIncludeDir>
|
||||
|
||||
@@ -254,17 +254,11 @@ void Host::SetRelativeMouseMode(bool enabled)
|
||||
{
|
||||
}
|
||||
|
||||
std::optional<WindowInfo> Host::AcquireRenderWindow()
|
||||
std::optional<WindowInfo> Host::AcquireRenderWindow(bool recreate_window)
|
||||
{
|
||||
return GSRunner::GetPlatformWindowInfo();
|
||||
}
|
||||
|
||||
std::optional<WindowInfo> Host::UpdateRenderWindow(bool recreate_window)
|
||||
{
|
||||
// We shouldn't ever recreate with the runner, so this is okay..
|
||||
return GSRunner::GetPlatformWindowInfo();
|
||||
}
|
||||
|
||||
void Host::ReleaseRenderWindow()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ target_sources(pcsx2-qt PRIVATE
|
||||
MainWindow.ui
|
||||
PrecompiledHeader.cpp
|
||||
PrecompiledHeader.h
|
||||
SettingWidgetBinder.h
|
||||
Themes.cpp
|
||||
QtHost.cpp
|
||||
QtHost.h
|
||||
QtKeyCodes.cpp
|
||||
@@ -31,7 +33,6 @@ target_sources(pcsx2-qt PRIVATE
|
||||
QtProgressCallback.h
|
||||
QtUtils.cpp
|
||||
QtUtils.h
|
||||
SettingWidgetBinder.h
|
||||
GameList/EmptyGameListWidget.ui
|
||||
GameList/GameListModel.cpp
|
||||
GameList/GameListModel.h
|
||||
|
||||
@@ -181,7 +181,7 @@ void GameListWidget::initialize()
|
||||
m_list_view = new GameListGridListView(m_ui.stack);
|
||||
m_list_view->setModel(m_sort_model);
|
||||
m_list_view->setModelColumn(GameListModel::Column_Cover);
|
||||
m_list_view->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
m_list_view->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
m_list_view->setViewMode(QListView::IconMode);
|
||||
m_list_view->setResizeMode(QListView::Adjust);
|
||||
m_list_view->setUniformItemSizes(true);
|
||||
|
||||
@@ -66,7 +66,6 @@
|
||||
#include "pcsx2/Frontend/Achievements.h"
|
||||
#endif
|
||||
|
||||
|
||||
const char* MainWindow::OPEN_FILE_FILTER =
|
||||
QT_TRANSLATE_NOOP("MainWindow", "All File Types (*.bin *.iso *.cue *.chd *.cso *.gz *.elf *.irx *.gs *.gs.xz *.gs.zst *.dump);;"
|
||||
"Single-Track Raw Images (*.bin *.iso);;"
|
||||
@@ -87,16 +86,7 @@ const char* MainWindow::DISC_IMAGE_FILTER = QT_TRANSLATE_NOOP("MainWindow", "All
|
||||
"GZ Images (*.gz);;"
|
||||
"Block Dumps (*.dump)");
|
||||
|
||||
#ifdef __APPLE__
|
||||
const char* MainWindow::DEFAULT_THEME_NAME = "";
|
||||
#else
|
||||
const char* MainWindow::DEFAULT_THEME_NAME = "darkfusion";
|
||||
#endif
|
||||
|
||||
MainWindow* g_main_window = nullptr;
|
||||
static QString s_unthemed_style_name;
|
||||
static QPalette s_unthemed_palette;
|
||||
static bool s_unthemed_style_name_set;
|
||||
|
||||
#if defined(_WIN32) || defined(__APPLE__)
|
||||
static const bool s_use_central_widget = false;
|
||||
@@ -415,10 +405,9 @@ void MainWindow::connectVMThreadSignals(EmuThread* thread)
|
||||
connect(m_ui.actionStartFullscreenUI, &QAction::triggered, thread, &EmuThread::startFullscreenUI);
|
||||
connect(m_ui.actionStartFullscreenUI2, &QAction::triggered, thread, &EmuThread::startFullscreenUI);
|
||||
connect(thread, &EmuThread::messageConfirmed, this, &MainWindow::confirmMessage, Qt::BlockingQueuedConnection);
|
||||
connect(thread, &EmuThread::onCreateDisplayRequested, this, &MainWindow::createDisplayWindow, Qt::BlockingQueuedConnection);
|
||||
connect(thread, &EmuThread::onUpdateDisplayRequested, this, &MainWindow::updateDisplayWindow, Qt::BlockingQueuedConnection);
|
||||
connect(thread, &EmuThread::onDestroyDisplayRequested, this, &MainWindow::destroyDisplay, Qt::BlockingQueuedConnection);
|
||||
connect(thread, &EmuThread::onResizeDisplayRequested, this, &MainWindow::displayResizeRequested);
|
||||
connect(thread, &EmuThread::onAcquireRenderWindowRequested, this, &MainWindow::acquireRenderWindow, Qt::BlockingQueuedConnection);
|
||||
connect(thread, &EmuThread::onReleaseRenderWindowRequested, this, &MainWindow::releaseRenderWindow, Qt::BlockingQueuedConnection);
|
||||
connect(thread, &EmuThread::onResizeRenderWindowRequested, this, &MainWindow::displayResizeRequested);
|
||||
connect(thread, &EmuThread::onRelativeMouseModeRequested, this, &MainWindow::relativeMouseModeRequested);
|
||||
connect(thread, &EmuThread::onVMStarting, this, &MainWindow::onVMStarting);
|
||||
connect(thread, &EmuThread::onVMStarted, this, &MainWindow::onVMStarted);
|
||||
@@ -503,453 +492,7 @@ void MainWindow::resetSettings(bool ui)
|
||||
g_main_window->recreateSettings();
|
||||
}
|
||||
|
||||
void MainWindow::updateApplicationTheme()
|
||||
{
|
||||
if (!s_unthemed_style_name_set)
|
||||
{
|
||||
s_unthemed_style_name_set = true;
|
||||
s_unthemed_style_name = QApplication::style()->objectName();
|
||||
s_unthemed_palette = QApplication::style()->standardPalette();
|
||||
}
|
||||
|
||||
setStyleFromSettings();
|
||||
setIconThemeFromStyle();
|
||||
}
|
||||
|
||||
void MainWindow::setStyleFromSettings()
|
||||
{
|
||||
const std::string theme(Host::GetBaseStringSettingValue("UI", "Theme", DEFAULT_THEME_NAME));
|
||||
|
||||
if (theme == "fusion")
|
||||
{
|
||||
qApp->setPalette(s_unthemed_palette);
|
||||
qApp->setStyleSheet(QString());
|
||||
qApp->setStyle(QStyleFactory::create("Fusion"));
|
||||
}
|
||||
else if (theme == "darkfusion")
|
||||
{
|
||||
// adapted from https://gist.github.com/QuantumCD/6245215
|
||||
qApp->setStyle(QStyleFactory::create("Fusion"));
|
||||
|
||||
const QColor lighterGray(75, 75, 75);
|
||||
const QColor darkGray(53, 53, 53);
|
||||
const QColor gray(128, 128, 128);
|
||||
const QColor black(25, 25, 25);
|
||||
const QColor blue(198, 238, 255);
|
||||
|
||||
QPalette darkPalette;
|
||||
darkPalette.setColor(QPalette::Window, darkGray);
|
||||
darkPalette.setColor(QPalette::WindowText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Base, black);
|
||||
darkPalette.setColor(QPalette::AlternateBase, darkGray);
|
||||
darkPalette.setColor(QPalette::ToolTipBase, darkGray);
|
||||
darkPalette.setColor(QPalette::ToolTipText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Text, Qt::white);
|
||||
darkPalette.setColor(QPalette::Button, darkGray);
|
||||
darkPalette.setColor(QPalette::ButtonText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Link, blue);
|
||||
darkPalette.setColor(QPalette::Highlight, lighterGray);
|
||||
darkPalette.setColor(QPalette::HighlightedText, Qt::white);
|
||||
darkPalette.setColor(QPalette::PlaceholderText, QColor(Qt::white).darker());
|
||||
|
||||
darkPalette.setColor(QPalette::Active, QPalette::Button, darkGray);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, gray);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, gray);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::Text, gray);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::Light, darkGray);
|
||||
|
||||
qApp->setPalette(darkPalette);
|
||||
|
||||
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
|
||||
}
|
||||
else if (theme == "darkfusionblue")
|
||||
{
|
||||
// adapted from https://gist.github.com/QuantumCD/6245215
|
||||
qApp->setStyle(QStyleFactory::create("Fusion"));
|
||||
|
||||
const QColor darkGray(53, 53, 53);
|
||||
const QColor gray(128, 128, 128);
|
||||
const QColor black(25, 25, 25);
|
||||
const QColor blue(198, 238, 255);
|
||||
const QColor blue2(0, 88, 208);
|
||||
|
||||
QPalette darkPalette;
|
||||
darkPalette.setColor(QPalette::Window, darkGray);
|
||||
darkPalette.setColor(QPalette::WindowText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Base, black);
|
||||
darkPalette.setColor(QPalette::AlternateBase, darkGray);
|
||||
darkPalette.setColor(QPalette::ToolTipBase, blue2);
|
||||
darkPalette.setColor(QPalette::ToolTipText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Text, Qt::white);
|
||||
darkPalette.setColor(QPalette::Button, darkGray);
|
||||
darkPalette.setColor(QPalette::ButtonText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Link, blue);
|
||||
darkPalette.setColor(QPalette::Highlight, blue2);
|
||||
darkPalette.setColor(QPalette::HighlightedText, Qt::white);
|
||||
darkPalette.setColor(QPalette::PlaceholderText, QColor(Qt::white).darker());
|
||||
|
||||
darkPalette.setColor(QPalette::Active, QPalette::Button, darkGray);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, gray);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, gray);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::Text, gray);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::Light, darkGray);
|
||||
|
||||
qApp->setPalette(darkPalette);
|
||||
|
||||
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
|
||||
}
|
||||
else if (theme == "UntouchedLagoon")
|
||||
{
|
||||
// Custom palette by RedDevilus, Tame (Light/Washed out) Green as main color and Grayish Blue as complimentary.
|
||||
// Alternative white theme.
|
||||
qApp->setStyle(QStyleFactory::create("Fusion"));
|
||||
|
||||
const QColor black(25, 25, 25);
|
||||
const QColor darkteal(0, 77, 77);
|
||||
const QColor teal(0, 128, 128);
|
||||
const QColor tameTeal(160, 190, 185);
|
||||
const QColor grayBlue(160, 180, 190);
|
||||
|
||||
QPalette standardPalette;
|
||||
standardPalette.setColor(QPalette::Window, tameTeal);
|
||||
standardPalette.setColor(QPalette::WindowText, black.lighter());
|
||||
standardPalette.setColor(QPalette::Base, grayBlue);
|
||||
standardPalette.setColor(QPalette::AlternateBase, tameTeal);
|
||||
standardPalette.setColor(QPalette::ToolTipBase, tameTeal);
|
||||
standardPalette.setColor(QPalette::ToolTipText, grayBlue);
|
||||
standardPalette.setColor(QPalette::Text, black);
|
||||
standardPalette.setColor(QPalette::Button, tameTeal);
|
||||
standardPalette.setColor(QPalette::ButtonText, black);
|
||||
standardPalette.setColor(QPalette::Link, black.lighter());
|
||||
standardPalette.setColor(QPalette::Highlight, teal);
|
||||
standardPalette.setColor(QPalette::HighlightedText, grayBlue.lighter());
|
||||
|
||||
standardPalette.setColor(QPalette::Active, QPalette::Button, tameTeal);
|
||||
standardPalette.setColor(QPalette::Disabled, QPalette::ButtonText, darkteal);
|
||||
standardPalette.setColor(QPalette::Disabled, QPalette::WindowText, darkteal.lighter());
|
||||
standardPalette.setColor(QPalette::Disabled, QPalette::Text, darkteal.lighter());
|
||||
standardPalette.setColor(QPalette::Disabled, QPalette::Light, tameTeal);
|
||||
|
||||
qApp->setPalette(standardPalette);
|
||||
|
||||
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
|
||||
}
|
||||
else if (theme == "BabyPastel")
|
||||
{
|
||||
// Custom palette by RedDevilus, Blue as main color and blue as complimentary.
|
||||
// Alternative light theme.
|
||||
qApp->setStyle(QStyleFactory::create("Fusion"));
|
||||
|
||||
const QColor gray(150, 150, 150);
|
||||
const QColor black(25, 25, 25);
|
||||
const QColor redpinkish(200, 75, 132);
|
||||
const QColor pink(255, 174, 201);
|
||||
const QColor brightPink(255, 230, 255);
|
||||
const QColor congoPink(255, 127, 121);
|
||||
const QColor blue(221, 225, 239);
|
||||
|
||||
QPalette standardPalette;
|
||||
standardPalette.setColor(QPalette::Window, pink);
|
||||
standardPalette.setColor(QPalette::WindowText, black);
|
||||
standardPalette.setColor(QPalette::Base, brightPink);
|
||||
standardPalette.setColor(QPalette::AlternateBase, blue);
|
||||
standardPalette.setColor(QPalette::ToolTipBase, pink);
|
||||
standardPalette.setColor(QPalette::ToolTipText, brightPink);
|
||||
standardPalette.setColor(QPalette::Text, black);
|
||||
standardPalette.setColor(QPalette::Button, pink);
|
||||
standardPalette.setColor(QPalette::ButtonText, black);
|
||||
standardPalette.setColor(QPalette::Link, black);
|
||||
standardPalette.setColor(QPalette::Highlight, congoPink);
|
||||
standardPalette.setColor(QPalette::HighlightedText, black);
|
||||
|
||||
standardPalette.setColor(QPalette::Active, QPalette::Button, pink);
|
||||
standardPalette.setColor(QPalette::Disabled, QPalette::ButtonText, redpinkish);
|
||||
standardPalette.setColor(QPalette::Disabled, QPalette::WindowText, redpinkish);
|
||||
standardPalette.setColor(QPalette::Disabled, QPalette::Text, redpinkish);
|
||||
standardPalette.setColor(QPalette::Disabled, QPalette::Light, gray);
|
||||
|
||||
qApp->setPalette(standardPalette);
|
||||
|
||||
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
|
||||
}
|
||||
else if (theme == "PizzaBrown")
|
||||
{
|
||||
// Custom palette by KamFretoZ, a Pizza Tower Reference!
|
||||
// With a mixtures of Light Brown, Peachy/Creamy White, Latte-like Color.
|
||||
// Thanks to Jordan for the idea :P
|
||||
// Alternative light theme.
|
||||
qApp->setStyle(QStyleFactory::create("Fusion"));
|
||||
|
||||
const QColor gray(128, 128, 128);
|
||||
const QColor extr(248, 192, 88);
|
||||
const QColor main(233, 187, 147);
|
||||
const QColor comp(248, 230, 213);
|
||||
const QColor highlight(188, 100, 60);
|
||||
|
||||
QPalette standardPalette;
|
||||
standardPalette.setColor(QPalette::Window, main);
|
||||
standardPalette.setColor(QPalette::WindowText, Qt::black);
|
||||
standardPalette.setColor(QPalette::Base, comp);
|
||||
standardPalette.setColor(QPalette::AlternateBase, extr);
|
||||
standardPalette.setColor(QPalette::ToolTipBase, comp);
|
||||
standardPalette.setColor(QPalette::ToolTipText, Qt::black);
|
||||
standardPalette.setColor(QPalette::Text, Qt::black);
|
||||
standardPalette.setColor(QPalette::Button, extr);
|
||||
standardPalette.setColor(QPalette::ButtonText, Qt::black);
|
||||
standardPalette.setColor(QPalette::Link, Qt::black);
|
||||
standardPalette.setColor(QPalette::Highlight, highlight);
|
||||
standardPalette.setColor(QPalette::HighlightedText, Qt::white);
|
||||
standardPalette.setColor(QPalette::Active, QPalette::Button, extr);
|
||||
standardPalette.setColor(QPalette::Disabled, QPalette::ButtonText, gray.darker());
|
||||
standardPalette.setColor(QPalette::Disabled, QPalette::WindowText, gray.darker());
|
||||
standardPalette.setColor(QPalette::Disabled, QPalette::Text, Qt::gray);
|
||||
standardPalette.setColor(QPalette::Disabled, QPalette::Light, gray.lighter());
|
||||
|
||||
qApp->setPalette(standardPalette);
|
||||
|
||||
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #cc3f18; border: 1px solid white; }");
|
||||
}
|
||||
else if (theme == "PCSX2Blue")
|
||||
{
|
||||
// Custom palette by RedDevilus, White as main color and Blue as complimentary.
|
||||
// Alternative light theme.
|
||||
qApp->setStyle(QStyleFactory::create("Fusion"));
|
||||
|
||||
const QColor blackish(35, 35, 35);
|
||||
const QColor darkBlue(73, 97, 177);
|
||||
const QColor blue2(80, 120, 200);
|
||||
const QColor blue(106, 156, 255);
|
||||
const QColor lightBlue(130, 155, 241);
|
||||
|
||||
QPalette standardPalette;
|
||||
standardPalette.setColor(QPalette::Window, blue2.lighter());
|
||||
standardPalette.setColor(QPalette::WindowText, blackish);
|
||||
standardPalette.setColor(QPalette::Base, lightBlue);
|
||||
standardPalette.setColor(QPalette::AlternateBase, blue2.lighter());
|
||||
standardPalette.setColor(QPalette::ToolTipBase, blue2);
|
||||
standardPalette.setColor(QPalette::ToolTipText, Qt::white);
|
||||
standardPalette.setColor(QPalette::Text, blackish);
|
||||
standardPalette.setColor(QPalette::Button, blue);
|
||||
standardPalette.setColor(QPalette::ButtonText, blackish);
|
||||
standardPalette.setColor(QPalette::Link, darkBlue);
|
||||
standardPalette.setColor(QPalette::Highlight, Qt::white);
|
||||
standardPalette.setColor(QPalette::HighlightedText, blackish);
|
||||
|
||||
standardPalette.setColor(QPalette::Active, QPalette::Button, blue);
|
||||
standardPalette.setColor(QPalette::Disabled, QPalette::ButtonText, darkBlue);
|
||||
standardPalette.setColor(QPalette::Disabled, QPalette::WindowText, darkBlue);
|
||||
standardPalette.setColor(QPalette::Disabled, QPalette::Text, darkBlue);
|
||||
standardPalette.setColor(QPalette::Disabled, QPalette::Light, darkBlue);
|
||||
|
||||
qApp->setPalette(standardPalette);
|
||||
|
||||
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
|
||||
}
|
||||
else if (theme == "ScarletDevilRed")
|
||||
{
|
||||
// Custom palette by RedDevilus, Red as main color and Purple as complimentary.
|
||||
// Alternative dark theme.
|
||||
qApp->setStyle(QStyleFactory::create("Fusion"));
|
||||
|
||||
const QColor darkRed(80, 45, 69);
|
||||
const QColor purplishRed(120, 45, 69);
|
||||
const QColor brightRed(200, 45, 69);
|
||||
|
||||
QPalette darkPalette;
|
||||
darkPalette.setColor(QPalette::Window, darkRed);
|
||||
darkPalette.setColor(QPalette::WindowText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Base, purplishRed);
|
||||
darkPalette.setColor(QPalette::AlternateBase, darkRed);
|
||||
darkPalette.setColor(QPalette::ToolTipBase, darkRed);
|
||||
darkPalette.setColor(QPalette::ToolTipText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Text, Qt::white);
|
||||
darkPalette.setColor(QPalette::Button, purplishRed.darker());
|
||||
darkPalette.setColor(QPalette::ButtonText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Link, brightRed);
|
||||
darkPalette.setColor(QPalette::Highlight, brightRed);
|
||||
darkPalette.setColor(QPalette::HighlightedText, Qt::white);
|
||||
|
||||
darkPalette.setColor(QPalette::Active, QPalette::Button, purplishRed.darker());
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, brightRed);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, brightRed);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::Text, brightRed);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::Light, darkRed);
|
||||
|
||||
qApp->setPalette(darkPalette);
|
||||
|
||||
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
|
||||
}
|
||||
else if (theme == "CobaltSky")
|
||||
{
|
||||
// Custom palette by KamFretoZ, A soothing deep royal blue
|
||||
// that are meant to be easy on the eyes as the main color.
|
||||
// Alternative dark theme.
|
||||
qApp->setStyle(QStyleFactory::create("Fusion"));
|
||||
|
||||
const QColor gray(192, 192, 192);
|
||||
const QColor royalBlue(29, 41, 81);
|
||||
const QColor darkishBlue(17, 30, 108);
|
||||
const QColor highlight(36, 93, 218);
|
||||
|
||||
QPalette darkPalette;
|
||||
darkPalette.setColor(QPalette::Window, royalBlue);
|
||||
darkPalette.setColor(QPalette::WindowText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Base, royalBlue.lighter());
|
||||
darkPalette.setColor(QPalette::AlternateBase, darkishBlue);
|
||||
darkPalette.setColor(QPalette::ToolTipBase, darkishBlue);
|
||||
darkPalette.setColor(QPalette::ToolTipText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Text, Qt::white);
|
||||
darkPalette.setColor(QPalette::Button, darkishBlue);
|
||||
darkPalette.setColor(QPalette::ButtonText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Link, Qt::white);
|
||||
darkPalette.setColor(QPalette::Highlight, highlight);
|
||||
darkPalette.setColor(QPalette::HighlightedText, Qt::white);
|
||||
|
||||
darkPalette.setColor(QPalette::Active, QPalette::Button, darkishBlue);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, gray);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, gray);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::Text, gray);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::Light, gray);
|
||||
|
||||
qApp->setPalette(darkPalette);
|
||||
|
||||
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
|
||||
}
|
||||
else if (theme == "VioletAngelPurple")
|
||||
{
|
||||
// Custom palette by RedDevilus, Blue as main color and Purple as complimentary.
|
||||
// Alternative dark theme.
|
||||
qApp->setStyle(QStyleFactory::create("Fusion"));
|
||||
|
||||
const QColor blackishblue(50, 25, 70);
|
||||
const QColor darkerPurple(90, 30, 105);
|
||||
const QColor nauticalPurple(110, 30, 125);
|
||||
|
||||
QPalette darkPalette;
|
||||
darkPalette.setColor(QPalette::Window, blackishblue);
|
||||
darkPalette.setColor(QPalette::WindowText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Base, nauticalPurple);
|
||||
darkPalette.setColor(QPalette::AlternateBase, blackishblue);
|
||||
darkPalette.setColor(QPalette::ToolTipBase, nauticalPurple);
|
||||
darkPalette.setColor(QPalette::ToolTipText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Text, Qt::white);
|
||||
darkPalette.setColor(QPalette::Button, nauticalPurple.darker());
|
||||
darkPalette.setColor(QPalette::ButtonText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Link, darkerPurple.lighter());
|
||||
darkPalette.setColor(QPalette::Highlight, darkerPurple.lighter());
|
||||
darkPalette.setColor(QPalette::HighlightedText, Qt::white);
|
||||
|
||||
darkPalette.setColor(QPalette::Active, QPalette::Button, nauticalPurple.darker());
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, darkerPurple.lighter());
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, darkerPurple.lighter());
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::Text, darkerPurple.darker());
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::Light, nauticalPurple);
|
||||
|
||||
qApp->setPalette(darkPalette);
|
||||
|
||||
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
|
||||
}
|
||||
else if (theme == "Ruby")
|
||||
{
|
||||
// Custom palette by Daisouji, Black as main color and Red as complimentary.
|
||||
// Alternative dark (black) theme.
|
||||
qApp->setStyle(QStyleFactory::create("Fusion"));
|
||||
|
||||
const QColor gray(128, 128, 128);
|
||||
const QColor slate(18, 18, 18);
|
||||
const QColor rubyish(172, 21, 31);
|
||||
|
||||
QPalette darkPalette;
|
||||
darkPalette.setColor(QPalette::Window, slate);
|
||||
darkPalette.setColor(QPalette::WindowText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Base, slate.lighter());
|
||||
darkPalette.setColor(QPalette::AlternateBase, slate.lighter());
|
||||
darkPalette.setColor(QPalette::ToolTipBase, slate);
|
||||
darkPalette.setColor(QPalette::ToolTipText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Text, Qt::white);
|
||||
darkPalette.setColor(QPalette::Button, slate);
|
||||
darkPalette.setColor(QPalette::ButtonText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Link, Qt::white);
|
||||
darkPalette.setColor(QPalette::Highlight, rubyish);
|
||||
darkPalette.setColor(QPalette::HighlightedText, Qt::white);
|
||||
|
||||
darkPalette.setColor(QPalette::Active, QPalette::Button, slate);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, gray);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, gray);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::Text, gray);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::Light, slate.lighter());
|
||||
|
||||
qApp->setPalette(darkPalette);
|
||||
|
||||
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
|
||||
}
|
||||
else if (theme == "Sapphire")
|
||||
{
|
||||
// Custom palette by RedDevilus, Black as main color and Blue as complimentary.
|
||||
// Alternative dark (black) theme.
|
||||
qApp->setStyle(QStyleFactory::create("Fusion"));
|
||||
|
||||
const QColor gray(128, 128, 128);
|
||||
const QColor slate(18, 18, 18);
|
||||
const QColor persianBlue(32, 35, 204);
|
||||
|
||||
QPalette darkPalette;
|
||||
darkPalette.setColor(QPalette::Window, slate);
|
||||
darkPalette.setColor(QPalette::WindowText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Base, slate.lighter());
|
||||
darkPalette.setColor(QPalette::AlternateBase, slate.lighter());
|
||||
darkPalette.setColor(QPalette::ToolTipBase, slate);
|
||||
darkPalette.setColor(QPalette::ToolTipText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Text, Qt::white);
|
||||
darkPalette.setColor(QPalette::Button, slate);
|
||||
darkPalette.setColor(QPalette::ButtonText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Link, Qt::white);
|
||||
darkPalette.setColor(QPalette::Highlight, persianBlue);
|
||||
darkPalette.setColor(QPalette::HighlightedText, Qt::white);
|
||||
|
||||
darkPalette.setColor(QPalette::Active, QPalette::Button, slate);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, gray);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, gray);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::Text, gray);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::Light, slate.lighter());
|
||||
|
||||
qApp->setPalette(darkPalette);
|
||||
|
||||
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
|
||||
}
|
||||
else if (theme == "Custom")
|
||||
{
|
||||
|
||||
//Additional Theme option than loads .qss from main PCSX2 Directory
|
||||
qApp->setStyle(QStyleFactory::create("Fusion"));
|
||||
|
||||
QString sheet_content;
|
||||
QFile sheets(QString::fromStdString(Path::Combine(EmuFolders::DataRoot, "custom.qss")));
|
||||
|
||||
if (sheets.open(QFile::ReadOnly))
|
||||
{
|
||||
QString sheet_content = QString::fromUtf8(sheets.readAll().data());
|
||||
qApp->setStyleSheet(sheet_content);
|
||||
}
|
||||
else
|
||||
{
|
||||
qApp->setStyle(QStyleFactory::create("Fusion"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qApp->setPalette(s_unthemed_palette);
|
||||
qApp->setStyleSheet(QString());
|
||||
qApp->setStyle(s_unthemed_style_name);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::setIconThemeFromStyle()
|
||||
{
|
||||
QPalette palette = qApp->palette();
|
||||
bool dark = palette.windowText().color().value() > palette.window().color().value();
|
||||
QIcon::setThemeName(dark ? QStringLiteral("white") : QStringLiteral("black"));
|
||||
}
|
||||
|
||||
void MainWindow::onScreenshotActionTriggered()
|
||||
{
|
||||
@@ -1574,7 +1117,11 @@ void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point)
|
||||
if (m_ui.menuDebug->menuAction()->isVisible())
|
||||
{
|
||||
action = menu.addAction(tr("Boot and Debug"));
|
||||
connect(action, &QAction::triggered, [this, entry]() { DebugInterface::setPauseOnEntry(true); startGameListEntry(entry); getDebuggerWindow()->show(); });
|
||||
connect(action, &QAction::triggered, [this, entry]() {
|
||||
DebugInterface::setPauseOnEntry(true);
|
||||
startGameListEntry(entry);
|
||||
getDebuggerWindow()->show();
|
||||
});
|
||||
}
|
||||
|
||||
menu.addSeparator();
|
||||
@@ -1838,7 +1385,7 @@ void MainWindow::onToolsCoverDownloaderTriggered()
|
||||
|
||||
void MainWindow::updateTheme()
|
||||
{
|
||||
updateApplicationTheme();
|
||||
QtHost::UpdateApplicationTheme();
|
||||
m_game_list_widget->refreshImages();
|
||||
}
|
||||
|
||||
@@ -2200,57 +1747,27 @@ bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, qintptr
|
||||
|
||||
#endif
|
||||
|
||||
std::optional<WindowInfo> MainWindow::createDisplayWindow(bool fullscreen, bool render_to_main)
|
||||
std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool recreate_window, bool fullscreen, bool render_to_main, bool surfaceless)
|
||||
{
|
||||
DevCon.WriteLn(
|
||||
"createDisplayWindow() fullscreen=%s render_to_main=%s", fullscreen ? "true" : "false", render_to_main ? "true" : "false");
|
||||
|
||||
createDisplayWidget(fullscreen, render_to_main);
|
||||
|
||||
// we need the surface visible.. this might be able to be replaced with something else
|
||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
|
||||
std::optional<WindowInfo> wi = m_display_widget->getWindowInfo();
|
||||
if (!wi.has_value())
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("Failed to get window info from widget"));
|
||||
destroyDisplayWidget(true);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
g_emu_thread->connectDisplaySignals(m_display_widget);
|
||||
m_display_created = true;
|
||||
|
||||
updateWindowTitle();
|
||||
updateWindowState();
|
||||
|
||||
m_ui.actionStartFullscreenUI->setEnabled(false);
|
||||
m_ui.actionStartFullscreenUI2->setEnabled(false);
|
||||
|
||||
updateDisplayWidgetCursor();
|
||||
m_display_widget->setFocus();
|
||||
|
||||
return wi;
|
||||
}
|
||||
|
||||
std::optional<WindowInfo> MainWindow::updateDisplayWindow(bool recreate_window, bool fullscreen, bool render_to_main, bool surfaceless)
|
||||
{
|
||||
DevCon.WriteLn("updateDisplayWindow() recreate=%s fullscreen=%s render_to_main=%s surfaceless=%s", recreate_window ? "true" : "false",
|
||||
DevCon.WriteLn("acquireRenderWindow() recreate=%s fullscreen=%s render_to_main=%s surfaceless=%s", recreate_window ? "true" : "false",
|
||||
fullscreen ? "true" : "false", render_to_main ? "true" : "false", surfaceless ? "true" : "false");
|
||||
|
||||
QWidget* container = m_display_container ? static_cast<QWidget*>(m_display_container) : static_cast<QWidget*>(m_display_widget);
|
||||
const bool is_fullscreen = isRenderingFullscreen();
|
||||
const bool is_rendering_to_main = isRenderingToMain();
|
||||
const bool changing_surfaceless = (!m_display_widget != surfaceless);
|
||||
if (!recreate_window && fullscreen == is_fullscreen && is_rendering_to_main == render_to_main && !changing_surfaceless)
|
||||
return m_display_widget->getWindowInfo();
|
||||
if (m_display_created && !recreate_window && fullscreen == is_fullscreen && is_rendering_to_main == render_to_main &&
|
||||
!changing_surfaceless)
|
||||
{
|
||||
return m_display_widget ? m_display_widget->getWindowInfo() : WindowInfo();
|
||||
}
|
||||
|
||||
// Skip recreating the surface if we're just transitioning between fullscreen and windowed with render-to-main off.
|
||||
// .. except on Wayland, where everything tends to break if you don't recreate.
|
||||
const bool has_container = (m_display_container != nullptr);
|
||||
const bool needs_container = DisplayContainer::isNeeded(fullscreen, render_to_main);
|
||||
if (!recreate_window && !is_rendering_to_main && !render_to_main && has_container == needs_container && !needs_container &&
|
||||
!changing_surfaceless)
|
||||
if (m_display_created && !recreate_window && !is_rendering_to_main && !render_to_main && has_container == needs_container &&
|
||||
!needs_container && !changing_surfaceless)
|
||||
{
|
||||
DevCon.WriteLn("Toggling to %s without recreating surface", (fullscreen ? "fullscreen" : "windowed"));
|
||||
|
||||
@@ -2277,6 +1794,7 @@ std::optional<WindowInfo> MainWindow::updateDisplayWindow(bool recreate_window,
|
||||
}
|
||||
|
||||
destroyDisplayWidget(surfaceless);
|
||||
m_display_created = true;
|
||||
|
||||
// if we're going to surfaceless, we're done here
|
||||
if (surfaceless)
|
||||
@@ -2284,10 +1802,13 @@ std::optional<WindowInfo> MainWindow::updateDisplayWindow(bool recreate_window,
|
||||
|
||||
createDisplayWidget(fullscreen, render_to_main);
|
||||
|
||||
// we need the surface visible.. this might be able to be replaced with something else
|
||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
|
||||
std::optional<WindowInfo> wi = m_display_widget->getWindowInfo();
|
||||
if (!wi.has_value())
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("Failed to get new window info from widget"));
|
||||
QMessageBox::critical(this, tr("Error"), tr("Failed to get window info from widget"));
|
||||
destroyDisplayWidget(true);
|
||||
return std::nullopt;
|
||||
}
|
||||
@@ -2297,6 +1818,9 @@ std::optional<WindowInfo> MainWindow::updateDisplayWindow(bool recreate_window,
|
||||
updateWindowTitle();
|
||||
updateWindowState();
|
||||
|
||||
m_ui.actionStartFullscreenUI->setEnabled(false);
|
||||
m_ui.actionStartFullscreenUI2->setEnabled(false);
|
||||
|
||||
updateDisplayWidgetCursor();
|
||||
m_display_widget->setFocus();
|
||||
|
||||
@@ -2406,7 +1930,7 @@ void MainWindow::relativeMouseModeRequested(bool enabled)
|
||||
updateDisplayWidgetCursor();
|
||||
}
|
||||
|
||||
void MainWindow::destroyDisplay()
|
||||
void MainWindow::releaseRenderWindow()
|
||||
{
|
||||
// Now we can safely destroy the display window.
|
||||
destroyDisplayWidget(true);
|
||||
@@ -2958,19 +2482,33 @@ void MainWindow::doDiscChange(CDVD_SourceType source, const QString& path)
|
||||
|
||||
MainWindow::VMLock MainWindow::pauseAndLockVM()
|
||||
{
|
||||
const bool was_fullscreen = isRenderingFullscreen();
|
||||
// To switch out of fullscreen when displaying a popup, or not to?
|
||||
// For Windows, with driver's direct scanout, what renders behind tends to be hit and miss.
|
||||
// We can't draw anything over exclusive fullscreen, so get out of it in that case.
|
||||
// Wayland's a pain as usual, we need to recreate the window, which means there'll be a brief
|
||||
// period when there's no window, and Qt might shut us down. So avoid it there.
|
||||
// On MacOS, it forces a workspace switch, which is kinda jarring.
|
||||
#ifndef __APPLE__
|
||||
const bool was_fullscreen = g_emu_thread->isFullscreen() && !s_use_central_widget;
|
||||
#else
|
||||
const bool was_fullscreen = false;
|
||||
#endif
|
||||
const bool was_paused = s_vm_paused;
|
||||
|
||||
// We use surfaceless rather than switching out of fullscreen, because
|
||||
// we're paused, so we're not going to be rendering anyway.
|
||||
if (was_fullscreen)
|
||||
g_emu_thread->setSurfaceless(true);
|
||||
if (!was_paused)
|
||||
g_emu_thread->setVMPaused(true);
|
||||
|
||||
// We want to parent dialogs to the display widget, except if we were fullscreen,
|
||||
// since it's going to get destroyed by the surfaceless call above.
|
||||
QWidget* dialog_parent = was_fullscreen ? static_cast<QWidget*>(this) : getDisplayContainer();
|
||||
// We need to switch out of exclusive fullscreen before we can display our popup.
|
||||
// However, we do not want to switch back to render-to-main, the window might have generated this event.
|
||||
if (was_fullscreen)
|
||||
{
|
||||
g_emu_thread->setFullscreen(false, false);
|
||||
while (QtHost::IsVMValid() && g_emu_thread->isFullscreen())
|
||||
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1);
|
||||
}
|
||||
|
||||
// Now we'll either have a borderless window, or a regular window (if we were exclusive fullscreen).
|
||||
QWidget* dialog_parent = getDisplayContainer();
|
||||
|
||||
return VMLock(dialog_parent, was_paused, was_fullscreen);
|
||||
}
|
||||
@@ -3000,17 +2538,10 @@ MainWindow::VMLock::VMLock(VMLock&& lock)
|
||||
MainWindow::VMLock::~VMLock()
|
||||
{
|
||||
if (m_was_fullscreen)
|
||||
g_emu_thread->setSurfaceless(false);
|
||||
g_emu_thread->setFullscreen(true, true);
|
||||
|
||||
if (!m_was_paused)
|
||||
g_emu_thread->setVMPaused(false);
|
||||
|
||||
if (m_was_fullscreen)
|
||||
{
|
||||
// Wait until we get the display widget back, so we're not the last window left and closing.
|
||||
while (QtHost::IsVMValid() && !g_main_window->m_display_widget)
|
||||
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::VMLock::cancelResume()
|
||||
|
||||
@@ -69,7 +69,7 @@ public:
|
||||
void cancelResume();
|
||||
|
||||
private:
|
||||
VMLock(QWidget* dialog_parent, bool was_paused, bool was_fullscreen);
|
||||
VMLock(QWidget* dialog_parent, bool was_paused, bool was_exclusive_fullscreen);
|
||||
friend MainWindow;
|
||||
|
||||
QWidget* m_dialog_parent;
|
||||
@@ -77,9 +77,6 @@ public:
|
||||
bool m_was_fullscreen;
|
||||
};
|
||||
|
||||
/// Default theme name for the platform.
|
||||
static const char* DEFAULT_THEME_NAME;
|
||||
|
||||
/// Default filter for opening a file.
|
||||
static const char* OPEN_FILE_FILTER;
|
||||
|
||||
@@ -90,9 +87,6 @@ public:
|
||||
MainWindow();
|
||||
~MainWindow();
|
||||
|
||||
/// Sets application theme according to settings.
|
||||
static void updateApplicationTheme();
|
||||
|
||||
void initialize();
|
||||
void connectVMThreadSignals(EmuThread* thread);
|
||||
void startupUpdateCheck();
|
||||
@@ -128,11 +122,10 @@ public Q_SLOTS:
|
||||
private Q_SLOTS:
|
||||
void onUpdateCheckComplete();
|
||||
|
||||
std::optional<WindowInfo> createDisplayWindow(bool fullscreen, bool render_to_main);
|
||||
std::optional<WindowInfo> updateDisplayWindow(bool recreate_window, bool fullscreen, bool render_to_main, bool surfaceless);
|
||||
std::optional<WindowInfo> acquireRenderWindow(bool recreate_window, bool fullscreen, bool render_to_main, bool surfaceless);
|
||||
void displayResizeRequested(qint32 width, qint32 height);
|
||||
void relativeMouseModeRequested(bool enabled);
|
||||
void destroyDisplay();
|
||||
void releaseRenderWindow();
|
||||
void focusDisplayWidget();
|
||||
|
||||
void onGameListRefreshComplete();
|
||||
|
||||
@@ -438,16 +438,28 @@
|
||||
<property name="text">
|
||||
<string>&GitHub Repository...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="github">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionSupportForums">
|
||||
<property name="text">
|
||||
<string>Support &Forums...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="at">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDiscordServer">
|
||||
<property name="text">
|
||||
<string>&Discord Server...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="discord">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionCheckForUpdates">
|
||||
<property name="icon">
|
||||
@@ -471,6 +483,10 @@
|
||||
<property name="text">
|
||||
<string>&About PCSX2...</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/AppIcon.png</normaloff>:/icons/AppIcon.png</iconset>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionChangeDisc">
|
||||
<property name="icon">
|
||||
|
||||
@@ -196,8 +196,8 @@ void EmuThread::startFullscreenUI(bool fullscreen)
|
||||
// this should just set the flag so it gets automatically started
|
||||
ImGuiManager::InitializeFullscreenUI();
|
||||
m_run_fullscreen_ui = true;
|
||||
if (fullscreen)
|
||||
m_is_fullscreen = true;
|
||||
m_is_rendering_to_main = shouldRenderToMain();
|
||||
m_is_fullscreen = fullscreen;
|
||||
|
||||
if (!GetMTGS().WaitForOpen())
|
||||
{
|
||||
@@ -242,6 +242,7 @@ void EmuThread::startVM(std::shared_ptr<VMBootParameters> boot_params)
|
||||
pxAssertRel(!VMManager::HasValidVM(), "VM is shut down");
|
||||
|
||||
// Determine whether to start fullscreen or not.
|
||||
m_is_rendering_to_main = shouldRenderToMain();
|
||||
if (boot_params->fullscreen.has_value())
|
||||
m_is_fullscreen = boot_params->fullscreen.value();
|
||||
else
|
||||
@@ -492,14 +493,14 @@ void EmuThread::toggleFullscreen()
|
||||
return;
|
||||
}
|
||||
|
||||
setFullscreen(!m_is_fullscreen);
|
||||
setFullscreen(!m_is_fullscreen, true);
|
||||
}
|
||||
|
||||
void EmuThread::setFullscreen(bool fullscreen)
|
||||
void EmuThread::setFullscreen(bool fullscreen, bool allow_render_to_main)
|
||||
{
|
||||
if (!isOnEmuThread())
|
||||
{
|
||||
QMetaObject::invokeMethod(this, "setFullscreen", Qt::QueuedConnection, Q_ARG(bool, fullscreen));
|
||||
QMetaObject::invokeMethod(this, "setFullscreen", Qt::QueuedConnection, Q_ARG(bool, fullscreen), Q_ARG(bool, allow_render_to_main));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -508,6 +509,7 @@ void EmuThread::setFullscreen(bool fullscreen)
|
||||
|
||||
// This will call back to us on the MTGS thread.
|
||||
m_is_fullscreen = fullscreen;
|
||||
m_is_rendering_to_main = allow_render_to_main && shouldRenderToMain();
|
||||
GetMTGS().UpdateDisplayWindow();
|
||||
GetMTGS().WaitGS();
|
||||
|
||||
@@ -893,32 +895,24 @@ void EmuThread::endCapture()
|
||||
GetMTGS().RunOnGSThread(&GSEndCapture);
|
||||
}
|
||||
|
||||
std::optional<WindowInfo> EmuThread::acquireRenderWindow()
|
||||
std::optional<WindowInfo> EmuThread::acquireRenderWindow(bool recreate_window)
|
||||
{
|
||||
m_is_rendering_to_main = shouldRenderToMain();
|
||||
m_is_surfaceless = false;
|
||||
// Check if we're wanting to get exclusive fullscreen. This should be safe to read, since we're going to be calling from the GS thread.
|
||||
m_is_exclusive_fullscreen = m_is_fullscreen && GSWantsExclusiveFullscreen();
|
||||
const bool window_fullscreen = m_is_fullscreen && !m_is_exclusive_fullscreen;
|
||||
const bool render_to_main = !m_is_exclusive_fullscreen && !window_fullscreen && m_is_rendering_to_main;
|
||||
|
||||
return emit onCreateDisplayRequested(m_is_fullscreen, m_is_rendering_to_main);
|
||||
}
|
||||
|
||||
std::optional<WindowInfo> EmuThread::updateRenderWindow(bool recreate_window)
|
||||
{
|
||||
return emit onUpdateDisplayRequested(recreate_window, m_is_fullscreen, !m_is_fullscreen && m_is_rendering_to_main, m_is_surfaceless);
|
||||
return emit onAcquireRenderWindowRequested(recreate_window, window_fullscreen, render_to_main, m_is_surfaceless);
|
||||
}
|
||||
|
||||
void EmuThread::releaseRenderWindow()
|
||||
{
|
||||
emit onDestroyDisplayRequested();
|
||||
emit onReleaseRenderWindowRequested();
|
||||
}
|
||||
|
||||
std::optional<WindowInfo> Host::AcquireRenderWindow()
|
||||
std::optional<WindowInfo> Host::AcquireRenderWindow(bool recreate_window)
|
||||
{
|
||||
return g_emu_thread->acquireRenderWindow();
|
||||
}
|
||||
|
||||
std::optional<WindowInfo> Host::UpdateRenderWindow(bool recreate_window)
|
||||
{
|
||||
return g_emu_thread->updateRenderWindow(recreate_window);
|
||||
return g_emu_thread->acquireRenderWindow(recreate_window);
|
||||
}
|
||||
|
||||
void Host::ReleaseRenderWindow()
|
||||
@@ -932,7 +926,7 @@ void Host::BeginPresentFrame()
|
||||
|
||||
void Host::RequestResizeHostDisplay(s32 width, s32 height)
|
||||
{
|
||||
g_emu_thread->onResizeDisplayRequested(width, height);
|
||||
g_emu_thread->onResizeRenderWindowRequested(width, height);
|
||||
}
|
||||
|
||||
void Host::OnVMStarting()
|
||||
@@ -1194,7 +1188,7 @@ bool Host::IsFullscreen()
|
||||
|
||||
void Host::SetFullscreen(bool enabled)
|
||||
{
|
||||
g_emu_thread->setFullscreen(enabled);
|
||||
g_emu_thread->setFullscreen(enabled, true);
|
||||
}
|
||||
|
||||
alignas(16) static SysMtgsThread s_mtgs_thread;
|
||||
@@ -1250,7 +1244,7 @@ void Host::SetDefaultUISettings(SettingsInterface& si)
|
||||
si.SetBoolValue("UI", "RenderToSeparateWindow", false);
|
||||
si.SetBoolValue("UI", "HideMainWindowWhenRunning", false);
|
||||
si.SetBoolValue("UI", "DisableWindowResize", false);
|
||||
si.SetStringValue("UI", "Theme", MainWindow::DEFAULT_THEME_NAME);
|
||||
si.SetStringValue("UI", "Theme", QtHost::GetDefaultThemeName());
|
||||
}
|
||||
|
||||
void QtHost::SaveSettings()
|
||||
@@ -1797,7 +1791,7 @@ int main(int argc, char* argv[])
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
// Set theme before creating any windows.
|
||||
MainWindow::updateApplicationTheme();
|
||||
QtHost::UpdateApplicationTheme();
|
||||
MainWindow* main_window = new MainWindow();
|
||||
|
||||
// Start up the CPU thread.
|
||||
|
||||
@@ -61,6 +61,7 @@ public:
|
||||
|
||||
__fi QEventLoop* getEventLoop() const { return m_event_loop; }
|
||||
__fi bool isFullscreen() const { return m_is_fullscreen; }
|
||||
__fi bool isExclusiveFullscreen() const { return m_is_exclusive_fullscreen; }
|
||||
__fi bool isRenderingToMain() const { return m_is_rendering_to_main; }
|
||||
__fi bool isSurfaceless() const { return m_is_surfaceless; }
|
||||
__fi bool isRunningFullscreenUI() const { return m_run_fullscreen_ui; }
|
||||
@@ -69,8 +70,7 @@ public:
|
||||
bool shouldRenderToMain() const;
|
||||
|
||||
/// Called back from the GS thread when the display state changes (e.g. fullscreen, render to main).
|
||||
std::optional<WindowInfo> acquireRenderWindow();
|
||||
std::optional<WindowInfo> updateRenderWindow(bool recreate_window);
|
||||
std::optional<WindowInfo> acquireRenderWindow(bool recreate_window);
|
||||
void connectDisplaySignals(DisplayWidget* widget);
|
||||
void releaseRenderWindow();
|
||||
|
||||
@@ -93,7 +93,7 @@ public Q_SLOTS:
|
||||
void saveState(const QString& filename);
|
||||
void saveStateToSlot(qint32 slot);
|
||||
void toggleFullscreen();
|
||||
void setFullscreen(bool fullscreen);
|
||||
void setFullscreen(bool fullscreen, bool allow_render_to_main);
|
||||
void setSurfaceless(bool surfaceless);
|
||||
void applySettings();
|
||||
void reloadGameSettings();
|
||||
@@ -117,10 +117,9 @@ public Q_SLOTS:
|
||||
Q_SIGNALS:
|
||||
bool messageConfirmed(const QString& title, const QString& message);
|
||||
|
||||
std::optional<WindowInfo> onCreateDisplayRequested(bool fullscreen, bool render_to_main);
|
||||
std::optional<WindowInfo> onUpdateDisplayRequested(bool recreate_window, bool fullscreen, bool render_to_main, bool surfaceless);
|
||||
void onResizeDisplayRequested(qint32 width, qint32 height);
|
||||
void onDestroyDisplayRequested();
|
||||
std::optional<WindowInfo> onAcquireRenderWindowRequested(bool recreate_window, bool fullscreen, bool render_to_main, bool surfaceless);
|
||||
void onResizeRenderWindowRequested(qint32 width, qint32 height);
|
||||
void onReleaseRenderWindowRequested();
|
||||
void onRelativeMouseModeRequested(bool enabled);
|
||||
|
||||
/// Called when the VM is starting initialization, but has not been completed yet.
|
||||
@@ -195,6 +194,7 @@ private:
|
||||
bool m_run_fullscreen_ui = false;
|
||||
bool m_is_rendering_to_main = false;
|
||||
bool m_is_fullscreen = false;
|
||||
bool m_is_exclusive_fullscreen = false;
|
||||
bool m_is_surfaceless = false;
|
||||
bool m_save_state_on_shutdown = false;
|
||||
bool m_pause_on_focus_loss = false;
|
||||
@@ -213,6 +213,12 @@ extern EmuThread* g_emu_thread;
|
||||
|
||||
namespace QtHost
|
||||
{
|
||||
/// Default theme name for the platform.
|
||||
const char* GetDefaultThemeName();
|
||||
|
||||
/// Sets application theme according to settings.
|
||||
void UpdateApplicationTheme();
|
||||
|
||||
/// Sets batch mode (exit after game shutdown).
|
||||
bool InBatchMode();
|
||||
|
||||
|
||||
@@ -37,13 +37,19 @@ ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent,
|
||||
#ifdef SDL_BUILD
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableSDLSource, "InputSources", "SDL", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableSDLEnhancedMode, "InputSources", "SDLControllerEnhancedMode", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableSDLRawInput, "InputSources", "SDLRawInput", false);
|
||||
connect(m_ui.enableSDLSource, &QCheckBox::stateChanged, this, &ControllerGlobalSettingsWidget::updateSDLOptionsEnabled);
|
||||
connect(m_ui.ledSettings, &QToolButton::clicked, this, &ControllerGlobalSettingsWidget::ledSettingsClicked);
|
||||
#else
|
||||
m_ui.enableSDLSource->setEnabled(false);
|
||||
m_ui.ledSettings->setEnabled(false);
|
||||
m_ui.enableSDLRawInput->setEnabled(false);
|
||||
#endif
|
||||
|
||||
#if defined(SDL_BUILD) && defined(_WIN32)
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableSDLRawInput, "InputSources", "SDLRawInput", false);
|
||||
#else
|
||||
m_ui.gridLayout_2->removeWidget(m_ui.enableSDLRawInput);
|
||||
m_ui.enableSDLRawInput->deleteLater();
|
||||
m_ui.enableSDLRawInput = nullptr;
|
||||
#endif
|
||||
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableMouseMapping, "UI", "EnableMouseMapping", false);
|
||||
@@ -62,7 +68,6 @@ ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent,
|
||||
m_ui.mainLayout->removeWidget(m_ui.dinputGroup);
|
||||
m_ui.dinputGroup->deleteLater();
|
||||
m_ui.dinputGroup = nullptr;
|
||||
m_ui.enableSDLRawInput->hide();
|
||||
#endif
|
||||
|
||||
if (dialog->isEditingProfile())
|
||||
@@ -116,7 +121,9 @@ void ControllerGlobalSettingsWidget::updateSDLOptionsEnabled()
|
||||
const bool enabled = m_ui.enableSDLSource->isChecked();
|
||||
m_ui.enableSDLEnhancedMode->setEnabled(enabled);
|
||||
m_ui.ledSettings->setEnabled(enabled);
|
||||
#ifdef _WIN32
|
||||
m_ui.enableSDLRawInput->setEnabled(enabled);
|
||||
#endif
|
||||
}
|
||||
|
||||
void ControllerGlobalSettingsWidget::ledSettingsClicked()
|
||||
|
||||
@@ -189,7 +189,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget*
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.gpuTargetCLUTMode, "EmuCore/GS", "UserHacks_GPUTargetCLUTMode", 0);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.skipDrawStart, "EmuCore/GS", "UserHacks_SkipDraw_Start", 0);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.skipDrawEnd, "EmuCore/GS", "UserHacks_SkipDraw_End", 0);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.hwAutoFlush, "EmuCore/GS", "UserHacks_AutoFlush", false);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.hwAutoFlush, "EmuCore/GS", "UserHacks_AutoFlushLevel", 0);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.frameBufferConversion, "EmuCore/GS", "UserHacks_CPU_FB_Conversion", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.disableDepthEmulation, "EmuCore/GS", "UserHacks_DisableDepthSupport", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.disableSafeFeatures, "EmuCore/GS", "UserHacks_Disable_Safe_Features", false);
|
||||
@@ -662,7 +662,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget*
|
||||
dialog->registerWidgetHelp(m_ui.osdShowGSStats, tr("Show Statistics"), tr("Unchecked"),
|
||||
tr("Shows counters for internal graphical utilization, useful for debugging."));
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.osdShowIndicators, tr("Show Indicators"), tr("Unchecked"),
|
||||
dialog->registerWidgetHelp(m_ui.osdShowIndicators, tr("Show Indicators"), tr("Checked"),
|
||||
tr("Shows OSD icon indicators for emulation states such as Pausing, Turbo, Fast-Forward, and Slow-Motion."));
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.osdShowSettings, tr("Show Settings"), tr("Unchecked"),
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>720</width>
|
||||
<height>476</height>
|
||||
<height>498</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -780,222 +780,6 @@
|
||||
<string>Hardware Fixes</string>
|
||||
</attribute>
|
||||
<layout class="QFormLayout" name="hardwareFixesLayout">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string>Half Screen Fix:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="halfScreenFix">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Automatic (Default)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Force Disabled</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Force Enabled</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_36">
|
||||
<property name="text">
|
||||
<string>CPU Sprite Render Size:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_16">
|
||||
<property name="text">
|
||||
<string>Software CLUT Render:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="cpuCLUTRender">
|
||||
<property name="currentText">
|
||||
<string extracomment="0 (Disabled)">0 (Disabled)</string>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>0 (Disabled)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>1 (Normal)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>2 (Aggressive)</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_47">
|
||||
<property name="text">
|
||||
<string extracomment="CLUT: Color Look Up Table, often referred to as a palette in non-PS2 things. GPU Target CLUT: GPU handling of when a game uses data from a render target as a CLUT.">GPU Target CLUT:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QComboBox" name="gpuTargetCLUTMode">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Disabled (Default)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Enabled (Exact Match)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Enabled (Check Inside Target)</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_45">
|
||||
<property name="text">
|
||||
<string>Texture Inside RT:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QComboBox" name="textureInsideRt">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Disabled (Default)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Inside Target</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Merge Targets</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_12">
|
||||
<property name="text">
|
||||
<string>Skipdraw Range:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="skipDrawStart">
|
||||
<property name="maximum">
|
||||
<number>10000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="skipDrawEnd">
|
||||
<property name="maximum">
|
||||
<number>10000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="7" column="0" colspan="2">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="hwAutoFlush">
|
||||
<property name="text">
|
||||
<string>Auto Flush</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="disableSafeFeatures">
|
||||
<property name="text">
|
||||
<string>Disable Safe Features</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="disableDepthEmulation">
|
||||
<property name="text">
|
||||
<string>Disable Depth Emulation</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="frameBufferConversion">
|
||||
<property name="text">
|
||||
<string>Frame Buffer Conversion</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="readTCOnClose">
|
||||
<property name="text">
|
||||
<string>Read Targets When Closing</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QCheckBox" name="disablePartialInvalidation">
|
||||
<property name="text">
|
||||
<string>Disable Partial Source Invalidation</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QCheckBox" name="targetPartialInvalidation">
|
||||
<property name="text">
|
||||
<string>Target Partial Invalidation</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="preloadFrameData">
|
||||
<property name="text">
|
||||
<string>Preload Frame Data</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QCheckBox" name="estimateTextureRegion">
|
||||
<property name="text">
|
||||
<string>Estimate Texture Region</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QCheckBox" name="gpuPaletteConversion">
|
||||
<property name="text">
|
||||
<string>GPU Palette Conversion</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
@@ -1037,6 +821,39 @@
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string>Half Screen Fix:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="halfScreenFix">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Automatic (Default)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Force Disabled</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Force Enabled</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_36">
|
||||
<property name="text">
|
||||
<string>CPU Sprite Render Size:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5" stretch="1,0">
|
||||
<item>
|
||||
@@ -1119,6 +936,208 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_16">
|
||||
<property name="text">
|
||||
<string>Software CLUT Render:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="cpuCLUTRender">
|
||||
<property name="currentText">
|
||||
<string extracomment="0 (Disabled)">0 (Disabled)</string>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>0 (Disabled)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>1 (Normal)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>2 (Aggressive)</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_47">
|
||||
<property name="text">
|
||||
<string extracomment="CLUT: Color Look Up Table, often referred to as a palette in non-PS2 things. GPU Target CLUT: GPU handling of when a game uses data from a render target as a CLUT.">GPU Target CLUT:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QComboBox" name="gpuTargetCLUTMode">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Disabled (Default)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Enabled (Exact Match)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Enabled (Check Inside Target)</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_45">
|
||||
<property name="text">
|
||||
<string>Texture Inside RT:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QComboBox" name="textureInsideRt">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Disabled (Default)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Inside Target</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Merge Targets</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_30">
|
||||
<property name="text">
|
||||
<string>Auto Flush:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QComboBox" name="hwAutoFlush">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Disabled (Default)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Enabled (Sprites Only)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Enabled (All Primitives)</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="label_12">
|
||||
<property name="text">
|
||||
<string>Skipdraw Range:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="skipDrawStart">
|
||||
<property name="maximum">
|
||||
<number>10000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="skipDrawEnd">
|
||||
<property name="maximum">
|
||||
<number>10000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="8" column="0" colspan="2">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="frameBufferConversion">
|
||||
<property name="text">
|
||||
<string>Frame Buffer Conversion</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="readTCOnClose">
|
||||
<property name="text">
|
||||
<string>Read Targets When Closing</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QCheckBox" name="targetPartialInvalidation">
|
||||
<property name="text">
|
||||
<string>Target Partial Invalidation</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="preloadFrameData">
|
||||
<property name="text">
|
||||
<string>Preload Frame Data</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="disableDepthEmulation">
|
||||
<property name="text">
|
||||
<string>Disable Depth Emulation</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="disableSafeFeatures">
|
||||
<property name="text">
|
||||
<string>Disable Safe Features</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="disablePartialInvalidation">
|
||||
<property name="text">
|
||||
<string>Disable Partial Source Invalidation</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QCheckBox" name="estimateTextureRegion">
|
||||
<property name="text">
|
||||
<string>Estimate Texture Region</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QCheckBox" name="gpuPaletteConversion">
|
||||
<property name="text">
|
||||
<string>GPU Palette Conversion</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QGroupBox" name="upscalingFixesTab">
|
||||
|
||||
@@ -20,11 +20,12 @@
|
||||
#include "MainWindow.h"
|
||||
#include "SettingWidgetBinder.h"
|
||||
#include "SettingsDialog.h"
|
||||
#include "QtHost.h"
|
||||
|
||||
static const char* THEME_NAMES[] = {
|
||||
QT_TRANSLATE_NOOP("InterfaceSettingsWidget", "Native"),
|
||||
//: Ignore what Crowdin says in this string about "[Light]/[Dark]" being untouchable here, these are not variables in this case and must be translated.
|
||||
QT_TRANSLATE_NOOP("InterfaceSettingsWidget", "Fusion [Light]"),
|
||||
QT_TRANSLATE_NOOP("InterfaceSettingsWidget", "Fusion [Light/Dark]"),
|
||||
//: Ignore what Crowdin says in this string about "[Light]/[Dark]" being untouchable here, these are not variables in this case and must be translated.
|
||||
QT_TRANSLATE_NOOP("InterfaceSettingsWidget", "Dark Fusion (Gray) [Dark]"),
|
||||
//: Ignore what Crowdin says in this string about "[Light]/[Dark]" being untouchable here, these are not variables in this case and must be translated.
|
||||
@@ -91,7 +92,7 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsDialog* dialog, QWidget
|
||||
connect(m_ui.renderToSeparateWindow, &QCheckBox::stateChanged, this, &InterfaceSettingsWidget::onRenderToSeparateWindowChanged);
|
||||
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.theme, "UI", "Theme", THEME_NAMES, THEME_VALUES,
|
||||
MainWindow::DEFAULT_THEME_NAME);
|
||||
QtHost::GetDefaultThemeName());
|
||||
connect(m_ui.theme, QOverload<int>::of(&QComboBox::currentIndexChanged), [this]() { emit themeChanged(); });
|
||||
|
||||
// Per-game settings is special, we don't want to bind it if we're editing per-game settings.
|
||||
|
||||
@@ -0,0 +1,495 @@
|
||||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2023 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 "QtHost.h"
|
||||
|
||||
#include "pcsx2/Config.h"
|
||||
#include "pcsx2/HostSettings.h"
|
||||
|
||||
#include "common/Path.h"
|
||||
|
||||
#include <QtGui/QPalette>
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtWidgets/QStyle>
|
||||
#include <QtWidgets/QStyleFactory>
|
||||
|
||||
namespace QtHost
|
||||
{
|
||||
static void SetStyleFromSettings();
|
||||
static void SetIconThemeFromStyle();
|
||||
} // namespace QtHost
|
||||
|
||||
static QString s_unthemed_style_name;
|
||||
static bool s_unthemed_style_name_set;
|
||||
|
||||
const char* QtHost::GetDefaultThemeName()
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
return "";
|
||||
#else
|
||||
return "darkfusion";
|
||||
#endif
|
||||
}
|
||||
|
||||
void QtHost::UpdateApplicationTheme()
|
||||
{
|
||||
if (!s_unthemed_style_name_set)
|
||||
{
|
||||
s_unthemed_style_name_set = true;
|
||||
s_unthemed_style_name = QApplication::style()->objectName();
|
||||
}
|
||||
|
||||
SetStyleFromSettings();
|
||||
SetIconThemeFromStyle();
|
||||
}
|
||||
|
||||
void QtHost::SetStyleFromSettings()
|
||||
{
|
||||
const std::string theme(Host::GetBaseStringSettingValue("UI", "Theme", GetDefaultThemeName()));
|
||||
|
||||
if (theme == "fusion")
|
||||
{
|
||||
// setPalette() shouldn't be necessary, as the documentation claims that setStyle() resets the palette, but it
|
||||
// is here, to work around a bug in 6.4.x and 6.5.x where the palette doesn't restore after changing themes.
|
||||
qApp->setPalette(QPalette());
|
||||
qApp->setStyle(QStyleFactory::create("Fusion"));
|
||||
qApp->setStyleSheet(QString());
|
||||
}
|
||||
else if (theme == "darkfusion")
|
||||
{
|
||||
// adapted from https://gist.github.com/QuantumCD/6245215
|
||||
qApp->setStyle(QStyleFactory::create("Fusion"));
|
||||
|
||||
const QColor lighterGray(75, 75, 75);
|
||||
const QColor darkGray(53, 53, 53);
|
||||
const QColor gray(128, 128, 128);
|
||||
const QColor black(25, 25, 25);
|
||||
const QColor blue(198, 238, 255);
|
||||
|
||||
QPalette darkPalette;
|
||||
darkPalette.setColor(QPalette::Window, darkGray);
|
||||
darkPalette.setColor(QPalette::WindowText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Base, black);
|
||||
darkPalette.setColor(QPalette::AlternateBase, darkGray);
|
||||
darkPalette.setColor(QPalette::ToolTipBase, darkGray);
|
||||
darkPalette.setColor(QPalette::ToolTipText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Text, Qt::white);
|
||||
darkPalette.setColor(QPalette::Button, darkGray);
|
||||
darkPalette.setColor(QPalette::ButtonText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Link, blue);
|
||||
darkPalette.setColor(QPalette::Highlight, lighterGray);
|
||||
darkPalette.setColor(QPalette::HighlightedText, Qt::white);
|
||||
darkPalette.setColor(QPalette::PlaceholderText, QColor(Qt::white).darker());
|
||||
|
||||
darkPalette.setColor(QPalette::Active, QPalette::Button, darkGray);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, gray);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, gray);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::Text, gray);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::Light, darkGray);
|
||||
|
||||
qApp->setPalette(darkPalette);
|
||||
|
||||
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
|
||||
}
|
||||
else if (theme == "darkfusionblue")
|
||||
{
|
||||
// adapted from https://gist.github.com/QuantumCD/6245215
|
||||
qApp->setStyle(QStyleFactory::create("Fusion"));
|
||||
|
||||
const QColor darkGray(53, 53, 53);
|
||||
const QColor gray(128, 128, 128);
|
||||
const QColor black(25, 25, 25);
|
||||
const QColor blue(198, 238, 255);
|
||||
const QColor blue2(0, 88, 208);
|
||||
|
||||
QPalette darkPalette;
|
||||
darkPalette.setColor(QPalette::Window, darkGray);
|
||||
darkPalette.setColor(QPalette::WindowText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Base, black);
|
||||
darkPalette.setColor(QPalette::AlternateBase, darkGray);
|
||||
darkPalette.setColor(QPalette::ToolTipBase, blue2);
|
||||
darkPalette.setColor(QPalette::ToolTipText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Text, Qt::white);
|
||||
darkPalette.setColor(QPalette::Button, darkGray);
|
||||
darkPalette.setColor(QPalette::ButtonText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Link, blue);
|
||||
darkPalette.setColor(QPalette::Highlight, blue2);
|
||||
darkPalette.setColor(QPalette::HighlightedText, Qt::white);
|
||||
darkPalette.setColor(QPalette::PlaceholderText, QColor(Qt::white).darker());
|
||||
|
||||
darkPalette.setColor(QPalette::Active, QPalette::Button, darkGray);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, gray);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, gray);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::Text, gray);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::Light, darkGray);
|
||||
|
||||
qApp->setPalette(darkPalette);
|
||||
|
||||
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
|
||||
}
|
||||
else if (theme == "UntouchedLagoon")
|
||||
{
|
||||
// Custom palette by RedDevilus, Tame (Light/Washed out) Green as main color and Grayish Blue as complimentary.
|
||||
// Alternative white theme.
|
||||
qApp->setStyle(QStyleFactory::create("Fusion"));
|
||||
|
||||
const QColor black(25, 25, 25);
|
||||
const QColor darkteal(0, 77, 77);
|
||||
const QColor teal(0, 128, 128);
|
||||
const QColor tameTeal(160, 190, 185);
|
||||
const QColor grayBlue(160, 180, 190);
|
||||
|
||||
QPalette standardPalette;
|
||||
standardPalette.setColor(QPalette::Window, tameTeal);
|
||||
standardPalette.setColor(QPalette::WindowText, black.lighter());
|
||||
standardPalette.setColor(QPalette::Base, grayBlue);
|
||||
standardPalette.setColor(QPalette::AlternateBase, tameTeal);
|
||||
standardPalette.setColor(QPalette::ToolTipBase, tameTeal);
|
||||
standardPalette.setColor(QPalette::ToolTipText, grayBlue);
|
||||
standardPalette.setColor(QPalette::Text, black);
|
||||
standardPalette.setColor(QPalette::Button, tameTeal);
|
||||
standardPalette.setColor(QPalette::ButtonText, black);
|
||||
standardPalette.setColor(QPalette::Link, black.lighter());
|
||||
standardPalette.setColor(QPalette::Highlight, teal);
|
||||
standardPalette.setColor(QPalette::HighlightedText, grayBlue.lighter());
|
||||
|
||||
standardPalette.setColor(QPalette::Active, QPalette::Button, tameTeal);
|
||||
standardPalette.setColor(QPalette::Disabled, QPalette::ButtonText, darkteal);
|
||||
standardPalette.setColor(QPalette::Disabled, QPalette::WindowText, darkteal.lighter());
|
||||
standardPalette.setColor(QPalette::Disabled, QPalette::Text, darkteal.lighter());
|
||||
standardPalette.setColor(QPalette::Disabled, QPalette::Light, tameTeal);
|
||||
|
||||
qApp->setPalette(standardPalette);
|
||||
|
||||
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
|
||||
}
|
||||
else if (theme == "BabyPastel")
|
||||
{
|
||||
// Custom palette by RedDevilus, Blue as main color and blue as complimentary.
|
||||
// Alternative light theme.
|
||||
qApp->setStyle(QStyleFactory::create("Fusion"));
|
||||
|
||||
const QColor gray(150, 150, 150);
|
||||
const QColor black(25, 25, 25);
|
||||
const QColor redpinkish(200, 75, 132);
|
||||
const QColor pink(255, 174, 201);
|
||||
const QColor brightPink(255, 230, 255);
|
||||
const QColor congoPink(255, 127, 121);
|
||||
const QColor blue(221, 225, 239);
|
||||
|
||||
QPalette standardPalette;
|
||||
standardPalette.setColor(QPalette::Window, pink);
|
||||
standardPalette.setColor(QPalette::WindowText, black);
|
||||
standardPalette.setColor(QPalette::Base, brightPink);
|
||||
standardPalette.setColor(QPalette::AlternateBase, blue);
|
||||
standardPalette.setColor(QPalette::ToolTipBase, pink);
|
||||
standardPalette.setColor(QPalette::ToolTipText, brightPink);
|
||||
standardPalette.setColor(QPalette::Text, black);
|
||||
standardPalette.setColor(QPalette::Button, pink);
|
||||
standardPalette.setColor(QPalette::ButtonText, black);
|
||||
standardPalette.setColor(QPalette::Link, black);
|
||||
standardPalette.setColor(QPalette::Highlight, congoPink);
|
||||
standardPalette.setColor(QPalette::HighlightedText, black);
|
||||
|
||||
standardPalette.setColor(QPalette::Active, QPalette::Button, pink);
|
||||
standardPalette.setColor(QPalette::Disabled, QPalette::ButtonText, redpinkish);
|
||||
standardPalette.setColor(QPalette::Disabled, QPalette::WindowText, redpinkish);
|
||||
standardPalette.setColor(QPalette::Disabled, QPalette::Text, redpinkish);
|
||||
standardPalette.setColor(QPalette::Disabled, QPalette::Light, gray);
|
||||
|
||||
qApp->setPalette(standardPalette);
|
||||
|
||||
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
|
||||
}
|
||||
else if (theme == "PizzaBrown")
|
||||
{
|
||||
// Custom palette by KamFretoZ, a Pizza Tower Reference!
|
||||
// With a mixtures of Light Brown, Peachy/Creamy White, Latte-like Color.
|
||||
// Thanks to Jordan for the idea :P
|
||||
// Alternative light theme.
|
||||
qApp->setStyle(QStyleFactory::create("Fusion"));
|
||||
|
||||
const QColor gray(128, 128, 128);
|
||||
const QColor extr(248, 192, 88);
|
||||
const QColor main(233, 187, 147);
|
||||
const QColor comp(248, 230, 213);
|
||||
const QColor highlight(188, 100, 60);
|
||||
|
||||
QPalette standardPalette;
|
||||
standardPalette.setColor(QPalette::Window, main);
|
||||
standardPalette.setColor(QPalette::WindowText, Qt::black);
|
||||
standardPalette.setColor(QPalette::Base, comp);
|
||||
standardPalette.setColor(QPalette::AlternateBase, extr);
|
||||
standardPalette.setColor(QPalette::ToolTipBase, comp);
|
||||
standardPalette.setColor(QPalette::ToolTipText, Qt::black);
|
||||
standardPalette.setColor(QPalette::Text, Qt::black);
|
||||
standardPalette.setColor(QPalette::Button, extr);
|
||||
standardPalette.setColor(QPalette::ButtonText, Qt::black);
|
||||
standardPalette.setColor(QPalette::Link, Qt::black);
|
||||
standardPalette.setColor(QPalette::Highlight, highlight);
|
||||
standardPalette.setColor(QPalette::HighlightedText, Qt::white);
|
||||
standardPalette.setColor(QPalette::Active, QPalette::Button, extr);
|
||||
standardPalette.setColor(QPalette::Disabled, QPalette::ButtonText, gray.darker());
|
||||
standardPalette.setColor(QPalette::Disabled, QPalette::WindowText, gray.darker());
|
||||
standardPalette.setColor(QPalette::Disabled, QPalette::Text, Qt::gray);
|
||||
standardPalette.setColor(QPalette::Disabled, QPalette::Light, gray.lighter());
|
||||
|
||||
qApp->setPalette(standardPalette);
|
||||
|
||||
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #cc3f18; border: 1px solid white; }");
|
||||
}
|
||||
else if (theme == "PCSX2Blue")
|
||||
{
|
||||
// Custom palette by RedDevilus, White as main color and Blue as complimentary.
|
||||
// Alternative light theme.
|
||||
qApp->setStyle(QStyleFactory::create("Fusion"));
|
||||
|
||||
const QColor blackish(35, 35, 35);
|
||||
const QColor darkBlue(73, 97, 177);
|
||||
const QColor blue2(80, 120, 200);
|
||||
const QColor blue(106, 156, 255);
|
||||
const QColor lightBlue(130, 155, 241);
|
||||
|
||||
QPalette standardPalette;
|
||||
standardPalette.setColor(QPalette::Window, blue2.lighter());
|
||||
standardPalette.setColor(QPalette::WindowText, blackish);
|
||||
standardPalette.setColor(QPalette::Base, lightBlue);
|
||||
standardPalette.setColor(QPalette::AlternateBase, blue2.lighter());
|
||||
standardPalette.setColor(QPalette::ToolTipBase, blue2);
|
||||
standardPalette.setColor(QPalette::ToolTipText, Qt::white);
|
||||
standardPalette.setColor(QPalette::Text, blackish);
|
||||
standardPalette.setColor(QPalette::Button, blue);
|
||||
standardPalette.setColor(QPalette::ButtonText, blackish);
|
||||
standardPalette.setColor(QPalette::Link, darkBlue);
|
||||
standardPalette.setColor(QPalette::Highlight, Qt::white);
|
||||
standardPalette.setColor(QPalette::HighlightedText, blackish);
|
||||
|
||||
standardPalette.setColor(QPalette::Active, QPalette::Button, blue);
|
||||
standardPalette.setColor(QPalette::Disabled, QPalette::ButtonText, darkBlue);
|
||||
standardPalette.setColor(QPalette::Disabled, QPalette::WindowText, darkBlue);
|
||||
standardPalette.setColor(QPalette::Disabled, QPalette::Text, darkBlue);
|
||||
standardPalette.setColor(QPalette::Disabled, QPalette::Light, darkBlue);
|
||||
|
||||
qApp->setPalette(standardPalette);
|
||||
|
||||
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
|
||||
}
|
||||
else if (theme == "ScarletDevilRed")
|
||||
{
|
||||
// Custom palette by RedDevilus, Red as main color and Purple as complimentary.
|
||||
// Alternative dark theme.
|
||||
qApp->setStyle(QStyleFactory::create("Fusion"));
|
||||
|
||||
const QColor darkRed(80, 45, 69);
|
||||
const QColor purplishRed(120, 45, 69);
|
||||
const QColor brightRed(200, 45, 69);
|
||||
|
||||
QPalette darkPalette;
|
||||
darkPalette.setColor(QPalette::Window, darkRed);
|
||||
darkPalette.setColor(QPalette::WindowText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Base, purplishRed);
|
||||
darkPalette.setColor(QPalette::AlternateBase, darkRed);
|
||||
darkPalette.setColor(QPalette::ToolTipBase, darkRed);
|
||||
darkPalette.setColor(QPalette::ToolTipText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Text, Qt::white);
|
||||
darkPalette.setColor(QPalette::Button, purplishRed.darker());
|
||||
darkPalette.setColor(QPalette::ButtonText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Link, brightRed);
|
||||
darkPalette.setColor(QPalette::Highlight, brightRed);
|
||||
darkPalette.setColor(QPalette::HighlightedText, Qt::white);
|
||||
|
||||
darkPalette.setColor(QPalette::Active, QPalette::Button, purplishRed.darker());
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, brightRed);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, brightRed);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::Text, brightRed);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::Light, darkRed);
|
||||
|
||||
qApp->setPalette(darkPalette);
|
||||
|
||||
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
|
||||
}
|
||||
else if (theme == "CobaltSky")
|
||||
{
|
||||
// Custom palette by KamFretoZ, A soothing deep royal blue
|
||||
// that are meant to be easy on the eyes as the main color.
|
||||
// Alternative dark theme.
|
||||
qApp->setStyle(QStyleFactory::create("Fusion"));
|
||||
|
||||
const QColor gray(150, 150, 150);
|
||||
const QColor royalBlue(29, 41, 81);
|
||||
const QColor darkishBlue(17, 30, 108);
|
||||
const QColor highlight(36, 93, 218);
|
||||
|
||||
QPalette darkPalette;
|
||||
darkPalette.setColor(QPalette::Window, royalBlue);
|
||||
darkPalette.setColor(QPalette::WindowText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Base, royalBlue.lighter());
|
||||
darkPalette.setColor(QPalette::AlternateBase, darkishBlue);
|
||||
darkPalette.setColor(QPalette::ToolTipBase, darkishBlue);
|
||||
darkPalette.setColor(QPalette::ToolTipText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Text, Qt::white);
|
||||
darkPalette.setColor(QPalette::Button, darkishBlue);
|
||||
darkPalette.setColor(QPalette::ButtonText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Link, Qt::white);
|
||||
darkPalette.setColor(QPalette::Highlight, highlight);
|
||||
darkPalette.setColor(QPalette::HighlightedText, Qt::white);
|
||||
|
||||
darkPalette.setColor(QPalette::Active, QPalette::Button, darkishBlue);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, gray);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, gray);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::Text, gray);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::Light, gray);
|
||||
|
||||
qApp->setPalette(darkPalette);
|
||||
|
||||
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
|
||||
}
|
||||
else if (theme == "VioletAngelPurple")
|
||||
{
|
||||
// Custom palette by RedDevilus, Blue as main color and Purple as complimentary.
|
||||
// Alternative dark theme.
|
||||
qApp->setStyle(QStyleFactory::create("Fusion"));
|
||||
|
||||
const QColor blackishblue(50, 25, 70);
|
||||
const QColor darkerPurple(90, 30, 105);
|
||||
const QColor nauticalPurple(110, 30, 125);
|
||||
|
||||
QPalette darkPalette;
|
||||
darkPalette.setColor(QPalette::Window, blackishblue);
|
||||
darkPalette.setColor(QPalette::WindowText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Base, nauticalPurple);
|
||||
darkPalette.setColor(QPalette::AlternateBase, blackishblue);
|
||||
darkPalette.setColor(QPalette::ToolTipBase, nauticalPurple);
|
||||
darkPalette.setColor(QPalette::ToolTipText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Text, Qt::white);
|
||||
darkPalette.setColor(QPalette::Button, nauticalPurple.darker());
|
||||
darkPalette.setColor(QPalette::ButtonText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Link, darkerPurple.lighter());
|
||||
darkPalette.setColor(QPalette::Highlight, darkerPurple.lighter());
|
||||
darkPalette.setColor(QPalette::HighlightedText, Qt::white);
|
||||
|
||||
darkPalette.setColor(QPalette::Active, QPalette::Button, nauticalPurple.darker());
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, darkerPurple.lighter());
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, darkerPurple.lighter());
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::Text, darkerPurple.darker());
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::Light, nauticalPurple);
|
||||
|
||||
qApp->setPalette(darkPalette);
|
||||
|
||||
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
|
||||
}
|
||||
else if (theme == "Ruby")
|
||||
{
|
||||
// Custom palette by Daisouji, Black as main color and Red as complimentary.
|
||||
// Alternative dark (black) theme.
|
||||
qApp->setStyle(QStyleFactory::create("Fusion"));
|
||||
|
||||
const QColor gray(128, 128, 128);
|
||||
const QColor slate(18, 18, 18);
|
||||
const QColor rubyish(172, 21, 31);
|
||||
|
||||
QPalette darkPalette;
|
||||
darkPalette.setColor(QPalette::Window, slate);
|
||||
darkPalette.setColor(QPalette::WindowText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Base, slate.lighter());
|
||||
darkPalette.setColor(QPalette::AlternateBase, slate.lighter());
|
||||
darkPalette.setColor(QPalette::ToolTipBase, slate);
|
||||
darkPalette.setColor(QPalette::ToolTipText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Text, Qt::white);
|
||||
darkPalette.setColor(QPalette::Button, slate);
|
||||
darkPalette.setColor(QPalette::ButtonText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Link, Qt::white);
|
||||
darkPalette.setColor(QPalette::Highlight, rubyish);
|
||||
darkPalette.setColor(QPalette::HighlightedText, Qt::white);
|
||||
|
||||
darkPalette.setColor(QPalette::Active, QPalette::Button, slate);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, gray);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, gray);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::Text, gray);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::Light, slate.lighter());
|
||||
|
||||
qApp->setPalette(darkPalette);
|
||||
|
||||
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
|
||||
}
|
||||
else if (theme == "Sapphire")
|
||||
{
|
||||
// Custom palette by RedDevilus, Black as main color and Blue as complimentary.
|
||||
// Alternative dark (black) theme.
|
||||
qApp->setStyle(QStyleFactory::create("Fusion"));
|
||||
|
||||
const QColor gray(128, 128, 128);
|
||||
const QColor slate(18, 18, 18);
|
||||
const QColor persianBlue(32, 35, 204);
|
||||
|
||||
QPalette darkPalette;
|
||||
darkPalette.setColor(QPalette::Window, slate);
|
||||
darkPalette.setColor(QPalette::WindowText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Base, slate.lighter());
|
||||
darkPalette.setColor(QPalette::AlternateBase, slate.lighter());
|
||||
darkPalette.setColor(QPalette::ToolTipBase, slate);
|
||||
darkPalette.setColor(QPalette::ToolTipText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Text, Qt::white);
|
||||
darkPalette.setColor(QPalette::Button, slate);
|
||||
darkPalette.setColor(QPalette::ButtonText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Link, Qt::white);
|
||||
darkPalette.setColor(QPalette::Highlight, persianBlue);
|
||||
darkPalette.setColor(QPalette::HighlightedText, Qt::white);
|
||||
|
||||
darkPalette.setColor(QPalette::Active, QPalette::Button, slate);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, gray);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, gray);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::Text, gray);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::Light, slate.lighter());
|
||||
|
||||
qApp->setPalette(darkPalette);
|
||||
|
||||
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
|
||||
}
|
||||
else if (theme == "Custom")
|
||||
{
|
||||
//Additional Theme option than loads .qss from main PCSX2 Directory
|
||||
qApp->setStyle(QStyleFactory::create("Fusion"));
|
||||
|
||||
QString sheet_content;
|
||||
QFile sheets(QString::fromStdString(Path::Combine(EmuFolders::DataRoot, "custom.qss")));
|
||||
|
||||
if (sheets.open(QFile::ReadOnly))
|
||||
{
|
||||
QString sheet_content = QString::fromUtf8(sheets.readAll().data());
|
||||
qApp->setStyleSheet(sheet_content);
|
||||
}
|
||||
else
|
||||
{
|
||||
qApp->setStyle(QStyleFactory::create("Fusion"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// setPalette() shouldn't be necessary, as the documentation claims that setStyle() resets the palette, but it
|
||||
// is here, to work around a bug in 6.4.x and 6.5.x where the palette doesn't restore after changing themes.
|
||||
qApp->setPalette(QPalette());
|
||||
qApp->setStyle(s_unthemed_style_name);
|
||||
qApp->setStyleSheet(QString());
|
||||
}
|
||||
}
|
||||
|
||||
void QtHost::SetIconThemeFromStyle()
|
||||
{
|
||||
QPalette palette = qApp->palette();
|
||||
bool dark = palette.windowText().color().value() > palette.window().color().value();
|
||||
QIcon::setThemeName(dark ? QStringLiteral("white") : QStringLiteral("black"));
|
||||
}
|
||||
@@ -144,6 +144,7 @@
|
||||
<ClCompile Include="Settings\DebugSettingsWidget.cpp" />
|
||||
<ClCompile Include="Settings\FolderSettingsWidget.cpp" />
|
||||
<ClCompile Include="Settings\MemoryCardConvertWorker.cpp" />
|
||||
<ClCompile Include="Themes.cpp" />
|
||||
<ClCompile Include="Tools\InputRecording\InputRecordingViewer.cpp" />
|
||||
<ClCompile Include="Tools\InputRecording\NewInputRecordingDlg.cpp" />
|
||||
<ClCompile Include="Debugger\CpuWidget.cpp" />
|
||||
|
||||
@@ -315,6 +315,7 @@
|
||||
<ClCompile Include="$(IntDir)moc_ColorPickerButton.cpp">
|
||||
<Filter>moc</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Themes.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Manifest Include="..\pcsx2\windows\PCSX2.manifest">
|
||||
|
||||
@@ -5,7 +5,7 @@ IFS="
|
||||
|
||||
printf "<RCC>\n"
|
||||
printf "\t<qresource>\n"
|
||||
for i in $(find . -not -iname '*.sh' -not -iname '*.qrc' -type f | cut -d'/' -f2-99); do
|
||||
for i in $(find . -not -iname '.*' -not -iname '*.sh' -not -iname '*.qrc' -type f | cut -d'/' -f2-99 | sort -f); do
|
||||
printf "\t\t<file>%s</file>\n" "$i"
|
||||
done
|
||||
printf "\t</qresource>\n"
|
||||
|
||||
|
After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 700 B |
|
After Width: | Height: | Size: 1.2 KiB |
@@ -2,6 +2,10 @@
|
||||
Name=Black Icon Theme
|
||||
Comment=Black Icon Theme
|
||||
|
||||
[32]
|
||||
Size=32
|
||||
Type=Fixed
|
||||
|
||||
[svg]
|
||||
Size=64
|
||||
Type=Scalable
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-at" viewBox="0 0 16 16"> <path d="M13.106 7.222c0-2.967-2.249-5.032-5.482-5.032-3.35 0-5.646 2.318-5.646 5.702 0 3.493 2.235 5.708 5.762 5.708.862 0 1.689-.123 2.304-.335v-.862c-.43.199-1.354.328-2.29.328-2.926 0-4.813-1.88-4.813-4.798 0-2.844 1.921-4.881 4.594-4.881 2.735 0 4.608 1.688 4.608 4.156 0 1.682-.554 2.769-1.416 2.769-.492 0-.772-.28-.772-.76V5.206H8.923v.834h-.11c-.266-.595-.881-.964-1.6-.964-1.4 0-2.378 1.162-2.378 2.823 0 1.737.957 2.906 2.379 2.906.8 0 1.415-.39 1.709-1.087h.11c.081.67.703 1.148 1.503 1.148 1.572 0 2.57-1.415 2.57-3.643zm-7.177.704c0-1.197.54-1.907 1.456-1.907.93 0 1.524.738 1.524 1.907S8.308 9.84 7.371 9.84c-.895 0-1.442-.725-1.442-1.914z"/> </svg>
|
||||
|
After Width: | Height: | Size: 939 B |
|
Before Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 695 B |
|
After Width: | Height: | Size: 1.3 KiB |
@@ -2,6 +2,10 @@
|
||||
Name=White Icon Theme
|
||||
Comment=White Icon Theme
|
||||
|
||||
[32]
|
||||
Size=32
|
||||
Type=Fixed
|
||||
|
||||
[svg]
|
||||
Size=64
|
||||
Type=Scalable
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <svg style="color: white" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-at" viewBox="0 0 16 16"> <path d="M13.106 7.222c0-2.967-2.249-5.032-5.482-5.032-3.35 0-5.646 2.318-5.646 5.702 0 3.493 2.235 5.708 5.762 5.708.862 0 1.689-.123 2.304-.335v-.862c-.43.199-1.354.328-2.29.328-2.926 0-4.813-1.88-4.813-4.798 0-2.844 1.921-4.881 4.594-4.881 2.735 0 4.608 1.688 4.608 4.156 0 1.682-.554 2.769-1.416 2.769-.492 0-.772-.28-.772-.76V5.206H8.923v.834h-.11c-.266-.595-.881-.964-1.6-.964-1.4 0-2.378 1.162-2.378 2.823 0 1.737.957 2.906 2.379 2.906.8 0 1.415-.39 1.709-1.087h.11c.081.67.703 1.148 1.503 1.148 1.572 0 2.57-1.415 2.57-3.643zm-7.177.704c0-1.197.54-1.907 1.456-1.907.93 0 1.524.738 1.524 1.907S8.308 9.84 7.371 9.84c-.895 0-1.442-.725-1.442-1.914z" fill="white"></path> </svg>
|
||||
|
After Width: | Height: | Size: 978 B |
@@ -1,10 +1,13 @@
|
||||
<RCC>
|
||||
<qresource>
|
||||
<file>icons/address-book-new-22.png</file>
|
||||
<file>icons/AppIcon.png</file>
|
||||
<file>icons/applications-system-24.png</file>
|
||||
<file>icons/black/32/discord.png</file>
|
||||
<file>icons/black/32/github.png</file>
|
||||
<file>icons/black/index.theme</file>
|
||||
<file>icons/black/svg/arrow-left-right-line.svg</file>
|
||||
<file>icons/black/svg/artboard-2-line.svg</file>
|
||||
<file>icons/black/svg/at.svg</file>
|
||||
<file>icons/black/svg/book-open-line.svg</file>
|
||||
<file>icons/black/svg/brush-line.svg</file>
|
||||
<file>icons/black/svg/checkbox-multiple-blank-line.svg</file>
|
||||
@@ -56,16 +59,18 @@
|
||||
<file>icons/black/svg/usb-fill.svg</file>
|
||||
<file>icons/black/svg/volume-up-line.svg</file>
|
||||
<file>icons/black/svg/window-2-line.svg</file>
|
||||
<file>icons/discord.png</file>
|
||||
<file>icons/logo.png</file>
|
||||
<file>icons/media-optical-24.png</file>
|
||||
<file>icons/media-optical-gear-24.png</file>
|
||||
<file>icons/media-optical.png</file>
|
||||
<file>icons/QT.png</file>
|
||||
<file>icons/update.png</file>
|
||||
<file>icons/white/32/discord.png</file>
|
||||
<file>icons/white/32/github.png</file>
|
||||
<file>icons/white/index.theme</file>
|
||||
<file>icons/white/svg/arrow-left-right-line.svg</file>
|
||||
<file>icons/white/svg/artboard-2-line.svg</file>
|
||||
<file>icons/white/svg/at.svg</file>
|
||||
<file>icons/white/svg/book-open-line.svg</file>
|
||||
<file>icons/white/svg/brush-line.svg</file>
|
||||
<file>icons/white/svg/checkbox-multiple-blank-line.svg</file>
|
||||
|
||||
@@ -253,7 +253,7 @@ bool InputIsoFile::Open(std::string srcfile, bool testOnly)
|
||||
|
||||
if (!detected)
|
||||
{
|
||||
Console.Error(fmt::format("Unable to identify the ISO image type for '{}'", srcfile));
|
||||
Console.Error(fmt::format("Unable to identify the ISO image type for '{}'", m_filename));
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -566,7 +566,6 @@ set(pcsx2GSHeaders
|
||||
GS/Renderers/Common/GSRenderer.h
|
||||
GS/Renderers/Common/GSTexture.h
|
||||
GS/Renderers/Common/GSVertex.h
|
||||
GS/Renderers/Common/GSVertexList.h
|
||||
GS/Renderers/Common/GSVertexTrace.h
|
||||
GS/Renderers/Null/GSRendererNull.h
|
||||
GS/Renderers/HW/GSHwHack.h
|
||||
@@ -1250,7 +1249,7 @@ function(setup_main_executable target)
|
||||
install(FILES $<TARGET_PDB_FILE:${target}> DESTINATION ${CMAKE_SOURCE_DIR}/bin)
|
||||
endif()
|
||||
find_program(WINDEPLOYQT_EXE windeployqt HINTS "${QT_BINARY_DIRECTORY}")
|
||||
install(CODE "execute_process(COMMAND \"${WINDEPLOYQT_EXE}\" \"${CMAKE_SOURCE_DIR}/bin/$<TARGET_FILE_NAME:${target}>\" --plugindir ${CMAKE_SOURCE_DIR}/bin/QtPlugins --no-compiler-runtime --no-system-d3d-compiler)")
|
||||
install(CODE "execute_process(COMMAND \"${WINDEPLOYQT_EXE}\" \"${CMAKE_SOURCE_DIR}/bin/$<TARGET_FILE_NAME:${target}>\" --plugindir \"${CMAKE_SOURCE_DIR}/bin/QtPlugins\" --no-compiler-runtime --no-system-d3d-compiler COMMAND_ERROR_IS_FATAL ANY)")
|
||||
install(CODE "file(WRITE \"${CMAKE_SOURCE_DIR}/bin/qt.conf\" \"[Paths]\\nPlugins = ./QtPlugins\")")
|
||||
endif()
|
||||
|
||||
|
||||
@@ -350,6 +350,13 @@ enum class GSCASMode : u8
|
||||
SharpenAndResize,
|
||||
};
|
||||
|
||||
enum class GSHWAutoFlushLevel : u8
|
||||
{
|
||||
Disabled,
|
||||
SpritesOnly,
|
||||
Enabled,
|
||||
};
|
||||
|
||||
enum class GSGPUTargetCLUTMode : u8
|
||||
{
|
||||
Disabled,
|
||||
@@ -669,7 +676,6 @@ struct Pcsx2Config
|
||||
Mipmap : 1,
|
||||
ManualUserHacks : 1,
|
||||
UserHacks_AlignSpriteX : 1,
|
||||
UserHacks_AutoFlush : 1,
|
||||
UserHacks_CPUFBConversion : 1,
|
||||
UserHacks_ReadTCOnClose : 1,
|
||||
UserHacks_DisableDepthSupport : 1,
|
||||
@@ -704,85 +710,87 @@ struct Pcsx2Config
|
||||
};
|
||||
};
|
||||
|
||||
int VsyncQueueSize{2};
|
||||
int VsyncQueueSize = 2;
|
||||
|
||||
// forces the MTGS to execute tags/tasks in fully blocking/synchronous
|
||||
// style. Useful for debugging potential bugs in the MTGS pipeline.
|
||||
bool SynchronousMTGS{false};
|
||||
bool FrameLimitEnable{true};
|
||||
bool SynchronousMTGS = false;
|
||||
bool FrameLimitEnable = true;
|
||||
|
||||
VsyncMode VsyncEnable{VsyncMode::Off};
|
||||
VsyncMode VsyncEnable = VsyncMode::Off;
|
||||
|
||||
float LimitScalar{1.0f};
|
||||
float FramerateNTSC{DEFAULT_FRAME_RATE_NTSC};
|
||||
float FrameratePAL{DEFAULT_FRAME_RATE_PAL};
|
||||
float LimitScalar = 1.0f;
|
||||
float FramerateNTSC = DEFAULT_FRAME_RATE_NTSC;
|
||||
float FrameratePAL = DEFAULT_FRAME_RATE_PAL;
|
||||
|
||||
AspectRatioType AspectRatio{AspectRatioType::RAuto4_3_3_2};
|
||||
FMVAspectRatioSwitchType FMVAspectRatioSwitch{FMVAspectRatioSwitchType::Off};
|
||||
GSInterlaceMode InterlaceMode{GSInterlaceMode::Automatic};
|
||||
GSPostBilinearMode LinearPresent{ GSPostBilinearMode::BilinearSmooth };
|
||||
AspectRatioType AspectRatio = AspectRatioType::RAuto4_3_3_2;
|
||||
FMVAspectRatioSwitchType FMVAspectRatioSwitch = FMVAspectRatioSwitchType::Off;
|
||||
GSInterlaceMode InterlaceMode = GSInterlaceMode::Automatic;
|
||||
GSPostBilinearMode LinearPresent = GSPostBilinearMode::BilinearSmooth;
|
||||
|
||||
float StretchY{100.0f};
|
||||
int Crop[4]{};
|
||||
float StretchY = 100.0f;
|
||||
int Crop[4] = {};
|
||||
|
||||
float OsdScale{100.0};
|
||||
float OsdScale = 100.0;
|
||||
|
||||
GSRendererType Renderer{GSRendererType::Auto};
|
||||
float UpscaleMultiplier{1.0f};
|
||||
GSRendererType Renderer = GSRendererType::Auto;
|
||||
float UpscaleMultiplier = 1.0f;
|
||||
|
||||
HWMipmapLevel HWMipmap{HWMipmapLevel::Automatic};
|
||||
AccBlendLevel AccurateBlendingUnit{AccBlendLevel::Basic};
|
||||
CRCHackLevel CRCHack{CRCHackLevel::Automatic};
|
||||
BiFiltering TextureFiltering{BiFiltering::PS2};
|
||||
TexturePreloadingLevel TexturePreloading{TexturePreloadingLevel::Full};
|
||||
GSDumpCompressionMethod GSDumpCompression{GSDumpCompressionMethod::Zstandard};
|
||||
GSHardwareDownloadMode HWDownloadMode{GSHardwareDownloadMode::Enabled};
|
||||
GSCASMode CASMode{GSCASMode::Disabled};
|
||||
int Dithering{2};
|
||||
int MaxAnisotropy{0};
|
||||
int SWExtraThreads{2};
|
||||
int SWExtraThreadsHeight{4};
|
||||
int TVShader{0};
|
||||
s16 GetSkipCountFunctionId{-1};
|
||||
s16 BeforeDrawFunctionId{-1};
|
||||
int SkipDrawStart{0};
|
||||
int SkipDrawEnd{0};
|
||||
HWMipmapLevel HWMipmap = HWMipmapLevel::Automatic;
|
||||
AccBlendLevel AccurateBlendingUnit = AccBlendLevel::Basic;
|
||||
CRCHackLevel CRCHack = CRCHackLevel::Automatic;
|
||||
BiFiltering TextureFiltering = BiFiltering::PS2;
|
||||
TexturePreloadingLevel TexturePreloading = TexturePreloadingLevel::Full;
|
||||
GSDumpCompressionMethod GSDumpCompression = GSDumpCompressionMethod::Zstandard;
|
||||
GSHardwareDownloadMode HWDownloadMode = GSHardwareDownloadMode::Enabled;
|
||||
GSCASMode CASMode = GSCASMode::Disabled;
|
||||
u8 Dithering = 2;
|
||||
u8 MaxAnisotropy = 0;
|
||||
u8 TVShader = 0;
|
||||
s16 GetSkipCountFunctionId = -1;
|
||||
s16 BeforeDrawFunctionId = -1;
|
||||
int SkipDrawStart = 0;
|
||||
int SkipDrawEnd = 0;
|
||||
|
||||
int UserHacks_HalfBottomOverride{-1};
|
||||
int UserHacks_HalfPixelOffset{0};
|
||||
int UserHacks_RoundSprite{0};
|
||||
int UserHacks_TCOffsetX{0};
|
||||
int UserHacks_TCOffsetY{0};
|
||||
int UserHacks_CPUSpriteRenderBW{0};
|
||||
int UserHacks_CPUSpriteRenderLevel{0};
|
||||
int UserHacks_CPUCLUTRender{0};
|
||||
GSGPUTargetCLUTMode UserHacks_GPUTargetCLUTMode{GSGPUTargetCLUTMode::Disabled};
|
||||
GSTextureInRtMode UserHacks_TextureInsideRt{GSTextureInRtMode::Disabled};
|
||||
TriFiltering TriFilter{TriFiltering::Automatic};
|
||||
int OverrideTextureBarriers{-1};
|
||||
GSHWAutoFlushLevel UserHacks_AutoFlush = GSHWAutoFlushLevel::Disabled;
|
||||
s8 UserHacks_HalfBottomOverride = -1;
|
||||
s8 UserHacks_HalfPixelOffset = 0;
|
||||
s8 UserHacks_RoundSprite = 0;
|
||||
s32 UserHacks_TCOffsetX = 0;
|
||||
s32 UserHacks_TCOffsetY = 0;
|
||||
u8 UserHacks_CPUSpriteRenderBW = 0;
|
||||
u8 UserHacks_CPUSpriteRenderLevel = 0;
|
||||
u8 UserHacks_CPUCLUTRender = 0;
|
||||
GSGPUTargetCLUTMode UserHacks_GPUTargetCLUTMode = GSGPUTargetCLUTMode::Disabled;
|
||||
GSTextureInRtMode UserHacks_TextureInsideRt = GSTextureInRtMode::Disabled;
|
||||
TriFiltering TriFilter = TriFiltering::Automatic;
|
||||
s8 OverrideTextureBarriers = -1;
|
||||
|
||||
int CAS_Sharpness{50};
|
||||
int ShadeBoost_Brightness{50};
|
||||
int ShadeBoost_Contrast{50};
|
||||
int ShadeBoost_Saturation{50};
|
||||
int PNGCompressionLevel{1};
|
||||
u8 CAS_Sharpness = 50;
|
||||
u8 ShadeBoost_Brightness = 50;
|
||||
u8 ShadeBoost_Contrast = 50;
|
||||
u8 ShadeBoost_Saturation = 50;
|
||||
u8 PNGCompressionLevel = 1;
|
||||
|
||||
int SaveN{0};
|
||||
int SaveL{5000};
|
||||
u16 SWExtraThreads = 2;
|
||||
u16 SWExtraThreadsHeight = 4;
|
||||
|
||||
GSScreenshotSize ScreenshotSize{GSScreenshotSize::WindowResolution};
|
||||
GSScreenshotFormat ScreenshotFormat{GSScreenshotFormat::PNG};
|
||||
int ScreenshotQuality{50};
|
||||
int SaveN = 0;
|
||||
int SaveL = 5000;
|
||||
|
||||
std::string CaptureContainer{DEFAULT_CAPTURE_CONTAINER};
|
||||
GSScreenshotSize ScreenshotSize = GSScreenshotSize::WindowResolution;
|
||||
GSScreenshotFormat ScreenshotFormat = GSScreenshotFormat::PNG;
|
||||
int ScreenshotQuality = 50;
|
||||
|
||||
std::string CaptureContainer = DEFAULT_CAPTURE_CONTAINER;
|
||||
std::string VideoCaptureCodec;
|
||||
std::string VideoCaptureParameters;
|
||||
std::string AudioCaptureCodec;
|
||||
std::string AudioCaptureParameters;
|
||||
int VideoCaptureBitrate{DEFAULT_VIDEO_CAPTURE_BITRATE};
|
||||
int VideoCaptureWidth{DEFAULT_VIDEO_CAPTURE_WIDTH};
|
||||
int VideoCaptureHeight{DEFAULT_VIDEO_CAPTURE_HEIGHT};
|
||||
int AudioCaptureBitrate{DEFAULT_AUDIO_CAPTURE_BITRATE};
|
||||
int VideoCaptureBitrate = DEFAULT_VIDEO_CAPTURE_BITRATE;
|
||||
int VideoCaptureWidth = DEFAULT_VIDEO_CAPTURE_WIDTH;
|
||||
int VideoCaptureHeight = DEFAULT_VIDEO_CAPTURE_HEIGHT;
|
||||
int AudioCaptureBitrate = DEFAULT_AUDIO_CAPTURE_BITRATE;
|
||||
|
||||
std::string Adapter;
|
||||
std::string HWDumpDirectory;
|
||||
|
||||
@@ -598,6 +598,12 @@ u32 R5900DebugInterface::getPC()
|
||||
return cpuRegs.pc;
|
||||
}
|
||||
|
||||
// Taken from COP0.cpp
|
||||
bool R5900DebugInterface::getCPCOND0()
|
||||
{
|
||||
return (((dmacRegs.stat.CIS | ~dmacRegs.pcr.CPC) & 0x3FF) == 0x3ff);
|
||||
}
|
||||
|
||||
void R5900DebugInterface::setPc(u32 newPc)
|
||||
{
|
||||
cpuRegs.pc = newPc;
|
||||
@@ -945,6 +951,11 @@ u32 R3000DebugInterface::getPC()
|
||||
return psxRegs.pc;
|
||||
}
|
||||
|
||||
bool R3000DebugInterface::getCPCOND0()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void R3000DebugInterface::setPc(u32 newPc)
|
||||
{
|
||||
psxRegs.pc = newPc;
|
||||
|
||||
@@ -77,6 +77,7 @@ public:
|
||||
virtual u128 getHI() = 0;
|
||||
virtual u128 getLO() = 0;
|
||||
virtual u32 getPC() = 0;
|
||||
virtual bool getCPCOND0() = 0;
|
||||
virtual void setPc(u32 newPc) = 0;
|
||||
virtual void setRegister(int cat, int num, u128 newValue) = 0;
|
||||
|
||||
@@ -129,6 +130,7 @@ public:
|
||||
u128 getHI() override;
|
||||
u128 getLO() override;
|
||||
u32 getPC() override;
|
||||
bool getCPCOND0() override;
|
||||
void setPc(u32 newPc) override;
|
||||
void setRegister(int cat, int num, u128 newValue) override;
|
||||
[[nodiscard]] SymbolMap& GetSymbolMap() const override;
|
||||
@@ -168,6 +170,7 @@ public:
|
||||
u128 getHI() override;
|
||||
u128 getLO() override;
|
||||
u32 getPC() override;
|
||||
bool getCPCOND0() override;
|
||||
void setPc(u32 newPc) override;
|
||||
void setRegister(int cat, int num, u128 newValue) override;
|
||||
[[nodiscard]] SymbolMap& GetSymbolMap() const override;
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace MIPSAnalyst
|
||||
const R5900::OPCODE& opcode = R5900::GetInstruction(op);
|
||||
|
||||
int branchType = (opcode.flags & BRANCHTYPE_MASK);
|
||||
if ((opcode.flags & IS_BRANCH) && (branchType == BRANCHTYPE_BRANCH || branchType == BRANCHTYPE_BC1))
|
||||
if ((opcode.flags & IS_BRANCH) && (branchType == BRANCHTYPE_BRANCH || branchType == BRANCHTYPE_BC1 || branchType == BRANCHTYPE_BC0))
|
||||
return addr + 4 + ((signed short)(op&0xFFFF)<<2);
|
||||
else
|
||||
return INVALIDTARGET;
|
||||
@@ -71,7 +71,7 @@ namespace MIPSAnalyst
|
||||
const R5900::OPCODE& opcode = R5900::GetInstruction(op);
|
||||
|
||||
int branchType = (opcode.flags & BRANCHTYPE_MASK);
|
||||
if ((opcode.flags & IS_BRANCH) && (branchType == BRANCHTYPE_BRANCH || branchType == BRANCHTYPE_BC1))
|
||||
if ((opcode.flags & IS_BRANCH) && (branchType == BRANCHTYPE_BRANCH || branchType == BRANCHTYPE_BC1 || branchType == BRANCHTYPE_BC0))
|
||||
{
|
||||
if (!(opcode.flags & IS_LINKED))
|
||||
return addr + 4 + ((signed short)(op&0xFFFF)<<2);
|
||||
@@ -409,6 +409,20 @@ namespace MIPSAnalyst
|
||||
value = info.cpu->getRegister(EECAT_FCR,31)._u32[0] & 0x00800000;
|
||||
info.branchTarget = info.opcodeAddress + 4 + ((s16)(op&0xFFFF)<<2);
|
||||
|
||||
switch (opcode.flags & CONDTYPE_MASK)
|
||||
{
|
||||
case CONDTYPE_EQ:
|
||||
info.conditionMet = value == 0;
|
||||
break;
|
||||
case CONDTYPE_NE:
|
||||
info.conditionMet = value != 0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case BRANCHTYPE_BC0:
|
||||
info.isConditional = true;
|
||||
value = info.cpu->getCPCOND0();
|
||||
info.branchTarget = info.opcodeAddress + 4 + ((s16)(op&0xFFFF)<<2);
|
||||
switch (opcode.flags & CONDTYPE_MASK)
|
||||
{
|
||||
case CONDTYPE_EQ:
|
||||
|
||||
@@ -38,6 +38,7 @@ SERIAL-12345: # !required! Serial number for the game, this is how games are loo
|
||||
- GIFFIFOHack
|
||||
- GoemonTlbHack
|
||||
- IbitHack
|
||||
- FullVU0SyncHack
|
||||
- VUSyncHack
|
||||
- VUOverflowHack
|
||||
- SoftwareRendererFMVHack
|
||||
@@ -220,6 +221,9 @@ These values are case-sensitive so take care. If you incorrectly specify a Game
|
||||
* `IbitHack`
|
||||
* VU I bit Hack avoid constant recompilation in some games (Scarface The World Is Yours, Crash Tag Team Racing).
|
||||
|
||||
* `FullVU0SyncHack`
|
||||
* Increase the synchronisation points between COP2 and VU0.
|
||||
|
||||
* `VUSyncHack`
|
||||
* Make the VU's run behind/in sync with the EE to fix some timing issues.
|
||||
|
||||
|
||||
@@ -101,6 +101,7 @@
|
||||
"VIFFIFOHack",
|
||||
"VuAddSubHack",
|
||||
"VUOverflowHack",
|
||||
"FullVU0SyncHack",
|
||||
"VUSyncHack",
|
||||
"XGKickHack"
|
||||
]
|
||||
@@ -114,7 +115,7 @@
|
||||
"autoFlush": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 1
|
||||
"maximum": 2
|
||||
},
|
||||
"conservativeFramebuffer": {
|
||||
"type": "integer",
|
||||
|
||||
@@ -3182,6 +3182,8 @@ void FullscreenUI::DrawGraphicsSettingsPage()
|
||||
static constexpr const char* s_half_pixel_offset_options[] = {
|
||||
"Off (Default)", "Normal (Vertex)", "Special (Texture)", "Special (Texture - Aggressive)"};
|
||||
static constexpr const char* s_round_sprite_options[] = {"Off (Default)", "Half", "Full"};
|
||||
static constexpr const char* s_auto_flush_options[] = {
|
||||
"Disabled (Default)", "Enabled (Sprites Only)", "Enabled (All Primitives)"};
|
||||
|
||||
DrawIntListSetting(bsi, "CRC Fix Level", "Applies manual fixes to difficult-to-emulate effects in the hardware renderers.",
|
||||
"EmuCore/GS", "crc_hack_level", static_cast<int>(CRCHackLevel::Automatic), s_crc_fix_options, std::size(s_crc_fix_options),
|
||||
@@ -3198,8 +3200,8 @@ void FullscreenUI::DrawGraphicsSettingsPage()
|
||||
bsi, "Skip Draw Start", "Object range to skip drawing.", "EmuCore/GS", "UserHacks_SkipDraw_Start", 0, 0, 5000, 1);
|
||||
DrawIntSpinBoxSetting(
|
||||
bsi, "Skip Draw End", "Object range to skip drawing.", "EmuCore/GS", "UserHacks_SkipDraw_End", 0, 0, 5000, 1);
|
||||
DrawToggleSetting(bsi, "Auto Flush (Hardware)", "Force a primitive flush when a framebuffer is also an input texture.",
|
||||
"EmuCore/GS", "UserHacks_AutoFlush", false, manual_hw_fixes);
|
||||
DrawIntListSetting(bsi, "Auto Flush (Hardware)", "Force a primitive flush when a framebuffer is also an input texture.",
|
||||
"EmuCore/GS", "UserHacks_AutoFlushLevel", 0, s_auto_flush_options, std::size(s_auto_flush_options), 0, manual_hw_fixes);
|
||||
DrawToggleSetting(bsi, "CPU Framebuffer Conversion", "Convert 4-bit and 8-bit frame buffer on the CPU instead of the GPU.",
|
||||
"EmuCore/GS", "UserHacks_CPU_FB_Conversion", false, manual_hw_fixes);
|
||||
DrawToggleSetting(bsi, "Disable Depth Support", "Disable the support of depth buffer in the texture cache.", "EmuCore/GS",
|
||||
@@ -3748,6 +3750,8 @@ void FullscreenUI::DrawControllerSettingsPage()
|
||||
DrawToggleSetting(bsi, ICON_FA_WIFI " SDL DualShock 4 / DualSense Enhanced Mode",
|
||||
"Provides vibration and LED control support over Bluetooth.", "InputSources", "SDLControllerEnhancedMode", false,
|
||||
bsi->GetBoolValue("InputSources", "SDL", true), false);
|
||||
#endif
|
||||
#if defined(SDL_BUILD) && defined(_WIN32)
|
||||
DrawToggleSetting(bsi, ICON_FA_COG " SDL Raw Input", "Allow SDL to use raw access to input devices.", "InputSources", "SDLRawInput",
|
||||
false, bsi->GetBoolValue("InputSources", "SDL", true), false);
|
||||
#endif
|
||||
|
||||
@@ -407,11 +407,11 @@ void ImGuiManager::DrawSettingsOverlay()
|
||||
if (GSConfig.UserHacks_CPUCLUTRender != 0)
|
||||
APPEND("CCLUT={} ", GSConfig.UserHacks_CPUCLUTRender);
|
||||
if (GSConfig.UserHacks_GPUTargetCLUTMode != GSGPUTargetCLUTMode::Disabled)
|
||||
APPEND("GCLUT={} ", static_cast<int>(GSConfig.UserHacks_GPUTargetCLUTMode));
|
||||
APPEND("GCLUT={} ", static_cast<unsigned>(GSConfig.UserHacks_GPUTargetCLUTMode));
|
||||
if (GSConfig.SkipDrawStart != 0 || GSConfig.SkipDrawEnd != 0)
|
||||
APPEND("SD={}/{} ", GSConfig.SkipDrawStart, GSConfig.SkipDrawEnd);
|
||||
if (GSConfig.UserHacks_TextureInsideRt != GSTextureInRtMode::Disabled)
|
||||
APPEND("TexRT={} ", static_cast<int>(GSConfig.UserHacks_TextureInsideRt));
|
||||
APPEND("TexRT={} ", static_cast<unsigned>(GSConfig.UserHacks_TextureInsideRt));
|
||||
if (GSConfig.UserHacks_WildHack)
|
||||
APPEND("WA ");
|
||||
if (GSConfig.UserHacks_BilinearHack)
|
||||
@@ -422,8 +422,8 @@ void ImGuiManager::DrawSettingsOverlay()
|
||||
APPEND("MS ");
|
||||
if (GSConfig.UserHacks_AlignSpriteX)
|
||||
APPEND("AS ");
|
||||
if (GSConfig.UserHacks_AutoFlush)
|
||||
APPEND("ATFL ");
|
||||
if (GSConfig.UserHacks_AutoFlush != GSHWAutoFlushLevel::Disabled)
|
||||
APPEND("ATFL={} ", static_cast<unsigned>(GSConfig.UserHacks_AutoFlush));
|
||||
if (GSConfig.UserHacks_CPUFBConversion)
|
||||
APPEND("FBC ");
|
||||
if (GSConfig.UserHacks_ReadTCOnClose)
|
||||
|
||||
@@ -133,52 +133,8 @@ static RenderAPI GetAPIForRenderer(GSRendererType renderer)
|
||||
}
|
||||
}
|
||||
|
||||
static void UpdateExclusiveFullscreen(bool force_off)
|
||||
static bool OpenGSDevice(GSRendererType renderer, bool clear_state_on_fail, bool recreate_window)
|
||||
{
|
||||
if (!g_gs_device->SupportsExclusiveFullscreen())
|
||||
return;
|
||||
|
||||
std::string fullscreen_mode = Host::GetBaseStringSettingValue("EmuCore/GS", "FullscreenMode", "");
|
||||
u32 width, height;
|
||||
float refresh_rate;
|
||||
|
||||
const bool wants_fullscreen = !force_off && Host::IsFullscreen() && !fullscreen_mode.empty() &&
|
||||
GSDevice::ParseFullscreenMode(fullscreen_mode, &width, &height, &refresh_rate);
|
||||
const bool is_fullscreen = g_gs_device->IsExclusiveFullscreen();
|
||||
if (wants_fullscreen == is_fullscreen)
|
||||
return;
|
||||
|
||||
if (wants_fullscreen)
|
||||
{
|
||||
Console.WriteLn("Trying to acquire exclusive fullscreen...");
|
||||
if (g_gs_device->SetExclusiveFullscreen(true, width, height, refresh_rate))
|
||||
{
|
||||
Host::AddKeyedOSDMessage(
|
||||
"UpdateExclusiveFullscreen", "Acquired exclusive fullscreen.", Host::OSD_INFO_DURATION);
|
||||
}
|
||||
else
|
||||
{
|
||||
Host::AddKeyedOSDMessage(
|
||||
"UpdateExclusiveFullscreen", "Failed to acquire exclusive fullscreen.", Host::OSD_WARNING_DURATION);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLn("Leaving exclusive fullscreen...");
|
||||
g_gs_device->SetExclusiveFullscreen(false, 0, 0, 0.0f);
|
||||
Host::AddKeyedOSDMessage("UpdateExclusiveFullscreen", "Lost exclusive fullscreen.", Host::OSD_INFO_DURATION);
|
||||
}
|
||||
}
|
||||
|
||||
static bool OpenGSDevice(GSRendererType renderer, bool clear_state_on_fail, bool reopening, bool recreate_window)
|
||||
{
|
||||
std::optional<WindowInfo> wi = reopening ? Host::UpdateRenderWindow(recreate_window) : Host::AcquireRenderWindow();
|
||||
if (!wi.has_value())
|
||||
{
|
||||
Console.Error("Failed to acquire render window.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const RenderAPI new_api = GetAPIForRenderer(renderer);
|
||||
switch (new_api)
|
||||
{
|
||||
@@ -212,8 +168,7 @@ static bool OpenGSDevice(GSRendererType renderer, bool clear_state_on_fail, bool
|
||||
return false;
|
||||
}
|
||||
|
||||
const VsyncMode vsync_mode = Host::GetEffectiveVSyncMode();
|
||||
bool okay = g_gs_device->Create(wi.value(), vsync_mode);
|
||||
bool okay = g_gs_device->Create();
|
||||
if (okay)
|
||||
{
|
||||
okay = ImGuiManager::Initialize();
|
||||
@@ -239,24 +194,17 @@ static bool OpenGSDevice(GSRendererType renderer, bool clear_state_on_fail, bool
|
||||
Console.WriteLn(Color_StrongGreen, "%s Graphics Driver Info:", GSDevice::RenderAPIToString(new_api));
|
||||
Console.Indent().WriteLn(g_gs_device->GetDriverInfo());
|
||||
|
||||
// Switch to exclusive fullscreen if enabled.
|
||||
UpdateExclusiveFullscreen(false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void CloseGSDevice(bool clear_state, bool reopening)
|
||||
static void CloseGSDevice(bool clear_state)
|
||||
{
|
||||
if (!g_gs_device)
|
||||
return;
|
||||
|
||||
UpdateExclusiveFullscreen(true);
|
||||
ImGuiManager::Shutdown(clear_state);
|
||||
g_gs_device->Destroy();
|
||||
g_gs_device.reset();
|
||||
|
||||
if (!reopening)
|
||||
Host::ReleaseRenderWindow();
|
||||
}
|
||||
|
||||
static bool OpenGSRenderer(GSRendererType renderer, u8* basemem)
|
||||
@@ -336,18 +284,18 @@ bool GSreopen(bool recreate_device, bool recreate_renderer, const Pcsx2Config::G
|
||||
{
|
||||
// We need a new render window when changing APIs.
|
||||
const bool recreate_window = (g_gs_device->GetRenderAPI() != GetAPIForRenderer(GSConfig.Renderer));
|
||||
CloseGSDevice(false, true);
|
||||
CloseGSDevice(false);
|
||||
|
||||
if (!OpenGSDevice(GSConfig.Renderer, false, true, recreate_window) ||
|
||||
if (!OpenGSDevice(GSConfig.Renderer, false, recreate_window) ||
|
||||
(recreate_renderer && !OpenGSRenderer(GSConfig.Renderer, basemem)))
|
||||
{
|
||||
Host::AddKeyedOSDMessage(
|
||||
"GSReopenFailed", "Failed to reopen, restoring old configuration.", Host::OSD_CRITICAL_ERROR_DURATION);
|
||||
|
||||
CloseGSDevice(false, true);
|
||||
CloseGSDevice(false);
|
||||
|
||||
GSConfig = old_config;
|
||||
if (!OpenGSDevice(GSConfig.Renderer, false, true, recreate_window) ||
|
||||
if (!OpenGSDevice(GSConfig.Renderer, false, recreate_window) ||
|
||||
(recreate_renderer && !OpenGSRenderer(GSConfig.Renderer, basemem)))
|
||||
{
|
||||
pxFailRel("Failed to reopen GS on old config");
|
||||
@@ -387,12 +335,12 @@ bool GSopen(const Pcsx2Config::GSOptions& config, GSRendererType renderer, u8* b
|
||||
GSConfig = config;
|
||||
GSConfig.Renderer = renderer;
|
||||
|
||||
bool res = OpenGSDevice(renderer, true, false, false);
|
||||
bool res = OpenGSDevice(renderer, true, false);
|
||||
if (res)
|
||||
{
|
||||
res = OpenGSRenderer(renderer, basemem);
|
||||
if (!res)
|
||||
CloseGSDevice(true, false);
|
||||
CloseGSDevice(true);
|
||||
}
|
||||
|
||||
if (!res)
|
||||
@@ -410,7 +358,8 @@ bool GSopen(const Pcsx2Config::GSOptions& config, GSRendererType renderer, u8* b
|
||||
void GSclose()
|
||||
{
|
||||
CloseGSRenderer();
|
||||
CloseGSDevice(true, false);
|
||||
CloseGSDevice(true);
|
||||
Host::ReleaseRenderWindow();
|
||||
}
|
||||
|
||||
void GSreset(bool hardware_reset)
|
||||
@@ -516,7 +465,7 @@ void GSvsync(u32 field, bool registers_written)
|
||||
{
|
||||
try
|
||||
{
|
||||
g_gs_renderer->VSync(field, registers_written);
|
||||
g_gs_renderer->VSync(field, registers_written, g_gs_renderer->IsIdleFrame());
|
||||
}
|
||||
catch (GSRecoverableError)
|
||||
{
|
||||
@@ -626,24 +575,12 @@ void GSResizeDisplayWindow(int width, int height, float scale)
|
||||
|
||||
void GSUpdateDisplayWindow()
|
||||
{
|
||||
UpdateExclusiveFullscreen(true);
|
||||
g_gs_device->DestroySurface();
|
||||
|
||||
const std::optional<WindowInfo> wi = Host::UpdateRenderWindow(false);
|
||||
if (!wi.has_value())
|
||||
if (!g_gs_device->UpdateWindow())
|
||||
{
|
||||
pxFailRel("Failed to get window info after update.");
|
||||
Host::ReportErrorAsync("Error", "Failed to change window after update. The log may contain more information.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!g_gs_device->ChangeWindow(wi.value()))
|
||||
{
|
||||
pxFailRel("Failed to change window after update.");
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateExclusiveFullscreen(false);
|
||||
|
||||
ImGuiManager::WindowResized();
|
||||
}
|
||||
|
||||
@@ -652,6 +589,16 @@ void GSSetVSyncMode(VsyncMode mode)
|
||||
g_gs_device->SetVSync(mode);
|
||||
}
|
||||
|
||||
bool GSWantsExclusiveFullscreen()
|
||||
{
|
||||
if (!g_gs_device || !g_gs_device->SupportsExclusiveFullscreen())
|
||||
return false;
|
||||
|
||||
u32 width, height;
|
||||
float refresh_rate;
|
||||
return GSDevice::GetRequestedExclusiveFullscreenMode(&width, &height, &refresh_rate);
|
||||
}
|
||||
|
||||
bool GSGetHostRefreshRate(float* refresh_rate)
|
||||
{
|
||||
if (!g_gs_device)
|
||||
@@ -748,12 +695,13 @@ void GSgetStats(std::string& info)
|
||||
}
|
||||
else
|
||||
{
|
||||
fmt::format_to(std::back_inserter(info), "{} HW | {} P | {} D | {} DC | {} B | {} RB | {} TC | {} TU",
|
||||
fmt::format_to(std::back_inserter(info), "{} HW | {} P | {} D | {} DC | {} B | {} RP | {} RB | {} TC | {} TU",
|
||||
api_name,
|
||||
(int)pm.Get(GSPerfMon::Prim),
|
||||
(int)pm.Get(GSPerfMon::Draw),
|
||||
(int)std::ceil(pm.Get(GSPerfMon::DrawCalls)),
|
||||
(int)std::ceil(pm.Get(GSPerfMon::Barriers)),
|
||||
(int)std::ceil(pm.Get(GSPerfMon::RenderPasses)),
|
||||
(int)std::ceil(pm.Get(GSPerfMon::Readbacks)),
|
||||
(int)std::ceil(pm.Get(GSPerfMon::TextureCopies)),
|
||||
(int)std::ceil(pm.Get(GSPerfMon::TextureUploads)));
|
||||
|
||||
@@ -99,6 +99,7 @@ void GSResizeDisplayWindow(int width, int height, float scale);
|
||||
void GSUpdateDisplayWindow();
|
||||
void GSSetVSyncMode(VsyncMode mode);
|
||||
|
||||
bool GSWantsExclusiveFullscreen();
|
||||
bool GSGetHostRefreshRate(float* refresh_rate);
|
||||
void GSGetAdaptersAndFullscreenModes(
|
||||
GSRendererType renderer, std::vector<std::string>* adapters, std::vector<std::string>* fullscreen_modes);
|
||||
@@ -128,11 +129,8 @@ struct GSRecoverableError : GSError
|
||||
namespace Host
|
||||
{
|
||||
/// Called when the GS is creating a render device.
|
||||
std::optional<WindowInfo> AcquireRenderWindow();
|
||||
|
||||
/// Called on the MTGS thread when a request to update the display is received.
|
||||
/// This could be a fullscreen transition, for example.
|
||||
std::optional<WindowInfo> UpdateRenderWindow(bool recreate_window);
|
||||
/// This could also be fullscreen transition.
|
||||
std::optional<WindowInfo> AcquireRenderWindow(bool recreate_window);
|
||||
|
||||
/// Called before drawing the OSD and other display elements.
|
||||
void BeginPresentFrame();
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
|
||||
<Type Name="FastList<*>">
|
||||
<DisplayString>{{ size={m_free_indexes_stack_top} }}</DisplayString>
|
||||
<Expand>
|
||||
<Item Name="[size]" ExcludeView="simple">m_free_indexes_stack_top</Item>
|
||||
<Item Name="[capacity]" ExcludeView="simple">m_capacity - 1</Item>
|
||||
|
||||
<CustomListItems MaxItemsPerView="5000" ExcludeView="simple">
|
||||
<Variable Name="index" InitialValue="m_buffer[0].next_index" />
|
||||
|
||||
<Size>m_free_indexes_stack_top</Size>
|
||||
<Loop>
|
||||
<Item>m_buffer[index].data,na</Item>
|
||||
<Exec>index = m_buffer[index].next_index</Exec>
|
||||
</Loop>
|
||||
</CustomListItems>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
<Type Name="GSVector2T<*>">
|
||||
<DisplayString>{{ {x}, {y} }}</DisplayString>
|
||||
</Type>
|
||||
|
||||
<Type Name="GSVector4">
|
||||
<DisplayString>{{ {F32[0]}, {F32[1]}, {F32[2]}, {F32[3]} }}</DisplayString>
|
||||
</Type>
|
||||
<Type Name="GSVector4i">
|
||||
<DisplayString>{{ {I32[0]}, {I32[1]}, {I32[2]}, {I32[3]} }}</DisplayString>
|
||||
</Type>
|
||||
|
||||
<Type Name="GSTextureCache::Target">
|
||||
<DisplayString Condition="m_type == 0">{{ RT @ BP={m_TEX0.TBP0,X}-{m_end_block,X} BW={m_TEX0.TBW} PSM={m_TEX0.PSM,X} {m_unscaled_size.x}x{m_unscaled_size.y} {m_valid.z},{m_valid.w} }}</DisplayString>
|
||||
<DisplayString Condition="m_type == 1">{{ Depth @ BP={m_TEX0.TBP0,X}-{m_end_block,X} BW={m_TEX0.TBW} PSM={m_TEX0.PSM,X} {m_unscaled_size.x}x{m_unscaled_size.y} {m_valid.z},{m_valid.w} }}</DisplayString>
|
||||
</Type>
|
||||
</AutoVisualizer>
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
#include "PrecompiledHeader.h"
|
||||
#include "GS/GSClut.h"
|
||||
#include "GS/GSExtra.h"
|
||||
#include "GS/GSLocalMemory.h"
|
||||
#include "GS/GSGL.h"
|
||||
#include "GS/Renderers/Common/GSDevice.h"
|
||||
@@ -27,7 +28,7 @@ GSClut::GSClut(GSLocalMemory* mem)
|
||||
static constexpr u32 CLUT_ALLOC_SIZE = 4096 * 2;
|
||||
|
||||
// 1k + 1k for mirrored area simulating wrapping memory
|
||||
m_clut = static_cast<u16*>(_aligned_malloc(CLUT_ALLOC_SIZE, 32));
|
||||
m_clut = static_cast<u16*>(_aligned_malloc(CLUT_ALLOC_SIZE, VECTOR_ALIGNMENT));
|
||||
if (!m_clut)
|
||||
throw std::bad_alloc();
|
||||
|
||||
|
||||
@@ -15,8 +15,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GSVector.h"
|
||||
#include "GS/GSVector.h"
|
||||
#include "pcsx2/Config.h"
|
||||
#include "common/Align.h"
|
||||
|
||||
/// Like `memcmp(&a, &b, sizeof(T)) == 0` but faster
|
||||
template <typename T>
|
||||
@@ -117,6 +118,17 @@ static constexpr u32 MAX_SKIPPED_DUPLICATE_FRAMES = 3;
|
||||
extern void* GSAllocateWrappedMemory(size_t size, size_t repeat);
|
||||
extern void GSFreeWrappedMemory(void* ptr, size_t size, size_t repeat);
|
||||
|
||||
/// We want all allocations and pitches to be aligned to 32-bit, regardless of whether we're
|
||||
/// SSE4 or AVX2, because of multi-ISA.
|
||||
static constexpr u32 VECTOR_ALIGNMENT = 32;
|
||||
|
||||
/// Aligns allocation/pitch size to preferred host size.
|
||||
template<typename T>
|
||||
__fi static T VectorAlign(T value)
|
||||
{
|
||||
return Common::AlignUpPow2(value, VECTOR_ALIGNMENT);
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
|
||||
#ifdef __POSIX__
|
||||
|
||||
@@ -286,7 +286,7 @@ GSPixelOffset* GSLocalMemory::GetPixelOffset(const GIFRegFRAME& FRAME, const GIF
|
||||
return it->second;
|
||||
}
|
||||
|
||||
GSPixelOffset* off = (GSPixelOffset*)_aligned_malloc(sizeof(GSPixelOffset), 32);
|
||||
GSPixelOffset* off = (GSPixelOffset*)_aligned_malloc(sizeof(GSPixelOffset), VECTOR_ALIGNMENT);
|
||||
|
||||
off->hash = hash;
|
||||
off->fbp = fbp;
|
||||
@@ -339,7 +339,7 @@ GSPixelOffset4* GSLocalMemory::GetPixelOffset4(const GIFRegFRAME& FRAME, const G
|
||||
return it->second;
|
||||
}
|
||||
|
||||
GSPixelOffset4* off = (GSPixelOffset4*)_aligned_malloc(sizeof(GSPixelOffset4), 32);
|
||||
GSPixelOffset4* off = (GSPixelOffset4*)_aligned_malloc(sizeof(GSPixelOffset4), VECTOR_ALIGNMENT);
|
||||
|
||||
off->hash = hash;
|
||||
off->fbp = fbp;
|
||||
@@ -544,7 +544,7 @@ void GSLocalMemory::SaveBMP(const std::string& fn, u32 bp, u32 bw, u32 psm, int
|
||||
{
|
||||
int pitch = w * 4;
|
||||
int size = pitch * h;
|
||||
void* bits = _aligned_malloc(size, 32);
|
||||
void* bits = _aligned_malloc(size, VECTOR_ALIGNMENT);
|
||||
|
||||
GIFRegTEX0 TEX0;
|
||||
|
||||
|
||||
@@ -19,8 +19,9 @@
|
||||
#include "common/FileSystem.h"
|
||||
#include "common/StringUtil.h"
|
||||
|
||||
#include "GSDump.h"
|
||||
#include "GSLzma.h"
|
||||
#include "GS/GSDump.h"
|
||||
#include "GS/GSLzma.h"
|
||||
#include "GS/GSExtra.h"
|
||||
|
||||
using namespace GSDumpTypes;
|
||||
|
||||
@@ -273,8 +274,8 @@ void GSDumpLzma::Initialize()
|
||||
}
|
||||
|
||||
m_buff_size = 1024*1024;
|
||||
m_area = (uint8_t*)_aligned_malloc(m_buff_size, 32);
|
||||
m_inbuf = (uint8_t*)_aligned_malloc(BUFSIZ, 32);
|
||||
m_area = (uint8_t*)_aligned_malloc(m_buff_size, VECTOR_ALIGNMENT);
|
||||
m_inbuf = (uint8_t*)_aligned_malloc(BUFSIZ, VECTOR_ALIGNMENT);
|
||||
m_avail = 0;
|
||||
m_start = 0;
|
||||
|
||||
|
||||
@@ -27,9 +27,9 @@ public:
|
||||
Swizzle,
|
||||
Unswizzle,
|
||||
Fillrate,
|
||||
Quad,
|
||||
SyncPoint,
|
||||
Barriers,
|
||||
RenderPasses,
|
||||
CounterLast,
|
||||
|
||||
// Reused counters for HW.
|
||||
|
||||
@@ -30,7 +30,7 @@ int GSState::s_transfer_n = 0;
|
||||
|
||||
static __fi bool IsAutoFlushEnabled()
|
||||
{
|
||||
return (GSConfig.Renderer == GSRendererType::SW) ? GSConfig.AutoFlushSW : GSConfig.UserHacks_AutoFlush;
|
||||
return (GSConfig.Renderer == GSRendererType::SW) ? GSConfig.AutoFlushSW : (GSConfig.UserHacks_AutoFlush != GSHWAutoFlushLevel::Disabled);
|
||||
}
|
||||
|
||||
static __fi bool IsFirstProvokingVertex()
|
||||
@@ -188,6 +188,7 @@ void GSState::Reset(bool hardware_reset)
|
||||
m_vertex.next = 0;
|
||||
m_index.tail = 0;
|
||||
m_scanmask_used = 0;
|
||||
m_texflush_flag = false;
|
||||
m_dirty_gs_regs = 0;
|
||||
m_backed_up_ctx = -1;
|
||||
|
||||
@@ -407,9 +408,6 @@ void GSState::DumpVertices(const std::string& filename)
|
||||
case GSFlushReason::CLUTCHANGE:
|
||||
file << "CLUT CHANGE (RELOAD REQ)";
|
||||
break;
|
||||
case GSFlushReason::TEXFLUSH:
|
||||
file << "TEXFLUSH CALL";
|
||||
break;
|
||||
case GSFlushReason::GSTRANSFER:
|
||||
file << "GS TRANSFER";
|
||||
break;
|
||||
@@ -1168,12 +1166,7 @@ void GSState::GIFRegHandlerFOGCOL(const GIFReg* RESTRICT r)
|
||||
void GSState::GIFRegHandlerTEXFLUSH(const GIFReg* RESTRICT r)
|
||||
{
|
||||
GL_REG("TEXFLUSH = 0x%x_%x PRIM TME %x", r->U32[1], r->U32[0], PRIM->TME);
|
||||
|
||||
// Some games do a single sprite draw to itself, then flush the texture cache, then use that texture again.
|
||||
// This won't get picked up by the new autoflush logic (which checks for page crossings for the PS2 Texture Cache flush)
|
||||
// so we need to do it here.
|
||||
if (IsAutoFlushEnabled() && IsAutoFlushDraw())
|
||||
Flush(GSFlushReason::TEXFLUSH);
|
||||
m_texflush_flag = true;
|
||||
}
|
||||
|
||||
template <int i>
|
||||
@@ -1580,6 +1573,9 @@ void GSState::FlushPrim()
|
||||
{
|
||||
GL_REG("FlushPrim ctxt %d", PRIM->CTXT);
|
||||
|
||||
// clear texture cache flushed flag, since we're reading from it
|
||||
m_texflush_flag = PRIM->TME ? false : m_texflush_flag;
|
||||
|
||||
// internal frame rate detection based on sprite blits to the display framebuffer
|
||||
{
|
||||
const u32 FRAME_FBP = m_context->FRAME.FBP;
|
||||
@@ -2638,31 +2634,31 @@ void GSState::GrowVertexBuffer()
|
||||
{
|
||||
const u32 maxcount = std::max<u32>(m_vertex.maxcount * 3 / 2, 10000);
|
||||
|
||||
GSVertex* vertex = (GSVertex*)_aligned_malloc(sizeof(GSVertex) * maxcount, 32);
|
||||
GSVertex* vertex = static_cast<GSVertex*>(_aligned_malloc(sizeof(GSVertex) * maxcount, 32));
|
||||
// Worst case index list is a list of points with vs expansion, 6 indices per point
|
||||
u32* index = (u32*)_aligned_malloc(sizeof(u32) * maxcount * 6, 32);
|
||||
u16* index = static_cast<u16*>(_aligned_malloc(sizeof(u16) * maxcount * 6, 32));
|
||||
|
||||
if (vertex == NULL || index == NULL)
|
||||
if (!vertex || !index)
|
||||
{
|
||||
const u32 vert_byte_count = sizeof(GSVertex) * maxcount;
|
||||
const u32 idx_byte_count = sizeof(u32) * maxcount * 3;
|
||||
const u32 idx_byte_count = sizeof(u16) * maxcount * 3;
|
||||
|
||||
Console.Error("GS: failed to allocate %zu bytes for verticles and %zu for indices.",
|
||||
Console.Error("GS: failed to allocate %zu bytes for vertices and %zu for indices.",
|
||||
vert_byte_count, idx_byte_count);
|
||||
|
||||
throw GSError();
|
||||
}
|
||||
|
||||
if (m_vertex.buff != NULL)
|
||||
if (m_vertex.buff)
|
||||
{
|
||||
memcpy(vertex, m_vertex.buff, sizeof(GSVertex) * m_vertex.tail);
|
||||
std::memcpy(vertex, m_vertex.buff, sizeof(GSVertex) * m_vertex.tail);
|
||||
|
||||
_aligned_free(m_vertex.buff);
|
||||
}
|
||||
|
||||
if (m_index.buff != NULL)
|
||||
if (m_index.buff)
|
||||
{
|
||||
memcpy(index, m_index.buff, sizeof(u32) * m_index.tail);
|
||||
std::memcpy(index, m_index.buff, sizeof(u16) * m_index.tail);
|
||||
|
||||
_aligned_free(m_index.buff);
|
||||
}
|
||||
@@ -2776,9 +2772,9 @@ GSState::PRIM_OVERLAP GSState::PrimitiveOverlap()
|
||||
return overlap;
|
||||
}
|
||||
|
||||
__forceinline bool GSState::IsAutoFlushDraw()
|
||||
__forceinline bool GSState::IsAutoFlushDraw(u32 prim)
|
||||
{
|
||||
if (!PRIM->TME)
|
||||
if (!PRIM->TME || (GSConfig.UserHacks_AutoFlush == GSHWAutoFlushLevel::SpritesOnly && prim != GS_SPRITE))
|
||||
return false;
|
||||
|
||||
const u32 frame_mask = GSLocalMemory::m_psm[m_context->TEX0.PSM].fmsk;
|
||||
@@ -2846,13 +2842,13 @@ template<u32 prim, bool index_swap>
|
||||
__forceinline void GSState::HandleAutoFlush()
|
||||
{
|
||||
// Kind of a cheat, making the assumption that 2 consecutive fan/strip triangles won't overlap each other (*should* be safe)
|
||||
if ((m_index.tail & 1) && (PRIM->PRIM == GS_TRIANGLESTRIP || PRIM->PRIM == GS_TRIANGLEFAN))
|
||||
if ((m_index.tail & 1) && (prim == GS_TRIANGLESTRIP || prim == GS_TRIANGLEFAN))
|
||||
return;
|
||||
|
||||
// To briefly explain what's going on here, what we are checking for is draws over a texture when the source and destination are themselves.
|
||||
// Because one page of the texture gets buffered in the Texture Cache (the PS2's one) if any of those pixels are overwritten, you still read the old data.
|
||||
// So we need to calculate if a page boundary is being crossed for the format it is in and if the same part of the texture being written and read inside the draw.
|
||||
if (IsAutoFlushDraw())
|
||||
if (IsAutoFlushDraw(prim))
|
||||
{
|
||||
int n = 1;
|
||||
u32 buff[3];
|
||||
@@ -2955,7 +2951,7 @@ __forceinline void GSState::HandleAutoFlush()
|
||||
const GSVector4i tex_page = tex_rect.xyxy() & page_mask;
|
||||
|
||||
// Crossed page since last draw end
|
||||
if(!tex_page.eq(last_tex_page))
|
||||
if (!tex_page.eq(last_tex_page) || m_texflush_flag)
|
||||
{
|
||||
const u32 frame_mask = GSLocalMemory::m_psm[m_context->TEX0.PSM].fmsk;
|
||||
const bool frame_hit = (m_context->FRAME.Block() == m_context->TEX0.TBP0) && !(m_context->TEST.ATE && m_context->TEST.ATST == 0 && m_context->TEST.AFAIL == 2) && ((m_context->FRAME.FBMSK & frame_mask) != frame_mask);
|
||||
@@ -3063,21 +3059,24 @@ static constexpr u32 MaxVerticesForPrim(u32 prim)
|
||||
{
|
||||
switch (prim)
|
||||
{
|
||||
// Four indices per 1 vertex.
|
||||
case GS_POINTLIST:
|
||||
case GS_INVALID:
|
||||
// Needed due to expansion in hardware renderers.
|
||||
|
||||
// Indices are shifted left by 2 to form quads.
|
||||
case GS_LINELIST:
|
||||
case GS_LINESTRIP:
|
||||
return (std::numeric_limits<u16>::max() / 4) - 4;
|
||||
|
||||
// Four indices per two vertices.
|
||||
case GS_SPRITE:
|
||||
return (std::numeric_limits<u16>::max() / 2) - 2;
|
||||
|
||||
case GS_LINELIST:
|
||||
case GS_LINESTRIP:
|
||||
case GS_TRIANGLELIST:
|
||||
case GS_TRIANGLESTRIP:
|
||||
case GS_TRIANGLEFAN:
|
||||
default:
|
||||
return 0;
|
||||
return (std::numeric_limits<u16>::max() - 3);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3229,19 +3228,19 @@ __forceinline void GSState::VertexKick(u32 skip)
|
||||
m_backed_up_ctx = m_env.PRIM.CTXT;
|
||||
}
|
||||
|
||||
u32* RESTRICT buff = &m_index.buff[m_index.tail];
|
||||
u16* RESTRICT buff = &m_index.buff[m_index.tail];
|
||||
|
||||
switch (prim)
|
||||
{
|
||||
case GS_POINTLIST:
|
||||
buff[0] = head + 0;
|
||||
buff[0] = static_cast<u16>(head + 0);
|
||||
m_vertex.head = head + 1;
|
||||
m_vertex.next = head + 1;
|
||||
m_index.tail += 1;
|
||||
break;
|
||||
case GS_LINELIST:
|
||||
buff[0] = head + (index_swap ? 1 : 0);
|
||||
buff[1] = head + (index_swap ? 0 : 1);
|
||||
buff[0] = static_cast<u16>(head + (index_swap ? 1 : 0));
|
||||
buff[1] = static_cast<u16>(head + (index_swap ? 0 : 1));
|
||||
m_vertex.head = head + 2;
|
||||
m_vertex.next = head + 2;
|
||||
m_index.tail += 2;
|
||||
@@ -3254,16 +3253,16 @@ __forceinline void GSState::VertexKick(u32 skip)
|
||||
head = next;
|
||||
m_vertex.tail = next + 2;
|
||||
}
|
||||
buff[0] = head + (index_swap ? 1 : 0);
|
||||
buff[1] = head + (index_swap ? 0 : 1);
|
||||
buff[0] = static_cast<u16>(head + (index_swap ? 1 : 0));
|
||||
buff[1] = static_cast<u16>(head + (index_swap ? 0 : 1));
|
||||
m_vertex.head = head + 1;
|
||||
m_vertex.next = head + 2;
|
||||
m_index.tail += 2;
|
||||
break;
|
||||
case GS_TRIANGLELIST:
|
||||
buff[0] = head + (index_swap ? 2 : 0);
|
||||
buff[1] = head + 1;
|
||||
buff[2] = head + (index_swap ? 0 : 2);
|
||||
buff[0] = static_cast<u16>(head + (index_swap ? 2 : 0));
|
||||
buff[1] = static_cast<u16>(head + 1);
|
||||
buff[2] = static_cast<u16>(head + (index_swap ? 0 : 2));
|
||||
m_vertex.head = head + 3;
|
||||
m_vertex.next = head + 3;
|
||||
m_index.tail += 3;
|
||||
@@ -3277,24 +3276,24 @@ __forceinline void GSState::VertexKick(u32 skip)
|
||||
head = next;
|
||||
m_vertex.tail = next + 3;
|
||||
}
|
||||
buff[0] = head + (index_swap ? 2 : 0);
|
||||
buff[1] = head + 1;
|
||||
buff[2] = head + (index_swap ? 0 : 2);
|
||||
buff[0] = static_cast<u16>(head + (index_swap ? 2 : 0));
|
||||
buff[1] = static_cast<u16>(head + 1);
|
||||
buff[2] = static_cast<u16>(head + (index_swap ? 0 : 2));
|
||||
m_vertex.head = head + 1;
|
||||
m_vertex.next = head + 3;
|
||||
m_index.tail += 3;
|
||||
break;
|
||||
case GS_TRIANGLEFAN:
|
||||
// TODO: remove gaps, next == head && head < tail - 3 || next > head && next < tail - 2 (very rare)
|
||||
buff[0] = index_swap ? (tail - 1) : (head + 0);
|
||||
buff[1] = tail - 2;
|
||||
buff[2] = index_swap ? (head + 0) : (tail - 1);
|
||||
buff[0] = static_cast<u16>(index_swap ? (tail - 1) : (head + 0));
|
||||
buff[1] = static_cast<u16>(tail - 2);
|
||||
buff[2] = static_cast<u16>(index_swap ? (head + 0) : (tail - 1));
|
||||
m_vertex.next = tail;
|
||||
m_index.tail += 3;
|
||||
break;
|
||||
case GS_SPRITE:
|
||||
buff[0] = head + 0;
|
||||
buff[1] = head + 1;
|
||||
buff[0] = static_cast<u16>(head + 0);
|
||||
buff[1] = static_cast<u16>(head + 1);
|
||||
m_vertex.head = head + 2;
|
||||
m_vertex.next = head + 2;
|
||||
m_index.tail += 2;
|
||||
|
||||
@@ -144,10 +144,6 @@ protected:
|
||||
GSVector4i m_scissor = {};
|
||||
GSVector4i m_ofxy = {};
|
||||
|
||||
u8 m_scanmask_used = 0;
|
||||
bool tex_flushed = true;
|
||||
bool m_isPackedUV_HackFlag = false;
|
||||
|
||||
struct
|
||||
{
|
||||
GSVertex* buff;
|
||||
@@ -158,7 +154,7 @@ protected:
|
||||
|
||||
struct
|
||||
{
|
||||
u32* buff;
|
||||
u16* buff;
|
||||
u32 tail;
|
||||
} m_index = {};
|
||||
|
||||
@@ -168,7 +164,7 @@ protected:
|
||||
void UpdateVertexKick();
|
||||
|
||||
void GrowVertexBuffer();
|
||||
bool IsAutoFlushDraw();
|
||||
bool IsAutoFlushDraw(u32 prim);
|
||||
template<u32 prim, bool index_swap>
|
||||
void HandleAutoFlush();
|
||||
void CLUTAutoFlush(u32 prim);
|
||||
@@ -228,6 +224,9 @@ public:
|
||||
std::unique_ptr<GSDumpBase> m_dump;
|
||||
bool m_nativeres = false;
|
||||
bool m_mipmap = false;
|
||||
bool m_texflush_flag = false;
|
||||
bool m_isPackedUV_HackFlag = false;
|
||||
u8 m_scanmask_used = 0;
|
||||
u8 m_force_preload = 0;
|
||||
u32 m_dirty_gs_regs = 0;
|
||||
int m_backed_up_ctx = 0;
|
||||
@@ -268,17 +267,16 @@ public:
|
||||
RESET = 1 << 1,
|
||||
CONTEXTCHANGE = 1 << 2,
|
||||
CLUTCHANGE = 1 << 3,
|
||||
TEXFLUSH = 1 << 4,
|
||||
GSTRANSFER = 1 << 5,
|
||||
UPLOADDIRTYTEX = 1 << 6,
|
||||
LOCALTOLOCALMOVE = 1 << 7,
|
||||
DOWNLOADFIFO = 1 << 8,
|
||||
SAVESTATE = 1 << 9,
|
||||
LOADSTATE = 1 << 10,
|
||||
AUTOFLUSH = 1 << 11,
|
||||
VSYNC = 1 << 12,
|
||||
GSREOPEN = 1 << 13,
|
||||
VERTEXCOUNT = 1 << 14,
|
||||
GSTRANSFER = 1 << 4,
|
||||
UPLOADDIRTYTEX = 1 << 5,
|
||||
LOCALTOLOCALMOVE = 1 << 6,
|
||||
DOWNLOADFIFO = 1 << 7,
|
||||
SAVESTATE = 1 << 8,
|
||||
LOADSTATE = 1 << 9,
|
||||
AUTOFLUSH = 1 << 10,
|
||||
VSYNC = 1 << 11,
|
||||
GSREOPEN = 1 << 12,
|
||||
VERTEXCOUNT = 1 << 13,
|
||||
};
|
||||
|
||||
GSFlushReason m_state_flush_reason = UNKNOWN;
|
||||
|
||||
@@ -28,6 +28,16 @@ class alignas(16) GSVector4i
|
||||
{
|
||||
}
|
||||
|
||||
constexpr GSVector4i(cxpr_init_tag, short s0, short s1, short s2, short s3, short s4, short s5, short s6, short s7)
|
||||
: I16{s0, s1, s2, s3, s4, s5, s6, s7}
|
||||
{
|
||||
}
|
||||
|
||||
constexpr GSVector4i(cxpr_init_tag, char b0, char b1, char b2, char b3, char b4, char b5, char b6, char b7, char b8, char b9, char b10, char b11, char b12, char b13, char b14, char b15)
|
||||
: I8{b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15}
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
union
|
||||
{
|
||||
@@ -62,6 +72,16 @@ public:
|
||||
return GSVector4i(cxpr_init, x, x, x, x);
|
||||
}
|
||||
|
||||
constexpr static GSVector4i cxpr16(short s0, short s1, short s2, short s3, short s4, short s5, short s6, short s7)
|
||||
{
|
||||
return GSVector4i(cxpr_init, s0, s1, s2, s3, s4, s5, s6, s7);
|
||||
}
|
||||
|
||||
constexpr static GSVector4i cxpr8(char b0, char b1, char b2, char b3, char b4, char b5, char b6, char b7, char b8, char b9, char b10, char b11, char b12, char b13, char b14, char b15)
|
||||
{
|
||||
return GSVector4i(cxpr_init, b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15);
|
||||
}
|
||||
|
||||
__forceinline GSVector4i(int x, int y, int z, int w)
|
||||
{
|
||||
m = _mm_set_epi32(w, z, y, x);
|
||||
@@ -2001,6 +2021,10 @@ public:
|
||||
return v;
|
||||
}
|
||||
|
||||
__forceinline static GSVector4i broadcast16(u16 value)
|
||||
{
|
||||
return GSVector4i(_mm_set1_epi16(value));
|
||||
}
|
||||
|
||||
__forceinline static GSVector4i zero() { return GSVector4i(_mm_setzero_si128()); }
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
#include "GS/GSGL.h"
|
||||
#include "GS/GS.h"
|
||||
#include "Host.h"
|
||||
#include "HostSettings.h"
|
||||
#include "common/Align.h"
|
||||
#include "common/StringUtil.h"
|
||||
|
||||
#include "imgui.h"
|
||||
@@ -90,7 +92,7 @@ GSDevice::GSDevice() = default;
|
||||
GSDevice::~GSDevice()
|
||||
{
|
||||
// should've been cleaned up in Destroy()
|
||||
pxAssert(m_pool.empty() && !m_merge && !m_weavebob && !m_blend && !m_mad && !m_target_tmp && !m_cas);
|
||||
pxAssert(m_pool[0].empty() && m_pool[1].empty() && !m_merge && !m_weavebob && !m_blend && !m_mad && !m_target_tmp && !m_cas);
|
||||
}
|
||||
|
||||
const char* GSDevice::RenderAPIToString(RenderAPI api)
|
||||
@@ -112,14 +114,16 @@ const char* GSDevice::RenderAPIToString(RenderAPI api)
|
||||
}
|
||||
}
|
||||
|
||||
bool GSDevice::ParseFullscreenMode(const std::string_view& mode, u32* width, u32* height, float* refresh_rate)
|
||||
bool GSDevice::GetRequestedExclusiveFullscreenMode(u32* width, u32* height, float* refresh_rate)
|
||||
{
|
||||
const std::string mode = Host::GetBaseStringSettingValue("EmuCore/GS", "FullscreenMode", "");
|
||||
if (!mode.empty())
|
||||
{
|
||||
const std::string_view mode_view = mode;
|
||||
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));
|
||||
std::optional<u32> owidth = StringUtil::FromChars<u32>(mode_view.substr(0, sep1));
|
||||
sep1++;
|
||||
|
||||
while (sep1 < mode.length() && std::isspace(mode[sep1]))
|
||||
@@ -130,7 +134,7 @@ bool GSDevice::ParseFullscreenMode(const std::string_view& mode, u32* width, u32
|
||||
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));
|
||||
std::optional<u32> oheight = StringUtil::FromChars<u32>(mode_view.substr(sep1, sep2 - sep1));
|
||||
sep2++;
|
||||
|
||||
while (sep2 < mode.length() && std::isspace(mode[sep2]))
|
||||
@@ -138,7 +142,7 @@ bool GSDevice::ParseFullscreenMode(const std::string_view& mode, u32* width, u32
|
||||
|
||||
if (oheight.has_value() && sep2 < mode.length())
|
||||
{
|
||||
std::optional<float> orefresh_rate = StringUtil::FromChars<float>(mode.substr(sep2));
|
||||
std::optional<float> orefresh_rate = StringUtil::FromChars<float>(mode_view.substr(sep2));
|
||||
if (orefresh_rate.has_value())
|
||||
{
|
||||
*width = owidth.value();
|
||||
@@ -165,9 +169,9 @@ std::string GSDevice::GetFullscreenModeString(u32 width, u32 height, float refre
|
||||
|
||||
void GSDevice::GenerateExpansionIndexBuffer(void* buffer)
|
||||
{
|
||||
static constexpr u32 MAX_INDEX = std::numeric_limits<u16>::max();
|
||||
static constexpr u32 MAX_INDEX = EXPAND_BUFFER_SIZE / 6 / sizeof(u16);
|
||||
|
||||
u32* idx_buffer = static_cast<u32*>(buffer);
|
||||
u16* idx_buffer = static_cast<u16*>(buffer);
|
||||
for (u32 i = 0; i < MAX_INDEX; i++)
|
||||
{
|
||||
const u32 base = i * 4;
|
||||
@@ -180,10 +184,9 @@ void GSDevice::GenerateExpansionIndexBuffer(void* buffer)
|
||||
}
|
||||
}
|
||||
|
||||
bool GSDevice::Create(const WindowInfo& wi, VsyncMode vsync)
|
||||
bool GSDevice::Create()
|
||||
{
|
||||
m_window_info = wi;
|
||||
m_vsync_mode = vsync;
|
||||
m_vsync_mode = Host::GetEffectiveVSyncMode();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -199,6 +202,20 @@ void GSDevice::Destroy()
|
||||
PurgePool();
|
||||
}
|
||||
|
||||
bool GSDevice::AcquireWindow(bool recreate_window)
|
||||
{
|
||||
std::optional<WindowInfo> wi = Host::AcquireRenderWindow(recreate_window);
|
||||
if (!wi.has_value())
|
||||
{
|
||||
Console.Error("Failed to acquire render window.");
|
||||
Host::ReportErrorAsync("Error", "Failed to acquire render window. The log may have more information.");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_window_info = std::move(wi.value());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GSDevice::GetHostRefreshRate(float* refresh_rate)
|
||||
{
|
||||
if (m_window_info.surface_refresh_rate > 0.0f)
|
||||
@@ -247,11 +264,12 @@ GSTexture* GSDevice::FetchSurface(GSTexture::Type type, int width, int height, i
|
||||
{
|
||||
const GSVector2i size(width, height);
|
||||
const bool prefer_new_texture = (m_features.prefer_new_textures && type == GSTexture::Type::Texture && !prefer_reuse);
|
||||
FastList<GSTexture*>& pool = m_pool[type != GSTexture::Type::Texture];
|
||||
|
||||
GSTexture* t = nullptr;
|
||||
auto fallback = m_pool.end();
|
||||
auto fallback = pool.end();
|
||||
|
||||
for (auto i = m_pool.begin(); i != m_pool.end(); ++i)
|
||||
for (auto i = pool.begin(); i != pool.end(); ++i)
|
||||
{
|
||||
t = *i;
|
||||
|
||||
@@ -262,10 +280,10 @@ GSTexture* GSDevice::FetchSurface(GSTexture::Type type, int width, int height, i
|
||||
if (!prefer_new_texture || t->GetLastFrameUsed() != m_frame)
|
||||
{
|
||||
m_pool_memory_usage -= t->GetMemUsage();
|
||||
m_pool.erase(i);
|
||||
pool.erase(i);
|
||||
break;
|
||||
}
|
||||
else if (fallback == m_pool.end())
|
||||
else if (fallback == pool.end())
|
||||
{
|
||||
fallback = i;
|
||||
}
|
||||
@@ -276,11 +294,12 @@ GSTexture* GSDevice::FetchSurface(GSTexture::Type type, int width, int height, i
|
||||
|
||||
if (!t)
|
||||
{
|
||||
if (m_pool.size() >= MAX_POOLED_TEXTURES && fallback != m_pool.end())
|
||||
if (pool.size() >= ((type == GSTexture::Type::Texture) ? MAX_POOLED_TEXTURES : MAX_POOLED_TARGETS) &&
|
||||
fallback != pool.end())
|
||||
{
|
||||
t = *fallback;
|
||||
m_pool_memory_usage -= t->GetMemUsage();
|
||||
m_pool.erase(fallback);
|
||||
pool.erase(fallback);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -322,17 +341,24 @@ void GSDevice::Recycle(GSTexture* t)
|
||||
|
||||
t->SetLastFrameUsed(m_frame);
|
||||
|
||||
m_pool.push_front(t);
|
||||
FastList<GSTexture*>& pool = m_pool[!t->IsTexture()];
|
||||
pool.push_front(t);
|
||||
m_pool_memory_usage += t->GetMemUsage();
|
||||
|
||||
//printf("%d\n",m_pool.size());
|
||||
|
||||
while (m_pool.size() > MAX_POOLED_TEXTURES)
|
||||
const u32 max_size = t->IsTexture() ? MAX_POOLED_TEXTURES : MAX_POOLED_TARGETS;
|
||||
const u32 max_age = t->IsTexture() ? MAX_TEXTURE_AGE : MAX_TARGET_AGE;
|
||||
while (pool.size() > max_size)
|
||||
{
|
||||
m_pool_memory_usage -= m_pool.back()->GetMemUsage();
|
||||
delete m_pool.back();
|
||||
// Don't toss when the texture was last used in this frame.
|
||||
// Because we're going to need to keep it alive anyway.
|
||||
GSTexture* back = pool.back();
|
||||
if ((m_frame - back->GetLastFrameUsed()) < max_age)
|
||||
break;
|
||||
|
||||
m_pool.pop_back();
|
||||
m_pool_memory_usage -= back->GetMemUsage();
|
||||
delete back;
|
||||
|
||||
pool.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,20 +372,33 @@ void GSDevice::AgePool()
|
||||
{
|
||||
m_frame++;
|
||||
|
||||
while (m_pool.size() > 40 && m_frame - m_pool.back()->GetLastFrameUsed() > 10)
|
||||
// Toss out textures when they're not too-recently used.
|
||||
for (u32 pool_idx = 0; pool_idx < m_pool.size(); pool_idx++)
|
||||
{
|
||||
m_pool_memory_usage -= m_pool.back()->GetMemUsage();
|
||||
delete m_pool.back();
|
||||
const u32 max_age = (pool_idx == 0) ? MAX_TEXTURE_AGE : MAX_TARGET_AGE;
|
||||
FastList<GSTexture*>& pool = m_pool[pool_idx];
|
||||
while (!pool.empty())
|
||||
{
|
||||
GSTexture* back = pool.back();
|
||||
if ((m_frame - back->GetLastFrameUsed()) < max_age)
|
||||
break;
|
||||
|
||||
m_pool.pop_back();
|
||||
m_pool_memory_usage -= back->GetMemUsage();
|
||||
delete back;
|
||||
|
||||
pool.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GSDevice::PurgePool()
|
||||
{
|
||||
for (auto t : m_pool)
|
||||
delete t;
|
||||
m_pool.clear();
|
||||
for (FastList<GSTexture*>& pool : m_pool)
|
||||
{
|
||||
for (GSTexture* t : pool)
|
||||
delete t;
|
||||
pool.clear();
|
||||
}
|
||||
m_pool_memory_usage = 0;
|
||||
}
|
||||
|
||||
@@ -379,14 +418,6 @@ GSTexture* GSDevice::CreateTexture(int w, int h, int mipmap_levels, GSTexture::F
|
||||
return FetchSurface(GSTexture::Type::Texture, w, h, levels, format, false, prefer_reuse);
|
||||
}
|
||||
|
||||
GSTexture::Format GSDevice::GetDefaultTextureFormat(GSTexture::Type type)
|
||||
{
|
||||
if (type == GSTexture::Type::DepthStencil)
|
||||
return GSTexture::Format::DepthStencil;
|
||||
else
|
||||
return GSTexture::Format::Color;
|
||||
}
|
||||
|
||||
void GSDevice::StretchRect(GSTexture* sTex, GSTexture* dTex, const GSVector4& dRect, ShaderConvert shader, bool linear)
|
||||
{
|
||||
StretchRect(sTex, GSVector4(0, 0, 1, 1), dTex, dRect, shader, linear);
|
||||
@@ -440,7 +471,7 @@ void GSDevice::ClearCurrent()
|
||||
|
||||
void GSDevice::Merge(GSTexture* sTex[3], GSVector4* sRect, GSVector4* dRect, const GSVector2i& fs, const GSRegPMODE& PMODE, const GSRegEXTBUF& EXTBUF, const GSVector4& c)
|
||||
{
|
||||
if (ResizeTarget(&m_merge, fs.x, fs.y))
|
||||
if (ResizeRenderTarget(&m_merge, fs.x, fs.y, false, false))
|
||||
DoMerge(sTex, sRect, m_merge, dRect, PMODE, EXTBUF, c, GSConfig.PCRTCOffsets);
|
||||
|
||||
m_current = m_merge;
|
||||
@@ -480,20 +511,20 @@ void GSDevice::Interlace(const GSVector2i& ds, int field, int mode, float yoffse
|
||||
switch (mode)
|
||||
{
|
||||
case 0: // Weave
|
||||
ResizeTarget(&m_weavebob, ds.x, ds.y);
|
||||
ResizeRenderTarget(&m_weavebob, ds.x, ds.y, true, false);
|
||||
do_interlace(m_merge, m_weavebob, ShaderInterlace::WEAVE, false, offset, field);
|
||||
m_current = m_weavebob;
|
||||
break;
|
||||
case 1: // Bob
|
||||
// Field is reversed here as we are countering the bounce.
|
||||
ResizeTarget(&m_weavebob, ds.x, ds.y);
|
||||
ResizeRenderTarget(&m_weavebob, ds.x, ds.y, true, false);
|
||||
do_interlace(m_merge, m_weavebob, ShaderInterlace::BOB, true, yoffset * (1 - field), 0);
|
||||
m_current = m_weavebob;
|
||||
break;
|
||||
case 2: // Blend
|
||||
ResizeTarget(&m_weavebob, ds.x, ds.y);
|
||||
ResizeRenderTarget(&m_weavebob, ds.x, ds.y, true, false);
|
||||
do_interlace(m_merge, m_weavebob, ShaderInterlace::WEAVE, false, offset, field);
|
||||
ResizeTarget(&m_blend, ds.x, ds.y);
|
||||
ResizeRenderTarget(&m_blend, ds.x, ds.y, true, false);
|
||||
do_interlace(m_weavebob, m_blend, ShaderInterlace::BLEND, false, 0, 0);
|
||||
m_current = m_blend;
|
||||
break;
|
||||
@@ -502,9 +533,9 @@ void GSDevice::Interlace(const GSVector2i& ds, int field, int mode, float yoffse
|
||||
bufIdx &= ~1;
|
||||
bufIdx |= field;
|
||||
bufIdx &= 3;
|
||||
ResizeTarget(&m_mad, ds.x, ds.y * 2.0f);
|
||||
ResizeRenderTarget(&m_mad, ds.x, ds.y * 2.0f, true, false);
|
||||
do_interlace(m_merge, m_mad, ShaderInterlace::MAD_BUFFER, false, offset, bufIdx);
|
||||
ResizeTarget(&m_weavebob, ds.x, ds.y);
|
||||
ResizeRenderTarget(&m_weavebob, ds.x, ds.y, true, false);
|
||||
do_interlace(m_mad, m_weavebob, ShaderInterlace::MAD_RECONSTRUCT, false, 0, bufIdx);
|
||||
m_current = m_weavebob;
|
||||
break;
|
||||
@@ -518,9 +549,8 @@ void GSDevice::FXAA()
|
||||
{
|
||||
// Combining FXAA+ShadeBoost can't share the same target.
|
||||
GSTexture*& dTex = (m_current == m_target_tmp) ? m_merge : m_target_tmp;
|
||||
if (ResizeTexture(&dTex, GSTexture::Type::RenderTarget, m_current->GetWidth(), m_current->GetHeight(), false, true))
|
||||
if (ResizeRenderTarget(&dTex, m_current->GetWidth(), m_current->GetHeight(), false, false))
|
||||
{
|
||||
InvalidateRenderTarget(dTex);
|
||||
DoFXAA(m_current, dTex);
|
||||
m_current = dTex;
|
||||
}
|
||||
@@ -528,10 +558,8 @@ void GSDevice::FXAA()
|
||||
|
||||
void GSDevice::ShadeBoost()
|
||||
{
|
||||
if (ResizeTexture(&m_target_tmp, GSTexture::Type::RenderTarget, m_current->GetWidth(), m_current->GetHeight(), false, true))
|
||||
if (ResizeRenderTarget(&m_target_tmp, m_current->GetWidth(), m_current->GetHeight(), false, false))
|
||||
{
|
||||
InvalidateRenderTarget(m_target_tmp);
|
||||
|
||||
// predivide to avoid the divide (multiply) in the shader
|
||||
const float params[4] = {
|
||||
static_cast<float>(GSConfig.ShadeBoost_Brightness) * (1.0f / 50.0f),
|
||||
@@ -556,7 +584,7 @@ void GSDevice::Resize(int width, int height)
|
||||
s = m_current->GetSize() * GSVector2i(++multiplier);
|
||||
}
|
||||
|
||||
if (ResizeTexture(&dTex, GSTexture::Type::RenderTarget, s.x, s.y, false))
|
||||
if (ResizeRenderTarget(&dTex, s.x, s.y, false, false))
|
||||
{
|
||||
const GSVector4 sRect(0, 0, 1, 1);
|
||||
const GSVector4 dRect(0, 0, s.x, s.y);
|
||||
@@ -565,55 +593,48 @@ void GSDevice::Resize(int width, int height)
|
||||
}
|
||||
}
|
||||
|
||||
bool GSDevice::ResizeTexture(GSTexture** t, GSTexture::Type type, int w, int h, bool clear, bool prefer_reuse)
|
||||
bool GSDevice::ResizeRenderTarget(GSTexture** t, int w, int h, bool preserve_contents, bool recycle)
|
||||
{
|
||||
if (t == NULL)
|
||||
pxAssert(t);
|
||||
|
||||
GSTexture* orig_tex = *t;
|
||||
if (orig_tex && orig_tex->GetWidth() == w && orig_tex->GetHeight() == h)
|
||||
{
|
||||
ASSERT(0);
|
||||
if (!preserve_contents)
|
||||
InvalidateRenderTarget(orig_tex);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
GSTexture* new_tex;
|
||||
try
|
||||
{
|
||||
const GSTexture::Format fmt = orig_tex ? orig_tex->GetFormat() : GSTexture::Format::Color;
|
||||
new_tex = FetchSurface(GSTexture::Type::RenderTarget, w, h, 1, fmt, !preserve_contents, true);
|
||||
}
|
||||
catch (std::bad_alloc&)
|
||||
{
|
||||
Console.WriteLn("%dx%d texture allocation failed in ResizeTexture()", w, h);
|
||||
return false;
|
||||
}
|
||||
|
||||
GSTexture* t2 = *t;
|
||||
|
||||
if (t2 == NULL || t2->GetWidth() != w || t2->GetHeight() != h)
|
||||
if (preserve_contents && orig_tex)
|
||||
{
|
||||
const GSTexture::Format fmt = t2 ? t2->GetFormat() : GetDefaultTextureFormat(type);
|
||||
const int levels = t2 ? (t2->IsMipmap() ? MipmapLevelsForSize(w, h) : 1) : 1;
|
||||
|
||||
GSTexture* new_t = FetchSurface(type, w, h, levels, fmt, clear, prefer_reuse);
|
||||
if (new_t)
|
||||
{
|
||||
if (t2)
|
||||
{
|
||||
// TODO: We probably want to make this optional if we're overwriting it...
|
||||
const GSVector4 sRect(0, 0, 1, 1);
|
||||
const GSVector4 dRect(0, 0, t2->GetWidth(), t2->GetHeight());
|
||||
StretchRect(t2, sRect, new_t, dRect, ShaderConvert::COPY, true);
|
||||
Recycle(t2);
|
||||
}
|
||||
|
||||
t2 = new_t;
|
||||
*t = t2;
|
||||
}
|
||||
constexpr GSVector4 sRect = GSVector4::cxpr(0, 0, 1, 1);
|
||||
const GSVector4 dRect = GSVector4(orig_tex->GetRect());
|
||||
StretchRect(orig_tex, sRect, new_tex, dRect, ShaderConvert::COPY, true);
|
||||
}
|
||||
|
||||
return t2 != NULL;
|
||||
}
|
||||
if (orig_tex)
|
||||
{
|
||||
if (recycle)
|
||||
Recycle(orig_tex);
|
||||
else
|
||||
delete orig_tex;
|
||||
}
|
||||
|
||||
bool GSDevice::ResizeTexture(GSTexture** t, int w, int h, bool prefer_reuse)
|
||||
{
|
||||
return ResizeTexture(t, GSTexture::Type::Texture, w, h, false, prefer_reuse);
|
||||
}
|
||||
|
||||
bool GSDevice::ResizeTarget(GSTexture** t, int w, int h)
|
||||
{
|
||||
return ResizeTexture(t, GSTexture::Type::RenderTarget, w, h);
|
||||
}
|
||||
|
||||
bool GSDevice::ResizeTarget(GSTexture** t)
|
||||
{
|
||||
GSVector2i s = m_current->GetSize();
|
||||
return ResizeTexture(t, GSTexture::Type::RenderTarget, s.x, s.y);
|
||||
*t = new_tex;
|
||||
return true;
|
||||
}
|
||||
|
||||
void GSDevice::SetHWDrawConfigForAlphaPass(GSHWDrawConfig::PSSelector* ps,
|
||||
|
||||
@@ -264,6 +264,7 @@ struct alignas(16) GSHWDrawConfig
|
||||
/// Returns true if the fixed index buffer should be used.
|
||||
__fi bool UseExpandIndexBuffer() const { return (expand == VSExpand::Point || expand == VSExpand::Sprite); }
|
||||
};
|
||||
static_assert(sizeof(VSSelector) == 1, "VSSelector is a single byte");
|
||||
#pragma pack(pop)
|
||||
#pragma pack(push, 4)
|
||||
struct PSSelector
|
||||
@@ -382,6 +383,7 @@ struct alignas(16) GSHWDrawConfig
|
||||
no_color = no_color1 = 1;
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(PSSelector) == 12, "PSSelector is 12 bytes");
|
||||
#pragma pack(pop)
|
||||
struct PSSelectorHash
|
||||
{
|
||||
@@ -627,7 +629,7 @@ struct alignas(16) GSHWDrawConfig
|
||||
GSTexture* tex; ///< Source texture
|
||||
GSTexture* pal; ///< Palette texture
|
||||
const GSVertex* verts;///< Vertices to draw
|
||||
const u32* indices; ///< Indices to draw
|
||||
const u16* indices; ///< Indices to draw
|
||||
u32 nverts; ///< Number of vertices
|
||||
u32 nindices; ///< Number of indices
|
||||
u32 indices_per_prim; ///< Number of indices that make up one primitive
|
||||
@@ -738,18 +740,21 @@ public:
|
||||
// clang-format on
|
||||
|
||||
private:
|
||||
FastList<GSTexture*> m_pool;
|
||||
std::array<FastList<GSTexture*>, 2> m_pool; // [texture, target]
|
||||
u64 m_pool_memory_usage = 0;
|
||||
|
||||
static const std::array<HWBlend, 3*3*3*3> m_blendMap;
|
||||
static const std::array<u8, 16> m_replaceDualSrcBlendMap;
|
||||
|
||||
protected:
|
||||
static constexpr int NUM_INTERLACE_SHADERS = 5;
|
||||
static constexpr int NUM_INTERLACE_SHADERS = 5;
|
||||
static constexpr float MAD_SENSITIVITY = 0.08f;
|
||||
static constexpr u32 MAX_POOLED_TEXTURES = 300;
|
||||
static constexpr u32 NUM_CAS_CONSTANTS = 12; // 8 plus src offset x/y, 16 byte alignment
|
||||
static constexpr u32 EXPAND_BUFFER_SIZE = sizeof(u32) * std::numeric_limits<u16>::max() * 6;
|
||||
static constexpr u32 MAX_POOLED_TARGETS = 300;
|
||||
static constexpr u32 MAX_TARGET_AGE = 20;
|
||||
static constexpr u32 MAX_POOLED_TEXTURES = 300;
|
||||
static constexpr u32 MAX_TEXTURE_AGE = 10;
|
||||
static constexpr u32 NUM_CAS_CONSTANTS = 12; // 8 plus src offset x/y, 16 byte alignment
|
||||
static constexpr u32 EXPAND_BUFFER_SIZE = sizeof(u16) * 65532 * 6;
|
||||
|
||||
WindowInfo m_window_info;
|
||||
VsyncMode m_vsync_mode = VsyncMode::Off;
|
||||
@@ -776,6 +781,8 @@ protected:
|
||||
bool m_rbswapped = false;
|
||||
FeatureSupport m_features;
|
||||
|
||||
bool AcquireWindow(bool recreate_window);
|
||||
|
||||
virtual GSTexture* CreateSurface(GSTexture::Type type, int width, int height, int levels, GSTexture::Format format) = 0;
|
||||
GSTexture* FetchSurface(GSTexture::Type type, int width, int height, int levels, GSTexture::Format format, bool clear, bool prefer_reuse);
|
||||
|
||||
@@ -797,8 +804,8 @@ public:
|
||||
/// Returns a string representing the specified API.
|
||||
static const char* RenderAPIToString(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);
|
||||
/// Parses the configured fullscreen mode into its components (width * height @ refresh hz)
|
||||
static bool GetRequestedExclusiveFullscreenMode(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);
|
||||
@@ -806,7 +813,6 @@ public:
|
||||
/// Generates a fixed index buffer for expanding points and sprites. Buffer is assumed to be at least EXPAND_BUFFER_SIZE in size.
|
||||
static void GenerateExpansionIndexBuffer(void* buffer);
|
||||
|
||||
__fi unsigned int GetFrameNumber() const { return m_frame; }
|
||||
__fi u64 GetPoolMemoryUsage() const { return m_pool_memory_usage; }
|
||||
|
||||
__fi FeatureSupport Features() const { return m_features; }
|
||||
@@ -828,7 +834,7 @@ public:
|
||||
/// Recreates the font, call when the window scaling changes.
|
||||
bool UpdateImGuiFontTexture();
|
||||
|
||||
virtual bool Create(const WindowInfo& wi, VsyncMode vsync);
|
||||
virtual bool Create();
|
||||
virtual void Destroy();
|
||||
|
||||
/// Returns the graphics API used by this device.
|
||||
@@ -841,7 +847,7 @@ public:
|
||||
virtual void DestroySurface() = 0;
|
||||
|
||||
/// Switches to a new window/surface.
|
||||
virtual bool ChangeWindow(const WindowInfo& wi) = 0;
|
||||
virtual bool UpdateWindow() = 0;
|
||||
|
||||
/// Call when the window size changes externally to recreate any resources.
|
||||
virtual void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) = 0;
|
||||
@@ -849,12 +855,6 @@ public:
|
||||
/// Returns true if exclusive fullscreen is supported.
|
||||
virtual bool SupportsExclusiveFullscreen() const = 0;
|
||||
|
||||
/// Returns true if exclusive fullscreen is active.
|
||||
virtual bool IsExclusiveFullscreen() = 0;
|
||||
|
||||
/// Attempts to switch to the specified mode in exclusive fullscreen.
|
||||
virtual bool SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) = 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 PresentResult BeginPresent(bool frame_skip) = 0;
|
||||
@@ -890,7 +890,6 @@ public:
|
||||
GSTexture* CreateRenderTarget(int w, int h, GSTexture::Format format, bool clear = true);
|
||||
GSTexture* CreateDepthStencil(int w, int h, GSTexture::Format format, bool clear = true);
|
||||
GSTexture* CreateTexture(int w, int h, int mipmap_levels, GSTexture::Format format, bool prefer_reuse = false);
|
||||
GSTexture::Format GetDefaultTextureFormat(GSTexture::Type type);
|
||||
|
||||
virtual std::unique_ptr<GSDownloadTexture> CreateDownloadTexture(u32 width, u32 height, GSTexture::Format format) = 0;
|
||||
|
||||
@@ -929,10 +928,7 @@ public:
|
||||
|
||||
void CAS(GSTexture*& tex, GSVector4i& src_rect, GSVector4& src_uv, const GSVector4& draw_rect, bool sharpen_only);
|
||||
|
||||
bool ResizeTexture(GSTexture** t, GSTexture::Type type, int w, int h, bool clear = true, bool prefer_reuse = false);
|
||||
bool ResizeTexture(GSTexture** t, int w, int h, bool prefer_reuse = false);
|
||||
bool ResizeTarget(GSTexture** t, int w, int h);
|
||||
bool ResizeTarget(GSTexture** t);
|
||||
bool ResizeRenderTarget(GSTexture** t, int w, int h, bool preserve_contents, bool recycle);
|
||||
|
||||
bool IsRBSwapped() { return m_rbswapped; }
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
|
||||
<Type Name="FastList<*>">
|
||||
<DisplayString>{{ size={m_free_indexes_stack_top} }}</DisplayString>
|
||||
<Expand>
|
||||
<Item Name="[size]" ExcludeView="simple">m_free_indexes_stack_top</Item>
|
||||
<Item Name="[capacity]" ExcludeView="simple">m_capacity - 1</Item>
|
||||
|
||||
<CustomListItems MaxItemsPerView="5000" ExcludeView="simple">
|
||||
<Variable Name="index" InitialValue="m_buffer[0].next_index" />
|
||||
|
||||
<Size>m_free_indexes_stack_top</Size>
|
||||
<Loop>
|
||||
<Item>m_buffer[index].data,na</Item>
|
||||
<Exec>index = m_buffer[index].next_index</Exec>
|
||||
</Loop>
|
||||
</CustomListItems>
|
||||
</Expand>
|
||||
</Type>
|
||||
</AutoVisualizer>
|
||||
@@ -527,7 +527,7 @@ void GSRenderer::EndPresentFrame()
|
||||
ImGuiManager::NewFrame();
|
||||
}
|
||||
|
||||
void GSRenderer::VSync(u32 field, bool registers_written)
|
||||
void GSRenderer::VSync(u32 field, bool registers_written, bool idle_frame)
|
||||
{
|
||||
Flush(GSFlushReason::VSYNC);
|
||||
|
||||
@@ -569,6 +569,9 @@ void GSRenderer::VSync(u32 field, bool registers_written)
|
||||
|
||||
const bool blank_frame = !Merge(field);
|
||||
|
||||
m_last_draw_n = s_n;
|
||||
m_last_transfer_n = s_transfer_n;
|
||||
|
||||
if (skip_frame)
|
||||
{
|
||||
if (BeginPresentFrame(true))
|
||||
@@ -578,7 +581,8 @@ void GSRenderer::VSync(u32 field, bool registers_written)
|
||||
return;
|
||||
}
|
||||
|
||||
g_gs_device->AgePool();
|
||||
if (!idle_frame)
|
||||
g_gs_device->AgePool();
|
||||
|
||||
g_perfmon.EndFrame();
|
||||
if ((g_perfmon.GetFrame() & 0x1f) == 0)
|
||||
@@ -898,6 +902,11 @@ GSTexture* GSRenderer::LookupPaletteSource(u32 CBP, u32 CPSM, u32 CBW, GSVector2
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool GSRenderer::IsIdleFrame() const
|
||||
{
|
||||
return (m_last_draw_n == s_n && m_last_transfer_n == s_transfer_n);
|
||||
}
|
||||
|
||||
bool GSRenderer::SaveSnapshotToMemory(u32 window_width, u32 window_height, bool apply_aspect, bool crop_borders,
|
||||
u32* width, u32* height, std::vector<u32>* pixels)
|
||||
{
|
||||
|
||||
@@ -32,6 +32,10 @@ private:
|
||||
u32 m_dump_frames = 0;
|
||||
u32 m_skipped_duplicate_frames = 0;
|
||||
|
||||
// Tracking draw counters for idle frame detection.
|
||||
int m_last_draw_n = 0;
|
||||
int m_last_transfer_n = 0;
|
||||
|
||||
protected:
|
||||
GSVector2i m_real_size{0, 0};
|
||||
bool m_texture_shuffle = false;
|
||||
@@ -48,7 +52,7 @@ public:
|
||||
|
||||
virtual void Destroy();
|
||||
|
||||
virtual void VSync(u32 field, bool registers_written);
|
||||
virtual void VSync(u32 field, bool registers_written, bool idle_frame);
|
||||
virtual bool CanUpscale() { return false; }
|
||||
virtual float GetUpscaleMultiplier() { return 1.0f; }
|
||||
virtual float GetTextureScaleFactor() { return 1.0f; }
|
||||
@@ -57,6 +61,8 @@ public:
|
||||
|
||||
virtual GSTexture* LookupPaletteSource(u32 CBP, u32 CPSM, u32 CBW, GSVector2i& offset, float* scale, const GSVector2i& size);
|
||||
|
||||
bool IsIdleFrame() const;
|
||||
|
||||
bool SaveSnapshotToMemory(u32 window_width, u32 window_height, bool apply_aspect, bool crop_borders,
|
||||
u32* width, u32* height, std::vector<u32>* pixels);
|
||||
|
||||
|
||||
@@ -126,6 +126,10 @@ public:
|
||||
{
|
||||
return (m_type == Type::DepthStencil);
|
||||
}
|
||||
__fi bool IsTexture() const
|
||||
{
|
||||
return (m_type == Type::Texture);
|
||||
}
|
||||
|
||||
__fi State GetState() const { return m_state; }
|
||||
__fi void SetState(State state) { m_state = state; }
|
||||
|
||||
@@ -1,17 +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 "GSVertexList.h"
|
||||
@@ -1,82 +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
|
||||
|
||||
template <class Vertex>
|
||||
class GSVertexList
|
||||
{
|
||||
void* m_base;
|
||||
Vertex* m_v[3];
|
||||
int m_count;
|
||||
|
||||
public:
|
||||
GSVertexList()
|
||||
: m_count(0)
|
||||
{
|
||||
m_base = _aligned_malloc(sizeof(Vertex) * std::size(m_v), 32);
|
||||
|
||||
for (size_t i = 0; i < std::size(m_v); i++)
|
||||
{
|
||||
m_v[i] = &((Vertex*)m_base)[i];
|
||||
}
|
||||
}
|
||||
|
||||
virtual ~GSVertexList()
|
||||
{
|
||||
_aligned_free(m_base);
|
||||
}
|
||||
|
||||
void RemoveAll()
|
||||
{
|
||||
m_count = 0;
|
||||
}
|
||||
|
||||
__forceinline Vertex& AddTail()
|
||||
{
|
||||
ASSERT(m_count < 3);
|
||||
|
||||
return *m_v[m_count++];
|
||||
}
|
||||
|
||||
__forceinline void RemoveAt(int pos, int keep)
|
||||
{
|
||||
if (keep == 1)
|
||||
{
|
||||
Vertex* tmp = m_v[pos + 0];
|
||||
m_v[pos + 0] = m_v[pos + 1];
|
||||
m_v[pos + 1] = tmp;
|
||||
}
|
||||
else if (keep == 2)
|
||||
{
|
||||
Vertex* tmp = m_v[pos + 0];
|
||||
m_v[pos + 0] = m_v[pos + 1];
|
||||
m_v[pos + 1] = m_v[pos + 2];
|
||||
m_v[pos + 2] = tmp;
|
||||
}
|
||||
|
||||
m_count = pos + keep;
|
||||
}
|
||||
|
||||
__forceinline void GetAt(int i, Vertex& v)
|
||||
{
|
||||
v = *m_v[i];
|
||||
}
|
||||
|
||||
int GetCount()
|
||||
{
|
||||
return m_count;
|
||||
}
|
||||
};
|
||||
@@ -24,7 +24,7 @@ GSVertexTrace::GSVertexTrace(const GSState* state, bool provoking_vertex_first)
|
||||
MULTI_ISA_SELECT(GSVertexTracePopulateFunctions)(*this, provoking_vertex_first);
|
||||
}
|
||||
|
||||
void GSVertexTrace::Update(const void* vertex, const u32* index, int v_count, int i_count, GS_PRIM_CLASS primclass)
|
||||
void GSVertexTrace::Update(const void* vertex, const u16* index, int v_count, int i_count, GS_PRIM_CLASS primclass)
|
||||
{
|
||||
if (i_count == 0)
|
||||
return;
|
||||
@@ -43,7 +43,7 @@ void GSVertexTrace::Update(const void* vertex, const u32* index, int v_count, in
|
||||
// that feel big enough.
|
||||
if (!fst && !m_accurate_stq && m_min.t.z > 1e30)
|
||||
{
|
||||
fprintf(stderr, "Vertex Trace: float overflow detected ! min %e max %e\n", m_min.t.z, m_max.t.z);
|
||||
Console.Warning("Vertex Trace: float overflow detected ! min %e max %e", m_min.t.z, m_max.t.z);
|
||||
m_accurate_stq = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ public:
|
||||
protected:
|
||||
const GSState* m_state;
|
||||
|
||||
typedef void (*FindMinMaxPtr)(GSVertexTrace& vt, const void* vertex, const u32* index, int count);
|
||||
typedef void (*FindMinMaxPtr)(GSVertexTrace& vt, const void* vertex, const u16* index, int count);
|
||||
|
||||
FindMinMaxPtr m_fmm[2][2][2][2][4];
|
||||
|
||||
@@ -77,7 +77,7 @@ public:
|
||||
public:
|
||||
GSVertexTrace(const GSState* state, bool provoking_vertex_first);
|
||||
|
||||
void Update(const void* vertex, const u32* index, int v_count, int i_count, GS_PRIM_CLASS primclass);
|
||||
void Update(const void* vertex, const u16* index, int v_count, int i_count, GS_PRIM_CLASS primclass);
|
||||
|
||||
bool IsLinear() const { return m_filter.opt_linear; }
|
||||
bool IsRealLinear() const { return m_filter.linear; }
|
||||
|
||||
@@ -22,7 +22,7 @@ class CURRENT_ISA::GSVertexTraceFMM
|
||||
static constexpr GSVector4 s_minmax = GSVector4::cxpr(FLT_MAX, -FLT_MAX, 0.f, 0.f);
|
||||
|
||||
template <GS_PRIM_CLASS primclass, u32 iip, u32 tme, u32 fst, u32 color, bool flat_swapped>
|
||||
static void FindMinMax(GSVertexTrace& vt, const void* vertex, const u32* index, int count);
|
||||
static void FindMinMax(GSVertexTrace& vt, const void* vertex, const u16* index, int count);
|
||||
|
||||
template <GS_PRIM_CLASS primclass, u32 iip, u32 tme, u32 fst, u32 color>
|
||||
static constexpr GSVertexTrace::FindMinMaxPtr GetFMM(bool provoking_vertex_first);
|
||||
@@ -76,7 +76,7 @@ void GSVertexTraceFMM::Populate(GSVertexTrace& vt, bool provoking_vertex_first)
|
||||
}
|
||||
|
||||
template <GS_PRIM_CLASS primclass, u32 iip, u32 tme, u32 fst, u32 color, bool flat_swapped>
|
||||
void GSVertexTraceFMM::FindMinMax(GSVertexTrace& vt, const void* vertex, const u32* index, int count)
|
||||
void GSVertexTraceFMM::FindMinMax(GSVertexTrace& vt, const void* vertex, const u16* index, int count)
|
||||
{
|
||||
const GSDrawingContext* context = vt.m_state->m_context;
|
||||
|
||||
|
||||
@@ -120,6 +120,79 @@ std::vector<std::string> D3D::GetFullscreenModes(IDXGIFactory5* factory, const s
|
||||
return modes;
|
||||
}
|
||||
|
||||
bool D3D::GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory, const RECT& window_rect, u32 width,
|
||||
u32 height, float refresh_rate, DXGI_FORMAT format, DXGI_MODE_DESC* fullscreen_mode, IDXGIOutput** output)
|
||||
{
|
||||
// We need to find which monitor the window is located on.
|
||||
const GSVector4i client_rc_vec = GSVector4i::load<false>(&window_rect);
|
||||
|
||||
// The window might be on a different adapter to which we are rendering.. so we have to enumerate them all.
|
||||
HRESULT hr;
|
||||
wil::com_ptr_nothrow<IDXGIOutput> first_output, intersecting_output;
|
||||
|
||||
for (u32 adapter_index = 0; !intersecting_output; adapter_index++)
|
||||
{
|
||||
wil::com_ptr_nothrow<IDXGIAdapter1> adapter;
|
||||
hr = factory->EnumAdapters1(adapter_index, adapter.put());
|
||||
if (hr == DXGI_ERROR_NOT_FOUND)
|
||||
break;
|
||||
else if (FAILED(hr))
|
||||
continue;
|
||||
|
||||
for (u32 output_index = 0;; output_index++)
|
||||
{
|
||||
wil::com_ptr_nothrow<IDXGIOutput> this_output;
|
||||
DXGI_OUTPUT_DESC output_desc;
|
||||
hr = adapter->EnumOutputs(output_index, this_output.put());
|
||||
if (hr == DXGI_ERROR_NOT_FOUND)
|
||||
break;
|
||||
else if (FAILED(hr) || FAILED(this_output->GetDesc(&output_desc)))
|
||||
continue;
|
||||
|
||||
const GSVector4i output_rc = GSVector4i::load<false>(&output_desc.DesktopCoordinates);
|
||||
if (!client_rc_vec.rintersect(output_rc).rempty())
|
||||
{
|
||||
intersecting_output = std::move(this_output);
|
||||
break;
|
||||
}
|
||||
|
||||
// Fallback to the first monitor.
|
||||
if (!first_output)
|
||||
first_output = std::move(this_output);
|
||||
}
|
||||
}
|
||||
|
||||
if (!intersecting_output)
|
||||
{
|
||||
if (!first_output)
|
||||
{
|
||||
Console.Error("No DXGI output found. Can't use exclusive fullscreen.");
|
||||
return false;
|
||||
}
|
||||
|
||||
Console.Warning("No DXGI output found for window, using first.");
|
||||
intersecting_output = std::move(first_output);
|
||||
}
|
||||
|
||||
DXGI_MODE_DESC request_mode = {};
|
||||
request_mode.Width = width;
|
||||
request_mode.Height = height;
|
||||
request_mode.Format = format;
|
||||
request_mode.RefreshRate.Numerator = static_cast<UINT>(std::floor(refresh_rate * 1000.0f));
|
||||
request_mode.RefreshRate.Denominator = 1000u;
|
||||
|
||||
if (FAILED(hr = intersecting_output->FindClosestMatchingMode(&request_mode, fullscreen_mode, nullptr)) ||
|
||||
request_mode.Format != format)
|
||||
{
|
||||
Console.Error("Failed to find closest matching mode, hr=%08X", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
*output = intersecting_output.get();
|
||||
intersecting_output->AddRef();
|
||||
return true;
|
||||
}
|
||||
|
||||
wil::com_ptr_nothrow<IDXGIAdapter1> D3D::GetAdapterByName(IDXGIFactory5* factory, const std::string_view& name)
|
||||
{
|
||||
if (name.empty())
|
||||
|
||||
@@ -36,6 +36,10 @@ namespace D3D
|
||||
// returns a list of fullscreen modes for the specified adapter
|
||||
std::vector<std::string> GetFullscreenModes(IDXGIFactory5* factory, const std::string_view& adapter_name);
|
||||
|
||||
// returns the fullscreen mode to use for the specified dimensions
|
||||
bool GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory, const RECT& window_rect, u32 width, u32 height,
|
||||
float refresh_rate, DXGI_FORMAT format, DXGI_MODE_DESC* fullscreen_mode, IDXGIOutput** output);
|
||||
|
||||
// get an adapter based on name
|
||||
wil::com_ptr_nothrow<IDXGIAdapter1> GetAdapterByName(IDXGIFactory5* factory, const std::string_view& name);
|
||||
|
||||
|
||||
@@ -37,6 +37,8 @@
|
||||
|
||||
// #define REPORT_LEAKED_OBJECTS 1
|
||||
|
||||
static constexpr std::array<float, 4> s_present_clear_color = {};
|
||||
|
||||
static bool SupportsTextureFormat(ID3D11Device* dev, DXGI_FORMAT format)
|
||||
{
|
||||
UINT support;
|
||||
@@ -75,9 +77,9 @@ RenderAPI GSDevice11::GetRenderAPI() const
|
||||
return RenderAPI::D3D11;
|
||||
}
|
||||
|
||||
bool GSDevice11::Create(const WindowInfo& wi, VsyncMode vsync)
|
||||
bool GSDevice11::Create()
|
||||
{
|
||||
if (!GSDevice::Create(wi, vsync))
|
||||
if (!GSDevice::Create())
|
||||
return false;
|
||||
|
||||
UINT create_flags = 0;
|
||||
@@ -150,7 +152,7 @@ bool GSDevice11::Create(const WindowInfo& wi, VsyncMode vsync)
|
||||
DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allow_tearing_supported, sizeof(allow_tearing_supported));
|
||||
m_allow_tearing_supported = (SUCCEEDED(hr) && allow_tearing_supported == TRUE);
|
||||
|
||||
if (wi.type != WindowInfo::Type::Surfaceless && !CreateSwapChain(nullptr))
|
||||
if (!AcquireWindow(true) || (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain()))
|
||||
return false;
|
||||
|
||||
D3D11_BUFFER_DESC bd;
|
||||
@@ -490,7 +492,7 @@ bool GSDevice11::Create(const WindowInfo& wi, VsyncMode vsync)
|
||||
void GSDevice11::Destroy()
|
||||
{
|
||||
GSDevice::Destroy();
|
||||
GSDevice11::DestroySurface();
|
||||
DestroySwapChain();
|
||||
DestroyTimestampQueries();
|
||||
|
||||
m_convert = {};
|
||||
@@ -561,7 +563,7 @@ bool GSDevice11::HasSurface() const
|
||||
|
||||
bool GSDevice11::GetHostRefreshRate(float* refresh_rate)
|
||||
{
|
||||
if (m_swap_chain && IsExclusiveFullscreen())
|
||||
if (m_swap_chain && m_is_exclusive_fullscreen)
|
||||
{
|
||||
DXGI_SWAP_CHAIN_DESC desc;
|
||||
if (SUCCEEDED(m_swap_chain->GetDesc(&desc)) && desc.BufferDesc.RefreshRate.Numerator > 0 &&
|
||||
@@ -583,50 +585,84 @@ void GSDevice11::SetVSync(VsyncMode mode)
|
||||
m_vsync_mode = mode;
|
||||
}
|
||||
|
||||
bool GSDevice11::CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode)
|
||||
bool GSDevice11::CreateSwapChain()
|
||||
{
|
||||
constexpr DXGI_FORMAT swap_chain_format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
|
||||
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_MODE_DESC fullscreen_mode;
|
||||
wil::com_ptr_nothrow<IDXGIOutput> fullscreen_output;
|
||||
if (Host::IsFullscreen())
|
||||
{
|
||||
u32 fullscreen_width, fullscreen_height;
|
||||
float fullscreen_refresh_rate;
|
||||
m_is_exclusive_fullscreen =
|
||||
GetRequestedExclusiveFullscreenMode(&fullscreen_width, &fullscreen_height, &fullscreen_refresh_rate) &&
|
||||
D3D::GetRequestedExclusiveFullscreenModeDesc(m_dxgi_factory.get(), client_rc, fullscreen_width,
|
||||
fullscreen_height, fullscreen_refresh_rate, swap_chain_format, &fullscreen_mode,
|
||||
fullscreen_output.put());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_is_exclusive_fullscreen = false;
|
||||
}
|
||||
|
||||
m_using_flip_model_swap_chain = !EmuConfig.GS.UseBlitSwapChain || m_is_exclusive_fullscreen;
|
||||
|
||||
DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {};
|
||||
swap_chain_desc.Width = width;
|
||||
swap_chain_desc.Height = height;
|
||||
swap_chain_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
swap_chain_desc.Width = static_cast<u32>(client_rc.right - client_rc.left);
|
||||
swap_chain_desc.Height = static_cast<u32>(client_rc.bottom - client_rc.top);
|
||||
swap_chain_desc.Format = swap_chain_format;
|
||||
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);
|
||||
m_using_allow_tearing = (m_allow_tearing_supported && m_using_flip_model_swap_chain && !m_is_exclusive_fullscreen);
|
||||
if (m_using_allow_tearing)
|
||||
swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
|
||||
|
||||
DXGI_SWAP_CHAIN_FULLSCREEN_DESC fs_desc = {};
|
||||
if (fullscreen_mode)
|
||||
HRESULT hr = S_OK;
|
||||
|
||||
if (m_is_exclusive_fullscreen)
|
||||
{
|
||||
swap_chain_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
|
||||
swap_chain_desc.Width = fullscreen_mode->Width;
|
||||
swap_chain_desc.Height = fullscreen_mode->Height;
|
||||
fs_desc.RefreshRate = fullscreen_mode->RefreshRate;
|
||||
fs_desc.ScanlineOrdering = fullscreen_mode->ScanlineOrdering;
|
||||
fs_desc.Scaling = fullscreen_mode->Scaling;
|
||||
DXGI_SWAP_CHAIN_DESC1 fs_sd_desc = swap_chain_desc;
|
||||
DXGI_SWAP_CHAIN_FULLSCREEN_DESC fs_desc = {};
|
||||
|
||||
fs_sd_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
|
||||
fs_sd_desc.Width = fullscreen_mode.Width;
|
||||
fs_sd_desc.Height = fullscreen_mode.Height;
|
||||
fs_desc.RefreshRate = fullscreen_mode.RefreshRate;
|
||||
fs_desc.ScanlineOrdering = fullscreen_mode.ScanlineOrdering;
|
||||
fs_desc.Scaling = fullscreen_mode.Scaling;
|
||||
fs_desc.Windowed = FALSE;
|
||||
|
||||
Console.WriteLn("Creating a %dx%d exclusive fullscreen swap chain", fs_sd_desc.Width, fs_sd_desc.Height);
|
||||
hr = m_dxgi_factory->CreateSwapChainForHwnd(
|
||||
m_dev.get(), window_hwnd, &fs_sd_desc, &fs_desc, fullscreen_output.get(), m_swap_chain.put());
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Console.Warning("Failed to create fullscreen swap chain, trying windowed.");
|
||||
m_is_exclusive_fullscreen = false;
|
||||
m_using_allow_tearing = m_allow_tearing_supported && m_using_flip_model_swap_chain;
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLn("Creating a %dx%d %s %s swap chain", swap_chain_desc.Width, swap_chain_desc.Height,
|
||||
m_using_flip_model_swap_chain ? "flip-discard" : "discard", fullscreen_mode ? "full-screen" : "windowed");
|
||||
if (!m_is_exclusive_fullscreen)
|
||||
{
|
||||
Console.WriteLn("Creating a %dx%d %s windowed swap chain", swap_chain_desc.Width, swap_chain_desc.Height,
|
||||
m_using_flip_model_swap_chain ? "flip-discard" : "discard");
|
||||
hr = m_dxgi_factory->CreateSwapChainForHwnd(
|
||||
m_dev.get(), window_hwnd, &swap_chain_desc, nullptr, nullptr, m_swap_chain.put());
|
||||
}
|
||||
|
||||
HRESULT hr = m_dxgi_factory->CreateSwapChainForHwnd(m_dev.get(), window_hwnd, &swap_chain_desc,
|
||||
fullscreen_mode ? &fs_desc : nullptr, nullptr, m_swap_chain.put());
|
||||
if (FAILED(hr) && m_using_flip_model_swap_chain)
|
||||
{
|
||||
Console.Warning("Failed to create a flip-discard swap chain, trying discard.");
|
||||
@@ -635,8 +671,8 @@ bool GSDevice11::CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode)
|
||||
m_using_flip_model_swap_chain = false;
|
||||
m_using_allow_tearing = false;
|
||||
|
||||
hr = m_dxgi_factory->CreateSwapChainForHwnd(m_dev.get(), window_hwnd, &swap_chain_desc,
|
||||
fullscreen_mode ? &fs_desc : nullptr, nullptr, m_swap_chain.put());
|
||||
hr = m_dxgi_factory->CreateSwapChainForHwnd(
|
||||
m_dev.get(), window_hwnd, &swap_chain_desc, nullptr, nullptr, m_swap_chain.put());
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Console.Error("CreateSwapChainForHwnd failed: 0x%08X", hr);
|
||||
@@ -648,7 +684,16 @@ bool GSDevice11::CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode)
|
||||
if (FAILED(hr))
|
||||
Console.Warning("MakeWindowAssociation() to disable ALT+ENTER failed");
|
||||
|
||||
return CreateSwapChainRTV();
|
||||
if (!CreateSwapChainRTV())
|
||||
{
|
||||
DestroySwapChain();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Render a frame as soon as possible to clear out whatever was previously being displayed.
|
||||
m_ctx->ClearRenderTargetView(m_swap_chain_rtv.get(), s_present_clear_color.data());
|
||||
m_swap_chain->Present(0, m_using_allow_tearing ? DXGI_PRESENT_ALLOW_TEARING : 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GSDevice11::CreateSwapChainRTV()
|
||||
@@ -670,6 +715,7 @@ bool GSDevice11::CreateSwapChainRTV()
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Console.Error("CreateRenderTargetView for swap chain failed: 0x%08X", hr);
|
||||
m_swap_chain_rtv.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -696,25 +742,41 @@ bool GSDevice11::CreateSwapChainRTV()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GSDevice11::ChangeWindow(const WindowInfo& new_wi)
|
||||
void GSDevice11::DestroySwapChain()
|
||||
{
|
||||
DestroySurface();
|
||||
if (!m_swap_chain)
|
||||
return;
|
||||
|
||||
m_window_info = new_wi;
|
||||
if (new_wi.type == WindowInfo::Type::Surfaceless)
|
||||
return true;
|
||||
m_swap_chain_rtv.reset();
|
||||
|
||||
return CreateSwapChain(nullptr);
|
||||
// switch out of fullscreen before destroying
|
||||
BOOL is_fullscreen;
|
||||
if (SUCCEEDED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) && is_fullscreen)
|
||||
m_swap_chain->SetFullscreenState(FALSE, nullptr);
|
||||
|
||||
m_swap_chain.reset();
|
||||
m_is_exclusive_fullscreen = false;
|
||||
}
|
||||
|
||||
bool GSDevice11::UpdateWindow()
|
||||
{
|
||||
DestroySwapChain();
|
||||
|
||||
if (!AcquireWindow(false))
|
||||
return false;
|
||||
|
||||
if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain())
|
||||
{
|
||||
Console.WriteLn("Failed to create swap chain on updated window");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GSDevice11::DestroySurface()
|
||||
{
|
||||
m_swap_chain_rtv.reset();
|
||||
|
||||
if (IsExclusiveFullscreen())
|
||||
SetExclusiveFullscreen(false, 0, 0, 0.0f);
|
||||
|
||||
m_swap_chain.reset();
|
||||
DestroySwapChain();
|
||||
}
|
||||
|
||||
std::string GSDevice11::GetDriverInfo() const
|
||||
@@ -768,7 +830,7 @@ std::string GSDevice11::GetDriverInfo() const
|
||||
|
||||
void GSDevice11::ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale)
|
||||
{
|
||||
if (!m_swap_chain)
|
||||
if (!m_swap_chain || m_is_exclusive_fullscreen)
|
||||
return;
|
||||
|
||||
m_window_info.surface_scale = new_window_scale;
|
||||
@@ -792,79 +854,21 @@ bool GSDevice11::SupportsExclusiveFullscreen() const
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GSDevice11::IsExclusiveFullscreen()
|
||||
{
|
||||
BOOL is_fullscreen = FALSE;
|
||||
return (m_swap_chain && SUCCEEDED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) && is_fullscreen);
|
||||
}
|
||||
|
||||
bool GSDevice11::SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate)
|
||||
{
|
||||
if (!m_swap_chain)
|
||||
return false;
|
||||
|
||||
BOOL is_fullscreen = FALSE;
|
||||
HRESULT hr = m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr);
|
||||
if (!fullscreen)
|
||||
{
|
||||
// leaving fullscreen
|
||||
if (is_fullscreen)
|
||||
return SUCCEEDED(m_swap_chain->SetFullscreenState(FALSE, nullptr));
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
IDXGIOutput* output;
|
||||
if (FAILED(hr = m_swap_chain->GetContainingOutput(&output)))
|
||||
return false;
|
||||
|
||||
DXGI_SWAP_CHAIN_DESC current_desc;
|
||||
hr = m_swap_chain->GetDesc(¤t_desc);
|
||||
if (FAILED(hr))
|
||||
return false;
|
||||
|
||||
DXGI_MODE_DESC new_mode = current_desc.BufferDesc;
|
||||
new_mode.Width = width;
|
||||
new_mode.Height = height;
|
||||
new_mode.RefreshRate.Numerator = static_cast<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;
|
||||
}
|
||||
|
||||
GSDevice::PresentResult GSDevice11::BeginPresent(bool frame_skip)
|
||||
{
|
||||
if (frame_skip || !m_swap_chain)
|
||||
return PresentResult::FrameSkipped;
|
||||
|
||||
// Check if we lost exclusive fullscreen. If so, notify the host, so it can switch to windowed mode.
|
||||
// This might get called repeatedly if it takes a while to switch back, that's the host's problem.
|
||||
BOOL is_fullscreen;
|
||||
if (m_is_exclusive_fullscreen &&
|
||||
(FAILED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) || !is_fullscreen))
|
||||
{
|
||||
Host::RunOnCPUThread([]() { Host::SetFullscreen(false); });
|
||||
return PresentResult::FrameSkipped;
|
||||
}
|
||||
|
||||
// When using vsync, the time here seems to include the time for the buffer to become available.
|
||||
// This blows our our GPU usage number considerably, so read the timestamp before the final blit
|
||||
// in this configuration. It does reduce accuracy a little, but better than seeing 100% all of
|
||||
@@ -872,8 +876,7 @@ GSDevice::PresentResult GSDevice11::BeginPresent(bool frame_skip)
|
||||
if (m_vsync_mode != VsyncMode::Off && m_gpu_timing_enabled)
|
||||
PopTimestampQuery();
|
||||
|
||||
static constexpr std::array<float, 4> clear_color = {};
|
||||
m_ctx->ClearRenderTargetView(m_swap_chain_rtv.get(), clear_color.data());
|
||||
m_ctx->ClearRenderTargetView(m_swap_chain_rtv.get(), s_present_clear_color.data());
|
||||
m_ctx->OMSetRenderTargets(1, m_swap_chain_rtv.addressof(), nullptr);
|
||||
if (m_state.rt_view)
|
||||
m_state.rt_view->Release();
|
||||
@@ -883,7 +886,9 @@ GSDevice::PresentResult GSDevice11::BeginPresent(bool frame_skip)
|
||||
{
|
||||
m_state.dsv->Release();
|
||||
m_state.dsv = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
g_perfmon.Put(GSPerfMon::RenderPasses, 1);
|
||||
|
||||
const GSVector2i size = GetWindowSize();
|
||||
SetViewport(size);
|
||||
@@ -1440,7 +1445,7 @@ void GSDevice11::DoMultiStretchRects(const MultiStretchRect* rects, u32 num_rect
|
||||
const u32 vertex_reserve_size = num_rects * 4;
|
||||
const u32 index_reserve_size = num_rects * 6;
|
||||
GSVertexPT1* verts = static_cast<GSVertexPT1*>(IAMapVertexBuffer(sizeof(GSVertexPT1), vertex_reserve_size));
|
||||
u32* idx = IAMapIndexBuffer(index_reserve_size);
|
||||
u16* idx = IAMapIndexBuffer(index_reserve_size);
|
||||
u32 icount = 0;
|
||||
u32 vcount = 0;
|
||||
for (u32 i = 0; i < num_rects; i++)
|
||||
@@ -1712,7 +1717,6 @@ void GSDevice11::RenderImGui()
|
||||
const UINT vb_stride = sizeof(ImDrawVert);
|
||||
const UINT vb_offset = 0;
|
||||
m_ctx->IASetVertexBuffers(0, 1, m_vb.addressof(), &vb_stride, &vb_offset);
|
||||
m_ctx->IASetIndexBuffer(m_ib.get(), DXGI_FORMAT_R16_UINT, 0);
|
||||
IASetInputLayout(m_imgui.il.get());
|
||||
IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
||||
VSSetShader(m_imgui.vs.get(), m_imgui.vs_cb.get());
|
||||
@@ -1756,16 +1760,8 @@ void GSDevice11::RenderImGui()
|
||||
m_ctx->Unmap(m_vb.get(), 0);
|
||||
}
|
||||
|
||||
// Bit awkward, because this is using 16-bit indices, not 32-bit.
|
||||
static_assert(sizeof(ImDrawIdx) == sizeof(u16));
|
||||
const u32 index_count = static_cast<u32>(cmd_list->IdxBuffer.Size + 1) / 2;
|
||||
u32* index_map = IAMapIndexBuffer(index_count);
|
||||
if (!index_map)
|
||||
continue;
|
||||
|
||||
const u32 index_start = m_index.start * 2;
|
||||
std::memcpy(index_map, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx));
|
||||
IAUnmapIndexBuffer(index_count);
|
||||
IASetIndexBuffer(cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size);
|
||||
|
||||
for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
|
||||
{
|
||||
@@ -1787,14 +1783,13 @@ void GSDevice11::RenderImGui()
|
||||
m_state.ps_sr_views[0] = static_cast<ID3D11ShaderResourceView*>(pcmd->GetTexID());
|
||||
PSUpdateShaderState();
|
||||
|
||||
m_ctx->DrawIndexed(pcmd->ElemCount, index_start + pcmd->IdxOffset, vertex_offset + pcmd->VtxOffset);
|
||||
m_ctx->DrawIndexed(pcmd->ElemCount, m_index.start + pcmd->IdxOffset, vertex_offset + pcmd->VtxOffset);
|
||||
}
|
||||
|
||||
g_perfmon.Put(GSPerfMon::DrawCalls, cmd_list->CmdBuffer.Size);
|
||||
}
|
||||
|
||||
m_ctx->IASetVertexBuffers(0, 1, m_vb.addressof(), &m_state.vb_stride, &vb_offset);
|
||||
m_ctx->IASetIndexBuffer(m_state.index_buffer, DXGI_FORMAT_R32_UINT, 0);
|
||||
}
|
||||
|
||||
void GSDevice11::SetupDATE(GSTexture* rt, GSTexture* ds, const GSVertexPT1* vertices, bool datm)
|
||||
@@ -1912,9 +1907,9 @@ bool GSDevice11::IASetExpandVertexBuffer(const void* vertex, u32 stride, u32 cou
|
||||
return true;
|
||||
}
|
||||
|
||||
u32* GSDevice11::IAMapIndexBuffer(u32 count)
|
||||
u16* GSDevice11::IAMapIndexBuffer(u32 count)
|
||||
{
|
||||
if (count > (INDEX_BUFFER_SIZE / sizeof(u32)))
|
||||
if (count > (INDEX_BUFFER_SIZE / sizeof(u16)))
|
||||
return nullptr;
|
||||
|
||||
D3D11_MAP type = D3D11_MAP_WRITE_NO_OVERWRITE;
|
||||
@@ -1922,7 +1917,7 @@ u32* GSDevice11::IAMapIndexBuffer(u32 count)
|
||||
m_index.start = m_ib_pos;
|
||||
m_ib_pos += count;
|
||||
|
||||
if (m_ib_pos > (INDEX_BUFFER_SIZE / sizeof(u32)))
|
||||
if (m_ib_pos > (INDEX_BUFFER_SIZE / sizeof(u16)))
|
||||
{
|
||||
m_index.start = 0;
|
||||
m_ib_pos = count;
|
||||
@@ -1933,7 +1928,7 @@ u32* GSDevice11::IAMapIndexBuffer(u32 count)
|
||||
if (FAILED(m_ctx->Map(m_ib.get(), 0, type, 0, &m)))
|
||||
return nullptr;
|
||||
|
||||
return static_cast<u32*>(m.pData) + m_index.start;
|
||||
return static_cast<u16*>(m.pData) + m_index.start;
|
||||
}
|
||||
|
||||
void GSDevice11::IAUnmapIndexBuffer(u32 count)
|
||||
@@ -1944,11 +1939,11 @@ void GSDevice11::IAUnmapIndexBuffer(u32 count)
|
||||
|
||||
bool GSDevice11::IASetIndexBuffer(const void* index, u32 count)
|
||||
{
|
||||
u32* map = IAMapIndexBuffer(count);
|
||||
u16* map = IAMapIndexBuffer(count);
|
||||
if (!map)
|
||||
return false;
|
||||
|
||||
std::memcpy(map, index, count * sizeof(u32));
|
||||
std::memcpy(map, index, count * sizeof(u16));
|
||||
IAUnmapIndexBuffer(count);
|
||||
IASetIndexBuffer(m_ib.get());
|
||||
return true;
|
||||
@@ -1958,7 +1953,7 @@ void GSDevice11::IASetIndexBuffer(ID3D11Buffer* buffer)
|
||||
{
|
||||
if (m_state.index_buffer != buffer)
|
||||
{
|
||||
m_ctx->IASetIndexBuffer(buffer, DXGI_FORMAT_R32_UINT, 0);
|
||||
m_ctx->IASetIndexBuffer(buffer, DXGI_FORMAT_R16_UINT, 0);
|
||||
m_state.index_buffer = buffer;
|
||||
}
|
||||
}
|
||||
@@ -2073,6 +2068,8 @@ void GSDevice11::OMSetRenderTargets(GSTexture* rt, GSTexture* ds, const GSVector
|
||||
if (ds) dsv = *(GSTexture11*)ds;
|
||||
|
||||
const bool changed = (m_state.rt_view != rtv || m_state.dsv != dsv);
|
||||
g_perfmon.Put(GSPerfMon::RenderPasses, static_cast<double>(changed));
|
||||
|
||||
if (m_state.rt_view != rtv)
|
||||
{
|
||||
if (m_state.rt_view)
|
||||
|
||||
@@ -120,8 +120,9 @@ private:
|
||||
|
||||
void SetFeatures();
|
||||
|
||||
bool CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode);
|
||||
bool CreateSwapChain();
|
||||
bool CreateSwapChainRTV();
|
||||
void DestroySwapChain();
|
||||
|
||||
bool CreateTimestampQueries();
|
||||
void DestroyTimestampQueries();
|
||||
@@ -160,6 +161,7 @@ private:
|
||||
bool m_allow_tearing_supported = false;
|
||||
bool m_using_flip_model_swap_chain = true;
|
||||
bool m_using_allow_tearing = false;
|
||||
bool m_is_exclusive_fullscreen = false;
|
||||
|
||||
struct
|
||||
{
|
||||
@@ -281,16 +283,14 @@ public:
|
||||
__fi ID3D11Device1* GetD3DDevice() const { return m_dev.get(); }
|
||||
__fi ID3D11DeviceContext1* GetD3DContext() const { return m_ctx.get(); }
|
||||
|
||||
bool Create(const WindowInfo& wi, VsyncMode vsync) override;
|
||||
bool Create() override;
|
||||
void Destroy() override;
|
||||
|
||||
RenderAPI GetRenderAPI() const override;
|
||||
|
||||
bool ChangeWindow(const WindowInfo& new_wi) override;
|
||||
bool UpdateWindow() override;
|
||||
void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override;
|
||||
bool SupportsExclusiveFullscreen() const override;
|
||||
bool IsExclusiveFullscreen() override;
|
||||
bool SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override;
|
||||
bool HasSurface() const override;
|
||||
void DestroySurface() override;
|
||||
std::string GetDriverInfo() const override;
|
||||
@@ -343,7 +343,7 @@ public:
|
||||
bool IASetVertexBuffer(const void* vertex, u32 stride, u32 count);
|
||||
bool IASetExpandVertexBuffer(const void* vertex, u32 stride, u32 count);
|
||||
|
||||
u32* IAMapIndexBuffer(u32 count);
|
||||
u16* IAMapIndexBuffer(u32 count);
|
||||
void IAUnmapIndexBuffer(u32 count);
|
||||
bool IASetIndexBuffer(const void* index, u32 count);
|
||||
void IASetIndexBuffer(ID3D11Buffer* buffer);
|
||||
|
||||
@@ -44,6 +44,8 @@ static bool IsDATEModePrimIDInit(u32 flag) { return flag == 1 || flag == 2; }
|
||||
static constexpr std::array<D3D12_PRIMITIVE_TOPOLOGY, 3> s_primitive_topology_mapping =
|
||||
{{D3D_PRIMITIVE_TOPOLOGY_POINTLIST, D3D_PRIMITIVE_TOPOLOGY_LINELIST, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST}};
|
||||
|
||||
static constexpr std::array<float, 4> s_present_clear_color = {};
|
||||
|
||||
static D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE GetLoadOpForTexture(GSTexture12* tex)
|
||||
{
|
||||
if (!tex)
|
||||
@@ -116,9 +118,9 @@ bool GSDevice12::HasSurface() const
|
||||
return static_cast<bool>(m_swap_chain);
|
||||
}
|
||||
|
||||
bool GSDevice12::Create(const WindowInfo& wi, VsyncMode vsync)
|
||||
bool GSDevice12::Create()
|
||||
{
|
||||
if (!GSDevice::Create(wi, vsync))
|
||||
if (!GSDevice::Create())
|
||||
return false;
|
||||
|
||||
m_dxgi_factory = D3D::CreateFactory(EmuConfig.GS.UseDebugDevice);
|
||||
@@ -144,7 +146,7 @@ bool GSDevice12::Create(const WindowInfo& wi, VsyncMode vsync)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain(nullptr))
|
||||
if (!AcquireWindow(true) || (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain()))
|
||||
return false;
|
||||
|
||||
{
|
||||
@@ -215,16 +217,14 @@ void GSDevice12::Destroy()
|
||||
EndRenderPass();
|
||||
ExecuteCommandList(true);
|
||||
DestroyResources();
|
||||
|
||||
g_d3d12_context->WaitForGPUIdle();
|
||||
GSDevice12::DestroySurface();
|
||||
DestroySwapChain();
|
||||
g_d3d12_context->Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
bool GSDevice12::GetHostRefreshRate(float* refresh_rate)
|
||||
{
|
||||
if (m_swap_chain && IsExclusiveFullscreen())
|
||||
if (m_swap_chain && m_is_exclusive_fullscreen)
|
||||
{
|
||||
DXGI_SWAP_CHAIN_DESC desc;
|
||||
if (SUCCEEDED(m_swap_chain->GetDesc(&desc)) && desc.BufferDesc.RefreshRate.Numerator > 0 &&
|
||||
@@ -246,59 +246,101 @@ void GSDevice12::SetVSync(VsyncMode mode)
|
||||
m_vsync_mode = mode;
|
||||
}
|
||||
|
||||
|
||||
bool GSDevice12::CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode)
|
||||
bool GSDevice12::CreateSwapChain()
|
||||
{
|
||||
constexpr DXGI_FORMAT swap_chain_format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
|
||||
if (m_window_info.type != WindowInfo::Type::Win32)
|
||||
return false;
|
||||
|
||||
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_MODE_DESC fullscreen_mode;
|
||||
wil::com_ptr_nothrow<IDXGIOutput> fullscreen_output;
|
||||
if (Host::IsFullscreen())
|
||||
{
|
||||
u32 fullscreen_width, fullscreen_height;
|
||||
float fullscreen_refresh_rate;
|
||||
m_is_exclusive_fullscreen =
|
||||
GetRequestedExclusiveFullscreenMode(&fullscreen_width, &fullscreen_height, &fullscreen_refresh_rate) &&
|
||||
D3D::GetRequestedExclusiveFullscreenModeDesc(m_dxgi_factory.get(), client_rc, fullscreen_width,
|
||||
fullscreen_height, fullscreen_refresh_rate, swap_chain_format, &fullscreen_mode,
|
||||
fullscreen_output.put());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_is_exclusive_fullscreen = false;
|
||||
}
|
||||
|
||||
DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {};
|
||||
swap_chain_desc.Width = width;
|
||||
swap_chain_desc.Height = height;
|
||||
swap_chain_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
swap_chain_desc.Width = static_cast<u32>(client_rc.right - client_rc.left);
|
||||
swap_chain_desc.Height = static_cast<u32>(client_rc.bottom - client_rc.top);
|
||||
swap_chain_desc.Format = swap_chain_format;
|
||||
swap_chain_desc.SampleDesc.Count = 1;
|
||||
swap_chain_desc.BufferCount = 3;
|
||||
swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
||||
swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
|
||||
|
||||
m_using_allow_tearing = (m_allow_tearing_supported && !fullscreen_mode);
|
||||
m_using_allow_tearing = (m_allow_tearing_supported && !m_is_exclusive_fullscreen);
|
||||
if (m_using_allow_tearing)
|
||||
swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
|
||||
|
||||
DXGI_SWAP_CHAIN_FULLSCREEN_DESC fs_desc = {};
|
||||
if (fullscreen_mode)
|
||||
HRESULT hr = S_OK;
|
||||
|
||||
if (m_is_exclusive_fullscreen)
|
||||
{
|
||||
swap_chain_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
|
||||
swap_chain_desc.Width = fullscreen_mode->Width;
|
||||
swap_chain_desc.Height = fullscreen_mode->Height;
|
||||
fs_desc.RefreshRate = fullscreen_mode->RefreshRate;
|
||||
fs_desc.ScanlineOrdering = fullscreen_mode->ScanlineOrdering;
|
||||
fs_desc.Scaling = fullscreen_mode->Scaling;
|
||||
DXGI_SWAP_CHAIN_DESC1 fs_sd_desc = swap_chain_desc;
|
||||
DXGI_SWAP_CHAIN_FULLSCREEN_DESC fs_desc = {};
|
||||
|
||||
fs_sd_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
|
||||
fs_sd_desc.Width = fullscreen_mode.Width;
|
||||
fs_sd_desc.Height = fullscreen_mode.Height;
|
||||
fs_desc.RefreshRate = fullscreen_mode.RefreshRate;
|
||||
fs_desc.ScanlineOrdering = fullscreen_mode.ScanlineOrdering;
|
||||
fs_desc.Scaling = fullscreen_mode.Scaling;
|
||||
fs_desc.Windowed = FALSE;
|
||||
|
||||
Console.WriteLn("Creating a %dx%d exclusive fullscreen swap chain", fs_sd_desc.Width, fs_sd_desc.Height);
|
||||
hr = m_dxgi_factory->CreateSwapChainForHwnd(g_d3d12_context->GetCommandQueue(), window_hwnd, &fs_sd_desc,
|
||||
&fs_desc, fullscreen_output.get(), m_swap_chain.put());
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Console.Warning("Failed to create fullscreen swap chain, trying windowed.");
|
||||
m_is_exclusive_fullscreen = false;
|
||||
m_using_allow_tearing = m_allow_tearing_supported;
|
||||
}
|
||||
}
|
||||
|
||||
DevCon.WriteLn("Creating a %dx%d %s swap chain", swap_chain_desc.Width, swap_chain_desc.Height,
|
||||
fullscreen_mode ? "full-screen" : "windowed");
|
||||
|
||||
HRESULT hr = m_dxgi_factory->CreateSwapChainForHwnd(g_d3d12_context->GetCommandQueue(), window_hwnd,
|
||||
&swap_chain_desc, fullscreen_mode ? &fs_desc : nullptr, nullptr, m_swap_chain.put());
|
||||
if (FAILED(hr))
|
||||
if (!m_is_exclusive_fullscreen)
|
||||
{
|
||||
Console.Error("CreateSwapChainForHwnd failed: 0x%08X", hr);
|
||||
return false;
|
||||
Console.WriteLn("Creating a %dx%d windowed swap chain", swap_chain_desc.Width, swap_chain_desc.Height);
|
||||
hr = m_dxgi_factory->CreateSwapChainForHwnd(
|
||||
g_d3d12_context->GetCommandQueue(), window_hwnd, &swap_chain_desc, nullptr, nullptr, m_swap_chain.put());
|
||||
}
|
||||
|
||||
hr = m_dxgi_factory->MakeWindowAssociation(window_hwnd, DXGI_MWA_NO_WINDOW_CHANGES);
|
||||
if (FAILED(hr))
|
||||
Console.Warning("MakeWindowAssociation() to disable ALT+ENTER failed");
|
||||
|
||||
return CreateSwapChainRTV();
|
||||
if (!CreateSwapChainRTV())
|
||||
{
|
||||
DestroySwapChain();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Render a frame as soon as possible to clear out whatever was previously being displayed.
|
||||
EndRenderPass();
|
||||
D3D12::Texture& swap_chain_buf = m_swap_chain_buffers[m_current_swap_chain_buffer];
|
||||
ID3D12GraphicsCommandList4* cmdlist = g_d3d12_context->GetCommandList();
|
||||
m_current_swap_chain_buffer = ((m_current_swap_chain_buffer + 1) % static_cast<u32>(m_swap_chain_buffers.size()));
|
||||
swap_chain_buf.TransitionToState(cmdlist, D3D12_RESOURCE_STATE_RENDER_TARGET);
|
||||
cmdlist->ClearRenderTargetView(swap_chain_buf.GetWriteDescriptor(), s_present_clear_color.data(), 0, nullptr);
|
||||
swap_chain_buf.TransitionToState(cmdlist, D3D12_RESOURCE_STATE_PRESENT);
|
||||
ExecuteCommandList(false);
|
||||
m_swap_chain->Present(0, m_using_allow_tearing ? DXGI_PRESENT_ALLOW_TEARING : 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GSDevice12::CreateSwapChainRTV()
|
||||
@@ -315,6 +357,7 @@ bool GSDevice12::CreateSwapChainRTV()
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Console.Error("GetBuffer for RTV failed: 0x%08X", hr);
|
||||
m_swap_chain_buffers.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -322,6 +365,7 @@ bool GSDevice12::CreateSwapChainRTV()
|
||||
if (!tex.Adopt(std::move(backbuffer), DXGI_FORMAT_UNKNOWN, swap_chain_desc.BufferDesc.Format,
|
||||
DXGI_FORMAT_UNKNOWN, D3D12_RESOURCE_STATE_PRESENT))
|
||||
{
|
||||
m_swap_chain_buffers.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -360,26 +404,43 @@ void GSDevice12::DestroySwapChainRTVs()
|
||||
m_current_swap_chain_buffer = 0;
|
||||
}
|
||||
|
||||
bool GSDevice12::ChangeWindow(const WindowInfo& new_wi)
|
||||
void GSDevice12::DestroySwapChain()
|
||||
{
|
||||
DestroySurface();
|
||||
if (!m_swap_chain)
|
||||
return;
|
||||
|
||||
m_window_info = new_wi;
|
||||
if (new_wi.type == WindowInfo::Type::Surfaceless)
|
||||
return true;
|
||||
DestroySwapChainRTVs();
|
||||
|
||||
return CreateSwapChain(nullptr);
|
||||
// switch out of fullscreen before destroying
|
||||
BOOL is_fullscreen;
|
||||
if (SUCCEEDED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) && is_fullscreen)
|
||||
m_swap_chain->SetFullscreenState(FALSE, nullptr);
|
||||
|
||||
m_swap_chain.reset();
|
||||
m_is_exclusive_fullscreen = false;
|
||||
}
|
||||
|
||||
bool GSDevice12::UpdateWindow()
|
||||
{
|
||||
ExecuteCommandList(true);
|
||||
DestroySwapChain();
|
||||
|
||||
if (!AcquireWindow(false))
|
||||
return false;
|
||||
|
||||
if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain())
|
||||
{
|
||||
Console.WriteLn("Failed to create swap chain on updated window");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GSDevice12::DestroySurface()
|
||||
{
|
||||
ExecuteCommandList(true);
|
||||
|
||||
if (IsExclusiveFullscreen())
|
||||
SetExclusiveFullscreen(false, 0, 0, 0.0f);
|
||||
|
||||
DestroySwapChainRTVs();
|
||||
m_swap_chain.reset();
|
||||
DestroySwapChain();
|
||||
}
|
||||
|
||||
std::string GSDevice12::GetDriverInfo() const
|
||||
@@ -452,75 +513,6 @@ bool GSDevice12::SupportsExclusiveFullscreen() const
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GSDevice12::IsExclusiveFullscreen()
|
||||
{
|
||||
BOOL is_fullscreen = FALSE;
|
||||
return (m_swap_chain && SUCCEEDED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) && is_fullscreen);
|
||||
}
|
||||
|
||||
bool GSDevice12::SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate)
|
||||
{
|
||||
if (!m_swap_chain)
|
||||
return false;
|
||||
|
||||
BOOL is_fullscreen = FALSE;
|
||||
HRESULT hr = m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr);
|
||||
if (!fullscreen)
|
||||
{
|
||||
// leaving fullscreen
|
||||
if (is_fullscreen)
|
||||
return SUCCEEDED(m_swap_chain->SetFullscreenState(FALSE, nullptr));
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
IDXGIOutput* output;
|
||||
if (FAILED(hr = m_swap_chain->GetContainingOutput(&output)))
|
||||
return false;
|
||||
|
||||
DXGI_SWAP_CHAIN_DESC current_desc;
|
||||
hr = m_swap_chain->GetDesc(¤t_desc);
|
||||
if (FAILED(hr))
|
||||
return false;
|
||||
|
||||
DXGI_MODE_DESC new_mode = current_desc.BufferDesc;
|
||||
new_mode.Width = width;
|
||||
new_mode.Height = height;
|
||||
new_mode.RefreshRate.Numerator = static_cast<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.Width &&
|
||||
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;
|
||||
}
|
||||
|
||||
g_d3d12_context->ExecuteCommandList(D3D12::Context::WaitType::Sleep);
|
||||
DestroySwapChainRTVs();
|
||||
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;
|
||||
}
|
||||
|
||||
GSDevice::PresentResult GSDevice12::BeginPresent(bool frame_skip)
|
||||
{
|
||||
EndRenderPass();
|
||||
@@ -531,13 +523,23 @@ GSDevice::PresentResult GSDevice12::BeginPresent(bool frame_skip)
|
||||
if (frame_skip || !m_swap_chain)
|
||||
return PresentResult::FrameSkipped;
|
||||
|
||||
static constexpr std::array<float, 4> clear_color = {};
|
||||
// Check if we lost exclusive fullscreen. If so, notify the host, so it can switch to windowed mode.
|
||||
// This might get called repeatedly if it takes a while to switch back, that's the host's problem.
|
||||
BOOL is_fullscreen;
|
||||
if (m_is_exclusive_fullscreen &&
|
||||
(FAILED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) || !is_fullscreen))
|
||||
{
|
||||
Host::RunOnCPUThread([]() { Host::SetFullscreen(false); });
|
||||
return PresentResult::FrameSkipped;
|
||||
}
|
||||
|
||||
D3D12::Texture& swap_chain_buf = m_swap_chain_buffers[m_current_swap_chain_buffer];
|
||||
|
||||
ID3D12GraphicsCommandList* cmdlist = g_d3d12_context->GetCommandList();
|
||||
swap_chain_buf.TransitionToState(cmdlist, D3D12_RESOURCE_STATE_RENDER_TARGET);
|
||||
cmdlist->ClearRenderTargetView(swap_chain_buf.GetWriteDescriptor(), clear_color.data(), 0, nullptr);
|
||||
cmdlist->ClearRenderTargetView(swap_chain_buf.GetWriteDescriptor(), s_present_clear_color.data(), 0, nullptr);
|
||||
cmdlist->OMSetRenderTargets(1, &swap_chain_buf.GetWriteDescriptor().cpu_handle, FALSE, nullptr);
|
||||
g_perfmon.Put(GSPerfMon::RenderPasses, 1);
|
||||
|
||||
const D3D12_VIEWPORT vp{0.0f, 0.0f, static_cast<float>(m_window_info.surface_width),
|
||||
static_cast<float>(m_window_info.surface_height), 0.0f, 1.0f};
|
||||
@@ -949,13 +951,13 @@ void GSDevice12::DoMultiStretchRects(
|
||||
{
|
||||
// Set up vertices first.
|
||||
const u32 vertex_reserve_size = num_rects * 4 * sizeof(GSVertexPT1);
|
||||
const u32 index_reserve_size = num_rects * 6 * sizeof(u32);
|
||||
const u32 index_reserve_size = num_rects * 6 * sizeof(u16);
|
||||
if (!m_vertex_stream_buffer.ReserveMemory(vertex_reserve_size, sizeof(GSVertexPT1)) ||
|
||||
!m_index_stream_buffer.ReserveMemory(index_reserve_size, sizeof(u32)))
|
||||
!m_index_stream_buffer.ReserveMemory(index_reserve_size, sizeof(u16)))
|
||||
{
|
||||
ExecuteCommandListAndRestartRenderPass(false, "Uploading bytes to vertex buffer");
|
||||
if (!m_vertex_stream_buffer.ReserveMemory(vertex_reserve_size, sizeof(GSVertexPT1)) ||
|
||||
!m_index_stream_buffer.ReserveMemory(index_reserve_size, sizeof(u32)))
|
||||
!m_index_stream_buffer.ReserveMemory(index_reserve_size, sizeof(u16)))
|
||||
{
|
||||
pxFailRel("Failed to reserve space for vertices");
|
||||
}
|
||||
@@ -965,7 +967,7 @@ void GSDevice12::DoMultiStretchRects(
|
||||
// Don't use primitive restart here, it ends up slower on some drivers.
|
||||
const GSVector2 ds(static_cast<float>(dTex->GetWidth()), static_cast<float>(dTex->GetHeight()));
|
||||
GSVertexPT1* verts = reinterpret_cast<GSVertexPT1*>(m_vertex_stream_buffer.GetCurrentHostPointer());
|
||||
u32* idx = reinterpret_cast<u32*>(m_index_stream_buffer.GetCurrentHostPointer());
|
||||
u16* idx = reinterpret_cast<u16*>(m_index_stream_buffer.GetCurrentHostPointer());
|
||||
u32 icount = 0;
|
||||
u32 vcount = 0;
|
||||
for (u32 i = 0; i < num_rects; i++)
|
||||
@@ -996,12 +998,12 @@ void GSDevice12::DoMultiStretchRects(
|
||||
|
||||
m_vertex.start = m_vertex_stream_buffer.GetCurrentOffset() / sizeof(GSVertexPT1);
|
||||
m_vertex.count = vcount;
|
||||
m_index.start = m_index_stream_buffer.GetCurrentOffset() / sizeof(u32);
|
||||
m_index.start = m_index_stream_buffer.GetCurrentOffset() / sizeof(u16);
|
||||
m_index.count = icount;
|
||||
m_vertex_stream_buffer.CommitMemory(vcount * sizeof(GSVertexPT1));
|
||||
m_index_stream_buffer.CommitMemory(icount * sizeof(u32));
|
||||
m_index_stream_buffer.CommitMemory(icount * sizeof(u16));
|
||||
SetVertexBuffer(m_vertex_stream_buffer.GetGPUPointer(), m_vertex_stream_buffer.GetSize(), sizeof(GSVertexPT1));
|
||||
SetIndexBuffer(m_index_stream_buffer.GetGPUPointer(), m_index_stream_buffer.GetSize(), DXGI_FORMAT_R32_UINT);
|
||||
SetIndexBuffer(m_index_stream_buffer.GetGPUPointer(), m_index_stream_buffer.GetSize(), DXGI_FORMAT_R16_UINT);
|
||||
|
||||
// Even though we're batching, a cmdbuffer submit could've messed this up.
|
||||
const GSVector4i rc(dTex->GetRect());
|
||||
@@ -1422,22 +1424,10 @@ void GSDevice12::RenderImGui()
|
||||
m_vertex_stream_buffer.CommitMemory(size);
|
||||
}
|
||||
|
||||
u32 index_offset;
|
||||
{
|
||||
const u32 size = sizeof(ImDrawIdx) * static_cast<u32>(cmd_list->IdxBuffer.Size);
|
||||
if (!m_index_stream_buffer.ReserveMemory(size, sizeof(ImDrawIdx)))
|
||||
{
|
||||
Console.Warning("Skipping ImGui draw because of no vertex buffer space");
|
||||
return;
|
||||
}
|
||||
|
||||
index_offset = m_index_stream_buffer.GetCurrentOffset() / sizeof(ImDrawIdx);
|
||||
std::memcpy(m_index_stream_buffer.GetCurrentHostPointer(), cmd_list->IdxBuffer.Data, size);
|
||||
m_index_stream_buffer.CommitMemory(size);
|
||||
}
|
||||
|
||||
SetVertexBuffer(m_vertex_stream_buffer.GetGPUPointer(), m_vertex_stream_buffer.GetSize(), sizeof(ImDrawVert));
|
||||
SetIndexBuffer(m_index_stream_buffer.GetGPUPointer(), m_index_stream_buffer.GetSize(), DXGI_FORMAT_R16_UINT);
|
||||
|
||||
static_assert(sizeof(ImDrawIdx) == sizeof(u16));
|
||||
IASetIndexBuffer(cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size);
|
||||
|
||||
for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
|
||||
{
|
||||
@@ -1468,7 +1458,7 @@ void GSDevice12::RenderImGui()
|
||||
if (ApplyUtilityState())
|
||||
{
|
||||
g_d3d12_context->GetCommandList()->DrawIndexedInstanced(
|
||||
pcmd->ElemCount, 1, index_offset + pcmd->IdxOffset, vertex_offset + pcmd->VtxOffset, 0);
|
||||
pcmd->ElemCount, 1, m_index.start + pcmd->IdxOffset, vertex_offset + pcmd->VtxOffset, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1536,17 +1526,17 @@ void GSDevice12::IASetVertexBuffer(const void* vertex, size_t stride, size_t cou
|
||||
|
||||
void GSDevice12::IASetIndexBuffer(const void* index, size_t count)
|
||||
{
|
||||
const u32 size = sizeof(u32) * static_cast<u32>(count);
|
||||
if (!m_index_stream_buffer.ReserveMemory(size, sizeof(u32)))
|
||||
const u32 size = sizeof(u16) * static_cast<u32>(count);
|
||||
if (!m_index_stream_buffer.ReserveMemory(size, sizeof(u16)))
|
||||
{
|
||||
ExecuteCommandListAndRestartRenderPass(false, "Uploading bytes to index buffer");
|
||||
if (!m_index_stream_buffer.ReserveMemory(size, sizeof(u32)))
|
||||
if (!m_index_stream_buffer.ReserveMemory(size, sizeof(u16)))
|
||||
pxFailRel("Failed to reserve space for vertices");
|
||||
}
|
||||
|
||||
m_index.start = m_index_stream_buffer.GetCurrentOffset() / sizeof(u32);
|
||||
m_index.start = m_index_stream_buffer.GetCurrentOffset() / sizeof(u16);
|
||||
m_index.count = count;
|
||||
SetIndexBuffer(m_index_stream_buffer.GetGPUPointer(), m_index_stream_buffer.GetSize(), DXGI_FORMAT_R32_UINT);
|
||||
SetIndexBuffer(m_index_stream_buffer.GetGPUPointer(), m_index_stream_buffer.GetSize(), DXGI_FORMAT_R16_UINT);
|
||||
|
||||
std::memcpy(m_index_stream_buffer.GetCurrentHostPointer(), index, size);
|
||||
m_index_stream_buffer.CommitMemory(size);
|
||||
@@ -1622,7 +1612,7 @@ bool GSDevice12::GetSampler(D3D12::DescriptorHandle* cpu_handle, GSHWDrawConfig:
|
||||
sd.AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
|
||||
sd.MinLOD = 0.0f;
|
||||
sd.MaxLOD = (ss.lodclamp || !ss.UseMipmapFiltering()) ? 0.25f : FLT_MAX;
|
||||
sd.MaxAnisotropy = std::clamp(GSConfig.MaxAnisotropy, 1, 16);
|
||||
sd.MaxAnisotropy = std::clamp<u8>(GSConfig.MaxAnisotropy, 1, 16);
|
||||
sd.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER;
|
||||
|
||||
if (!g_d3d12_context->GetSamplerHeapManager().Allocate(cpu_handle))
|
||||
@@ -2758,11 +2748,15 @@ void GSDevice12::EndRenderPass()
|
||||
if (!m_in_render_pass)
|
||||
return;
|
||||
|
||||
g_d3d12_context->GetCommandList()->EndRenderPass();
|
||||
m_in_render_pass = false;
|
||||
|
||||
// to render again, we need to reset OM
|
||||
m_dirty_flags |= DIRTY_FLAG_RENDER_TARGET;
|
||||
|
||||
g_perfmon.Put(GSPerfMon::RenderPasses, 1);
|
||||
|
||||
g_d3d12_context->GetCommandList()->EndRenderPass();
|
||||
|
||||
}
|
||||
|
||||
void GSDevice12::SetViewport(const D3D12_VIEWPORT& viewport)
|
||||
@@ -3212,13 +3206,22 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
|
||||
}
|
||||
|
||||
// avoid restarting the render pass just to switch from rt+depth to rt and vice versa
|
||||
if (m_in_render_pass && !hdr_rt && !draw_ds && m_current_depth_target && m_current_render_target == draw_rt &&
|
||||
config.tex != m_current_depth_target && m_current_depth_target->GetSize() == draw_rt->GetSize())
|
||||
if (m_in_render_pass && (m_current_render_target == draw_rt || m_current_depth_target == draw_ds))
|
||||
{
|
||||
// avoid restarting the render pass just to switch from rt+depth to rt and vice versa
|
||||
// keep the depth even if doing HDR draws, because the next draw will probably re-enable depth
|
||||
if (!draw_rt && m_current_render_target && config.tex != m_current_render_target &&
|
||||
m_current_render_target->GetSize() == draw_ds->GetSize())
|
||||
{
|
||||
draw_rt = m_current_render_target;
|
||||
m_pipeline_selector.rt = true;
|
||||
}
|
||||
}
|
||||
else if (!draw_ds && m_current_depth_target && config.tex != m_current_depth_target &&
|
||||
m_current_depth_target->GetSize() == draw_rt->GetSize())
|
||||
{
|
||||
draw_ds = m_current_depth_target;
|
||||
m_pipeline_selector.ds = true;
|
||||
m_pipeline_selector.dss.ztst = ZTST_ALWAYS;
|
||||
m_pipeline_selector.dss.zwe = false;
|
||||
}
|
||||
|
||||
OMSetRenderTargets(draw_rt, draw_ds, config.scissor);
|
||||
@@ -3353,7 +3356,7 @@ void GSDevice12::UploadHWDrawVerticesAndIndices(const GSHWDrawConfig& config)
|
||||
{
|
||||
m_index.start = 0;
|
||||
m_index.count = config.nindices;
|
||||
SetIndexBuffer(m_expand_index_buffer->GetGPUVirtualAddress(), EXPAND_BUFFER_SIZE, DXGI_FORMAT_R32_UINT);
|
||||
SetIndexBuffer(m_expand_index_buffer->GetGPUVirtualAddress(), EXPAND_BUFFER_SIZE, DXGI_FORMAT_R16_UINT);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -51,15 +51,16 @@ public:
|
||||
u32 key;
|
||||
};
|
||||
|
||||
GSHWDrawConfig::BlendState bs;
|
||||
GSHWDrawConfig::VSSelector vs;
|
||||
GSHWDrawConfig::DepthStencilSelector dss;
|
||||
GSHWDrawConfig::ColorMaskSelector cms;
|
||||
GSHWDrawConfig::BlendState bs;
|
||||
u8 pad;
|
||||
|
||||
__fi bool operator==(const PipelineSelector& p) const { return (memcmp(this, &p, sizeof(p)) == 0); }
|
||||
__fi bool operator!=(const PipelineSelector& p) const { return (memcmp(this, &p, sizeof(p)) != 0); }
|
||||
__fi bool operator==(const PipelineSelector& p) const { return BitEqual(*this, p); }
|
||||
__fi bool operator!=(const PipelineSelector& p) const { return !BitEqual(*this, p); }
|
||||
|
||||
__fi PipelineSelector() { memset(this, 0, sizeof(*this)); }
|
||||
__fi PipelineSelector() { std::memset(this, 0, sizeof(*this)); }
|
||||
};
|
||||
static_assert(sizeof(PipelineSelector) == 24, "Pipeline selector is 24 bytes");
|
||||
|
||||
@@ -145,6 +146,7 @@ private:
|
||||
|
||||
bool m_allow_tearing_supported = false;
|
||||
bool m_using_allow_tearing = false;
|
||||
bool m_is_exclusive_fullscreen = false;
|
||||
bool m_device_lost = false;
|
||||
|
||||
ComPtr<ID3D12RootSignature> m_tfx_root_signature;
|
||||
@@ -191,9 +193,10 @@ private:
|
||||
|
||||
void LookupNativeFormat(GSTexture::Format format, DXGI_FORMAT* d3d_format, DXGI_FORMAT* srv_format, DXGI_FORMAT* rtv_format, DXGI_FORMAT* dsv_format) const;
|
||||
|
||||
bool CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode);
|
||||
bool CreateSwapChain();
|
||||
bool CreateSwapChainRTV();
|
||||
void DestroySwapChainRTVs();
|
||||
void DestroySwapChain();
|
||||
|
||||
GSTexture* CreateSurface(GSTexture::Type type, int width, int height, int levels, GSTexture::Format format) override;
|
||||
|
||||
@@ -243,14 +246,12 @@ public:
|
||||
RenderAPI GetRenderAPI() const override;
|
||||
bool HasSurface() const override;
|
||||
|
||||
bool Create(const WindowInfo& wi, VsyncMode vsync) override;
|
||||
bool Create() override;
|
||||
void Destroy() override;
|
||||
|
||||
bool ChangeWindow(const WindowInfo& new_wi) override;
|
||||
bool UpdateWindow() override;
|
||||
void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override;
|
||||
bool SupportsExclusiveFullscreen() const override;
|
||||
bool IsExclusiveFullscreen() override;
|
||||
bool SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override;
|
||||
void DestroySurface() override;
|
||||
std::string GetDriverInfo() const override;
|
||||
|
||||
|
||||
@@ -297,7 +297,7 @@ bool GSHwHack::GSC_BlackAndBurnoutSky(GSRendererHW& r, int& skip)
|
||||
if (RPRIM->PRIM == GS_SPRITE && !RPRIM->IIP && RPRIM->TME && !RPRIM->FGE && RPRIM->ABE && !RPRIM->AA1 && !RPRIM->FST && !RPRIM->FIX &&
|
||||
ALPHA.A == ALPHA.B && ALPHA.D == 0 && FRAME.PSM == PSMCT32 && TEX0.CPSM == PSMCT32 && TEX0.TCC && !TEX0.TFX && !TEX0.CSM)
|
||||
{
|
||||
if (TEX0.TBW == 16 && TEX0.TW == 10 && TEX0.PSM == PSMT8 && TEX0.TH == 8 && FRAME.FBW == 16)
|
||||
if (TEX0.TBW == 16 && TEX0.TW == 10 && TEX0.PSM == PSMT8 && TEX0.TH >= 7 && FRAME.FBW == 16)
|
||||
{
|
||||
// Readback clouds being rendered during level loading.
|
||||
// Later the alpha channel from the 32 bit frame buffer is used as an 8 bit indexed texture to draw
|
||||
|
||||
@@ -112,7 +112,7 @@ void GSRendererHW::UpdateSettings(const Pcsx2Config::GSOptions& old_config)
|
||||
SetTCOffset();
|
||||
}
|
||||
|
||||
void GSRendererHW::VSync(u32 field, bool registers_written)
|
||||
void GSRendererHW::VSync(u32 field, bool registers_written, bool idle_frame)
|
||||
{
|
||||
if (m_force_preload > 0)
|
||||
{
|
||||
@@ -138,7 +138,7 @@ void GSRendererHW::VSync(u32 field, bool registers_written)
|
||||
|
||||
// Don't age the texture cache when no draws or EE writes have occurred.
|
||||
// Xenosaga needs its targets kept around while it's loading, because it uses them for a fade transition.
|
||||
if (m_last_draw_n == s_n && m_last_transfer_n == s_transfer_n)
|
||||
if (idle_frame)
|
||||
{
|
||||
GL_INS("No draws or transfers, not aging TC");
|
||||
}
|
||||
@@ -147,10 +147,7 @@ void GSRendererHW::VSync(u32 field, bool registers_written)
|
||||
g_texture_cache->IncAge();
|
||||
}
|
||||
|
||||
m_last_draw_n = s_n + 1; // +1 for vsync
|
||||
m_last_transfer_n = s_transfer_n;
|
||||
|
||||
GSRenderer::VSync(field, registers_written);
|
||||
GSRenderer::VSync(field, registers_written, idle_frame);
|
||||
|
||||
if (g_texture_cache->GetHashCacheMemoryUsage() > 1024 * 1024 * 1024)
|
||||
{
|
||||
@@ -258,14 +255,10 @@ void GSRendererHW::Lines2Sprites()
|
||||
int i = static_cast<int>(count) * 2 - 4;
|
||||
GSVertex* s = &m_vertex.buff[count - 2];
|
||||
GSVertex* q = &m_vertex.buff[count * 2 - 4];
|
||||
u32* RESTRICT index = &m_index.buff[count * 3 - 6];
|
||||
u16* RESTRICT index = &m_index.buff[count * 3 - 6];
|
||||
|
||||
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 = !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));
|
||||
// Sprites are flat shaded, so the provoking vertex doesn't matter here.
|
||||
constexpr GSVector4i indices = GSVector4i::cxpr16(0, 1, 2, 1, 2, 3, 0, 0);
|
||||
|
||||
for (; i >= 0; i -= 4, s -= 2, q -= 4, index -= 6)
|
||||
{
|
||||
@@ -310,9 +303,10 @@ void GSRendererHW::Lines2Sprites()
|
||||
q[1] = v0;
|
||||
q[2] = v1;
|
||||
|
||||
const GSVector4i i_splat(i);
|
||||
GSVector4i::store<false>(index, i_splat + indices_low);
|
||||
GSVector4i::storel(index + 4, i_splat + indices_high);
|
||||
const GSVector4i this_indices = GSVector4i::broadcast16(i).add16(indices);
|
||||
const int high = this_indices.extract32<2>();
|
||||
GSVector4i::storel(index, this_indices);
|
||||
std::memcpy(&index[4], &high, sizeof(high));
|
||||
}
|
||||
|
||||
m_vertex.head = m_vertex.tail = m_vertex.next = count * 2;
|
||||
@@ -322,26 +316,30 @@ void GSRendererHW::Lines2Sprites()
|
||||
|
||||
void GSRendererHW::ExpandLineIndices()
|
||||
{
|
||||
const u32 process_count = (m_index.tail + 3) / 4 * 4;
|
||||
const u32 process_count = (m_index.tail + 7) / 8 * 8;
|
||||
const u32 expansion_factor = 3;
|
||||
m_index.tail *= expansion_factor;
|
||||
GSVector4i* end = reinterpret_cast<GSVector4i*>(m_index.buff);
|
||||
GSVector4i* read = reinterpret_cast<GSVector4i*>(m_index.buff + process_count);
|
||||
GSVector4i* write = reinterpret_cast<GSVector4i*>(m_index.buff + process_count * expansion_factor);
|
||||
|
||||
constexpr GSVector4i low0 = GSVector4i::cxpr(0, 1, 2, 1);
|
||||
constexpr GSVector4i low1 = GSVector4i::cxpr(2, 3, 0, 1);
|
||||
constexpr GSVector4i low2 = GSVector4i::cxpr(2, 1, 2, 3);
|
||||
constexpr GSVector4i mask0 = GSVector4i::cxpr8(0, 1, 0, 1, 2, 3, 0, 1, 2, 3, 2, 3, 4, 5, 4, 5);
|
||||
constexpr GSVector4i mask1 = GSVector4i::cxpr8(6, 7, 4, 5, 6, 7, 6, 7, 8, 9, 8, 9, 10, 11, 8, 9);
|
||||
constexpr GSVector4i mask2 = GSVector4i::cxpr8(10, 11, 10, 11, 12, 13, 12, 13, 14, 15, 12, 13, 14, 15, 14, 15);
|
||||
|
||||
constexpr GSVector4i low0 = GSVector4i::cxpr16(0, 1, 2, 1, 2, 3, 0, 1);
|
||||
constexpr GSVector4i low1 = GSVector4i::cxpr16(2, 1, 2, 3, 0, 1, 2, 1);
|
||||
constexpr GSVector4i low2 = GSVector4i::cxpr16(2, 3, 0, 1, 2, 1, 2, 3);
|
||||
|
||||
while (read > end)
|
||||
{
|
||||
read -= 1;
|
||||
write -= expansion_factor;
|
||||
|
||||
const GSVector4i in = read->sll32(2);
|
||||
write[0] = in.xxyx() | low0;
|
||||
write[1] = in.yyzz() | low1;
|
||||
write[2] = in.wzww() | low2;
|
||||
const GSVector4i in = read->sll16(2);
|
||||
write[0] = in.shuffle8(mask0) | low0;
|
||||
write[1] = in.shuffle8(mask1) | low1;
|
||||
write[2] = in.shuffle8(mask2) | low2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -623,7 +621,7 @@ GSVector4 GSRendererHW::RealignTargetTextureCoordinate(const GSTextureCache::Sou
|
||||
GSVector4i GSRendererHW::ComputeBoundingBox(const GSVector2i& rtsize, float rtscale)
|
||||
{
|
||||
const GSVector4 offset = GSVector4(-1.0f, 1.0f); // Round value
|
||||
const GSVector4 box = m_vt.m_min.p.xyxy(m_vt.m_max.p) + offset.xxyy();
|
||||
const GSVector4 box = m_vt.m_min.p.upld(m_vt.m_max.p) + offset.xxyy();
|
||||
return GSVector4i(box * GSVector4(rtscale)).rintersect(GSVector4i(0, 0, rtsize.x, rtsize.y));
|
||||
}
|
||||
|
||||
@@ -655,9 +653,9 @@ void GSRendererHW::MergeSprite(GSTextureCache::Source* tex)
|
||||
}
|
||||
|
||||
#if 0
|
||||
GSVector4 delta_p = m_vt.m_max.p - m_vt.m_min.p;
|
||||
GSVector4 delta_t = m_vt.m_max.t - m_vt.m_min.t;
|
||||
bool is_blit = PrimitiveOverlap() == PRIM_OVERLAP_NO;
|
||||
const GSVector4 delta_p = m_vt.m_max.p - m_vt.m_min.p;
|
||||
const GSVector4 delta_t = m_vt.m_max.t - m_vt.m_min.t;
|
||||
const bool is_blit = PrimitiveOverlap() == PRIM_OVERLAP_NO;
|
||||
GL_INS("PP SAMPLER: Dp %f %f Dt %f %f. Is blit %d, is paving %d, count %d", delta_p.x, delta_p.y, delta_t.x, delta_t.y, is_blit, is_paving, m_vertex.tail);
|
||||
#endif
|
||||
|
||||
@@ -769,7 +767,7 @@ bool GSRendererHW::IsPossibleChannelShuffle() const
|
||||
// WRC 4 does channel shuffles in vertical strips. So check for page alignment.
|
||||
// Texture TBW should also be twice the framebuffer FBW, because the page is twice as wide.
|
||||
if (m_cached_ctx.TEX0.TBW == (m_cached_ctx.FRAME.FBW * 2) &&
|
||||
GSLocalMemory::IsPageAligned(m_cached_ctx.FRAME.PSM, GSVector4i(m_vt.m_min.p.xyxy(m_vt.m_max.p))))
|
||||
GSLocalMemory::IsPageAligned(m_cached_ctx.FRAME.PSM, GSVector4i(m_vt.m_min.p.upld(m_vt.m_max.p))))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -804,11 +802,11 @@ bool GSRendererHW::IsSplitTextureShuffle()
|
||||
// For texture shuffles, the U will be offset by 8.
|
||||
const GSLocalMemory::psm_t& frame_psm = GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM];
|
||||
|
||||
const GSVector4i pos_rc = GSVector4i(m_vt.m_min.p.upld(m_vt.m_max.p));
|
||||
const GSVector4i pos_rc = GSVector4i(m_vt.m_min.p.upld(m_vt.m_max.p + GSVector4::cxpr(0.5f)));
|
||||
const GSVector4i tex_rc = GSVector4i(m_vt.m_min.t.upld(m_vt.m_max.t));
|
||||
|
||||
// Width/height should match.
|
||||
if (pos_rc.width() != tex_rc.width() || pos_rc.height() != tex_rc.height())
|
||||
if (std::abs(pos_rc.width() - tex_rc.width()) > 8 || pos_rc.height() != tex_rc.height())
|
||||
return false;
|
||||
|
||||
// X might be offset by up to -8/+8, but either the position or UV should be aligned.
|
||||
@@ -867,7 +865,7 @@ bool GSRendererHW::IsSplitTextureShuffle()
|
||||
GSVector4i GSRendererHW::GetSplitTextureShuffleDrawRect() const
|
||||
{
|
||||
const GSLocalMemory::psm_t& frame_psm = GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM];
|
||||
GSVector4i r = GSVector4i(m_vt.m_min.p.xyxy(m_vt.m_max.p)).rintersect(GSVector4i(m_context->scissor.in));
|
||||
GSVector4i r = GSVector4i(m_vt.m_min.p.upld(m_vt.m_max.p + GSVector4::cxpr(0.5f))).rintersect(GSVector4i(m_context->scissor.in));
|
||||
|
||||
// Some games (e.g. Crash Twinsanity) adjust both FBP and TBP0, so the rectangle will be half the size
|
||||
// of the actual shuffle. Others leave the FBP alone, but only adjust TBP0, and offset the draw rectangle
|
||||
@@ -1434,7 +1432,7 @@ void GSRendererHW::Draw()
|
||||
#endif
|
||||
}
|
||||
|
||||
GL_PUSH("HW Draw %d", s_n);
|
||||
GL_PUSH("HW Draw %d (Context %u)", s_n, PRIM->CTXT);
|
||||
|
||||
// When the format is 24bit (Z or C), DATE ceases to function.
|
||||
// It was believed that in 24bit mode all pixels pass because alpha doesn't exist
|
||||
@@ -1572,7 +1570,7 @@ void GSRendererHW::Draw()
|
||||
}
|
||||
|
||||
// The rectangle of the draw rounded up.
|
||||
const GSVector4 rect = m_vt.m_min.p.xyxy(m_vt.m_max.p) + GSVector4(0.0f, 0.0f, 0.5f, 0.5f);
|
||||
const GSVector4 rect = m_vt.m_min.p.upld(m_vt.m_max.p + GSVector4::cxpr(0.5f));
|
||||
m_r = GSVector4i(rect).rintersect(GSVector4i(context->scissor.in));
|
||||
|
||||
if (!m_channel_shuffle && m_cached_ctx.FRAME.Block() == m_cached_ctx.TEX0.TBP0 &&
|
||||
@@ -2513,9 +2511,9 @@ void GSRendererHW::SetupIA(float target_scale, float sx, float sy)
|
||||
m_conf.nindices = m_index.tail;
|
||||
}
|
||||
|
||||
void GSRendererHW::EmulateZbuffer()
|
||||
void GSRendererHW::EmulateZbuffer(const GSTextureCache::Target* ds)
|
||||
{
|
||||
if (m_cached_ctx.TEST.ZTE)
|
||||
if (ds && m_cached_ctx.TEST.ZTE)
|
||||
{
|
||||
m_conf.depth.ztst = m_cached_ctx.TEST.ZTST;
|
||||
// AA1: Z is not written on lines since coverage is always less than 0x80.
|
||||
@@ -3316,7 +3314,7 @@ void GSRendererHW::EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER, bool&
|
||||
|
||||
// For stat to optimize accurate option
|
||||
#if 0
|
||||
GL_INS("BLEND_INFO: %u/%u/%u/%u. Clamp:%u. Prim:%d number %u (drawlist %u) (sw %d)",
|
||||
GL_INS("BLEND_INFO: %u/%u/%u/%u. Clamp:%u. Prim:%d number %u (drawlist %zu) (sw %d)",
|
||||
m_conf.ps.blend_a, m_conf.ps.blend_b, m_conf.ps.blend_c, m_conf.ps.blend_d,
|
||||
m_env.COLCLAMP.CLAMP, m_vt.m_primclass, m_vertex.next, m_drawlist.size(), sw_blending);
|
||||
#endif
|
||||
@@ -3606,11 +3604,11 @@ __ri void GSRendererHW::EmulateTextureSampler(const GSTextureCache::Target* rt,
|
||||
const u8 wms = EffectiveClamp(m_cached_ctx.CLAMP.WMS, !tex->m_target && (source_region.HasX() || redundant_wms));
|
||||
const u8 wmt = EffectiveClamp(m_cached_ctx.CLAMP.WMT, !tex->m_target && (source_region.HasY() || redundant_wmt));
|
||||
const bool complex_wms_wmt = !!((wms | wmt) & 2) || target_region;
|
||||
GL_CACHE("WMS: %s [%s%s] WMT: %s [%s%s] Complex: %d TargetRegion: %d MINU: %d MAXU: %d MINV: %d MAXV: %d",
|
||||
clamp_modes[m_cached_ctx.CLAMP.WMS], redundant_wms ? "redundant," : "", clamp_modes[wms],
|
||||
clamp_modes[m_cached_ctx.CLAMP.WMT], redundant_wmt ? "redundant," : "", clamp_modes[wmt],
|
||||
complex_wms_wmt, target_region,
|
||||
m_cached_ctx.CLAMP.MINU, m_cached_ctx.CLAMP.MAXU, m_cached_ctx.CLAMP.MINV, m_cached_ctx.CLAMP.MAXV);
|
||||
GL_CACHE("FST: %s WMS: %s [%s%s] WMT: %s [%s%s] Complex: %d TargetRegion: %d MINU: %d MAXU: %d MINV: %d MAXV: %d",
|
||||
PRIM->FST ? "UV" : "STQ", clamp_modes[m_cached_ctx.CLAMP.WMS], redundant_wms ? "redundant," : "",
|
||||
clamp_modes[wms], clamp_modes[m_cached_ctx.CLAMP.WMT], redundant_wmt ? "redundant," : "", clamp_modes[wmt],
|
||||
complex_wms_wmt, target_region, m_cached_ctx.CLAMP.MINU, m_cached_ctx.CLAMP.MAXU, m_cached_ctx.CLAMP.MINV,
|
||||
m_cached_ctx.CLAMP.MAXV);
|
||||
|
||||
const bool need_mipmap = IsMipMapDraw();
|
||||
const bool shader_emulated_sampler = tex->m_palette || (tex->m_target && !m_conf.ps.shuffle && cpsm.fmt != 0) ||
|
||||
@@ -4092,7 +4090,7 @@ bool GSRendererHW::CanUseTexIsFB(const GSTextureCache::Target* rt, const GSTextu
|
||||
|
||||
// Make sure that we're not sampling away from the area we're rendering.
|
||||
// We need to take the absolute here, because Beyond Good and Evil undithers itself using a -1,-1 offset.
|
||||
const GSVector4 diff(m_vt.m_min.p.xyxy(m_vt.m_max.p) - m_vt.m_min.t.xyxy(m_vt.m_max.t));
|
||||
const GSVector4 diff(m_vt.m_min.p.upld(m_vt.m_max.p) - m_vt.m_min.t.upld(m_vt.m_max.t));
|
||||
GL_CACHE("Coord diff: %f,%f", diff.x, diff.y);
|
||||
if ((diff.abs() < GSVector4(1.0f)).alltrue())
|
||||
{
|
||||
@@ -4162,8 +4160,8 @@ void GSRendererHW::ResetStates()
|
||||
__ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Target* ds, GSTextureCache::Source* tex, const TextureMinMaxResult& tmm)
|
||||
{
|
||||
#ifdef ENABLE_OGL_DEBUG
|
||||
const GSVector4i area_out = GSVector4i(m_vt.m_min.p.xyxy(m_vt.m_max.p)).rintersect(GSVector4i(m_context->scissor.in));
|
||||
const GSVector4i area_in = GSVector4i(m_vt.m_min.t.xyxy(m_vt.m_max.t));
|
||||
const GSVector4i area_out = GSVector4i(m_vt.m_min.p.upld(m_vt.m_max.p)).rintersect(GSVector4i(m_context->scissor.in));
|
||||
const GSVector4i area_in = GSVector4i(m_vt.m_min.t.upld(m_vt.m_max.t));
|
||||
|
||||
GL_PUSH("GL Draw from (area %d,%d => %d,%d) in (area %d,%d => %d,%d)",
|
||||
area_in.x, area_in.y, area_in.z, area_in.w,
|
||||
@@ -4189,7 +4187,7 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
|
||||
m_conf.ds = ds ? ds->m_texture : nullptr;
|
||||
|
||||
// Z setup has to come before channel shuffle
|
||||
EmulateZbuffer();
|
||||
EmulateZbuffer(ds);
|
||||
|
||||
// HLE implementation of the channel selection effect
|
||||
//
|
||||
@@ -4321,7 +4319,10 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
|
||||
// No point outputting colours if we're just writing depth.
|
||||
// We might still need the framebuffer for DATE, though.
|
||||
if (!rt || m_conf.colormask.wrgba == 0)
|
||||
{
|
||||
m_conf.ps.DisableColorOutput();
|
||||
m_conf.colormask.wrgba = 0;
|
||||
}
|
||||
|
||||
if (m_conf.ps.scanmsk & 2)
|
||||
DATE_PRIMID = false; // to have discard in the shader work correctly
|
||||
@@ -4934,16 +4935,15 @@ void GSRendererHW::OI_DoubleHalfClear(GSTextureCache::Target*& rt, GSTextureCach
|
||||
clear_depth ? "depth" : "target", base << 5, half << 5, w_pages, h_pages, m_cached_ctx.FRAME.FBW, color);
|
||||
|
||||
// If some of the channels are masked, we need to keep them.
|
||||
if (m_cached_ctx.FRAME.FBMSK != 0)
|
||||
if (!clear_depth && m_cached_ctx.FRAME.FBMSK != 0)
|
||||
{
|
||||
GSTexture* tex = nullptr;
|
||||
GSTextureCache::Target* target = clear_depth ? ds : rt;
|
||||
const GSVector2 size = GSVector2(static_cast<float>(target->GetUnscaledWidth()) * target->m_scale, static_cast<float>(target->GetUnscaledHeight()) * target->m_scale);
|
||||
pxAssert(!target->m_texture->IsDepthStencil());
|
||||
try
|
||||
{
|
||||
tex = target->m_texture->IsDepthStencil() ?
|
||||
g_gs_device->CreateDepthStencil(size.x, size.y, target->m_texture->GetFormat(), true) :
|
||||
g_gs_device->CreateRenderTarget(size.x, size.y, target->m_texture->GetFormat(), true);
|
||||
tex = g_gs_device->CreateRenderTarget(size.x, size.y, target->m_texture->GetFormat(), false);
|
||||
}
|
||||
catch (const std::bad_alloc&)
|
||||
{
|
||||
@@ -5059,7 +5059,7 @@ bool GSRendererHW::OI_GsMemClear()
|
||||
if (((m_vertex.next == 2) || ZisFrame) && m_vt.m_eq.rgba == 0xFFFF)
|
||||
{
|
||||
const GSOffset& off = m_context->offset.fb;
|
||||
GSVector4i r = GSVector4i(m_vt.m_min.p.xyxy(m_vt.m_max.p)).rintersect(GSVector4i(m_context->scissor.in));
|
||||
GSVector4i r = GSVector4i(m_vt.m_min.p.upld(m_vt.m_max.p)).rintersect(GSVector4i(m_context->scissor.in));
|
||||
|
||||
if (r.width() == 32 && ZisFrame)
|
||||
r.z += 32;
|
||||
|
||||
@@ -86,7 +86,7 @@ private:
|
||||
bool& target_region, GSVector2i& unscaled_size, float& scale, GSTexture*& src_copy);
|
||||
bool CanUseTexIsFB(const GSTextureCache::Target* rt, const GSTextureCache::Source* tex) const;
|
||||
|
||||
void EmulateZbuffer();
|
||||
void EmulateZbuffer(const GSTextureCache::Target* ds);
|
||||
void EmulateATST(float& AREF, GSHWDrawConfig::PSSelector& ps, bool pass_2);
|
||||
|
||||
void SetTCOffset();
|
||||
@@ -149,10 +149,6 @@ private:
|
||||
std::unique_ptr<GSTextureCacheSW::Texture> m_sw_texture[7 + 1];
|
||||
std::unique_ptr<GSVirtualAlignedClass<32>> m_sw_rasterizer;
|
||||
|
||||
// Tracking draw counters for idle frame detection.
|
||||
int m_last_draw_n = 0;
|
||||
int m_last_transfer_n = 0;
|
||||
|
||||
public:
|
||||
GSRendererHW();
|
||||
virtual ~GSRendererHW() override;
|
||||
@@ -178,7 +174,7 @@ public:
|
||||
|
||||
void Reset(bool hardware_reset) override;
|
||||
void UpdateSettings(const Pcsx2Config::GSOptions& old_config) override;
|
||||
void VSync(u32 field, bool registers_written) override;
|
||||
void VSync(u32 field, bool registers_written, bool idle_frame) override;
|
||||
|
||||
GSTexture* GetOutput(int i, float& scale, int& y_offset) override;
|
||||
GSTexture* GetFeedbackOutput(float& scale) override;
|
||||
|
||||
@@ -35,12 +35,16 @@ std::unique_ptr<GSTextureCache> g_texture_cache;
|
||||
|
||||
static u8* s_unswizzle_buffer;
|
||||
|
||||
/// List of candidates for purging when the hash cache gets too large.
|
||||
static std::vector<std::pair<GSTextureCache::HashCacheMap::iterator, s32>> s_hash_cache_purge_list;
|
||||
|
||||
GSTextureCache::GSTextureCache()
|
||||
{
|
||||
// In theory 4MB is enough but 9MB is safer for overflow (8MB
|
||||
// isn't enough in custom resolution)
|
||||
// Test: onimusha 3 PAL 60Hz
|
||||
s_unswizzle_buffer = (u8*)_aligned_malloc(9 * 1024 * 1024, 32);
|
||||
s_unswizzle_buffer = (u8*)_aligned_malloc(9 * 1024 * 1024, VECTOR_ALIGNMENT);
|
||||
pxAssertRel(s_unswizzle_buffer, "Failed to allocate unswizzle buffer");
|
||||
|
||||
m_surface_offset_cache.reserve(S_SURFACE_OFFSET_CACHE_MAX_SIZE);
|
||||
}
|
||||
@@ -49,6 +53,7 @@ GSTextureCache::~GSTextureCache()
|
||||
{
|
||||
RemoveAll();
|
||||
|
||||
s_hash_cache_purge_list = {};
|
||||
_aligned_free(s_unswizzle_buffer);
|
||||
}
|
||||
|
||||
@@ -851,8 +856,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const GIFRegTEX0& TEX0, con
|
||||
// If the source is reading the rt, make sure it's big enough.
|
||||
if (t && GSUtil::HasCompatibleBits(psm, t->m_TEX0.PSM))
|
||||
{
|
||||
GSVector4i dirty_rect = t->m_dirty.GetTotalRect(t->m_TEX0, GSVector2i(new_rect.z, new_rect.w));
|
||||
const GSVector2i size_delta = { (dirty_rect.z - t->m_valid.z), (dirty_rect.w - t->m_valid.w) };
|
||||
const GSVector2i size_delta = { (new_rect.z - t->m_valid.z), (new_rect.w - t->m_valid.w) };
|
||||
if (size_delta.x > 0 || size_delta.y > 0)
|
||||
{
|
||||
RGBAMask rgba;
|
||||
@@ -1267,7 +1271,13 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe
|
||||
g_gs_device->StretchRect(dst->m_texture, sRect, tex, dRect, (type == RenderTarget) ? ShaderConvert::COPY : ShaderConvert::DEPTH_COPY, false);
|
||||
g_perfmon.Put(GSPerfMon::TextureCopies, 1);
|
||||
m_target_memory_usage = (m_target_memory_usage - dst->m_texture->GetMemUsage()) + tex->GetMemUsage();
|
||||
g_gs_device->Recycle(dst->m_texture);
|
||||
|
||||
// If we're changing resolution scale, just toss the texture, it's not going to get reused.
|
||||
if (!GSConfig.UserHacks_NativePaletteDraw || (dst->m_scale != 1.0f && scale != 1.0f))
|
||||
delete dst->m_texture;
|
||||
else
|
||||
g_gs_device->Recycle(dst->m_texture);
|
||||
|
||||
dst->m_texture = tex;
|
||||
dst->m_scale = scale;
|
||||
dst->m_unscaled_size = new_size;
|
||||
@@ -2209,9 +2219,9 @@ void GSTextureCache::InvalidateLocalMem(const GSOffset& off, const GSVector4i& r
|
||||
}
|
||||
|
||||
const GSVector4i draw_rect = (t->readbacks_since_draw > 1) ? t->m_drawn_since_read : targetr.rintersect(t->m_drawn_since_read);
|
||||
|
||||
const GSVector4i dirty_rect = t->m_dirty.GetTotalRect(t->m_TEX0, t->m_unscaled_size);
|
||||
// Recently made this section dirty, no need to read it.
|
||||
if (draw_rect.rintersect(t->m_dirty.GetTotalRect(t->m_TEX0, t->m_unscaled_size)).eq(draw_rect))
|
||||
if (draw_rect.rintersect(dirty_rect).eq(draw_rect))
|
||||
return;
|
||||
|
||||
if (t->m_drawn_since_read.eq(GSVector4i::zero()))
|
||||
@@ -2224,6 +2234,10 @@ void GSTextureCache::InvalidateLocalMem(const GSOffset& off, const GSVector4i& r
|
||||
|
||||
if (!draw_rect.rempty())
|
||||
{
|
||||
// The draw rect and read rect overlap somewhat, we should update the target before downloading it.
|
||||
if (!dirty_rect.rintersect(targetr).rempty())
|
||||
t->Update(false);
|
||||
|
||||
Read(t, draw_rect);
|
||||
|
||||
z_found = read_start >= t->m_TEX0.TBP0 && read_end <= t->m_end_block;
|
||||
@@ -2346,8 +2360,9 @@ void GSTextureCache::InvalidateLocalMem(const GSOffset& off, const GSVector4i& r
|
||||
continue;
|
||||
}
|
||||
|
||||
const GSVector4i dirty_rect = t->m_dirty.GetTotalRect(t->m_TEX0, t->m_unscaled_size);
|
||||
// Recently made this section dirty, no need to read it.
|
||||
if (targetr.rintersect(t->m_dirty.GetTotalRect(t->m_TEX0, t->m_unscaled_size)).eq(targetr))
|
||||
if (targetr.rintersect(dirty_rect).eq(targetr))
|
||||
{
|
||||
if (exact_bp)
|
||||
return;
|
||||
@@ -2368,6 +2383,10 @@ void GSTextureCache::InvalidateLocalMem(const GSOffset& off, const GSVector4i& r
|
||||
continue;
|
||||
}
|
||||
|
||||
// The draw rect and read rect overlap somewhat, we should update the target before downloading it.
|
||||
if (!dirty_rect.rintersect(targetr).rempty())
|
||||
t->Update(false);
|
||||
|
||||
Read(t, targetr);
|
||||
|
||||
// Try to cut down how much we read next, if we can.
|
||||
@@ -2417,15 +2436,17 @@ bool GSTextureCache::Move(u32 SBP, u32 SBW, u32 SPSM, int sx, int sy, u32 DBP, u
|
||||
|
||||
// DX11/12 is a bit lame and can't partial copy depth targets. We could do this with a blit instead,
|
||||
// but so far haven't seen anything which needs it.
|
||||
const GSLocalMemory::psm_t& spsm_s = GSLocalMemory::m_psm[SPSM];
|
||||
const GSLocalMemory::psm_t& dpsm_s = GSLocalMemory::m_psm[DPSM];
|
||||
if (GSConfig.Renderer == GSRendererType::DX11 || GSConfig.Renderer == GSRendererType::DX12)
|
||||
{
|
||||
if (GSLocalMemory::m_psm[SPSM].depth || GSLocalMemory::m_psm[DPSM].depth)
|
||||
if (spsm_s.depth || dpsm_s.depth)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Look for an exact match on the targets.
|
||||
GSTextureCache::Target* src = GetExactTarget(SBP, SBW, SPSM);
|
||||
GSTextureCache::Target* dst = GetExactTarget(DBP, DBW, DPSM);
|
||||
GSTextureCache::Target* src = GetExactTarget(SBP, SBW, spsm_s.depth ? DepthStencil : RenderTarget);
|
||||
GSTextureCache::Target* dst = GetExactTarget(DBP, DBW, dpsm_s.depth ? DepthStencil : RenderTarget);
|
||||
|
||||
// Beware of the case where a game might create a larger texture by moving a bunch of chunks around.
|
||||
// We use dx/dy == 0 and the TBW check as a safeguard to make sure these go through to local memory.
|
||||
@@ -2498,7 +2519,8 @@ bool GSTextureCache::Move(u32 SBP, u32 SBW, u32 SPSM, int sx, int sy, u32 DBP, u
|
||||
// Make sure the copy doesn't go out of bounds (it shouldn't).
|
||||
if ((scaled_dx + scaled_w) > dst->m_texture->GetWidth() || (scaled_dy + scaled_h) > dst->m_texture->GetHeight())
|
||||
return false;
|
||||
GL_CACHE("HW Move 0x%x to 0x%x <%d,%d->%d,%d> -> <%d,%d->%d,%d>", SBP, DBP, sx, sy, sx + w, sy, h, dx, dy, dx + w, dy + h);
|
||||
GL_CACHE("HW Move 0x%x[BW:%u PSM:%s] to 0x%x[BW:%u PSM:%s] <%d,%d->%d,%d> -> <%d,%d->%d,%d>", SBP, SBW,
|
||||
psm_str(SPSM), DBP, DBW, psm_str(DPSM), sx, sy, sx + w, sy + h, dx, dy, dx + w, dy + h);
|
||||
g_gs_device->CopyRect(src->m_texture, dst->m_texture,
|
||||
GSVector4i(scaled_sx, scaled_sy, scaled_sx + scaled_w, scaled_sy + scaled_h),
|
||||
scaled_dx, scaled_dy);
|
||||
@@ -2576,7 +2598,7 @@ bool GSTextureCache::ShuffleMove(u32 BP, u32 BW, u32 PSM, int sx, int sy, int dx
|
||||
|
||||
#undef V
|
||||
|
||||
static constexpr u32 indices[6] = { 0, 1, 2, 2, 1, 3 };
|
||||
static constexpr u16 indices[6] = { 0, 1, 2, 2, 1, 3 };
|
||||
|
||||
// If we ever do this sort of thing somewhere else, extract this to a helper function.
|
||||
GSHWDrawConfig config;
|
||||
@@ -2633,14 +2655,17 @@ bool GSTextureCache::ShuffleMove(u32 BP, u32 BW, u32 PSM, int sx, int sy, int dx
|
||||
return true;
|
||||
}
|
||||
|
||||
GSTextureCache::Target* GSTextureCache::GetExactTarget(u32 BP, u32 BW, u32 PSM) const
|
||||
GSTextureCache::Target* GSTextureCache::GetExactTarget(u32 BP, u32 BW, int type)
|
||||
{
|
||||
auto& rts = m_dst[GSLocalMemory::m_psm[PSM].depth ? DepthStencil : RenderTarget];
|
||||
auto& rts = m_dst[type];
|
||||
for (auto it = rts.begin(); it != rts.end(); ++it) // Iterate targets from MRU to LRU.
|
||||
{
|
||||
Target* t = *it;
|
||||
if (t->m_TEX0.TBP0 == BP && t->m_TEX0.TBW == BW && t->m_TEX0.PSM == PSM)
|
||||
if (t->m_TEX0.TBP0 == BP && t->m_TEX0.TBW == BW)
|
||||
{
|
||||
rts.MoveFront(it.Index());
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
@@ -2832,29 +2857,11 @@ void GSTextureCache::IncAge()
|
||||
Source* s = *i;
|
||||
|
||||
++i;
|
||||
if (++s->m_age > (s->CanPreload() ? max_preload_age : max_age))
|
||||
if (++s->m_age > ((!s->m_from_hash_cache && s->CanPreload()) ? max_preload_age : max_age))
|
||||
m_src.RemoveAt(s);
|
||||
}
|
||||
|
||||
const u32 max_hash_cache_age = 30;
|
||||
for (auto it = m_hash_cache.begin(); it != m_hash_cache.end();)
|
||||
{
|
||||
HashCacheEntry& e = it->second;
|
||||
if (e.refcount == 0 && ++e.age > max_hash_cache_age)
|
||||
{
|
||||
const u32 mem_usage = e.texture->GetMemUsage();
|
||||
if (e.is_replacement)
|
||||
m_hash_cache_replacement_memory_usage -= mem_usage;
|
||||
else
|
||||
m_hash_cache_memory_usage -= mem_usage;
|
||||
g_gs_device->Recycle(e.texture);
|
||||
m_hash_cache.erase(it++);
|
||||
}
|
||||
else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
AgeHashCache();
|
||||
|
||||
// Clearing of Rendertargets causes flickering in many scene transitions.
|
||||
// Sigh, this seems to be used to invalidate surfaces. So set a huge maxage to avoid flicker,
|
||||
@@ -3605,6 +3612,7 @@ GSTextureCache::HashCacheEntry* GSTextureCache::LookupHashCache(const GIFRegTEX0
|
||||
if (it != m_hash_cache.end())
|
||||
{
|
||||
// super easy, cache hit. remove paltex if it's a replacement texture.
|
||||
GL_CACHE("HC Hit: %" PRIx64 " %" PRIx64 " R-%" PRIx64, key.TEX0Hash, key.CLUTHash, key.region.bits);
|
||||
HashCacheEntry* entry = &it->second;
|
||||
paltex &= (entry->texture->GetFormat() == GSTexture::Format::UNorm8);
|
||||
entry->refcount++;
|
||||
@@ -3612,6 +3620,8 @@ GSTextureCache::HashCacheEntry* GSTextureCache::LookupHashCache(const GIFRegTEX0
|
||||
}
|
||||
|
||||
// cache miss.
|
||||
GL_CACHE("HC Miss: %" PRIx64 " %" PRIx64 " R-%" PRIx64, key.TEX0Hash, key.CLUTHash, key.region.bits);
|
||||
|
||||
// check for a replacement texture with the full clut key
|
||||
if (replace)
|
||||
{
|
||||
@@ -3697,6 +3707,70 @@ GSTextureCache::HashCacheEntry* GSTextureCache::LookupHashCache(const GIFRegTEX0
|
||||
return &m_hash_cache.emplace(key, entry).first->second;
|
||||
}
|
||||
|
||||
void GSTextureCache::RemoveFromHashCache(HashCacheMap::iterator it)
|
||||
{
|
||||
HashCacheEntry& e = it->second;
|
||||
const u32 mem_usage = e.texture->GetMemUsage();
|
||||
if (e.is_replacement)
|
||||
m_hash_cache_replacement_memory_usage -= mem_usage;
|
||||
else
|
||||
m_hash_cache_memory_usage -= mem_usage;
|
||||
g_gs_device->Recycle(e.texture);
|
||||
m_hash_cache.erase(it);
|
||||
}
|
||||
|
||||
void GSTextureCache::AgeHashCache()
|
||||
{
|
||||
// Where did this number come from?
|
||||
// A game called Corvette draws its background FMVs with a ton of 17x17 tiles, which ends up
|
||||
// being about 600 texture uploads per frame. We'll use 800 as an upper bound for a bit of
|
||||
// a buffer, hopefully nothing's going to end up with more textures than that.
|
||||
constexpr u32 MAX_HASH_CACHE_SIZE = 800;
|
||||
constexpr u32 MAX_HASH_CACHE_AGE = 30;
|
||||
|
||||
bool might_need_cache_purge = (m_hash_cache.size() > MAX_HASH_CACHE_SIZE);
|
||||
if (might_need_cache_purge)
|
||||
s_hash_cache_purge_list.clear();
|
||||
|
||||
for (auto it = m_hash_cache.begin(); it != m_hash_cache.end();)
|
||||
{
|
||||
HashCacheEntry& e = it->second;
|
||||
if (e.refcount > 0)
|
||||
{
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (++e.age > MAX_HASH_CACHE_AGE)
|
||||
{
|
||||
RemoveFromHashCache(it++);
|
||||
continue;
|
||||
}
|
||||
|
||||
// We might free up enough just with "normal" removals above.
|
||||
if (might_need_cache_purge)
|
||||
{
|
||||
might_need_cache_purge = (m_hash_cache.size() > MAX_HASH_CACHE_SIZE);
|
||||
if (might_need_cache_purge)
|
||||
s_hash_cache_purge_list.emplace_back(it, static_cast<s32>(e.age));
|
||||
}
|
||||
|
||||
++it;
|
||||
}
|
||||
|
||||
// Pushing to a list, sorting, and removing ends up faster than re-iterating the map.
|
||||
if (might_need_cache_purge)
|
||||
{
|
||||
std::sort(s_hash_cache_purge_list.begin(), s_hash_cache_purge_list.end(),
|
||||
[](const auto& lhs, const auto& rhs) { return lhs.second > rhs.second; });
|
||||
|
||||
const u32 entries_to_purge = std::min(static_cast<u32>(m_hash_cache.size() - MAX_HASH_CACHE_SIZE),
|
||||
static_cast<u32>(s_hash_cache_purge_list.size()));
|
||||
for (u32 i = 0; i < entries_to_purge; i++)
|
||||
RemoveFromHashCache(s_hash_cache_purge_list[i].first);
|
||||
}
|
||||
}
|
||||
|
||||
GSTextureCache::Target* GSTextureCache::CreateTarget(const GIFRegTEX0& TEX0, int w, int h, float scale, int type, const bool clear)
|
||||
{
|
||||
ASSERT(type == RenderTarget || type == DepthStencil);
|
||||
@@ -3796,7 +3870,8 @@ GSTexture* GSTextureCache::LookupPaletteSource(u32 CBP, u32 CPSM, u32 CBW, GSVec
|
||||
|
||||
void GSTextureCache::Read(Target* t, const GSVector4i& r)
|
||||
{
|
||||
if (!t->m_dirty.empty() || r.width() == 0 || r.height() == 0)
|
||||
if ((!t->m_dirty.empty() && !t->m_dirty.GetTotalRect(t->m_TEX0, t->m_unscaled_size).rintersect(r).rempty())
|
||||
|| r.width() == 0 || r.height() == 0)
|
||||
return;
|
||||
|
||||
const GIFRegTEX0& TEX0 = t->m_TEX0;
|
||||
@@ -4154,7 +4229,7 @@ void GSTextureCache::Source::UpdateLayer(const GIFRegTEX0& TEX0, const GSVector4
|
||||
void GSTextureCache::Source::Write(const GSVector4i& r, int layer, const GSOffset& off)
|
||||
{
|
||||
if (!m_write.rect)
|
||||
m_write.rect = static_cast<GSVector4i*>(_aligned_malloc(3 * sizeof(GSVector4i), 32));
|
||||
m_write.rect = static_cast<GSVector4i*>(_aligned_malloc(3 * sizeof(GSVector4i), 16));
|
||||
|
||||
m_write.rect[m_write.count++] = r;
|
||||
|
||||
@@ -4214,6 +4289,8 @@ void GSTextureCache::Source::Flush(u32 count, int layer, const GSOffset& off)
|
||||
rtx = psm.rtxP;
|
||||
}
|
||||
|
||||
pitch = VectorAlign(pitch);
|
||||
|
||||
for (u32 i = 0; i < count; i++)
|
||||
{
|
||||
const GSVector4i r(m_write.rect[i]);
|
||||
@@ -4385,7 +4462,7 @@ void GSTextureCache::Target::Update(bool reset_age)
|
||||
}
|
||||
else
|
||||
{
|
||||
const int pitch = Common::AlignUpPow2(r.width() * sizeof(u32), 32);
|
||||
const int pitch = VectorAlign(r.width() * sizeof(u32));
|
||||
g_gs_renderer->m_mem.ReadTexture(off, r, s_unswizzle_buffer, pitch, TEXA);
|
||||
|
||||
t->Update(t_r, s_unswizzle_buffer, pitch);
|
||||
@@ -5127,13 +5204,13 @@ GSTextureCache::SourceRegion GSTextureCache::SourceRegion::AdjustForMipmap(u32 l
|
||||
if (HasX())
|
||||
{
|
||||
const u32 new_minx = GetMinX() >> level;
|
||||
const u32 new_maxx = std::max<u32>(GetMaxX() >> level, new_minx + 1);
|
||||
const u32 new_maxx = new_minx + std::max(GetWidth() >> level, 1u);
|
||||
ret.SetX(new_minx, new_maxx);
|
||||
}
|
||||
if (HasY())
|
||||
{
|
||||
const u32 new_miny = GetMinY() >> level;
|
||||
const u32 new_maxy = std::max<u32>(GetMaxY() >> level, new_miny + 1);
|
||||
const u32 new_maxy = new_miny + std::max(GetHeight() >> level, 1u);
|
||||
ret.SetY(new_miny, new_maxy);
|
||||
}
|
||||
return ret;
|
||||
@@ -5241,7 +5318,7 @@ static void HashTextureLevel(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, GST
|
||||
{
|
||||
// Expand texture indices. Align to 32 bytes for AVX2.
|
||||
const bool palette = (psm.pal > 0);
|
||||
const u32 pitch = Common::AlignUpPow2(static_cast<u32>(block_rect.z) << (palette ? 0 : 2), 32);
|
||||
const u32 pitch = VectorAlign(static_cast<u32>(block_rect.z) << (palette ? 0 : 2));
|
||||
const u32 row_size = static_cast<u32>(tw) << (palette ? 0 : 2);
|
||||
const GSLocalMemory::readTexture rtx = palette ? psm.rtxP : psm.rtx;
|
||||
|
||||
@@ -5317,8 +5394,7 @@ void GSTextureCache::PreloadTexture(const GIFRegTEX0& TEX0, const GIFRegTEXA& TE
|
||||
}
|
||||
else
|
||||
{
|
||||
// Align pitch to 32 bytes for AVX2 if we're going through the temp buffer path.
|
||||
pitch = Common::AlignUpPow2(pitch, 32);
|
||||
pitch = VectorAlign(pitch);
|
||||
|
||||
u8* buff = s_unswizzle_buffer;
|
||||
rtx(mem, off, block_rect, buff, pitch, TEXA);
|
||||
|
||||
@@ -123,6 +123,8 @@ public:
|
||||
bool is_replacement;
|
||||
};
|
||||
|
||||
using HashCacheMap = std::unordered_map<HashCacheKey, HashCacheEntry, HashCacheKeyHash>;
|
||||
|
||||
class Surface : public GSAlignedClass<32>
|
||||
{
|
||||
protected:
|
||||
@@ -374,7 +376,7 @@ protected:
|
||||
PaletteMap m_palette_map;
|
||||
SourceMap m_src;
|
||||
u64 m_source_memory_usage = 0;
|
||||
std::unordered_map<HashCacheKey, HashCacheEntry, HashCacheKeyHash> m_hash_cache;
|
||||
HashCacheMap m_hash_cache;
|
||||
u64 m_hash_cache_memory_usage = 0;
|
||||
u64 m_hash_cache_replacement_memory_usage = 0;
|
||||
|
||||
@@ -402,6 +404,8 @@ protected:
|
||||
bool PrepareDownloadTexture(u32 width, u32 height, GSTexture::Format format, std::unique_ptr<GSDownloadTexture>* tex);
|
||||
|
||||
HashCacheEntry* LookupHashCache(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, bool& paltex, const u32* clut, const GSVector2i* lod, SourceRegion region);
|
||||
void RemoveFromHashCache(HashCacheMap::iterator it);
|
||||
void AgeHashCache();
|
||||
|
||||
static void PreloadTexture(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, SourceRegion region, GSLocalMemory& mem, bool paltex, GSTexture* tex, u32 level);
|
||||
static HashType HashTexture(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, SourceRegion region);
|
||||
@@ -440,8 +444,8 @@ public:
|
||||
bool is_frame = false, bool is_clear = false, bool preload = GSConfig.PreloadFrameWithGSData);
|
||||
Target* LookupDisplayTarget(GIFRegTEX0 TEX0, const GSVector2i& size, float scale);
|
||||
|
||||
/// Looks up a target in the cache, and only returns it if the BP/BW/PSM match exactly.
|
||||
Target* GetExactTarget(u32 BP, u32 BW, u32 PSM) const;
|
||||
/// Looks up a target in the cache, and only returns it if the BP/BW match exactly.
|
||||
Target* GetExactTarget(u32 BP, u32 BW, int type);
|
||||
Target* GetTargetWithSharedBits(u32 BP, u32 PSM) const;
|
||||
|
||||
GSVector2i GetTargetSize(u32 bp, u32 fbw, u32 psm, s32 min_width, s32 min_height);
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
|
||||
<Type Name="GSTextureCache::Target">
|
||||
<DisplayString Condition="m_type == 0">{{ RT @ BP={m_TEX0.TBP0,X}-{m_end_block,X} BW={m_TEX0.TBW} PSM={m_TEX0.PSM,X} {m_unscaled_size.x}x{m_unscaled_size.y} {m_valid.z},{m_valid.w} }}</DisplayString>
|
||||
<DisplayString Condition="m_type == 1">{{ Depth @ BP={m_TEX0.TBP0,X}-{m_end_block,X} BW={m_TEX0.TBW} PSM={m_TEX0.PSM,X} {m_unscaled_size.x}x{m_unscaled_size.y} {m_valid.z},{m_valid.w} }}</DisplayString>
|
||||
</Type>
|
||||
</AutoVisualizer>
|
||||
@@ -209,6 +209,7 @@ std::optional<TextureName> GSTextureReplacements::ParseReplacementName(const std
|
||||
&ret.bits, &extension_dot) == 4 &&
|
||||
extension_dot == '.')
|
||||
{
|
||||
ret.CLUTHash = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -225,6 +226,7 @@ std::optional<TextureName> GSTextureReplacements::ParseReplacementName(const std
|
||||
3 &&
|
||||
extension_dot == '.')
|
||||
{
|
||||
ret.CLUTHash = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
@@ -378,7 +378,7 @@ public:
|
||||
MRCOwned<id<MTLFunction>> LoadShader(NSString* name);
|
||||
MRCOwned<id<MTLRenderPipelineState>> MakePipeline(MTLRenderPipelineDescriptor* desc, id<MTLFunction> vertex, id<MTLFunction> fragment, NSString* name);
|
||||
MRCOwned<id<MTLComputePipelineState>> MakeComputePipeline(id<MTLFunction> compute, NSString* name);
|
||||
bool Create(const WindowInfo& wi, VsyncMode vsync) override;
|
||||
bool Create() override;
|
||||
void Destroy() override;
|
||||
|
||||
void AttachSurfaceOnMainThread();
|
||||
@@ -387,10 +387,8 @@ public:
|
||||
RenderAPI GetRenderAPI() const override;
|
||||
bool HasSurface() const override;
|
||||
void DestroySurface() override;
|
||||
bool ChangeWindow(const WindowInfo& wi) override;
|
||||
bool UpdateWindow() override;
|
||||
bool SupportsExclusiveFullscreen() const override;
|
||||
bool IsExclusiveFullscreen() override;
|
||||
bool SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override;
|
||||
std::string GetDriverInfo() const override;
|
||||
|
||||
void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override;
|
||||
|
||||
@@ -371,6 +371,7 @@ void GSDeviceMTL::EndRenderPass()
|
||||
if (m_current_render.encoder)
|
||||
{
|
||||
EndDebugGroup(m_current_render.encoder);
|
||||
g_perfmon.Put(GSPerfMon::RenderPasses, 1);
|
||||
if (m_spin_timer)
|
||||
[m_current_render.encoder updateFence:m_spin_fence afterStages:MTLRenderStageFragment];
|
||||
[m_current_render.encoder endEncoding];
|
||||
@@ -763,9 +764,9 @@ static MRCOwned<id<MTLBuffer>> CreatePrivateBufferWithContent(
|
||||
return actual;
|
||||
}
|
||||
|
||||
bool GSDeviceMTL::Create(const WindowInfo& wi, VsyncMode vsync)
|
||||
bool GSDeviceMTL::Create()
|
||||
{ @autoreleasepool {
|
||||
if (!GSDevice::Create(wi, vsync))
|
||||
if (!GSDevice::Create())
|
||||
return false;
|
||||
|
||||
NSString* ns_adapter_name = [NSString stringWithUTF8String:EmuConfig.GS.Adapter.c_str()];
|
||||
@@ -809,11 +810,16 @@ bool GSDeviceMTL::Create(const WindowInfo& wi, VsyncMode vsync)
|
||||
|
||||
if (m_dev.IsOk() && m_queue)
|
||||
{
|
||||
// This is a little less than ideal, pinging back and forward between threads, but we don't really
|
||||
// have any other option, because Qt uses a blocking queued connection for window acquire.
|
||||
if (!AcquireWindow(true))
|
||||
return false;
|
||||
|
||||
OnMainThread([this]
|
||||
{
|
||||
AttachSurfaceOnMainThread();
|
||||
});
|
||||
SetVSync(vsync);
|
||||
[m_layer setDisplaySyncEnabled:m_vsync_mode != VsyncMode::Off];
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1176,20 +1182,21 @@ void GSDeviceMTL::DestroySurface()
|
||||
m_layer = nullptr;
|
||||
}
|
||||
|
||||
bool GSDeviceMTL::ChangeWindow(const WindowInfo& wi)
|
||||
bool GSDeviceMTL::UpdateWindow()
|
||||
{
|
||||
OnMainThread([this, &wi]
|
||||
{
|
||||
DetachSurfaceOnMainThread();
|
||||
m_window_info = wi;
|
||||
AttachSurfaceOnMainThread();
|
||||
});
|
||||
DestroySurface();
|
||||
|
||||
if (!AcquireWindow(false))
|
||||
return false;
|
||||
|
||||
if (m_window_info.type == WindowInfo::Type::Surfaceless)
|
||||
return true;
|
||||
|
||||
OnMainThread([this] { AttachSurfaceOnMainThread(); });
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GSDeviceMTL::SupportsExclusiveFullscreen() const { return false; }
|
||||
bool GSDeviceMTL::IsExclusiveFullscreen() { return false; }
|
||||
bool GSDeviceMTL::SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) { return false; }
|
||||
|
||||
std::string GSDeviceMTL::GetDriverInfo() const
|
||||
{ @autoreleasepool {
|
||||
@@ -1240,53 +1247,51 @@ static bool s_capture_next = false;
|
||||
|
||||
GSDevice::PresentResult GSDeviceMTL::BeginPresent(bool frame_skip)
|
||||
{ @autoreleasepool {
|
||||
GSDeviceMTL* dev = static_cast<GSDeviceMTL*>(g_gs_device.get());
|
||||
if (dev && m_capture_start_frame && dev->FrameNo() == m_capture_start_frame)
|
||||
if (m_capture_start_frame && FrameNo() == m_capture_start_frame)
|
||||
s_capture_next = true;
|
||||
if (frame_skip || m_window_info.type == WindowInfo::Type::Surfaceless || !g_gs_device)
|
||||
{
|
||||
ImGui::EndFrame();
|
||||
return PresentResult::FrameSkipped;
|
||||
}
|
||||
id<MTLCommandBuffer> buf = dev->GetRenderCmdBuf();
|
||||
id<MTLCommandBuffer> buf = GetRenderCmdBuf();
|
||||
m_current_drawable = MRCRetain([m_layer nextDrawable]);
|
||||
dev->EndRenderPass();
|
||||
EndRenderPass();
|
||||
if (!m_current_drawable)
|
||||
{
|
||||
[buf pushDebugGroup:@"Present Skipped"];
|
||||
[buf popDebugGroup];
|
||||
dev->FlushEncoders();
|
||||
FlushEncoders();
|
||||
ImGui::EndFrame();
|
||||
return PresentResult::FrameSkipped;
|
||||
}
|
||||
[m_pass_desc colorAttachments][0].texture = [m_current_drawable texture];
|
||||
id<MTLRenderCommandEncoder> enc = [buf renderCommandEncoderWithDescriptor:m_pass_desc];
|
||||
[enc setLabel:@"Present"];
|
||||
dev->m_current_render.encoder = MRCRetain(enc);
|
||||
m_current_render.encoder = MRCRetain(enc);
|
||||
return PresentResult::OK;
|
||||
}}
|
||||
|
||||
void GSDeviceMTL::EndPresent()
|
||||
{ @autoreleasepool {
|
||||
GSDeviceMTL* dev = static_cast<GSDeviceMTL*>(g_gs_device.get());
|
||||
pxAssertDev(dev && dev->m_current_render.encoder && dev->m_current_render_cmdbuf, "BeginPresent cmdbuf was destroyed");
|
||||
pxAssertDev(m_current_render.encoder && m_current_render_cmdbuf, "BeginPresent cmdbuf was destroyed");
|
||||
ImGui::Render();
|
||||
dev->RenderImGui(ImGui::GetDrawData());
|
||||
dev->EndRenderPass();
|
||||
RenderImGui(ImGui::GetDrawData());
|
||||
EndRenderPass();
|
||||
if (m_current_drawable)
|
||||
{
|
||||
const bool use_present_drawable = m_use_present_drawable == UsePresentDrawable::Always ||
|
||||
(m_use_present_drawable == UsePresentDrawable::IfVsync && m_vsync_mode != VsyncMode::Off);
|
||||
|
||||
if (use_present_drawable)
|
||||
[dev->m_current_render_cmdbuf presentDrawable:m_current_drawable];
|
||||
[m_current_render_cmdbuf presentDrawable:m_current_drawable];
|
||||
else
|
||||
[dev->m_current_render_cmdbuf addScheduledHandler:[drawable = std::move(m_current_drawable)](id<MTLCommandBuffer>){
|
||||
[m_current_render_cmdbuf addScheduledHandler:[drawable = std::move(m_current_drawable)](id<MTLCommandBuffer>){
|
||||
[drawable present];
|
||||
}];
|
||||
}
|
||||
dev->FlushEncoders();
|
||||
dev->FrameCompleted();
|
||||
FlushEncoders();
|
||||
FrameCompleted();
|
||||
m_current_drawable = nullptr;
|
||||
if (m_capture_start_frame)
|
||||
{
|
||||
@@ -1340,6 +1345,9 @@ void GSDeviceMTL::EndPresent()
|
||||
|
||||
void GSDeviceMTL::SetVSync(VsyncMode mode)
|
||||
{
|
||||
if (m_vsync_mode == mode)
|
||||
return;
|
||||
|
||||
[m_layer setDisplaySyncEnabled:mode != VsyncMode::Off];
|
||||
m_vsync_mode = mode;
|
||||
}
|
||||
@@ -2178,7 +2186,7 @@ void GSDeviceMTL::SendHWDraw(GSHWDrawConfig& config, id<MTLRenderCommandEncoder>
|
||||
textureBarrier(enc);
|
||||
[enc drawIndexedPrimitives:topology
|
||||
indexCount:count
|
||||
indexType:MTLIndexTypeUInt32
|
||||
indexType:MTLIndexTypeUInt16
|
||||
indexBuffer:buffer
|
||||
indexBufferOffset:off + p * sizeof(*config.indices)];
|
||||
p += count;
|
||||
@@ -2200,7 +2208,7 @@ void GSDeviceMTL::SendHWDraw(GSHWDrawConfig& config, id<MTLRenderCommandEncoder>
|
||||
textureBarrier(enc);
|
||||
[enc drawIndexedPrimitives:topology
|
||||
indexCount:config.indices_per_prim
|
||||
indexType:MTLIndexTypeUInt32
|
||||
indexType:MTLIndexTypeUInt16
|
||||
indexBuffer:buffer
|
||||
indexBufferOffset:off + p * sizeof(*config.indices)];
|
||||
}
|
||||
@@ -2217,7 +2225,7 @@ void GSDeviceMTL::SendHWDraw(GSHWDrawConfig& config, id<MTLRenderCommandEncoder>
|
||||
|
||||
[enc drawIndexedPrimitives:topology
|
||||
indexCount:config.nindices
|
||||
indexType:MTLIndexTypeUInt32
|
||||
indexType:MTLIndexTypeUInt16
|
||||
indexBuffer:buffer
|
||||
indexBufferOffset:off];
|
||||
|
||||
|
||||
@@ -85,9 +85,13 @@ void GSDeviceOGL::SetVSync(VsyncMode mode)
|
||||
m_vsync_mode = mode;
|
||||
}
|
||||
|
||||
bool GSDeviceOGL::Create(const WindowInfo& wi, VsyncMode vsync)
|
||||
bool GSDeviceOGL::Create()
|
||||
{
|
||||
if (!GSDevice::Create(wi, vsync))
|
||||
if (!GSDevice::Create())
|
||||
return false;
|
||||
|
||||
// GL is a pain and needs the window super early to create the context.
|
||||
if (!AcquireWindow(true))
|
||||
return false;
|
||||
|
||||
// We need at least GL3.3.
|
||||
@@ -95,7 +99,7 @@ bool GSDeviceOGL::Create(const WindowInfo& wi, VsyncMode vsync)
|
||||
{GL::Context::Profile::Core, 4, 5}, {GL::Context::Profile::Core, 4, 4}, {GL::Context::Profile::Core, 4, 3},
|
||||
{GL::Context::Profile::Core, 4, 2}, {GL::Context::Profile::Core, 4, 1}, {GL::Context::Profile::Core, 4, 0},
|
||||
{GL::Context::Profile::Core, 3, 3}};
|
||||
m_gl_context = GL::Context::Create(wi, version_list);
|
||||
m_gl_context = GL::Context::Create(m_window_info, version_list);
|
||||
if (!m_gl_context)
|
||||
{
|
||||
Console.Error("Failed to create any GL context");
|
||||
@@ -109,6 +113,10 @@ bool GSDeviceOGL::Create(const WindowInfo& wi, VsyncMode vsync)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Render a frame as soon as possible to clear out whatever was previously being displayed.
|
||||
if (m_window_info.type != WindowInfo::Type::Surfaceless)
|
||||
RenderBlankFrame();
|
||||
|
||||
if (!GLLoader::check_gl_requirements())
|
||||
return false;
|
||||
|
||||
@@ -163,7 +171,7 @@ bool GSDeviceOGL::Create(const WindowInfo& wi, VsyncMode vsync)
|
||||
GLint max_vertex_ssbos = 0;
|
||||
glGetIntegerv(GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS, &max_vertex_ssbos);
|
||||
DevCon.WriteLn("GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS: %d", max_vertex_ssbos);
|
||||
m_features.vs_expand = (max_vertex_ssbos > 0);
|
||||
m_features.vs_expand = (max_vertex_ssbos > 0 && GLAD_GL_ARB_gpu_shader5);
|
||||
}
|
||||
if (!m_features.vs_expand)
|
||||
Console.Warning("Vertex expansion is not supported. This will reduce performance.");
|
||||
@@ -651,11 +659,16 @@ void GSDeviceOGL::DestroyResources()
|
||||
glDeleteFramebuffers(1, &m_fbo_write);
|
||||
}
|
||||
|
||||
bool GSDeviceOGL::ChangeWindow(const WindowInfo& new_wi)
|
||||
bool GSDeviceOGL::UpdateWindow()
|
||||
{
|
||||
pxAssert(m_gl_context);
|
||||
|
||||
if (!m_gl_context->ChangeSurface(new_wi))
|
||||
DestroySurface();
|
||||
|
||||
if (!AcquireWindow(false))
|
||||
return false;
|
||||
|
||||
if (!m_gl_context->ChangeSurface(m_window_info))
|
||||
{
|
||||
Console.Error("Failed to change surface");
|
||||
return false;
|
||||
@@ -663,11 +676,13 @@ bool GSDeviceOGL::ChangeWindow(const WindowInfo& new_wi)
|
||||
|
||||
m_window_info = m_gl_context->GetWindowInfo();
|
||||
|
||||
if (new_wi.type != WindowInfo::Type::Surfaceless)
|
||||
if (m_window_info.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));
|
||||
|
||||
RenderBlankFrame();
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -691,16 +706,6 @@ bool GSDeviceOGL::SupportsExclusiveFullscreen() const
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GSDeviceOGL::IsExclusiveFullscreen()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GSDeviceOGL::SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void GSDeviceOGL::DestroySurface()
|
||||
{
|
||||
m_window_info = {};
|
||||
@@ -876,8 +881,8 @@ void GSDeviceOGL::DrawPrimitive()
|
||||
void GSDeviceOGL::DrawIndexedPrimitive()
|
||||
{
|
||||
g_perfmon.Put(GSPerfMon::DrawCalls, 1);
|
||||
glDrawElementsBaseVertex(m_draw_topology, static_cast<u32>(m_index.count), GL_UNSIGNED_INT,
|
||||
reinterpret_cast<void*>(static_cast<u32>(m_index.start) * sizeof(u32)), static_cast<GLint>(m_vertex.start));
|
||||
glDrawElementsBaseVertex(m_draw_topology, static_cast<u32>(m_index.count), GL_UNSIGNED_SHORT,
|
||||
reinterpret_cast<void*>(static_cast<u32>(m_index.start) * sizeof(u16)), static_cast<GLint>(m_vertex.start));
|
||||
}
|
||||
|
||||
void GSDeviceOGL::DrawIndexedPrimitive(int offset, int count)
|
||||
@@ -885,8 +890,8 @@ void GSDeviceOGL::DrawIndexedPrimitive(int offset, int count)
|
||||
//ASSERT(offset + count <= (int)m_index.count);
|
||||
|
||||
g_perfmon.Put(GSPerfMon::DrawCalls, 1);
|
||||
glDrawElementsBaseVertex(m_draw_topology, count, GL_UNSIGNED_INT,
|
||||
reinterpret_cast<void*>((static_cast<u32>(m_index.start) + static_cast<u32>(offset)) * sizeof(u32)),
|
||||
glDrawElementsBaseVertex(m_draw_topology, count, GL_UNSIGNED_SHORT,
|
||||
reinterpret_cast<void*>((static_cast<u32>(m_index.start) + static_cast<u32>(offset)) * sizeof(u16)),
|
||||
static_cast<GLint>(m_vertex.start));
|
||||
}
|
||||
|
||||
@@ -1134,7 +1139,9 @@ std::string GSDeviceOGL::GenGlslHeader(const std::string_view& entry, GLenum typ
|
||||
else
|
||||
{
|
||||
header = "#version 330 core\n";
|
||||
header += "#extension GL_ARB_shading_language_420pack: require\n";
|
||||
header += "#extension GL_ARB_shading_language_420pack : require\n";
|
||||
if (GLAD_GL_ARB_gpu_shader5)
|
||||
header += "#extension GL_ARB_gpu_shader5 : require\n";
|
||||
if (m_features.vs_expand)
|
||||
header += "#extension GL_ARB_shader_storage_buffer_object: require\n";
|
||||
}
|
||||
@@ -1548,15 +1555,15 @@ void GSDeviceOGL::DrawMultiStretchRects(
|
||||
void GSDeviceOGL::DoMultiStretchRects(const MultiStretchRect* rects, u32 num_rects, const GSVector2& ds)
|
||||
{
|
||||
const u32 vertex_reserve_size = num_rects * 4 * sizeof(GSVertexPT1);
|
||||
const u32 index_reserve_size = num_rects * 6 * sizeof(u32);
|
||||
const u32 index_reserve_size = num_rects * 6 * sizeof(u16);
|
||||
auto vertex_map = m_vertex_stream_buffer->Map(sizeof(GSVertexPT1), vertex_reserve_size);
|
||||
auto index_map = m_index_stream_buffer->Map(sizeof(u32), index_reserve_size);
|
||||
auto index_map = m_index_stream_buffer->Map(sizeof(u16), index_reserve_size);
|
||||
m_vertex.start = vertex_map.index_aligned;
|
||||
m_index.start = index_map.index_aligned;
|
||||
|
||||
// Don't use primitive restart here, it ends up slower on some drivers.
|
||||
GSVertexPT1* verts = reinterpret_cast<GSVertexPT1*>(vertex_map.pointer);
|
||||
u32* idx = reinterpret_cast<u32*>(index_map.pointer);
|
||||
u16* idx = reinterpret_cast<u16*>(index_map.pointer);
|
||||
u32 icount = 0;
|
||||
u32 vcount = 0;
|
||||
for (u32 i = 0; i < num_rects; i++)
|
||||
@@ -1587,7 +1594,7 @@ void GSDeviceOGL::DoMultiStretchRects(const MultiStretchRect* rects, u32 num_rec
|
||||
m_vertex.count = vcount;
|
||||
m_index.count = icount;
|
||||
m_vertex_stream_buffer->Unmap(vcount * sizeof(GSVertexPT1));
|
||||
m_index_stream_buffer->Unmap(icount * sizeof(u32));
|
||||
m_index_stream_buffer->Unmap(icount * sizeof(u16));
|
||||
|
||||
PSSetShaderResource(0, rects[0].src);
|
||||
PSSetSamplerState(rects[0].linear ? m_convert.ln : m_convert.pt);
|
||||
@@ -1807,8 +1814,8 @@ void GSDeviceOGL::IASetVertexBuffer(const void* vertices, size_t count)
|
||||
|
||||
void GSDeviceOGL::IASetIndexBuffer(const void* index, size_t count)
|
||||
{
|
||||
const u32 size = static_cast<u32>(count) * sizeof(u32);
|
||||
auto res = m_index_stream_buffer->Map(sizeof(u32), size);
|
||||
const u32 size = static_cast<u32>(count) * sizeof(u16);
|
||||
auto res = m_index_stream_buffer->Map(sizeof(u16), size);
|
||||
m_index.start = res.index_aligned;
|
||||
m_index.count = count;
|
||||
std::memcpy(res.pointer, index, size);
|
||||
@@ -1999,18 +2006,7 @@ void GSDeviceOGL::RenderImGui()
|
||||
m_vertex_stream_buffer->Unmap(size);
|
||||
}
|
||||
|
||||
// Bit awkward, because this is using 16-bit indices, not 32-bit.
|
||||
u32 index_start;
|
||||
{
|
||||
static_assert(sizeof(ImDrawIdx) == sizeof(u16));
|
||||
|
||||
const u32 size = static_cast<u32>(cmd_list->IdxBuffer.Size) * sizeof(ImDrawIdx);
|
||||
auto res = m_index_stream_buffer->Map(sizeof(u16), size);
|
||||
index_start = res.index_aligned;
|
||||
std::memcpy(res.pointer, cmd_list->IdxBuffer.Data, size);
|
||||
m_index_stream_buffer->Unmap(size);
|
||||
m_index_stream_buffer->Bind();
|
||||
}
|
||||
IASetIndexBuffer(cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size);
|
||||
|
||||
for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
|
||||
{
|
||||
@@ -2038,7 +2034,7 @@ void GSDeviceOGL::RenderImGui()
|
||||
}
|
||||
|
||||
glDrawElementsBaseVertex(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, GL_UNSIGNED_SHORT,
|
||||
(void*)(intptr_t)((pcmd->IdxOffset + index_start) * sizeof(ImDrawIdx)), pcmd->VtxOffset + vertex_start);
|
||||
(void*)(intptr_t)((pcmd->IdxOffset + m_index.start) * sizeof(ImDrawIdx)), pcmd->VtxOffset + vertex_start);
|
||||
}
|
||||
|
||||
g_perfmon.Put(GSPerfMon::DrawCalls, cmd_list->CmdBuffer.Size);
|
||||
@@ -2048,6 +2044,17 @@ void GSDeviceOGL::RenderImGui()
|
||||
glScissor(GLState::scissor.x, GLState::scissor.y, GLState::scissor.width(), GLState::scissor.height());
|
||||
}
|
||||
|
||||
void GSDeviceOGL::RenderBlankFrame()
|
||||
{
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
m_gl_context->SwapBuffers();
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, GLState::fbo);
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
}
|
||||
|
||||
void GSDeviceOGL::OMAttachRt(GSTextureOGL* rt)
|
||||
{
|
||||
if (rt)
|
||||
@@ -2165,6 +2172,8 @@ void GSDeviceOGL::OMSetRenderTargets(GSTexture* rt, GSTexture* ds, const GSVecto
|
||||
GSTextureOGL* RT = static_cast<GSTextureOGL*>(rt);
|
||||
GSTextureOGL* DS = static_cast<GSTextureOGL*>(ds);
|
||||
|
||||
g_perfmon.Put(GSPerfMon::RenderPasses, static_cast<double>(GLState::rt != RT || GLState::ds != DS));
|
||||
|
||||
OMSetFBO(m_fbo);
|
||||
if (rt)
|
||||
{
|
||||
@@ -2384,7 +2393,7 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
|
||||
psel.vs = config.vs;
|
||||
psel.ps.key_hi = config.ps.key_hi;
|
||||
psel.ps.key_lo = config.ps.key_lo;
|
||||
psel.pad = 0;
|
||||
std::memset(psel.pad, 0, sizeof(psel.pad));
|
||||
|
||||
SetupPipeline(psel);
|
||||
|
||||
@@ -2431,21 +2440,23 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
|
||||
OMSetBlendState(config.blend.enable, s_gl_blend_factors[config.blend.src_factor],
|
||||
s_gl_blend_factors[config.blend.dst_factor], s_gl_blend_ops[config.blend.op],
|
||||
config.blend.constant_enable, config.blend.constant);
|
||||
OMSetColorMaskState(config.colormask);
|
||||
|
||||
// avoid changing framebuffer just to switch from rt+depth to rt and vice versa
|
||||
GSTexture* draw_rt = hdr_rt ? hdr_rt : config.rt;
|
||||
GSTexture* draw_ds = config.ds;
|
||||
if (!draw_ds && GLState::ds && GLState::rt == draw_rt && config.tex != GLState::ds &&
|
||||
GLState::ds->GetSize() == draw_rt->GetSize())
|
||||
if (!draw_rt && GLState::rt && GLState::ds == draw_ds && config.tex != GLState::rt &&
|
||||
GLState::rt->GetSize() == draw_ds->GetSize())
|
||||
{
|
||||
draw_rt = GLState::rt;
|
||||
}
|
||||
else if (!draw_ds && GLState::ds && GLState::rt == draw_rt && config.tex != GLState::ds &&
|
||||
GLState::ds->GetSize() == draw_rt->GetSize())
|
||||
{
|
||||
// should already be always-pass.
|
||||
draw_ds = GLState::ds;
|
||||
config.depth.ztst = ZTST_ALWAYS;
|
||||
config.depth.zwe = false;
|
||||
}
|
||||
|
||||
OMSetRenderTargets(draw_rt, draw_ds, &config.scissor);
|
||||
OMSetColorMaskState(config.colormask);
|
||||
SetupOM(config.depth);
|
||||
|
||||
SendHWDraw(config, psel.ps.IsFeedbackLoop());
|
||||
|
||||
@@ -130,10 +130,10 @@ public:
|
||||
{
|
||||
PSSelector ps;
|
||||
VSSelector vs;
|
||||
u16 pad;
|
||||
u8 pad[3];
|
||||
|
||||
__fi bool operator==(const ProgramSelector& p) const { return (std::memcmp(this, &p, sizeof(*this)) == 0); }
|
||||
__fi bool operator!=(const ProgramSelector& p) const { return (std::memcmp(this, &p, sizeof(*this)) != 0); }
|
||||
__fi bool operator==(const ProgramSelector& p) const { return BitEqual(*this, p); }
|
||||
__fi bool operator!=(const ProgramSelector& p) const { return !BitEqual(*this, p); }
|
||||
};
|
||||
static_assert(sizeof(ProgramSelector) == 16, "Program selector is 16 bytes");
|
||||
|
||||
@@ -262,6 +262,7 @@ private:
|
||||
|
||||
bool CreateImGuiProgram();
|
||||
void RenderImGui();
|
||||
void RenderBlankFrame();
|
||||
|
||||
void OMAttachRt(GSTextureOGL* rt = nullptr);
|
||||
void OMAttachDs(GSTextureOGL* ds = nullptr);
|
||||
@@ -286,14 +287,12 @@ public:
|
||||
RenderAPI GetRenderAPI() const override;
|
||||
bool HasSurface() const override;
|
||||
|
||||
bool Create(const WindowInfo& wi, VsyncMode vsync) override;
|
||||
bool Create() override;
|
||||
void Destroy() override;
|
||||
|
||||
bool ChangeWindow(const WindowInfo& new_wi) override;
|
||||
bool UpdateWindow() override;
|
||||
void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override;
|
||||
bool SupportsExclusiveFullscreen() const override;
|
||||
bool IsExclusiveFullscreen() override;
|
||||
bool SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override;
|
||||
void DestroySurface() override;
|
||||
std::string GetDriverInfo() const override;
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "GS/Renderers/OpenGL/GSDeviceOGL.h"
|
||||
#include "GS/Renderers/OpenGL/GSTextureOGL.h"
|
||||
#include "GS/Renderers/OpenGL/GLState.h"
|
||||
#include "GS/GSExtra.h"
|
||||
#include "GS/GSPerfMon.h"
|
||||
#include "GS/GSPng.h"
|
||||
#include "GS/GSGL.h"
|
||||
@@ -476,7 +477,7 @@ std::unique_ptr<GSDownloadTextureOGL> GSDownloadTextureOGL::Create(u32 width, u3
|
||||
}
|
||||
|
||||
// Fallback to glReadPixels() + CPU buffer.
|
||||
u8* cpu_buffer = static_cast<u8*>(_aligned_malloc(buffer_size, 32));
|
||||
u8* cpu_buffer = static_cast<u8*>(_aligned_malloc(buffer_size, VECTOR_ALIGNMENT));
|
||||
if (!cpu_buffer)
|
||||
return {};
|
||||
|
||||
|
||||
@@ -161,7 +161,7 @@ typedef GSVector4 VectorF;
|
||||
#define LOCAL_STEP local.d4
|
||||
#endif
|
||||
|
||||
void GSDrawScanline::CSetupPrim(const GSVertexSW* vertex, const u32* index, const GSVertexSW& dscan, GSScanlineLocalData& local)
|
||||
void GSDrawScanline::CSetupPrim(const GSVertexSW* vertex, const u16* index, const GSVertexSW& dscan, GSScanlineLocalData& local)
|
||||
{
|
||||
const GSScanlineGlobalData& global = GlobalFromLocal(local);
|
||||
GSScanlineSelector sel = global.sel;
|
||||
@@ -451,10 +451,14 @@ __ri static void WritePixel(const T& src, int addr, int i, u32 psm, const GSScan
|
||||
}
|
||||
|
||||
void GSDrawScanline::CDrawScanline(int pixels, int left, int top, const GSVertexSW& scan, GSScanlineLocalData& local)
|
||||
{
|
||||
CDrawScanline(pixels, left, top, scan, local, GlobalFromLocal(local).sel);
|
||||
}
|
||||
|
||||
__ri void GSDrawScanline::CDrawScanline(int pixels, int left, int top, const GSVertexSW& scan, GSScanlineLocalData& local, GSScanlineSelector sel)
|
||||
{
|
||||
const GSScanlineGlobalData& global = GlobalFromLocal(local);
|
||||
|
||||
GSScanlineSelector sel = global.sel;
|
||||
constexpr int vlen = sizeof(VectorF) / sizeof(float);
|
||||
|
||||
#if _M_SSE < 0x501
|
||||
@@ -1729,17 +1733,10 @@ void GSDrawScanline::CDrawScanline(int pixels, int left, int top, const GSVertex
|
||||
|
||||
void GSDrawScanline::CDrawEdge(int pixels, int left, int top, const GSVertexSW& scan, GSScanlineLocalData& local)
|
||||
{
|
||||
// This sucks. But so does not jitting!
|
||||
const GSScanlineGlobalData* old_gd = local.gd;
|
||||
GSScanlineGlobalData gd;
|
||||
std::memcpy(&gd, &local.gd, sizeof(gd));
|
||||
gd.sel.zwrite = 0;
|
||||
gd.sel.edge = 1;
|
||||
local.gd = &gd;
|
||||
|
||||
CDrawScanline(pixels, left, top, scan, local);
|
||||
|
||||
local.gd = old_gd;
|
||||
GSScanlineSelector sel = local.gd->sel;
|
||||
sel.zwrite = 0;
|
||||
sel.edge = 1;
|
||||
CDrawScanline(pixels, left, top, scan, local, sel);
|
||||
}
|
||||
|
||||
template <class T, bool masked>
|
||||
|
||||
@@ -38,7 +38,7 @@ public:
|
||||
~GSDrawScanline() override;
|
||||
|
||||
/// Function pointer types which we call back into.
|
||||
using SetupPrimPtr = void(*)(const GSVertexSW* vertex, const u32* index, const GSVertexSW& dscan, GSScanlineLocalData& local);
|
||||
using SetupPrimPtr = void(*)(const GSVertexSW* vertex, const u16* index, const GSVertexSW& dscan, GSScanlineLocalData& local);
|
||||
using DrawScanlinePtr = void(*)(int pixels, int left, int top, const GSVertexSW& scan, GSScanlineLocalData& local);
|
||||
|
||||
/// Flushes the code cache, forcing everything to be recompiled.
|
||||
@@ -60,9 +60,10 @@ private:
|
||||
GSCodeGeneratorFunctionMap<GSSetupPrimCodeGenerator, u64, SetupPrimPtr> m_sp_map;
|
||||
GSCodeGeneratorFunctionMap<GSDrawScanlineCodeGenerator, u64, DrawScanlinePtr> m_ds_map;
|
||||
|
||||
static void CSetupPrim(const GSVertexSW* vertex, const u32* index, const GSVertexSW& dscan, GSScanlineLocalData& local);
|
||||
static void CSetupPrim(const GSVertexSW* vertex, const u16* index, const GSVertexSW& dscan, GSScanlineLocalData& local);
|
||||
static void CDrawScanline(int pixels, int left, int top, const GSVertexSW& scan, GSScanlineLocalData& local);
|
||||
static void CDrawEdge(int pixels, int left, int top, const GSVertexSW& scan, GSScanlineLocalData& local);
|
||||
__ri static void CDrawScanline(int pixels, int left, int top, const GSVertexSW& scan, GSScanlineLocalData& local, GSScanlineSelector sel);
|
||||
};
|
||||
|
||||
MULTI_ISA_UNSHARED_END
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "GSDrawScanlineCodeGenerator.all.h"
|
||||
#include "GS/Renderers/Common/GSFunctionMap.h"
|
||||
#include "GSVertexSW.h"
|
||||
#include "common/Perf.h"
|
||||
|
||||
MULTI_ISA_UNSHARED_IMPL;
|
||||
using namespace Xbyak;
|
||||
@@ -590,6 +591,8 @@ L("exit");
|
||||
if (isYmm)
|
||||
vzeroupper();
|
||||
ret();
|
||||
|
||||
Perf::any.RegisterKey(actual.getCode(), actual.getSize(), "GSDrawScanline_", m_sel.key);
|
||||
}
|
||||
|
||||
/// Inputs: a0=pixels, a1=left, a2[x64]=top, a3[x64]=v
|
||||
|
||||
@@ -91,7 +91,8 @@ GSDrawScanlineCodeGenerator::GSDrawScanlineCodeGenerator(u64 key, void* code, si
|
||||
|
||||
if (shouldUseCDrawScanline(key))
|
||||
{
|
||||
jmp(reinterpret_cast<const void*>(&GSDrawScanline::CDrawScanline));
|
||||
jmp(reinterpret_cast<const void*>(static_cast<void (*)(int, int, int, const GSVertexSW&, GSScanlineLocalData&)>(
|
||||
&GSDrawScanline::CDrawScanline)));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ GSRasterizer::GSRasterizer(GSDrawScanline* ds, int id, int threads)
|
||||
|
||||
m_thread_height = compute_best_thread_height(threads);
|
||||
|
||||
m_edge.buff = static_cast<GSVertexSW*>(_aligned_malloc(sizeof(GSVertexSW) * 2048, 32));
|
||||
m_edge.buff = static_cast<GSVertexSW*>(_aligned_malloc(sizeof(GSVertexSW) * 2048, VECTOR_ALIGNMENT));
|
||||
m_edge.count = 0;
|
||||
if (!m_edge.buff)
|
||||
throw std::bad_alloc();
|
||||
@@ -154,10 +154,10 @@ void GSRasterizer::Draw(GSRasterizerData& data)
|
||||
const GSVertexSW* vertex = data.vertex;
|
||||
const GSVertexSW* vertex_end = data.vertex + data.vertex_count;
|
||||
|
||||
const u32* index = data.index;
|
||||
const u32* index_end = data.index + data.index_count;
|
||||
const u16* index = data.index;
|
||||
const u16* index_end = data.index + data.index_count;
|
||||
|
||||
u32 tmp_index[] = {0, 1, 2};
|
||||
static constexpr u16 tmp_index[] = {0, 1, 2};
|
||||
|
||||
bool scissor_test = !data.bbox.eq(data.bbox.rintersect(data.scissor));
|
||||
|
||||
@@ -261,7 +261,7 @@ void GSRasterizer::Draw(GSRasterizerData& data)
|
||||
}
|
||||
|
||||
template <bool scissor_test>
|
||||
void GSRasterizer::DrawPoint(const GSVertexSW* vertex, int vertex_count, const u32* index, int index_count)
|
||||
void GSRasterizer::DrawPoint(const GSVertexSW* vertex, int vertex_count, const u16* index, int index_count)
|
||||
{
|
||||
m_primcount++;
|
||||
|
||||
@@ -286,7 +286,7 @@ void GSRasterizer::DrawPoint(const GSVertexSW* vertex, int vertex_count, const u
|
||||
}
|
||||
else
|
||||
{
|
||||
u32 tmp_index[1] = {0};
|
||||
static constexpr u16 tmp_index[1] = {0};
|
||||
|
||||
for (int i = 0; i < vertex_count; i++, vertex++)
|
||||
{
|
||||
@@ -307,7 +307,7 @@ void GSRasterizer::DrawPoint(const GSVertexSW* vertex, int vertex_count, const u
|
||||
}
|
||||
}
|
||||
|
||||
void GSRasterizer::DrawLine(const GSVertexSW* vertex, const u32* index)
|
||||
void GSRasterizer::DrawLine(const GSVertexSW* vertex, const u16* index)
|
||||
{
|
||||
m_primcount++;
|
||||
|
||||
@@ -425,7 +425,7 @@ static const u8 s_ysort[8][4] =
|
||||
|
||||
#if _M_SSE >= 0x501
|
||||
|
||||
void GSRasterizer::DrawTriangle(const GSVertexSW* vertex, const u32* index)
|
||||
void GSRasterizer::DrawTriangle(const GSVertexSW* vertex, const u16* index)
|
||||
{
|
||||
m_primcount++;
|
||||
|
||||
@@ -606,7 +606,7 @@ void GSRasterizer::DrawTriangleSection(int top, int bottom, GSVertexSW2& RESTRIC
|
||||
|
||||
#else
|
||||
|
||||
void GSRasterizer::DrawTriangle(const GSVertexSW* vertex, const u32* index)
|
||||
void GSRasterizer::DrawTriangle(const GSVertexSW* vertex, const u16* index)
|
||||
{
|
||||
m_primcount++;
|
||||
|
||||
@@ -784,7 +784,7 @@ void GSRasterizer::DrawTriangleSection(int top, int bottom, GSVertexSW& RESTRICT
|
||||
|
||||
#endif
|
||||
|
||||
void GSRasterizer::DrawSprite(const GSVertexSW* vertex, const u32* index)
|
||||
void GSRasterizer::DrawSprite(const GSVertexSW* vertex, const u16* index)
|
||||
{
|
||||
m_primcount++;
|
||||
|
||||
@@ -1082,7 +1082,7 @@ void GSRasterizer::AddScanline(GSVertexSW* e, int pixels, int left, int top, con
|
||||
AddScanlineInfo(e, pixels, left, top);
|
||||
}
|
||||
|
||||
void GSRasterizer::Flush(const GSVertexSW* vertex, const u32* index, const GSVertexSW& dscan, bool edge /* = false */)
|
||||
void GSRasterizer::Flush(const GSVertexSW* vertex, const u16* index, const GSVertexSW& dscan, bool edge /* = false */)
|
||||
{
|
||||
// TODO: on win64 this could be the place where xmm6-15 are preserved (not by each DrawScanline)
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ public:
|
||||
u8* buff;
|
||||
GSVertexSW* vertex;
|
||||
int vertex_count;
|
||||
u32* index;
|
||||
u16* index;
|
||||
int index_count;
|
||||
u64 frame;
|
||||
u64 start;
|
||||
@@ -101,10 +101,10 @@ protected:
|
||||
__forceinline bool HasEdge() const { return (m_draw_edge != nullptr); }
|
||||
|
||||
template <bool scissor_test>
|
||||
void DrawPoint(const GSVertexSW* vertex, int vertex_count, const u32* index, int index_count);
|
||||
void DrawLine(const GSVertexSW* vertex, const u32* index);
|
||||
void DrawTriangle(const GSVertexSW* vertex, const u32* index);
|
||||
void DrawSprite(const GSVertexSW* vertex, const u32* index);
|
||||
void DrawPoint(const GSVertexSW* vertex, int vertex_count, const u16* index, int index_count);
|
||||
void DrawLine(const GSVertexSW* vertex, const u16* index);
|
||||
void DrawTriangle(const GSVertexSW* vertex, const u16* index);
|
||||
void DrawSprite(const GSVertexSW* vertex, const u16* index);
|
||||
|
||||
#if _M_SSE >= 0x501
|
||||
__forceinline void DrawTriangleSection(int top, int bottom, GSVertexSW2& RESTRICT edge, const GSVertexSW2& RESTRICT dedge, const GSVertexSW2& RESTRICT dscan, const GSVector4& RESTRICT p0);
|
||||
@@ -115,7 +115,7 @@ protected:
|
||||
void DrawEdge(const GSVertexSW& v0, const GSVertexSW& v1, const GSVertexSW& dv, int orientation, int side);
|
||||
|
||||
__forceinline void AddScanline(GSVertexSW* e, int pixels, int left, int top, const GSVertexSW& scan);
|
||||
__forceinline void Flush(const GSVertexSW* vertex, const u32* index, const GSVertexSW& dscan, bool edge = false);
|
||||
__forceinline void Flush(const GSVertexSW* vertex, const u16* index, const GSVertexSW& dscan, bool edge = false);
|
||||
|
||||
__forceinline void DrawScanline(int pixels, int left, int top, const GSVertexSW& scan);
|
||||
__forceinline void DrawEdge(int pixels, int left, int top, const GSVertexSW& scan);
|
||||
|
||||