578 lines
14 KiB
C++
578 lines
14 KiB
C++
#include "gles.h"
|
|
#include "rend/TexCache.h"
|
|
#include "hw/pvr/pvr_mem.h"
|
|
|
|
/*
|
|
Textures
|
|
|
|
Textures are converted to native OpenGL textures
|
|
The mapping is done with tcw:tsp -> GL texture. That includes stuff like
|
|
filtering/ texture repeat
|
|
|
|
To save space native formats are used for 1555/565/4444 (only bit shuffling is done)
|
|
YUV is converted to 565 (some loss of quality on that)
|
|
PALs are decoded to their unpaletted format, 8888 is downcasted to 4444
|
|
|
|
Mipmaps
|
|
not supported for now
|
|
|
|
Compression
|
|
look into it, but afaik PVRC is not realtime doable
|
|
*/
|
|
|
|
#if FEAT_HAS_SOFTREND
|
|
#include <xmmintrin.h>
|
|
#endif
|
|
|
|
u16 temp_tex_buffer[1024*1024];
|
|
extern u32 decoded_colors[3][65536];
|
|
|
|
typedef void TexConvFP(PixelBuffer* pb,u8* p_in,u32 Width,u32 Height);
|
|
|
|
struct PvrTexInfo
|
|
{
|
|
const char* name;
|
|
int bpp; //4/8 for pal. 16 for uv, argb
|
|
GLuint type;
|
|
TexConvFP *PL;
|
|
TexConvFP *TW;
|
|
TexConvFP *VQ;
|
|
};
|
|
|
|
PvrTexInfo format[8]=
|
|
{
|
|
{"1555", 16,GL_UNSIGNED_SHORT_5_5_5_1, &tex1555_PL,&tex1555_TW,&tex1555_VQ}, //1555
|
|
{"565", 16,GL_UNSIGNED_SHORT_5_6_5, &tex565_PL,&tex565_TW,&tex565_VQ}, //565
|
|
{"4444", 16,GL_UNSIGNED_SHORT_4_4_4_4, &tex4444_PL,&tex4444_TW,&tex4444_VQ}, //4444
|
|
{"yuv", 16,GL_UNSIGNED_SHORT_5_6_5, &texYUV422_PL,&texYUV422_TW,&texYUV422_VQ}, //yuv
|
|
{"UNSUPPORTED BUMP MAPPED POLY", 16,GL_UNSIGNED_SHORT_4_4_4_4,&texBMP_PL,&texBMP_TW,&texBMP_VQ}, //bump_ns
|
|
{"pal4", 4,0,0,texPAL4_TW,0}, //pal4
|
|
{"pla8", 8,0,0,texPAL8_TW,0}, //pal8
|
|
{"ns/1555", 0}, //ns, 1555
|
|
};
|
|
|
|
const u32 MipPoint[8] =
|
|
{
|
|
0x00006,//8
|
|
0x00016,//16
|
|
0x00056,//32
|
|
0x00156,//64
|
|
0x00556,//128
|
|
0x01556,//256
|
|
0x05556,//512
|
|
0x15556//1024
|
|
};
|
|
|
|
const GLuint PAL_TYPE[4]=
|
|
{GL_UNSIGNED_SHORT_5_5_5_1,GL_UNSIGNED_SHORT_5_6_5,GL_UNSIGNED_SHORT_4_4_4_4,GL_UNSIGNED_SHORT_4_4_4_4};
|
|
|
|
|
|
//Texture Cache :)
|
|
struct TextureCacheData
|
|
{
|
|
TSP tsp; //dreamcast texture parameters
|
|
TCW tcw;
|
|
|
|
GLuint texID; //gl texture
|
|
u16* pData;
|
|
int tex_type;
|
|
|
|
u32 Lookups;
|
|
|
|
//decoded texture info
|
|
u32 sa; //pixel data start address in vram (might be offset for mipmaps/etc)
|
|
u32 sa_tex; //texture data start address in vram
|
|
u32 w,h; //width & height of the texture
|
|
u32 size; //size, in bytes, in vram
|
|
|
|
PvrTexInfo* tex;
|
|
TexConvFP* texconv;
|
|
|
|
u32 dirty;
|
|
vram_block* lock_block;
|
|
|
|
u32 Updates;
|
|
|
|
//used for palette updates
|
|
u32 pal_local_rev; //local palette rev
|
|
u32* pal_table_rev; //table palette rev pointer
|
|
u32 indirect_color_ptr; //palette color table index for pal. tex
|
|
//VQ quantizers table for VQ tex
|
|
//a texture can't be both VQ and PAL at the same time
|
|
|
|
void PrintTextureName()
|
|
{
|
|
printf("Texture: %s ",tex?tex->name:"?format?");
|
|
|
|
if (tcw.VQ_Comp)
|
|
printf(" VQ");
|
|
|
|
if (tcw.ScanOrder==0)
|
|
printf(" TW");
|
|
|
|
if (tcw.MipMapped)
|
|
printf(" MM");
|
|
|
|
if (tcw.StrideSel)
|
|
printf(" Stride");
|
|
|
|
printf(" %dx%d @ 0x%X",8<<tsp.TexU,8<<tsp.TexV,tcw.TexAddr<<3);
|
|
printf("\n");
|
|
}
|
|
|
|
void SetRepeatMode(GLuint dir,u32 clamp,u32 mirror)
|
|
{
|
|
if (clamp)
|
|
glTexParameteri (GL_TEXTURE_2D, dir, GL_CLAMP_TO_EDGE);
|
|
else
|
|
glTexParameteri (GL_TEXTURE_2D, dir, mirror?GL_MIRRORED_REPEAT : GL_REPEAT);
|
|
}
|
|
|
|
//Create GL texture from tsp/tcw
|
|
void Create(bool isGL)
|
|
{
|
|
//ask GL for texture ID
|
|
if (isGL) {
|
|
glGenTextures(1, &texID);
|
|
}
|
|
else {
|
|
texID = 0;
|
|
}
|
|
|
|
pData = 0;
|
|
tex_type = 0;
|
|
|
|
//Reset state info ..
|
|
Lookups=0;
|
|
Updates=0;
|
|
dirty=FrameCount;
|
|
lock_block=0;
|
|
|
|
//decode info from tsp/tcw into the texture struct
|
|
tex=&format[tcw.PixelFmt==7?0:tcw.PixelFmt]; //texture format table entry
|
|
|
|
sa_tex = (tcw.TexAddr<<3) & VRAM_MASK; //texture start address
|
|
sa = sa_tex; //data texture start address (modified for MIPs, as needed)
|
|
w=8<<tsp.TexU; //tex width
|
|
h=8<<tsp.TexV; //tex height
|
|
|
|
if (texID) {
|
|
//bind texture to set modes
|
|
glBindTexture(GL_TEXTURE_2D, texID);
|
|
|
|
//set texture repeat mode
|
|
SetRepeatMode(GL_TEXTURE_WRAP_S, tsp.ClampU, tsp.FlipU); // glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, (tsp.ClampU ? GL_CLAMP_TO_EDGE : (tsp.FlipU ? GL_MIRRORED_REPEAT : GL_REPEAT))) ;
|
|
SetRepeatMode(GL_TEXTURE_WRAP_T, tsp.ClampV, tsp.FlipV); // glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, (tsp.ClampV ? GL_CLAMP_TO_EDGE : (tsp.FlipV ? GL_MIRRORED_REPEAT : GL_REPEAT))) ;
|
|
|
|
#ifdef GLES
|
|
glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
|
|
#endif
|
|
|
|
//set texture filter mode
|
|
if (tsp.FilterMode == 0)
|
|
{
|
|
//disable filtering, mipmaps
|
|
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
|
|
}
|
|
else
|
|
{
|
|
//bilinear filtering
|
|
//PowerVR supports also trilinear via two passes, but we ignore that for now
|
|
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, (tcw.MipMapped && settings.rend.UseMipmaps)?GL_LINEAR_MIPMAP_NEAREST:GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
|
|
}
|
|
}
|
|
|
|
//PAL texture
|
|
if (tex->bpp==4)
|
|
{
|
|
pal_table_rev=&pal_rev_16[tcw.PalSelect];
|
|
indirect_color_ptr=tcw.PalSelect<<4;
|
|
}
|
|
else if (tex->bpp==8)
|
|
{
|
|
pal_table_rev=&pal_rev_256[tcw.PalSelect>>4];
|
|
indirect_color_ptr=(tcw.PalSelect>>4)<<8;
|
|
}
|
|
else
|
|
{
|
|
pal_table_rev=0;
|
|
}
|
|
|
|
//VQ table (if VQ tex)
|
|
if (tcw.VQ_Comp)
|
|
indirect_color_ptr=sa;
|
|
|
|
//Convert a pvr texture into OpenGL
|
|
switch (tcw.PixelFmt)
|
|
{
|
|
|
|
case 0: //0 1555 value: 1 bit; RGB values: 5 bits each
|
|
case 7: //7 Reserved Regarded as 1555
|
|
case 1: //1 565 R value: 5 bits; G value: 6 bits; B value: 5 bits
|
|
case 2: //2 4444 value: 4 bits; RGB values: 4 bits each
|
|
case 3: //3 YUV422 32 bits per 2 pixels; YUYV values: 8 bits each
|
|
case 4: //4 -NOT_PROPERLY SUPPORTED- Bump Map 16 bits/pixel; S value: 8 bits; R value: 8 bits -NOT_PROPERLY SUPPORTED-
|
|
case 5: //5 4 BPP Palette Palette texture with 4 bits/pixel
|
|
case 6: //6 8 BPP Palette Palette texture with 8 bits/pixel
|
|
if (tcw.ScanOrder && tex->PL)
|
|
{
|
|
//Texture is stored 'planar' in memory, no deswizzle is needed
|
|
verify(tcw.VQ_Comp==0);
|
|
//Planar textures support stride selection, mostly used for non power of 2 textures (videos)
|
|
int stride=w;
|
|
if (tcw.StrideSel)
|
|
stride=(TEXT_CONTROL&31)*32;
|
|
//Call the format specific conversion code
|
|
texconv=tex->PL;
|
|
//calculate the size, in bytes, for the locking
|
|
size=stride*h*tex->bpp/8;
|
|
}
|
|
else
|
|
{
|
|
verify(w==h || !tcw.MipMapped); // are non square mipmaps supported ? i can't recall right now *WARN*
|
|
|
|
if (tcw.VQ_Comp)
|
|
{
|
|
verify(tex->VQ!=0);
|
|
indirect_color_ptr=sa;
|
|
if (tcw.MipMapped)
|
|
sa+=MipPoint[tsp.TexU];
|
|
texconv=tex->VQ;
|
|
size=w*h/8;
|
|
}
|
|
else
|
|
{
|
|
verify(tex->TW!=0)
|
|
if (tcw.MipMapped)
|
|
sa+=MipPoint[tsp.TexU]*tex->bpp/2;
|
|
texconv=tex->TW;
|
|
size=w*h*tex->bpp/8;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
printf("Unhandled texture %d\n",tcw.PixelFmt);
|
|
size=w*h*2;
|
|
memset(temp_tex_buffer,0xFFFFFFFF,size);
|
|
texconv=0;
|
|
}
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
//texture state tracking stuff
|
|
Updates++;
|
|
dirty=0;
|
|
|
|
GLuint textype=tex->type;
|
|
|
|
if (pal_table_rev)
|
|
{
|
|
textype=PAL_TYPE[PAL_RAM_CTRL&3];
|
|
pal_local_rev=*pal_table_rev; //make sure to update the local rev, so it won't have to redo the tex
|
|
}
|
|
|
|
palette_index=indirect_color_ptr; //might be used if pal. tex
|
|
vq_codebook=(u8*)&vram[indirect_color_ptr]; //might be used if VQ tex
|
|
|
|
//texture conversion work
|
|
PixelBuffer pbt;
|
|
pbt.p_buffer_start=pbt.p_current_line=temp_tex_buffer;
|
|
pbt.pixels_per_line=w;
|
|
|
|
u32 stride=w;
|
|
|
|
if (tcw.StrideSel && tcw.ScanOrder && tex->PL)
|
|
stride=(TEXT_CONTROL&31)*32; //I think this needs +1 ?
|
|
|
|
if(texconv!=0)
|
|
{
|
|
texconv(&pbt,(u8*)&vram[sa],stride,h);
|
|
}
|
|
else
|
|
{
|
|
//fill it in with a temp color
|
|
printf("UNHANDLED TEXTURE\n");
|
|
memset(temp_tex_buffer,0xF88F8F7F,w*h*2);
|
|
}
|
|
|
|
//PrintTextureName();
|
|
|
|
//lock the texture to detect changes in it
|
|
lock_block = libCore_vramlock_Lock(sa_tex,sa+size-1,this);
|
|
|
|
if (texID) {
|
|
//upload to OpenGL !
|
|
glBindTexture(GL_TEXTURE_2D, texID);
|
|
GLuint comps=textype==GL_UNSIGNED_SHORT_5_6_5?GL_RGB:GL_RGBA;
|
|
glTexImage2D(GL_TEXTURE_2D, 0,comps , w, h, 0, comps, textype, temp_tex_buffer);
|
|
if (tcw.MipMapped && settings.rend.UseMipmaps)
|
|
glGenerateMipmap(GL_TEXTURE_2D);
|
|
}
|
|
else {
|
|
#if FEAT_HAS_SOFTREND
|
|
if (textype == GL_UNSIGNED_SHORT_5_6_5)
|
|
tex_type = 0;
|
|
else if (textype == GL_UNSIGNED_SHORT_5_5_5_1)
|
|
tex_type = 1;
|
|
else if (textype == GL_UNSIGNED_SHORT_4_4_4_4)
|
|
tex_type = 2;
|
|
|
|
if (pData) {
|
|
_mm_free(pData);
|
|
}
|
|
|
|
pData = (u16*)_mm_malloc(w * h * 16, 16);
|
|
for (int y = 0; y < h; y++) {
|
|
for (int x = 0; x < w; x++) {
|
|
u32* data = (u32*)&pData[(x + y*w) * 8];
|
|
|
|
data[0] = decoded_colors[tex_type][temp_tex_buffer[(x + 1) % w + (y + 1) % h * w]];
|
|
data[1] = decoded_colors[tex_type][temp_tex_buffer[(x + 0) % w + (y + 1) % h * w]];
|
|
data[2] = decoded_colors[tex_type][temp_tex_buffer[(x + 1) % w + (y + 0) % h * w]];
|
|
data[3] = decoded_colors[tex_type][temp_tex_buffer[(x + 0) % w + (y + 0) % h * w]];
|
|
}
|
|
}
|
|
#else
|
|
die("Soft rend disabled, invalid code path");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
//true if : dirty or paletted texture and revs don't match
|
|
bool NeedsUpdate() { return (dirty) || (pal_table_rev!=0 && *pal_table_rev!=pal_local_rev); }
|
|
|
|
void Delete()
|
|
{
|
|
if (pData) {
|
|
#if FEAT_HAS_SOFTREND
|
|
_mm_free(pData);
|
|
pData = 0;
|
|
#else
|
|
die("softrend disabled, invalid codepath");
|
|
#endif
|
|
}
|
|
|
|
if (texID) {
|
|
glDeleteTextures(1, &texID);
|
|
}
|
|
if (lock_block)
|
|
libCore_vramlock_Unlock_block(lock_block);
|
|
lock_block=0;
|
|
}
|
|
};
|
|
|
|
#include <map>
|
|
map<u64,TextureCacheData> TexCache;
|
|
typedef map<u64,TextureCacheData>::iterator TexCacheIter;
|
|
|
|
//TexCacheList<TextureCacheData> TexCache;
|
|
|
|
struct FBT
|
|
{
|
|
u32 TexAddr;
|
|
GLuint depthb,stencilb;
|
|
GLuint tex;
|
|
GLuint fbo;
|
|
};
|
|
|
|
FBT fb_rtt;
|
|
|
|
void BindRTT(u32 addy, u32 fbw, u32 fbh, u32 channels, u32 fmt)
|
|
{
|
|
FBT& rv=fb_rtt;
|
|
|
|
if (rv.fbo) glDeleteFramebuffers(1,&rv.fbo);
|
|
if (rv.tex) glDeleteTextures(1,&rv.tex);
|
|
if (rv.depthb) glDeleteRenderbuffers(1,&rv.depthb);
|
|
if (rv.stencilb) glDeleteRenderbuffers(1,&rv.stencilb);
|
|
|
|
rv.TexAddr=addy>>3;
|
|
|
|
// Find the largest square power of two texture that fits into the viewport
|
|
|
|
// Get the currently bound frame buffer object. On most platforms this just gives 0.
|
|
//glGetIntegerv(GL_FRAMEBUFFER_BINDING, &m_i32OriginalFbo);
|
|
|
|
// Generate and bind a render buffer which will become a depth buffer shared between our two FBOs
|
|
glGenRenderbuffers(1, &rv.depthb);
|
|
glBindRenderbuffer(GL_RENDERBUFFER, rv.depthb);
|
|
|
|
/*
|
|
Currently it is unknown to GL that we want our new render buffer to be a depth buffer.
|
|
glRenderbufferStorage will fix this and in this case will allocate a depth buffer
|
|
m_i32TexSize by m_i32TexSize.
|
|
*/
|
|
|
|
#ifdef GLES
|
|
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24_OES, fbw, fbh);
|
|
#else
|
|
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, fbw, fbh);
|
|
#endif
|
|
|
|
glGenRenderbuffers(1, &rv.stencilb);
|
|
glBindRenderbuffer(GL_RENDERBUFFER, rv.stencilb);
|
|
glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, fbw, fbh);
|
|
|
|
// Create a texture for rendering to
|
|
glGenTextures(1, &rv.tex);
|
|
glBindTexture(GL_TEXTURE_2D, rv.tex);
|
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, channels, fbw, fbh, 0, channels, fmt, 0);
|
|
|
|
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
|
|
// Create the object that will allow us to render to the aforementioned texture
|
|
glGenFramebuffers(1, &rv.fbo);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, rv.fbo);
|
|
|
|
// Attach the texture to the FBO
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, rv.tex, 0);
|
|
|
|
// Attach the depth buffer we created earlier to our FBO.
|
|
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rv.depthb);
|
|
|
|
// Check that our FBO creation was successful
|
|
GLuint uStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
|
|
|
verify(uStatus == GL_FRAMEBUFFER_COMPLETE);
|
|
}
|
|
|
|
GLuint gl_GetTexture(TSP tsp, TCW tcw)
|
|
{
|
|
if (tcw.TexAddr==fb_rtt.TexAddr && fb_rtt.tex)
|
|
{
|
|
return fb_rtt.tex;
|
|
}
|
|
|
|
//lookup texture
|
|
TextureCacheData* tf;
|
|
//= TexCache.Find(tcw.full,tsp.full);
|
|
u64 key=((u64)tcw.full<<32) | tsp.full;
|
|
|
|
TexCacheIter tx=TexCache.find(key);
|
|
|
|
if (tx!=TexCache.end())
|
|
{
|
|
tf=&tx->second;
|
|
}
|
|
else //create if not existing
|
|
{
|
|
TextureCacheData tfc={0};
|
|
TexCache[key]=tfc;
|
|
|
|
tx=TexCache.find(key);
|
|
tf=&tx->second;
|
|
|
|
tf->tsp=tsp;
|
|
tf->tcw=tcw;
|
|
tf->Create(true);
|
|
}
|
|
|
|
//update if needed
|
|
if (tf->NeedsUpdate())
|
|
tf->Update();
|
|
|
|
//update state for opts/stuff
|
|
tf->Lookups++;
|
|
|
|
//return gl texture
|
|
return tf->texID;
|
|
}
|
|
|
|
|
|
text_info raw_GetTexture(TSP tsp, TCW tcw)
|
|
{
|
|
text_info rv = { 0 };
|
|
|
|
//lookup texture
|
|
TextureCacheData* tf;
|
|
//= TexCache.Find(tcw.full,tsp.full);
|
|
u64 key = ((u64)tcw.full << 32) | tsp.full;
|
|
|
|
TexCacheIter tx = TexCache.find(key);
|
|
|
|
if (tx != TexCache.end())
|
|
{
|
|
tf = &tx->second;
|
|
}
|
|
else //create if not existing
|
|
{
|
|
TextureCacheData tfc = { 0 };
|
|
TexCache[key] = tfc;
|
|
|
|
tx = TexCache.find(key);
|
|
tf = &tx->second;
|
|
|
|
tf->tsp = tsp;
|
|
tf->tcw = tcw;
|
|
tf->Create(false);
|
|
}
|
|
|
|
//update if needed
|
|
if (tf->NeedsUpdate())
|
|
tf->Update();
|
|
|
|
//update state for opts/stuff
|
|
tf->Lookups++;
|
|
|
|
//return gl texture
|
|
rv.height = tf->h;
|
|
rv.width = tf->w;
|
|
rv.pdata = tf->pData;
|
|
rv.textype = tf->tex_type;
|
|
|
|
|
|
return rv;
|
|
}
|
|
|
|
void CollectCleanup() {
|
|
vector<u64> list;
|
|
|
|
u32 TargetFrame = max((u32)120,FrameCount) - 120;
|
|
|
|
for (TexCacheIter i=TexCache.begin();i!=TexCache.end();i++)
|
|
{
|
|
if ( i->second.dirty && i->second.dirty < TargetFrame) {
|
|
list.push_back(i->first);
|
|
}
|
|
|
|
if (list.size() > 5)
|
|
break;
|
|
}
|
|
|
|
for (size_t i=0; i<list.size(); i++) {
|
|
//printf("Deleting %d\n",TexCache[list[i]].texID);
|
|
TexCache[list[i]].Delete();
|
|
|
|
TexCache.erase(list[i]);
|
|
}
|
|
}
|
|
|
|
void DoCleanup() {
|
|
|
|
}
|
|
void killtex()
|
|
{
|
|
for (TexCacheIter i=TexCache.begin();i!=TexCache.end();i++)
|
|
{
|
|
i->second.Delete();
|
|
}
|
|
|
|
TexCache.clear();
|
|
}
|
|
|
|
void rend_text_invl(vram_block* bl)
|
|
{
|
|
TextureCacheData* tcd = (TextureCacheData*)bl->userdata;
|
|
tcd->dirty=FrameCount;
|
|
tcd->lock_block=0;
|
|
|
|
libCore_vramlock_Unlock_block_wb(bl);
|
|
}
|