Decode EFB copies used as paletted textures.
A number of games make an EFB copy in I4/I8 format, then use it as a texture in C4/C8 format. Detect when this happens, and decode the copy on the GPU using the specified palette. This has a few advantages: it allows using EFB2Tex for a few more games, it, it preserves the resolution of scaled EFB copies, and it's probably a bit faster. D3D only at the moment, but porting to OpenGL should be straightforward..
This commit is contained in:
parent
fbbbad98e9
commit
c0a4760f0e
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#include "Core/HW/Memmap.h"
|
#include "Core/HW/Memmap.h"
|
||||||
#include "VideoBackends/D3D/D3DBase.h"
|
#include "VideoBackends/D3D/D3DBase.h"
|
||||||
|
#include "VideoBackends/D3D/D3DShader.h"
|
||||||
#include "VideoBackends/D3D/D3DState.h"
|
#include "VideoBackends/D3D/D3DState.h"
|
||||||
#include "VideoBackends/D3D/D3DUtil.h"
|
#include "VideoBackends/D3D/D3DUtil.h"
|
||||||
#include "VideoBackends/D3D/FramebufferManager.h"
|
#include "VideoBackends/D3D/FramebufferManager.h"
|
||||||
|
@ -14,6 +15,7 @@
|
||||||
#include "VideoBackends/D3D/TextureEncoder.h"
|
#include "VideoBackends/D3D/TextureEncoder.h"
|
||||||
#include "VideoBackends/D3D/VertexShaderCache.h"
|
#include "VideoBackends/D3D/VertexShaderCache.h"
|
||||||
#include "VideoCommon/ImageWrite.h"
|
#include "VideoCommon/ImageWrite.h"
|
||||||
|
#include "VideoCommon/LookUpTables.h"
|
||||||
#include "VideoCommon/RenderBase.h"
|
#include "VideoCommon/RenderBase.h"
|
||||||
#include "VideoCommon/VideoConfig.h"
|
#include "VideoCommon/VideoConfig.h"
|
||||||
|
|
||||||
|
@ -179,17 +181,167 @@ void TextureCache::TCacheEntry::FromRenderTarget(u32 dstAddr, unsigned int dstFo
|
||||||
|
|
||||||
size_in_bytes = (u32)encoded_size;
|
size_in_bytes = (u32)encoded_size;
|
||||||
|
|
||||||
TextureCache::MakeRangeDynamic(addr, (u32)encoded_size);
|
TextureCache::MakeRangeDynamic(dstAddr, (u32)encoded_size);
|
||||||
|
|
||||||
this->hash = hash;
|
this->hash = hash;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char palette_shader[] =
|
||||||
|
R"HLSL(
|
||||||
|
sampler samp0 : register(s0);
|
||||||
|
Texture2DArray Tex0 : register(t0);
|
||||||
|
Buffer<uint> Tex1 : register(t1);
|
||||||
|
uniform float Multiply;
|
||||||
|
|
||||||
|
uint Convert3To8(uint v)
|
||||||
|
{
|
||||||
|
// Swizzle bits: 00000123 -> 12312312
|
||||||
|
return (v << 5) | (v << 2) | (v >> 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint Convert4To8(uint v)
|
||||||
|
{
|
||||||
|
// Swizzle bits: 00001234 -> 12341234
|
||||||
|
return (v << 4) | v;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint Convert5To8(uint v)
|
||||||
|
{
|
||||||
|
// Swizzle bits: 00012345 -> 12345123
|
||||||
|
return (v << 3) | (v >> 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint Convert6To8(uint v)
|
||||||
|
{
|
||||||
|
// Swizzle bits: 00123456 -> 12345612
|
||||||
|
return (v << 2) | (v >> 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 DecodePixel_RGB5A3(uint val)
|
||||||
|
{
|
||||||
|
int r,g,b,a;
|
||||||
|
if ((val&0x8000))
|
||||||
|
{
|
||||||
|
r=Convert5To8((val>>10) & 0x1f);
|
||||||
|
g=Convert5To8((val>>5 ) & 0x1f);
|
||||||
|
b=Convert5To8((val ) & 0x1f);
|
||||||
|
a=0xFF;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
a=Convert3To8((val>>12) & 0x7);
|
||||||
|
r=Convert4To8((val>>8 ) & 0xf);
|
||||||
|
g=Convert4To8((val>>4 ) & 0xf);
|
||||||
|
b=Convert4To8((val ) & 0xf);
|
||||||
|
}
|
||||||
|
return float4(r, g, b, a) / 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 DecodePixel_RGB565(uint val)
|
||||||
|
{
|
||||||
|
int r, g, b, a;
|
||||||
|
r = Convert5To8((val >> 11) & 0x1f);
|
||||||
|
g = Convert6To8((val >> 5) & 0x3f);
|
||||||
|
b = Convert5To8((val) & 0x1f);
|
||||||
|
a = 0xFF;
|
||||||
|
return float4(r, g, b, a) / 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 DecodePixel_IA8(uint val)
|
||||||
|
{
|
||||||
|
int i = val & 0xFF;
|
||||||
|
int a = val >> 8;
|
||||||
|
return float4(i, i, i, a) / 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main(
|
||||||
|
out float4 ocol0 : SV_Target,
|
||||||
|
in float4 pos : SV_Position,
|
||||||
|
in float3 uv0 : TEXCOORD0)
|
||||||
|
{
|
||||||
|
uint src = round(Tex0.Sample(samp0,uv0) * Multiply).r;
|
||||||
|
src = Tex1.Load(src);
|
||||||
|
src = ((src << 8) & 0xFF00) | (src >> 8);
|
||||||
|
ocol0 = DECODE(src);
|
||||||
|
}
|
||||||
|
)HLSL";
|
||||||
|
|
||||||
|
void TextureCache::ConvertTexture(TCacheEntryBase* entry, TCacheEntryBase* unconverted, void* palette, TlutFormat format)
|
||||||
|
{
|
||||||
|
g_renderer->ResetAPIState();
|
||||||
|
|
||||||
|
// stretch picture with increased internal resolution
|
||||||
|
const D3D11_VIEWPORT vp = CD3D11_VIEWPORT(0.f, 0.f, (float)unconverted->config.width, (float)unconverted->config.height);
|
||||||
|
D3D::context->RSSetViewports(1, &vp);
|
||||||
|
|
||||||
|
D3D11_BOX box{ 0, 0, 0, 512, 1, 1 };
|
||||||
|
D3D::context->UpdateSubresource(palette_buf, 0, &box, palette, 0, 0);
|
||||||
|
|
||||||
|
D3D::stateman->SetTexture(1, palette_buf_srv);
|
||||||
|
|
||||||
|
// TODO: Add support for C14X2 format. (Different multiplier, more palette entries.)
|
||||||
|
float params[4] = { unconverted->format == 0 ? 15.f : 255.f };
|
||||||
|
D3D::context->UpdateSubresource(palette_uniform, 0, nullptr, ¶ms, 0, 0);
|
||||||
|
D3D::stateman->SetPixelConstants(palette_uniform);
|
||||||
|
|
||||||
|
const D3D11_RECT sourcerect = CD3D11_RECT(0, 0, unconverted->config.width, unconverted->config.height);
|
||||||
|
|
||||||
|
D3D::SetPointCopySampler();
|
||||||
|
|
||||||
|
// Make sure we don't draw with the texture set as both a source and target.
|
||||||
|
// (This can happen because we don't unbind textures when we free them.)
|
||||||
|
D3D::stateman->UnsetTexture(static_cast<TCacheEntry*>(entry)->texture->GetSRV());
|
||||||
|
|
||||||
|
D3D::context->OMSetRenderTargets(1, &static_cast<TCacheEntry*>(entry)->texture->GetRTV(), nullptr);
|
||||||
|
|
||||||
|
// Create texture copy
|
||||||
|
D3D::drawShadedTexQuad(
|
||||||
|
static_cast<TCacheEntry*>(unconverted)->texture->GetSRV(),
|
||||||
|
&sourcerect, unconverted->config.width, unconverted->config.height,
|
||||||
|
palette_pixel_shader[format],
|
||||||
|
VertexShaderCache::GetSimpleVertexShader(), VertexShaderCache::GetSimpleInputLayout(),
|
||||||
|
GeometryShaderCache::GetCopyGeometryShader());
|
||||||
|
|
||||||
|
D3D::context->OMSetRenderTargets(1, &FramebufferManager::GetEFBColorTexture()->GetRTV(), FramebufferManager::GetEFBDepthTexture()->GetDSV());
|
||||||
|
|
||||||
|
g_renderer->RestoreAPIState();
|
||||||
|
}
|
||||||
|
|
||||||
|
ID3D11PixelShader *GetConvertShader(const char* Type)
|
||||||
|
{
|
||||||
|
std::string shader = "#define DECODE DecodePixel_";
|
||||||
|
shader.append(Type);
|
||||||
|
shader.append("\n");
|
||||||
|
shader.append(palette_shader);
|
||||||
|
return D3D::CompileAndCreatePixelShader(shader);
|
||||||
|
}
|
||||||
|
|
||||||
TextureCache::TextureCache()
|
TextureCache::TextureCache()
|
||||||
{
|
{
|
||||||
// FIXME: Is it safe here?
|
// FIXME: Is it safe here?
|
||||||
g_encoder = new PSTextureEncoder;
|
g_encoder = new PSTextureEncoder;
|
||||||
g_encoder->Init();
|
g_encoder->Init();
|
||||||
|
|
||||||
|
palette_buf = nullptr;
|
||||||
|
palette_buf_srv = nullptr;
|
||||||
|
palette_uniform = nullptr;
|
||||||
|
palette_pixel_shader[GX_TL_IA8] = GetConvertShader("IA8");
|
||||||
|
palette_pixel_shader[GX_TL_RGB565] = GetConvertShader("RGB565");
|
||||||
|
palette_pixel_shader[GX_TL_RGB5A3] = GetConvertShader("RGB5A3");
|
||||||
|
auto lutBd = CD3D11_BUFFER_DESC(sizeof(u16) * 256, D3D11_BIND_SHADER_RESOURCE);
|
||||||
|
HRESULT hr = D3D::device->CreateBuffer(&lutBd, nullptr, &palette_buf);
|
||||||
|
CHECK(SUCCEEDED(hr), "create palette decoder lut buffer");
|
||||||
|
D3D::SetDebugObjectName(palette_buf, "texture decoder lut buffer");
|
||||||
|
// TODO: C14X2 format.
|
||||||
|
auto outlutUavDesc = CD3D11_SHADER_RESOURCE_VIEW_DESC(palette_buf, DXGI_FORMAT_R16_UINT, 0, 256, 0);
|
||||||
|
hr = D3D::device->CreateShaderResourceView(palette_buf, &outlutUavDesc, &palette_buf_srv);
|
||||||
|
CHECK(SUCCEEDED(hr), "create palette decoder lut srv");
|
||||||
|
D3D::SetDebugObjectName(palette_buf_srv, "texture decoder lut srv");
|
||||||
|
const D3D11_BUFFER_DESC cbdesc = CD3D11_BUFFER_DESC(16, D3D11_BIND_CONSTANT_BUFFER, D3D11_USAGE_DEFAULT);
|
||||||
|
hr = D3D::device->CreateBuffer(&cbdesc, nullptr, &palette_uniform);
|
||||||
|
CHECK(SUCCEEDED(hr), "Create palette decoder constant buffer");
|
||||||
|
D3D::SetDebugObjectName((ID3D11DeviceChild*)palette_uniform, "a constant buffer used in TextureCache::CopyRenderTargetToTexture");
|
||||||
}
|
}
|
||||||
|
|
||||||
TextureCache::~TextureCache()
|
TextureCache::~TextureCache()
|
||||||
|
@ -200,6 +352,12 @@ TextureCache::~TextureCache()
|
||||||
g_encoder->Shutdown();
|
g_encoder->Shutdown();
|
||||||
delete g_encoder;
|
delete g_encoder;
|
||||||
g_encoder = nullptr;
|
g_encoder = nullptr;
|
||||||
|
|
||||||
|
SAFE_RELEASE(palette_buf);
|
||||||
|
SAFE_RELEASE(palette_buf_srv);
|
||||||
|
SAFE_RELEASE(palette_uniform);
|
||||||
|
for (ID3D11PixelShader*& shader : palette_pixel_shader)
|
||||||
|
SAFE_RELEASE(shader);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,8 +42,15 @@ private:
|
||||||
|
|
||||||
u64 EncodeToRamFromTexture(u32 address, void* source_texture, u32 SourceW, u32 SourceH, bool bFromZBuffer, bool bIsIntensityFmt, u32 copyfmt, int bScaleByHalf, const EFBRectangle& source) {return 0;};
|
u64 EncodeToRamFromTexture(u32 address, void* source_texture, u32 SourceW, u32 SourceH, bool bFromZBuffer, bool bIsIntensityFmt, u32 copyfmt, int bScaleByHalf, const EFBRectangle& source) {return 0;};
|
||||||
|
|
||||||
|
void ConvertTexture(TCacheEntryBase* entry, TCacheEntryBase* unconverted, void* palette, TlutFormat format) override;
|
||||||
|
|
||||||
void CompileShaders() override { }
|
void CompileShaders() override { }
|
||||||
void DeleteShaders() override { }
|
void DeleteShaders() override { }
|
||||||
|
|
||||||
|
ID3D11Buffer* palette_buf;
|
||||||
|
ID3D11ShaderResourceView* palette_buf_srv;
|
||||||
|
ID3D11Buffer* palette_uniform;
|
||||||
|
ID3D11PixelShader* palette_pixel_shader[3];
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,6 +82,7 @@ void InitBackendInfo()
|
||||||
g_Config.backend_info.bSupportsGeometryShaders = true;
|
g_Config.backend_info.bSupportsGeometryShaders = true;
|
||||||
g_Config.backend_info.bSupports3DVision = true;
|
g_Config.backend_info.bSupports3DVision = true;
|
||||||
g_Config.backend_info.bSupportsPostProcessing = false;
|
g_Config.backend_info.bSupportsPostProcessing = false;
|
||||||
|
g_Config.backend_info.bSupportsPaletteConversion = true;
|
||||||
|
|
||||||
IDXGIFactory* factory;
|
IDXGIFactory* factory;
|
||||||
IDXGIAdapter* ad;
|
IDXGIAdapter* ad;
|
||||||
|
|
|
@ -204,7 +204,7 @@ void TextureCache::TCacheEntry::FromRenderTarget(u32 dstAddr, unsigned int dstFo
|
||||||
if (false == g_ActiveConfig.bCopyEFBToTexture)
|
if (false == g_ActiveConfig.bCopyEFBToTexture)
|
||||||
{
|
{
|
||||||
int encoded_size = TextureConverter::EncodeToRamFromTexture(
|
int encoded_size = TextureConverter::EncodeToRamFromTexture(
|
||||||
addr,
|
dstAddr,
|
||||||
read_texture,
|
read_texture,
|
||||||
srcFormat == PEControl::Z24,
|
srcFormat == PEControl::Z24,
|
||||||
isIntensity,
|
isIntensity,
|
||||||
|
@ -212,12 +212,12 @@ void TextureCache::TCacheEntry::FromRenderTarget(u32 dstAddr, unsigned int dstFo
|
||||||
scaleByHalf,
|
scaleByHalf,
|
||||||
srcRect);
|
srcRect);
|
||||||
|
|
||||||
u8* dst = Memory::GetPointer(addr);
|
u8* dst = Memory::GetPointer(dstAddr);
|
||||||
u64 const new_hash = GetHash64(dst,encoded_size,g_ActiveConfig.iSafeTextureCache_ColorSamples);
|
u64 const new_hash = GetHash64(dst,encoded_size,g_ActiveConfig.iSafeTextureCache_ColorSamples);
|
||||||
|
|
||||||
size_in_bytes = (u32)encoded_size;
|
size_in_bytes = (u32)encoded_size;
|
||||||
|
|
||||||
TextureCache::MakeRangeDynamic(addr,encoded_size);
|
TextureCache::MakeRangeDynamic(dstAddr, encoded_size);
|
||||||
|
|
||||||
hash = new_hash;
|
hash = new_hash;
|
||||||
}
|
}
|
||||||
|
@ -359,4 +359,10 @@ void TextureCache::DeleteShaders()
|
||||||
s_DepthMatrixProgram.Destroy();
|
s_DepthMatrixProgram.Destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TextureCache::ConvertTexture(TextureCache::TCacheEntryBase* entry, TCacheEntryBase* unconverted, void* palette, TlutFormat format)
|
||||||
|
{
|
||||||
|
// TODO: Implement.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,7 @@ private:
|
||||||
~TextureCache();
|
~TextureCache();
|
||||||
|
|
||||||
TCacheEntryBase* CreateTexture(const TCacheEntryConfig& config) override;
|
TCacheEntryBase* CreateTexture(const TCacheEntryConfig& config) override;
|
||||||
|
void ConvertTexture(TCacheEntryBase* entry, TCacheEntryBase* unconverted, void* palette, TlutFormat format) override;
|
||||||
|
|
||||||
void CompileShaders() override;
|
void CompileShaders() override;
|
||||||
void DeleteShaders() override;
|
void DeleteShaders() override;
|
||||||
|
|
|
@ -140,6 +140,7 @@ static void InitBackendInfo()
|
||||||
g_Config.backend_info.bSupportsGeometryShaders = true;
|
g_Config.backend_info.bSupportsGeometryShaders = true;
|
||||||
g_Config.backend_info.bSupports3DVision = false;
|
g_Config.backend_info.bSupports3DVision = false;
|
||||||
g_Config.backend_info.bSupportsPostProcessing = true;
|
g_Config.backend_info.bSupportsPostProcessing = true;
|
||||||
|
g_Config.backend_info.bSupportsPaletteConversion = false;
|
||||||
|
|
||||||
g_Config.backend_info.Adapters.clear();
|
g_Config.backend_info.Adapters.clear();
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ size_t TextureCache::temp_size;
|
||||||
|
|
||||||
TextureCache::TexCache TextureCache::textures;
|
TextureCache::TexCache TextureCache::textures;
|
||||||
TextureCache::TexPool TextureCache::texture_pool;
|
TextureCache::TexPool TextureCache::texture_pool;
|
||||||
|
TextureCache::TCacheEntryBase* TextureCache::bound_textures[8];
|
||||||
|
|
||||||
TextureCache::BackupConfig TextureCache::backup_config;
|
TextureCache::BackupConfig TextureCache::backup_config;
|
||||||
|
|
||||||
|
@ -74,6 +75,8 @@ void TextureCache::RequestInvalidateTextureCache()
|
||||||
|
|
||||||
void TextureCache::Invalidate()
|
void TextureCache::Invalidate()
|
||||||
{
|
{
|
||||||
|
UnbindTextures();
|
||||||
|
|
||||||
for (auto& tex : textures)
|
for (auto& tex : textures)
|
||||||
{
|
{
|
||||||
delete tex.second;
|
delete tex.second;
|
||||||
|
@ -143,7 +146,7 @@ void TextureCache::Cleanup(int _frameCount)
|
||||||
}
|
}
|
||||||
if (_frameCount > TEXTURE_KILL_THRESHOLD + iter->second->frameCount &&
|
if (_frameCount > TEXTURE_KILL_THRESHOLD + iter->second->frameCount &&
|
||||||
// EFB copies living on the host GPU are unrecoverable and thus shouldn't be deleted
|
// EFB copies living on the host GPU are unrecoverable and thus shouldn't be deleted
|
||||||
!iter->second->IsEfbCopy())
|
!iter->second->IsUnrecoverable())
|
||||||
{
|
{
|
||||||
FreeTexture(iter->second);
|
FreeTexture(iter->second);
|
||||||
iter = textures.erase(iter);
|
iter = textures.erase(iter);
|
||||||
|
@ -174,17 +177,17 @@ void TextureCache::Cleanup(int _frameCount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextureCache::InvalidateRange(u32 start_address, u32 size)
|
void TextureCache::MakeRangeDynamic(u32 start_address, u32 size)
|
||||||
{
|
{
|
||||||
TexCache::iterator
|
TexCache::iterator
|
||||||
iter = textures.begin(),
|
iter = textures.begin();
|
||||||
tcend = textures.end();
|
|
||||||
while (iter != tcend)
|
while (iter != textures.end())
|
||||||
{
|
{
|
||||||
if (iter->second->OverlapsMemoryRange(start_address, size))
|
if (iter->second->OverlapsMemoryRange(start_address, size))
|
||||||
{
|
{
|
||||||
FreeTexture(iter->second);
|
FreeTexture(iter->second);
|
||||||
textures.erase(iter++);
|
iter = textures.erase(iter);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -193,55 +196,21 @@ void TextureCache::InvalidateRange(u32 start_address, u32 size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextureCache::MakeRangeDynamic(u32 start_address, u32 size)
|
|
||||||
{
|
|
||||||
TexCache::iterator
|
|
||||||
iter = textures.lower_bound(start_address),
|
|
||||||
tcend = textures.upper_bound(start_address + size);
|
|
||||||
|
|
||||||
if (iter != textures.begin())
|
|
||||||
--iter;
|
|
||||||
|
|
||||||
for (; iter != tcend; ++iter)
|
|
||||||
{
|
|
||||||
if (iter->second->OverlapsMemoryRange(start_address, size))
|
|
||||||
{
|
|
||||||
iter->second->SetHashes(TEXHASH_INVALID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TextureCache::TCacheEntryBase::OverlapsMemoryRange(u32 range_address, u32 range_size) const
|
bool TextureCache::TCacheEntryBase::OverlapsMemoryRange(u32 range_address, u32 range_size) const
|
||||||
{
|
{
|
||||||
if (addr + size_in_bytes <= range_address)
|
if (!addr.HasMemAddress())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (addr >= range_address + range_size)
|
u32 memaddr = addr.GetMemAddress();
|
||||||
|
if (memaddr + size_in_bytes <= range_address)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (memaddr >= range_address + range_size)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextureCache::ClearRenderTargets()
|
|
||||||
{
|
|
||||||
TexCache::iterator
|
|
||||||
iter = textures.begin(),
|
|
||||||
tcend = textures.end();
|
|
||||||
|
|
||||||
while (iter != tcend)
|
|
||||||
{
|
|
||||||
if (iter->second->IsEfbCopy())
|
|
||||||
{
|
|
||||||
FreeTexture(iter->second);
|
|
||||||
textures.erase(iter++);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
++iter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TextureCache::DumpTexture(TCacheEntryBase* entry, std::string basename, unsigned int level)
|
void TextureCache::DumpTexture(TCacheEntryBase* entry, std::string basename, unsigned int level)
|
||||||
{
|
{
|
||||||
std::string szDir = File::GetUserPath(D_DUMPTEXTURES_IDX) +
|
std::string szDir = File::GetUserPath(D_DUMPTEXTURES_IDX) +
|
||||||
|
@ -267,16 +236,30 @@ static u32 CalculateLevelSize(u32 level_0_size, u32 level)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used by TextureCache::Load
|
// Used by TextureCache::Load
|
||||||
static TextureCache::TCacheEntryBase* ReturnEntry(unsigned int stage, TextureCache::TCacheEntryBase* entry)
|
TextureCache::TCacheEntryBase* TextureCache::ReturnEntry(unsigned int stage, TCacheEntryBase* entry)
|
||||||
{
|
{
|
||||||
entry->frameCount = FRAMECOUNT_INVALID;
|
entry->frameCount = FRAMECOUNT_INVALID;
|
||||||
entry->Bind(stage);
|
bound_textures[stage] = entry;
|
||||||
|
|
||||||
GFX_DEBUGGER_PAUSE_AT(NEXT_TEXTURE_CHANGE, true);
|
GFX_DEBUGGER_PAUSE_AT(NEXT_TEXTURE_CHANGE, true);
|
||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TextureCache::BindTextures()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 8; ++i)
|
||||||
|
{
|
||||||
|
if (bound_textures[i])
|
||||||
|
bound_textures[i]->Bind(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextureCache::UnbindTextures()
|
||||||
|
{
|
||||||
|
std::fill(std::begin(bound_textures), std::end(bound_textures), nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
TextureCache::TCacheEntryBase* TextureCache::Load(const u32 stage)
|
TextureCache::TCacheEntryBase* TextureCache::Load(const u32 stage)
|
||||||
{
|
{
|
||||||
const FourTexUnits &tex = bpmem.tex[stage >> 2];
|
const FourTexUnits &tex = bpmem.tex[stage >> 2];
|
||||||
|
@ -309,6 +292,11 @@ TextureCache::TCacheEntryBase* TextureCache::Load(const u32 stage)
|
||||||
u32 full_format = texformat;
|
u32 full_format = texformat;
|
||||||
|
|
||||||
const bool isPaletteTexture = (texformat == GX_TF_C4 || texformat == GX_TF_C8 || texformat == GX_TF_C14X2);
|
const bool isPaletteTexture = (texformat == GX_TF_C4 || texformat == GX_TF_C8 || texformat == GX_TF_C14X2);
|
||||||
|
|
||||||
|
// Reject invalid tlut format.
|
||||||
|
if (isPaletteTexture && tlutfmt > GX_TL_RGB5A3)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
if (isPaletteTexture)
|
if (isPaletteTexture)
|
||||||
full_format = texformat | (tlutfmt << 16);
|
full_format = texformat | (tlutfmt << 16);
|
||||||
|
|
||||||
|
@ -323,19 +311,43 @@ TextureCache::TCacheEntryBase* TextureCache::Load(const u32 stage)
|
||||||
// TODO: This doesn't hash GB tiles for preloaded RGBA8 textures (instead, it's hashing more data from the low tmem bank than it should)
|
// TODO: This doesn't hash GB tiles for preloaded RGBA8 textures (instead, it's hashing more data from the low tmem bank than it should)
|
||||||
tex_hash = GetHash64(src_data, texture_size, g_ActiveConfig.iSafeTextureCache_ColorSamples);
|
tex_hash = GetHash64(src_data, texture_size, g_ActiveConfig.iSafeTextureCache_ColorSamples);
|
||||||
u32 palette_size = 0;
|
u32 palette_size = 0;
|
||||||
|
u64 tlut_hash = 0;
|
||||||
if (isPaletteTexture)
|
if (isPaletteTexture)
|
||||||
{
|
{
|
||||||
palette_size = TexDecoder_GetPaletteSize(texformat);
|
palette_size = TexDecoder_GetPaletteSize(texformat);
|
||||||
u64 tlut_hash = GetHash64(&texMem[tlutaddr], palette_size, g_ActiveConfig.iSafeTextureCache_ColorSamples);
|
tlut_hash = GetHash64(&texMem[tlutaddr], palette_size, g_ActiveConfig.iSafeTextureCache_ColorSamples);
|
||||||
|
|
||||||
// Mix the tlut hash into the texture hash. So we only have to compare it once.
|
|
||||||
tex_hash ^= tlut_hash;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GPUs don't like when the specified mipmap count would require more than one 1x1-sized LOD in the mipmap chain
|
// GPUs don't like when the specified mipmap count would require more than one 1x1-sized LOD in the mipmap chain
|
||||||
// e.g. 64x64 with 7 LODs would have the mipmap chain 64x64,32x32,16x16,8x8,4x4,2x2,1x1,0x0, so we limit the mipmap count to 6 there
|
// e.g. 64x64 with 7 LODs would have the mipmap chain 64x64,32x32,16x16,8x8,4x4,2x2,1x1,0x0, so we limit the mipmap count to 6 there
|
||||||
tex_levels = std::min<u32>(IntLog2(std::max(width, height)) + 1, tex_levels);
|
tex_levels = std::min<u32>(IntLog2(std::max(width, height)) + 1, tex_levels);
|
||||||
|
|
||||||
|
// Compute a texture ID; this isn't everything about a texture, rather just
|
||||||
|
// enough to group together textures with related memory addresses.
|
||||||
|
TextureAddress texID;
|
||||||
|
TextureAddress paletteDecodedID;
|
||||||
|
if (from_tmem)
|
||||||
|
{
|
||||||
|
u32 tmem_addr = bpmem.tex[stage / 4].texImage1[stage % 4].tmem_even * TMEM_LINE_SIZE;
|
||||||
|
if (texformat == GX_TF_RGBA8 && from_tmem)
|
||||||
|
{
|
||||||
|
u32 tmem_odd_addr = bpmem.tex[stage / 4].texImage2[stage % 4].tmem_odd * TMEM_LINE_SIZE;
|
||||||
|
texID = TextureAddress::TMemRGBA8(tmem_addr, tmem_odd_addr);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
texID = TextureAddress::TMem(tmem_addr);
|
||||||
|
if (isPaletteTexture)
|
||||||
|
paletteDecodedID = TextureAddress::TMemPalette(tmem_addr, tlutaddr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
texID = TextureAddress::Mem(address);
|
||||||
|
if (isPaletteTexture)
|
||||||
|
paletteDecodedID = TextureAddress::MemPalette(address, tlutaddr);
|
||||||
|
}
|
||||||
|
|
||||||
// Find all texture cache entries for the current texture address, and decide whether to use one of
|
// Find all texture cache entries for the current texture address, and decide whether to use one of
|
||||||
// them, or to create a new one
|
// them, or to create a new one
|
||||||
//
|
//
|
||||||
|
@ -360,7 +372,13 @@ TextureCache::TCacheEntryBase* TextureCache::Load(const u32 stage)
|
||||||
//
|
//
|
||||||
// For efb copies, the entry created in CopyRenderTargetToTexture always has to be used, or else it was
|
// For efb copies, the entry created in CopyRenderTargetToTexture always has to be used, or else it was
|
||||||
// done in vain.
|
// done in vain.
|
||||||
std::pair <TexCache::iterator, TexCache::iterator> iter_range = textures.equal_range(address);
|
std::pair<TexCache::iterator, TexCache::iterator> iter_range = textures.equal_range(texID);
|
||||||
|
bool palette_decoded_entry = false;
|
||||||
|
if (isPaletteTexture && iter_range.first == iter_range.second)
|
||||||
|
{
|
||||||
|
iter_range = textures.equal_range(paletteDecodedID);
|
||||||
|
palette_decoded_entry = true;
|
||||||
|
}
|
||||||
TexCache::iterator iter = iter_range.first;
|
TexCache::iterator iter = iter_range.first;
|
||||||
TexCache::iterator oldest_entry = iter;
|
TexCache::iterator oldest_entry = iter;
|
||||||
int temp_frameCount = 0x7fffffff;
|
int temp_frameCount = 0x7fffffff;
|
||||||
|
@ -370,14 +388,39 @@ TextureCache::TCacheEntryBase* TextureCache::Load(const u32 stage)
|
||||||
TCacheEntryBase* entry = iter->second;
|
TCacheEntryBase* entry = iter->second;
|
||||||
if (entry->IsEfbCopy())
|
if (entry->IsEfbCopy())
|
||||||
{
|
{
|
||||||
// For EFB copies, only the hash and the texture address need to match. Ignore the hash when
|
// EFB copies have slightly different rules: the hash doesn't need to match
|
||||||
// using EFB to texture, because there's no hash in this case
|
// in EFB2Tex mode, and EFB copy formats have different meanings from texture
|
||||||
if (g_ActiveConfig.bCopyEFBToTexture || entry->hash == tex_hash)
|
// formats.
|
||||||
|
if (g_ActiveConfig.bCopyEFBToTexture ||
|
||||||
|
(tex_hash == entry->hash && (!isPaletteTexture || g_Config.backend_info.bSupportsPaletteConversion)))
|
||||||
{
|
{
|
||||||
// TODO: Print a warning if the format changes! In this case,
|
// TODO: We should check format/width/height/levels for EFB copies. Checking
|
||||||
// we could reinterpret the internal texture object data to the new pixel format
|
// format is complicated because EFB copy formats don't exactly match
|
||||||
// (similar to what is already being done in Renderer::ReinterpretPixelFormat())
|
// texture formats. I'm not sure what effect checking width/height/levels
|
||||||
// TODO: Convert paletted textures, which are efb copies, using the right palette, so they display correctly
|
// would have.
|
||||||
|
if (!palette_decoded_entry && isPaletteTexture && g_Config.backend_info.bSupportsPaletteConversion)
|
||||||
|
{
|
||||||
|
// Perform palette decoding.
|
||||||
|
// TODO: Skip decoding if we find a match.
|
||||||
|
std::pair<TexCache::iterator, TexCache::iterator> decoded_iter_range = textures.equal_range(paletteDecodedID);
|
||||||
|
while (decoded_iter_range.first != decoded_iter_range.second)
|
||||||
|
{
|
||||||
|
// Pool this texture and make a new one later.
|
||||||
|
FreeTexture(decoded_iter_range.first->second);
|
||||||
|
decoded_iter_range.first = textures.erase(decoded_iter_range.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
TCacheEntryBase *decoded_entry = AllocateTexture(entry->config);
|
||||||
|
|
||||||
|
decoded_entry->SetGeneralParameters(paletteDecodedID, texture_size, full_format);
|
||||||
|
decoded_entry->SetDimensions(entry->native_width, entry->native_height, 1);
|
||||||
|
decoded_entry->SetHashes(TEXHASH_INVALID);
|
||||||
|
decoded_entry->frameCount = FRAMECOUNT_INVALID;
|
||||||
|
|
||||||
|
g_texture_cache->ConvertTexture(decoded_entry, entry, &texMem[tlutaddr], (TlutFormat)tlutfmt);
|
||||||
|
textures.insert(TexCache::value_type(paletteDecodedID, decoded_entry));
|
||||||
|
entry = decoded_entry;
|
||||||
|
}
|
||||||
return ReturnEntry(stage, entry);
|
return ReturnEntry(stage, entry);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -389,12 +432,14 @@ TextureCache::TCacheEntryBase* TextureCache::Load(const u32 stage)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
// For normal textures, all texture parameters need to match
|
|
||||||
if (entry->hash == tex_hash && entry->format == full_format && entry->native_levels >= tex_levels &&
|
|
||||||
entry->native_width == nativeW && entry->native_height == nativeH)
|
|
||||||
{
|
{
|
||||||
return ReturnEntry(stage, entry);
|
// For normal textures, all texture parameters need to match
|
||||||
|
if (entry->hash == (tex_hash ^ tlut_hash) && entry->format == full_format && entry->native_levels >= tex_levels &&
|
||||||
|
entry->native_width == nativeW && entry->native_height == nativeH)
|
||||||
|
{
|
||||||
|
return ReturnEntry(stage, entry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the entry which hasn't been used for the longest time
|
// Find the entry which hasn't been used for the longest time
|
||||||
|
@ -468,11 +513,11 @@ TextureCache::TCacheEntryBase* TextureCache::Load(const u32 stage)
|
||||||
TCacheEntryBase* entry = AllocateTexture(config);
|
TCacheEntryBase* entry = AllocateTexture(config);
|
||||||
GFX_DEBUGGER_PAUSE_AT(NEXT_NEW_TEXTURE, true);
|
GFX_DEBUGGER_PAUSE_AT(NEXT_NEW_TEXTURE, true);
|
||||||
|
|
||||||
textures.insert(TexCache::value_type(address, entry));
|
textures.insert(TexCache::value_type(isPaletteTexture ? paletteDecodedID : texID, entry));
|
||||||
|
|
||||||
entry->SetGeneralParameters(address, texture_size, full_format);
|
entry->SetGeneralParameters(isPaletteTexture ? paletteDecodedID : texID, texture_size, full_format);
|
||||||
entry->SetDimensions(nativeW, nativeH, tex_levels);
|
entry->SetDimensions(nativeW, nativeH, tex_levels);
|
||||||
entry->hash = tex_hash;
|
entry->hash = tex_hash ^ tlut_hash;
|
||||||
|
|
||||||
// load texture
|
// load texture
|
||||||
entry->Load(width, height, expandedWidth, 0);
|
entry->Load(width, height, expandedWidth, 0);
|
||||||
|
@ -828,7 +873,7 @@ void TextureCache::CopyRenderTargetToTexture(u32 dstAddr, unsigned int dstFormat
|
||||||
unsigned int scaled_tex_h = g_ActiveConfig.bCopyEFBScaled ? Renderer::EFBToScaledY(tex_h) : tex_h;
|
unsigned int scaled_tex_h = g_ActiveConfig.bCopyEFBScaled ? Renderer::EFBToScaledY(tex_h) : tex_h;
|
||||||
|
|
||||||
// remove all texture cache entries at dstAddr
|
// remove all texture cache entries at dstAddr
|
||||||
std::pair <TexCache::iterator, TexCache::iterator> iter_range = textures.equal_range(dstAddr);
|
std::pair <TexCache::iterator, TexCache::iterator> iter_range = textures.equal_range(TextureAddress::Mem(dstAddr));
|
||||||
TexCache::iterator iter = iter_range.first;
|
TexCache::iterator iter = iter_range.first;
|
||||||
while (iter != iter_range.second)
|
while (iter != iter_range.second)
|
||||||
{
|
{
|
||||||
|
@ -846,7 +891,7 @@ void TextureCache::CopyRenderTargetToTexture(u32 dstAddr, unsigned int dstFormat
|
||||||
TCacheEntryBase* entry = AllocateTexture(config);
|
TCacheEntryBase* entry = AllocateTexture(config);
|
||||||
|
|
||||||
// TODO: Using the wrong dstFormat, dumb...
|
// TODO: Using the wrong dstFormat, dumb...
|
||||||
entry->SetGeneralParameters(dstAddr, 0, dstFormat);
|
entry->SetGeneralParameters(TextureAddress::Mem(dstAddr), 0, dstFormat);
|
||||||
entry->SetDimensions(tex_w, tex_h, 1);
|
entry->SetDimensions(tex_w, tex_h, 1);
|
||||||
entry->SetHashes(TEXHASH_INVALID);
|
entry->SetHashes(TEXHASH_INVALID);
|
||||||
|
|
||||||
|
@ -854,7 +899,7 @@ void TextureCache::CopyRenderTargetToTexture(u32 dstAddr, unsigned int dstFormat
|
||||||
|
|
||||||
entry->FromRenderTarget(dstAddr, dstFormat, srcFormat, srcRect, isIntensity, scaleByHalf, cbufid, colmat);
|
entry->FromRenderTarget(dstAddr, dstFormat, srcFormat, srcRect, isIntensity, scaleByHalf, cbufid, colmat);
|
||||||
|
|
||||||
textures.insert(TexCache::value_type(dstAddr, entry));
|
textures.insert(TexCache::value_type(TextureAddress::Mem(dstAddr), entry));
|
||||||
}
|
}
|
||||||
|
|
||||||
TextureCache::TCacheEntryBase* TextureCache::AllocateTexture(const TCacheEntryConfig& config)
|
TextureCache::TCacheEntryBase* TextureCache::AllocateTexture(const TCacheEntryConfig& config)
|
||||||
|
|
|
@ -43,13 +43,56 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
class TextureAddress
|
||||||
|
{
|
||||||
|
u32 address1;
|
||||||
|
u32 address2;
|
||||||
|
enum AddressKind
|
||||||
|
{
|
||||||
|
// A texture in RAM
|
||||||
|
RAM,
|
||||||
|
// A texture loaded into TMEM
|
||||||
|
TMEM,
|
||||||
|
// A texture in RAM, fully decoded using a palette.
|
||||||
|
RAM_PALETTE,
|
||||||
|
// An RGBA8 texture in TMEM.
|
||||||
|
TMEM_RGBA8,
|
||||||
|
// A palette texture in TMEM.
|
||||||
|
TMEM_PALETTE,
|
||||||
|
// Uninitialized address.
|
||||||
|
INVALID
|
||||||
|
};
|
||||||
|
AddressKind kind;
|
||||||
|
TextureAddress(u32 a, u32 b, AddressKind k) : address1(a), address2(b), kind(k) {}
|
||||||
|
public:
|
||||||
|
TextureAddress() : kind(INVALID), address1(0), address2(0) {}
|
||||||
|
static TextureAddress Mem(u32 a) { return TextureAddress(a, 0, RAM); }
|
||||||
|
static TextureAddress MemPalette(u32 a, u32 b) { return TextureAddress(a, b, RAM_PALETTE); }
|
||||||
|
static TextureAddress TMem(u32 a) { return TextureAddress(a, 0, TMEM); }
|
||||||
|
static TextureAddress TMemRGBA8(u32 a, u32 b) { return TextureAddress(a, b, TMEM_RGBA8); }
|
||||||
|
static TextureAddress TMemPalette(u32 a, u32 b) { return TextureAddress(a, b, TMEM_PALETTE); }
|
||||||
|
bool operator == (const TextureAddress& b) const
|
||||||
|
{
|
||||||
|
return kind == b.kind && address1 == b.address1 && address2 == b.address2;
|
||||||
|
}
|
||||||
|
bool operator < (const TextureAddress& b) const
|
||||||
|
{
|
||||||
|
if (kind != b.kind)
|
||||||
|
return kind < b.kind;
|
||||||
|
if (address1 != b.address1)
|
||||||
|
return address1 < b.address1;
|
||||||
|
return address2 < b.address2;
|
||||||
|
}
|
||||||
|
bool IsMemOnlyAddress() const { return kind == RAM; }
|
||||||
|
bool HasMemAddress() const { return kind == RAM || kind == RAM_PALETTE; }
|
||||||
|
u32 GetMemAddress() const { return address1; }
|
||||||
|
};
|
||||||
struct TCacheEntryBase
|
struct TCacheEntryBase
|
||||||
{
|
{
|
||||||
const TCacheEntryConfig config;
|
const TCacheEntryConfig config;
|
||||||
|
|
||||||
// common members
|
// common members
|
||||||
u32 addr;
|
TextureAddress addr;
|
||||||
u32 size_in_bytes;
|
u32 size_in_bytes;
|
||||||
u64 hash;
|
u64 hash;
|
||||||
u32 format;
|
u32 format;
|
||||||
|
@ -61,7 +104,7 @@ public:
|
||||||
int frameCount;
|
int frameCount;
|
||||||
|
|
||||||
|
|
||||||
void SetGeneralParameters(u32 _addr, u32 _size, u32 _format)
|
void SetGeneralParameters(TextureAddress _addr, u32 _size, u32 _format)
|
||||||
{
|
{
|
||||||
addr = _addr;
|
addr = _addr;
|
||||||
size_in_bytes = _size;
|
size_in_bytes = _size;
|
||||||
|
@ -96,6 +139,7 @@ public:
|
||||||
bool OverlapsMemoryRange(u32 range_address, u32 range_size) const;
|
bool OverlapsMemoryRange(u32 range_address, u32 range_size) const;
|
||||||
|
|
||||||
bool IsEfbCopy() { return config.rendertarget; }
|
bool IsEfbCopy() { return config.rendertarget; }
|
||||||
|
bool IsUnrecoverable() { return IsEfbCopy() && addr.IsMemOnlyAddress(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
virtual ~TextureCache(); // needs virtual for DX11 dtor
|
virtual ~TextureCache(); // needs virtual for DX11 dtor
|
||||||
|
@ -107,9 +151,7 @@ public:
|
||||||
static void Cleanup(int frameCount);
|
static void Cleanup(int frameCount);
|
||||||
|
|
||||||
static void Invalidate();
|
static void Invalidate();
|
||||||
static void InvalidateRange(u32 start_address, u32 size);
|
|
||||||
static void MakeRangeDynamic(u32 start_address, u32 size);
|
static void MakeRangeDynamic(u32 start_address, u32 size);
|
||||||
static void ClearRenderTargets(); // currently only used by OGL
|
|
||||||
|
|
||||||
virtual TCacheEntryBase* CreateTexture(const TCacheEntryConfig& config) = 0;
|
virtual TCacheEntryBase* CreateTexture(const TCacheEntryConfig& config) = 0;
|
||||||
|
|
||||||
|
@ -117,11 +159,15 @@ public:
|
||||||
virtual void DeleteShaders() = 0; // currently only implemented by OGL
|
virtual void DeleteShaders() = 0; // currently only implemented by OGL
|
||||||
|
|
||||||
static TCacheEntryBase* Load(const u32 stage);
|
static TCacheEntryBase* Load(const u32 stage);
|
||||||
|
static void UnbindTextures();
|
||||||
|
static void BindTextures();
|
||||||
static void CopyRenderTargetToTexture(u32 dstAddr, unsigned int dstFormat, PEControl::PixelFormat srcFormat,
|
static void CopyRenderTargetToTexture(u32 dstAddr, unsigned int dstFormat, PEControl::PixelFormat srcFormat,
|
||||||
const EFBRectangle& srcRect, bool isIntensity, bool scaleByHalf);
|
const EFBRectangle& srcRect, bool isIntensity, bool scaleByHalf);
|
||||||
|
|
||||||
static void RequestInvalidateTextureCache();
|
static void RequestInvalidateTextureCache();
|
||||||
|
|
||||||
|
virtual void ConvertTexture(TCacheEntryBase* entry, TCacheEntryBase* unconverted, void* palette, TlutFormat format) = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
TextureCache();
|
TextureCache();
|
||||||
|
|
||||||
|
@ -135,11 +181,14 @@ private:
|
||||||
static TCacheEntryBase* AllocateTexture(const TCacheEntryConfig& config);
|
static TCacheEntryBase* AllocateTexture(const TCacheEntryConfig& config);
|
||||||
static void FreeTexture(TCacheEntryBase* entry);
|
static void FreeTexture(TCacheEntryBase* entry);
|
||||||
|
|
||||||
typedef std::multimap<u32, TCacheEntryBase*> TexCache;
|
static TCacheEntryBase* ReturnEntry(unsigned int stage, TCacheEntryBase* entry);
|
||||||
|
|
||||||
|
typedef std::multimap<TextureAddress, TCacheEntryBase*> TexCache;
|
||||||
typedef std::unordered_multimap<TCacheEntryConfig, TCacheEntryBase*, TCacheEntryConfig::Hasher> TexPool;
|
typedef std::unordered_multimap<TCacheEntryConfig, TCacheEntryBase*, TCacheEntryConfig::Hasher> TexPool;
|
||||||
|
|
||||||
static TexCache textures;
|
static TexCache textures;
|
||||||
static TexPool texture_pool;
|
static TexPool texture_pool;
|
||||||
|
static TCacheEntryBase* bound_textures[8];
|
||||||
|
|
||||||
// Backup configuration values
|
// Backup configuration values
|
||||||
static struct BackupConfig
|
static struct BackupConfig
|
||||||
|
|
|
@ -209,6 +209,7 @@ void VertexManager::Flush()
|
||||||
if (bpmem.tevind[i].IsActive() && bpmem.tevind[i].bt < bpmem.genMode.numindstages)
|
if (bpmem.tevind[i].IsActive() && bpmem.tevind[i].bt < bpmem.genMode.numindstages)
|
||||||
usedtextures[bpmem.tevindref.getTexMap(bpmem.tevind[i].bt)] = true;
|
usedtextures[bpmem.tevindref.getTexMap(bpmem.tevind[i].bt)] = true;
|
||||||
|
|
||||||
|
TextureCache::UnbindTextures();
|
||||||
for (unsigned int i : usedtextures)
|
for (unsigned int i : usedtextures)
|
||||||
{
|
{
|
||||||
g_renderer->SetSamplerState(i & 3, i >> 2);
|
g_renderer->SetSamplerState(i & 3, i >> 2);
|
||||||
|
@ -224,6 +225,7 @@ void VertexManager::Flush()
|
||||||
ERROR_LOG(VIDEO, "error loading texture");
|
ERROR_LOG(VIDEO, "error loading texture");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
TextureCache::BindTextures();
|
||||||
}
|
}
|
||||||
|
|
||||||
// set global vertex constants
|
// set global vertex constants
|
||||||
|
|
|
@ -159,6 +159,7 @@ struct VideoConfig final
|
||||||
bool bSupportsBBox;
|
bool bSupportsBBox;
|
||||||
bool bSupportsGSInstancing; // Needed by GeometryShaderGen, so must stay in VideoCommon
|
bool bSupportsGSInstancing; // Needed by GeometryShaderGen, so must stay in VideoCommon
|
||||||
bool bSupportsPostProcessing;
|
bool bSupportsPostProcessing;
|
||||||
|
bool bSupportsPaletteConversion;
|
||||||
} backend_info;
|
} backend_info;
|
||||||
|
|
||||||
// Utility
|
// Utility
|
||||||
|
|
Loading…
Reference in New Issue