mirror of https://github.com/PCSX2/pcsx2.git
GS/HW: Add "Merge Targets" texture-in-RT mode
Can take several targets from the cache, and create a combined/merged source from them. Fixes shadow maps in Destroy All Humans.
This commit is contained in:
parent
75957c84e3
commit
64b38e5a4a
|
@ -16718,12 +16718,16 @@ SLES-53196:
|
|||
name: "Destroy All Humans!"
|
||||
region: "PAL-M4"
|
||||
gsHWFixes:
|
||||
mipmap: 1
|
||||
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
|
||||
trilinearFiltering: 1
|
||||
textureInsideRT: 2 # Fixes shadow maps.
|
||||
SLES-53197:
|
||||
name: "Destroy All Humans!"
|
||||
region: "PAL-G"
|
||||
gsHWFixes:
|
||||
mipmap: 1
|
||||
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
|
||||
trilinearFiltering: 1
|
||||
textureInsideRT: 2 # Fixes shadow maps.
|
||||
SLES-53199:
|
||||
name: "25 to Life"
|
||||
region: "PAL-E"
|
||||
|
@ -17886,7 +17890,9 @@ SLES-53641:
|
|||
name: "Destroy All Humans!"
|
||||
region: "PAL-R"
|
||||
gsHWFixes:
|
||||
mipmap: 1
|
||||
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
|
||||
trilinearFiltering: 1
|
||||
textureInsideRT: 2 # Fixes shadow maps.
|
||||
SLES-53644:
|
||||
name: "Gene Troopers"
|
||||
region: "PAL-M5"
|
||||
|
@ -19797,7 +19803,9 @@ SLES-54384:
|
|||
name: "Destroy All Humans 2 - Make War not Love"
|
||||
region: "PAL-M5"
|
||||
gsHWFixes:
|
||||
mipmap: 1
|
||||
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
|
||||
trilinearFiltering: 1
|
||||
textureInsideRT: 2 # Fixes shadow maps.
|
||||
SLES-54385:
|
||||
name: "Atelier Iris 2 - The Azoth of Destiny"
|
||||
region: "PAL-E"
|
||||
|
@ -20855,6 +20863,8 @@ SLES-54786:
|
|||
SLES-54788:
|
||||
name: "Aqua Teen Hunger Force - Zombie Ninja Pro-Am"
|
||||
region: "PAL-E"
|
||||
gsHWFixes:
|
||||
textureInsideRT: 2 # Fixes shadows.
|
||||
SLES-54789:
|
||||
name: "Beta Bloc"
|
||||
region: "PAL-E"
|
||||
|
@ -32693,7 +32703,9 @@ SLPM-66430:
|
|||
name: "Destroy All Humans!"
|
||||
region: "NTSC-J"
|
||||
gsHWFixes:
|
||||
mipmap: 1
|
||||
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
|
||||
trilinearFiltering: 1
|
||||
textureInsideRT: 2 # Fixes shadow maps.
|
||||
SLPM-66431:
|
||||
name: "WinBack 2 - Project Poseidon"
|
||||
region: "NTSC-J"
|
||||
|
@ -45111,7 +45123,9 @@ SLUS-20945:
|
|||
region: "NTSC-U"
|
||||
compat: 5
|
||||
gsHWFixes:
|
||||
mipmap: 1
|
||||
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
|
||||
trilinearFiltering: 1
|
||||
textureInsideRT: 2 # Fixes shadow maps.
|
||||
SLUS-20946:
|
||||
name: "Grand Theft Auto - San Andreas"
|
||||
region: "NTSC-U"
|
||||
|
@ -47879,7 +47893,9 @@ SLUS-21439:
|
|||
clampModes:
|
||||
eeClampMode: 3 # Fixes material stretching across screen that appears for a split second.
|
||||
gsHWFixes:
|
||||
mipmap: 1
|
||||
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
|
||||
trilinearFiltering: 1
|
||||
textureInsideRT: 2 # Fixes shadow maps.
|
||||
SLUS-21440:
|
||||
name: "Big Idea's VeggieTales - LarryBoy and the Bad Apple"
|
||||
region: "NTSC-U"
|
||||
|
@ -48728,6 +48744,8 @@ SLUS-21633:
|
|||
name: "Aqua Teen Hunger Force - Zombie Ninja Pro-Am"
|
||||
region: "NTSC-U"
|
||||
compat: 5
|
||||
gsHWFixes:
|
||||
textureInsideRT: 2 # Fixes shadows.
|
||||
SLUS-21634:
|
||||
name: "Crazy Frog Racer 2"
|
||||
region: "NTSC-U"
|
||||
|
@ -50790,7 +50808,9 @@ SLUS-29150:
|
|||
name: "Destroy All Humans! [Demo]"
|
||||
region: "NTSC-U"
|
||||
gsHWFixes:
|
||||
mipmap: 1
|
||||
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
|
||||
trilinearFiltering: 1
|
||||
textureInsideRT: 2 # Fixes shadow maps.
|
||||
SLUS-29152:
|
||||
name: "Battlefield 2 - Modern Combat [Regular Demo]"
|
||||
region: "NTSC-U"
|
||||
|
@ -50980,7 +51000,9 @@ SLUS-29196:
|
|||
name: "Destroy All Humans! 2 [Demo]"
|
||||
region: "NTSC-U"
|
||||
gsHWFixes:
|
||||
mipmap: 1
|
||||
mipmap: 2 # Mipmap + trilinear, improves ground textures to match sw renderer.
|
||||
trilinearFiltering: 1
|
||||
textureInsideRT: 2 # Fixes shadow maps.
|
||||
SLUS-29197:
|
||||
name: "Flushed Away [Demo]"
|
||||
region: "NTSC-U"
|
||||
|
|
|
@ -178,11 +178,11 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget*
|
|||
&GraphicsSettingsWidget::onTrilinearFilteringChanged);
|
||||
connect(m_ui.gpuPaletteConversion, QOverload<int>::of(&QCheckBox::stateChanged), this,
|
||||
&GraphicsSettingsWidget::onGpuPaletteConversionChanged);
|
||||
connect(m_ui.textureInsideRt, QOverload<int>::of(&QCheckBox::stateChanged), this,
|
||||
connect(m_ui.textureInsideRt, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||
&GraphicsSettingsWidget::onTextureInsideRtChanged);
|
||||
onTrilinearFilteringChanged();
|
||||
onGpuPaletteConversionChanged(m_ui.gpuPaletteConversion->checkState());
|
||||
onTextureInsideRtChanged(m_ui.textureInsideRt->checkState());
|
||||
onTextureInsideRtChanged();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// HW Renderer Fixes
|
||||
|
@ -200,7 +200,8 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget*
|
|||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.preloadFrameData, "EmuCore/GS", "preload_frame_with_gs_data", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(
|
||||
sif, m_ui.disablePartialInvalidation, "EmuCore/GS", "UserHacks_DisablePartialInvalidation", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.textureInsideRt, "EmuCore/GS", "UserHacks_TextureInsideRt", false);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(
|
||||
sif, m_ui.textureInsideRt, "EmuCore/GS", "UserHacks_TextureInsideRt", static_cast<int>(GSTextureInRtMode::Disabled));
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.readTCOnClose, "EmuCore/GS", "UserHacks_ReadTCOnClose", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.targetPartialInvalidation, "EmuCore/GS", "UserHacks_TargetPartialInvalidation", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.estimateTextureRegion, "EmuCore/GS", "UserHacks_EstimateTextureRegion", false);
|
||||
|
@ -540,7 +541,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget*
|
|||
tr("Uploads GS data when rendering a new frame to reproduce some effects accurately. "
|
||||
"Fixes black screen issues in games like Armored Core: Last Raven."));
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.textureInsideRt, tr("Texture Inside RT"), tr("Unchecked"),
|
||||
dialog->registerWidgetHelp(m_ui.textureInsideRt, tr("Texture Inside RT"), tr("Disabled"),
|
||||
tr("Allows the texture cache to reuse as an input texture the inner portion of a previous framebuffer."));
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.readTCOnClose, tr("Read Targets When Closing"), tr("Unchecked"),
|
||||
|
@ -861,11 +862,10 @@ void GraphicsSettingsWidget::onGpuPaletteConversionChanged(int state)
|
|||
m_ui.anisotropicFiltering->setDisabled(disabled);
|
||||
}
|
||||
|
||||
void GraphicsSettingsWidget::onTextureInsideRtChanged(int state)
|
||||
void GraphicsSettingsWidget::onTextureInsideRtChanged()
|
||||
{
|
||||
const bool disabled = state == Qt::CheckState::PartiallyChecked ?
|
||||
Host::GetBaseBoolSettingValue("EmuCore/GS", "UserHacks_TextureInsideRt", false) :
|
||||
(state != 0);
|
||||
const bool disabled = static_cast<GSTextureInRtMode>(m_dialog->getEffectiveIntValue("EmuCore/GS", "UserHacks_TextureInsideRt",
|
||||
static_cast<int>(GSTextureInRtMode::Disabled))) >= GSTextureInRtMode::InsideTargets;
|
||||
|
||||
m_ui.targetPartialInvalidation->setDisabled(disabled);
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ private Q_SLOTS:
|
|||
void onAdapterChanged(int index);
|
||||
void onTrilinearFilteringChanged();
|
||||
void onGpuPaletteConversionChanged(int state);
|
||||
void onTextureInsideRtChanged(int state);
|
||||
void onTextureInsideRtChanged();
|
||||
void onFullscreenModeChanged(int index);
|
||||
void onShadeBoostChanged();
|
||||
void onCaptureContainerChanged();
|
||||
|
|
|
@ -978,14 +978,14 @@
|
|||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_12">
|
||||
<property name="text">
|
||||
<string>Skipdraw Range:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<item row="5" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="skipDrawStart">
|
||||
|
@ -1003,7 +1003,7 @@
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="2">
|
||||
<item row="6" column="0" colspan="2">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="preloadFrameData">
|
||||
|
@ -1012,13 +1012,6 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="textureInsideRt">
|
||||
<property name="text">
|
||||
<string>Texture Inside RT</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="disableDepthEmulation">
|
||||
<property name="text">
|
||||
|
@ -1061,7 +1054,7 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="readTCOnClose">
|
||||
<property name="text">
|
||||
<string>Read Targets When Closing</string>
|
||||
|
@ -1077,6 +1070,32 @@
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_45">
|
||||
<property name="text">
|
||||
<string>Texture Inside RT:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QComboBox" name="textureInsideRt">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Disabled (Default)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Inside Target</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Merge Targets</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QGroupBox" name="upscalingFixesTab">
|
||||
|
|
|
@ -357,6 +357,13 @@ enum class GSGPUTargetCLUTMode : u8
|
|||
InsideTarget,
|
||||
};
|
||||
|
||||
enum class GSTextureInRtMode : u8
|
||||
{
|
||||
Disabled,
|
||||
InsideTargets,
|
||||
MergeTargets,
|
||||
};
|
||||
|
||||
// Template function for casting enumerations to their underlying type
|
||||
template <typename Enumeration>
|
||||
typename std::underlying_type<Enumeration>::type enum_cast(Enumeration E)
|
||||
|
@ -670,7 +677,6 @@ struct Pcsx2Config
|
|||
UserHacks_DisableSafeFeatures : 1,
|
||||
UserHacks_MergePPSprite : 1,
|
||||
UserHacks_WildHack : 1,
|
||||
UserHacks_TextureInsideRt : 1,
|
||||
UserHacks_TargetPartialInvalidation : 1,
|
||||
UserHacks_EstimateTextureRegion : 1,
|
||||
FXAA : 1,
|
||||
|
@ -748,6 +754,7 @@ struct Pcsx2Config
|
|||
int UserHacks_CPUSpriteRenderBW{0};
|
||||
int UserHacks_CPUCLUTRender{ 0 };
|
||||
GSGPUTargetCLUTMode UserHacks_GPUTargetCLUTMode{GSGPUTargetCLUTMode::Disabled};
|
||||
GSTextureInRtMode UserHacks_TextureInsideRt{GSTextureInRtMode::Disabled};
|
||||
TriFiltering TriFilter{TriFiltering::Automatic};
|
||||
int OverrideTextureBarriers{-1};
|
||||
int OverrideGeometryShaders{-1};
|
||||
|
|
|
@ -159,7 +159,7 @@
|
|||
"textureInsideRT": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 1
|
||||
"maximum": 2
|
||||
},
|
||||
"alignSprite": {
|
||||
"type": "integer",
|
||||
|
|
|
@ -3166,6 +3166,7 @@ void FullscreenUI::DrawGraphicsSettingsPage()
|
|||
"3 (192 Max Width)", "4 (256 Max Width)", "5 (320 Max Width)", "6 (384 Max Width)", "7 (448 Max Width)",
|
||||
"8 (512 Max Width)", "9 (576 Max Width)", "10 (640 Max Width)"};
|
||||
static constexpr const char* s_cpu_clut_render_options[] = {"0 (Disabled)", "1 (Normal)", "2 (Aggressive)"};
|
||||
static constexpr const char* s_texture_inside_rt_options[] = {"Disabled", "Inside Target", "Merge Targets"};
|
||||
static constexpr const char* s_half_pixel_offset_options[] = {
|
||||
"Off (Default)", "Normal (Vertex)", "Special (Texture)", "Special (Texture - Aggressive)"};
|
||||
static constexpr const char* s_round_sprite_options[] = {"Off (Default)", "Half", "Full"};
|
||||
|
@ -3193,9 +3194,9 @@ void FullscreenUI::DrawGraphicsSettingsPage()
|
|||
DrawToggleSetting(bsi, "Disable Partial Invalidation",
|
||||
"Removes texture cache entries when there is any intersection, rather than only the intersected areas.", "EmuCore/GS",
|
||||
"UserHacks_DisablePartialInvalidation", false, manual_hw_fixes);
|
||||
DrawToggleSetting(bsi, "Texture Inside Render Target",
|
||||
DrawIntListSetting(bsi, "Texture Inside Render Target",
|
||||
"Allows the texture cache to reuse as an input texture the inner portion of a previous framebuffer.", "EmuCore/GS",
|
||||
"UserHacks_TextureInsideRt", false, manual_hw_fixes);
|
||||
"UserHacks_TextureInsideRt", 0, s_texture_inside_rt_options, std::size(s_texture_inside_rt_options), 0, manual_hw_fixes);
|
||||
DrawToggleSetting(bsi, "Target Partial Invalidation",
|
||||
"Allows partial invalidation of render targets, which can fix graphical errors in some games.", "EmuCore/GS",
|
||||
"UserHacks_TargetPartialInvalidation", false,
|
||||
|
|
|
@ -409,8 +409,8 @@ void ImGuiManager::DrawSettingsOverlay()
|
|||
APPEND("CCD={} ", GSConfig.UserHacks_CPUCLUTRender);
|
||||
if (GSConfig.SkipDrawStart != 0 || GSConfig.SkipDrawEnd != 0)
|
||||
APPEND("SD={}/{} ", GSConfig.SkipDrawStart, GSConfig.SkipDrawEnd);
|
||||
if (GSConfig.UserHacks_TextureInsideRt)
|
||||
APPEND("TexRT ");
|
||||
if (GSConfig.UserHacks_TextureInsideRt != GSTextureInRtMode::Disabled)
|
||||
APPEND("TexRT={} ", static_cast<int>(GSConfig.UserHacks_TextureInsideRt));
|
||||
if (GSConfig.UserHacks_WildHack)
|
||||
APPEND("WA ");
|
||||
if (GSConfig.UserHacks_MergePPSprite)
|
||||
|
|
|
@ -25,6 +25,12 @@
|
|||
#include "common/Align.h"
|
||||
#include "common/HashCombine.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <stdlib.h>
|
||||
#else
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
|
||||
static u8* s_unswizzle_buffer;
|
||||
|
||||
GSTextureCache::GSTextureCache()
|
||||
|
@ -377,6 +383,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const GIFRegTEX0& TEX0, con
|
|||
|
||||
bool found_t = false;
|
||||
bool tex_in_rt = false;
|
||||
bool tex_merge_rt = false;
|
||||
for (auto t : m_dst[RenderTarget])
|
||||
{
|
||||
if (t->m_used)
|
||||
|
@ -428,6 +435,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const GIFRegTEX0& TEX0, con
|
|||
|
||||
found_t = true;
|
||||
tex_in_rt = false;
|
||||
tex_merge_rt = false;
|
||||
x_offset = 0;
|
||||
y_offset = 0;
|
||||
break;
|
||||
|
@ -442,40 +450,57 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const GIFRegTEX0& TEX0, con
|
|||
dst = t;
|
||||
found_t = true;
|
||||
tex_in_rt = false;
|
||||
tex_merge_rt = false;
|
||||
x_offset = 0;
|
||||
y_offset = 0;
|
||||
break;
|
||||
}
|
||||
// Make sure the texture actually is INSIDE the RT, it's possibly not valid if it isn't.
|
||||
// Also check BP >= TBP, create source isn't equpped to expand it backwards and all data comes from the target. (GH3)
|
||||
else if (GSConfig.UserHacks_TextureInsideRt && psm >= PSM_PSMCT32 && psm <= PSM_PSMCT16S && t->m_TEX0.PSM == psm &&
|
||||
(t->Overlaps(bp, bw, psm, r) || t_wraps) && t->m_age <= 1 && !found_t && bp >= t->m_TEX0.TBP0)
|
||||
else if (GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::InsideTargets && psm >= PSM_PSMCT32 &&
|
||||
psm <= PSM_PSMCT16S && t->m_TEX0.PSM == psm && (t->Overlaps(bp, bw, psm, r) || t_wraps) &&
|
||||
t->m_age <= 1 && !found_t)
|
||||
{
|
||||
// Only PSMCT32 to limit false hits.
|
||||
// PSM equality needed because CreateSource does not handle PSM conversion.
|
||||
// Only inclusive hit to limit false hits.
|
||||
|
||||
// Check if it is possible to hit with valid <x,y> offset on the given Target.
|
||||
// Fixes Jak eyes rendering.
|
||||
// Fixes Xenosaga 3 last dungeon graphic bug.
|
||||
// Fixes Pause menu in The Getaway.
|
||||
|
||||
SurfaceOffset so = ComputeSurfaceOffset(bp, bw, psm, r, t);
|
||||
if (!so.is_valid && t_wraps)
|
||||
if (bp >= t->m_TEX0.TBP0)
|
||||
{
|
||||
// Improves Beyond Good & Evil shadow.
|
||||
const u32 bp_unwrap = bp + GSTextureCache::MAX_BP + 0x1;
|
||||
so = ComputeSurfaceOffset(bp_unwrap, bw, psm, r, t);
|
||||
// Check if it is possible to hit with valid <x,y> offset on the given Target.
|
||||
// Fixes Jak eyes rendering.
|
||||
// Fixes Xenosaga 3 last dungeon graphic bug.
|
||||
// Fixes Pause menu in The Getaway.
|
||||
|
||||
SurfaceOffset so = ComputeSurfaceOffset(bp, bw, psm, r, t);
|
||||
if (!so.is_valid && t_wraps)
|
||||
{
|
||||
// Improves Beyond Good & Evil shadow.
|
||||
const u32 bp_unwrap = bp + GSTextureCache::MAX_BP + 0x1;
|
||||
so = ComputeSurfaceOffset(bp_unwrap, bw, psm, r, t);
|
||||
}
|
||||
if (so.is_valid)
|
||||
{
|
||||
dst = t;
|
||||
// Offset from Target to Source in Target coords.
|
||||
x_offset = so.b2a_offset.x;
|
||||
y_offset = so.b2a_offset.y;
|
||||
tex_in_rt = true;
|
||||
tex_merge_rt = false;
|
||||
found_t = true;
|
||||
// Keep looking, just in case there is an exact match (Situation: Target frame drawn inside target frame, current makes a separate texture)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (so.is_valid)
|
||||
else if (GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::MergeTargets && !tex_merge_rt)
|
||||
{
|
||||
dst = t;
|
||||
// Offset from Target to Source in Target coords.
|
||||
x_offset = so.b2a_offset.x;
|
||||
y_offset = so.b2a_offset.y;
|
||||
tex_in_rt = true;
|
||||
found_t = true;
|
||||
// Keep looking, just in case there is an exact match (Situation: Target frame drawn inside target frame, current makes a separate texture)
|
||||
x_offset = 0;
|
||||
y_offset = 0;
|
||||
tex_in_rt = false;
|
||||
tex_merge_rt = true;
|
||||
|
||||
// Prefer a target inside over a target outside.
|
||||
found_t = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@ -530,7 +555,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const GIFRegTEX0& TEX0, con
|
|||
//
|
||||
// Sigh... They don't help us.
|
||||
|
||||
if (!found_t && !GSConfig.UserHacks_DisableDepthSupport)
|
||||
if (!found_t && !dst && !GSConfig.UserHacks_DisableDepthSupport)
|
||||
{
|
||||
// Let's try a trick to avoid to use wrongly a depth buffer
|
||||
// Unfortunately, I don't have any Arc the Lad testcase
|
||||
|
@ -557,6 +582,9 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const GIFRegTEX0& TEX0, con
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tex_merge_rt)
|
||||
src = CreateMergedSource(TEX0, TEXA, region, dst->m_texture->GetScale());
|
||||
}
|
||||
|
||||
if (!src)
|
||||
|
@ -1261,7 +1289,8 @@ void GSTextureCache::InvalidateVideoMem(const GSOffset& off, const GSVector4i& r
|
|||
const u32 end_bp = off.bnNoWrap(rect.z - 1, rect.w - 1);
|
||||
|
||||
// Ideally in the future we can turn this on unconditionally, but for now it breaks too much.
|
||||
const bool check_inside_target = (GSConfig.UserHacks_TargetPartialInvalidation || GSConfig.UserHacks_TextureInsideRt);
|
||||
const bool check_inside_target = (GSConfig.UserHacks_TargetPartialInvalidation ||
|
||||
GSConfig.UserHacks_TextureInsideRt != GSTextureInRtMode::Disabled);
|
||||
RGBAMask rgba;
|
||||
rgba._u32 = GSUtil::GetChannelMask(psm);
|
||||
|
||||
|
@ -2543,6 +2572,289 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con
|
|||
return src;
|
||||
}
|
||||
|
||||
GSTextureCache::Source* GSTextureCache::CreateMergedSource(GIFRegTEX0 TEX0, GIFRegTEXA TEXA, SourceRegion region, const GSVector2& scale)
|
||||
{
|
||||
// We *should* be able to use the TBW here as an indicator of size... except Destroy All Humans 2 sets
|
||||
// TBW to 10, and samples from 64 through 703... which means it'd be grabbing the next row at the end.
|
||||
const int tex_width = std::max<int>(64 * TEX0.TBW, region.GetMaxX());
|
||||
const int tex_height = region.HasY() ? region.GetHeight() : (1 << TEX0.TH);
|
||||
const int scaled_width = static_cast<int>(static_cast<float>(tex_width) * scale.x);
|
||||
const int scaled_height = static_cast<int>(static_cast<float>(tex_height) * scale.y);
|
||||
|
||||
// Compute new end block based on size.
|
||||
const u32 end_block = GSLocalMemory::m_psm[TEX0.PSM].info.bn(tex_width - 1, tex_height - 1, TEX0.TBP0, TEX0.TBW);
|
||||
GL_PUSH("Merging targets from %x through %x", TEX0.TBP0, end_block);
|
||||
|
||||
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[TEX0.PSM];
|
||||
const int page_width = psm.pgs.x;
|
||||
const int page_height = psm.pgs.y;
|
||||
constexpr int page_blocks = 32;
|
||||
|
||||
// Number of pages to increment by for each row. This may be smaller than tex_width.
|
||||
const int row_page_increment = TEX0.TBW;
|
||||
const int page_block_increment = row_page_increment * page_blocks;
|
||||
const int width_in_pages = (tex_width + (page_width - 1)) / page_width;
|
||||
const int height_in_pages = (tex_height + (page_height - 1)) / page_height;
|
||||
const int num_pages = width_in_pages * height_in_pages;
|
||||
const GSVector4i page_rect(GSVector4i(psm.pgs).zwxy());
|
||||
|
||||
// Temporary texture for preloading local memory.
|
||||
const GSOffset lm_off(psm.info, TEX0.TBP0, TEX0.TBW, TEX0.PSM);
|
||||
GSTexture* lmtex = nullptr;
|
||||
GSTexture::GSMap lmtex_map;
|
||||
bool lmtex_mapped = false;
|
||||
|
||||
u8* pages_done = static_cast<u8*>(alloca((num_pages + 7) / 8));
|
||||
std::memset(pages_done, 0, (num_pages + 7) / 8);
|
||||
|
||||
// Queue of rectangles to copy, we try to batch as many at once as possible.
|
||||
// Multiply by 2 in case we need to preload.
|
||||
GSDevice::MultiStretchRect* copy_queue =
|
||||
static_cast<GSDevice::MultiStretchRect*>(alloca(sizeof(GSDevice::MultiStretchRect) * num_pages * 2));
|
||||
u32 copy_count = 0;
|
||||
|
||||
// Page counters.
|
||||
u32 start_TBP0 = TEX0.TBP0;
|
||||
u32 current_TBP0 = start_TBP0;
|
||||
int page_x = 0;
|
||||
int page_y = 0;
|
||||
|
||||
// Helper to preload a page.
|
||||
auto preload_page = [&](int dst_x, int dst_y) {
|
||||
if (!lmtex)
|
||||
{
|
||||
lmtex = g_gs_device->CreateTexture(tex_width, tex_height, 1, GSTexture::Format::Color, false);
|
||||
lmtex_mapped = lmtex->Map(lmtex_map);
|
||||
}
|
||||
|
||||
const GSVector4i rect(
|
||||
dst_x, dst_y, std::min(dst_x + page_width, tex_width), std::min(dst_y + page_height, tex_height));
|
||||
|
||||
if (lmtex_mapped)
|
||||
{
|
||||
psm.rtx(g_gs_renderer->m_mem, lm_off, rect, lmtex_map.bits + dst_y * lmtex_map.pitch + dst_x * sizeof(u32),
|
||||
lmtex_map.pitch, TEXA);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Slow for DX11... page_width * 4 should still be 32 byte aligned for AVX.
|
||||
const int pitch = page_width * sizeof(u32);
|
||||
psm.rtx(g_gs_renderer->m_mem, lm_off, rect, s_unswizzle_buffer, pitch, TEXA);
|
||||
lmtex->Update(rect, s_unswizzle_buffer, pitch);
|
||||
}
|
||||
|
||||
// Upload texture -> render target.
|
||||
const bool linear = (scale.x != 1.0f);
|
||||
copy_queue[copy_count++] = {lmtex, GSVector4(rect) / GSVector4(lmtex->GetSize()).xyxy(),
|
||||
GSVector4(rect) * GSVector4(scale).xyxy(), linear};
|
||||
};
|
||||
|
||||
// The idea: loop through pages that this texture covers, find targets which overlap, and copy them in.
|
||||
// For pages which don't have any targets covering, if preloading is enabled, load that page from local memory.
|
||||
// Try to batch as much as possible, ideally this shouldn't be more than a handful of draw calls.
|
||||
for (int page_num = 0; page_num < num_pages; page_num++)
|
||||
{
|
||||
const u32 this_start_block = current_TBP0;
|
||||
const u32 this_end_block = (current_TBP0 + page_blocks) - 1;
|
||||
bool found = false;
|
||||
|
||||
if (pages_done[page_num / 8] & (1u << (page_num % 8)))
|
||||
goto next_page;
|
||||
|
||||
GL_INS("Searching for block range %x - %x for (%u,%u)", this_start_block, this_end_block, page_x * page_width,
|
||||
page_y * page_height);
|
||||
|
||||
for (auto i = m_dst[RenderTarget].begin(); i != m_dst[RenderTarget].end(); ++i)
|
||||
{
|
||||
Target* const t = *i;
|
||||
if (this_start_block >= t->m_TEX0.TBP0 && this_end_block <= t->m_end_block && t->m_TEX0.PSM == TEX0.PSM)
|
||||
{
|
||||
GL_INS(" Candidate at BP %x BW %d PSM %d", t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM);
|
||||
|
||||
// Can't copy multiple pages when we're past the TBW.. only grab one page at a time then.
|
||||
GSVector4i src_rect(page_rect);
|
||||
bool copy_multiple_pages = (t->m_TEX0.TBW == TEX0.TBW && page_x < row_page_increment);
|
||||
int available_pages_x = 1;
|
||||
int available_pages_y = 1;
|
||||
if (copy_multiple_pages)
|
||||
{
|
||||
// TBW matches, we can copy multiple pages.
|
||||
available_pages_x = (row_page_increment - page_x);
|
||||
available_pages_y = (height_in_pages - page_y);
|
||||
src_rect.z = available_pages_x * page_width;
|
||||
src_rect.w = available_pages_y * page_height;
|
||||
}
|
||||
|
||||
// Why do we do this? Things go really haywire when we try to get surface offsets when
|
||||
// the valid rect isn't starting at 0. In most cases, the target has been cleared anyway,
|
||||
// so we really should be setting this to 0, not the draw...
|
||||
//
|
||||
// Guitar Hero needs it, the player meshes don't necessarily touch the corner, and so
|
||||
// does Aqua Teen Hunger Force.
|
||||
//
|
||||
const GSVector4i t_rect(t->m_valid.insert64<0>(0));
|
||||
|
||||
SurfaceOffset so;
|
||||
if (this_start_block > t->m_TEX0.TBP0)
|
||||
{
|
||||
SurfaceOffsetKey sok;
|
||||
sok.elems[0].bp = this_start_block;
|
||||
sok.elems[0].bw = TEX0.TBW;
|
||||
sok.elems[0].psm = TEX0.PSM;
|
||||
sok.elems[0].rect = src_rect;
|
||||
sok.elems[1].bp = t->m_TEX0.TBP0;
|
||||
sok.elems[1].bw = t->m_TEX0.TBW;
|
||||
sok.elems[1].psm = t->m_TEX0.PSM;
|
||||
sok.elems[1].rect = t_rect;
|
||||
so = ComputeSurfaceOffset(sok);
|
||||
if (!so.is_valid)
|
||||
goto next_page;
|
||||
}
|
||||
else
|
||||
{
|
||||
so.is_valid = true;
|
||||
so.b2a_offset = src_rect.rintersect(t_rect);
|
||||
}
|
||||
|
||||
// Adjust to what the target actually has.
|
||||
if (copy_multiple_pages)
|
||||
{
|
||||
// Min here because we don't want to go off the end of the target.
|
||||
available_pages_x = std::min(available_pages_x, (so.b2a_offset.width() + (psm.pgs.x - 1)) / psm.pgs.x);
|
||||
available_pages_y = std::min(available_pages_y, (so.b2a_offset.height() + (psm.pgs.y - 1)) / psm.pgs.y);
|
||||
}
|
||||
|
||||
// We might not even have a full page valid..
|
||||
const bool linear = (scale != t->m_texture->GetScale());
|
||||
const int src_x_end = so.b2a_offset.z;
|
||||
const int src_y_end = so.b2a_offset.w;
|
||||
int src_y = so.b2a_offset.y;
|
||||
int dst_y = page_y * page_height;
|
||||
int current_copy_page = page_num;
|
||||
|
||||
for (int copy_page_y = 0; copy_page_y < available_pages_y; copy_page_y++)
|
||||
{
|
||||
if (src_y >= src_y_end)
|
||||
break;
|
||||
|
||||
const int wanted_height = std::min(tex_height - dst_y, page_height);
|
||||
const int copy_height = std::min(src_y_end - src_y, wanted_height);
|
||||
int src_x = so.b2a_offset.x;
|
||||
int dst_x = page_x * page_width;
|
||||
int row_page = current_copy_page;
|
||||
pxAssert(dst_y < tex_height && copy_height > 0);
|
||||
|
||||
for (int copy_page_x = 0; copy_page_x < available_pages_x; copy_page_x++)
|
||||
{
|
||||
if (src_x >= src_x_end)
|
||||
break;
|
||||
|
||||
if ((pages_done[row_page / 8] & (1u << (row_page % 8))) == 0)
|
||||
{
|
||||
pages_done[row_page / 8] |= (1u << (row_page % 8));
|
||||
|
||||
// In case a whole page isn't valid.
|
||||
const int wanted_width = std::min(tex_width - dst_x, page_width);
|
||||
const int copy_width = std::min(src_x_end - src_x, wanted_width);
|
||||
pxAssert(dst_x < tex_width && copy_width > 0);
|
||||
|
||||
// Preload any missing parts. This will happen when the valid rect isn't page aligned.
|
||||
if (GSConfig.PreloadFrameWithGSData &&
|
||||
(copy_width < wanted_width || copy_height < wanted_height))
|
||||
{
|
||||
preload_page(dst_x, dst_y);
|
||||
}
|
||||
|
||||
GL_INS(" Copy from %d,%d -> %d,%d (%dx%d)", src_x, src_y, dst_x, dst_y, copy_width, copy_height);
|
||||
copy_queue[copy_count++] = {t->m_texture,
|
||||
(GSVector4(src_x, src_y, src_x + copy_width, src_y + copy_height) *
|
||||
GSVector4(t->m_texture->GetScale()).xyxy()) /
|
||||
GSVector4(t->m_texture->GetSize()).xyxy(),
|
||||
GSVector4(dst_x, dst_y, dst_x + copy_width, dst_y + copy_height) *
|
||||
GSVector4(scale).xyxy(),
|
||||
linear};
|
||||
}
|
||||
|
||||
row_page++;
|
||||
src_x += page_width;
|
||||
dst_x += page_width;
|
||||
}
|
||||
|
||||
current_copy_page += width_in_pages;
|
||||
src_y += page_height;
|
||||
dst_y += page_height;
|
||||
}
|
||||
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
if (GSConfig.PreloadFrameWithGSData)
|
||||
{
|
||||
pages_done[page_num / 8] |= (1u << (page_num % 8));
|
||||
|
||||
GL_INS(" *** NOT FOUND, preloading from local memory");
|
||||
const int dst_x = page_x * page_width;
|
||||
const int dst_y = page_y * page_height;
|
||||
preload_page(dst_x, dst_y);
|
||||
}
|
||||
else
|
||||
{
|
||||
GL_INS(" *** NOT FOUND");
|
||||
}
|
||||
}
|
||||
|
||||
next_page:
|
||||
current_TBP0 += page_blocks;
|
||||
page_x++;
|
||||
if (page_x == width_in_pages)
|
||||
{
|
||||
start_TBP0 += row_page_increment * page_blocks;
|
||||
current_TBP0 = start_TBP0;
|
||||
page_x = 0;
|
||||
page_y++;
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't find anything, abort.
|
||||
if (copy_count == 0)
|
||||
{
|
||||
GL_INS("No sources found.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Actually do the drawing.
|
||||
if (lmtex_mapped)
|
||||
lmtex->Unmap();
|
||||
|
||||
// Allocate our render target for drawing everything to.
|
||||
GSTexture* dtex = g_gs_device->CreateRenderTarget(scaled_width, scaled_height, GSTexture::Format::Color, true);
|
||||
dtex->SetScale(scale);
|
||||
m_source_memory_usage += dtex->GetMemUsage();
|
||||
|
||||
// Sort rect list by the texture, we want to batch as many as possible together.
|
||||
g_gs_device->SortMultiStretchRects(copy_queue, copy_count);
|
||||
g_gs_device->DrawMultiStretchRects(copy_queue, copy_count, dtex, ShaderConvert::COPY);
|
||||
g_perfmon.Put(GSPerfMon::TextureCopies, 1);
|
||||
|
||||
if (lmtex)
|
||||
g_gs_device->Recycle(lmtex);
|
||||
|
||||
Source* src = new Source(TEX0, TEXA);
|
||||
src->m_texture = dtex;
|
||||
src->m_end_block = end_block;
|
||||
src->m_target = true;
|
||||
|
||||
// Can't use the normal SetPages() here, it'll try to use TW/TH, which might be bad.
|
||||
src->m_pages = g_gs_renderer->m_context->offset.tex.pageLooperForRect(GSVector4i(0, 0, tex_width, tex_height));
|
||||
|
||||
m_src.Add(src, TEX0, g_gs_renderer->m_context->offset.tex);
|
||||
return src;
|
||||
}
|
||||
|
||||
// This really needs a better home...
|
||||
extern bool FMVstarted;
|
||||
|
||||
|
|
|
@ -394,6 +394,8 @@ protected:
|
|||
// TODO: virtual void Write(Source* s, const GSVector4i& r) = 0;
|
||||
// TODO: virtual void Write(Target* t, const GSVector4i& r) = 0;
|
||||
|
||||
Source* CreateMergedSource(GIFRegTEX0 TEX0, GIFRegTEXA TEXA, SourceRegion region, const GSVector2& scale);
|
||||
|
||||
public:
|
||||
GSTextureCache();
|
||||
~GSTextureCache();
|
||||
|
|
|
@ -587,7 +587,7 @@ void GSDeviceVK::DrawMultiStretchRects(
|
|||
last_tex = rects[i].src;
|
||||
last_linear = rects[i].linear;
|
||||
first += count;
|
||||
count = 0;
|
||||
count = 1;
|
||||
}
|
||||
|
||||
DoMultiStretchRects(rects + first, count, static_cast<GSTextureVK*>(dTex), shader);
|
||||
|
|
|
@ -691,8 +691,11 @@ u32 GameDatabaseSchema::GameEntry::applyGSHardwareFixes(Pcsx2Config::GSOptions&
|
|||
break;
|
||||
|
||||
case GSHWFixId::TextureInsideRT:
|
||||
config.UserHacks_TextureInsideRt = (value > 0);
|
||||
break;
|
||||
{
|
||||
if (value >= 0 && value <= static_cast<int>(GSTextureInRtMode::MergeTargets))
|
||||
config.UserHacks_TextureInsideRt = static_cast<GSTextureInRtMode>(value);
|
||||
}
|
||||
break;
|
||||
|
||||
case GSHWFixId::AlignSprite:
|
||||
config.UserHacks_AlignSpriteX = (value > 0);
|
||||
|
|
|
@ -517,6 +517,7 @@ bool Pcsx2Config::GSOptions::OptionsAreEqual(const GSOptions& right) const
|
|||
OpEqu(UserHacks_CPUSpriteRenderBW) &&
|
||||
OpEqu(UserHacks_CPUCLUTRender) &&
|
||||
OpEqu(UserHacks_GPUTargetCLUTMode) &&
|
||||
OpEqu(UserHacks_TextureInsideRt) &&
|
||||
OpEqu(OverrideTextureBarriers) &&
|
||||
OpEqu(OverrideGeometryShaders) &&
|
||||
|
||||
|
@ -647,7 +648,7 @@ void Pcsx2Config::GSOptions::LoadSave(SettingsWrapper& wrap)
|
|||
GSSettingBoolEx(UserHacks_DisableSafeFeatures, "UserHacks_Disable_Safe_Features");
|
||||
GSSettingBoolEx(UserHacks_MergePPSprite, "UserHacks_merge_pp_sprite");
|
||||
GSSettingBoolEx(UserHacks_WildHack, "UserHacks_WildHack");
|
||||
GSSettingBoolEx(UserHacks_TextureInsideRt, "UserHacks_TextureInsideRt");
|
||||
GSSettingIntEnumEx(UserHacks_TextureInsideRt, "UserHacks_TextureInsideRt");
|
||||
GSSettingBoolEx(UserHacks_TargetPartialInvalidation, "UserHacks_TargetPartialInvalidation");
|
||||
GSSettingBoolEx(UserHacks_EstimateTextureRegion, "UserHacks_EstimateTextureRegion");
|
||||
GSSettingBoolEx(FXAA, "fxaa");
|
||||
|
@ -775,7 +776,7 @@ void Pcsx2Config::GSOptions::MaskUserHacks()
|
|||
UserHacks_DisableDepthSupport = false;
|
||||
UserHacks_CPUFBConversion = false;
|
||||
UserHacks_ReadTCOnClose = false;
|
||||
UserHacks_TextureInsideRt = false;
|
||||
UserHacks_TextureInsideRt = GSTextureInRtMode::Disabled;
|
||||
UserHacks_TargetPartialInvalidation = false;
|
||||
UserHacks_EstimateTextureRegion = false;
|
||||
UserHacks_TCOffsetX = 0;
|
||||
|
|
Loading…
Reference in New Issue