mirror of https://github.com/PCSX2/pcsx2.git
GS: Add full (hash cache) texture preloading option
This commit is contained in:
parent
6b2a851dec
commit
5d33af13ca
|
@ -133,7 +133,8 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget*
|
|||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.accurateDATE, "EmuCore/GS", "accurate_date", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.conservativeBufferAllocation, "EmuCore/GS", "conservative_framebuffer", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.gpuPaletteConversion, "EmuCore/GS", "paltex", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.preloadTexture, "EmuCore/GS", "preload_texture", false);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.texturePreloading, "EmuCore/GS", "texture_preloading",
|
||||
static_cast<int>(TexturePreloadingLevel::Off));
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// HW Renderer Fixes
|
||||
|
|
|
@ -560,12 +560,12 @@
|
|||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0" colspan="2">
|
||||
<item row="9" column="0" colspan="2">
|
||||
<layout class="QGridLayout" name="basicCheckboxGridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="accurateDATE">
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="gpuPaletteConversion">
|
||||
<property name="text">
|
||||
<string>Accurate Destination Alpha Test</string>
|
||||
<string>GPU Palette Conversion</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -576,21 +576,14 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="gpuPaletteConversion">
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="accurateDATE">
|
||||
<property name="text">
|
||||
<string>GPU Palette Conversion</string>
|
||||
<string>Accurate Destination Alpha Test</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="preloadTexture">
|
||||
<property name="text">
|
||||
<string>Preload Textures</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="enableHWFixes">
|
||||
<property name="text">
|
||||
<string>Enable Hardware Renderer Fixes</string>
|
||||
|
@ -599,6 +592,32 @@
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="label_20">
|
||||
<property name="text">
|
||||
<string>Texture Preloading:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<widget class="QComboBox" name="texturePreloading">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>None</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Partial</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Full (Hash Cache)</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QGroupBox" name="hardwareFixesTab">
|
||||
|
|
|
@ -179,6 +179,13 @@ enum class AccBlendLevel : u8
|
|||
Ultra,
|
||||
};
|
||||
|
||||
enum class TexturePreloadingLevel : u8
|
||||
{
|
||||
Off,
|
||||
Partial,
|
||||
Full,
|
||||
};
|
||||
|
||||
// Template function for casting enumerations to their underlying type
|
||||
template <typename Enumeration>
|
||||
typename std::underlying_type<Enumeration>::type enum_cast(Enumeration E)
|
||||
|
@ -450,8 +457,7 @@ struct Pcsx2Config
|
|||
SaveRT : 1,
|
||||
SaveFrame : 1,
|
||||
SaveTexture : 1,
|
||||
SaveDepth : 1,
|
||||
PreloadTexture : 1;
|
||||
SaveDepth : 1;
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -490,6 +496,7 @@ struct Pcsx2Config
|
|||
AccBlendLevel AccurateBlendingUnit{AccBlendLevel::Basic};
|
||||
CRCHackLevel CRCHack{CRCHackLevel::Automatic};
|
||||
BiFiltering TextureFiltering{BiFiltering::PS2};
|
||||
TexturePreloadingLevel TexturePreloading{TexturePreloadingLevel::Off};
|
||||
int Dithering{2};
|
||||
int MaxAnisotropy{0};
|
||||
int SWExtraThreads{2};
|
||||
|
|
|
@ -798,6 +798,7 @@ void GSUpdateConfig(const Pcsx2Config::GSOptions& new_config)
|
|||
|
||||
// reload texture cache when trilinear filtering or mipmap options change
|
||||
if (GSConfig.HWMipmap != old_config.HWMipmap ||
|
||||
GSConfig.TexturePreloading != old_config.TexturePreloading ||
|
||||
GSConfig.UserHacks_TriFilter != old_config.UserHacks_TriFilter ||
|
||||
GSConfig.GPUPaletteConversion != old_config.GPUPaletteConversion)
|
||||
{
|
||||
|
@ -1208,6 +1209,10 @@ void GSApp::Init()
|
|||
m_gs_trifilter.push_back(GSSetting(static_cast<u32>(TriFiltering::PS2), "Trilinear", ""));
|
||||
m_gs_trifilter.push_back(GSSetting(static_cast<u32>(TriFiltering::Forced), "Trilinear", "Ultra/Slow"));
|
||||
|
||||
m_gs_texture_preloading.push_back(GSSetting(static_cast<u32>(TexturePreloadingLevel::Off), "None", "Default"));
|
||||
m_gs_texture_preloading.push_back(GSSetting(static_cast<u32>(TexturePreloadingLevel::Partial), "Partial", ""));
|
||||
m_gs_texture_preloading.push_back(GSSetting(static_cast<u32>(TexturePreloadingLevel::Full), "Full", "Hash Cache"));
|
||||
|
||||
m_gs_generic_list.push_back(GSSetting(-1, "Automatic", "Default"));
|
||||
m_gs_generic_list.push_back(GSSetting(0, "Force-Disabled", ""));
|
||||
m_gs_generic_list.push_back(GSSetting(1, "Force-Enabled", ""));
|
||||
|
@ -1321,7 +1326,6 @@ void GSApp::Init()
|
|||
m_default_configuration["paltex"] = "0";
|
||||
m_default_configuration["png_compression_level"] = std::to_string(Z_BEST_SPEED);
|
||||
m_default_configuration["preload_frame_with_gs_data"] = "0";
|
||||
m_default_configuration["preload_texture"] = "0";
|
||||
m_default_configuration["Renderer"] = std::to_string(static_cast<int>(GSRendererType::Auto));
|
||||
m_default_configuration["resx"] = "1024";
|
||||
m_default_configuration["resy"] = "1024";
|
||||
|
@ -1339,6 +1343,7 @@ void GSApp::Init()
|
|||
m_default_configuration["shaderfx_conf"] = "shaders/GS_FX_Settings.ini";
|
||||
m_default_configuration["shaderfx_glsl"] = "shaders/GS.fx";
|
||||
m_default_configuration["skip_duplicate_frames"] = "0";
|
||||
m_default_configuration["texture_preloading"] = "0";
|
||||
m_default_configuration["ThreadedPresentation"] = "0";
|
||||
m_default_configuration["throttle_present_rate"] = "0";
|
||||
m_default_configuration["TVShader"] = "0";
|
||||
|
|
|
@ -133,6 +133,7 @@ public:
|
|||
std::vector<GSSetting> m_gs_dithering;
|
||||
std::vector<GSSetting> m_gs_bifilter;
|
||||
std::vector<GSSetting> m_gs_trifilter;
|
||||
std::vector<GSSetting> m_gs_texture_preloading;
|
||||
std::vector<GSSetting> m_gs_hack;
|
||||
std::vector<GSSetting> m_gs_generic_list;
|
||||
std::vector<GSSetting> m_gs_offset_hack;
|
||||
|
|
|
@ -110,14 +110,25 @@ static const GSVector2i default_rt_size(2048, 2048);
|
|||
static const GSVector2i default_rt_size(0, 0);
|
||||
#endif
|
||||
|
||||
extern Pcsx2Config::GSOptions GSConfig;
|
||||
|
||||
// Maximum texture size to skip preload/hash path.
|
||||
// This is the width/height from the registers, i.e. not the power of 2.
|
||||
__fi static bool CanCacheTextureSize(u32 tw, u32 th)
|
||||
{
|
||||
static constexpr u32 MAXIMUM_CACHE_SIZE = 10; // 1024
|
||||
return (GSConfig.TexturePreloading == TexturePreloadingLevel::Full && tw <= MAXIMUM_CACHE_SIZE && th <= MAXIMUM_CACHE_SIZE);
|
||||
}
|
||||
|
||||
__fi static bool CanPreloadTextureSize(u32 tw, u32 th)
|
||||
{
|
||||
static constexpr u32 MAXIMUM_SIZE_IN_ONE_DIRECTION = 10; // 1024
|
||||
static constexpr u32 MAXIMUM_SIZE_IN_OTHER_DIRECTION = 8; // 256
|
||||
static constexpr u32 MAXIMUM_SIZE_IN_BOTH_DIRECTIONS = 9; // 512
|
||||
|
||||
if (GSConfig.TexturePreloading < TexturePreloadingLevel::Partial)
|
||||
return false;
|
||||
|
||||
// We use an area-based approach here. We want to hash long font maps,
|
||||
// like 128x1024 (used in FFX), but skip 1024x512 textures (e.g. Xenosaga).
|
||||
const u32 max_dimension = (tw > th) ? tw : th;
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
#include "GS/GSGL.h"
|
||||
#include "GS/GSIntrin.h"
|
||||
#include "GS/GSUtil.h"
|
||||
#include "common/Align.h"
|
||||
#include "common/HashCombine.h"
|
||||
|
||||
#define XXH_STATIC_LINKING_ONLY 1
|
||||
#define XXH_INLINE_ALL 1
|
||||
|
@ -26,6 +28,7 @@
|
|||
|
||||
bool GSTextureCache::m_disable_partial_invalidation = false;
|
||||
bool GSTextureCache::m_wrap_gs_mem = false;
|
||||
u8* GSTextureCache::m_temp;
|
||||
|
||||
GSTextureCache::GSTextureCache(GSRenderer* r)
|
||||
: m_renderer(r)
|
||||
|
@ -94,6 +97,10 @@ void GSTextureCache::RemoveAll()
|
|||
m_dst[type].clear();
|
||||
}
|
||||
|
||||
for (auto it : m_hash_cache)
|
||||
g_gs_device->Recycle(it.second.texture);
|
||||
m_hash_cache.clear();
|
||||
|
||||
m_palette_map.Clear();
|
||||
}
|
||||
|
||||
|
@ -155,7 +162,7 @@ GSTextureCache::Source* GSTextureCache::LookupDepthSource(const GIFRegTEX0& TEX0
|
|||
TEX0.TBP0, psm_str(psm));
|
||||
|
||||
// Create a shared texture source
|
||||
src = new Source(m_renderer, TEX0, TEXA, m_temp, true);
|
||||
src = new Source(m_renderer, TEX0, TEXA, true);
|
||||
src->m_texture = dst->m_texture;
|
||||
src->m_shared_texture = true;
|
||||
src->m_target = true; // So renderer can check if a conversion is required
|
||||
|
@ -746,7 +753,7 @@ void GSTextureCache::InvalidateVideoMem(const GSOffset& off, const GSVector4i& r
|
|||
|
||||
if (!s->m_target)
|
||||
{
|
||||
if (m_disable_partial_invalidation && s->m_repeating)
|
||||
if (s->m_from_hash_cache || (m_disable_partial_invalidation && s->m_repeating))
|
||||
{
|
||||
m_src.RemoveAt(s);
|
||||
}
|
||||
|
@ -1119,6 +1126,21 @@ void GSTextureCache::IncAge()
|
|||
}
|
||||
}
|
||||
|
||||
const u32 max_hash_cache_age = 30;
|
||||
for (auto it = m_hash_cache.begin(); it != m_hash_cache.end();)
|
||||
{
|
||||
HashCacheEntry& e = it->second;
|
||||
if (e.refcount == 0 && ++e.age > max_hash_cache_age)
|
||||
{
|
||||
g_gs_device->Recycle(e.texture);
|
||||
m_hash_cache.erase(it++);
|
||||
}
|
||||
else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
m_src.m_used = false;
|
||||
|
||||
// Clearing of Rendertargets causes flickering in many scene transitions.
|
||||
|
@ -1165,7 +1187,7 @@ void GSTextureCache::IncAge()
|
|||
GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, Target* dst, bool half_right, int x_offset, int y_offset, bool mipmap)
|
||||
{
|
||||
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[TEX0.PSM];
|
||||
Source* src = new Source(m_renderer, TEX0, TEXA, m_temp);
|
||||
Source* src = new Source(m_renderer, TEX0, TEXA, false);
|
||||
|
||||
int tw = 1 << TEX0.TW;
|
||||
int th = 1 << TEX0.TH;
|
||||
|
@ -1426,7 +1448,37 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con
|
|||
}
|
||||
else
|
||||
{
|
||||
if (GSConfig.GPUPaletteConversion && psm.pal > 0)
|
||||
// try the hash cache
|
||||
if (!mipmap && CanCacheTextureSize(TEX0.TW, TEX0.TH))
|
||||
{
|
||||
const bool paltex = (GSConfig.GPUPaletteConversion && psm.pal > 0);
|
||||
const u32* clut = (!paltex && psm.pal > 0) ? static_cast<const u32*>(m_renderer->m_mem.m_clut) : nullptr;
|
||||
const HashCacheKey key{ HashCacheKey::Create(TEX0, TEXA, m_renderer, clut) };
|
||||
|
||||
auto it = m_hash_cache.find(key);
|
||||
if (it == m_hash_cache.end())
|
||||
{
|
||||
// hash and upload texture
|
||||
src->m_texture = g_gs_device->CreateTexture(tw, th, paltex ? false : mipmap, paltex ? GSTexture::Format::UNorm8 : GSTexture::Format::Color);
|
||||
PreloadTexture(TEX0, TEXA, m_renderer->m_mem, paltex, src->m_texture, 0);
|
||||
|
||||
// insert it into the hash cache
|
||||
HashCacheEntry entry{ src->m_texture, 1, 0 };
|
||||
it = m_hash_cache.emplace(key, entry).first;
|
||||
}
|
||||
else
|
||||
{
|
||||
// use existing texture
|
||||
src->m_texture = it->second.texture;
|
||||
it->second.refcount++;
|
||||
}
|
||||
|
||||
src->m_from_hash_cache = &it->second;
|
||||
|
||||
if (psm.pal > 0)
|
||||
AttachPaletteToSource(src, psm.pal, paltex);
|
||||
}
|
||||
else if (GSConfig.GPUPaletteConversion && psm.pal > 0)
|
||||
{
|
||||
src->m_texture = g_gs_device->CreateTexture(tw, th, false, GSTexture::Format::UNorm8);
|
||||
AttachPaletteToSource(src, psm.pal, true);
|
||||
|
@ -1452,7 +1504,7 @@ GSTextureCache::Target* GSTextureCache::CreateTarget(const GIFRegTEX0& TEX0, int
|
|||
{
|
||||
ASSERT(type == RenderTarget || type == DepthStencil);
|
||||
|
||||
Target* t = new Target(m_renderer, TEX0, m_temp, m_can_convert_depth, type);
|
||||
Target* t = new Target(m_renderer, TEX0, m_can_convert_depth, type);
|
||||
|
||||
// FIXME: initial data should be unswizzled from local mem in Update() if dirty
|
||||
|
||||
|
@ -1605,11 +1657,11 @@ void GSTextureCache::PrintMemoryUsage()
|
|||
|
||||
// GSTextureCache::Surface
|
||||
|
||||
GSTextureCache::Surface::Surface(GSRenderer* r, u8* temp)
|
||||
GSTextureCache::Surface::Surface(GSRenderer* r)
|
||||
: m_renderer(r)
|
||||
, m_texture(NULL)
|
||||
, m_from_hash_cache(NULL)
|
||||
, m_age(0)
|
||||
, m_temp(temp)
|
||||
, m_32_bits_fmt(false)
|
||||
, m_shared_texture(false)
|
||||
, m_end_block(0)
|
||||
|
@ -1621,7 +1673,7 @@ GSTextureCache::Surface::~Surface()
|
|||
{
|
||||
// Shared textures are pointers copy. Therefore no allocation
|
||||
// to recycle.
|
||||
if (!m_shared_texture && m_texture)
|
||||
if (!m_shared_texture && !m_from_hash_cache && m_texture)
|
||||
g_gs_device->Recycle(m_texture);
|
||||
}
|
||||
|
||||
|
@ -1647,8 +1699,8 @@ bool GSTextureCache::Surface::Overlaps(u32 bp, u32 bw, u32 psm, const GSVector4i
|
|||
|
||||
// GSTextureCache::Source
|
||||
|
||||
GSTextureCache::Source::Source(GSRenderer* r, const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, u8* temp, bool dummy_container)
|
||||
: Surface(r, temp)
|
||||
GSTextureCache::Source::Source(GSRenderer* r, const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, bool dummy_container)
|
||||
: Surface(r)
|
||||
, m_palette_obj(nullptr)
|
||||
, m_palette(nullptr)
|
||||
, m_valid_rect(0, 0)
|
||||
|
@ -1699,7 +1751,7 @@ void GSTextureCache::Source::Update(const GSVector4i& rect, int level)
|
|||
{
|
||||
Surface::UpdateAge();
|
||||
|
||||
if (m_target || (m_complete_layers & (1u << level)))
|
||||
if (m_target || m_from_hash_cache || (m_complete_layers & (1u << level)))
|
||||
return;
|
||||
|
||||
if (CanPreload())
|
||||
|
@ -1906,28 +1958,6 @@ void GSTextureCache::Source::Flush(u32 count, int layer)
|
|||
m_write.count -= count;
|
||||
}
|
||||
|
||||
using BlockHashState = XXH3_state_t;
|
||||
|
||||
__fi static void BlockHashReset(BlockHashState& st)
|
||||
{
|
||||
XXH3_64bits_reset(&st);
|
||||
}
|
||||
|
||||
__fi static void BlockHashAccumulate(BlockHashState& st, const u8* bp)
|
||||
{
|
||||
XXH3_64bits_update(&st, bp, BLOCK_SIZE);
|
||||
}
|
||||
|
||||
__fi static void BlockHashAccumulate(BlockHashState& st, const u8* bp, u32 size)
|
||||
{
|
||||
XXH3_64bits_update(&st, bp, size);
|
||||
}
|
||||
|
||||
__fi static GSTextureCache::Source::HashType FinishBlockHash(BlockHashState& st)
|
||||
{
|
||||
return XXH3_64bits_digest(&st);
|
||||
}
|
||||
|
||||
void GSTextureCache::Source::PreloadLevel(int level)
|
||||
{
|
||||
// m_TEX0 is adjusted for mips (messy, should be changed).
|
||||
|
@ -1936,42 +1966,8 @@ void GSTextureCache::Source::PreloadLevel(int level)
|
|||
const int tw = 1 << m_TEX0.TW;
|
||||
const int th = 1 << m_TEX0.TH;
|
||||
|
||||
// For textures which are smaller than the block size, we expand and then hash.
|
||||
// This is because otherwise we get the padding bytes, which can be random junk.
|
||||
if (tw < bs.x || th < bs.y)
|
||||
{
|
||||
PreloadSmallLevel(level);
|
||||
return;
|
||||
}
|
||||
|
||||
// From GSLocalMemory foreachBlock(), used for reading textures.
|
||||
// We want to hash the exact same blocks here.
|
||||
const GSVector4i rect(0, 0, tw, th);
|
||||
const GSVector4i block_rect(rect.ralign<Align_Outside>(bs));
|
||||
const GSOffset& off = m_renderer->m_context->offset.tex;
|
||||
GSLocalMemory& mem = m_renderer->m_mem;
|
||||
HashType hash;
|
||||
{
|
||||
BlockHashState hash_st;
|
||||
BlockHashReset(hash_st);
|
||||
|
||||
GSOffset::BNHelper bn = off.bnMulti(block_rect.left, block_rect.top);
|
||||
const int right = block_rect.right >> off.blockShiftX();
|
||||
const int bottom = block_rect.bottom >> off.blockShiftY();
|
||||
const int xAdd = (1 << off.blockShiftX()) * (psm.bpp / 8);
|
||||
|
||||
for (; bn.blkY() < bottom; bn.nextBlockY())
|
||||
{
|
||||
for (int x = 0; bn.blkX() < right; bn.nextBlockX(), x += xAdd)
|
||||
{
|
||||
BlockHashAccumulate(hash_st, mem.BlockPtr(bn.value()));
|
||||
}
|
||||
}
|
||||
|
||||
hash = FinishBlockHash(hash_st);
|
||||
}
|
||||
|
||||
// Layer is complete again, regardless of whether the hash matches or not (and we reupload).
|
||||
const HashType hash = HashTexture(m_renderer, m_TEX0, m_TEXA);
|
||||
const u8 layer_bit = static_cast<u8>(1) << level;
|
||||
m_complete_layers |= layer_bit;
|
||||
|
||||
|
@ -1982,91 +1978,8 @@ void GSTextureCache::Source::PreloadLevel(int level)
|
|||
m_valid_hashes |= layer_bit;
|
||||
m_layer_hash[level] = hash;
|
||||
|
||||
// Expand texture/apply palette.
|
||||
const int read_width = std::max(tw, psm.bs.x);
|
||||
u32 pitch = static_cast<u32>(read_width) * sizeof(u32);
|
||||
u32 row_size = static_cast<u32>(tw) * sizeof(u32);
|
||||
GSLocalMemory::readTexture rtx = psm.rtx;
|
||||
if (m_palette)
|
||||
{
|
||||
pitch >>= 2;
|
||||
row_size >>= 2;
|
||||
rtx = psm.rtxP;
|
||||
}
|
||||
|
||||
// If we can stream it directly to GPU memory, do so, otherwise go through a temp buffer.
|
||||
GSTexture::GSMap map;
|
||||
if (rect.eq(block_rect) && m_texture->Map(map, &rect, level))
|
||||
{
|
||||
(m_renderer->m_mem.*rtx)(off, block_rect, map.bits, map.pitch, m_TEXA);
|
||||
m_texture->Unmap();
|
||||
}
|
||||
else
|
||||
{
|
||||
u8* buff = m_temp;
|
||||
(m_renderer->m_mem.*rtx)(off, block_rect, buff, pitch, m_TEXA);
|
||||
m_texture->Update(rect, buff, pitch, level);
|
||||
}
|
||||
}
|
||||
|
||||
void GSTextureCache::Source::PreloadSmallLevel(int level)
|
||||
{
|
||||
// m_TEX0 is adjusted for mips (messy, should be changed).
|
||||
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[m_TEX0.PSM];
|
||||
const GSVector2i& bs = psm.bs;
|
||||
const int tw = 1 << m_TEX0.TW;
|
||||
const int th = 1 << m_TEX0.TH;
|
||||
const GSVector4i rect(0, 0, tw, th);
|
||||
const GSVector4i block_rect(rect.ralign<Align_Outside>(bs));
|
||||
const GSOffset& off = m_renderer->m_context->offset.tex;
|
||||
GSLocalMemory& mem = m_renderer->m_mem;
|
||||
|
||||
// Expand texture/apply palette.
|
||||
u32 pitch = static_cast<u32>(block_rect.z) * sizeof(u32);
|
||||
u32 row_size = static_cast<u32>(tw) * sizeof(u32);
|
||||
GSLocalMemory::readTexture rtx = psm.rtx;
|
||||
if (m_palette)
|
||||
{
|
||||
pitch >>= 2;
|
||||
row_size >>= 2;
|
||||
rtx = psm.rtxP;
|
||||
}
|
||||
|
||||
// Use temp buffer for expanding, since we may not need to update.
|
||||
u8* buff = m_temp;
|
||||
(m_renderer->m_mem.*rtx)(off, block_rect, buff, pitch, m_TEXA);
|
||||
|
||||
// Hash the expanded texture.
|
||||
HashType hash;
|
||||
{
|
||||
u8* ptr = buff;
|
||||
BlockHashState state;
|
||||
BlockHashReset(state);
|
||||
if (pitch == row_size)
|
||||
{
|
||||
BlockHashAccumulate(state, ptr, pitch * static_cast<u32>(th));
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int y = 0; y < th; y++, ptr += pitch)
|
||||
BlockHashAccumulate(state, ptr, row_size);
|
||||
}
|
||||
hash = FinishBlockHash(state);
|
||||
}
|
||||
|
||||
// Layer is complete again, regardless of whether the hash matches or not (and we reupload).
|
||||
const u8 layer_bit = static_cast<u8>(1) << level;
|
||||
m_complete_layers |= layer_bit;
|
||||
|
||||
// Check whether the hash matches. Black textures will be 0, so check the valid bit.
|
||||
if ((m_valid_hashes & layer_bit) && m_layer_hash[level] == hash)
|
||||
return;
|
||||
|
||||
m_valid_hashes |= layer_bit;
|
||||
m_layer_hash[level] = hash;
|
||||
|
||||
// Upload to GPU.
|
||||
m_texture->Update(rect, buff, pitch, level);
|
||||
// And upload the texture.
|
||||
PreloadTexture(m_TEX0, m_TEXA, m_renderer->m_mem, m_palette != nullptr, m_texture, level);
|
||||
}
|
||||
|
||||
bool GSTextureCache::Source::ClutMatch(const PaletteKey& palette_key)
|
||||
|
@ -2076,8 +1989,8 @@ bool GSTextureCache::Source::ClutMatch(const PaletteKey& palette_key)
|
|||
|
||||
// GSTextureCache::Target
|
||||
|
||||
GSTextureCache::Target::Target(GSRenderer* r, const GIFRegTEX0& TEX0, u8* temp, const bool depth_supported, const int type)
|
||||
: Surface(r, temp)
|
||||
GSTextureCache::Target::Target(GSRenderer* r, const GIFRegTEX0& TEX0, const bool depth_supported, const int type)
|
||||
: Surface(r)
|
||||
, m_type(type)
|
||||
, m_used(false)
|
||||
, m_depth_supported(depth_supported)
|
||||
|
@ -2219,7 +2132,16 @@ void GSTextureCache::SourceMap::Add(Source* s, const GIFRegTEX0& TEX0, const GSO
|
|||
void GSTextureCache::SourceMap::RemoveAll()
|
||||
{
|
||||
for (auto s : m_surfaces)
|
||||
{
|
||||
if (s->m_from_hash_cache)
|
||||
{
|
||||
pxAssert(s->m_from_hash_cache->refcount > 0);
|
||||
if ((--s->m_from_hash_cache->refcount) == 0)
|
||||
s->m_from_hash_cache->age = 0;
|
||||
}
|
||||
|
||||
delete s;
|
||||
}
|
||||
|
||||
m_surfaces.clear();
|
||||
|
||||
|
@ -2250,6 +2172,13 @@ void GSTextureCache::SourceMap::RemoveAt(Source* s)
|
|||
});
|
||||
}
|
||||
|
||||
if (s->m_from_hash_cache)
|
||||
{
|
||||
pxAssert(s->m_from_hash_cache->refcount > 0);
|
||||
if ((--s->m_from_hash_cache->refcount) == 0)
|
||||
s->m_from_hash_cache->age = 0;
|
||||
}
|
||||
|
||||
delete s;
|
||||
}
|
||||
|
||||
|
@ -2640,3 +2569,159 @@ bool GSTextureCache::SurfaceOffsetKeyEqual::operator()(const GSTextureCache::Sur
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
using BlockHashState = XXH3_state_t;
|
||||
|
||||
__fi static void BlockHashReset(BlockHashState& st)
|
||||
{
|
||||
XXH3_64bits_reset(&st);
|
||||
}
|
||||
|
||||
__fi static void BlockHashAccumulate(BlockHashState& st, const u8* bp)
|
||||
{
|
||||
XXH3_64bits_update(&st, bp, BLOCK_SIZE);
|
||||
}
|
||||
|
||||
__fi static void BlockHashAccumulate(BlockHashState& st, const u8* bp, u32 size)
|
||||
{
|
||||
XXH3_64bits_update(&st, bp, size);
|
||||
}
|
||||
|
||||
__fi static GSTextureCache::HashType FinishBlockHash(BlockHashState& st)
|
||||
{
|
||||
return XXH3_64bits_digest(&st);
|
||||
}
|
||||
|
||||
GSTextureCache::HashType GSTextureCache::HashTexture(GSRenderer* renderer, const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA)
|
||||
{
|
||||
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[TEX0.PSM];
|
||||
const GSVector2i& bs = psm.bs;
|
||||
const int tw = 1 << TEX0.TW;
|
||||
const int th = 1 << TEX0.TH;
|
||||
|
||||
// From GSLocalMemory foreachBlock(), used for reading textures.
|
||||
// We want to hash the exact same blocks here.
|
||||
const GSVector4i rect(0, 0, tw, th);
|
||||
const GSVector4i block_rect(rect.ralign<Align_Outside>(bs));
|
||||
GSLocalMemory& mem = renderer->m_mem;
|
||||
GSOffset off = mem.GetOffset(TEX0.TBP0, TEX0.TBW, TEX0.PSM);
|
||||
|
||||
// For textures which are smaller than the block size, we expand and then hash.
|
||||
// This is because otherwise we get the padding bytes, which can be random junk.
|
||||
GSTextureCache::HashType hash;
|
||||
BlockHashState hash_st;
|
||||
if (tw < bs.x || th < bs.y)
|
||||
{
|
||||
// Expand texture indices. Align to 32 bytes for AVX2.
|
||||
const u32 pitch = Common::AlignUpPow2(static_cast<u32>(block_rect.w), 32);
|
||||
const u32 row_size = static_cast<u32>(tw);
|
||||
const GSLocalMemory::readTexture rtx = psm.rtxP;
|
||||
|
||||
// Use temp buffer for expanding, since we may not need to update.
|
||||
(renderer->m_mem.*rtx)(off, block_rect, m_temp, pitch, TEXA);
|
||||
|
||||
// Hash the expanded texture.
|
||||
u8* ptr = m_temp;
|
||||
BlockHashReset(hash_st);
|
||||
if (pitch == row_size)
|
||||
{
|
||||
BlockHashAccumulate(hash_st, ptr, pitch * static_cast<u32>(th));
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int y = 0; y < th; y++, ptr += pitch)
|
||||
BlockHashAccumulate(hash_st, ptr, row_size);
|
||||
}
|
||||
hash = FinishBlockHash(hash_st);
|
||||
}
|
||||
else
|
||||
{
|
||||
BlockHashReset(hash_st);
|
||||
|
||||
GSOffset::BNHelper bn = off.bnMulti(block_rect.left, block_rect.top);
|
||||
const int right = block_rect.right >> off.blockShiftX();
|
||||
const int bottom = block_rect.bottom >> off.blockShiftY();
|
||||
const int xAdd = (1 << off.blockShiftX()) * (psm.bpp / 8);
|
||||
|
||||
for (; bn.blkY() < bottom; bn.nextBlockY())
|
||||
{
|
||||
for (int x = 0; bn.blkX() < right; bn.nextBlockX(), x += xAdd)
|
||||
{
|
||||
BlockHashAccumulate(hash_st, mem.BlockPtr(bn.value()));
|
||||
}
|
||||
}
|
||||
|
||||
hash = FinishBlockHash(hash_st);
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
void GSTextureCache::PreloadTexture(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, GSLocalMemory& mem, bool paltex, GSTexture* tex, u32 level)
|
||||
{
|
||||
// m_TEX0 is adjusted for mips (messy, should be changed).
|
||||
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[TEX0.PSM];
|
||||
const GSVector2i& bs = psm.bs;
|
||||
const int tw = 1 << TEX0.TW;
|
||||
const int th = 1 << TEX0.TH;
|
||||
|
||||
// Expand texture/apply palette.
|
||||
const GSVector4i rect(0, 0, tw, th);
|
||||
const GSVector4i block_rect(rect.ralign<Align_Outside>(bs));
|
||||
const GSOffset off(mem.GetOffset(TEX0.TBP0, TEX0.TBW, TEX0.PSM));
|
||||
const int read_width = std::max(tw, psm.bs.x);
|
||||
u32 pitch = static_cast<u32>(read_width) * sizeof(u32);
|
||||
u32 row_size = static_cast<u32>(tw) * sizeof(u32);
|
||||
GSLocalMemory::readTexture rtx = psm.rtx;
|
||||
if (paltex)
|
||||
{
|
||||
pitch >>= 2;
|
||||
row_size >>= 2;
|
||||
rtx = psm.rtxP;
|
||||
}
|
||||
|
||||
// If we can stream it directly to GPU memory, do so, otherwise go through a temp buffer.
|
||||
GSTexture::GSMap map;
|
||||
if (rect.eq(block_rect) && tex->Map(map, &rect, level))
|
||||
{
|
||||
(mem.*rtx)(off, block_rect, map.bits, map.pitch, TEXA);
|
||||
tex->Unmap();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Align pitch to 32 bytes for AVX2 if we're going through the temp buffer path.
|
||||
pitch = Common::AlignUpPow2(pitch, 32);
|
||||
|
||||
u8* buff = m_temp;
|
||||
(mem.*rtx)(off, block_rect, buff, pitch, TEXA);
|
||||
tex->Update(rect, buff, pitch, level);
|
||||
}
|
||||
}
|
||||
|
||||
GSTextureCache::HashCacheKey::HashCacheKey()
|
||||
: TEX0Hash(0)
|
||||
, CLUTHash(0)
|
||||
{
|
||||
TEX0.U64 = 0;
|
||||
TEXA.U64 = 0;
|
||||
}
|
||||
|
||||
GSTextureCache::HashCacheKey GSTextureCache::HashCacheKey::Create(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, GSRenderer* renderer, const u32* clut)
|
||||
{
|
||||
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[TEX0.PSM];
|
||||
|
||||
HashCacheKey ret;
|
||||
ret.TEX0.U64 = TEX0.U64 & 0x00000007FFF00000ULL;
|
||||
ret.TEXA.U64 = (psm.pal == 0 && psm.fmt > 0) ? (TEXA.U64 & 0x000000FF000080FFULL) : 0;
|
||||
ret.CLUTHash = clut ? GSTextureCache::PaletteKeyHash{}({clut, psm.pal}) : 0;
|
||||
ret.TEX0Hash = HashTexture(renderer, TEX0, TEXA);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
u64 GSTextureCache::HashCacheKeyHash::operator()(const HashCacheKey& key) const
|
||||
{
|
||||
std::size_t h = 0;
|
||||
HashCombine(h, key.TEX0Hash, key.CLUTHash, key.TEX0.U64, key.TEXA.U64);
|
||||
return h;
|
||||
}
|
||||
|
|
|
@ -40,6 +40,35 @@ public:
|
|||
return valid && overlap;
|
||||
}
|
||||
|
||||
using HashType = u64;
|
||||
|
||||
struct HashCacheKey
|
||||
{
|
||||
HashType TEX0Hash, CLUTHash;
|
||||
GIFRegTEX0 TEX0;
|
||||
GIFRegTEXA TEXA;
|
||||
|
||||
HashCacheKey();
|
||||
|
||||
static HashCacheKey Create(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, GSRenderer* renderer, const u32* clut);
|
||||
|
||||
__fi bool operator==(const HashCacheKey& e) const { return std::memcmp(this, &e, sizeof(*this)) == 0; }
|
||||
__fi bool operator!=(const HashCacheKey& e) const { return std::memcmp(this, &e, sizeof(*this)) != 0; }
|
||||
__fi bool operator<(const HashCacheKey& e) const { return std::memcmp(this, &e, sizeof(*this)) < 0; }
|
||||
};
|
||||
|
||||
struct HashCacheKeyHash
|
||||
{
|
||||
u64 operator()(const HashCacheKey& key) const;
|
||||
};
|
||||
|
||||
struct HashCacheEntry
|
||||
{
|
||||
GSTexture* texture;
|
||||
u32 refcount;
|
||||
u32 age;
|
||||
};
|
||||
|
||||
class Surface : public GSAlignedClass<32>
|
||||
{
|
||||
protected:
|
||||
|
@ -47,16 +76,16 @@ public:
|
|||
|
||||
public:
|
||||
GSTexture* m_texture;
|
||||
HashCacheEntry* m_from_hash_cache;
|
||||
GIFRegTEX0 m_TEX0;
|
||||
GIFRegTEXA m_TEXA;
|
||||
int m_age;
|
||||
u8* m_temp;
|
||||
bool m_32_bits_fmt; // Allow to detect the casting of 32 bits as 16 bits texture
|
||||
bool m_shared_texture;
|
||||
u32 m_end_block; // Hint of the surface area.
|
||||
|
||||
public:
|
||||
Surface(GSRenderer* r, u8* temp);
|
||||
Surface(GSRenderer* r);
|
||||
virtual ~Surface();
|
||||
|
||||
void UpdateAge();
|
||||
|
@ -111,10 +140,6 @@ public:
|
|||
|
||||
class Source : public Surface
|
||||
{
|
||||
public:
|
||||
using HashType = u64;
|
||||
|
||||
private:
|
||||
struct
|
||||
{
|
||||
GSVector4i* rect;
|
||||
|
@ -122,7 +147,6 @@ public:
|
|||
} m_write;
|
||||
|
||||
void PreloadLevel(int level);
|
||||
void PreloadSmallLevel(int level);
|
||||
|
||||
void Write(const GSVector4i& r, int layer);
|
||||
void Flush(u32 count, int layer);
|
||||
|
@ -149,10 +173,10 @@ public:
|
|||
GSOffset::PageLooper m_pages;
|
||||
|
||||
public:
|
||||
Source(GSRenderer* r, const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, u8* temp, bool dummy_container = false);
|
||||
Source(GSRenderer* r, const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, bool dummy_container = false);
|
||||
virtual ~Source();
|
||||
|
||||
__fi bool CanPreload() const { return (GSConfig.PreloadTexture && CanPreloadTextureSize(m_TEX0.TW, m_TEX0.TH)); }
|
||||
__fi bool CanPreload() const { return CanPreloadTextureSize(m_TEX0.TW, m_TEX0.TH); }
|
||||
|
||||
void Update(const GSVector4i& rect, int layer = 0);
|
||||
void UpdateLayer(const GIFRegTEX0& TEX0, const GSVector4i& rect, int layer = 0);
|
||||
|
@ -171,7 +195,7 @@ public:
|
|||
bool m_dirty_alpha;
|
||||
|
||||
public:
|
||||
Target(GSRenderer* r, const GIFRegTEX0& TEX0, u8* temp, const bool depth_supported, const int type);
|
||||
Target(GSRenderer* r, const GIFRegTEX0& TEX0, const bool depth_supported, const int type);
|
||||
|
||||
void UpdateValidity(const GSVector4i& rect);
|
||||
|
||||
|
@ -250,9 +274,10 @@ protected:
|
|||
GSRenderer* m_renderer;
|
||||
PaletteMap m_palette_map;
|
||||
SourceMap m_src;
|
||||
std::unordered_map<HashCacheKey, HashCacheEntry, HashCacheKeyHash> m_hash_cache;
|
||||
FastList<Target*> m_dst[2];
|
||||
bool m_preload_frame;
|
||||
u8* m_temp;
|
||||
static u8* m_temp;
|
||||
bool m_can_convert_depth;
|
||||
bool m_cpu_fb_conversion;
|
||||
static bool m_disable_partial_invalidation;
|
||||
|
@ -264,6 +289,9 @@ protected:
|
|||
Source* CreateSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, Target* t = NULL, bool half_right = false, int x_offset = 0, int y_offset = 0, bool mipmap = false);
|
||||
Target* CreateTarget(const GIFRegTEX0& TEX0, int w, int h, int type, const bool clear);
|
||||
|
||||
static void PreloadTexture(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, GSLocalMemory& mem, bool paltex, GSTexture* tex, u32 level);
|
||||
static HashType HashTexture(GSRenderer* renderer, const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA);
|
||||
|
||||
// TODO: virtual void Write(Source* s, const GSVector4i& r) = 0;
|
||||
// TODO: virtual void Write(Target* t, const GSVector4i& r) = 0;
|
||||
|
||||
|
|
|
@ -285,7 +285,6 @@ RendererTab::RendererTab(wxWindow* parent)
|
|||
|
||||
auto* paltex_prereq = m_ui.addCheckBox(hw_checks_box, "GPU Palette Conversion", "paltex", IDC_PALTEX, hw_prereq);
|
||||
auto aniso_prereq = [this, paltex_prereq]{ return m_is_hardware && paltex_prereq->GetValue() == false; };
|
||||
m_ui.addCheckBox(hw_checks_box, "Preload Textures", "preload_texture", IDC_PRELOAD_TEXTURES, hw_prereq);
|
||||
|
||||
auto* hw_choice_grid = new wxFlexGridSizer(2, space, space);
|
||||
|
||||
|
@ -296,6 +295,7 @@ RendererTab::RendererTab(wxWindow* parent)
|
|||
m_ui.addComboBoxAndLabel(hw_choice_grid, "Mipmapping:", "mipmap_hw", &theApp.m_gs_hw_mipmapping, IDC_MIPMAP_HW, hw_prereq);
|
||||
m_ui.addComboBoxAndLabel(hw_choice_grid, "CRC Hack Level:", "crc_hack_level", &theApp.m_gs_crc_level, IDC_CRC_LEVEL, hw_prereq);
|
||||
m_ui.addComboBoxAndLabel(hw_choice_grid, "Blending Accuracy:", "accurate_blending_unit", &theApp.m_gs_acc_blend_level, IDC_ACCURATE_BLEND_UNIT, hw_prereq);
|
||||
m_ui.addComboBoxAndLabel(hw_choice_grid, "Texture Preloading:", "texture_preloading", &theApp.m_gs_texture_preloading, IDC_PRELOAD_TEXTURES, hw_prereq);
|
||||
|
||||
hardware_box->Add(hw_checks_box, wxSizerFlags().Centre());
|
||||
hardware_box->AddSpacer(space);
|
||||
|
|
|
@ -359,6 +359,7 @@ bool Pcsx2Config::GSOptions::OptionsAreEqual(const GSOptions& right) const
|
|||
OpEqu(AccurateBlendingUnit) &&
|
||||
OpEqu(CRCHack) &&
|
||||
OpEqu(TextureFiltering) &&
|
||||
OpEqu(TexturePreloading) &&
|
||||
OpEqu(Dithering) &&
|
||||
OpEqu(MaxAnisotropy) &&
|
||||
OpEqu(SWExtraThreads) &&
|
||||
|
@ -510,7 +511,6 @@ void Pcsx2Config::GSOptions::ReloadIniSettings()
|
|||
GSSettingBoolEx(SaveFrame, "savef");
|
||||
GSSettingBoolEx(SaveTexture, "savet");
|
||||
GSSettingBoolEx(SaveDepth, "savez");
|
||||
GSSettingBoolEx(PreloadTexture, "preload_texture");
|
||||
|
||||
GSSettingIntEnumEx(InterlaceMode, "interlace");
|
||||
|
||||
|
@ -523,6 +523,7 @@ void Pcsx2Config::GSOptions::ReloadIniSettings()
|
|||
GSSettingIntEnumEx(AccurateBlendingUnit, "accurate_blending_unit");
|
||||
GSSettingIntEnumEx(CRCHack, "crc_hack_level");
|
||||
GSSettingIntEnumEx(TextureFiltering, "filter");
|
||||
GSSettingIntEnumEx(TexturePreloading, "texture_preloading");
|
||||
GSSettingIntEx(Dithering, "dithering_ps2");
|
||||
GSSettingIntEx(MaxAnisotropy, "MaxAnisotropy");
|
||||
GSSettingIntEx(SWExtraThreads, "extrathreads");
|
||||
|
|
Loading…
Reference in New Issue