mirror of https://github.com/PCSX2/pcsx2.git
GS: Uncap upscale multiplier subject to GPU limits
This commit is contained in:
parent
46e30467de
commit
315d30fe4c
|
@ -140,13 +140,6 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
// HW Settings
|
// HW Settings
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
static const char* upscale_entries[] = {"Native (PS2) (Default)", "1.25x Native", "1.5x Native", "1.75x Native", "2x Native (~720p)",
|
|
||||||
"2.25x Native", "2.5x Native", "2.75x Native", "3x Native (~1080p)", "3.5x Native", "4x Native (~1440p/2K)", "5x Native (~1620p)",
|
|
||||||
"6x Native (~2160p/4K)", "7x Native (~2520p)", "8x Native (~2880p/5K)", nullptr};
|
|
||||||
static const char* upscale_values[] = {
|
|
||||||
"1", "1.25", "1.5", "1.75", "2", "2.25", "2.5", "2.75", "3", "3.5", "4", "5", "6", "7", "8", nullptr};
|
|
||||||
SettingWidgetBinder::BindWidgetToEnumSetting(
|
|
||||||
sif, m_ui.upscaleMultiplier, "EmuCore/GS", "upscale_multiplier", upscale_entries, upscale_values, "1.0");
|
|
||||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.textureFiltering, "EmuCore/GS", "filter", static_cast<int>(BiFiltering::PS2));
|
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.textureFiltering, "EmuCore/GS", "filter", static_cast<int>(BiFiltering::PS2));
|
||||||
SettingWidgetBinder::BindWidgetToIntSetting(
|
SettingWidgetBinder::BindWidgetToIntSetting(
|
||||||
sif, m_ui.trilinearFiltering, "EmuCore/GS", "TriFilter", static_cast<int>(TriFiltering::Automatic), -1);
|
sif, m_ui.trilinearFiltering, "EmuCore/GS", "TriFilter", static_cast<int>(TriFiltering::Automatic), -1);
|
||||||
|
@ -158,6 +151,8 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
|
||||||
sif, m_ui.blending, "EmuCore/GS", "accurate_blending_unit", static_cast<int>(AccBlendLevel::Basic));
|
sif, m_ui.blending, "EmuCore/GS", "accurate_blending_unit", static_cast<int>(AccBlendLevel::Basic));
|
||||||
SettingWidgetBinder::BindWidgetToIntSetting(
|
SettingWidgetBinder::BindWidgetToIntSetting(
|
||||||
sif, m_ui.texturePreloading, "EmuCore/GS", "texture_preloading", static_cast<int>(TexturePreloadingLevel::Off));
|
sif, m_ui.texturePreloading, "EmuCore/GS", "texture_preloading", static_cast<int>(TexturePreloadingLevel::Off));
|
||||||
|
connect(m_ui.upscaleMultiplier, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||||
|
&GraphicsSettingsWidget::onUpscaleMultiplierChanged);
|
||||||
connect(m_ui.trilinearFiltering, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
connect(m_ui.trilinearFiltering, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||||
&GraphicsSettingsWidget::onTrilinearFilteringChanged);
|
&GraphicsSettingsWidget::onTrilinearFilteringChanged);
|
||||||
onTrilinearFilteringChanged();
|
onTrilinearFilteringChanged();
|
||||||
|
@ -1036,10 +1031,9 @@ void GraphicsSettingsWidget::updateRendererDependentOptions()
|
||||||
m_ui.exclusiveFullscreenControl->setEnabled(is_auto || is_vk);
|
m_ui.exclusiveFullscreenControl->setEnabled(is_auto || is_vk);
|
||||||
|
|
||||||
// populate adapters
|
// populate adapters
|
||||||
std::vector<std::string> adapters;
|
std::vector<GSAdapterInfo> adapters = GSGetAdapterInfo(type);
|
||||||
std::vector<std::string> fullscreen_modes;
|
const GSAdapterInfo* current_adapter_info = nullptr;
|
||||||
GSGetAdaptersAndFullscreenModes(type, &adapters, &fullscreen_modes);
|
|
||||||
|
|
||||||
// fill+select adapters
|
// fill+select adapters
|
||||||
{
|
{
|
||||||
QSignalBlocker sb(m_ui.adapterDropdown);
|
QSignalBlocker sb(m_ui.adapterDropdown);
|
||||||
|
@ -1062,12 +1056,17 @@ void GraphicsSettingsWidget::updateRendererDependentOptions()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const std::string& adapter : adapters)
|
for (const GSAdapterInfo& adapter : adapters)
|
||||||
{
|
{
|
||||||
m_ui.adapterDropdown->addItem(QString::fromStdString(adapter));
|
m_ui.adapterDropdown->addItem(QString::fromStdString(adapter.name));
|
||||||
if (current_adapter == adapter)
|
if (current_adapter == adapter.name)
|
||||||
|
{
|
||||||
m_ui.adapterDropdown->setCurrentIndex(m_ui.adapterDropdown->count() - 1);
|
m_ui.adapterDropdown->setCurrentIndex(m_ui.adapterDropdown->count() - 1);
|
||||||
|
current_adapter_info = &adapter;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
current_adapter_info = (current_adapter_info || adapters.empty()) ? current_adapter_info : &adapters.front();
|
||||||
}
|
}
|
||||||
|
|
||||||
// fill+select fullscreen modes
|
// fill+select fullscreen modes
|
||||||
|
@ -1090,13 +1089,90 @@ void GraphicsSettingsWidget::updateRendererDependentOptions()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const std::string& fs_mode : fullscreen_modes)
|
if (current_adapter_info)
|
||||||
{
|
{
|
||||||
m_ui.fullscreenModes->addItem(QString::fromStdString(fs_mode));
|
for (const std::string& fs_mode : current_adapter_info->fullscreen_modes)
|
||||||
if (current_mode == fs_mode)
|
{
|
||||||
m_ui.fullscreenModes->setCurrentIndex(m_ui.fullscreenModes->count() - 1);
|
m_ui.fullscreenModes->addItem(QString::fromStdString(fs_mode));
|
||||||
|
if (current_mode == fs_mode)
|
||||||
|
m_ui.fullscreenModes->setCurrentIndex(m_ui.fullscreenModes->count() - 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// assume the GPU can do 10K textures.
|
||||||
|
const u32 max_upscale_multiplier = std::max(current_adapter_info ? current_adapter_info->max_upscale_multiplier : 0u, 10u);
|
||||||
|
populateUpscaleMultipliers(max_upscale_multiplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphicsSettingsWidget::populateUpscaleMultipliers(u32 max_upscale_multiplier)
|
||||||
|
{
|
||||||
|
static constexpr std::pair<const char*, float> templates[] = {
|
||||||
|
{QT_TRANSLATE_NOOP("GraphicsSettingsWidget", "Native (PS2) (Default)"), 1.0f},
|
||||||
|
{QT_TRANSLATE_NOOP("GraphicsSettingsWidget", "1.25x Native"), 1.25f},
|
||||||
|
{QT_TRANSLATE_NOOP("GraphicsSettingsWidget", "1.5x Native"), 1.5f},
|
||||||
|
{QT_TRANSLATE_NOOP("GraphicsSettingsWidget", "1.75x Native"), 1.75f},
|
||||||
|
{QT_TRANSLATE_NOOP("GraphicsSettingsWidget", "2x Native (~720p)"), 2.0f},
|
||||||
|
{QT_TRANSLATE_NOOP("GraphicsSettingsWidget", "2.25x Native"), 2.25f},
|
||||||
|
{QT_TRANSLATE_NOOP("GraphicsSettingsWidget", "2.5x Native"), 2.5f},
|
||||||
|
{QT_TRANSLATE_NOOP("GraphicsSettingsWidget", "2.75x Native"), 2.75f},
|
||||||
|
{QT_TRANSLATE_NOOP("GraphicsSettingsWidget", "3x Native (~1080p)"), 3.0f},
|
||||||
|
{QT_TRANSLATE_NOOP("GraphicsSettingsWidget", "3.5x Native"), 3.5f},
|
||||||
|
{QT_TRANSLATE_NOOP("GraphicsSettingsWidget", "4x Native (~1440p/2K)"), 4.0f},
|
||||||
|
{QT_TRANSLATE_NOOP("GraphicsSettingsWidget", "5x Native (~1620p)"), 5.0f},
|
||||||
|
{QT_TRANSLATE_NOOP("GraphicsSettingsWidget", "6x Native (~2160p/4K)"), 6.0f},
|
||||||
|
{QT_TRANSLATE_NOOP("GraphicsSettingsWidget", "7x Native (~2520p)"), 7.0f},
|
||||||
|
{QT_TRANSLATE_NOOP("GraphicsSettingsWidget", "8x Native (~2880p/5K)"), 8.0f},
|
||||||
|
};
|
||||||
|
static constexpr u32 max_template_multiplier = 8;
|
||||||
|
|
||||||
|
// Limit the dropdown to 12x if we're not showing advanced settings. Save the noobs.
|
||||||
|
static constexpr u32 max_non_advanced_multiplier = 12;
|
||||||
|
|
||||||
|
QSignalBlocker sb(m_ui.upscaleMultiplier);
|
||||||
|
m_ui.upscaleMultiplier->clear();
|
||||||
|
|
||||||
|
for (const auto& [name, value] : templates)
|
||||||
|
{
|
||||||
|
if (value > max_upscale_multiplier)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
m_ui.upscaleMultiplier->addItem(tr(name), QVariant(value));
|
||||||
|
}
|
||||||
|
const u32 max_shown_multiplier = QtHost::ShouldShowAdvancedSettings() ?
|
||||||
|
max_upscale_multiplier :
|
||||||
|
std::min(max_upscale_multiplier, max_non_advanced_multiplier);
|
||||||
|
for (u32 i = max_template_multiplier + 1; i <= max_shown_multiplier; i++)
|
||||||
|
m_ui.upscaleMultiplier->addItem(tr("%1x Native ").arg(i), QVariant(static_cast<float>(i)));
|
||||||
|
|
||||||
|
const float global_value = Host::GetBaseFloatSettingValue("EmuCore/GS", "upscale_multiplier", 1.0f);
|
||||||
|
if (m_dialog->isPerGameSettings())
|
||||||
|
{
|
||||||
|
m_ui.upscaleMultiplier->addItem(tr("Use Global Setting [%1]").arg(QStringLiteral("%1x").arg(global_value)));
|
||||||
|
|
||||||
|
const std::optional<float> config_value = m_dialog->getFloatValue("EmuCore/GS", "upscale_multiplier", std::nullopt);
|
||||||
|
if (config_value.has_value())
|
||||||
|
{
|
||||||
|
if (int index = m_ui.upscaleMultiplier->findData(QVariant(config_value.value())); index > 0)
|
||||||
|
m_ui.upscaleMultiplier->setCurrentIndex(index);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_ui.upscaleMultiplier->setCurrentIndex(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (int index = m_ui.upscaleMultiplier->findData(QVariant(global_value)); index > 0)
|
||||||
|
m_ui.upscaleMultiplier->setCurrentIndex(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphicsSettingsWidget::onUpscaleMultiplierChanged()
|
||||||
|
{
|
||||||
|
const QVariant data = m_ui.upscaleMultiplier->currentData();
|
||||||
|
m_dialog->setFloatSettingValue("EmuCore/GS", "upscale_multiplier",
|
||||||
|
data.isValid() ? std::optional<float>(data.toFloat()) : std::optional<float>());
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsSettingsWidget::resetManualHardwareFixes()
|
void GraphicsSettingsWidget::resetManualHardwareFixes()
|
||||||
|
|
|
@ -29,6 +29,7 @@ private Q_SLOTS:
|
||||||
void onSWTextureFilteringChange();
|
void onSWTextureFilteringChange();
|
||||||
void onRendererChanged(int index);
|
void onRendererChanged(int index);
|
||||||
void onAdapterChanged(int index);
|
void onAdapterChanged(int index);
|
||||||
|
void onUpscaleMultiplierChanged();
|
||||||
void onTrilinearFilteringChanged();
|
void onTrilinearFilteringChanged();
|
||||||
void onGpuPaletteConversionChanged(int state);
|
void onGpuPaletteConversionChanged(int state);
|
||||||
void onCPUSpriteRenderBWChanged();
|
void onCPUSpriteRenderBWChanged();
|
||||||
|
@ -46,6 +47,7 @@ private Q_SLOTS:
|
||||||
private:
|
private:
|
||||||
GSRendererType getEffectiveRenderer() const;
|
GSRendererType getEffectiveRenderer() const;
|
||||||
void updateRendererDependentOptions();
|
void updateRendererDependentOptions();
|
||||||
|
void populateUpscaleMultipliers(u32 max_upscale_multiplier);
|
||||||
void resetManualHardwareFixes();
|
void resetManualHardwareFixes();
|
||||||
|
|
||||||
SettingsWindow* m_dialog;
|
SettingsWindow* m_dialog;
|
||||||
|
|
|
@ -48,6 +48,8 @@
|
||||||
#include "common/SmallString.h"
|
#include "common/SmallString.h"
|
||||||
#include "common/StringUtil.h"
|
#include "common/StringUtil.h"
|
||||||
|
|
||||||
|
#include "IconsFontAwesome5.h"
|
||||||
|
|
||||||
#include "fmt/format.h"
|
#include "fmt/format.h"
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
@ -171,6 +173,24 @@ static void CloseGSDevice(bool clear_state)
|
||||||
g_gs_device.reset();
|
g_gs_device.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void GSClampUpscaleMultiplier(Pcsx2Config::GSOptions& config)
|
||||||
|
{
|
||||||
|
const u32 max_upscale_multiplier = GSGetMaxUpscaleMultiplier(g_gs_device->GetMaxTextureSize());
|
||||||
|
if (config.UpscaleMultiplier <= static_cast<float>(max_upscale_multiplier))
|
||||||
|
{
|
||||||
|
// Shouldn't happen, but just in case.
|
||||||
|
if (config.UpscaleMultiplier < 1.0f)
|
||||||
|
config.UpscaleMultiplier = 1.0f;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Host::AddIconOSDMessage("GSUpscaleMultiplierInvalid", ICON_FA_EXCLAMATION_TRIANGLE,
|
||||||
|
fmt::format(TRANSLATE_FS("GS", "Configured upscale multiplier {}x is above your GPU's supported multiplier of {}x."),
|
||||||
|
config.UpscaleMultiplier, max_upscale_multiplier),
|
||||||
|
Host::OSD_WARNING_DURATION);
|
||||||
|
config.UpscaleMultiplier = static_cast<float>(max_upscale_multiplier);
|
||||||
|
}
|
||||||
|
|
||||||
static bool OpenGSRenderer(GSRendererType renderer, u8* basemem)
|
static bool OpenGSRenderer(GSRendererType renderer, u8* basemem)
|
||||||
{
|
{
|
||||||
// Must be done first, initialization routines in GSState use GSIsHardwareRenderer().
|
// Must be done first, initialization routines in GSState use GSIsHardwareRenderer().
|
||||||
|
@ -184,6 +204,7 @@ static bool OpenGSRenderer(GSRendererType renderer, u8* basemem)
|
||||||
}
|
}
|
||||||
else if (renderer != GSRendererType::SW)
|
else if (renderer != GSRendererType::SW)
|
||||||
{
|
{
|
||||||
|
GSClampUpscaleMultiplier(GSConfig);
|
||||||
g_gs_renderer = std::make_unique<GSRendererHW>();
|
g_gs_renderer = std::make_unique<GSRendererHW>();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -548,9 +569,9 @@ std::optional<float> GSGetHostRefreshRate()
|
||||||
return surface_refresh_rate;
|
return surface_refresh_rate;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GSGetAdaptersAndFullscreenModes(
|
std::vector<GSAdapterInfo> GSGetAdapterInfo(GSRendererType renderer)
|
||||||
GSRendererType renderer, std::vector<std::string>* adapters, std::vector<std::string>* fullscreen_modes)
|
|
||||||
{
|
{
|
||||||
|
std::vector<GSAdapterInfo> ret;
|
||||||
switch (renderer)
|
switch (renderer)
|
||||||
{
|
{
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
@ -559,12 +580,7 @@ void GSGetAdaptersAndFullscreenModes(
|
||||||
{
|
{
|
||||||
auto factory = D3D::CreateFactory(false);
|
auto factory = D3D::CreateFactory(false);
|
||||||
if (factory)
|
if (factory)
|
||||||
{
|
ret = D3D::GetAdapterInfo(factory.get());
|
||||||
if (adapters)
|
|
||||||
*adapters = D3D::GetAdapterNames(factory.get());
|
|
||||||
if (fullscreen_modes)
|
|
||||||
*fullscreen_modes = D3D::GetFullscreenModes(factory.get(), EmuConfig.GS.Adapter);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
|
@ -572,7 +588,7 @@ void GSGetAdaptersAndFullscreenModes(
|
||||||
#ifdef ENABLE_VULKAN
|
#ifdef ENABLE_VULKAN
|
||||||
case GSRendererType::VK:
|
case GSRendererType::VK:
|
||||||
{
|
{
|
||||||
GSDeviceVK::GetAdaptersAndFullscreenModes(adapters, fullscreen_modes);
|
ret = GSDeviceVK::GetAdapterInfo();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
|
@ -580,8 +596,7 @@ void GSGetAdaptersAndFullscreenModes(
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
case GSRendererType::Metal:
|
case GSRendererType::Metal:
|
||||||
{
|
{
|
||||||
if (adapters)
|
ret = GetMetalAdapterList();
|
||||||
*adapters = GetMetalAdapterList();
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
|
@ -589,6 +604,14 @@ void GSGetAdaptersAndFullscreenModes(
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 GSGetMaxUpscaleMultiplier(u32 max_texture_size)
|
||||||
|
{
|
||||||
|
// Maximum GS target size is 1280x1280. Assume we want to upscale the max size target.
|
||||||
|
return std::max(max_texture_size / 1280, 1u);
|
||||||
}
|
}
|
||||||
|
|
||||||
GSVideoMode GSgetDisplayMode()
|
GSVideoMode GSgetDisplayMode()
|
||||||
|
@ -712,6 +735,9 @@ void GSUpdateConfig(const Pcsx2Config::GSOptions& new_config)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure upscale multiplier is in range.
|
||||||
|
GSClampUpscaleMultiplier(GSConfig);
|
||||||
|
|
||||||
// Options which aren't using the global struct yet, so we need to recreate all GS objects.
|
// Options which aren't using the global struct yet, so we need to recreate all GS objects.
|
||||||
if (GSConfig.SWExtraThreads != old_config.SWExtraThreads ||
|
if (GSConfig.SWExtraThreads != old_config.SWExtraThreads ||
|
||||||
GSConfig.SWExtraThreadsHeight != old_config.SWExtraThreadsHeight)
|
GSConfig.SWExtraThreadsHeight != old_config.SWExtraThreadsHeight)
|
||||||
|
|
|
@ -41,6 +41,14 @@ enum class GSDisplayAlignment
|
||||||
RightOrBottom
|
RightOrBottom
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct GSAdapterInfo
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
std::vector<std::string> fullscreen_modes;
|
||||||
|
u32 max_texture_size;
|
||||||
|
u32 max_upscale_multiplier;
|
||||||
|
};
|
||||||
|
|
||||||
class SmallStringBase;
|
class SmallStringBase;
|
||||||
|
|
||||||
// Returns the ID for the specified function, otherwise -1.
|
// Returns the ID for the specified function, otherwise -1.
|
||||||
|
@ -83,8 +91,8 @@ GSRendererType GSGetCurrentRenderer();
|
||||||
bool GSIsHardwareRenderer();
|
bool GSIsHardwareRenderer();
|
||||||
bool GSWantsExclusiveFullscreen();
|
bool GSWantsExclusiveFullscreen();
|
||||||
std::optional<float> GSGetHostRefreshRate();
|
std::optional<float> GSGetHostRefreshRate();
|
||||||
void GSGetAdaptersAndFullscreenModes(
|
std::vector<GSAdapterInfo> GSGetAdapterInfo(GSRendererType renderer);
|
||||||
GSRendererType renderer, std::vector<std::string>* adapters, std::vector<std::string>* fullscreen_modes);
|
u32 GSGetMaxUpscaleMultiplier(u32 max_texture_size);
|
||||||
GSVideoMode GSgetDisplayMode();
|
GSVideoMode GSgetDisplayMode();
|
||||||
void GSgetInternalResolution(int* width, int* height);
|
void GSgetInternalResolution(int* width, int* height);
|
||||||
void GSgetStats(SmallStringBase& info);
|
void GSgetStats(SmallStringBase& info);
|
||||||
|
|
|
@ -435,7 +435,8 @@ void GSDevice::TextureRecycleDeleter::operator()(GSTexture* const tex)
|
||||||
|
|
||||||
GSTexture* GSDevice::FetchSurface(GSTexture::Type type, int width, int height, int levels, GSTexture::Format format, bool clear, bool prefer_unused_texture)
|
GSTexture* GSDevice::FetchSurface(GSTexture::Type type, int width, int height, int levels, GSTexture::Format format, bool clear, bool prefer_unused_texture)
|
||||||
{
|
{
|
||||||
const GSVector2i size(width, height);
|
const GSVector2i size(std::clamp(width, 1, static_cast<int>(g_gs_device->GetMaxTextureSize())),
|
||||||
|
std::clamp(height, 1, static_cast<int>(g_gs_device->GetMaxTextureSize())));
|
||||||
FastList<GSTexture*>& pool = m_pool[type != GSTexture::Type::Texture];
|
FastList<GSTexture*>& pool = m_pool[type != GSTexture::Type::Texture];
|
||||||
|
|
||||||
GSTexture* t = nullptr;
|
GSTexture* t = nullptr;
|
||||||
|
@ -475,14 +476,14 @@ GSTexture* GSDevice::FetchSurface(GSTexture::Type type, int width, int height, i
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
t = CreateSurface(type, width, height, levels, format);
|
t = CreateSurface(type, size.x, size.y, levels, format);
|
||||||
if (!t)
|
if (!t)
|
||||||
{
|
{
|
||||||
Console.Error("GS: Memory allocation failure for %dx%d texture. Purging pool and retrying.", width, height);
|
ERROR_LOG("GS: Memory allocation failure for {}x{} texture. Purging pool and retrying.", size.x, size.y);
|
||||||
PurgePool();
|
PurgePool();
|
||||||
if (!t)
|
if (!t)
|
||||||
{
|
{
|
||||||
Console.Error("GS: Memory allocation failure for %dx%d texture after purging pool.", width, height);
|
ERROR_LOG("GS: Memory allocation failure for {}x{} texture after purging pool.", size.x, size.y);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -804,6 +804,7 @@ public:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
FeatureSupport m_features;
|
FeatureSupport m_features;
|
||||||
|
u32 m_max_texture_size = 0;
|
||||||
|
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
|
@ -888,6 +889,7 @@ public:
|
||||||
__fi u64 GetPoolMemoryUsage() const { return m_pool_memory_usage; }
|
__fi u64 GetPoolMemoryUsage() const { return m_pool_memory_usage; }
|
||||||
|
|
||||||
__fi FeatureSupport Features() const { return m_features; }
|
__fi FeatureSupport Features() const { return m_features; }
|
||||||
|
__fi u32 GetMaxTextureSize() const { return m_max_texture_size; }
|
||||||
|
|
||||||
__fi const WindowInfo& GetWindowInfo() const { return m_window_info; }
|
__fi const WindowInfo& GetWindowInfo() const { return m_window_info; }
|
||||||
__fi s32 GetWindowWidth() const { return static_cast<s32>(m_window_info.surface_width); }
|
__fi s32 GetWindowWidth() const { return static_cast<s32>(m_window_info.surface_width); }
|
||||||
|
|
|
@ -42,10 +42,10 @@ wil::com_ptr_nothrow<IDXGIFactory5> D3D::CreateFactory(bool debug)
|
||||||
return factory;
|
return factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string FixupDuplicateAdapterNames(const std::vector<std::string>& adapter_names, std::string adapter_name)
|
static std::string FixupDuplicateAdapterNames(const std::vector<GSAdapterInfo>& adapters, std::string adapter_name)
|
||||||
{
|
{
|
||||||
if (std::any_of(adapter_names.begin(), adapter_names.end(),
|
if (std::any_of(adapters.begin(), adapters.end(),
|
||||||
[&adapter_name](const std::string& other) { return (adapter_name == other); }))
|
[&adapter_name](const GSAdapterInfo& other) { return (adapter_name == other.name); }))
|
||||||
{
|
{
|
||||||
std::string original_adapter_name = std::move(adapter_name);
|
std::string original_adapter_name = std::move(adapter_name);
|
||||||
|
|
||||||
|
@ -54,73 +54,72 @@ static std::string FixupDuplicateAdapterNames(const std::vector<std::string>& ad
|
||||||
{
|
{
|
||||||
adapter_name = fmt::format("{} ({})", original_adapter_name.c_str(), current_extra);
|
adapter_name = fmt::format("{} ({})", original_adapter_name.c_str(), current_extra);
|
||||||
current_extra++;
|
current_extra++;
|
||||||
} while (std::any_of(adapter_names.begin(), adapter_names.end(),
|
} while (std::any_of(adapters.begin(), adapters.end(),
|
||||||
[&adapter_name](const std::string& other) { return (adapter_name == other); }));
|
[&adapter_name](const GSAdapterInfo& other) { return (adapter_name == other.name); }));
|
||||||
}
|
}
|
||||||
|
|
||||||
return adapter_name;
|
return adapter_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> D3D::GetAdapterNames(IDXGIFactory5* factory)
|
std::vector<GSAdapterInfo> D3D::GetAdapterInfo(IDXGIFactory5* factory)
|
||||||
{
|
{
|
||||||
std::vector<std::string> adapter_names;
|
std::vector<GSAdapterInfo> adapters;
|
||||||
|
|
||||||
wil::com_ptr_nothrow<IDXGIAdapter1> adapter;
|
wil::com_ptr_nothrow<IDXGIAdapter1> adapter;
|
||||||
for (u32 index = 0;; index++)
|
for (u32 index = 0;; index++)
|
||||||
{
|
{
|
||||||
const HRESULT hr = factory->EnumAdapters1(index, adapter.put());
|
HRESULT hr = factory->EnumAdapters1(index, adapter.put());
|
||||||
if (hr == DXGI_ERROR_NOT_FOUND)
|
if (hr == DXGI_ERROR_NOT_FOUND)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (FAILED(hr))
|
if (FAILED(hr))
|
||||||
{
|
{
|
||||||
Console.Error(fmt::format("IDXGIFactory2::EnumAdapters() returned %08X", hr));
|
ERROR_LOG("IDXGIFactory2::EnumAdapters() returned {:08X}", static_cast<unsigned>(hr));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
adapter_names.push_back(FixupDuplicateAdapterNames(adapter_names, GetAdapterName(adapter.get())));
|
GSAdapterInfo ai;
|
||||||
|
ai.name = FixupDuplicateAdapterNames(adapters, GetAdapterName(adapter.get()));
|
||||||
|
|
||||||
|
// Unfortunately we can't get any properties such as feature level without creating the device.
|
||||||
|
// So just assume a max of the D3D11 max across the board.
|
||||||
|
ai.max_texture_size = D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION;
|
||||||
|
ai.max_upscale_multiplier = GSGetMaxUpscaleMultiplier(ai.max_texture_size);
|
||||||
|
|
||||||
|
wil::com_ptr_nothrow<IDXGIOutput> output;
|
||||||
|
if (SUCCEEDED(hr = adapter->EnumOutputs(0, &output)))
|
||||||
|
{
|
||||||
|
UINT num_modes = 0;
|
||||||
|
if (SUCCEEDED(hr = output->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, 0, &num_modes, nullptr)))
|
||||||
|
{
|
||||||
|
std::vector<DXGI_MODE_DESC> dmodes(num_modes);
|
||||||
|
if (SUCCEEDED(hr = output->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, 0, &num_modes, dmodes.data())))
|
||||||
|
{
|
||||||
|
for (const DXGI_MODE_DESC& mode : dmodes)
|
||||||
|
{
|
||||||
|
ai.fullscreen_modes.push_back(GSDevice::GetFullscreenModeString(mode.Width, mode.Height,
|
||||||
|
static_cast<float>(mode.RefreshRate.Numerator) / static_cast<float>(mode.RefreshRate.Denominator)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ERROR_LOG("GetDisplayModeList() (2) failed: {:08X}", static_cast<unsigned>(hr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ERROR_LOG("GetDisplayModeList() failed: {:08X}", static_cast<unsigned>(hr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ERROR_LOG("EnumOutputs() failed: {:08X}", static_cast<unsigned>(hr));
|
||||||
|
}
|
||||||
|
|
||||||
|
adapters.push_back(std::move(ai));
|
||||||
}
|
}
|
||||||
|
|
||||||
return adapter_names;
|
return adapters;
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::string> D3D::GetFullscreenModes(IDXGIFactory5* factory, const std::string_view adapter_name)
|
|
||||||
{
|
|
||||||
std::vector<std::string> modes;
|
|
||||||
HRESULT hr;
|
|
||||||
|
|
||||||
wil::com_ptr_nothrow<IDXGIAdapter1> adapter = GetChosenOrFirstAdapter(factory, adapter_name);
|
|
||||||
if (!adapter)
|
|
||||||
return modes;
|
|
||||||
|
|
||||||
wil::com_ptr_nothrow<IDXGIOutput> output;
|
|
||||||
if (FAILED(hr = adapter->EnumOutputs(0, &output)))
|
|
||||||
{
|
|
||||||
Console.Error("EnumOutputs() failed: %08X", hr);
|
|
||||||
return modes;
|
|
||||||
}
|
|
||||||
|
|
||||||
UINT num_modes = 0;
|
|
||||||
if (FAILED(hr = output->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, 0, &num_modes, nullptr)))
|
|
||||||
{
|
|
||||||
Console.Error("GetDisplayModeList() failed: %08X", hr);
|
|
||||||
return modes;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<DXGI_MODE_DESC> dmodes(num_modes);
|
|
||||||
if (FAILED(hr = output->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, 0, &num_modes, dmodes.data())))
|
|
||||||
{
|
|
||||||
Console.Error("GetDisplayModeList() (2) failed: %08X", hr);
|
|
||||||
return modes;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const DXGI_MODE_DESC& mode : dmodes)
|
|
||||||
{
|
|
||||||
modes.push_back(GSDevice::GetFullscreenModeString(mode.Width, mode.Height,
|
|
||||||
static_cast<float>(mode.RefreshRate.Numerator) / static_cast<float>(mode.RefreshRate.Denominator)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return modes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool D3D::GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory, const RECT& window_rect, u32 width,
|
bool D3D::GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory, const RECT& window_rect, u32 width,
|
||||||
|
@ -187,7 +186,7 @@ bool D3D::GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory, const
|
||||||
if (FAILED(hr = intersecting_output->FindClosestMatchingMode(&request_mode, fullscreen_mode, nullptr)) ||
|
if (FAILED(hr = intersecting_output->FindClosestMatchingMode(&request_mode, fullscreen_mode, nullptr)) ||
|
||||||
request_mode.Format != format)
|
request_mode.Format != format)
|
||||||
{
|
{
|
||||||
Console.Error("Failed to find closest matching mode, hr=%08X", hr);
|
ERROR_LOG("Failed to find closest matching mode, hr={:08X}", static_cast<unsigned>(hr));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,7 +202,7 @@ wil::com_ptr_nothrow<IDXGIAdapter1> D3D::GetAdapterByName(IDXGIFactory5* factory
|
||||||
|
|
||||||
// This might seem a bit odd to cache the names.. but there's a method to the madness.
|
// This might seem a bit odd to cache the names.. but there's a method to the madness.
|
||||||
// We might have two GPUs with the same name... :)
|
// We might have two GPUs with the same name... :)
|
||||||
std::vector<std::string> adapter_names;
|
std::vector<GSAdapterInfo> adapter_names;
|
||||||
|
|
||||||
wil::com_ptr_nothrow<IDXGIAdapter1> adapter;
|
wil::com_ptr_nothrow<IDXGIAdapter1> adapter;
|
||||||
for (u32 index = 0;; index++)
|
for (u32 index = 0;; index++)
|
||||||
|
@ -214,18 +213,19 @@ wil::com_ptr_nothrow<IDXGIAdapter1> D3D::GetAdapterByName(IDXGIFactory5* factory
|
||||||
|
|
||||||
if (FAILED(hr))
|
if (FAILED(hr))
|
||||||
{
|
{
|
||||||
Console.Error(fmt::format("IDXGIFactory2::EnumAdapters() returned %08X", hr));
|
ERROR_LOG("IDXGIFactory2::EnumAdapters() returned {:08X}", static_cast<unsigned>(hr));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string adapter_name = FixupDuplicateAdapterNames(adapter_names, GetAdapterName(adapter.get()));
|
GSAdapterInfo ai;
|
||||||
if (adapter_name == name)
|
ai.name = FixupDuplicateAdapterNames(adapter_names, GetAdapterName(adapter.get()));
|
||||||
|
if (ai.name == name)
|
||||||
{
|
{
|
||||||
Console.WriteLn(fmt::format("D3D: Found adapter '{}'", adapter_name));
|
INFO_LOG("D3D: Found adapter '{}'", ai.name);
|
||||||
return adapter;
|
return adapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
adapter_names.push_back(std::move(adapter_name));
|
adapter_names.push_back(std::move(ai));
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.Warning(fmt::format("Adapter '{}' not found.", name));
|
Console.Warning(fmt::format("Adapter '{}' not found.", name));
|
||||||
|
@ -404,9 +404,7 @@ GSRendererType D3D::GetPreferredRenderer()
|
||||||
if (check_for_mapping_layers())
|
if (check_for_mapping_layers())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
std::vector<std::string> vk_adapter_names;
|
if (!GSDeviceVK::EnumerateGPUs().empty())
|
||||||
GSDeviceVK::GetAdaptersAndFullscreenModes(&vk_adapter_names, nullptr);
|
|
||||||
if (!vk_adapter_names.empty())
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
Host::AddIconOSDMessage("VKDriverUnsupported", ICON_FA_TV, TRANSLATE_STR("GS",
|
Host::AddIconOSDMessage("VKDriverUnsupported", ICON_FA_TV, TRANSLATE_STR("GS",
|
||||||
|
|
|
@ -6,7 +6,8 @@
|
||||||
#include "common/RedtapeWindows.h"
|
#include "common/RedtapeWindows.h"
|
||||||
#include "common/RedtapeWilCom.h"
|
#include "common/RedtapeWilCom.h"
|
||||||
|
|
||||||
#include "pcsx2/Config.h"
|
#include "Config.h"
|
||||||
|
#include "GS/GS.h"
|
||||||
|
|
||||||
#include <d3d11_1.h>
|
#include <d3d11_1.h>
|
||||||
#include <dxgi1_5.h>
|
#include <dxgi1_5.h>
|
||||||
|
@ -19,11 +20,8 @@ namespace D3D
|
||||||
// create a dxgi factory
|
// create a dxgi factory
|
||||||
wil::com_ptr_nothrow<IDXGIFactory5> CreateFactory(bool debug);
|
wil::com_ptr_nothrow<IDXGIFactory5> CreateFactory(bool debug);
|
||||||
|
|
||||||
// returns a list of all adapter names
|
// returns a list of all adapter information
|
||||||
std::vector<std::string> GetAdapterNames(IDXGIFactory5* factory);
|
std::vector<GSAdapterInfo> GetAdapterInfo(IDXGIFactory5* factory);
|
||||||
|
|
||||||
// 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
|
// returns the fullscreen mode to use for the specified dimensions
|
||||||
bool GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory, const RECT& window_rect, u32 width, u32 height,
|
bool GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory, const RECT& window_rect, u32 width, u32 height,
|
||||||
|
|
|
@ -595,13 +595,10 @@ void GSDevice11::SetFeatures(IDXGIAdapter1* adapter)
|
||||||
m_features.vs_expand = false;
|
m_features.vs_expand = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
int GSDevice11::GetMaxTextureSize() const
|
m_max_texture_size = (m_feature_level >= D3D_FEATURE_LEVEL_11_0) ?
|
||||||
{
|
D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION :
|
||||||
return (m_feature_level >= D3D_FEATURE_LEVEL_11_0) ?
|
D3D10_REQ_TEXTURE2D_U_OR_V_DIMENSION;
|
||||||
D3D10_REQ_TEXTURE2D_U_OR_V_DIMENSION :
|
|
||||||
D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GSDevice11::HasSurface() const
|
bool GSDevice11::HasSurface() const
|
||||||
|
@ -1185,10 +1182,8 @@ void GSDevice11::InsertDebugMessage(DebugMessageCategory category, const char* f
|
||||||
GSTexture* GSDevice11::CreateSurface(GSTexture::Type type, int width, int height, int levels, GSTexture::Format format)
|
GSTexture* GSDevice11::CreateSurface(GSTexture::Type type, int width, int height, int levels, GSTexture::Format format)
|
||||||
{
|
{
|
||||||
D3D11_TEXTURE2D_DESC desc = {};
|
D3D11_TEXTURE2D_DESC desc = {};
|
||||||
|
desc.Width = width;
|
||||||
// Texture limit for D3D10/11 min 1, max 8192 D3D10, max 16384 D3D11.
|
desc.Height = height;
|
||||||
desc.Width = std::clamp(width, 1, GetMaxTextureSize());
|
|
||||||
desc.Height = std::clamp(height, 1, GetMaxTextureSize());
|
|
||||||
desc.Format = GSTexture11::GetDXGIFormat(format);
|
desc.Format = GSTexture11::GetDXGIFormat(format);
|
||||||
desc.MipLevels = levels;
|
desc.MipLevels = levels;
|
||||||
desc.ArraySize = 1;
|
desc.ArraySize = 1;
|
||||||
|
|
|
@ -91,7 +91,6 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
void SetFeatures(IDXGIAdapter1* adapter);
|
void SetFeatures(IDXGIAdapter1* adapter);
|
||||||
int GetMaxTextureSize() const;
|
|
||||||
|
|
||||||
u32 GetSwapChainBufferCount() const;
|
u32 GetSwapChainBufferCount() const;
|
||||||
bool CreateSwapChain();
|
bool CreateSwapChain();
|
||||||
|
|
|
@ -1227,6 +1227,8 @@ bool GSDevice12::CheckFeatures()
|
||||||
SupportsTextureFormat(DXGI_FORMAT_BC3_UNORM);
|
SupportsTextureFormat(DXGI_FORMAT_BC3_UNORM);
|
||||||
m_features.bptc_textures = SupportsTextureFormat(DXGI_FORMAT_BC7_UNORM);
|
m_features.bptc_textures = SupportsTextureFormat(DXGI_FORMAT_BC7_UNORM);
|
||||||
|
|
||||||
|
m_max_texture_size = D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION;
|
||||||
|
|
||||||
BOOL allow_tearing_supported = false;
|
BOOL allow_tearing_supported = false;
|
||||||
HRESULT hr = m_dxgi_factory->CheckFeatureSupport(
|
HRESULT hr = m_dxgi_factory->CheckFeatureSupport(
|
||||||
DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allow_tearing_supported, sizeof(allow_tearing_supported));
|
DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allow_tearing_supported, sizeof(allow_tearing_supported));
|
||||||
|
@ -1289,22 +1291,19 @@ void GSDevice12::LookupNativeFormat(GSTexture::Format format, DXGI_FORMAT* d3d_f
|
||||||
|
|
||||||
GSTexture* GSDevice12::CreateSurface(GSTexture::Type type, int width, int height, int levels, GSTexture::Format format)
|
GSTexture* GSDevice12::CreateSurface(GSTexture::Type type, int width, int height, int levels, GSTexture::Format format)
|
||||||
{
|
{
|
||||||
const u32 clamped_width = static_cast<u32>(std::clamp<int>(width, 1, D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION));
|
|
||||||
const u32 clamped_height = static_cast<u32>(std::clamp<int>(height, 1, D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION));
|
|
||||||
|
|
||||||
DXGI_FORMAT dxgi_format, srv_format, rtv_format, dsv_format;
|
DXGI_FORMAT dxgi_format, srv_format, rtv_format, dsv_format;
|
||||||
LookupNativeFormat(format, &dxgi_format, &srv_format, &rtv_format, &dsv_format);
|
LookupNativeFormat(format, &dxgi_format, &srv_format, &rtv_format, &dsv_format);
|
||||||
|
|
||||||
const DXGI_FORMAT uav_format = (type == GSTexture::Type::RWTexture) ? dxgi_format : DXGI_FORMAT_UNKNOWN;
|
const DXGI_FORMAT uav_format = (type == GSTexture::Type::RWTexture) ? dxgi_format : DXGI_FORMAT_UNKNOWN;
|
||||||
|
|
||||||
std::unique_ptr<GSTexture12> tex(GSTexture12::Create(type, format, clamped_width, clamped_height, levels,
|
std::unique_ptr<GSTexture12> tex(GSTexture12::Create(type, format, width, height, levels,
|
||||||
dxgi_format, srv_format, rtv_format, dsv_format, uav_format));
|
dxgi_format, srv_format, rtv_format, dsv_format, uav_format));
|
||||||
if (!tex)
|
if (!tex)
|
||||||
{
|
{
|
||||||
// We're probably out of vram, try flushing the command buffer to release pending textures.
|
// We're probably out of vram, try flushing the command buffer to release pending textures.
|
||||||
PurgePool();
|
PurgePool();
|
||||||
ExecuteCommandListAndRestartRenderPass(true, "Couldn't allocate texture.");
|
ExecuteCommandListAndRestartRenderPass(true, "Couldn't allocate texture.");
|
||||||
tex = GSTexture12::Create(type, format, clamped_width, clamped_height, levels, dxgi_format, srv_format,
|
tex = GSTexture12::Create(type, format, width, height, levels, dxgi_format, srv_format,
|
||||||
rtv_format, dsv_format, uav_format);
|
rtv_format, dsv_format, uav_format);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,12 +25,25 @@ GSDevice* MakeGSDeviceMTL()
|
||||||
return new GSDeviceMTL();
|
return new GSDeviceMTL();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> GetMetalAdapterList()
|
std::vector<GSAdapterInfo> GetMetalAdapterList()
|
||||||
{ @autoreleasepool {
|
{ @autoreleasepool {
|
||||||
std::vector<std::string> list;
|
std::vector<GSAdapterInfo> list;
|
||||||
auto devs = MRCTransfer(MTLCopyAllDevices());
|
auto devs = MRCTransfer(MTLCopyAllDevices());
|
||||||
for (id<MTLDevice> dev in devs.Get())
|
for (id<MTLDevice> dev in devs.Get())
|
||||||
list.push_back([[dev name] UTF8String]);
|
{
|
||||||
|
GSAdapterInfo ai;
|
||||||
|
ai.name = [[dev name] UTF8String];
|
||||||
|
|
||||||
|
ai.max_texture_size = 8192;
|
||||||
|
if ([dev supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily1_v1])
|
||||||
|
ai.max_texture_size = 16384;
|
||||||
|
if (@available(macOS 10.15, iOS 13.0, *))
|
||||||
|
if ([dev supportsFamily:MTLGPUFamilyApple3])
|
||||||
|
ai.max_texture_size = 16384;
|
||||||
|
|
||||||
|
ai.max_upscale_multiplier = GSGetMaxUpscaleMultiplier(ai.max_texture_size);
|
||||||
|
list.push_back(std::move(ai));
|
||||||
|
}
|
||||||
return list;
|
return list;
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
@ -507,8 +520,8 @@ GSTexture* GSDeviceMTL::CreateSurface(GSTexture::Type type, int width, int heigh
|
||||||
|
|
||||||
MTLTextureDescriptor* desc = [MTLTextureDescriptor
|
MTLTextureDescriptor* desc = [MTLTextureDescriptor
|
||||||
texture2DDescriptorWithPixelFormat:fmt
|
texture2DDescriptorWithPixelFormat:fmt
|
||||||
width:std::max(1, std::min(width, m_dev.features.max_texsize))
|
width:width
|
||||||
height:std::max(1, std::min(height, m_dev.features.max_texsize))
|
height:height
|
||||||
mipmapped:levels > 1];
|
mipmapped:levels > 1];
|
||||||
|
|
||||||
if (levels > 1)
|
if (levels > 1)
|
||||||
|
@ -905,6 +918,7 @@ bool GSDeviceMTL::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
|
||||||
m_features.stencil_buffer = true;
|
m_features.stencil_buffer = true;
|
||||||
m_features.cas_sharpening = true;
|
m_features.cas_sharpening = true;
|
||||||
m_features.test_and_sample_depth = true;
|
m_features.test_and_sample_depth = true;
|
||||||
|
m_max_texture_size = m_dev.features.max_texsize;
|
||||||
|
|
||||||
// Init metal stuff
|
// Init metal stuff
|
||||||
m_fn_constants = MRCTransfer([MTLFunctionConstantValues new]);
|
m_fn_constants = MRCTransfer([MTLFunctionConstantValues new]);
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "GS/GS.h"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
@ -12,6 +14,6 @@
|
||||||
|
|
||||||
class GSDevice;
|
class GSDevice;
|
||||||
GSDevice* MakeGSDeviceMTL();
|
GSDevice* MakeGSDeviceMTL();
|
||||||
std::vector<std::string> GetMetalAdapterList();
|
std::vector<GSAdapterInfo> GetMetalAdapterList();
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -767,6 +767,10 @@ bool GSDeviceOGL::CheckFeatures(bool& buggy_pbo)
|
||||||
(point_range[0] <= GSConfig.UpscaleMultiplier && point_range[1] >= GSConfig.UpscaleMultiplier);
|
(point_range[0] <= GSConfig.UpscaleMultiplier && point_range[1] >= GSConfig.UpscaleMultiplier);
|
||||||
m_features.line_expand = false;
|
m_features.line_expand = false;
|
||||||
|
|
||||||
|
GLint max_texture_size = 1024;
|
||||||
|
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);
|
||||||
|
m_max_texture_size = std::max(1024u, static_cast<u32>(max_texture_size));
|
||||||
|
|
||||||
Console.WriteLn("Using %s for point expansion, %s for line expansion and %s for sprite expansion.",
|
Console.WriteLn("Using %s for point expansion, %s for line expansion and %s for sprite expansion.",
|
||||||
m_features.point_expand ? "hardware" : (m_features.vs_expand ? "vertex expanding" : "UNSUPPORTED"),
|
m_features.point_expand ? "hardware" : (m_features.vs_expand ? "vertex expanding" : "UNSUPPORTED"),
|
||||||
m_features.line_expand ? "hardware" : (m_features.vs_expand ? "vertex expanding" : "UNSUPPORTED"),
|
m_features.line_expand ? "hardware" : (m_features.vs_expand ? "vertex expanding" : "UNSUPPORTED"),
|
||||||
|
|
|
@ -291,24 +291,58 @@ GSDeviceVK::GPUList GSDeviceVK::EnumerateGPUs(VkInstance instance)
|
||||||
if (has_missing_extension)
|
if (has_missing_extension)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
std::string gpu_name = props.deviceName;
|
GSAdapterInfo ai;
|
||||||
|
ai.name = props.deviceName;
|
||||||
|
ai.max_texture_size = std::min(props.limits.maxFramebufferWidth, props.limits.maxImageDimension2D);
|
||||||
|
ai.max_upscale_multiplier = GSGetMaxUpscaleMultiplier(ai.max_texture_size);
|
||||||
|
|
||||||
// handle duplicate adapter names
|
// handle duplicate adapter names
|
||||||
if (std::any_of(
|
if (std::any_of(
|
||||||
gpus.begin(), gpus.end(), [&gpu_name](const auto& other) { return (gpu_name == other.second); }))
|
gpus.begin(), gpus.end(), [&ai](const auto& other) { return (ai.name == other.second.name); }))
|
||||||
{
|
{
|
||||||
std::string original_adapter_name = std::move(gpu_name);
|
std::string original_adapter_name = std::move(ai.name);
|
||||||
|
|
||||||
u32 current_extra = 2;
|
u32 current_extra = 2;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
gpu_name = fmt::format("{} ({})", original_adapter_name, current_extra);
|
ai.name = fmt::format("{} ({})", original_adapter_name, current_extra);
|
||||||
current_extra++;
|
current_extra++;
|
||||||
} while (std::any_of(
|
} while (std::any_of(
|
||||||
gpus.begin(), gpus.end(), [&gpu_name](const auto& other) { return (gpu_name == other.second); }));
|
gpus.begin(), gpus.end(), [&ai](const auto& other) { return (ai.name == other.second.name); }));
|
||||||
}
|
}
|
||||||
|
|
||||||
gpus.emplace_back(device, std::move(gpu_name));
|
gpus.emplace_back(device, std::move(ai));
|
||||||
|
}
|
||||||
|
|
||||||
|
return gpus;
|
||||||
|
}
|
||||||
|
|
||||||
|
GSDeviceVK::GPUList GSDeviceVK::EnumerateGPUs()
|
||||||
|
{
|
||||||
|
std::unique_lock lock(s_instance_mutex);
|
||||||
|
|
||||||
|
// Device shouldn't be torn down since we have the lock.
|
||||||
|
GPUList gpus;
|
||||||
|
if (g_gs_device && Vulkan::IsVulkanLibraryLoaded())
|
||||||
|
{
|
||||||
|
gpus = EnumerateGPUs(GSDeviceVK::GetInstance()->GetVulkanInstance());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (Vulkan::LoadVulkanLibrary(nullptr))
|
||||||
|
{
|
||||||
|
OptionalExtensions oe = {};
|
||||||
|
const VkInstance instance = CreateVulkanInstance(WindowInfo(), &oe, false, false);
|
||||||
|
if (instance != VK_NULL_HANDLE)
|
||||||
|
{
|
||||||
|
if (Vulkan::LoadVulkanInstanceFunctions(instance))
|
||||||
|
gpus = EnumerateGPUs(instance);
|
||||||
|
|
||||||
|
vkDestroyInstance(instance, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vulkan::UnloadVulkanLibrary();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return gpus;
|
return gpus;
|
||||||
|
@ -1928,58 +1962,28 @@ bool GSDeviceVK::AllocatePreinitializedGPUBuffer(u32 size, VkBuffer* gpu_buffer,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void GSDeviceVK::GPUListToAdapterNames(std::vector<std::string>* dest, VkInstance instance)
|
std::vector<GSAdapterInfo> GSDeviceVK::GetAdapterInfo()
|
||||||
{
|
{
|
||||||
GPUList gpus = EnumerateGPUs(instance);
|
GPUList gpus = EnumerateGPUs();
|
||||||
dest->clear();
|
std::vector<GSAdapterInfo> ret;
|
||||||
dest->reserve(gpus.size());
|
ret.reserve(gpus.size());
|
||||||
for (auto& [gpu, name] : gpus)
|
for (auto& [physical_device, ai] : gpus)
|
||||||
dest->push_back(std::move(name));
|
ret.push_back(std::move(ai));
|
||||||
}
|
return ret;
|
||||||
|
|
||||||
void GSDeviceVK::GetAdaptersAndFullscreenModes(
|
|
||||||
std::vector<std::string>* adapters, std::vector<std::string>* fullscreen_modes)
|
|
||||||
{
|
|
||||||
std::unique_lock lock(s_instance_mutex);
|
|
||||||
|
|
||||||
// Device shouldn't be torn down since we have the lock.
|
|
||||||
if (g_gs_device && Vulkan::IsVulkanLibraryLoaded())
|
|
||||||
{
|
|
||||||
if (adapters)
|
|
||||||
GPUListToAdapterNames(adapters, GSDeviceVK::GetInstance()->GetVulkanInstance());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (Vulkan::LoadVulkanLibrary(nullptr))
|
|
||||||
{
|
|
||||||
OptionalExtensions oe = {};
|
|
||||||
const VkInstance instance = CreateVulkanInstance(WindowInfo(), &oe, false, false);
|
|
||||||
if (instance != VK_NULL_HANDLE)
|
|
||||||
{
|
|
||||||
if (Vulkan::LoadVulkanInstanceFunctions(instance))
|
|
||||||
GPUListToAdapterNames(adapters, instance);
|
|
||||||
|
|
||||||
vkDestroyInstance(instance, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
Vulkan::UnloadVulkanLibrary();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GSDeviceVK::IsSuitableDefaultRenderer()
|
bool GSDeviceVK::IsSuitableDefaultRenderer()
|
||||||
{
|
{
|
||||||
std::vector<std::string> adapters;
|
GPUList gpus = EnumerateGPUs();
|
||||||
GetAdaptersAndFullscreenModes(&adapters, nullptr);
|
if (gpus.empty())
|
||||||
if (adapters.empty())
|
|
||||||
{
|
{
|
||||||
// No adapters, not gonna be able to use VK.
|
// No adapters, not gonna be able to use VK.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the first GPU, should be enough.
|
// Check the first GPU, should be enough.
|
||||||
const std::string& name = adapters.front();
|
const std::string& name = gpus.front().second.name;
|
||||||
Console.WriteLn(fmt::format("Using Vulkan GPU '{}' for automatic renderer check.", name));
|
INFO_LOG("Using Vulkan GPU '{}' for automatic renderer check.", name);
|
||||||
|
|
||||||
// Any software rendering (LLVMpipe, SwiftShader).
|
// Any software rendering (LLVMpipe, SwiftShader).
|
||||||
if (StringUtil::StartsWithNoCase(name, "llvmpipe") || StringUtil::StartsWithNoCase(name, "SwiftShader"))
|
if (StringUtil::StartsWithNoCase(name, "llvmpipe") || StringUtil::StartsWithNoCase(name, "SwiftShader"))
|
||||||
|
@ -2451,18 +2455,17 @@ bool GSDeviceVK::CreateDeviceAndSwapChain()
|
||||||
m_instance = CreateVulkanInstance(m_window_info, &m_optional_extensions, enable_debug_utils, enable_validation_layer);
|
m_instance = CreateVulkanInstance(m_window_info, &m_optional_extensions, enable_debug_utils, enable_validation_layer);
|
||||||
if (m_instance == VK_NULL_HANDLE)
|
if (m_instance == VK_NULL_HANDLE)
|
||||||
{
|
{
|
||||||
Host::ReportErrorAsync(
|
Host::ReportErrorAsync("Error", "Failed to create Vulkan instance. Does your GPU and/or driver support Vulkan?");
|
||||||
"Error", "Failed to create Vulkan instance. Does your GPU and/or driver support Vulkan?");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.Error("Vulkan validation/debug layers requested but are unavailable. Creating non-debug device.");
|
ERROR_LOG("Vulkan validation/debug layers requested but are unavailable. Creating non-debug device.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Vulkan::LoadVulkanInstanceFunctions(m_instance))
|
if (!Vulkan::LoadVulkanInstanceFunctions(m_instance))
|
||||||
{
|
{
|
||||||
Console.Error("Failed to load Vulkan instance functions");
|
ERROR_LOG("Failed to load Vulkan instance functions");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2478,8 +2481,8 @@ bool GSDeviceVK::CreateDeviceAndSwapChain()
|
||||||
u32 gpu_index = 0;
|
u32 gpu_index = 0;
|
||||||
for (; gpu_index < static_cast<u32>(gpus.size()); gpu_index++)
|
for (; gpu_index < static_cast<u32>(gpus.size()); gpu_index++)
|
||||||
{
|
{
|
||||||
Console.WriteLn(fmt::format("GPU {}: {}", gpu_index, gpus[gpu_index].second));
|
DEV_LOG("GPU {}: {}", gpu_index, gpus[gpu_index].second.name);
|
||||||
if (gpus[gpu_index].second == GSConfig.Adapter)
|
if (gpus[gpu_index].second.name == GSConfig.Adapter)
|
||||||
{
|
{
|
||||||
m_physical_device = gpus[gpu_index].first;
|
m_physical_device = gpus[gpu_index].first;
|
||||||
break;
|
break;
|
||||||
|
@ -2488,14 +2491,13 @@ bool GSDeviceVK::CreateDeviceAndSwapChain()
|
||||||
|
|
||||||
if (gpu_index == static_cast<u32>(gpus.size()))
|
if (gpu_index == static_cast<u32>(gpus.size()))
|
||||||
{
|
{
|
||||||
Console.Warning(
|
WARNING_LOG("Requested GPU '{}' not found, using first ({})", GSConfig.Adapter, gpus[0].second.name);
|
||||||
fmt::format("Requested GPU '{}' not found, using first ({})", GSConfig.Adapter, gpus[0].second));
|
|
||||||
m_physical_device = gpus[0].first;
|
m_physical_device = gpus[0].first;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Console.WriteLn(fmt::format("No GPU requested, using first ({})", gpus[0].second));
|
INFO_LOG("No GPU requested, using first ({})", gpus[0].second.name);
|
||||||
m_physical_device = gpus[0].first;
|
m_physical_device = gpus[0].first;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2647,6 +2649,8 @@ bool GSDeviceVK::CheckFeatures()
|
||||||
Host::OSD_WARNING_DURATION);
|
Host::OSD_WARNING_DURATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_max_texture_size = m_device_properties.limits.maxImageDimension2D;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2693,18 +2697,13 @@ VkFormat GSDeviceVK::LookupNativeFormat(GSTexture::Format format) const
|
||||||
|
|
||||||
GSTexture* GSDeviceVK::CreateSurface(GSTexture::Type type, int width, int height, int levels, GSTexture::Format format)
|
GSTexture* GSDeviceVK::CreateSurface(GSTexture::Type type, int width, int height, int levels, GSTexture::Format format)
|
||||||
{
|
{
|
||||||
const u32 clamped_width =
|
std::unique_ptr<GSTexture> tex = GSTextureVK::Create(type, format, width, height, levels);
|
||||||
static_cast<u32>(std::clamp<int>(width, 1, m_device_properties.limits.maxImageDimension2D));
|
|
||||||
const u32 clamped_height =
|
|
||||||
static_cast<u32>(std::clamp<int>(height, 1, m_device_properties.limits.maxImageDimension2D));
|
|
||||||
|
|
||||||
std::unique_ptr<GSTexture> tex(GSTextureVK::Create(type, format, clamped_width, clamped_height, levels));
|
|
||||||
if (!tex)
|
if (!tex)
|
||||||
{
|
{
|
||||||
// We're probably out of vram, try flushing the command buffer to release pending textures.
|
// We're probably out of vram, try flushing the command buffer to release pending textures.
|
||||||
PurgePool();
|
PurgePool();
|
||||||
ExecuteCommandBufferAndRestartRenderPass(true, "Couldn't allocate texture.");
|
ExecuteCommandBufferAndRestartRenderPass(true, "Couldn't allocate texture.");
|
||||||
tex = GSTextureVK::Create(type, format, clamped_width, clamped_height, levels);
|
tex = GSTextureVK::Create(type, format, width, height, levels);
|
||||||
}
|
}
|
||||||
|
|
||||||
return tex.release();
|
return tex.release();
|
||||||
|
|
|
@ -129,11 +129,6 @@ private:
|
||||||
static VkInstance CreateVulkanInstance(const WindowInfo& wi, OptionalExtensions* oe, bool enable_debug_utils,
|
static VkInstance CreateVulkanInstance(const WindowInfo& wi, OptionalExtensions* oe, bool enable_debug_utils,
|
||||||
bool enable_validation_layer);
|
bool enable_validation_layer);
|
||||||
|
|
||||||
// Returns a list of Vulkan-compatible GPUs.
|
|
||||||
using GPUList = std::vector<std::pair<VkPhysicalDevice, std::string>>;
|
|
||||||
static GPUList EnumerateGPUs(VkInstance instance);
|
|
||||||
static void GPUListToAdapterNames(std::vector<std::string>* dest, VkInstance instance);
|
|
||||||
|
|
||||||
// Enable/disable debug message runtime.
|
// Enable/disable debug message runtime.
|
||||||
bool EnableDebugUtils();
|
bool EnableDebugUtils();
|
||||||
void DisableDebugUtils();
|
void DisableDebugUtils();
|
||||||
|
@ -474,8 +469,11 @@ public:
|
||||||
|
|
||||||
__fi static GSDeviceVK* GetInstance() { return static_cast<GSDeviceVK*>(g_gs_device.get()); }
|
__fi static GSDeviceVK* GetInstance() { return static_cast<GSDeviceVK*>(g_gs_device.get()); }
|
||||||
|
|
||||||
static void GetAdaptersAndFullscreenModes(
|
// Returns a list of Vulkan-compatible GPUs.
|
||||||
std::vector<std::string>* adapters, std::vector<std::string>* fullscreen_modes);
|
using GPUList = std::vector<std::pair<VkPhysicalDevice, GSAdapterInfo>>;
|
||||||
|
static GPUList EnumerateGPUs();
|
||||||
|
static GPUList EnumerateGPUs(VkInstance instance);
|
||||||
|
static std::vector<GSAdapterInfo> GetAdapterInfo();
|
||||||
|
|
||||||
/// Returns true if Vulkan is suitable as a default for the devices in the system.
|
/// Returns true if Vulkan is suitable as a default for the devices in the system.
|
||||||
static bool IsSuitableDefaultRenderer();
|
static bool IsSuitableDefaultRenderer();
|
||||||
|
|
|
@ -402,8 +402,7 @@ namespace FullscreenUI
|
||||||
static std::unique_ptr<INISettingsInterface> s_game_settings_interface;
|
static std::unique_ptr<INISettingsInterface> s_game_settings_interface;
|
||||||
static std::unique_ptr<GameList::Entry> s_game_settings_entry;
|
static std::unique_ptr<GameList::Entry> s_game_settings_entry;
|
||||||
static std::vector<std::pair<std::string, bool>> s_game_list_directories_cache;
|
static std::vector<std::pair<std::string, bool>> s_game_list_directories_cache;
|
||||||
static std::vector<std::string> s_graphics_adapter_list_cache;
|
static std::vector<GSAdapterInfo> s_graphics_adapter_list_cache;
|
||||||
static std::vector<std::string> s_fullscreen_mode_list_cache;
|
|
||||||
static Patch::PatchInfoList s_game_patch_list;
|
static Patch::PatchInfoList s_game_patch_list;
|
||||||
static std::vector<std::string> s_enabled_game_patch_cache;
|
static std::vector<std::string> s_enabled_game_patch_cache;
|
||||||
static Patch::PatchInfoList s_game_cheats_list;
|
static Patch::PatchInfoList s_game_cheats_list;
|
||||||
|
@ -762,7 +761,6 @@ void FullscreenUI::Shutdown(bool clear_state)
|
||||||
s_game_cheats_list = {};
|
s_game_cheats_list = {};
|
||||||
s_enabled_game_patch_cache = {};
|
s_enabled_game_patch_cache = {};
|
||||||
s_game_patch_list = {};
|
s_game_patch_list = {};
|
||||||
s_fullscreen_mode_list_cache = {};
|
|
||||||
s_graphics_adapter_list_cache = {};
|
s_graphics_adapter_list_cache = {};
|
||||||
s_current_game_title = {};
|
s_current_game_title = {};
|
||||||
s_current_game_subtitle = {};
|
s_current_game_subtitle = {};
|
||||||
|
@ -2736,7 +2734,7 @@ void FullscreenUI::SwitchToGameSettings(const GameList::Entry* entry)
|
||||||
|
|
||||||
void FullscreenUI::PopulateGraphicsAdapterList()
|
void FullscreenUI::PopulateGraphicsAdapterList()
|
||||||
{
|
{
|
||||||
GSGetAdaptersAndFullscreenModes(GSConfig.Renderer, &s_graphics_adapter_list_cache, &s_fullscreen_mode_list_cache);
|
s_graphics_adapter_list_cache = GSGetAdapterInfo(GSConfig.Renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FullscreenUI::PopulateGameListDirectoryCache(SettingsInterface* si)
|
void FullscreenUI::PopulateGameListDirectoryCache(SettingsInterface* si)
|
||||||
|
|
|
@ -883,9 +883,6 @@ void Pcsx2Config::GSOptions::LoadSave(SettingsWrapper& wrap)
|
||||||
SettingsWrapIntEnumEx(Renderer, "Renderer");
|
SettingsWrapIntEnumEx(Renderer, "Renderer");
|
||||||
SettingsWrapEntryEx(UpscaleMultiplier, "upscale_multiplier");
|
SettingsWrapEntryEx(UpscaleMultiplier, "upscale_multiplier");
|
||||||
|
|
||||||
// ~51x would the upper bound here for 32768x32768 textures, but you'll run out VRAM long before then.
|
|
||||||
UpscaleMultiplier = std::clamp(UpscaleMultiplier, 1.0f, 50.0f);
|
|
||||||
|
|
||||||
SettingsWrapBitBoolEx(HWMipmap, "hw_mipmap");
|
SettingsWrapBitBoolEx(HWMipmap, "hw_mipmap");
|
||||||
SettingsWrapIntEnumEx(AccurateBlendingUnit, "accurate_blending_unit");
|
SettingsWrapIntEnumEx(AccurateBlendingUnit, "accurate_blending_unit");
|
||||||
SettingsWrapIntEnumEx(TextureFiltering, "filter");
|
SettingsWrapIntEnumEx(TextureFiltering, "filter");
|
||||||
|
|
Loading…
Reference in New Issue