mirror of https://github.com/PCSX2/pcsx2.git
GS: Add texture preloading option
This commit is contained in:
parent
6187f36c33
commit
bf389b94d6
|
@ -439,7 +439,8 @@ struct Pcsx2Config
|
||||||
SaveRT : 1,
|
SaveRT : 1,
|
||||||
SaveFrame : 1,
|
SaveFrame : 1,
|
||||||
SaveTexture : 1,
|
SaveTexture : 1,
|
||||||
SaveDepth : 1;
|
SaveDepth : 1,
|
||||||
|
PreloadTexture : 1;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1303,6 +1303,7 @@ void GSApp::Init()
|
||||||
m_default_configuration["paltex"] = "0";
|
m_default_configuration["paltex"] = "0";
|
||||||
m_default_configuration["png_compression_level"] = std::to_string(Z_BEST_SPEED);
|
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_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["Renderer"] = std::to_string(static_cast<int>(GSRendererType::Auto));
|
||||||
m_default_configuration["resx"] = "1024";
|
m_default_configuration["resx"] = "1024";
|
||||||
m_default_configuration["resy"] = "1024";
|
m_default_configuration["resy"] = "1024";
|
||||||
|
|
|
@ -110,6 +110,9 @@ static const GSVector2i default_rt_size(2048, 2048);
|
||||||
static const GSVector2i default_rt_size(1280, 1024);
|
static const GSVector2i default_rt_size(1280, 1024);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Maximum texture size to skip preload/hash path.
|
||||||
|
static constexpr int MAXIMUM_PRELOAD_TEXTURE_SIZE = 512;
|
||||||
|
|
||||||
// Helper path to dump texture
|
// Helper path to dump texture
|
||||||
extern const std::string root_sw;
|
extern const std::string root_sw;
|
||||||
extern const std::string root_hw;
|
extern const std::string root_hw;
|
||||||
|
|
|
@ -2556,6 +2556,15 @@ void GSState::GetTextureMinMax(GSVector4i& r, const GIFRegTEX0& TEX0, const GIFR
|
||||||
|
|
||||||
GSVector4i tr(0, 0, w, h);
|
GSVector4i tr(0, 0, w, h);
|
||||||
|
|
||||||
|
// don't bother checking when preload is on, since we're going to test the whole thing anyway
|
||||||
|
if (GSConfig.PreloadTexture && GSConfig.UseHardwareRenderer() &&
|
||||||
|
(GSConfig.GPUPaletteConversion ||
|
||||||
|
(w <= MAXIMUM_PRELOAD_TEXTURE_SIZE && h <= MAXIMUM_PRELOAD_TEXTURE_SIZE)))
|
||||||
|
{
|
||||||
|
r = tr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const int wms = CLAMP.WMS;
|
const int wms = CLAMP.WMS;
|
||||||
const int wmt = CLAMP.WMT;
|
const int wmt = CLAMP.WMT;
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,11 @@
|
||||||
#include "GS/GSIntrin.h"
|
#include "GS/GSIntrin.h"
|
||||||
#include "GS/GSUtil.h"
|
#include "GS/GSUtil.h"
|
||||||
|
|
||||||
|
#define XXH_STATIC_LINKING_ONLY 1
|
||||||
|
#define XXH_INLINE_ALL 1
|
||||||
|
#include "xxhash.h"
|
||||||
|
|
||||||
|
bool GSTextureCache::m_paltex = false;
|
||||||
bool GSTextureCache::m_disable_partial_invalidation = false;
|
bool GSTextureCache::m_disable_partial_invalidation = false;
|
||||||
bool GSTextureCache::m_wrap_gs_mem = false;
|
bool GSTextureCache::m_wrap_gs_mem = false;
|
||||||
|
|
||||||
|
@ -1210,7 +1215,7 @@ void GSTextureCache::InvalidateVideoMemSubTarget(GSTextureCache::Target* rt)
|
||||||
|
|
||||||
void GSTextureCache::IncAge()
|
void GSTextureCache::IncAge()
|
||||||
{
|
{
|
||||||
int maxage = m_src.m_used ? 3 : 30;
|
int maxage = GSConfig.PreloadTexture ? (m_src.m_used ? 30 : 60) : (m_src.m_used ? 3 : 6);
|
||||||
|
|
||||||
// You can't use m_map[page] because Source* are duplicated on several pages.
|
// You can't use m_map[page] because Source* are duplicated on several pages.
|
||||||
for (auto i = m_src.m_surfaces.begin(); i != m_src.m_surfaces.end();)
|
for (auto i = m_src.m_surfaces.begin(); i != m_src.m_surfaces.end();)
|
||||||
|
@ -1802,7 +1807,7 @@ GSTextureCache::Surface::~Surface()
|
||||||
{
|
{
|
||||||
// Shared textures are pointers copy. Therefore no allocation
|
// Shared textures are pointers copy. Therefore no allocation
|
||||||
// to recycle.
|
// to recycle.
|
||||||
if (!m_shared_texture)
|
if (!m_shared_texture && m_texture)
|
||||||
g_gs_device->Recycle(m_texture);
|
g_gs_device->Recycle(m_texture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1854,6 +1859,7 @@ GSTextureCache::Source::Source(GSRenderer* r, const GIFRegTEX0& TEX0, const GIFR
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
memset(m_layer_TEX0, 0, sizeof(m_layer_TEX0));
|
memset(m_layer_TEX0, 0, sizeof(m_layer_TEX0));
|
||||||
|
memset(m_layer_hash, 0, sizeof(m_layer_hash));
|
||||||
|
|
||||||
memset(m_valid, 0, sizeof(m_valid));
|
memset(m_valid, 0, sizeof(m_valid));
|
||||||
|
|
||||||
|
@ -1887,8 +1893,14 @@ void GSTextureCache::Source::Update(const GSVector4i& rect, int layer)
|
||||||
|
|
||||||
const GSVector2i& bs = GSLocalMemory::m_psm[m_TEX0.PSM].bs;
|
const GSVector2i& bs = GSLocalMemory::m_psm[m_TEX0.PSM].bs;
|
||||||
|
|
||||||
int tw = std::max<int>(1 << m_TEX0.TW, bs.x);
|
const int tw = 1 << m_TEX0.TW;
|
||||||
int th = std::max<int>(1 << m_TEX0.TH, bs.y);
|
const int th = 1 << m_TEX0.TH;
|
||||||
|
const bool preload = (GSConfig.PreloadTexture && (GSConfig.GPUPaletteConversion || (tw <= MAXIMUM_PRELOAD_TEXTURE_SIZE && th <= MAXIMUM_PRELOAD_TEXTURE_SIZE)));
|
||||||
|
if (preload)
|
||||||
|
{
|
||||||
|
PreloadUpdate(tw, th, layer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
GSVector4i r = rect.ralign<Align_Outside>(bs);
|
GSVector4i r = rect.ralign<Align_Outside>(bs);
|
||||||
|
|
||||||
|
@ -1961,7 +1973,6 @@ void GSTextureCache::Source::Update(const GSVector4i& rect, int layer)
|
||||||
if (blocks > 0)
|
if (blocks > 0)
|
||||||
{
|
{
|
||||||
g_perfmon.Put(GSPerfMon::Unswizzle, bs.x * bs.y * blocks << (m_palette ? 2 : 0));
|
g_perfmon.Put(GSPerfMon::Unswizzle, bs.x * bs.y * blocks << (m_palette ? 2 : 0));
|
||||||
|
|
||||||
Flush(m_write.count, layer);
|
Flush(m_write.count, layer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2088,6 +2099,105 @@ void GSTextureCache::Source::Flush(u32 count, int layer)
|
||||||
m_write.count -= count;
|
m_write.count -= count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GSTextureCache::Source::HashType GSTextureCache::Source::HashTexture(u8* buff, u32 row_size, u32 pitch, u32 height)
|
||||||
|
{
|
||||||
|
if (row_size == pitch)
|
||||||
|
{
|
||||||
|
// fast path since it's all packed
|
||||||
|
return XXH3_64bits(buff, row_size * height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// slow path where we have to process rows-at-a-time
|
||||||
|
XXH3_state_t st;
|
||||||
|
XXH3_64bits_reset(&st);
|
||||||
|
for (u32 row = 0; row < height; row++)
|
||||||
|
{
|
||||||
|
XXH3_64bits_update(&st, buff, row_size);
|
||||||
|
buff += pitch;
|
||||||
|
}
|
||||||
|
return XXH3_64bits_digest(&st);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GSTextureCache::Source::PreloadUpdate(int tw, int th, int layer)
|
||||||
|
{
|
||||||
|
const GSVector2i& bs = GSLocalMemory::m_psm[m_TEX0.PSM].bs;
|
||||||
|
const GSOffset& off = m_renderer->m_context->offset.tex;
|
||||||
|
const GSVector4i rect(0, 0, tw, th);
|
||||||
|
const GSVector4i block_rect(rect.ralign<Align_Outside>(bs));
|
||||||
|
GSOffset::BNHelper bn = off.bnMulti(0, 0);
|
||||||
|
|
||||||
|
// flag everything as valid
|
||||||
|
if (m_repeating)
|
||||||
|
{
|
||||||
|
for (int y = block_rect.top; y < block_rect.bottom; y += bs.y, bn.nextBlockY())
|
||||||
|
{
|
||||||
|
for (int x = block_rect.left; x < block_rect.right; bn.nextBlockX(), x += bs.x)
|
||||||
|
{
|
||||||
|
const u32 i = static_cast<u32>((bn.blkY() << 7) + bn.blkX());
|
||||||
|
u32 block = bn.valueNoWrap();
|
||||||
|
|
||||||
|
if (block < MAX_BLOCKS || m_wrap_gs_mem)
|
||||||
|
{
|
||||||
|
u32 addr = i % MAX_BLOCKS;
|
||||||
|
|
||||||
|
u32 row = addr >> 5u;
|
||||||
|
u32 col = 1 << (addr & 31u);
|
||||||
|
m_valid[row] |= col;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int y = block_rect.top; y < block_rect.bottom; y += bs.y, bn.nextBlockY())
|
||||||
|
{
|
||||||
|
for (int x = block_rect.left; x < block_rect.right; x += bs.x, bn.nextBlockX())
|
||||||
|
{
|
||||||
|
u32 block = bn.valueNoWrap();
|
||||||
|
|
||||||
|
if (block < MAX_BLOCKS || m_wrap_gs_mem)
|
||||||
|
{
|
||||||
|
block %= MAX_BLOCKS;
|
||||||
|
|
||||||
|
u32 row = block >> 5u;
|
||||||
|
u32 col = 1 << (block & 31u);
|
||||||
|
m_valid[row] |= col;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layer == 0)
|
||||||
|
m_complete = true;
|
||||||
|
|
||||||
|
// decode texture to temporary memory
|
||||||
|
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[m_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 (m_palette)
|
||||||
|
{
|
||||||
|
pitch >>= 2;
|
||||||
|
row_size >>= 2;
|
||||||
|
rtx = psm.rtxP;
|
||||||
|
}
|
||||||
|
|
||||||
|
u8* buff = m_temp;
|
||||||
|
(m_renderer->m_mem.*rtx)(off, block_rect, buff, pitch, m_TEXA);
|
||||||
|
|
||||||
|
// hash the texture
|
||||||
|
const HashType hash = HashTexture(buff, row_size, pitch, static_cast<u32>(th));
|
||||||
|
const u8 layer_bit = static_cast<u8>(1) << layer;
|
||||||
|
if ((m_valid_hashes & layer_bit) && m_layer_hash[layer] == hash)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// reupload
|
||||||
|
m_valid_hashes |= layer_bit;
|
||||||
|
m_layer_hash[layer] = hash;
|
||||||
|
m_texture->Update(rect, buff, pitch, layer);
|
||||||
|
}
|
||||||
|
|
||||||
bool GSTextureCache::Source::ClutMatch(PaletteKey palette_key)
|
bool GSTextureCache::Source::ClutMatch(PaletteKey palette_key)
|
||||||
{
|
{
|
||||||
return PaletteKeyEqual()(palette_key, m_palette_obj->GetPaletteKey());
|
return PaletteKeyEqual()(palette_key, m_palette_obj->GetPaletteKey());
|
||||||
|
|
|
@ -106,6 +106,11 @@ public:
|
||||||
u32 count;
|
u32 count;
|
||||||
} m_write;
|
} m_write;
|
||||||
|
|
||||||
|
using HashType = u64;
|
||||||
|
|
||||||
|
HashType HashTexture(u8* buff, u32 row_size, u32 pitch, u32 height);
|
||||||
|
void PreloadUpdate(int tw, int th, int layer);
|
||||||
|
|
||||||
void Write(const GSVector4i& r, int layer);
|
void Write(const GSVector4i& r, int layer);
|
||||||
void Flush(u32 count, int layer);
|
void Flush(u32 count, int layer);
|
||||||
|
|
||||||
|
@ -114,6 +119,7 @@ public:
|
||||||
GSTexture* m_palette;
|
GSTexture* m_palette;
|
||||||
u32 m_valid[MAX_PAGES]; // each u32 bits map to the 32 blocks of that page
|
u32 m_valid[MAX_PAGES]; // each u32 bits map to the 32 blocks of that page
|
||||||
GSVector4i m_valid_rect;
|
GSVector4i m_valid_rect;
|
||||||
|
u8 m_valid_hashes = 0;
|
||||||
bool m_target;
|
bool m_target;
|
||||||
bool m_complete;
|
bool m_complete;
|
||||||
bool m_repeating;
|
bool m_repeating;
|
||||||
|
@ -124,6 +130,7 @@ public:
|
||||||
GSTexture* m_from_target;
|
GSTexture* m_from_target;
|
||||||
GIFRegTEX0 m_from_target_TEX0; // TEX0 of the target texture, if any, else equal to texture TEX0
|
GIFRegTEX0 m_from_target_TEX0; // TEX0 of the target texture, if any, else equal to texture TEX0
|
||||||
GIFRegTEX0 m_layer_TEX0[7]; // Detect already loaded value
|
GIFRegTEX0 m_layer_TEX0[7]; // Detect already loaded value
|
||||||
|
HashType m_layer_hash[7];
|
||||||
// Keep a GSTextureCache::SourceMap::m_map iterator to allow fast erase
|
// Keep a GSTextureCache::SourceMap::m_map iterator to allow fast erase
|
||||||
std::array<u16, MAX_PAGES> m_erase_it;
|
std::array<u16, MAX_PAGES> m_erase_it;
|
||||||
GSOffset::PageLooper m_pages;
|
GSOffset::PageLooper m_pages;
|
||||||
|
@ -214,7 +221,7 @@ protected:
|
||||||
PaletteMap m_palette_map;
|
PaletteMap m_palette_map;
|
||||||
SourceMap m_src;
|
SourceMap m_src;
|
||||||
FastList<Target*> m_dst[2];
|
FastList<Target*> m_dst[2];
|
||||||
bool m_paltex;
|
static bool m_paltex;
|
||||||
bool m_preload_frame;
|
bool m_preload_frame;
|
||||||
u8* m_temp;
|
u8* m_temp;
|
||||||
bool m_can_convert_depth;
|
bool m_can_convert_depth;
|
||||||
|
|
|
@ -285,6 +285,7 @@ RendererTab::RendererTab(wxWindow* parent)
|
||||||
|
|
||||||
auto* paltex_prereq = m_ui.addCheckBox(hw_checks_box, "GPU Palette Conversion", "paltex", IDC_PALTEX, hw_prereq);
|
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; };
|
auto aniso_prereq = [this, paltex_prereq]{ return m_is_hardware && paltex_prereq->GetValue() == false; };
|
||||||
|
m_ui.addCheckBox(hw_checks_box, "Preload Textures", "preload_texture", -1, hw_prereq);
|
||||||
|
|
||||||
auto* hw_choice_grid = new wxFlexGridSizer(2, space, space);
|
auto* hw_choice_grid = new wxFlexGridSizer(2, space, space);
|
||||||
|
|
||||||
|
|
|
@ -506,6 +506,7 @@ void Pcsx2Config::GSOptions::ReloadIniSettings()
|
||||||
GSSettingBoolEx(SaveFrame, "savef");
|
GSSettingBoolEx(SaveFrame, "savef");
|
||||||
GSSettingBoolEx(SaveTexture, "savet");
|
GSSettingBoolEx(SaveTexture, "savet");
|
||||||
GSSettingBoolEx(SaveDepth, "savez");
|
GSSettingBoolEx(SaveDepth, "savez");
|
||||||
|
GSSettingBoolEx(PreloadTexture, "preload_texture");
|
||||||
|
|
||||||
GSSettingIntEnumEx(InterlaceMode, "interlace");
|
GSSettingIntEnumEx(InterlaceMode, "interlace");
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue