Skip to content

Commit

Permalink
GS/HW: Add all levels/unclamped mipmap modes (stenzek)
Browse files Browse the repository at this point in the history
  • Loading branch information
Blackbird88 committed Sep 24, 2024
1 parent cd3e11b commit c2ac7e5
Show file tree
Hide file tree
Showing 12 changed files with 140 additions and 54 deletions.
8 changes: 5 additions & 3 deletions pcsx2-qt/Settings/GraphicsSettingsWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,8 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.anisotropicFiltering, "EmuCore/GS", "MaxAnisotropy",
s_anisotropic_filtering_entries, s_anisotropic_filtering_values, "0");
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.dithering, "EmuCore/GS", "dithering_ps2", 2);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.mipmapping, "EmuCore/GS", "hw_mipmap", true);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.hwMipmapMode, "EmuCore/GS", "hw_mipmap_mode",
static_cast<int>(GSHWMipmapMode::Enabled));
SettingWidgetBinder::BindWidgetToIntSetting(
sif, m_ui.blending, "EmuCore/GS", "accurate_blending_unit", static_cast<int>(AccBlendLevel::Basic));
SettingWidgetBinder::BindWidgetToIntSetting(
Expand Down Expand Up @@ -494,8 +495,9 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
"FMV resolution will remain unchanged, as the video files are pre-rendered."));

dialog->registerWidgetHelp(
m_ui.mipmapping, tr("Mipmapping"), tr("Checked"), tr("Enables mipmapping, which some games require to render correctly."));

m_ui.hwMipmapMode, tr("Mipmapping"), tr("Enabled"), tr("Enables mipmapping, which many games require to render correctly. "
"Unclamped allows higher texture detail levels to be used, but may break certain graphical effects."));

dialog->registerWidgetHelp(
m_ui.textureFiltering, tr("Texture Filtering"), tr("Bilinear (PS2)"), tr("Control the texture filtering of the emulation."));

Expand Down
42 changes: 33 additions & 9 deletions pcsx2-qt/Settings/GraphicsSettingsWidget.ui
Original file line number Diff line number Diff line change
Expand Up @@ -643,29 +643,53 @@
<string>Spin GPU During Readbacks</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="mipmapping">
<property name="text">
<string>Mipmapping</string>
</property>
</widget>
</item>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="spinCPUDuringReadbacks">
<property name="text">
<string>Spin CPU During Readbacks</string>
</property>
</widget>
</item>
<item row="1" column="1">
<item row="1" column="0">
<widget class="QCheckBox" name="enableHWFixes">
<property name="text">
<string>Manual Hardware Renderer Fixes</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QLabel" name="hwMipmappingLabel">
<property name="text">
<string>Mipmapping:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="hwMipmapMode">
<item>
<property name="text">
<string>Disabled</string>
</property>
</item>
<item>
<property name="text">
<string>Enabled</string>
</property>
</item>
<item>
<property name="text">
<string>All Levels</string>
</property>
</item>
<item>
<property name="text">
<string>Unclamped</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
Expand Down
11 changes: 10 additions & 1 deletion pcsx2/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,15 @@ enum class BiFiltering : u8
Forced_But_Sprite,
};

enum class GSHWMipmapMode : u8
{
Disabled,
Enabled,
AllLevels,
Unclamped,
MaxCount
};

enum class TriFiltering : s8
{
Automatic = -1,
Expand Down Expand Up @@ -627,7 +636,6 @@ struct Pcsx2Config
AutoFlushSW : 1,
PreloadFrameWithGSData : 1,
Mipmap : 1,
HWMipmap : 1,
ManualUserHacks : 1,
UserHacks_AlignSpriteX : 1,
UserHacks_CPUFBConversion : 1,
Expand Down Expand Up @@ -682,6 +690,7 @@ struct Pcsx2Config
float UpscaleMultiplier = 1.0f;

AccBlendLevel AccurateBlendingUnit = AccBlendLevel::Basic;
GSHWMipmapMode HWMipmapMode = GSHWMipmapMode::Disabled;
BiFiltering TextureFiltering = BiFiltering::PS2;
TexturePreloadingLevel TexturePreloading = TexturePreloadingLevel::Full;
GSDumpCompressionMethod GSDumpCompression = GSDumpCompressionMethod::Zstandard;
Expand Down
7 changes: 4 additions & 3 deletions pcsx2/GS/GS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -762,7 +762,7 @@ void GSUpdateConfig(const Pcsx2Config::GSOptions& new_config)

// reload texture cache when trilinear filtering or TC options change
if (
(GSIsHardwareRenderer() && GSConfig.HWMipmap != old_config.HWMipmap) ||
(GSIsHardwareRenderer() && GSConfig.HWMipmapMode != old_config.HWMipmapMode) ||
GSConfig.TexturePreloading != old_config.TexturePreloading ||
GSConfig.TriFilter != old_config.TriFilter ||
GSConfig.GPUPaletteConversion != old_config.GPUPaletteConversion ||
Expand Down Expand Up @@ -1140,9 +1140,10 @@ BEGIN_HOTKEY_LIST(g_gs_hotkeys){"Screenshot", TRANSLATE_NOOP("Hotkeys", "Graphic
[](s32 pressed) {
if (!pressed)
{
EmuConfig.GS.HWMipmap = !EmuConfig.GS.HWMipmap;
EmuConfig.GS.HWMipmapMode =
(EmuConfig.GS.HWMipmapMode >= GSHWMipmapMode::Enabled) ? GSHWMipmapMode::Disabled : GSHWMipmapMode::Enabled;
Host::AddKeyedOSDMessage("ToggleMipmapMode",
EmuConfig.GS.HWMipmap ?
(EmuConfig.GS.HWMipmapMode >= GSHWMipmapMode::Enabled) ?
TRANSLATE_STR("Hotkeys", "Hardware mipmapping is now enabled.") :
TRANSLATE_STR("Hotkeys", "Hardware mipmapping is now disabled."),
Host::OSD_INFO_DURATION);
Expand Down
71 changes: 50 additions & 21 deletions pcsx2/GS/Renderers/HW/GSRendererHW.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ GSRendererHW::GSRendererHW()
: GSRenderer()
{
MULTI_ISA_SELECT(GSRendererHWPopulateFunctions)(*this);
m_mipmap = GSConfig.HWMipmap;
m_mipmap = (GSConfig.HWMipmapMode >= GSHWMipmapMode::Enabled);
SetTCOffset();

pxAssert(!g_texture_cache);
Expand Down Expand Up @@ -88,7 +88,7 @@ void GSRendererHW::Reset(bool hardware_reset)
void GSRendererHW::UpdateSettings(const Pcsx2Config::GSOptions& old_config)
{
GSRenderer::UpdateSettings(old_config);
m_mipmap = GSConfig.HWMipmap;
m_mipmap = (GSConfig.HWMipmapMode >= GSHWMipmapMode::Enabled);
SetTCOffset();
}

Expand Down Expand Up @@ -2398,6 +2398,7 @@ void GSRendererHW::Draw()
GIFRegTEX0 TEX0 = {};
GSTextureCache::Source* src = nullptr;
TextureMinMaxResult tmm;
bool mipmap_active = false;

// Disable texture mapping if the blend is black and using alpha from vertex.
if (m_process_texture)
Expand All @@ -2407,7 +2408,8 @@ void GSRendererHW::Draw()
m_lod = GSVector2i(0, 0);

// Code from the SW renderer
if (IsMipMapActive())
mipmap_active = IsMipMapActive();
if (mipmap_active)
{
const int interpolation = (context->TEX1.MMIN & 1) + 1; // 1: round, 2: tri

Expand Down Expand Up @@ -2464,24 +2466,42 @@ void GSRendererHW::Draw()
m_lod.x = std::min<int>(m_lod.x, mxl);
m_lod.y = std::min<int>(m_lod.y, mxl);

TEX0 = (m_lod.x == 0) ? m_cached_ctx.TEX0 : GetTex0Layer(m_lod.x);
if (GSConfig.HWMipmapMode < GSHWMipmapMode::AllLevels)
{
TEX0 = (m_lod.x == 0) ? m_cached_ctx.TEX0 : GetTex0Layer(m_lod.x);

// upload the full chain (with offset) for the hash cache, in case some other texture uses more levels
// for basic mipmapping, we can get away with just doing the base image, since all the mips get generated anyway.
hash_lod_range = GSVector2i(m_lod.x, GSConfig.HWMipmap ? mxl : m_lod.x);
// upload the full chain (with offset) for the hash cache, in case some other texture uses more levels
// for basic mipmapping, we can get away with just doing the base image, since all the mips get generated anyway.
hash_lod_range = GSVector2i(m_lod.x, mxl);

MIP_CLAMP.MINU >>= m_lod.x;
MIP_CLAMP.MINV >>= m_lod.x;
MIP_CLAMP.MAXU >>= m_lod.x;
MIP_CLAMP.MAXV >>= m_lod.x;
MIP_CLAMP.MINU >>= m_lod.x;
MIP_CLAMP.MINV >>= m_lod.x;
MIP_CLAMP.MAXU >>= m_lod.x;
MIP_CLAMP.MAXV >>= m_lod.x;

for (int i = 0; i < m_lod.x; i++)
for (int i = 0; i < m_lod.x; i++)
{
m_vt.m_min.t *= 0.5f;
m_vt.m_max.t *= 0.5f;
}
}
else
{
m_vt.m_min.t *= 0.5f;
m_vt.m_max.t *= 0.5f;
hash_lod_range = GSVector2i(0, mxl);
TEX0 = m_cached_ctx.TEX0;
}

GL_CACHE("Mipmap LOD %d %d (%f %f) new size %dx%d (K %d L %u)", m_lod.x, m_lod.y, m_vt.m_lod.x, m_vt.m_lod.y, 1 << TEX0.TW, 1 << TEX0.TH, m_context->TEX1.K, m_context->TEX1.L);
GL_CACHE("Mipmap LOD %d %d (%f %f) new size %dx%d (K %d L %u)", m_lod.x, m_lod.y, m_vt.m_lod.x, m_vt.m_lod.y,
(1 << m_cached_ctx.TEX0.TW) >> m_lod.x, (1 << m_cached_ctx.TEX0.TH) >> m_lod.x, m_context->TEX1.K, m_context->TEX1.L);
}
else if (GSConfig.HWMipmapMode >= GSHWMipmapMode::AllLevels && m_context->TEX1.MXL > 0 && !m_context->TEX1.LCM)
{
mipmap_active = true;
hash_lod_range = GSVector2i(0, std::min<int>(static_cast<int>(m_context->TEX1.MXL), 6));
TEX0 = m_cached_ctx.TEX0;
GL_CACHE("Looking up all %d texture LODs", hash_lod_range.y);
}
else
{
Expand Down Expand Up @@ -2563,7 +2583,7 @@ void GSRendererHW::Draw()
else
{
src = tex_psm.depth ? g_texture_cache->LookupDepthSource(true, TEX0, env.TEXA, MIP_CLAMP, tmm.coverage, possible_shuffle, m_vt.IsLinear(), m_cached_ctx.FRAME.Block(), req_color, req_alpha) :
g_texture_cache->LookupSource(true, TEX0, env.TEXA, MIP_CLAMP, tmm.coverage, (GSConfig.HWMipmap || GSConfig.TriFilter == TriFiltering::Forced) ? &hash_lod_range : nullptr,
g_texture_cache->LookupSource(true, TEX0, env.TEXA, MIP_CLAMP, tmm.coverage, (GSConfig.HWMipmapMode >= GSHWMipmapMode::Enabled || GSConfig.TriFilter == TriFiltering::Forced) ? &hash_lod_range : nullptr,
possible_shuffle, m_vt.IsLinear(), m_cached_ctx.FRAME.Block(), req_color, req_alpha);

if (!src) [[unlikely]]
Expand Down Expand Up @@ -2958,7 +2978,7 @@ void GSRendererHW::Draw()
}

// Round 2
if (IsMipMapActive() && GSConfig.HWMipmap && !tex_psm.depth && !src->m_from_hash_cache)
if (mipmap_active && !tex_psm.depth && !src->m_from_hash_cache)
{
// Upload remaining texture layers
const GSVector4 tmin = m_vt.m_min.t;
Expand Down Expand Up @@ -4845,7 +4865,7 @@ __ri void GSRendererHW::EmulateTextureSampler(const GSTextureCache::Target* rt,
const bool shader_emulated_sampler = tex->m_palette || (tex->m_target && !m_conf.ps.shuffle && cpsm.fmt != 0) ||
complex_wms_wmt || psm.depth || target_region;
const bool can_trilinear = !tex->m_palette && !tex->m_target && !m_conf.ps.shuffle;
const bool trilinear_manual = need_mipmap && GSConfig.HWMipmap;
bool trilinear_manual = need_mipmap && GSConfig.HWMipmapMode >= GSHWMipmapMode::Enabled;

bool bilinear = m_vt.IsLinear();
int trilinear = 0;
Expand All @@ -4860,7 +4880,7 @@ __ri void GSRendererHW::EmulateTextureSampler(const GSTextureCache::Target* rt,
if (can_trilinear)
{
trilinear = static_cast<u8>(GS_MIN_FILTER::Linear_Mipmap_Linear);
trilinear_auto = !tex->m_target && (!need_mipmap || !GSConfig.HWMipmap);
trilinear_auto = !tex->m_target && (!need_mipmap || GSConfig.HWMipmapMode == GSHWMipmapMode::Disabled);
}
}
break;
Expand All @@ -4869,10 +4889,10 @@ __ri void GSRendererHW::EmulateTextureSampler(const GSTextureCache::Target* rt,
case TriFiltering::Automatic:
{
// Can only use PS2 trilinear when mipmapping is enabled.
if (need_mipmap && GSConfig.HWMipmap && can_trilinear)
if (need_mipmap && GSConfig.HWMipmapMode >= GSHWMipmapMode::Enabled && can_trilinear)
{
trilinear = m_context->TEX1.MMIN;
trilinear_auto = !tex->m_target && !GSConfig.HWMipmap;
trilinear_auto = !tex->m_target && GSConfig.HWMipmapMode == GSHWMipmapMode::Disabled;
}
}
break;
Expand All @@ -4881,7 +4901,14 @@ __ri void GSRendererHW::EmulateTextureSampler(const GSTextureCache::Target* rt,
default:
break;
}


if (GSConfig.HWMipmapMode >= GSHWMipmapMode::Unclamped && !shader_emulated_sampler &&
m_context->TEX1.MXL > 0 && m_context->TEX1.MMIN >= 2 && m_context->TEX1.MMIN <= 5 && !m_context->TEX1.LCM)
{
trilinear = static_cast<u8>(m_vt.IsLinear() ? GS_MIN_FILTER::Linear_Mipmap_Linear : GS_MIN_FILTER::Nearest_Mipmap_Linear);
trilinear_manual = false;
}

// 1 and 0 are equivalent
m_conf.ps.wms = (wms & 2 || target_region) ? wms : 0;
m_conf.ps.wmt = (wmt & 2 || target_region) ? wmt : 0;
Expand Down Expand Up @@ -5081,7 +5108,9 @@ __ri void GSRendererHW::EmulateTextureSampler(const GSTextureCache::Target* rt,
{
m_conf.cb_ps.LODParams.x = static_cast<float>(m_context->TEX1.K) / 16.0f;
m_conf.cb_ps.LODParams.y = static_cast<float>(1 << m_context->TEX1.L);
m_conf.cb_ps.LODParams.z = static_cast<float>(m_lod.x); // Offset because first layer is m_lod, dunno if we can do better
m_conf.cb_ps.LODParams.z = (GSConfig.HWMipmapMode >= GSHWMipmapMode::Unclamped) ?
std::log2(rt ? rt->GetScale() : ds->GetScale()) :
static_cast<float>((GSConfig.HWMipmapMode == GSHWMipmapMode::AllLevels) ? 0.0f : m_lod.x);
m_conf.cb_ps.LODParams.w = static_cast<float>(m_lod.y);
m_conf.ps.manual_lod = 1;
}
Expand Down
9 changes: 7 additions & 2 deletions pcsx2/GS/Renderers/HW/GSTextureCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4433,7 +4433,9 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con
{
// lod won't contain the full range when using basic mipmapping, only that
// which is hashed, so we just allocate the full thing.
tlevels = GSConfig.HWMipmap ? std::min(lod->y - lod->x + 1, GSDevice::GetMipmapLevelsForSize(tw, th)) : -1;
tlevels = (GSConfig.HWMipmapMode >= GSHWMipmapMode::Enabled) ?
std::min(lod->y - lod->x + 1, GSDevice::GetMipmapLevelsForSize(tw, th)) :
-1;
src->m_lod = *lod;
}

Expand Down Expand Up @@ -5376,7 +5378,10 @@ GSTextureCache::HashCacheEntry* GSTextureCache::LookupHashCache(const GIFRegTEX0
// expand/upload texture
const int tw = region.HasX() ? region.GetWidth() : (1 << TEX0.TW);
const int th = region.HasY() ? region.GetHeight() : (1 << TEX0.TH);
const int tlevels = lod ? (GSConfig.HWMipmap ? std::min(lod->y - lod->x + 1, GSDevice::GetMipmapLevelsForSize(tw, th)) : -1) : 1;
const int tlevels = lod ? ((GSConfig.HWMipmapMode >= GSHWMipmapMode::Enabled) ?
std::min(lod->y - lod->x + 1, GSDevice::GetMipmapLevelsForSize(tw, th)) :
-1) :
1;
GSTexture* tex = g_gs_device->CreateTexture(tw, th, tlevels, paltex ? GSTexture::Format::UNorm8 : GSTexture::Format::Color);
if (!tex)
{
Expand Down
2 changes: 1 addition & 1 deletion pcsx2/GS/Renderers/HW/GSTextureReplacements.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,7 @@ void GSTextureReplacements::PrecacheReplacementTextures()

// predict whether the requests will come with mipmaps
// TODO: This will be wrong for hw mipmap games like Jak.
const bool mipmap = GSConfig.HWMipmap || GSConfig.TriFilter == TriFiltering::Forced;
const bool mipmap = (GSConfig.HWMipmapMode >= GSHWMipmapMode::Enabled || GSConfig.TriFilter == TriFiltering::Forced);

// pretty simple, just go through the filenames and if any aren't cached, cache them
for (const auto& it : s_replacement_texture_filenames)
Expand Down
9 changes: 6 additions & 3 deletions pcsx2/GameDatabase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -622,7 +622,7 @@ bool GameDatabaseSchema::GameEntry::configMatchesHWFix(const Pcsx2Config::GSOpti
return (static_cast<int>(config.PCRTCOverscan) == value);

case GSHWFixId::Mipmap:
return (static_cast<int>(config.HWMipmap) == value);
return (static_cast<int>(config.HWMipmapMode) == value);

case GSHWFixId::TrilinearFiltering:
return (config.TriFilter == TriFiltering::Automatic || static_cast<int>(config.TriFilter) == value);
Expand Down Expand Up @@ -780,8 +780,11 @@ void GameDatabaseSchema::GameEntry::applyGSHardwareFixes(Pcsx2Config::GSOptions&
break;

case GSHWFixId::Mipmap:
config.HWMipmap = (value > 0);
break;
{
if (value >= 0 && value < static_cast<int>(GSHWMipmapMode::MaxCount))
config.HWMipmapMode = static_cast<GSHWMipmapMode>(value > 0);
}
break;

case GSHWFixId::TrilinearFiltering:
{
Expand Down
Loading

0 comments on commit c2ac7e5

Please sign in to comment.