Merge pull request #5767 from hrydgard/framebuffer-clut

Color-convert framebuffers that are bound as CLUT textures
This commit is contained in:
Henrik Rydgård 2014-05-28 00:26:12 +02:00
commit 1445d5ae31
18 changed files with 661 additions and 80 deletions

View File

@ -1243,6 +1243,8 @@ add_library(GPU OBJECT
GPU/Debugger/Breakpoints.h
GPU/Debugger/Stepping.cpp
GPU/Debugger/Stepping.h
GPU/GLES/DepalettizeShader.cpp
GPU/GLES/DepalettizeShader.h
GPU/GLES/GLES_GPU.cpp
GPU/GLES/GLES_GPU.h
GPU/GLES/FragmentShaderGenerator.cpp

View File

@ -739,7 +739,7 @@ int MediaEngine::writeVideoImageWithRange(u32 bufferPtr, int frameWidth, int vid
break;
default:
ERROR_LOG(ME, "Unsupported video pixel format %d", videoPixelMode);
ERROR_LOG_REPORT(ME, "Unsupported video pixel format %d", videoPixelMode);
break;
}
return videoImageSize;

View File

@ -0,0 +1,418 @@
// Copyright (c) 2014- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include <map>
#include "base/logging.h"
#include "Common/Log.h"
#include "Core/Reporting.h"
#include "DepalettizeShader.h"
#include "GPU/GPUState.h"
#include "GPU/GLES/TextureCache.h"
#ifdef _WIN32
#define SHADERLOG
#endif
static const char *depalVShader100 =
#ifdef USING_GLES
"#version 100\n"
"precision highp float;\n"
#endif
"attribute vec4 a_position;\n"
"attribute vec2 a_texcoord0;\n"
"varying vec2 v_texcoord0;\n"
"void main() {\n"
" v_texcoord0 = a_texcoord0;\n"
" gl_Position = a_position;\n"
"}\n";
static const char *depalVShader300 =
#ifdef USING_GLES
"#version 300 es\n"
"precision highp float;\n"
#else
"#version 330\n"
#endif
"in vec4 a_position;\n"
"in vec2 a_texcoord0;\n"
"out vec2 v_texcoord0;\n"
"void main() {\n"
" v_texcoord0 = a_texcoord0;\n"
" gl_Position = a_position;\n"
"}\n";
static bool CheckShaderCompileSuccess(GLuint shader, const char *code) {
GLint success;
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
#define MAX_INFO_LOG_SIZE 2048
GLchar infoLog[MAX_INFO_LOG_SIZE];
GLsizei len;
glGetShaderInfoLog(shader, MAX_INFO_LOG_SIZE, &len, infoLog);
infoLog[len] = '\0';
#ifdef ANDROID
ELOG("Error in shader compilation! %s\n", infoLog);
ELOG("Shader source:\n%s\n", (const char *)code);
#endif
ERROR_LOG(G3D, "Error in shader compilation!\n");
ERROR_LOG(G3D, "Info log: %s\n", infoLog);
ERROR_LOG(G3D, "Shader source:\n%s\n", (const char *)code);
#ifdef SHADERLOG
OutputDebugStringUTF8(infoLog);
#endif
shader = 0;
return false;
} else {
DEBUG_LOG(G3D, "Compiled shader:\n%s\n", (const char *)code);
#ifdef SHADERLOG
OutputDebugStringUTF8(code);
#endif
return true;
}
}
DepalShaderCache::DepalShaderCache() {
// Pre-build the vertex program
bool useGL3 = gl_extensions.GLES3 || gl_extensions.VersionGEThan(3, 3);
vertexShader_ = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader_, 1, useGL3 ? &depalVShader300 : &depalVShader100, 0);
glCompileShader(vertexShader_);
if (CheckShaderCompileSuccess(vertexShader_, depalVShader100)) {
// ...
}
}
DepalShaderCache::~DepalShaderCache() {
Clear();
glDeleteShader(vertexShader_);
}
#define WRITE p+=sprintf
void GenerateDepalShader300(char *buffer, GEBufferFormat pixelFormat) {
char *p = buffer;
#ifdef USING_GLES
WRITE(p, "#version 300 es\n");
WRITE(p, "precision mediump float;\n");
#else
WRITE(p, "#version 330\n");
#endif
WRITE(p, "in vec2 v_texcoord0;\n");
WRITE(p, "out vec4 fragColor0;\n");
WRITE(p, "uniform sampler2D tex;\n");
WRITE(p, "uniform sampler2D pal;\n");
WRITE(p, "void main() {\n");
WRITE(p, " vec4 index = texture2D(tex, v_texcoord0);\n");
int mask = gstate.getClutIndexMask();
int shift = gstate.getClutIndexShift();
int offset = gstate.getClutIndexStartPos();
const GEPaletteFormat clutFormat = gstate.getClutPaletteFormat();
// Unfortunately sampling turned our texture into floating point. To avoid this, might be able
// to declare them as isampler2D objects, but these require integer textures, which needs more work.
// Anyhow, we simply work around this by converting back to integer. Hopefully there will be no loss of precision.
// Use the mask to skip reading some components.
int shiftedMask = mask << shift;
switch (pixelFormat) {
case GE_FORMAT_8888:
if (shiftedMask & 0xFF) WRITE(p, " int r = int(index.r * 255.99);\n"); else WRITE(p, " int r = 0;\n");
if (shiftedMask & 0xFF00) WRITE(p, " int g = int(index.g * 255.99);\n"); else WRITE(p, " int g = 0;\n");
if (shiftedMask & 0xFF0000) WRITE(p, " int b = int(index.b * 255.99);\n"); else WRITE(p, " int b = 0;\n");
if (shiftedMask & 0xFF000000) WRITE(p, " int a = int(index.a * 255.99);\n"); else WRITE(p, " int a = 0;\n");
WRITE(p, " int color = (a << 24) | (b << 16) | (g << 8) | (r);\n");
break;
case GE_FORMAT_4444:
if (shiftedMask & 0xF) WRITE(p, " int r = int(index.r * 15.99);\n"); else WRITE(p, " int r = 0;\n");
if (shiftedMask & 0xF0) WRITE(p, " int g = int(index.g * 15.99);\n"); else WRITE(p, " int g = 0;\n");
if (shiftedMask & 0xF00) WRITE(p, " int b = int(index.b * 15.99);\n"); else WRITE(p, " int b = 0;\n");
if (shiftedMask & 0xF000) WRITE(p, " int a = int(index.a * 15.99);\n"); else WRITE(p, " int a = 0;\n");
WRITE(p, " int color = (a << 12) | (b << 8) | (g << 4) | (r);\n");
break;
case GE_FORMAT_565:
if (shiftedMask & 0x1F) WRITE(p, " int r = int(index.r * 31.99);\n"); else WRITE(p, " int r = 0;\n");
if (shiftedMask & 0x7E0) WRITE(p, " int g = int(index.g * 63.99);\n"); else WRITE(p, " int g = 0;\n");
if (shiftedMask & 0xF800) WRITE(p, " int b = int(index.b * 31.99);\n"); else WRITE(p, " int b = 0;\n");
WRITE(p, " int color = (b << 11) | (g << 5) | (r);");
break;
case GE_FORMAT_5551:
if (shiftedMask & 0x1F) WRITE(p, " int r = int(index.r * 31.99);\n"); else WRITE(p, " int r = 0;\n");
if (shiftedMask & 0x3E0) WRITE(p, " int g = int(index.g * 31.99);\n"); else WRITE(p, " int g = 0;\n");
if (shiftedMask & 0x7C00) WRITE(p, " int b = int(index.b * 31.99);\n"); else WRITE(p, " int b = 0;\n");
if (shiftedMask & 0x8000) WRITE(p, " int a = int(index.a);\n"); else WRITE(p, " int a = 0;\n");
WRITE(p, " int color = (a << 15) | (b << 10) | (g << 5) | (r);");
break;
}
float texturePixels = 256;
if (clutFormat != GE_CMODE_32BIT_ABGR8888)
texturePixels = 512;
WRITE(p, " color = ((color >> %i) & 0x%02x) | %i;\n", shift, mask, offset); // '|' matches what we have in gstate.h
WRITE(p, " fragColor0 = texture2D(pal, vec2((floor(float(color)) + 0.5) * (1.0 / %f), 0.0));\n", texturePixels);
WRITE(p, "}\n");
}
void GenerateDepalShader100(char *buffer, GEBufferFormat pixelFormat) {
char *p = buffer;
char lookupMethod[128] = "index.r";
char offset[128] = "";
const GEPaletteFormat clutFormat = gstate.getClutPaletteFormat();
const u32 clutBase = gstate.getClutIndexStartPos();
int shift = gstate.getClutIndexShift();
int mask = gstate.getClutIndexMask();
float multiplier = 1.0f;
// pixelformat is the format of the texture we are sampling.
bool formatOK = true;
switch (pixelFormat) {
case GE_FORMAT_8888:
if ((mask & 0xF) == 0xF) {
switch (shift) { // bgra?
case 0: strcpy(lookupMethod, "index.r"); break;
case 4: strcpy(lookupMethod, "index.r"); multiplier = (1.0f / 16.0f); break;
case 8: strcpy(lookupMethod, "index.g"); break;
case 12: strcpy(lookupMethod, "index.g"); multiplier = (1.0f / 16.0f); break;
case 16: strcpy(lookupMethod, "index.b"); break;
case 20: strcpy(lookupMethod, "index.b"); multiplier = (1.0f / 16.0f); break;
case 24: strcpy(lookupMethod, "index.a"); break;
case 28: strcpy(lookupMethod, "index.a"); multiplier = (1.0f / 16.0f); break;
default:
formatOK = false;
}
} else {
formatOK = false;
}
break;
case GE_FORMAT_4444:
if ((mask & 0xF) == 0xF) {
switch (shift) { // bgra?
case 0: strcpy(lookupMethod, "index.r"); break;
case 4: strcpy(lookupMethod, "index.g"); break;
case 8: strcpy(lookupMethod, "index.b"); break;
case 12: strcpy(lookupMethod, "index.a"); break;
default:
formatOK = false;
}
multiplier = 1.0f / 16.0f;
} else {
formatOK = false;
}
break;
case GE_FORMAT_565:
if ((mask & 0x3f) == 0x3F) {
switch (shift) { // bgra?
case 0: strcpy(lookupMethod, "index.r"); multiplier = 1.0f / 32.0f; break;
case 5: strcpy(lookupMethod, "index.g"); multiplier = 1.0f / 64.0f; break;
case 11: strcpy(lookupMethod, "index.b"); multiplier = 1.0f / 32.0f; break;
default:
formatOK = false;
}
} else {
formatOK = false;
}
break;
case GE_FORMAT_5551:
if ((mask & 0x1F) == 0x1F) {
switch (shift) { // bgra?
case 0: strcpy(lookupMethod, "index.r"); multiplier = 1.0f / 32.0f; break;
case 5: strcpy(lookupMethod, "index.g"); multiplier = 1.0f / 32.0f; break;
case 10: strcpy(lookupMethod, "index.b"); multiplier = 1.0f / 32.0f; break;
case 15: strcpy(lookupMethod, "index.a"); multiplier = 1.0f / 256.0f; break;
default:
formatOK = false;
}
} else {
formatOK = false;
}
break;
}
float texturePixels = 256.f;
if (clutFormat != GE_CMODE_32BIT_ABGR8888) {
texturePixels = 512.f;
multiplier *= 0.5f;
}
if (!formatOK) {
ERROR_LOG_REPORT_ONCE(depal, G3D, "%i depal unsupported: shift=%i mask=%02x offset=%i", pixelFormat, shift, mask, offset);
}
// Offset by half a texel (plus clutBase) to turn NEAREST filtering into FLOOR.
sprintf(offset, " + %f", (float)clutBase / texturePixels - 0.5f / texturePixels);
#ifdef USING_GLES
WRITE(p, "#version 100\n");
WRITE(p, "precision mediump float;\n");
#else
WRITE(p, "#version 110\n");
#endif
WRITE(p, "varying vec2 v_texcoord0;\n");
WRITE(p, "uniform sampler2D tex;\n");
WRITE(p, "uniform sampler2D pal;\n");
WRITE(p, "void main() {\n");
WRITE(p, " vec4 index = texture2D(tex, v_texcoord0);\n");
WRITE(p, " gl_FragColor = texture2D(pal, vec2((%s * %f)%s, 0.0));\n", lookupMethod, multiplier, offset);
WRITE(p, "}\n");
}
#undef WRITE
u32 DepalShaderCache::GenerateShaderID(GEBufferFormat pixelFormat) {
return (gstate.clutformat & 0xFFFFFF) | (pixelFormat << 24);
}
GLuint DepalShaderCache::GetClutTexture(const u32 clutID, u32 *rawClut) {
GEPaletteFormat palFormat = gstate.getClutPaletteFormat();
const u32 realClutID = clutID ^ palFormat;
auto oldtex = texCache_.find(realClutID);
if (oldtex != texCache_.end()) {
return oldtex->second->texture;
}
GLuint dstFmt = getClutDestFormat(palFormat);
int texturePixels = palFormat == GE_CMODE_32BIT_ABGR8888 ? 256 : 512;
bool useBGRA = UseBGRA8888() && dstFmt == GL_UNSIGNED_BYTE;
DepalTexture *tex = new DepalTexture();
glGenTextures(1, &tex->texture);
glBindTexture(GL_TEXTURE_2D, tex->texture);
GLuint components = dstFmt == GL_UNSIGNED_SHORT_5_6_5 ? GL_RGB : GL_RGBA;
GLuint components2 = components;
#if defined(MAY_HAVE_GLES3)
if (useBGRA) {
components2 = GL_BGRA_EXT;
}
#endif
glTexImage2D(GL_TEXTURE_2D, 0, components, texturePixels, 1, 0, components2, dstFmt, (void *)rawClut);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
texCache_[realClutID] = tex;
return tex->texture;
}
void DepalShaderCache::Clear() {
for (auto shader = cache_.begin(); shader != cache_.end(); ++shader) {
glDeleteShader(shader->second->fragShader);
glDeleteProgram(shader->second->program);
delete shader->second;
}
cache_.clear();
for (auto tex = texCache_.begin(); tex != texCache_.end(); ++tex) {
glDeleteTextures(1, &tex->second->texture);
delete tex->second;
}
texCache_.clear();
}
void DepalShaderCache::Decimate() {
// TODO
}
GLuint DepalShaderCache::GetDepalettizeShader(GEBufferFormat pixelFormat) {
u32 id = GenerateShaderID(pixelFormat);
bool useGL3 = gl_extensions.GLES3 || gl_extensions.VersionGEThan(3, 3);
auto shader = cache_.find(id);
if (shader != cache_.end()) {
return shader->second->program;
}
char *buffer = new char[2048];
if (useGL3) {
GenerateDepalShader300(buffer, pixelFormat);
} else {
GenerateDepalShader100(buffer, pixelFormat);
}
GLuint fragShader = glCreateShader(GL_FRAGMENT_SHADER);
const char *buf = buffer;
glShaderSource(fragShader, 1, &buf, 0);
glCompileShader(fragShader);
CheckShaderCompileSuccess(fragShader, buffer);
GLuint program = glCreateProgram();
glAttachShader(program, vertexShader_);
glAttachShader(program, fragShader);
glBindAttribLocation(program, 0, "a_position");
glBindAttribLocation(program, 1, "a_texcoord0");
if (useGL3) {
// This call is not really necessary, I think.
#ifndef MOBILE_DEVICE
glBindFragDataLocation(program, 0, "fragColor0");
#endif
}
glLinkProgram(program);
glUseProgram(program);
GLint u_tex = glGetUniformLocation(program, "tex");
GLint u_pal = glGetUniformLocation(program, "pal");
glUniform1i(u_tex, 0);
glUniform1i(u_pal, 1);
GLint linkStatus = GL_FALSE;
glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
if (linkStatus != GL_TRUE) {
GLint bufLength = 0;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength);
if (bufLength) {
char* errorbuf = new char[bufLength];
glGetProgramInfoLog(program, bufLength, NULL, errorbuf);
#ifdef SHADERLOG
OutputDebugStringUTF8(buffer);
OutputDebugStringUTF8(errorbuf);
#endif
ERROR_LOG(G3D, "Could not link program:\n %s \n\n %s", errorbuf, buf);
delete[] errorbuf; // we're dead!
}
delete[] buffer;
return 0;
}
DepalShader *depal = new DepalShader();
depal->program = program;
depal->fragShader = fragShader;
cache_[id] = depal;
delete[] buffer;
return depal->program;
}

View File

@ -0,0 +1,54 @@
// Copyright (c) 2014- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include <map>
#include "Common/CommonTypes.h"
#include "gfx_es2/gl_state.h"
#include "GPU/ge_constants.h"
class DepalShader {
public:
GLuint program;
GLuint fragShader;
};
class DepalTexture {
public:
GLuint texture;
};
// Caches both shaders and palette textures.
class DepalShaderCache {
public:
DepalShaderCache();
~DepalShaderCache();
// This also uploads the palette and binds the correct texture.
GLuint GetDepalettizeShader(GEBufferFormat pixelFormat);
GLuint GetClutTexture(const u32 clutHash, u32 *rawClut);
void Clear();
void Decimate();
private:
u32 GenerateShaderID(GEBufferFormat pixelFormat);
GLuint vertexShader_;
std::map<u32, DepalShader *> cache_;
std::map<u32, DepalTexture *> texCache_;
};

View File

@ -610,6 +610,7 @@ void FramebufferManager::DrawActiveTexture(GLuint texture, float x, float y, flo
shaderManager_->DirtyLastShader(); // dirty lastShader_
}
VirtualFramebuffer *FramebufferManager::GetVFBAt(u32 addr) {
VirtualFramebuffer *match = NULL;
for (size_t i = 0; i < vfbs_.size(); ++i) {
@ -684,6 +685,10 @@ void FramebufferManager::DestroyFramebuf(VirtualFramebuffer *v) {
delete v;
}
void FramebufferManager::RebindFramebuffer() {
fbo_bind_as_render_target(currentRenderVfb_->fbo);
}
void FramebufferManager::DoSetRenderFrameBuffer() {
/*
if (useBufferedRendering_ && currentRenderVfb_) {
@ -1009,7 +1014,7 @@ void FramebufferManager::BindFramebufferDepth(VirtualFramebuffer *sourceframebuf
}
}
void FramebufferManager::BindFramebufferColor(VirtualFramebuffer *framebuffer) {
void FramebufferManager::BindFramebufferColor(VirtualFramebuffer *framebuffer, bool skipCopy) {
if (framebuffer == NULL) {
framebuffer = currentRenderVfb_;
}
@ -1022,7 +1027,7 @@ void FramebufferManager::BindFramebufferColor(VirtualFramebuffer *framebuffer) {
// currentRenderVfb_ will always be set when this is called, except from the GE debugger.
// Let's just not bother with the copy in that case.
if (currentRenderVfb_ && MaskedEqual(framebuffer->fb_address, gstate.getFrameBufRawAddress())) {
if (!skipCopy && currentRenderVfb_ && MaskedEqual(framebuffer->fb_address, gstate.getFrameBufRawAddress())) {
#ifndef USING_GLES2
if (gl_extensions.FBO_ARB) {
bool useNV = false;
@ -1400,14 +1405,6 @@ void FramebufferManager::BlitFramebuffer_(VirtualFramebuffer *dst, int dstX, int
fbo_unbind();
}
static inline bool UseBGRA8888() {
// TODO: Other platforms? May depend on vendor which is faster?
#ifdef _WIN32
return gl_extensions.EXT_bgra;
#endif
return false;
}
// TODO: SSE/NEON
// Could also make C fake-simd for 64-bit, two 8888 pixels fit in a register :)
void ConvertFromRGBA8888(u8 *dst, const u8 *src, u32 stride, u32 height, GEBufferFormat format) {

View File

@ -86,6 +86,7 @@ struct VirtualFramebuffer {
GEBufferFormat format; // virtual, right now they are all RGBA8888
FBOColorDepth colorDepth;
FBO *fbo;
FBO *depalFBO;
bool dirtyAfterDisplay;
bool reallyDirtyAfterDisplay; // takes frame skipping into account
@ -161,7 +162,7 @@ public:
void BindFramebufferDepth(VirtualFramebuffer *sourceframebuffer, VirtualFramebuffer *targetframebuffer);
// For use when texturing from a framebuffer. May create a duplicate if target.
void BindFramebufferColor(VirtualFramebuffer *framebuffer);
void BindFramebufferColor(VirtualFramebuffer *framebuffer, bool skipCopy = false);
// Returns true if it's sure this is a direct FBO->FBO transfer and it has already handle it.
// In that case we hardly need to actually copy the bytes in VRAM, they will be wrong anyway (unless
@ -219,6 +220,8 @@ public:
bool GetCurrentDepthbuffer(GPUDebugBuffer &buffer);
bool GetCurrentStencilbuffer(GPUDebugBuffer &buffer);
void RebindFramebuffer();
private:
void CompileDraw2DProgram();
void DestroyDraw2DProgram();

View File

@ -410,6 +410,7 @@ GLES_GPU::GLES_GPU()
framebufferManager_.SetTextureCache(&textureCache_);
framebufferManager_.SetShaderManager(shaderManager_);
textureCache_.SetFramebufferManager(&framebufferManager_);
textureCache_.SetDepalShaderCache(&depalShaderCache_);
// Sanity check gstate
if ((int *)&gstate.transferstart - (int *)&gstate != 0xEA) {
@ -459,6 +460,7 @@ GLES_GPU::GLES_GPU()
GLES_GPU::~GLES_GPU() {
framebufferManager_.DestroyAllFBOs();
shaderManager_->ClearCache(true);
depalShaderCache_.Clear();
delete shaderManager_;
}
@ -495,6 +497,7 @@ void GLES_GPU::DeviceLost() {
// TransformDraw has registered as a GfxResourceHolder.
shaderManager_->ClearCache(false);
textureCache_.Clear(false);
depalShaderCache_.Clear();
framebufferManager_.DeviceLost();
}
@ -2069,6 +2072,7 @@ void GLES_GPU::DoState(PointerWrap &p) {
// In Freeze-Frame mode, we don't want to do any of this.
if (p.mode == p.MODE_READ && !PSP_CoreParameter().frozen) {
textureCache_.Clear(true);
depalShaderCache_.Clear();
transformDraw_.ClearTrackedVertexArrays();
gstate_c.textureChanged = TEXCHANGE_UPDATED;

View File

@ -26,6 +26,7 @@
#include "GPU/GLES/Framebuffer.h"
#include "GPU/GLES/TransformPipeline.h"
#include "GPU/GLES/TextureCache.h"
#include "GPU/GLES/DepalettizeShader.h"
class ShaderManager;
class LinkedShader;
@ -162,6 +163,7 @@ private:
FramebufferManager framebufferManager_;
TextureCache textureCache_;
DepalShaderCache depalShaderCache_;
TransformDrawEngine transformDraw_;
ShaderManager *shaderManager_;

View File

@ -684,10 +684,11 @@ Shader *ShaderManager::ApplyVertexShader(int prim, u32 vertType) {
LinkedShader *ShaderManager::ApplyFragmentShader(Shader *vs, int prim, u32 vertType) {
FragmentShaderID FSID;
ComputeFragmentShaderID(&FSID);
if (lastVShaderSame_ && FSID == lastFSID_) {
if (lastVShaderSame_ && FSID == lastFSID_ && !gstate_c.shaderChanged) {
lastShader_->UpdateUniforms(vertType);
return lastShader_;
}
gstate_c.shaderChanged = false;
lastFSID_ = FSID;

View File

@ -444,9 +444,6 @@ void TransformDrawEngine::ApplyDrawState(int prim) {
amask = false;
}
}
if (g_Config.bAlphaMaskHack) {
amask = true; // Yes, this makes no sense, but it "fixes" the 3rd Birthday by popular demand.
}
glstate.colorMask.set(rmask, gmask, bmask, amask);

View File

@ -27,6 +27,7 @@
#include "GPU/GLES/TextureCache.h"
#include "GPU/GLES/Framebuffer.h"
#include "GPU/GLES/FragmentShaderGenerator.h"
#include "GPU/GLES/DepalettizeShader.h"
#include "GPU/Common/TextureDecoder.h"
#include "Core/Config.h"
#include "Core/Host.h"
@ -57,14 +58,6 @@
extern int g_iNumVideos;
static inline bool UseBGRA8888() {
// TODO: Other platforms? May depend on vendor which is faster?
#ifdef _WIN32
return gl_extensions.EXT_bgra;
#endif
return false;
}
TextureCache::TextureCache() : clearCacheNextFrame_(false), lowMemoryMode_(false), clutBuf_(NULL) {
lastBoundTexture = -1;
decimationCounter_ = TEXCACHE_DECIMATION_INTERVAL;
@ -72,8 +65,16 @@ TextureCache::TextureCache() : clearCacheNextFrame_(false), lowMemoryMode_(false
tmpTexBuf32.resize(1024 * 512); // 2MB
tmpTexBuf16.resize(1024 * 512); // 1MB
tmpTexBufRearrange.resize(1024 * 512); // 2MB
// Aren't these way too big?
clutBufConverted_ = (u32 *)AllocateAlignedMemory(4096 * sizeof(u32), 16); // 16KB
clutBufRaw_ = (u32 *)AllocateAlignedMemory(4096 * sizeof(u32), 16); // 16KB
// Zap these so that reads from uninitialized parts of the CLUT look the same in
// release and debug
memset(clutBufConverted_, 0, 4096 * sizeof(u32));
memset(clutBufRaw_, 0, 4096 * sizeof(u32));
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAnisotropyLevel);
SetupTextureDecoder();
}
@ -117,6 +118,9 @@ void TextureCache::Decimate() {
for (TexCache::iterator iter = cache.begin(); iter != cache.end(); ) {
if (iter->second.lastFrame + killAge < gpuStats.numFlips) {
glDeleteTextures(1, &iter->second.texture);
if (iter->second.depalFBO) {
fbo_destroy(iter->second.depalFBO);
}
cache.erase(iter++);
} else {
++iter;
@ -128,6 +132,9 @@ void TextureCache::Decimate() {
// In low memory mode, we kill them all.
if (lowMemoryMode_ || iter->second.lastFrame + TEXTURE_SECOND_KILL_AGE < gpuStats.numFlips) {
glDeleteTextures(1, &iter->second.texture);
if (iter->second.depalFBO) {
fbo_destroy(iter->second.depalFBO);
}
secondCache.erase(iter++);
} else {
++iter;
@ -201,6 +208,7 @@ inline void AttachFramebufferValid(T &entry, VirtualFramebuffer *framebuffer) {
if (hasInvalidFramebuffer || hasOlderFramebuffer) {
entry->framebuffer = framebuffer;
entry->invalidHint = 0;
entry->status &= ~TextureCache::TexCacheEntry::STATUS_DEPALETTIZE;
host->GPUNotifyTextureAttachment(entry->addr);
}
}
@ -210,11 +218,12 @@ inline void AttachFramebufferInvalid(T &entry, VirtualFramebuffer *framebuffer)
if (entry->framebuffer == 0 || entry->framebuffer == framebuffer) {
entry->framebuffer = framebuffer;
entry->invalidHint = -1;
entry->status &= ~TextureCache::TexCacheEntry::STATUS_DEPALETTIZE;
host->GPUNotifyTextureAttachment(entry->addr);
}
}
inline void TextureCache::AttachFramebuffer(TexCacheEntry *entry, u32 address, VirtualFramebuffer *framebuffer, bool exactMatch) {
void TextureCache::AttachFramebuffer(TexCacheEntry *entry, u32 address, VirtualFramebuffer *framebuffer, bool exactMatch) {
// If they match exactly, it's non-CLUT and from the top left.
if (exactMatch) {
// Apply to non-buffered and buffered mode only.
@ -237,22 +246,41 @@ inline void TextureCache::AttachFramebuffer(TexCacheEntry *entry, u32 address, V
if (!(g_Config.iRenderingMode == FB_BUFFERED_MODE))
return;
// 3rd Birthday (and possibly other games) render to a 16 bit clut texture.
const bool compatFormat = framebuffer->format == entry->format
|| (framebuffer->format == GE_FORMAT_8888 && entry->format == GE_TFMT_CLUT32)
|| (framebuffer->format != GE_FORMAT_8888 && entry->format == GE_TFMT_CLUT16);
// Check for CLUT. The framebuffer is always RGB, but it can be interpreted as a CLUT texture.
// 3rd Birthday (and a bunch of other games) render to a 16 bit clut texture.
bool clutSuccess = false;
if (((framebuffer->format == GE_FORMAT_8888 && entry->format == GE_TFMT_CLUT32) ||
(framebuffer->format != GE_FORMAT_8888 && entry->format == GE_TFMT_CLUT16))) {
AttachFramebufferValid(entry, framebuffer);
entry->status |= TexCacheEntry::STATUS_DEPALETTIZE;
// We'll validate it later.
clutSuccess = true;
} else if (entry->format == GE_TFMT_CLUT8 || entry->format == GE_TFMT_CLUT4) {
ERROR_LOG_REPORT_ONCE(fourEightBit, G3D, "4 and 8-bit CLUT format not supported for framebuffers");
}
// Is it at least the right stride?
if (framebuffer->fb_stride == entry->bufw && compatFormat) {
if (framebuffer->format != entry->format) {
WARN_LOG_REPORT_ONCE(diffFormat2, G3D, "Render to texture with different formats %d != %d at %08x", entry->format, framebuffer->format, address);
// TODO: Use an FBO to translate the palette?
AttachFramebufferValid(entry, framebuffer);
} else if ((entry->addr - address) / entry->bufw < framebuffer->height) {
WARN_LOG_REPORT_ONCE(subarea, G3D, "Render to area containing texture at %08x", address);
// TODO: Keep track of the y offset.
// If "AttachFramebufferValid" , God of War Ghost of Sparta/Chains of Olympus will be missing special effect.
AttachFramebufferInvalid(entry, framebuffer);
if (!clutSuccess) {
// This is either normal or we failed to generate a shader to depalettize
const bool compatFormat = framebuffer->format == entry->format ||
(framebuffer->format == GE_FORMAT_8888 && entry->format == GE_TFMT_CLUT32) ||
(framebuffer->format != GE_FORMAT_8888 && entry->format == GE_TFMT_CLUT16);
// Is it at least the right stride?
if (framebuffer->fb_stride == entry->bufw) {
if (compatFormat) {
if (framebuffer->format != entry->format) {
WARN_LOG_REPORT_ONCE(diffFormat2, G3D, "Render to texture with different formats %d != %d at %08x", entry->format, framebuffer->format, address);
// TODO: Use an FBO to translate the palette?
AttachFramebufferValid(entry, framebuffer);
} else if ((entry->addr - address) / entry->bufw < framebuffer->height) {
WARN_LOG_REPORT_ONCE(subarea, G3D, "Render to area containing texture at %08x", address);
// TODO: Keep track of the y offset.
// If "AttachFramebufferValid" , God of War Ghost of Sparta/Chains of Olympus will be missing special effect.
AttachFramebufferInvalid(entry, framebuffer);
}
} else {
WARN_LOG_REPORT_ONCE(diffFormat2, G3D, "Render to texture with incompatible formats %d != %d at %08x", entry->format, framebuffer->format, address);
}
}
}
}
@ -495,6 +523,11 @@ void TextureCache::UpdateSamplingParams(TexCacheEntry &entry, bool force) {
bool sClamp = gstate.isTexCoordClampedS();
bool tClamp = gstate.isTexCoordClampedT();
if (entry.status & TexCacheEntry::STATUS_TEXPARAM_DIRTY) {
entry.status &= ~TexCacheEntry::STATUS_TEXPARAM_DIRTY;
force = true;
}
bool noMip = (gstate.texlevel & 0xFFFFFF) == 0x000001 || (gstate.texlevel & 0xFFFFFF) == 0x100001 ; // Fix texlevel at 0
if (entry.maxLevel == 0) {
@ -578,7 +611,6 @@ void TextureCache::UpdateSamplingParams(TexCacheEntry &entry, bool force) {
static void ConvertColors(void *dstBuf, const void *srcBuf, GLuint dstFmt, int numPixels) {
const u32 *src = (const u32 *)srcBuf;
u32 *dst = (u32 *)dstBuf;
// TODO: NEON.
switch (dstFmt) {
case GL_UNSIGNED_SHORT_4_4_4_4:
{
@ -601,6 +633,7 @@ static void ConvertColors(void *dstBuf, const void *srcBuf, GLuint dstFmt, int n
int i = sseChunks * 8 / 2;
#else
int i = 0;
// TODO: NEON.
#endif
for (; i < (numPixels + 1) / 2; i++) {
u32 c = src[i];
@ -633,6 +666,7 @@ static void ConvertColors(void *dstBuf, const void *srcBuf, GLuint dstFmt, int n
int i = sseChunks * 8 / 2;
#else
int i = 0;
// TODO: NEON.
#endif
for (; i < (numPixels + 1) / 2; i++) {
u32 c = src[i];
@ -662,6 +696,7 @@ static void ConvertColors(void *dstBuf, const void *srcBuf, GLuint dstFmt, int n
int i = sseChunks * 8 / 2;
#else
int i = 0;
// TODO: NEON.
#endif
for (; i < (numPixels + 1) / 2; i++) {
u32 c = src[i];
@ -723,37 +758,6 @@ static inline u32 MiniHash(const u32 *ptr) {
return ptr[0];
}
static inline u32 QuickClutHash(const u8 *clut, u32 bytes) {
// CLUTs always come in multiples of 32 bytes, can't load them any other way.
_dbg_assert_msg_(G3D, (bytes & 31) == 0, "CLUT should always have a multiple of 32 bytes.");
const u32 prime = 2246822519U;
u32 hash = 0;
#ifdef _M_SSE
if ((((u32)(intptr_t)clut) & 0xf) == 0) {
__m128i cursor = _mm_set1_epi32(0);
const __m128i mult = _mm_set1_epi32(prime);
const __m128i *p = (const __m128i *)clut;
for (u32 i = 0; i < bytes / 16; ++i) {
cursor = _mm_add_epi32(cursor, _mm_mul_epu32(_mm_load_si128(&p[i]), mult));
}
// Add the four parts into the low i32.
cursor = _mm_add_epi32(cursor, _mm_srli_si128(cursor, 8));
cursor = _mm_add_epi32(cursor, _mm_srli_si128(cursor, 4));
hash = _mm_cvtsi128_si32(cursor);
} else {
#else
// TODO: ARM NEON implementation (using CPUDetect to be sure it has NEON.)
{
#endif
for (const u32 *p = (u32 *)clut, *end = (u32 *)(clut + bytes); p < end; ) {
hash += *p++ * prime;
}
}
return hash;
}
static inline u32 QuickTexHash(u32 addr, int bufw, int w, int h, GETextureFormat format) {
const u32 sizeInRAM = (textureBitsPerPixel[format] * bufw * h) / 8;
const u32 *checkp = (const u32 *) Memory::GetPointer(addr);
@ -821,7 +825,8 @@ void TextureCache::UpdateCurrentClut() {
clutAlphaLinear_ = false;
break;
}
// Alpha 0 doesn't matter.
// Alpha 0 doesn't matter.
// TODO: Well, depending on blend mode etc, it can actually matter, although unlikely.
if (i != 0 && (clut[i] & 0xFFF0) != clutAlphaLinearColor_) {
clutAlphaLinear_ = false;
break;
@ -887,7 +892,73 @@ void TextureCache::SetTextureFramebuffer(TexCacheEntry *entry) {
entry->framebuffer->usageFlags |= FB_USAGE_TEXTURE;
bool useBufferedRendering = g_Config.iRenderingMode != FB_NON_BUFFERED_MODE;
if (useBufferedRendering) {
framebufferManager_->BindFramebufferColor(entry->framebuffer);
GLuint program = 0;
if (entry->status & TexCacheEntry::STATUS_DEPALETTIZE) {
program = depalShaderCache_->GetDepalettizeShader(entry->framebuffer->format);
}
if (program) {
GLuint clutTexture = depalShaderCache_->GetClutTexture(clutHash_, clutBuf_);
if (!entry->depalFBO) {
entry->depalFBO = fbo_create(entry->framebuffer->renderWidth, entry->framebuffer->renderHeight, 1, false, FBO_8888);
}
fbo_bind_as_render_target(entry->depalFBO);
static const float pos[12] = {
-1, -1, -1,
1, -1, -1,
1, 1, -1,
-1, 1, -1
};
static const float uv[8] = {
0, 0,
1, 0,
1, 1,
0, 1,
};
static const GLubyte indices[4] = { 0, 1, 3, 2 };
glUseProgram(program);
gstate_c.shaderChanged = true;
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, clutTexture);
glActiveTexture(GL_TEXTURE0);
framebufferManager_->BindFramebufferColor(entry->framebuffer, true);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
entry->status |= TexCacheEntry::STATUS_TEXPARAM_DIRTY;
glDisable(GL_BLEND);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glDisable(GL_SCISSOR_TEST);
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
glDisable(GL_STENCIL_TEST);
#if !defined(USING_GLES2)
glDisable(GL_LOGIC_OP);
#endif
glViewport(0, 0, entry->framebuffer->renderWidth, entry->framebuffer->renderHeight);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 12, pos);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 8, uv);
glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, indices);
/*
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
*/
fbo_bind_color_as_texture(entry->depalFBO, 0);
glstate.Restore();
framebufferManager_->RebindFramebuffer();
} else {
entry->status &= ~TexCacheEntry::STATUS_DEPALETTIZE;
framebufferManager_->BindFramebufferColor(entry->framebuffer);
}
// Keep the framebuffer alive.
entry->framebuffer->last_frame_used = gpuStats.numFlips;
@ -897,7 +968,7 @@ void TextureCache::SetTextureFramebuffer(TexCacheEntry *entry) {
gstate_c.curTextureHeight = entry->framebuffer->height;
gstate_c.flipTexture = true;
gstate_c.textureFullAlpha = entry->framebuffer->format == GE_FORMAT_565;
gstate_c.textureSimpleAlpha = false;
gstate_c.textureSimpleAlpha = gstate_c.textureFullAlpha;
UpdateSamplingParams(*entry, true);
} else {
if (entry->framebuffer->fbo)
@ -1149,6 +1220,7 @@ void TextureCache::SetTexture(bool force) {
entry->framebuffer = 0;
entry->maxLevel = maxLevel;
entry->lodBias = 0.0f;
entry->depalFBO = 0;
entry->dim = gstate.getTextureDimension(0);
entry->bufw = bufw;

View File

@ -17,14 +17,17 @@
#pragma once
#include "../Globals.h"
#include "gfx_es2/fbo.h"
#include "gfx_es2/gpu_features.h"
#include "Globals.h"
#include "GPU/GPUInterface.h"
#include "GPU/GPUState.h"
#include "TextureScaler.h"
#include "GPU/GLES/TextureScaler.h"
struct VirtualFramebuffer;
class FramebufferManager;
class DepalShaderCache;
enum TextureFiltering {
AUTO = 1,
@ -39,6 +42,14 @@ enum FramebufferNotification {
NOTIFY_FB_DESTROYED,
};
inline bool UseBGRA8888() {
// TODO: Other platforms? May depend on vendor which is faster?
#ifdef _WIN32
return gl_extensions.EXT_bgra;
#endif
return false;
}
class TextureCache {
public:
TextureCache();
@ -60,6 +71,9 @@ public:
void SetFramebufferManager(FramebufferManager *fbManager) {
framebufferManager_ = fbManager;
}
void SetDepalShaderCache(DepalShaderCache *dpCache) {
depalShaderCache_ = dpCache;
}
size_t NumLoadedTextures() const {
return cache.size();
@ -73,7 +87,6 @@ public:
// Only used by Qt UI?
bool DecodeTexture(u8 *output, GPUgstate state);
private:
// Wow this is starting to grow big. Soon need to start looking at resizing it.
// Must stay a POD.
struct TexCacheEntry {
@ -93,6 +106,9 @@ private:
STATUS_CHANGE_FREQUENT = 0x10, // Changes often (less than 15 frames in between.)
STATUS_CLUT_RECHECK = 0x20, // Another texture with same addr had a hashfail.
STATUS_DEPALETTIZE = 0x40,
STATUS_DEPALETTIZE_DIRTY = 0x80,
STATUS_TEXPARAM_DIRTY = 0x100
};
// Status, but int so we can zero initialize.
@ -100,6 +116,7 @@ private:
u32 addr;
u32 hash;
VirtualFramebuffer *framebuffer; // if null, not sourced from an FBO.
FBO *depalFBO;
u32 sizeInRAM;
int lastFrame;
int numFrames;
@ -137,6 +154,7 @@ private:
bool Matches(u16 dim2, u8 format2, int maxLevel2);
};
private:
void Decimate(); // Run this once per frame to get rid of old textures.
void *UnswizzleFromMem(const u8 *texptr, u32 bufw, u32 bytesPerPixel, u32 level);
void *ReadIndexedTex(int level, const u8 *texptr, int bytesPerIndex, GLuint dstFmt, int bufw);
@ -184,5 +202,7 @@ private:
int decimationCounter_;
FramebufferManager *framebufferManager_;
DepalShaderCache *depalShaderCache_;
};
GLenum getClutDestFormat(GEPaletteFormat format);

View File

@ -190,6 +190,7 @@
<ClInclude Include="Directx9\VertexShaderGeneratorDX9.h" />
<ClInclude Include="ge_constants.h" />
<ClInclude Include="GeDisasm.h" />
<ClInclude Include="GLES\DepalettizeShader.h" />
<ClInclude Include="GLES\FragmentShaderGenerator.h" />
<ClInclude Include="GLES\Framebuffer.h" />
<ClInclude Include="GLES\GLES_GPU.h" />
@ -242,6 +243,7 @@
<ClCompile Include="Directx9\VertexDecoderDX9.cpp" />
<ClCompile Include="Directx9\VertexShaderGeneratorDX9.cpp" />
<ClCompile Include="GeDisasm.cpp" />
<ClCompile Include="GLES\DepalettizeShader.cpp" />
<ClCompile Include="GLES\FragmentShaderGenerator.cpp" />
<ClCompile Include="GLES\Framebuffer.cpp" />
<ClCompile Include="GLES\GLES_GPU.cpp" />

View File

@ -165,6 +165,9 @@
<ClInclude Include="Common\TransformCommon.h">
<Filter>Common</Filter>
</ClInclude>
<ClInclude Include="GLES\DepalettizeShader.h">
<Filter>GLES</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="Math3D.cpp">
@ -308,8 +311,11 @@
<ClCompile Include="Common\TransformCommon.cpp">
<Filter>Common</Filter>
</ClCompile>
<ClCompile Include="GLES\DepalettizeShader.cpp">
<Filter>GLES</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="CMakeLists.txt" />
</ItemGroup>
</Project>
</Project>

View File

@ -453,6 +453,8 @@ struct GPUStateCache
bool textureSimpleAlpha;
bool vertexFullAlpha;
bool framebufChanged;
// Doesn't need savestating.
bool shaderChanged;
int skipDrawReason;

View File

@ -46,6 +46,7 @@ SOURCES += $$P/Core/*.cpp \ # Core
$$P/GPU/GPUState.cpp \
$$P/GPU/Math3D.cpp \
$$P/GPU/Null/NullGpu.cpp \
$$P/GPU/GLES/DepalettizeShader.cpp \
$$P/GPU/GLES/FragmentShaderGenerator.cpp \
$$P/GPU/GLES/Framebuffer.cpp \
$$P/GPU/GLES/GLES_GPU.cpp \

View File

@ -190,7 +190,6 @@ void GameSettingsScreen::CreateViews() {
graphicsSettings->Add(new ItemHeader(gs->T("Hack Settings", "Hack Settings (these WILL cause glitches)")));
graphicsSettings->Add(new CheckBox(&g_Config.bTimerHack, gs->T("Timer Hack")));
graphicsSettings->Add(new CheckBox(&g_Config.bDisableStencilTest, gs->T("Disable Stencil Test")));
graphicsSettings->Add(new CheckBox(&g_Config.bAlphaMaskHack, gs->T("Alpha Mask Hack (3rd Birthday)")));
graphicsSettings->Add(new CheckBox(&g_Config.bAlwaysDepthWrite, gs->T("Always Depth Write")));
CheckBox *prescale = graphicsSettings->Add(new CheckBox(&g_Config.bPrescaleUV, gs->T("Texture Coord Speedhack")));
if (PSP_IsInited())

View File

@ -144,6 +144,7 @@ EXEC_AND_LIB_FILES := \
$(SRC)/GPU/Debugger/Breakpoints.cpp \
$(SRC)/GPU/Debugger/Stepping.cpp \
$(SRC)/GPU/GLES/Framebuffer.cpp \
$(SRC)/GPU/GLES/DepalettizeShader.cpp \
$(SRC)/GPU/GLES/GLES_GPU.cpp.arm \
$(SRC)/GPU/GLES/TextureCache.cpp.arm \
$(SRC)/GPU/GLES/TransformPipeline.cpp.arm \