Merge pull request #2059 from magumagu/palette-convert

Decode EFB copies used as paletted textures.
This commit is contained in:
Pierre Bourdon 2015-02-20 01:11:25 +01:00
commit 8b095a0178
10 changed files with 276 additions and 73 deletions

View File

@ -4,6 +4,7 @@
#include "Core/HW/Memmap.h"
#include "VideoBackends/D3D/D3DBase.h"
#include "VideoBackends/D3D/D3DShader.h"
#include "VideoBackends/D3D/D3DState.h"
#include "VideoBackends/D3D/D3DUtil.h"
#include "VideoBackends/D3D/FramebufferManager.h"
@ -14,6 +15,7 @@
#include "VideoBackends/D3D/TextureEncoder.h"
#include "VideoBackends/D3D/VertexShaderCache.h"
#include "VideoCommon/ImageWrite.h"
#include "VideoCommon/LookUpTables.h"
#include "VideoCommon/RenderBase.h"
#include "VideoCommon/VideoConfig.h"
@ -179,17 +181,167 @@ void TextureCache::TCacheEntry::FromRenderTarget(u32 dstAddr, unsigned int dstFo
size_in_bytes = (u32)encoded_size;
TextureCache::MakeRangeDynamic(addr, (u32)encoded_size);
TextureCache::MakeRangeDynamic(dstAddr, (u32)encoded_size);
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, &params, 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()
{
// FIXME: Is it safe here?
g_encoder = new PSTextureEncoder;
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()
@ -200,6 +352,12 @@ TextureCache::~TextureCache()
g_encoder->Shutdown();
delete g_encoder;
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);
}
}

View File

@ -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;};
void ConvertTexture(TCacheEntryBase* entry, TCacheEntryBase* unconverted, void* palette, TlutFormat format) override;
void CompileShaders() override { }
void DeleteShaders() override { }
ID3D11Buffer* palette_buf;
ID3D11ShaderResourceView* palette_buf_srv;
ID3D11Buffer* palette_uniform;
ID3D11PixelShader* palette_pixel_shader[3];
};
}

View File

@ -82,6 +82,7 @@ void InitBackendInfo()
g_Config.backend_info.bSupportsGeometryShaders = true;
g_Config.backend_info.bSupports3DVision = true;
g_Config.backend_info.bSupportsPostProcessing = false;
g_Config.backend_info.bSupportsPaletteConversion = true;
IDXGIFactory* factory;
IDXGIAdapter* ad;

View File

@ -204,7 +204,7 @@ void TextureCache::TCacheEntry::FromRenderTarget(u32 dstAddr, unsigned int dstFo
if (false == g_ActiveConfig.bCopyEFBToTexture)
{
int encoded_size = TextureConverter::EncodeToRamFromTexture(
addr,
dstAddr,
read_texture,
srcFormat == PEControl::Z24,
isIntensity,
@ -212,12 +212,12 @@ void TextureCache::TCacheEntry::FromRenderTarget(u32 dstAddr, unsigned int dstFo
scaleByHalf,
srcRect);
u8* dst = Memory::GetPointer(addr);
u8* dst = Memory::GetPointer(dstAddr);
u64 const new_hash = GetHash64(dst,encoded_size,g_ActiveConfig.iSafeTextureCache_ColorSamples);
size_in_bytes = (u32)encoded_size;
TextureCache::MakeRangeDynamic(addr,encoded_size);
TextureCache::MakeRangeDynamic(dstAddr, encoded_size);
hash = new_hash;
}
@ -359,4 +359,10 @@ void TextureCache::DeleteShaders()
s_DepthMatrixProgram.Destroy();
}
void TextureCache::ConvertTexture(TextureCache::TCacheEntryBase* entry, TCacheEntryBase* unconverted, void* palette, TlutFormat format)
{
// TODO: Implement.
return;
}
}

View File

@ -48,6 +48,7 @@ private:
~TextureCache();
TCacheEntryBase* CreateTexture(const TCacheEntryConfig& config) override;
void ConvertTexture(TCacheEntryBase* entry, TCacheEntryBase* unconverted, void* palette, TlutFormat format) override;
void CompileShaders() override;
void DeleteShaders() override;

View File

@ -140,6 +140,7 @@ static void InitBackendInfo()
g_Config.backend_info.bSupportsGeometryShaders = true;
g_Config.backend_info.bSupports3DVision = false;
g_Config.backend_info.bSupportsPostProcessing = true;
g_Config.backend_info.bSupportsPaletteConversion = false;
g_Config.backend_info.Adapters.clear();

View File

@ -32,6 +32,7 @@ size_t TextureCache::temp_size;
TextureCache::TexCache TextureCache::textures;
TextureCache::TexPool TextureCache::texture_pool;
TextureCache::TCacheEntryBase* TextureCache::bound_textures[8];
TextureCache::BackupConfig TextureCache::backup_config;
@ -74,6 +75,8 @@ void TextureCache::RequestInvalidateTextureCache()
void TextureCache::Invalidate()
{
UnbindTextures();
for (auto& tex : textures)
{
delete tex.second;
@ -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
iter = textures.begin(),
tcend = textures.end();
while (iter != tcend)
iter = textures.begin();
while (iter != textures.end())
{
if (iter->second->OverlapsMemoryRange(start_address, size))
{
FreeTexture(iter->second);
textures.erase(iter++);
iter = textures.erase(iter);
}
else
{
@ -193,24 +196,6 @@ 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
{
if (addr + size_in_bytes <= range_address)
@ -222,26 +207,6 @@ bool TextureCache::TCacheEntryBase::OverlapsMemoryRange(u32 range_address, u32 r
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)
{
std::string szDir = File::GetUserPath(D_DUMPTEXTURES_IDX) +
@ -267,16 +232,30 @@ static u32 CalculateLevelSize(u32 level_0_size, u32 level)
}
// 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->Bind(stage);
bound_textures[stage] = entry;
GFX_DEBUGGER_PAUSE_AT(NEXT_TEXTURE_CHANGE, true);
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)
{
const FourTexUnits &tex = bpmem.tex[stage >> 2];
@ -309,6 +288,11 @@ TextureCache::TCacheEntryBase* TextureCache::Load(const u32 stage)
u32 full_format = texformat;
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)
full_format = texformat | (tlutfmt << 16);
@ -323,13 +307,11 @@ 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)
tex_hash = GetHash64(src_data, texture_size, g_ActiveConfig.iSafeTextureCache_ColorSamples);
u32 palette_size = 0;
u64 tlut_hash = 0;
if (isPaletteTexture)
{
palette_size = TexDecoder_GetPaletteSize(texformat);
u64 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;
tlut_hash = GetHash64(&texMem[tlutaddr], palette_size, g_ActiveConfig.iSafeTextureCache_ColorSamples);
}
// GPUs don't like when the specified mipmap count would require more than one 1x1-sized LOD in the mipmap chain
@ -364,37 +346,51 @@ TextureCache::TCacheEntryBase* TextureCache::Load(const u32 stage)
TexCache::iterator iter = iter_range.first;
TexCache::iterator oldest_entry = iter;
int temp_frameCount = 0x7fffffff;
TexCache::iterator unconverted_copy = textures.end();
while (iter != iter_range.second)
{
TCacheEntryBase* entry = iter->second;
if (entry->IsEfbCopy())
{
// For EFB copies, only the hash and the texture address need to match. Ignore the hash when
// using EFB to texture, because there's no hash in this case
if (g_ActiveConfig.bCopyEFBToTexture || entry->hash == tex_hash)
// EFB copies have slightly different rules: the hash doesn't need to match
// in EFB2Tex mode, and EFB copy formats have different meanings from texture
// 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,
// we could reinterpret the internal texture object data to the new pixel format
// (similar to what is already being done in Renderer::ReinterpretPixelFormat())
// TODO: Convert paletted textures, which are efb copies, using the right palette, so they display correctly
return ReturnEntry(stage, entry);
// TODO: We should check format/width/height/levels for EFB copies. Checking
// format is complicated because EFB copy formats don't exactly match
// texture formats. I'm not sure what effect checking width/height/levels
// would have.
if (!isPaletteTexture || !g_Config.backend_info.bSupportsPaletteConversion)
return ReturnEntry(stage, entry);
// Note that we found an unconverted EFB copy, then continue. We'll
// perform the conversion later. Currently, we only convert EFB copies to
// palette textures; we could do other conversions if it proved to be
// beneficial.
unconverted_copy = iter;
}
else
{
// Keeping an unused entry for an efb copy in the cache is pointless, because a new entry
// will be created in CopyRenderTargetToTexture
// Aggressively prune EFB copies: if it isn't useful here, it will probably
// never be useful again. It's theoretically possible for a game to do
// something weird where the copy could become useful in the future, but in
// practice it doesn't happen.
FreeTexture(entry);
iter = textures.erase(iter);
continue;
}
}
// 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)
else
{
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
@ -406,6 +402,29 @@ TextureCache::TCacheEntryBase* TextureCache::Load(const u32 stage)
++iter;
}
if (unconverted_copy != textures.end())
{
// Perform palette decoding.
TCacheEntryBase *entry = unconverted_copy->second;
TCacheEntryConfig config;
config.rendertarget = true;
config.width = entry->config.width;
config.height = entry->config.height;
config.layers = FramebufferManagerBase::GetEFBLayers();
TCacheEntryBase *decoded_entry = AllocateTexture(config);
decoded_entry->SetGeneralParameters(address, texture_size, full_format);
decoded_entry->SetDimensions(entry->native_width, entry->native_height, 1);
decoded_entry->SetHashes(tex_hash ^ tlut_hash);
decoded_entry->frameCount = FRAMECOUNT_INVALID;
decoded_entry->is_efb_copy = false;
g_texture_cache->ConvertTexture(decoded_entry, entry, &texMem[tlutaddr], (TlutFormat)tlutfmt);
textures.insert(TexCache::value_type(address, decoded_entry));
return ReturnEntry(stage, decoded_entry);
}
// If at least one entry was not used for the same frame, overwrite the oldest one
if (temp_frameCount != 0x7fffffff)
{
@ -472,7 +491,8 @@ TextureCache::TCacheEntryBase* TextureCache::Load(const u32 stage)
entry->SetGeneralParameters(address, texture_size, full_format);
entry->SetDimensions(nativeW, nativeH, tex_levels);
entry->hash = tex_hash;
entry->hash = tex_hash ^ tlut_hash;
entry->is_efb_copy = false;
// load texture
entry->Load(width, height, expandedWidth, 0);
@ -851,6 +871,7 @@ void TextureCache::CopyRenderTargetToTexture(u32 dstAddr, unsigned int dstFormat
entry->SetHashes(TEXHASH_INVALID);
entry->frameCount = FRAMECOUNT_INVALID;
entry->is_efb_copy = true;
entry->FromRenderTarget(dstAddr, dstFormat, srcFormat, srcRect, isIntensity, scaleByHalf, cbufid, colmat);

View File

@ -43,7 +43,6 @@ public:
};
};
struct TCacheEntryBase
{
const TCacheEntryConfig config;
@ -53,6 +52,7 @@ public:
u32 size_in_bytes;
u64 hash;
u32 format;
bool is_efb_copy;
unsigned int native_width, native_height; // Texture dimensions from the GameCube's point of view
unsigned int native_levels;
@ -95,7 +95,7 @@ public:
bool OverlapsMemoryRange(u32 range_address, u32 range_size) const;
bool IsEfbCopy() { return config.rendertarget; }
bool IsEfbCopy() { return is_efb_copy; }
};
virtual ~TextureCache(); // needs virtual for DX11 dtor
@ -107,9 +107,7 @@ public:
static void Cleanup(int frameCount);
static void Invalidate();
static void InvalidateRange(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;
@ -117,11 +115,15 @@ public:
virtual void DeleteShaders() = 0; // currently only implemented by OGL
static TCacheEntryBase* Load(const u32 stage);
static void UnbindTextures();
static void BindTextures();
static void CopyRenderTargetToTexture(u32 dstAddr, unsigned int dstFormat, PEControl::PixelFormat srcFormat,
const EFBRectangle& srcRect, bool isIntensity, bool scaleByHalf);
static void RequestInvalidateTextureCache();
virtual void ConvertTexture(TCacheEntryBase* entry, TCacheEntryBase* unconverted, void* palette, TlutFormat format) = 0;
protected:
TextureCache();
@ -135,11 +137,14 @@ private:
static TCacheEntryBase* AllocateTexture(const TCacheEntryConfig& config);
static void FreeTexture(TCacheEntryBase* entry);
static TCacheEntryBase* ReturnEntry(unsigned int stage, TCacheEntryBase* entry);
typedef std::multimap<u32, TCacheEntryBase*> TexCache;
typedef std::unordered_multimap<TCacheEntryConfig, TCacheEntryBase*, TCacheEntryConfig::Hasher> TexPool;
static TexCache textures;
static TexPool texture_pool;
static TCacheEntryBase* bound_textures[8];
// Backup configuration values
static struct BackupConfig

View File

@ -209,6 +209,7 @@ void VertexManager::Flush()
if (bpmem.tevind[i].IsActive() && bpmem.tevind[i].bt < bpmem.genMode.numindstages)
usedtextures[bpmem.tevindref.getTexMap(bpmem.tevind[i].bt)] = true;
TextureCache::UnbindTextures();
for (unsigned int i : usedtextures)
{
g_renderer->SetSamplerState(i & 3, i >> 2);
@ -224,6 +225,7 @@ void VertexManager::Flush()
ERROR_LOG(VIDEO, "error loading texture");
}
}
TextureCache::BindTextures();
}
// set global vertex constants

View File

@ -159,6 +159,7 @@ struct VideoConfig final
bool bSupportsBBox;
bool bSupportsGSInstancing; // Needed by GeometryShaderGen, so must stay in VideoCommon
bool bSupportsPostProcessing;
bool bSupportsPaletteConversion;
} backend_info;
// Utility