Files
ARMSX2/pcsx2/GS/GS.cpp
T
refractionpcsx2 48fd68ca87 GS: Implement PCRTC Offsets
This is off by default because people will complain about black borders, but I've tried to keep it to a minimum.

Enabling this option (Screen Offsets in the graphics settings) will allow you to position the screen in games which allow you to do so, maintain correct aspect ratios, and screen shake effects which are done on PCRTC (WipEout Fusion for example) will work.
2022-04-15 11:07:22 +01:00

1680 lines
48 KiB
C++

/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 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"
#ifndef PCSX2_CORE
// NOTE: The include order matters - GS.h includes windows.h
#include "GS/Window/GSwxDialog.h"
#endif
#include "GS.h"
#include "GSGL.h"
#include "GSUtil.h"
#include "GSExtra.h"
#include "Renderers/SW/GSRendererSW.h"
#include "Renderers/Null/GSRendererNull.h"
#include "Renderers/Null/GSDeviceNull.h"
#include "Renderers/HW/GSRendererNew.h"
#include "Renderers/HW/GSTextureReplacements.h"
#include "GSLzma.h"
#include "common/Console.h"
#include "common/StringUtil.h"
#include "pcsx2/Config.h"
#include "pcsx2/Counters.h"
#include "pcsx2/Host.h"
#include "pcsx2/HostDisplay.h"
#include "pcsx2/GS.h"
#ifdef PCSX2_CORE
#include "pcsx2/HostSettings.h"
#include "pcsx2/Frontend/InputManager.h"
#endif
#ifdef ENABLE_OPENGL
#include "Renderers/OpenGL/GSDeviceOGL.h"
#endif
#ifdef __APPLE__
#include "Renderers/Metal/GSMetalCPPAccessible.h"
#endif
#ifdef ENABLE_VULKAN
#include "Renderers/Vulkan/GSDeviceVK.h"
#endif
#ifdef _WIN32
#include "Renderers/DX11/GSDevice11.h"
#include "GS/Renderers/DX11/D3D.h"
static HRESULT s_hr = E_FAIL;
#endif
#include <fstream>
// do NOT undefine this/put it above includes, as x11 people love to redefine
// things that make obscure compiler bugs, unless you want to run around and
// debug obscure compiler errors --govanify
#undef None
Pcsx2Config::GSOptions GSConfig;
static std::unique_ptr<GSRenderer> s_gs;
static HostDisplay::RenderAPI s_render_api;
int GSinit()
{
if (!GSUtil::CheckSSE())
{
return -1;
}
// Vector instructions must be avoided when initialising GS since PCSX2
// can crash if the CPU does not support the instruction set.
// Initialise it here instead - it's not ideal since we have to strip the
// const type qualifier from all the affected variables.
GSinitConfig();
GSUtil::Init();
if (g_const == nullptr)
return -1;
else
g_const->Init();
#ifdef _WIN32
s_hr = ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
#endif
return 0;
}
void GSinitConfig()
{
static bool config_inited = false;
if (config_inited)
return;
config_inited = true;
theApp.SetConfigDir();
theApp.Init();
}
void GSshutdown()
{
if (s_gs)
{
s_gs->Destroy();
s_gs.reset();
}
if (g_gs_device)
{
g_gs_device->Destroy();
g_gs_device.reset();
}
Host::ReleaseHostDisplay();
#ifdef _WIN32
if (SUCCEEDED(s_hr))
{
::CoUninitialize();
s_hr = E_FAIL;
}
#endif
}
void GSclose()
{
if (s_gs)
{
s_gs->Destroy();
s_gs.reset();
}
if (g_gs_device)
{
g_gs_device->Destroy();
g_gs_device.reset();
}
if (HostDisplay* display = Host::GetHostDisplay(); display)
display->SetGPUTimingEnabled(false);
Host::ReleaseHostDisplay();
}
static HostDisplay::RenderAPI GetAPIForRenderer(GSRendererType renderer)
{
#if defined(_WIN32)
// On Windows, we use DX11 for software, since it's always available.
constexpr HostDisplay::RenderAPI default_api = HostDisplay::RenderAPI::D3D11;
#elif defined(__APPLE__)
// For Macs, default to Metal.
constexpr HostDisplay::RenderAPI default_api = HostDisplay::RenderAPI::Metal;
#else
// For Linux, default to OpenGL (because of hardware compatibility), if we
// have it, otherwise Vulkan (if we have it).
#if defined(ENABLE_OPENGL)
constexpr HostDisplay::RenderAPI default_api = HostDisplay::RenderAPI::OpenGL;
#elif defined(ENABLE_VULKAN)
constexpr HostDisplay::RenderAPI default_api = HostDisplay::RenderAPI::Vulkan;
#else
constexpr HostDisplay::RenderAPI default_api = HostDisplay::RenderAPI::None;
#endif
#endif
switch (renderer)
{
case GSRendererType::OGL:
return HostDisplay::RenderAPI::OpenGL;
case GSRendererType::VK:
return HostDisplay::RenderAPI::Vulkan;
#ifdef _WIN32
case GSRendererType::DX11:
return HostDisplay::RenderAPI::D3D11;
#endif
#ifdef __APPLE__
case GSRendererType::Metal:
return HostDisplay::RenderAPI::Metal;
#endif
default:
return default_api;
}
}
static bool DoGSOpen(GSRendererType renderer, u8* basemem)
{
HostDisplay* display = Host::GetHostDisplay();
pxAssert(display);
s_render_api = Host::GetHostDisplay()->GetRenderAPI();
switch (display->GetRenderAPI())
{
#ifdef _WIN32
case HostDisplay::RenderAPI::D3D11:
g_gs_device = std::make_unique<GSDevice11>();
break;
#endif
#ifdef __APPLE__
case HostDisplay::RenderAPI::Metal:
g_gs_device = std::unique_ptr<GSDevice>(MakeGSDeviceMTL());
break;
#endif
#ifdef ENABLE_OPENGL
case HostDisplay::RenderAPI::OpenGL:
case HostDisplay::RenderAPI::OpenGLES:
g_gs_device = std::make_unique<GSDeviceOGL>();
break;
#endif
#ifdef ENABLE_VULKAN
case HostDisplay::RenderAPI::Vulkan:
g_gs_device = std::make_unique<GSDeviceVK>();
break;
#endif
default:
Console.Error("Unknown render API %u", static_cast<unsigned>(display->GetRenderAPI()));
return false;
}
try
{
if (!g_gs_device->Create(display))
{
g_gs_device->Destroy();
g_gs_device.reset();
return false;
}
if (renderer == GSRendererType::Null)
{
s_gs = std::make_unique<GSRendererNull>();
}
else if (renderer != GSRendererType::SW)
{
s_gs = std::make_unique<GSRendererNew>();
}
else
{
const int threads = theApp.GetConfigI("extrathreads");
s_gs = std::make_unique<GSRendererSW>(threads);
}
}
catch (std::exception& ex)
{
Host::ReportFormattedErrorAsync("GS", "GS error: Exception caught in GSopen: %s", ex.what());
s_gs.reset();
g_gs_device->Destroy();
g_gs_device.reset();
return false;
}
s_gs->SetRegsMem(basemem);
display->SetVSync(EmuConfig.GetEffectiveVsyncMode());
display->SetGPUTimingEnabled(GSConfig.OsdShowGPU);
return true;
}
bool GSreopen(bool recreate_display)
{
Console.WriteLn("Reopening GS with %s display", recreate_display ? "new" : "existing");
s_gs->Flush();
freezeData fd = {};
if (s_gs->Freeze(&fd, true) != 0)
{
Console.Error("(GSreopen) Failed to get GS freeze size");
return false;
}
std::unique_ptr<u8[]> fd_data = std::make_unique<u8[]>(fd.size);
fd.data = fd_data.get();
if (s_gs->Freeze(&fd, false) != 0)
{
Console.Error("(GSreopen) Failed to freeze GS");
return false;
}
if (recreate_display)
{
g_gs_device->ResetAPIState();
if (Host::BeginPresentFrame(true))
Host::EndPresentFrame();
}
u8* basemem = s_gs->GetRegsMem();
const u32 gamecrc = s_gs->GetGameCRC();
const int gamecrc_options = s_gs->GetGameCRCOptions();
s_gs->Destroy();
s_gs.reset();
g_gs_device->Destroy();
g_gs_device.reset();
if (recreate_display)
{
Host::ReleaseHostDisplay();
if (!Host::AcquireHostDisplay(GetAPIForRenderer(GSConfig.Renderer)))
{
pxFailRel("(GSreopen) Failed to reacquire host display");
return false;
}
}
if (!DoGSOpen(GSConfig.Renderer, basemem))
{
pxFailRel("(GSreopen) Failed to recreate GS");
return false;
}
if (s_gs->Defrost(&fd) != 0)
{
pxFailRel("(GSreopen) Failed to defrost");
return false;
}
s_gs->SetGameCRC(gamecrc, gamecrc_options);
return true;
}
bool GSopen(const Pcsx2Config::GSOptions& config, GSRendererType renderer, u8* basemem)
{
if (renderer == GSRendererType::Auto)
renderer = GSUtil::GetPreferredRenderer();
GSConfig = config;
GSConfig.Renderer = renderer;
if (!Host::AcquireHostDisplay(GetAPIForRenderer(renderer)))
{
Console.Error("Failed to acquire host display");
return false;
}
if (!DoGSOpen(renderer, basemem))
{
Host::ReleaseHostDisplay();
return false;
}
return true;
}
void GSreset()
{
try
{
s_gs->Reset();
}
catch (GSRecoverableError)
{
}
}
void GSgifSoftReset(u32 mask)
{
try
{
s_gs->SoftReset(mask);
}
catch (GSRecoverableError)
{
}
}
void GSwriteCSR(u32 csr)
{
try
{
s_gs->WriteCSR(csr);
}
catch (GSRecoverableError)
{
}
}
void GSinitReadFIFO(u8* mem)
{
GL_PERF("Init Read FIFO1");
try
{
s_gs->InitReadFIFO(mem, 1);
}
catch (GSRecoverableError)
{
}
catch (const std::bad_alloc&)
{
fprintf(stderr, "GS: Memory allocation error\n");
}
}
void GSreadFIFO(u8* mem)
{
try
{
s_gs->ReadFIFO(mem, 1);
}
catch (GSRecoverableError)
{
}
catch (const std::bad_alloc&)
{
fprintf(stderr, "GS: Memory allocation error\n");
}
}
void GSinitReadFIFO2(u8* mem, u32 size)
{
GL_PERF("Init Read FIFO2");
try
{
s_gs->InitReadFIFO(mem, size);
}
catch (GSRecoverableError)
{
}
catch (const std::bad_alloc&)
{
fprintf(stderr, "GS: Memory allocation error\n");
}
}
void GSreadFIFO2(u8* mem, u32 size)
{
try
{
s_gs->ReadFIFO(mem, size);
}
catch (GSRecoverableError)
{
}
catch (const std::bad_alloc&)
{
fprintf(stderr, "GS: Memory allocation error\n");
}
}
void GSgifTransfer(const u8* mem, u32 size)
{
try
{
s_gs->Transfer<3>(mem, size);
}
catch (GSRecoverableError)
{
}
}
void GSgifTransfer1(u8* mem, u32 addr)
{
try
{
s_gs->Transfer<0>(const_cast<u8*>(mem) + addr, (0x4000 - addr) / 16);
}
catch (GSRecoverableError)
{
}
}
void GSgifTransfer2(u8* mem, u32 size)
{
try
{
s_gs->Transfer<1>(const_cast<u8*>(mem), size);
}
catch (GSRecoverableError)
{
}
}
void GSgifTransfer3(u8* mem, u32 size)
{
try
{
s_gs->Transfer<2>(const_cast<u8*>(mem), size);
}
catch (GSRecoverableError)
{
}
}
void GSvsync(u32 field, bool registers_written)
{
try
{
s_gs->VSync(field, registers_written);
}
catch (GSRecoverableError)
{
}
catch (const std::bad_alloc&)
{
fprintf(stderr, "GS: Memory allocation error\n");
}
}
u32 GSmakeSnapshot(char* path)
{
try
{
std::string s{path};
if (!s.empty())
{
// Allows for providing a complete path
std::string extension = s.substr(s.size() - 4, 4);
#ifdef _WIN32
std::transform(extension.begin(), extension.end(), extension.begin(), (char(_cdecl*)(int))tolower);
#else
std::transform(extension.begin(), extension.end(), extension.begin(), tolower);
#endif
if (extension == ".png")
return s_gs->MakeSnapshot(s);
else if (s[s.length() - 1] != DIRECTORY_SEPARATOR)
s = s + DIRECTORY_SEPARATOR;
}
return s_gs->MakeSnapshot(s + "gs");
}
catch (GSRecoverableError)
{
return false;
}
}
int GSfreeze(FreezeAction mode, freezeData* data)
{
try
{
if (mode == FreezeAction::Save)
{
return s_gs->Freeze(data, false);
}
else if (mode == FreezeAction::Size)
{
return s_gs->Freeze(data, true);
}
else if (mode == FreezeAction::Load)
{
return s_gs->Defrost(data);
}
}
catch (GSRecoverableError)
{
}
return 0;
}
#ifndef PCSX2_CORE
void GSkeyEvent(const HostKeyEvent& e)
{
try
{
if (s_gs)
s_gs->KeyEvent(e);
}
catch (GSRecoverableError)
{
}
}
void GSconfigure()
{
try
{
if (!GSUtil::CheckSSE())
return;
theApp.SetConfigDir();
theApp.Init();
if (RunwxDialog())
{
theApp.ReloadConfig();
// Force a reload of the gs state
//theApp.SetCurrentRendererType(GSRendererType::Undefined);
}
}
catch (GSRecoverableError)
{
}
}
int GStest()
{
if (!GSUtil::CheckSSE())
return -1;
return 0;
}
#endif
void pt(const char* str)
{
struct tm* current;
time_t now;
time(&now);
current = localtime(&now);
printf("%02i:%02i:%02i%s", current->tm_hour, current->tm_min, current->tm_sec, str);
}
bool GSsetupRecording(std::string& filename)
{
if (s_gs == NULL)
{
printf("GS: no s_gs for recording\n");
return false;
}
#if defined(__unix__) || defined(__APPLE__)
if (!theApp.GetConfigB("capture_enabled"))
{
printf("GS: Recording is disabled\n");
return false;
}
#endif
printf("GS: Recording start command\n");
if (s_gs->BeginCapture(filename))
{
pt(" - Capture started\n");
return true;
}
else
{
pt(" - Capture cancelled\n");
return false;
}
}
void GSendRecording()
{
printf("GS: Recording end command\n");
s_gs->EndCapture();
pt(" - Capture ended\n");
}
void GSsetGameCRC(u32 crc, int options)
{
s_gs->SetGameCRC(crc, options);
}
void GSsetFrameSkip(int frameskip)
{
s_gs->SetFrameSkip(frameskip);
}
void GSgetInternalResolution(int* width, int* height)
{
GSRenderer* gs = s_gs.get();
if (!gs)
{
*width = 0;
*height = 0;
return;
}
const GSVector2i res(gs->GetInternalResolution());
*width = res.x;
*height = res.y;
}
void GSgetStats(std::string& info)
{
GSPerfMon& pm = g_perfmon;
const char* api_name = HostDisplay::RenderAPIToString(s_render_api);
if (GSConfig.Renderer == GSRendererType::SW)
{
const double fps = GetVerticalFrequency();
const double fillrate = pm.Get(GSPerfMon::Fillrate);
info = format("%s SW | %d S | %d P | %d D | %.2f U | %.2f D | %.2f mpps",
api_name,
(int)pm.Get(GSPerfMon::SyncPoint),
(int)pm.Get(GSPerfMon::Prim),
(int)pm.Get(GSPerfMon::Draw),
pm.Get(GSPerfMon::Swizzle) / 1024,
pm.Get(GSPerfMon::Unswizzle) / 1024,
fps * fillrate / (1024 * 1024));
}
else if (GSConfig.Renderer == GSRendererType::Null)
{
info = format("%s Null", api_name);
}
else
{
if (GSConfig.TexturePreloading == TexturePreloadingLevel::Full)
{
info = format("%s HW | HC: %d MB | %d P | %d D | %d DC | %d RB | %d TC | %d TU",
api_name,
(int)std::ceil(static_cast<GSRendererHW*>(s_gs.get())->GetTextureCache()->GetHashCacheMemoryUsage() / 1048576.0f),
(int)pm.Get(GSPerfMon::Prim),
(int)pm.Get(GSPerfMon::Draw),
(int)std::ceil(pm.Get(GSPerfMon::DrawCalls)),
(int)std::ceil(pm.Get(GSPerfMon::Readbacks)),
(int)std::ceil(pm.Get(GSPerfMon::TextureCopies)),
(int)std::ceil(pm.Get(GSPerfMon::TextureUploads)));
}
else
{
info = format("%s HW | %d P | %d D | %d DC | %d RB | %d TC | %d 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::Readbacks)),
(int)std::ceil(pm.Get(GSPerfMon::TextureCopies)),
(int)std::ceil(pm.Get(GSPerfMon::TextureUploads)));
}
}
}
void GSgetTitleStats(std::string& info)
{
const char* api_name = HostDisplay::RenderAPIToString(s_render_api);
const char* hw_sw_name = (GSConfig.Renderer == GSRendererType::Null) ? " Null" : (GSConfig.UseHardwareRenderer() ? " HW" : " SW");
const char* deinterlace_mode = theApp.m_gs_deinterlace[static_cast<int>(GSConfig.InterlaceMode)].name.c_str();
#ifndef PCSX2_CORE
int iwidth, iheight;
GSgetInternalResolution(&iwidth, &iheight);
info = StringUtil::StdStringFromFormat("%s%s | %s | %dx%d", api_name, hw_sw_name, deinterlace_mode, iwidth, iheight);
#else
const char* interlace_mode = ReportInterlaceMode();
const char* video_mode = ReportVideoMode();
info = StringUtil::StdStringFromFormat("%s%s | %s | %s | %s", api_name, hw_sw_name, video_mode, interlace_mode, deinterlace_mode);
#endif
}
void GSUpdateConfig(const Pcsx2Config::GSOptions& new_config)
{
Pcsx2Config::GSOptions old_config(std::move(GSConfig));
GSConfig = new_config;
GSConfig.Renderer = (GSConfig.Renderer == GSRendererType::Auto) ? GSUtil::GetPreferredRenderer() : GSConfig.Renderer;
if (!s_gs)
return;
HostDisplay* display = Host::GetHostDisplay();
// Handle OSD scale changes by pushing a window resize through.
if (new_config.OsdScale != old_config.OsdScale)
{
g_gs_device->ResetAPIState();
Host::ResizeHostDisplay(display->GetWindowWidth(), display->GetWindowHeight(), display->GetWindowScale());
g_gs_device->RestoreAPIState();
}
// Options which need a full teardown/recreate.
if (!GSConfig.RestartOptionsAreEqual(old_config))
{
HostDisplay::RenderAPI existing_api = Host::GetHostDisplay()->GetRenderAPI();
if (existing_api == HostDisplay::RenderAPI::OpenGLES)
existing_api = HostDisplay::RenderAPI::OpenGL;
const bool do_full_restart = (
existing_api != GetAPIForRenderer(GSConfig.Renderer) ||
GSConfig.Adapter != old_config.Adapter ||
GSConfig.UseDebugDevice != old_config.UseDebugDevice ||
GSConfig.UseBlitSwapChain != old_config.UseBlitSwapChain ||
GSConfig.DisableShaderCache != old_config.DisableShaderCache ||
GSConfig.ThreadedPresentation != old_config.ThreadedPresentation
);
GSreopen(do_full_restart);
return;
}
// Options which aren't using the global struct yet, so we need to recreate all GS objects.
if (
GSConfig.DumpGSData != old_config.DumpGSData ||
GSConfig.SaveRT != old_config.SaveRT ||
GSConfig.SaveFrame != old_config.SaveFrame ||
GSConfig.SaveTexture != old_config.SaveTexture ||
GSConfig.SaveDepth != old_config.SaveDepth ||
GSConfig.UpscaleMultiplier != old_config.UpscaleMultiplier ||
GSConfig.CRCHack != old_config.CRCHack ||
GSConfig.SWExtraThreads != old_config.SWExtraThreads ||
GSConfig.SWExtraThreadsHeight != old_config.SWExtraThreadsHeight ||
GSConfig.SaveN != old_config.SaveN ||
GSConfig.SaveL != old_config.SaveL ||
GSConfig.ShaderFX_Conf != old_config.ShaderFX_Conf ||
GSConfig.ShaderFX_GLSL != old_config.ShaderFX_GLSL)
{
GSreopen(false);
return;
}
// This is where we would do finer-grained checks in the future.
// For example, flushing the texture cache when mipmap settings change.
if (GSConfig.CRCHack != old_config.CRCHack ||
GSConfig.PointListPalette != old_config.PointListPalette)
{
// for automatic mipmaps, we need to reload the crc
s_gs->SetGameCRC(s_gs->GetGameCRC(), s_gs->GetGameCRCOptions());
}
// renderer-specific options (e.g. auto flush, TC offset)
s_gs->UpdateSettings(old_config);
// reload texture cache when trilinear filtering or TC options change
if (
(GSConfig.UseHardwareRenderer() && GSConfig.HWMipmap != old_config.HWMipmap) ||
GSConfig.ConservativeFramebuffer != old_config.ConservativeFramebuffer ||
GSConfig.TexturePreloading != old_config.TexturePreloading ||
GSConfig.UserHacks_TriFilter != old_config.UserHacks_TriFilter ||
GSConfig.GPUPaletteConversion != old_config.GPUPaletteConversion ||
GSConfig.PreloadFrameWithGSData != old_config.PreloadFrameWithGSData ||
GSConfig.WrapGSMem != old_config.WrapGSMem ||
GSConfig.UserHacks_CPUFBConversion != old_config.UserHacks_CPUFBConversion ||
GSConfig.UserHacks_DisableDepthSupport != old_config.UserHacks_DisableDepthSupport ||
GSConfig.UserHacks_DisablePartialInvalidation != old_config.UserHacks_DisablePartialInvalidation ||
GSConfig.UserHacks_TextureInsideRt != old_config.UserHacks_TextureInsideRt)
{
s_gs->PurgeTextureCache();
s_gs->PurgePool();
}
// clear out the sampler cache when AF options change, since the anisotropy gets baked into them
if (GSConfig.MaxAnisotropy != old_config.MaxAnisotropy)
g_gs_device->ClearSamplerCache();
// texture dumping/replacement options
GSTextureReplacements::UpdateConfig(old_config);
// clear the hash texture cache since we might have replacements now
// also clear it when dumping changes, since we want to dump everything being used
if (GSConfig.LoadTextureReplacements != old_config.LoadTextureReplacements ||
GSConfig.DumpReplaceableTextures != old_config.DumpReplaceableTextures)
{
s_gs->PurgeTextureCache();
}
if (GSConfig.OsdShowGPU != old_config.OsdShowGPU)
{
if (HostDisplay* display = Host::GetHostDisplay(); display)
display->SetGPUTimingEnabled(GSConfig.OsdShowGPU);
}
}
void GSSwitchRenderer(GSRendererType new_renderer)
{
if (new_renderer == GSRendererType::Auto)
new_renderer = GSUtil::GetPreferredRenderer();
if (!s_gs || GSConfig.Renderer == new_renderer)
return;
HostDisplay::RenderAPI existing_api = Host::GetHostDisplay()->GetRenderAPI();
if (existing_api == HostDisplay::RenderAPI::OpenGLES)
existing_api = HostDisplay::RenderAPI::OpenGL;
const bool is_software_switch = (new_renderer == GSRendererType::SW || GSConfig.Renderer == GSRendererType::SW);
GSConfig.Renderer = new_renderer;
GSreopen(!is_software_switch && existing_api != GetAPIForRenderer(new_renderer));
}
void GSResetAPIState()
{
if (!g_gs_device)
return;
g_gs_device->ResetAPIState();
}
void GSRestoreAPIState()
{
if (!g_gs_device)
return;
g_gs_device->RestoreAPIState();
}
bool GSSaveSnapshotToMemory(u32 width, u32 height, std::vector<u32>* pixels)
{
if (!s_gs)
return false;
return s_gs->SaveSnapshotToMemory(width, height, pixels);
}
std::string format(const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
int size = vsnprintf(nullptr, 0, fmt, args) + 1;
va_end(args);
assert(size > 0);
std::vector<char> buffer(std::max(1, size));
va_start(args, fmt);
vsnprintf(buffer.data(), size, fmt, args);
va_end(args);
return {buffer.data()};
}
// Helper path to dump texture
#ifdef _WIN32
const std::string root_sw("c:\\temp1\\_");
const std::string root_hw("c:\\temp2\\_");
#else
#ifdef _M_AMD64
const std::string root_sw("/tmp/GS_SW_dump64/");
const std::string root_hw("/tmp/GS_HW_dump64/");
#else
const std::string root_sw("/tmp/GS_SW_dump32/");
const std::string root_hw("/tmp/GS_HW_dump32/");
#endif
#endif
#ifdef _WIN32
void* vmalloc(size_t size, bool code)
{
void* ptr = VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE, code ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE);
if (!ptr)
throw std::bad_alloc();
return ptr;
}
void vmfree(void* ptr, size_t size)
{
VirtualFree(ptr, 0, MEM_RELEASE);
}
static HANDLE s_fh = NULL;
static u8* s_Next[8];
void* fifo_alloc(size_t size, size_t repeat)
{
ASSERT(s_fh == NULL);
if (repeat >= std::size(s_Next))
{
fprintf(stderr, "Memory mapping overflow (%zu >= %u)\n", repeat, static_cast<unsigned>(std::size(s_Next)));
return vmalloc(size * repeat, false); // Fallback to default vmalloc
}
s_fh = CreateFileMapping(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, size, nullptr);
DWORD errorID = ::GetLastError();
if (s_fh == NULL)
{
fprintf(stderr, "Failed to reserve memory. WIN API ERROR:%u\n", errorID);
return vmalloc(size * repeat, false); // Fallback to default vmalloc
}
int mmap_segment_failed = 0;
void* fifo = MapViewOfFile(s_fh, FILE_MAP_ALL_ACCESS, 0, 0, size);
for (size_t i = 1; i < repeat; i++)
{
void* base = (u8*)fifo + size * i;
s_Next[i] = (u8*)MapViewOfFileEx(s_fh, FILE_MAP_ALL_ACCESS, 0, 0, size, base);
errorID = ::GetLastError();
if (s_Next[i] != base)
{
mmap_segment_failed++;
if (mmap_segment_failed > 4)
{
fprintf(stderr, "Memory mapping failed after %d attempts, aborting. WIN API ERROR:%u\n", mmap_segment_failed, errorID);
fifo_free(fifo, size, repeat);
return vmalloc(size * repeat, false); // Fallback to default vmalloc
}
do
{
UnmapViewOfFile(s_Next[i]);
s_Next[i] = 0;
} while (--i > 0);
fifo = MapViewOfFile(s_fh, FILE_MAP_ALL_ACCESS, 0, 0, size);
}
}
return fifo;
}
void fifo_free(void* ptr, size_t size, size_t repeat)
{
ASSERT(s_fh != NULL);
if (s_fh == NULL)
{
if (ptr != NULL)
vmfree(ptr, size);
return;
}
UnmapViewOfFile(ptr);
for (size_t i = 1; i < std::size(s_Next); i++)
{
if (s_Next[i] != 0)
{
UnmapViewOfFile(s_Next[i]);
s_Next[i] = 0;
}
}
CloseHandle(s_fh);
s_fh = NULL;
}
#else
#include <sys/mman.h>
#include <unistd.h>
void* vmalloc(size_t size, bool code)
{
size_t mask = getpagesize() - 1;
size = (size + mask) & ~mask;
int prot = PROT_READ | PROT_WRITE;
int flags = MAP_PRIVATE | MAP_ANONYMOUS;
if (code)
{
prot |= PROT_EXEC;
#if defined(_M_AMD64) && !defined(__APPLE__)
// macOS doesn't allow any mappings in the first 4GB of address space
flags |= MAP_32BIT;
#endif
}
void* ptr = mmap(NULL, size, prot, flags, -1, 0);
if (ptr == MAP_FAILED)
throw std::bad_alloc();
return ptr;
}
void vmfree(void* ptr, size_t size)
{
size_t mask = getpagesize() - 1;
size = (size + mask) & ~mask;
munmap(ptr, size);
}
static int s_shm_fd = -1;
void* fifo_alloc(size_t size, size_t repeat)
{
ASSERT(s_shm_fd == -1);
const char* file_name = "/GS.mem";
s_shm_fd = shm_open(file_name, O_RDWR | O_CREAT | O_EXCL, 0600);
if (s_shm_fd != -1)
{
shm_unlink(file_name); // file is deleted but descriptor is still open
}
else
{
fprintf(stderr, "Failed to open %s due to %s\n", file_name, strerror(errno));
return nullptr;
}
if (ftruncate(s_shm_fd, repeat * size) < 0)
fprintf(stderr, "Failed to reserve memory due to %s\n", strerror(errno));
void* fifo = mmap(nullptr, size * repeat, PROT_READ | PROT_WRITE, MAP_SHARED, s_shm_fd, 0);
for (size_t i = 1; i < repeat; i++)
{
void* base = (u8*)fifo + size * i;
u8* next = (u8*)mmap(base, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, s_shm_fd, 0);
if (next != base)
fprintf(stderr, "Fail to mmap contiguous segment\n");
}
return fifo;
}
void fifo_free(void* ptr, size_t size, size_t repeat)
{
ASSERT(s_shm_fd >= 0);
if (s_shm_fd < 0)
return;
munmap(ptr, size * repeat);
close(s_shm_fd);
s_shm_fd = -1;
}
#endif
size_t GSApp::GetIniString(const char* lpAppName, const char* lpKeyName, const char* lpDefault, char* lpReturnedString, size_t nSize, const char* lpFileName)
{
#ifdef PCSX2_CORE
std::string ret(Host::GetStringSettingValue("EmuCore/GS", lpKeyName, lpDefault));
return StringUtil::Strlcpy(lpReturnedString, ret, nSize);
#else
BuildConfigurationMap(lpFileName);
std::string key(lpKeyName);
std::string value = m_configuration_map[key];
if (value.empty())
{
// save the value for futur call
m_configuration_map[key] = std::string(lpDefault);
strcpy(lpReturnedString, lpDefault);
}
else
strcpy(lpReturnedString, value.c_str());
return 0;
#endif
}
bool GSApp::WriteIniString(const char* lpAppName, const char* lpKeyName, const char* pString, const char* lpFileName)
{
#ifndef PCSX2_CORE
BuildConfigurationMap(lpFileName);
std::string key(lpKeyName);
std::string value(pString);
m_configuration_map[key] = value;
// Save config to a file
FILE* f = px_fopen(lpFileName, "w");
if (f == NULL)
return false; // FIXME print a nice message
// Maintain compatibility with GSDumpGUI/old Windows ini.
#ifdef _WIN32
fprintf(f, "[Settings]\n");
#endif
for (const auto& entry : m_configuration_map)
{
// Do not save the inifile key which is not an option
if (entry.first.compare("inifile") == 0)
continue;
// Only keep option that have a default value (allow to purge old option of the GS.ini)
if (!entry.second.empty() && m_default_configuration.find(entry.first) != m_default_configuration.end())
fprintf(f, "%s = %s\n", entry.first.c_str(), entry.second.c_str());
}
fclose(f);
#endif
return false;
}
#ifndef PCSX2_CORE
int GSApp::GetIniInt(const char* lpAppName, const char* lpKeyName, int nDefault, const char* lpFileName)
{
BuildConfigurationMap(lpFileName);
std::string value = m_configuration_map[std::string(lpKeyName)];
if (value.empty())
{
// save the value for futur call
SetConfig(lpKeyName, nDefault);
return nDefault;
}
else
return atoi(value.c_str());
}
#endif
GSApp theApp;
GSApp::GSApp()
{
// Empty constructor causes an illegal instruction exception on an SSE4.2 machine on Windows.
// Non-empty doesn't, but raises a SIGILL signal when compiled against GCC 6.1.1.
// So here's a compromise.
#ifdef _WIN32
Init();
#endif
}
void GSApp::Init()
{
static bool is_initialised = false;
if (is_initialised)
return;
is_initialised = true;
m_section = "Settings";
m_gs_renderers.push_back(GSSetting(static_cast<u32>(GSRendererType::Auto), "Automatic", ""));
#ifdef _WIN32
m_gs_renderers.push_back(GSSetting(static_cast<u32>(GSRendererType::DX11), "Direct3D 11", ""));
#endif
#ifdef __APPLE__
m_gs_renderers.push_back(GSSetting(static_cast<u32>(GSRendererType::Metal), "Metal", ""));
#endif
#ifdef ENABLE_OPENGL
m_gs_renderers.push_back(GSSetting(static_cast<u32>(GSRendererType::OGL), "OpenGL", ""));
#endif
#ifdef ENABLE_VULKAN
m_gs_renderers.push_back(GSSetting(static_cast<u32>(GSRendererType::VK), "Vulkan", ""));
#endif
m_gs_renderers.push_back(GSSetting(static_cast<u32>(GSRendererType::SW), "Software", ""));
// The null renderer goes last, it has use for benchmarking purposes in a release build
m_gs_renderers.push_back(GSSetting(static_cast<u32>(GSRendererType::Null), "Null", ""));
m_gs_deinterlace.push_back(GSSetting(0, "None", ""));
m_gs_deinterlace.push_back(GSSetting(1, "Weave tff", "saw-tooth"));
m_gs_deinterlace.push_back(GSSetting(2, "Weave bff", "saw-tooth"));
m_gs_deinterlace.push_back(GSSetting(3, "Bob tff", "use blend if shaking"));
m_gs_deinterlace.push_back(GSSetting(4, "Bob bff", "use blend if shaking"));
m_gs_deinterlace.push_back(GSSetting(5, "Blend tff", "slight blur, 1/2 fps"));
m_gs_deinterlace.push_back(GSSetting(6, "Blend bff", "slight blur, 1/2 fps"));
m_gs_deinterlace.push_back(GSSetting(7, "Automatic", "Default"));
m_gs_upscale_multiplier.push_back(GSSetting(1, "Native", "PS2"));
m_gs_upscale_multiplier.push_back(GSSetting(2, "2x Native", "~720p"));
m_gs_upscale_multiplier.push_back(GSSetting(3, "3x Native", "~1080p"));
m_gs_upscale_multiplier.push_back(GSSetting(4, "4x Native", "~1440p 2K"));
m_gs_upscale_multiplier.push_back(GSSetting(5, "5x Native", "~1620p"));
m_gs_upscale_multiplier.push_back(GSSetting(6, "6x Native", "~2160p 4K"));
m_gs_upscale_multiplier.push_back(GSSetting(7, "7x Native", "~2520p"));
m_gs_upscale_multiplier.push_back(GSSetting(8, "8x Native", "~2880p"));
m_gs_max_anisotropy.push_back(GSSetting(0, "Off", "Default"));
m_gs_max_anisotropy.push_back(GSSetting(2, "2x", ""));
m_gs_max_anisotropy.push_back(GSSetting(4, "4x", ""));
m_gs_max_anisotropy.push_back(GSSetting(8, "8x", ""));
m_gs_max_anisotropy.push_back(GSSetting(16, "16x", ""));
m_gs_dithering.push_back(GSSetting(0, "Off", ""));
m_gs_dithering.push_back(GSSetting(2, "Unscaled", "Default"));
m_gs_dithering.push_back(GSSetting(1, "Scaled", ""));
m_gs_bifilter.push_back(GSSetting(static_cast<u32>(BiFiltering::Nearest), "Nearest", ""));
m_gs_bifilter.push_back(GSSetting(static_cast<u32>(BiFiltering::Forced_But_Sprite), "Bilinear", "Forced excluding sprite"));
m_gs_bifilter.push_back(GSSetting(static_cast<u32>(BiFiltering::Forced), "Bilinear", "Forced"));
m_gs_bifilter.push_back(GSSetting(static_cast<u32>(BiFiltering::PS2), "Bilinear", "PS2"));
m_gs_trifilter.push_back(GSSetting(static_cast<u32>(TriFiltering::Automatic), "Automatic", "Default"));
m_gs_trifilter.push_back(GSSetting(static_cast<u32>(TriFiltering::Off), "None", ""));
m_gs_trifilter.push_back(GSSetting(static_cast<u32>(TriFiltering::PS2), "Trilinear", ""));
m_gs_trifilter.push_back(GSSetting(static_cast<u32>(TriFiltering::Forced), "Trilinear", "Ultra/Slow"));
m_gs_texture_preloading.push_back(GSSetting(static_cast<u32>(TexturePreloadingLevel::Off), "None", "Default"));
m_gs_texture_preloading.push_back(GSSetting(static_cast<u32>(TexturePreloadingLevel::Partial), "Partial", ""));
m_gs_texture_preloading.push_back(GSSetting(static_cast<u32>(TexturePreloadingLevel::Full), "Full", "Hash Cache"));
m_gs_generic_list.push_back(GSSetting(-1, "Automatic", "Default"));
m_gs_generic_list.push_back(GSSetting(0, "Force-Disabled", ""));
m_gs_generic_list.push_back(GSSetting(1, "Force-Enabled", ""));
m_gs_hack.push_back(GSSetting(0, "Off", "Default"));
m_gs_hack.push_back(GSSetting(1, "Half", ""));
m_gs_hack.push_back(GSSetting(2, "Full", ""));
m_gs_offset_hack.push_back(GSSetting(0, "Off", "Default"));
m_gs_offset_hack.push_back(GSSetting(1, "Normal", "Vertex"));
m_gs_offset_hack.push_back(GSSetting(2, "Special", "Texture"));
m_gs_offset_hack.push_back(GSSetting(3, "Special", "Texture - aggressive"));
m_gs_hw_mipmapping = {
GSSetting(HWMipmapLevel::Automatic, "Automatic", "Default"),
GSSetting(HWMipmapLevel::Off, "Off", ""),
GSSetting(HWMipmapLevel::Basic, "Basic", "Fast"),
GSSetting(HWMipmapLevel::Full, "Full", "Slow"),
};
m_gs_crc_level = {
GSSetting(CRCHackLevel::Automatic, "Automatic", "Default"),
GSSetting(CRCHackLevel::Off, "None", "Debug"),
GSSetting(CRCHackLevel::Minimum, "Minimum", "Debug"),
#ifdef _DEBUG
GSSetting(CRCHackLevel::Partial, "Partial", "OpenGL"),
GSSetting(CRCHackLevel::Full, "Full", "Direct3D"),
#endif
GSSetting(CRCHackLevel::Aggressive, "Aggressive", ""),
};
m_gs_acc_blend_level.push_back(GSSetting(static_cast<u32>(AccBlendLevel::Minimum), "Minimum", "Fastest"));
m_gs_acc_blend_level.push_back(GSSetting(static_cast<u32>(AccBlendLevel::Basic), "Basic", "Recommended"));
m_gs_acc_blend_level.push_back(GSSetting(static_cast<u32>(AccBlendLevel::Medium), "Medium", ""));
m_gs_acc_blend_level.push_back(GSSetting(static_cast<u32>(AccBlendLevel::High), "High", ""));
m_gs_acc_blend_level.push_back(GSSetting(static_cast<u32>(AccBlendLevel::Full), "Full", "Very Slow"));
m_gs_acc_blend_level.push_back(GSSetting(static_cast<u32>(AccBlendLevel::Ultra), "Ultra", "Ultra Slow"));
m_gs_tv_shaders.push_back(GSSetting(0, "None", ""));
m_gs_tv_shaders.push_back(GSSetting(1, "Scanline filter", ""));
m_gs_tv_shaders.push_back(GSSetting(2, "Diagonal filter", ""));
m_gs_tv_shaders.push_back(GSSetting(3, "Triangular filter", ""));
m_gs_tv_shaders.push_back(GSSetting(4, "Wave filter", ""));
// clang-format off
// Avoid to clutter the ini file with useless options
#if defined(ENABLE_VULKAN) || defined(_WIN32)
m_default_configuration["Adapter"] = "";
#endif
#ifdef _WIN32
// Per OS option.
m_default_configuration["CaptureFileName"] = "";
m_default_configuration["CaptureVideoCodecDisplayName"] = "";
#else
m_default_configuration["linux_replay"] = "1";
#endif
m_default_configuration["aa1"] = "1";
m_default_configuration["accurate_date"] = "1";
m_default_configuration["accurate_blending_unit"] = "1";
m_default_configuration["AspectRatio"] = "1";
m_default_configuration["autoflush_sw"] = "1";
m_default_configuration["capture_enabled"] = "0";
m_default_configuration["capture_out_dir"] = "/tmp/GS_Capture";
m_default_configuration["capture_threads"] = "4";
m_default_configuration["CaptureHeight"] = "480";
m_default_configuration["CaptureWidth"] = "640";
m_default_configuration["crc_hack_level"] = std::to_string(static_cast<s8>(CRCHackLevel::Automatic));
m_default_configuration["CrcHacksExclusions"] = "";
m_default_configuration["disable_hw_gl_draw"] = "0";
m_default_configuration["disable_shader_cache"] = "0";
m_default_configuration["DisableDualSourceBlend"] = "0";
m_default_configuration["DisableFramebufferFetch"] = "0";
m_default_configuration["dithering_ps2"] = "2";
m_default_configuration["dump"] = "0";
m_default_configuration["DumpReplaceableTextures"] = "0";
m_default_configuration["DumpReplaceableMipmaps"] = "0";
m_default_configuration["DumpTexturesWithFMVActive"] = "0";
m_default_configuration["extrathreads"] = "2";
m_default_configuration["extrathreads_height"] = "4";
m_default_configuration["filter"] = std::to_string(static_cast<s8>(BiFiltering::PS2));
m_default_configuration["FMVSoftwareRendererSwitch"] = "0";
m_default_configuration["fxaa"] = "0";
m_default_configuration["HWDisableReadbacks"] = "0";
m_default_configuration["pcrtc_offsets"] = "0";
m_default_configuration["IntegerScaling"] = "0";
m_default_configuration["deinterlace"] = "7";
m_default_configuration["conservative_framebuffer"] = "1";
m_default_configuration["linear_present"] = "1";
m_default_configuration["LoadTextureReplacements"] = "0";
m_default_configuration["LoadTextureReplacementsAsync"] = "1";
m_default_configuration["MaxAnisotropy"] = "0";
m_default_configuration["mipmap"] = "1";
m_default_configuration["mipmap_hw"] = std::to_string(static_cast<int>(HWMipmapLevel::Automatic));
m_default_configuration["OsdShowMessages"] = "1";
m_default_configuration["OsdShowSpeed"] = "0";
m_default_configuration["OsdShowFPS"] = "0";
m_default_configuration["OsdShowCPU"] = "0";
m_default_configuration["OsdShowGPU"] = "0";
m_default_configuration["OsdShowResolution"] = "0";
m_default_configuration["OsdShowGSStats"] = "0";
m_default_configuration["OsdShowIndicators"] = "1";
m_default_configuration["OsdScale"] = "100";
m_default_configuration["override_GL_ARB_copy_image"] = "-1";
m_default_configuration["override_GL_ARB_clear_texture"] = "-1";
m_default_configuration["override_GL_ARB_clip_control"] = "-1";
m_default_configuration["override_GL_ARB_direct_state_access"] = "-1";
m_default_configuration["override_GL_ARB_draw_buffers_blend"] = "-1";
m_default_configuration["override_GL_ARB_gpu_shader5"] = "-1";
m_default_configuration["override_GL_ARB_shader_image_load_store"] = "-1";
m_default_configuration["override_GL_ARB_sparse_texture"] = "-1";
m_default_configuration["override_GL_ARB_sparse_texture2"] = "-1";
m_default_configuration["override_GL_ARB_texture_barrier"] = "-1";
m_default_configuration["OverrideTextureBarriers"] = "-1";
m_default_configuration["OverrideGeometryShaders"] = "-1";
m_default_configuration["paltex"] = "0";
m_default_configuration["png_compression_level"] = std::to_string(Z_BEST_SPEED);
m_default_configuration["PointListPalette"] = "0";
m_default_configuration["PrecacheTextureReplacements"] = "0";
m_default_configuration["preload_frame_with_gs_data"] = "0";
m_default_configuration["Renderer"] = std::to_string(static_cast<int>(GSRendererType::Auto));
m_default_configuration["save"] = "0";
m_default_configuration["savef"] = "0";
m_default_configuration["savel"] = "5000";
m_default_configuration["saven"] = "0";
m_default_configuration["savet"] = "0";
m_default_configuration["savez"] = "0";
m_default_configuration["ShadeBoost"] = "0";
m_default_configuration["ShadeBoost_Brightness"] = "50";
m_default_configuration["ShadeBoost_Contrast"] = "50";
m_default_configuration["ShadeBoost_Saturation"] = "50";
m_default_configuration["shaderfx"] = "0";
m_default_configuration["shaderfx_conf"] = "shaders/GS_FX_Settings.ini";
m_default_configuration["shaderfx_glsl"] = "shaders/GS.fx";
m_default_configuration["skip_duplicate_frames"] = "0";
m_default_configuration["texture_preloading"] = "0";
m_default_configuration["ThreadedPresentation"] = "0";
m_default_configuration["TVShader"] = "0";
m_default_configuration["upscale_multiplier"] = "1";
m_default_configuration["UseBlitSwapChain"] = "0";
m_default_configuration["UseDebugDevice"] = "0";
m_default_configuration["UserHacks"] = "0";
m_default_configuration["UserHacks_align_sprite_X"] = "0";
m_default_configuration["UserHacks_AutoFlush"] = "0";
m_default_configuration["UserHacks_DisableDepthSupport"] = "0";
m_default_configuration["UserHacks_Disable_Safe_Features"] = "0";
m_default_configuration["UserHacks_DisablePartialInvalidation"] = "0";
m_default_configuration["UserHacks_CPU_FB_Conversion"] = "0";
m_default_configuration["UserHacks_Half_Bottom_Override"] = "-1";
m_default_configuration["UserHacks_HalfPixelOffset"] = "0";
m_default_configuration["UserHacks_merge_pp_sprite"] = "0";
m_default_configuration["UserHacks_round_sprite_offset"] = "0";
m_default_configuration["UserHacks_SkipDraw_Start"] = "0";
m_default_configuration["UserHacks_SkipDraw_End"] = "0";
m_default_configuration["UserHacks_TCOffsetX"] = "0";
m_default_configuration["UserHacks_TCOffsetY"] = "0";
m_default_configuration["UserHacks_TextureInsideRt"] = "0";
m_default_configuration["UserHacks_TriFilter"] = std::to_string(static_cast<s8>(TriFiltering::Automatic));
m_default_configuration["UserHacks_WildHack"] = "0";
m_default_configuration["wrap_gs_mem"] = "0";
m_default_configuration["vsync"] = "0";
// clang-format on
}
#ifndef PCSX2_CORE
void GSApp::ReloadConfig()
{
if (m_configuration_map.empty())
return;
auto file = m_configuration_map.find("inifile");
if (file == m_configuration_map.end())
return;
// A map was built so reload it
std::string filename = file->second;
m_configuration_map.clear();
BuildConfigurationMap(filename.c_str());
}
void GSApp::BuildConfigurationMap(const char* lpFileName)
{
// Check if the map was already built
std::string inifile_value(lpFileName);
if (inifile_value.compare(m_configuration_map["inifile"]) == 0)
return;
m_configuration_map["inifile"] = inifile_value;
// Load config from file
#ifdef _WIN32
std::ifstream file(convert_utf8_to_utf16(lpFileName));
#else
std::ifstream file(lpFileName);
#endif
if (!file.is_open())
return;
std::string line;
while (std::getline(file, line))
{
const auto separator = line.find('=');
if (separator == std::string::npos)
continue;
std::string key = line.substr(0, separator);
// Trim trailing whitespace
key.erase(key.find_last_not_of(" \r\t") + 1);
if (key.empty())
continue;
// Only keep options that have a default value so older, no longer used
// ini options can be purged.
if (m_default_configuration.find(key) == m_default_configuration.end())
continue;
std::string value = line.substr(separator + 1);
// Trim leading whitespace
value.erase(0, value.find_first_not_of(" \r\t"));
m_configuration_map[key] = value;
}
}
#endif
void GSApp::SetConfigDir()
{
// we need to initialize the ini folder later at runtime than at theApp init, as
// core settings aren't populated yet, thus we do populate it if needed either when
// opening GS settings or init -- govanify
wxString iniName(L"GS.ini");
m_ini = EmuFolders::Settings.Combine(iniName).GetFullPath().ToUTF8();
}
std::string GSApp::GetConfigS(const char* entry)
{
char buff[4096] = {0};
auto def = m_default_configuration.find(entry);
if (def != m_default_configuration.end())
{
GetIniString(m_section.c_str(), entry, def->second.c_str(), buff, std::size(buff), m_ini.c_str());
}
else
{
fprintf(stderr, "Option %s doesn't have a default value\n", entry);
GetIniString(m_section.c_str(), entry, "", buff, std::size(buff), m_ini.c_str());
}
return {buff};
}
void GSApp::SetConfig(const char* entry, const char* value)
{
WriteIniString(m_section.c_str(), entry, value, m_ini.c_str());
}
int GSApp::GetConfigI(const char* entry)
{
auto def = m_default_configuration.find(entry);
if (def != m_default_configuration.end())
{
#ifndef PCSX2_CORE
return GetIniInt(m_section.c_str(), entry, std::stoi(def->second), m_ini.c_str());
#else
return Host::GetIntSettingValue("EmuCore/GS", entry, std::stoi(def->second));
#endif
}
else
{
fprintf(stderr, "Option %s doesn't have a default value\n", entry);
#ifndef PCSX2_CORE
return GetIniInt(m_section.c_str(), entry, 0, m_ini.c_str());
#else
return Host::GetIntSettingValue("EmuCore/GS", entry, 0);
#endif
}
}
bool GSApp::GetConfigB(const char* entry)
{
#ifndef PCSX2_CORE
return !!GetConfigI(entry);
#else
auto def = m_default_configuration.find(entry);
if (def != m_default_configuration.end())
{
return Host::GetBoolSettingValue("EmuCore/GS", entry, StringUtil::FromChars<bool>(def->second).value_or(false));
}
else
{
fprintf(stderr, "Option %s doesn't have a default value\n", entry);
return Host::GetBoolSettingValue("EmuCore/GS", entry, false);
}
#endif
}
void GSApp::SetConfig(const char* entry, int value)
{
char buff[32] = {0};
sprintf(buff, "%d", value);
SetConfig(entry, buff);
}
#ifdef PCSX2_CORE
static void HotkeyAdjustUpscaleMultiplier(s32 delta)
{
const u32 new_multiplier = static_cast<u32>(std::clamp(static_cast<s32>(EmuConfig.GS.UpscaleMultiplier) + delta, 1, 8));
Host::AddKeyedFormattedOSDMessage("UpscaleMultiplierChanged", 10.0f, "Upscale multiplier set to %ux.", new_multiplier);
EmuConfig.GS.UpscaleMultiplier = new_multiplier;
// this is pretty slow. we only really need to flush the TC and recompile shaders.
// TODO(Stenzek): Make it faster at some point in the future.
GetMTGS().ApplySettings();
}
static void HotkeyAdjustZoom(double delta)
{
const double new_zoom = std::clamp(EmuConfig.GS.Zoom + delta, 1.0, 200.0);
Host::AddKeyedFormattedOSDMessage("ZoomChanged", 10.0f, "Zoom set to %.1f%%.", new_zoom);
EmuConfig.GS.Zoom = new_zoom;
// no need to go through the full settings update for this
GetMTGS().RunOnGSThread([new_zoom]() { GSConfig.Zoom = new_zoom; });
}
BEGIN_HOTKEY_LIST(g_gs_hotkeys){
"ToggleSoftwareRendering", "Graphics", "Toggle Software Rendering", [](bool pressed) {
if (!pressed)
GetMTGS().ToggleSoftwareRendering();
}},
{"IncreaseUpscaleMultiplier", "Graphics", "Increase Upscale Multiplier", [](bool pressed) {
if (!pressed)
HotkeyAdjustUpscaleMultiplier(1);
}},
{"DecreaseUpscaleMultiplier", "Graphics", "Decrease Upscale Multiplier", [](bool pressed) {
if (!pressed)
HotkeyAdjustUpscaleMultiplier(-1);
}},
{"CycleAspectRatio", "Graphics", "Cycle Aspect Ratio", [](bool pressed) {
if (pressed)
return;
GetMTGS().RunOnGSThread([]() {
GSConfig.AspectRatio = static_cast<AspectRatioType>((static_cast<int>(GSConfig.AspectRatio) + 1) % static_cast<int>(AspectRatioType::MaxCount));
Host::AddKeyedFormattedOSDMessage("CycleAspectRatio", 10.0f, "Aspect ratio set to '%s'.", Pcsx2Config::GSOptions::AspectRatioNames[static_cast<int>(GSConfig.AspectRatio)]);
});
}},
{"CycleMipmapMode", "Graphics", "Cycle Hardware Mipmapping", [](bool pressed) {
if (pressed)
return;
static constexpr s32 CYCLE_COUNT = 4;
static constexpr std::array<const char*, CYCLE_COUNT> option_names = {{"Automatic", "Off", "Basic (Generated)", "Full (PS2)"}};
const HWMipmapLevel new_level = static_cast<HWMipmapLevel>(((static_cast<s32>(EmuConfig.GS.HWMipmap) + 2) % CYCLE_COUNT) - 1);
Host::AddKeyedFormattedOSDMessage("CycleMipmapMode", 10.0f, "Hardware mipmapping set to '%s'.", option_names[static_cast<s32>(new_level) + 1]);
EmuConfig.GS.HWMipmap = new_level;
GetMTGS().RunOnGSThread([new_level]() {
GSConfig.HWMipmap = new_level;
s_gs->PurgeTextureCache();
s_gs->PurgePool();
});
}},
{"CycleInterlaceMode", "Graphics", "Cycle Deinterlace Mode", [](bool pressed) {
if (pressed)
return;
static constexpr std::array<const char*, static_cast<int>(GSInterlaceMode::Count)> option_names = {{
"Off",
"Weave (Top Field First)",
"Weave (Bottom Field First)",
"Bob (Top Field First)",
"Bob (Bottom Field First)",
"Blend (Top Field First)",
"Blend (Bottom Field First)",
"Automatic",
}};
const GSInterlaceMode new_mode = static_cast<GSInterlaceMode>((static_cast<s32>(EmuConfig.GS.InterlaceMode) + 1) % static_cast<s32>(GSInterlaceMode::Count));
Host::AddKeyedFormattedOSDMessage("CycleMipmapMode", 10.0f, "Interlace mode set to '%s'.", option_names[static_cast<s32>(new_mode)]);
EmuConfig.GS.InterlaceMode = new_mode;
GetMTGS().RunOnGSThread([new_mode]() { GSConfig.InterlaceMode = new_mode; });
}},
{"ZoomIn", "Graphics", "Zoom In", [](bool pressed) {
if (!pressed)
HotkeyAdjustZoom(1.0);
}},
{"ZoomOut", "Graphics", "Zoom Out", [](bool pressed) {
if (!pressed)
HotkeyAdjustZoom(-1.0);
}},
END_HOTKEY_LIST()
#endif