Now builds on Playbook and Dev Alpha Make emulator more compatible with other OS (case sensitivity, defines, includes) Uses Android's code paths and backend
496 lines
11 KiB
C++
496 lines
11 KiB
C++
// Copyright (c) 2012- 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/.
|
|
|
|
#if defined(ANDROID) || defined(BLACKBERRY)
|
|
#include <GLES2/gl2.h>
|
|
#include <GLES2/gl2ext.h>
|
|
#else
|
|
#include <GL/glew.h>
|
|
#if defined(__APPLE__)
|
|
#include <OpenGL/gl.h>
|
|
#else
|
|
#include <GL/gl.h>
|
|
#endif
|
|
#endif
|
|
|
|
#include <map>
|
|
|
|
#include "../../Core/MemMap.h"
|
|
#include "../ge_constants.h"
|
|
#include "../GPUState.h"
|
|
#include "TextureCache.h"
|
|
|
|
|
|
struct TexCacheEntry
|
|
{
|
|
u32 addr;
|
|
u32 hash;
|
|
u32 frameCounter;
|
|
u32 numMips;
|
|
int dim;
|
|
GLuint texture;
|
|
};
|
|
|
|
typedef std::map<u32, TexCacheEntry> TexCache;
|
|
static TexCache cache;
|
|
|
|
u8 *tempArea;
|
|
|
|
void TextureCache_Clear(bool delete_them)
|
|
{
|
|
if (delete_them)
|
|
{
|
|
for (TexCache::iterator iter = cache.begin(); iter != cache.end(); ++iter)
|
|
{
|
|
DEBUG_LOG(G3D, "Deleting texture %i", iter->second.texture);
|
|
glDeleteTextures(1, &iter->second.texture);
|
|
}
|
|
}
|
|
if (cache.size()) {
|
|
INFO_LOG(G3D, "Texture cached cleared from %i textures", (int)cache.size());
|
|
cache.clear();
|
|
}
|
|
}
|
|
|
|
u32 PaletteLoad(int index)
|
|
{
|
|
int pf = gstate.clutformat & 3;
|
|
int shift = (gstate.clutformat >> 2) & 31;
|
|
int mask = (gstate.clutformat >> 8) & 255;
|
|
int start = ((gstate.clutformat >> 16) & 31) * 16;
|
|
|
|
if (pf<3)
|
|
{
|
|
//16-bit
|
|
u16 *p = (u16*)gstate.paletteMem;
|
|
u16 col = p[((start+index)>>shift) & mask];
|
|
int r,g,b,a;
|
|
|
|
// TODO: properly expand the lower bits.
|
|
switch (pf)
|
|
{
|
|
case 0:
|
|
r = (col&0x1f)*8;
|
|
g = ((col>>5)&0x3f)*4;
|
|
b = ((col>>11)&0x1f)*8;
|
|
a = 255;
|
|
break;
|
|
case 1:
|
|
r = (col&0x1f)*8;
|
|
g = ((col>>5)&0x1f)*8;
|
|
b = ((col>>10)&0x1f)*8;
|
|
a = (col>>15)*255;
|
|
break;
|
|
case 2:
|
|
r = (col&0xf)*16;
|
|
g = ((col>>4)&0xf)*16;
|
|
b = ((col>>8)&0xf)*16;
|
|
a = ((col>>12)&0xF)*16;
|
|
break;
|
|
}
|
|
|
|
// We now use OpenGL ES 2.0 style colors.
|
|
return (a << 24) | (b<<16) | (g<<8) | (r<<0);
|
|
}
|
|
else
|
|
{
|
|
u32 *p = (u32*)gstate.paletteMem;
|
|
u32 col = p[((start + index) >> shift) & mask];
|
|
return col;
|
|
}
|
|
}
|
|
|
|
// This should not have to be done per texture! OpenGL is silly yo
|
|
// TODO: Dirty-check this against the current texture.
|
|
void UpdateSamplingParams()
|
|
{
|
|
int minFilt = gstate.texfilter & 0x7;
|
|
int magFilt = (gstate.texfilter>>8)&1;
|
|
minFilt &= 1; //no mipmaps yet
|
|
|
|
int sClamp = gstate.texwrap & 1;
|
|
int tClamp = (gstate.texwrap>>8) & 1;
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, sClamp ? GL_CLAMP_TO_EDGE : GL_REPEAT);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, tClamp ? GL_CLAMP_TO_EDGE : GL_REPEAT);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilt ? GL_LINEAR : GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilt ? GL_LINEAR : GL_NEAREST);
|
|
}
|
|
|
|
|
|
// Convert from PSP bit order to GLES bit order
|
|
u16 convert565(u16 c) {
|
|
return (c >> 11) | (c & 0x07E0) | (c << 11);
|
|
}
|
|
|
|
// Convert from PSP bit order to GLES bit order
|
|
u16 convert4444(u16 c) {
|
|
return (c >> 12) | ((c >> 4) & 0xF0) | ((c << 4) & 0xF00) | (c << 12);
|
|
}
|
|
|
|
// Convert from PSP bit order to GLES bit order
|
|
u16 convert5551(u16 c) {
|
|
return ((c & 0x8000) >> 15) | (c << 1);
|
|
}
|
|
|
|
|
|
struct DXT1Block
|
|
{
|
|
u8 lines[4];
|
|
u16 color1;
|
|
u16 color2;
|
|
};
|
|
|
|
inline u8 Convert5To8(u8 v)
|
|
{
|
|
// Swizzle bits: 00012345 -> 12345123
|
|
return (v << 3) | (v >> 2);
|
|
}
|
|
|
|
inline u8 Convert6To8(u8 v)
|
|
{
|
|
// Swizzle bits: 00123456 -> 12345612
|
|
return (v << 2) | (v >> 4);
|
|
}
|
|
|
|
inline u32 makecol(int r, int g, int b, int a)
|
|
{
|
|
return (a << 24)|(r << 16)|(g << 8)|b;
|
|
}
|
|
|
|
void decodeDXT1Block(u32 *dst, const DXT1Block *src, int pitch)
|
|
{
|
|
// S3TC Decoder
|
|
// Needs more speed and debugging.
|
|
u16 c1 = src->color1;
|
|
u16 c2 = src->color2;
|
|
int blue1 = Convert5To8(c1 & 0x1F);
|
|
int blue2 = Convert5To8(c2 & 0x1F);
|
|
int green1 = Convert6To8((c1 >> 5) & 0x3F);
|
|
int green2 = Convert6To8((c2 >> 5) & 0x3F);
|
|
int red1 = Convert5To8((c1 >> 11) & 0x1F);
|
|
int red2 = Convert5To8((c2 >> 11) & 0x1F);
|
|
int colors[4];
|
|
colors[0] = makecol(red1, green1, blue1, 255);
|
|
colors[1] = makecol(red2, green2, blue2, 255);
|
|
if (c1 > c2)
|
|
{
|
|
int blue3 = ((blue2 - blue1) >> 1) - ((blue2 - blue1) >> 3);
|
|
int green3 = ((green2 - green1) >> 1) - ((green2 - green1) >> 3);
|
|
int red3 = ((red2 - red1) >> 1) - ((red2 - red1) >> 3);
|
|
colors[2] = makecol(red1 + red3, green1 + green3, blue1 + blue3, 255);
|
|
colors[3] = makecol(red2 - red3, green2 - green3, blue2 - blue3, 255);
|
|
}
|
|
else
|
|
{
|
|
colors[2] = makecol((red1 + red2 + 1) / 2, // Average
|
|
(green1 + green2 + 1) / 2,
|
|
(blue1 + blue2 + 1) / 2, 255);
|
|
colors[3] = makecol(red2, green2, blue2, 0); // Color2 but transparent
|
|
}
|
|
|
|
for (int y = 0; y < 4; y++)
|
|
{
|
|
int val = src->lines[y];
|
|
for (int x = 0; x < 4; x++)
|
|
{
|
|
dst[x] = colors[(val >> 6) & 3];
|
|
val <<= 2;
|
|
}
|
|
dst += pitch;
|
|
}
|
|
}
|
|
|
|
void PSPSetTexture()
|
|
{
|
|
if (!tempArea)
|
|
tempArea = new u8[512*512*4*4]; // PSP maximum texture size
|
|
|
|
u32 texaddr = (gstate.texaddr[0] & 0xFFFFF0) | ((gstate.texbufwidth[0]<<8) & 0xFF000000);
|
|
texaddr &= 0xFFFFFFF;
|
|
|
|
if (!texaddr) return;
|
|
|
|
DEBUG_LOG(G3D,"Texture at %08x",texaddr);
|
|
u8 *texptr = Memory::GetPointer(texaddr);
|
|
|
|
TexCache::iterator iter = cache.find(texaddr);
|
|
if (iter != cache.end())
|
|
{
|
|
//Validate the texture here (width, height etc)
|
|
TexCacheEntry &entry = iter->second;
|
|
|
|
int dim = gstate.texsize[0] & 0xF0F;
|
|
|
|
if (dim == entry.dim && entry.hash == *(u32*)texptr)
|
|
{
|
|
//got one!
|
|
glBindTexture(GL_TEXTURE_2D, entry.texture);
|
|
UpdateSamplingParams();
|
|
DEBUG_LOG(G3D,"Texture at %08x Found in Cache, applying", texaddr);
|
|
return; //Done!
|
|
}
|
|
else
|
|
{
|
|
NOTICE_LOG(G3D,"Texture different or overwritten, reloading at %08x", texaddr);
|
|
|
|
//Damnit, got overwritten.
|
|
//if (dim != entry.dim)
|
|
//{
|
|
// glDeleteTextures(1, &entry.texture);
|
|
//}
|
|
cache.erase(iter);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NOTICE_LOG(G3D,"No texture in cache, decoding...");
|
|
}
|
|
|
|
//we have to decode it
|
|
|
|
TexCacheEntry entry;
|
|
|
|
entry.addr = texaddr;
|
|
entry.hash = *(u32*)texptr;
|
|
glGenTextures(1, &entry.texture);
|
|
NOTICE_LOG(G3D, "Creating texture %i", entry.texture);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, entry.texture);
|
|
|
|
int bufw = gstate.texbufwidth[0] & 0x3ff;
|
|
|
|
entry.dim = gstate.texsize[0] & 0xF0F;
|
|
|
|
int w = 1 << (gstate.texsize[0] & 0xf);
|
|
int h = 1 << ((gstate.texsize[0]>>8) & 0xf);
|
|
|
|
gstate.curTextureHeight=h;
|
|
gstate.curTextureWidth=w;
|
|
int format = gstate.texformat & 0xF;
|
|
|
|
DEBUG_LOG(G3D,"Texture Width %04x Height %04x Bufw %d Fmt %d", w, h, bufw, format);
|
|
|
|
// TODO: Look into using BGRA for 32-bit textures when the GL_EXT_texture_format_BGRA8888 extension is available, as it's faster than RGBA on some chips.
|
|
|
|
switch (format)
|
|
{
|
|
case GE_TFMT_4444:
|
|
case GE_TFMT_5551:
|
|
case GE_TFMT_5650:
|
|
{
|
|
u16 *dst = (u16*)tempArea;
|
|
u16 *src = (u16*)texptr;
|
|
|
|
int fmt = GL_UNSIGNED_SHORT_5_6_5;
|
|
int internal_format = GL_RGBA;
|
|
u16 (*convFunc)(u16);
|
|
switch (format)
|
|
{
|
|
case GE_TFMT_4444: fmt = GL_UNSIGNED_SHORT_4_4_4_4; convFunc = &convert4444; break;
|
|
case GE_TFMT_5551: fmt = GL_UNSIGNED_SHORT_5_5_5_1; convFunc = &convert5551; break;
|
|
case GE_TFMT_5650: fmt = GL_UNSIGNED_SHORT_5_6_5; convFunc = &convert565; internal_format = GL_RGB; break;
|
|
}
|
|
|
|
if (gstate.texmode & 1) //Swizzled!
|
|
{
|
|
for (int y=0; y<h/8; y++)
|
|
{
|
|
for (int x=0; x<w/8; x++)
|
|
{
|
|
for (int yy=0; yy<8; yy++)
|
|
{
|
|
for (int xx=0; xx<8; xx++)
|
|
{
|
|
dst[(y*8+yy)*w + x*8 + xx] = convFunc(src[(y*(bufw/8)+x)*8 + (yy*8+xx)]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int y=0; y<h; y++)
|
|
{
|
|
const u16 *s = src + bufw * y;
|
|
u16 *d = dst + y * w;
|
|
for (int x = 0; x < w; x++)
|
|
*d++ = convFunc(*s++);
|
|
}
|
|
}
|
|
|
|
// TODO: This will have to be redone for OpenGL ES 2.0.
|
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, internal_format, w, h, 0, internal_format, fmt, (GLvoid*)tempArea);
|
|
break;
|
|
}
|
|
|
|
case GE_TFMT_CLUT4:
|
|
{
|
|
u32 *dst = (u32*)tempArea;
|
|
u8 *src = (u8*)texptr;
|
|
if (gstate.texmode & 1) //Swizzled!
|
|
{
|
|
for (int y=0; y<h/8; y++)
|
|
{
|
|
for (int x=0; x<w/32; x++)
|
|
{
|
|
for (int yy=0; yy<8; yy++)
|
|
{
|
|
for (int xx=0; xx<32; xx++)
|
|
{
|
|
int idx = src[(y*bufw*4+x*16*8) + (yy*16+xx/2)];
|
|
if (xx&1) idx>>=4; else idx&=0xF;
|
|
dst[(y*8+yy)*w + x*32 + xx] = PaletteLoad(idx);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int y=0; y<h; y++)
|
|
{
|
|
for (int x=0; x<w; x++)
|
|
{
|
|
int idx = *src;
|
|
if (x&1) idx >>= 4; else idx &= 0xF;
|
|
dst[x] = PaletteLoad(idx);
|
|
if (x&1)
|
|
src++;
|
|
}
|
|
src -= w/2;
|
|
src += bufw/2;
|
|
dst += w;
|
|
}
|
|
}
|
|
int fmt = GL_UNSIGNED_BYTE;
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, fmt, (GLvoid*)tempArea);
|
|
break;
|
|
}
|
|
|
|
case GE_TFMT_CLUT8:
|
|
{
|
|
u32 *dst = (u32*)tempArea;
|
|
u8 *src = (u8*)texptr;
|
|
if (gstate.texmode & 1) //Swizzled!
|
|
{
|
|
for (int y=0; y<h/8; y++)
|
|
{
|
|
for (int x=0; x<w/16; x++)
|
|
{
|
|
for (int yy=0; yy<8; yy++)
|
|
{
|
|
for (int xx=0; xx<16; xx++)
|
|
{
|
|
int idx = src[(y*bufw*8+x*16*8) + (yy*16+xx)];
|
|
dst[(y*8+yy)*w + x*16 + xx] = PaletteLoad(idx);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int y=0; y<h; y++)
|
|
{
|
|
for (int x=0; x<w; x++)
|
|
{
|
|
int idx = src[x];
|
|
dst[x] = PaletteLoad(idx);
|
|
}
|
|
src += bufw;
|
|
dst += w;
|
|
}
|
|
}
|
|
int fmt = GL_UNSIGNED_BYTE;
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, fmt, (GLvoid*)tempArea);
|
|
break;
|
|
}
|
|
|
|
case GE_TFMT_8888:
|
|
{
|
|
u32 *dst = (u32*)tempArea;
|
|
u32 *src = (u32*)texptr;
|
|
if (gstate.texmode & 1) //Swizzled!
|
|
{
|
|
for (int y=0; y<h/8; y++)
|
|
{
|
|
int i = y*bufw*8;
|
|
for (int x=0; x<w/4; x++)
|
|
{
|
|
for (int yy=0; yy<8; yy++)
|
|
{
|
|
for (int xx=0; xx<4; xx++)
|
|
{
|
|
dst[(y*8+yy)*w + x*4 + xx] = src[i];
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int y=0; y<h; y++)
|
|
{
|
|
memcpy(dst+y*w,src+bufw*y,4*bufw);
|
|
}
|
|
}
|
|
int fmt = GL_UNSIGNED_BYTE;
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, fmt, (GLvoid*)tempArea);
|
|
break;
|
|
}
|
|
|
|
case GE_TFMT_DXT1:
|
|
{
|
|
// THIS IS VERY BROKEN but can be debugged! :)
|
|
u32 *dst = (u32*)tempArea;
|
|
DXT1Block *src = (DXT1Block*)texptr;
|
|
|
|
for (int y=0; y<h/4; y++)
|
|
{
|
|
int i = y*w/4;
|
|
for (int x=0; x<w/4; x++)
|
|
{
|
|
decodeDXT1Block(dst + w*4 * y * 4 + x * 4, src + i, w);
|
|
i++;
|
|
}
|
|
}
|
|
int fmt = GL_UNSIGNED_BYTE;
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, fmt, (GLvoid*)tempArea);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case GE_TFMT_DXT3:
|
|
case GE_TFMT_DXT5:
|
|
default:
|
|
ERROR_LOG(G3D, "Unknown Texture Format %i, not setting texture",format);
|
|
PanicAlert("ANOTHER tex format??");
|
|
return;
|
|
}
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
|
|
// glGenerateMipmap(GL_TEXTURE_2D);
|
|
UpdateSamplingParams();
|
|
|
|
cache[texaddr] = entry;
|
|
}
|