diff --git a/core/nullDC.cpp b/core/nullDC.cpp
index d23bc1b44..4c48b3403 100755
--- a/core/nullDC.cpp
+++ b/core/nullDC.cpp
@@ -520,6 +520,8 @@ void LoadSettings()
settings.rend.ExtraDepthScale = atof(extra_depth_scale_str);
if (settings.rend.ExtraDepthScale == 0)
settings.rend.ExtraDepthScale = 1.f;
+ settings.rend.CustomTextures = cfgLoadInt("config", "rend.CustomTextures", 0);
+ settings.rend.DumpTextures = cfgLoadInt("config", "rend.DumpTextures", 0);
settings.pvr.subdivide_transp = cfgLoadInt("config","pvr.Subdivide",0);
diff --git a/core/rend/gles/CustomTexture.cpp b/core/rend/gles/CustomTexture.cpp
new file mode 100644
index 000000000..f3ab1241b
--- /dev/null
+++ b/core/rend/gles/CustomTexture.cpp
@@ -0,0 +1,123 @@
+/*
+ Copyright 2018 flyinghead
+
+ This file is part of reicast.
+
+ reicast 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, either version 2 of the License, or
+ (at your option) any later version.
+
+ reicast 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 for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with reicast. If not, see .
+ */
+#include "CustomTexture.h"
+
+#include
+#include
+#include
+#include
+
+#include "deps/libpng/png.h"
+#include "reios.h"
+
+void CustomTexture::LoaderThread()
+{
+ while (initialized)
+ {
+ TextureCacheData *texture = NULL;
+
+ work_queue_mutex.Lock();
+ if (!work_queue.empty())
+ {
+ texture = work_queue.back();
+ work_queue.pop_back();
+ }
+ work_queue_mutex.Unlock();
+
+ if (texture != NULL)
+ {
+ // FIXME texture may have been deleted. Need to detect this.
+ texture->ComputeHash();
+ int width, height;
+ u8 *image_data = LoadCustomTexture(texture->texture_hash, width, height);
+ if (image_data != NULL)
+ {
+ if (texture->custom_image_data != NULL)
+ delete [] texture->custom_image_data;
+ texture->custom_width = width;
+ texture->custom_height = height;
+ texture->custom_image_data = image_data;
+ }
+ }
+
+ wakeup_thread.Wait();
+ }
+}
+
+bool CustomTexture::Init()
+{
+ if (!initialized)
+ {
+ initialized = true;
+ std::string game_id_str = reios_product_number;
+ if (game_id_str.length() > 0)
+ {
+ std::replace(game_id_str.begin(), game_id_str.end(), ' ', '_');
+ textures_path = get_readonly_data_path("/data/") + "textures/" + game_id_str + "/";
+
+ DIR *dir = opendir(textures_path.c_str());
+ if (dir != NULL)
+ {
+ printf("Found custom textures directory: %s\n", textures_path.c_str());
+ custom_textures_available = true;
+ closedir(dir);
+ loader_thread.Start();
+ }
+ }
+ }
+ return custom_textures_available;
+}
+
+void CustomTexture::Terminate()
+{
+ if (initialized)
+ {
+ initialized = false;
+ work_queue_mutex.Lock();
+ work_queue.clear();
+ work_queue_mutex.Unlock();
+ wakeup_thread.Set();
+ loader_thread.WaitToEnd();
+ }
+}
+
+u8* CustomTexture::LoadCustomTexture(u32 hash, int& width, int& height)
+{
+ if (unknown_hashes.find(hash) != unknown_hashes.end())
+ return NULL;
+ std::stringstream path;
+ path << textures_path << std::hex << hash << ".png";
+
+ u8 *image_data = loadPNGData(path.str(), width, height, false);
+ if (image_data == NULL)
+ unknown_hashes.insert(hash);
+
+ return image_data;
+}
+
+void CustomTexture::LoadCustomTextureAsync(TextureCacheData *texture_data)
+{
+ if (!Init())
+ return;
+ work_queue_mutex.Lock();
+ work_queue.insert(work_queue.begin(), texture_data);
+ work_queue_mutex.Unlock();
+ wakeup_thread.Set();
+}
+
diff --git a/core/rend/gles/CustomTexture.h b/core/rend/gles/CustomTexture.h
new file mode 100644
index 000000000..1b8006281
--- /dev/null
+++ b/core/rend/gles/CustomTexture.h
@@ -0,0 +1,51 @@
+/*
+ Copyright 2018 flyinghead
+
+ This file is part of reicast.
+
+ reicast 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, either version 2 of the License, or
+ (at your option) any later version.
+
+ reicast 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 for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with reicast. If not, see .
+ */
+
+#ifndef CORE_REND_GLES_CUSTOMTEXTURE_H_
+#define CORE_REND_GLES_CUSTOMTEXTURE_H_
+
+#include
+#include
+#include "gles.h"
+
+class CustomTexture {
+public:
+ CustomTexture() : loader_thread(loader_thread_func, this), wakeup_thread(false, true) {}
+ ~CustomTexture() { Terminate(); }
+ u8* LoadCustomTexture(u32 hash, int& width, int& height);
+ void LoadCustomTextureAsync(TextureCacheData *texture_data);
+
+private:
+ bool Init();
+ void Terminate();
+ void LoaderThread();
+
+ static void *loader_thread_func(void *param) { ((CustomTexture *)param)->LoaderThread(); return NULL; }
+
+ bool initialized;
+ bool custom_textures_available;
+ std::string textures_path;
+ std::set unknown_hashes;
+ cThread loader_thread;
+ cResetEvent wakeup_thread;
+ std::vector work_queue;
+ cMutex work_queue_mutex;
+};
+
+#endif /* CORE_REND_GLES_CUSTOMTEXTURE_H_ */
diff --git a/core/rend/gles/gles.cpp b/core/rend/gles/gles.cpp
index d6339e47f..caa3e164c 100644
--- a/core/rend/gles/gles.cpp
+++ b/core/rend/gles/gles.cpp
@@ -2039,7 +2039,7 @@ void png_cstd_read(png_structp png_ptr, png_bytep data, png_size_t length)
fread(data,1, length,pngfile);
}
-GLuint loadPNG(const string& fname, int &width, int &height)
+u8* loadPNGData(const string& fname, int &width, int &height, bool bottom_to_top)
{
const char* filename=fname.c_str();
FILE* file = fopen(filename, "rb");
@@ -2047,7 +2047,7 @@ GLuint loadPNG(const string& fname, int &width, int &height)
if (!file)
{
- printf("Error opening %s\n", filename);
+ EMUERROR("Error opening %s\n", filename);
return TEXTURE_LOAD_ERROR;
}
@@ -2157,12 +2157,33 @@ GLuint loadPNG(const string& fname, int &width, int &height)
}
// set the individual row_pointers to point at the correct offsets of image_data
- for (int i = 0; i < height; ++i)
- row_pointers[height - 1 - i] = image_data + i * rowbytes;
+ if (bottom_to_top)
+ {
+ for (int i = 0; i < height; ++i)
+ row_pointers[height - 1 - i] = image_data + i * rowbytes;
+ }
+ else
+ {
+ for (int i = 0; i < height; ++i)
+ row_pointers[i] = image_data + i * rowbytes;
+ }
//read the png into image_data through row_pointers
png_read_image(png_ptr, row_pointers);
+ png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
+ delete[] row_pointers;
+ fclose(file);
+
+ return image_data;
+}
+
+GLuint loadPNG(const string& fname, int &width, int &height, bool bottom_to_top)
+{
+ png_byte *image_data = loadPNGData(fname, width, height);
+ if (image_data == NULL)
+ return TEXTURE_LOAD_ERROR;
+
//Now generate the OpenGL texture object
GLuint texture = glcache.GenTexture();
glcache.BindTexture(GL_TEXTURE_2D, texture);
@@ -2170,11 +2191,7 @@ GLuint loadPNG(const string& fname, int &width, int &height)
GL_UNSIGNED_BYTE, (GLvoid*) image_data);
glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- //clean up memory and close stuff
- png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
delete[] image_data;
- delete[] row_pointers;
- fclose(file);
return texture;
}
diff --git a/core/rend/gles/gles.h b/core/rend/gles/gles.h
index ed0993acc..6e0c169c2 100755
--- a/core/rend/gles/gles.h
+++ b/core/rend/gles/gles.h
@@ -169,7 +169,8 @@ GLuint gl_CompileShader(const char* shader, GLuint type);
GLuint gl_CompileAndLink(const char* VertexShader, const char* FragmentShader);
bool CompilePipelineShader(PipelineShader* s);
#define TEXTURE_LOAD_ERROR 0
-GLuint loadPNG(const string& subpath, int &width, int &height);
+u8* loadPNGData(const string& subpath, int &width, int &height, bool bottom_to_top = true);
+GLuint loadPNG(const string& subpath, int &width, int &height, bool bottom_to_top = true);
extern struct ShaderUniforms_t
{
@@ -228,3 +229,61 @@ struct FBT
};
extern FBT fb_rtt;
+
+struct PvrTexInfo;
+template class PixelBuffer;
+typedef void TexConvFP(PixelBuffer* pb,u8* p_in,u32 Width,u32 Height);
+typedef void TexConvFP32(PixelBuffer* pb,u8* p_in,u32 Width,u32 Height);
+
+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;
+ TexConvFP32* texconv32;
+
+ u32 dirty;
+ vram_block* lock_block;
+
+ u32 Updates;
+
+ //used for palette updates
+ u32 palette_hash; // Palette hash at time of last update
+ 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
+ u32 texture_hash; // xxhash of texture data, used for custom textures
+ u8* custom_image_data; // loaded custom image data
+ u32 custom_width;
+ u32 custom_height;
+
+ void PrintTextureName();
+
+ bool IsPaletted()
+ {
+ return tcw.PixelFmt == PixelPal4 || tcw.PixelFmt == PixelPal8;
+ }
+
+ void Create(bool isGL);
+ void ComputeHash();
+ void Update();
+ void UploadToGPU(GLuint textype, int width, int height, u8 *temp_tex_buffer);
+ void CheckCustomTexture();
+ //true if : dirty or paletted texture and hashes don't match
+ bool NeedsUpdate();
+ void Delete();
+};
diff --git a/core/rend/gles/gltex.cpp b/core/rend/gles/gltex.cpp
index a7acdf368..e997ff429 100644
--- a/core/rend/gles/gltex.cpp
+++ b/core/rend/gles/gltex.cpp
@@ -1,8 +1,13 @@
+#include
+#include
#include "glcache.h"
#include "rend/TexCache.h"
#include "hw/pvr/pvr_mem.h"
#include "hw/mem/_vmem.h"
#include "deps/libpng/png.h"
+#include "deps/xxhash/xxhash.h"
+#include "CustomTexture.h"
+#include "reios.h"
/*
Textures
@@ -28,9 +33,6 @@ Compression
extern u32 decoded_colors[3][65536];
-typedef void TexConvFP(PixelBuffer* pb,u8* p_in,u32 Width,u32 Height);
-typedef void TexConvFP32(PixelBuffer* pb,u8* p_in,u32 Width,u32 Height);
-
struct PvrTexInfo
{
const char* name;
@@ -73,6 +75,8 @@ const u32 MipPoint[8] =
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_INT_8_8_8_8};
+CustomTexture custom_texture;
+
static void dumpRtTexture(u32 name, u32 w, u32 h) {
char sname[256];
sprintf(sname, "texdump/%x-%d.png", name, FrameCount);
@@ -114,13 +118,27 @@ static void dumpRtTexture(u32 name, u32 w, u32 h) {
free(rows);
}
-static void dumpTexture(int texID, int w, int h, GLuint textype, void *temp_tex_buffer)
+static void dumpTexture(u32 hash, int w, int h, GLuint textype, void *temp_tex_buffer)
{
- char sname[256];
- sprintf(sname, "texdump/%d.png", texID);
- FILE *fp = fopen(sname, "wb");
- if (fp == NULL)
+ std::string base_dump_dir = get_writable_data_path("/data/texdump/");
+ if (!file_exists(base_dump_dir))
+ mkdir(base_dump_dir.c_str(), 0755);
+ std::string game_id_str = reios_product_number;
+ if (game_id_str.length() == 0)
return;
+ std::replace(game_id_str.begin(), game_id_str.end(), ' ', '_');
+ base_dump_dir += game_id_str + "/";
+ if (!file_exists(base_dump_dir))
+ mkdir(base_dump_dir.c_str(), 0755);
+
+ std::stringstream path;
+ path << base_dump_dir << std::hex << hash << ".png";
+ FILE *fp = fopen(path.str().c_str(), "wb");
+ if (fp == NULL)
+ {
+ printf("Failed to open %s for writing\n", path.str().c_str());
+ return;
+ }
u16 *src = (u16 *)temp_tex_buffer;
@@ -178,6 +196,7 @@ static void dumpTexture(int texID, int w, int h, GLuint textype, void *temp_tex_
break;
default:
printf("dumpTexture: unsupported picture format %x\n", textype);
+ fclose(fp);
free(rows[0]);
free(rows);
return;
@@ -206,366 +225,362 @@ static void dumpTexture(int texID, int w, int h, GLuint textype, void *temp_tex_
fclose(fp);
for (int y = 0; y < h; y++)
- free(rows[y]);
+ free(rows[y]);
free(rows);
}
//Texture Cache :)
-struct TextureCacheData
+void TextureCacheData::PrintTextureName()
{
- TSP tsp; //dreamcast texture parameters
- TCW tcw;
+ printf("Texture: %s ",tex?tex->name:"?format?");
- GLuint texID; //gl texture
- u16* pData;
- int tex_type;
+ if (tcw.VQ_Comp)
+ printf(" VQ");
- u32 Lookups;
+ if (tcw.ScanOrder==0)
+ printf(" TW");
- //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
+ if (tcw.MipMapped)
+ printf(" MM");
- PvrTexInfo* tex;
- TexConvFP* texconv;
- TexConvFP32* texconv32;
+ if (tcw.StrideSel)
+ printf(" Stride");
- u32 dirty;
- vram_block* lock_block;
+ printf(" %dx%d @ 0x%X",8<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<bpp==4)
+ indirect_color_ptr=tcw.PalSelect<<4;
+ else if (tex->bpp==8)
+ indirect_color_ptr=(tcw.PalSelect>>4)<<8;
+
+ //VQ table (if VQ tex)
+ if (tcw.VQ_Comp)
+ indirect_color_ptr=sa;
+
+ //Convert a pvr texture into OpenGL
+ switch (tcw.PixelFmt)
{
- return tcw.PixelFmt == PixelPal4 || tcw.PixelFmt == PixelPal8;
- }
- //Create GL texture from tsp/tcw
- void Create(bool isGL)
- {
- //ask GL for texture ID
- if (isGL) {
- texID = glcache.GenTexture();
- }
- 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 == PixelReserved ? Pixel1555 : 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<bpp==4)
- indirect_color_ptr=tcw.PalSelect<<4;
- else if (tex->bpp==8)
- indirect_color_ptr=(tcw.PalSelect>>4)<<8;
-
- //VQ table (if VQ tex)
- if (tcw.VQ_Comp)
- indirect_color_ptr=sa;
-
- //Convert a pvr texture into OpenGL
- switch (tcw.PixelFmt)
+ case Pixel1555: //0 1555 value: 1 bit; RGB values: 5 bits each
+ case PixelReserved: //7 Reserved Regarded as 1555
+ case Pixel565: //1 565 R value: 5 bits; G value: 6 bits; B value: 5 bits
+ case Pixel4444: //2 4444 value: 4 bits; RGB values: 4 bits each
+ case PixelYUV: //3 YUV422 32 bits per 2 pixels; YUYV values: 8 bits each
+ case PixelBumpMap: //4 Bump Map 16 bits/pixel; S value: 8 bits; R value: 8 bits
+ case PixelPal4: //5 4 BPP Palette Palette texture with 4 bits/pixel
+ case PixelPal8: //6 8 BPP Palette Palette texture with 8 bits/pixel
+ if (tcw.ScanOrder && (tex->PL || tex->PL32))
{
+ //Texture is stored 'planar' in memory, no deswizzle is needed
+ //verify(tcw.VQ_Comp==0);
+ if (tcw.VQ_Comp != 0)
+ printf("Warning: planar texture with VQ set (invalid)\n");
- case Pixel1555: //0 1555 value: 1 bit; RGB values: 5 bits each
- case PixelReserved: //7 Reserved Regarded as 1555
- case Pixel565: //1 565 R value: 5 bits; G value: 6 bits; B value: 5 bits
- case Pixel4444: //2 4444 value: 4 bits; RGB values: 4 bits each
- case PixelYUV: //3 YUV422 32 bits per 2 pixels; YUYV values: 8 bits each
- case PixelBumpMap: //4 Bump Map 16 bits/pixel; S value: 8 bits; R value: 8 bits
- case PixelPal4: //5 4 BPP Palette Palette texture with 4 bits/pixel
- case PixelPal8: //6 8 BPP Palette Palette texture with 8 bits/pixel
- if (tcw.ScanOrder && (tex->PL || tex->PL32))
- {
- //Texture is stored 'planar' in memory, no deswizzle is needed
- //verify(tcw.VQ_Comp==0);
- if (tcw.VQ_Comp != 0)
- printf("Warning: planar texture with VQ set (invalid)\n");
-
- //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;
- texconv32 = tex->PL32;
- //calculate the size, in bytes, for the locking
- size=stride*h*tex->bpp/8;
- }
- else
- {
- // Quake 3 Arena uses one. Not sure if valid but no need to crash
- //verify(w==h || !tcw.MipMapped); // are non square mipmaps supported ? i can't recall right now *WARN*
-
- if (tcw.VQ_Comp)
- {
- verify(tex->VQ != NULL || tex->VQ32 != NULL);
- indirect_color_ptr=sa;
- if (tcw.MipMapped)
- sa+=MipPoint[tsp.TexU];
- texconv = tex->VQ;
- texconv32 = tex->VQ32;
- size=w*h/8;
- }
- else
- {
- verify(tex->TW != NULL || tex->TW32 != NULL)
- if (tcw.MipMapped)
- sa+=MipPoint[tsp.TexU]*tex->bpp/2;
- texconv = tex->TW;
- texconv32 = tex->TW32;
- size=w*h*tex->bpp/8;
- }
- }
- break;
- default:
- printf("Unhandled texture %d\n",tcw.PixelFmt);
- size=w*h*2;
- texconv = NULL;
- texconv32 = NULL;
- }
- }
-
- void Update()
- {
- //texture state tracking stuff
- Updates++;
- dirty=0;
-
- GLuint textype=tex->type;
-
- bool has_alpha = false;
- if (IsPaletted())
- {
- textype=PAL_TYPE[PAL_RAM_CTRL&3];
- if (textype == GL_UNSIGNED_INT_8_8_8_8)
- has_alpha = true;
-
- // Get the palette hash to check for future updates
- if (tcw.PixelFmt == PixelPal4)
- palette_hash = pal_hash_16[tcw.PalSelect];
- else
- palette_hash = pal_hash_256[tcw.PalSelect >> 4];
- }
-
- 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
- u32 stride=w;
-
- if (tcw.StrideSel && tcw.ScanOrder && (tex->PL || tex->PL32))
- stride=(TEXT_CONTROL&31)*32; //I think this needs +1 ?
-
- //PrintTextureName();
- u32 original_h = h;
- if (sa_tex > VRAM_SIZE || size == 0 || sa + size > VRAM_SIZE)
- {
- if (sa + size > VRAM_SIZE)
- {
- // Shenmue Space Harrier mini-arcade loads a texture that goes beyond the end of VRAM
- // but only uses the top portion of it
- h = (VRAM_SIZE - sa) * 8 / stride / tex->bpp;
- size = stride * h * tex->bpp/8;
- }
- else
- {
- printf("Warning: invalid texture. Address %08X %08X size %d\n", sa_tex, sa, size);
- return;
- }
- }
-
- void *temp_tex_buffer = NULL;
- u32 upscaled_w = w;
- u32 upscaled_h = h;
-
- PixelBuffer pb16;
- PixelBuffer pb32;
-
- // Figure out if we really need to use a 32-bit pixel buffer
- bool need_32bit_buffer = true;
- if ((settings.rend.TextureUpscale <= 1
- || w * h > settings.rend.MaxFilteredTextureSize
- * settings.rend.MaxFilteredTextureSize // Don't process textures that are too big
- || tcw.PixelFmt == PixelYUV) // Don't process YUV textures
- && (!IsPaletted() || textype != GL_UNSIGNED_INT_8_8_8_8)
- && texconv != NULL)
- need_32bit_buffer = false;
- // TODO avoid upscaling/depost. textures that change too often
-
- if (texconv32 != NULL && need_32bit_buffer)
- {
- // Force the texture type since that's the only 32-bit one we know
- textype = GL_UNSIGNED_INT_8_8_8_8;
-
- pb32.init(w, h);
-
- texconv32(&pb32, (u8*)&vram[sa], stride, h);
-
-#ifdef DEPOSTERIZE
- {
- // Deposterization
- PixelBuffer tmp_buf;
- tmp_buf.init(w, h);
-
- DePosterize(pb32.data(), tmp_buf.data(), w, h);
- pb32.steal_data(tmp_buf);
- }
-#endif
-
- // xBRZ scaling
- if (settings.rend.TextureUpscale > 1)
- {
- PixelBuffer tmp_buf;
- tmp_buf.init(w * settings.rend.TextureUpscale, h * settings.rend.TextureUpscale);
-
- if (tcw.PixelFmt == Pixel1555 || tcw.PixelFmt == Pixel4444)
- // Alpha channel formats. Palettes with alpha are already handled
- has_alpha = true;
- UpscalexBRZ(settings.rend.TextureUpscale, pb32.data(), tmp_buf.data(), w, h, has_alpha);
- pb32.steal_data(tmp_buf);
- upscaled_w *= settings.rend.TextureUpscale;
- upscaled_h *= settings.rend.TextureUpscale;
- }
- temp_tex_buffer = pb32.data();
- }
- else if (texconv != NULL)
- {
- pb16.init(w, h);
-
- texconv(&pb16,(u8*)&vram[sa],stride,h);
- temp_tex_buffer = pb16.data();
+ //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;
+ texconv32 = tex->PL32;
+ //calculate the size, in bytes, for the locking
+ size=stride*h*tex->bpp/8;
}
else
{
- //fill it in with a temp color
- printf("UNHANDLED TEXTURE\n");
- pb16.init(w, h);
- memset(pb16.data(), 0x80, w * h * 2);
- temp_tex_buffer = pb16.data();
+ // Quake 3 Arena uses one. Not sure if valid but no need to crash
+ //verify(w==h || !tcw.MipMapped); // are non square mipmaps supported ? i can't recall right now *WARN*
+
+ if (tcw.VQ_Comp)
+ {
+ verify(tex->VQ != NULL || tex->VQ32 != NULL);
+ indirect_color_ptr=sa;
+ if (tcw.MipMapped)
+ sa+=MipPoint[tsp.TexU];
+ texconv = tex->VQ;
+ texconv32 = tex->VQ32;
+ size=w*h/8;
+ }
+ else
+ {
+ verify(tex->TW != NULL || tex->TW32 != NULL);
+ if (tcw.MipMapped)
+ sa+=MipPoint[tsp.TexU]*tex->bpp/2;
+ texconv = tex->TW;
+ texconv32 = tex->TW32;
+ size=w*h*tex->bpp/8;
+ }
}
- // Restore the original texture height if it was constrained to VRAM limits above
- h = original_h;
+ break;
+ default:
+ printf("Unhandled texture %d\n",tcw.PixelFmt);
+ size=w*h*2;
+ texconv = NULL;
+ texconv32 = NULL;
+ }
+}
- //lock the texture to detect changes in it
- lock_block = libCore_vramlock_Lock(sa_tex,sa+size-1,this);
+void TextureCacheData::ComputeHash()
+{
+ texture_hash = XXH32(&vram[sa], size, 7);
+ if (IsPaletted())
+ texture_hash ^= palette_hash;
+}
+
+void TextureCacheData::Update()
+{
+ //texture state tracking stuff
+ Updates++;
+ dirty=0;
- if (texID) {
- //upload to OpenGL !
- glcache.BindTexture(GL_TEXTURE_2D, texID);
- GLuint comps=textype==GL_UNSIGNED_SHORT_5_6_5?GL_RGB:GL_RGBA;
+ GLuint textype=tex->type;
+
+ bool has_alpha = false;
+ if (IsPaletted())
+ {
+ textype=PAL_TYPE[PAL_RAM_CTRL&3];
+ if (textype == GL_UNSIGNED_INT_8_8_8_8)
+ has_alpha = true;
+
+ // Get the palette hash to check for future updates
+ if (tcw.PixelFmt == PixelPal4)
+ palette_hash = pal_hash_16[tcw.PalSelect];
+ else
+ palette_hash = pal_hash_256[tcw.PalSelect >> 4];
+ }
+
+ 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
+ u32 stride=w;
+
+ if (tcw.StrideSel && tcw.ScanOrder && (tex->PL || tex->PL32))
+ stride=(TEXT_CONTROL&31)*32; //I think this needs +1 ?
+
+ //PrintTextureName();
+ u32 original_h = h;
+ if (sa_tex > VRAM_SIZE || size == 0 || sa + size > VRAM_SIZE)
+ {
+ if (sa + size > VRAM_SIZE)
+ {
+ // Shenmue Space Harrier mini-arcade loads a texture that goes beyond the end of VRAM
+ // but only uses the top portion of it
+ h = (VRAM_SIZE - sa) * 8 / stride / tex->bpp;
+ size = stride * h * tex->bpp/8;
+ }
+ else
+ {
+ printf("Warning: invalid texture. Address %08X %08X size %d\n", sa_tex, sa, size);
+ return;
+ }
+ }
+ if (settings.rend.CustomTextures)
+ {
+ custom_texture.LoadCustomTextureAsync(this);
+ }
+
+ void *temp_tex_buffer = NULL;
+ u32 upscaled_w = w;
+ u32 upscaled_h = h;
+
+ PixelBuffer pb16;
+ PixelBuffer pb32;
+
+ // Figure out if we really need to use a 32-bit pixel buffer
+ bool need_32bit_buffer = true;
+ if ((settings.rend.TextureUpscale <= 1
+ || w * h > settings.rend.MaxFilteredTextureSize
+ * settings.rend.MaxFilteredTextureSize // Don't process textures that are too big
+ || tcw.PixelFmt == PixelYUV) // Don't process YUV textures
+ && (!IsPaletted() || textype != GL_UNSIGNED_INT_8_8_8_8)
+ && texconv != NULL)
+ need_32bit_buffer = false;
+ // TODO avoid upscaling/depost. textures that change too often
+
+ if (texconv32 != NULL && need_32bit_buffer)
+ {
+ // Force the texture type since that's the only 32-bit one we know
+ textype = GL_UNSIGNED_INT_8_8_8_8;
+
+ pb32.init(w, h);
+
+ texconv32(&pb32, (u8*)&vram[sa], stride, h);
+
+#ifdef DEPOSTERIZE
+ {
+ // Deposterization
+ PixelBuffer tmp_buf;
+ tmp_buf.init(w, h);
+
+ DePosterize(pb32.data(), tmp_buf.data(), w, h);
+ pb32.steal_data(tmp_buf);
+ }
+#endif
+
+ // xBRZ scaling
+ if (settings.rend.TextureUpscale > 1)
+ {
+ PixelBuffer tmp_buf;
+ tmp_buf.init(w * settings.rend.TextureUpscale, h * settings.rend.TextureUpscale);
+
+ if (tcw.PixelFmt == Pixel1555 || tcw.PixelFmt == Pixel4444)
+ // Alpha channel formats. Palettes with alpha are already handled
+ has_alpha = true;
+ UpscalexBRZ(settings.rend.TextureUpscale, pb32.data(), tmp_buf.data(), w, h, has_alpha);
+ pb32.steal_data(tmp_buf);
+ upscaled_w *= settings.rend.TextureUpscale;
+ upscaled_h *= settings.rend.TextureUpscale;
+ }
+ temp_tex_buffer = pb32.data();
+ }
+ else if (texconv != NULL)
+ {
+ pb16.init(w, h);
+
+ texconv(&pb16,(u8*)&vram[sa],stride,h);
+ temp_tex_buffer = pb16.data();
+ }
+ else
+ {
+ //fill it in with a temp color
+ printf("UNHANDLED TEXTURE\n");
+ pb16.init(w, h);
+ memset(pb16.data(), 0x80, w * h * 2);
+ temp_tex_buffer = pb16.data();
+ }
+ // Restore the original texture height if it was constrained to VRAM limits above
+ h = original_h;
+
+ //lock the texture to detect changes in it
+ lock_block = libCore_vramlock_Lock(sa_tex,sa+size-1,this);
+
+ if (texID) {
+ //upload to OpenGL !
+ UploadToGPU(textype, upscaled_w, upscaled_h, (u8*)temp_tex_buffer);
+ if (settings.rend.DumpTextures)
+ {
+ ComputeHash();
+ dumpTexture(texture_hash, upscaled_w, upscaled_h, textype, temp_tex_buffer);
+ }
+ }
+ 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;
+
+ u16 *tex_data = (u16 *)temp_tex_buffer;
+ 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][tex_data[(x + 1) % w + (y + 1) % h * w]];
+ data[1] = decoded_colors[tex_type][tex_data[(x + 0) % w + (y + 1) % h * w]];
+ data[2] = decoded_colors[tex_type][tex_data[(x + 1) % w + (y + 0) % h * w]];
+ data[3] = decoded_colors[tex_type][tex_data[(x + 0) % w + (y + 0) % h * w]];
+ }
+ }
+ #else
+ die("Soft rend disabled, invalid code path");
+ #endif
+ }
+}
+
+void TextureCacheData::UploadToGPU(GLuint textype, int width, int height, u8 *temp_tex_buffer)
+{
+ //upload to OpenGL !
+ glcache.BindTexture(GL_TEXTURE_2D, texID);
+ GLuint comps=textype == GL_UNSIGNED_SHORT_5_6_5 ? GL_RGB : GL_RGBA;
#ifdef GLES
- GLuint actual_textype = textype == GL_UNSIGNED_INT_8_8_8_8 ? GL_UNSIGNED_BYTE : textype;
- glTexImage2D(GL_TEXTURE_2D, 0, comps, upscaled_w, upscaled_h, 0, comps, actual_textype,
+ GLuint actual_textype = textype == GL_UNSIGNED_INT_8_8_8_8 ? GL_UNSIGNED_BYTE : textype;
+ glTexImage2D(GL_TEXTURE_2D, 0, comps, width, height, 0, comps, actual_textype,
temp_tex_buffer);
#else
- glTexImage2D(GL_TEXTURE_2D, 0,comps , upscaled_w, upscaled_h, 0, comps, textype, temp_tex_buffer);
+ glTexImage2D(GL_TEXTURE_2D, 0,comps, width, height, 0, comps, textype, temp_tex_buffer);
#endif
- if (tcw.MipMapped && settings.rend.UseMipmaps)
- glGenerateMipmap(GL_TEXTURE_2D);
- //dumpTexture(texID, upscaled_w, upscaled_h, textype, temp_tex_buffer);
- }
- 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 (tcw.MipMapped && settings.rend.UseMipmaps)
+ glGenerateMipmap(GL_TEXTURE_2D);
+}
- u16 *tex_data = (u16 *)temp_tex_buffer;
- 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][tex_data[(x + 1) % w + (y + 1) % h * w]];
- data[1] = decoded_colors[tex_type][tex_data[(x + 0) % w + (y + 1) % h * w]];
- data[2] = decoded_colors[tex_type][tex_data[(x + 1) % w + (y + 0) % h * w]];
- data[3] = decoded_colors[tex_type][tex_data[(x + 0) % w + (y + 0) % h * w]];
- }
- }
- #else
- die("Soft rend disabled, invalid code path");
- #endif
- }
- }
-
- //true if : dirty or paletted texture and hashes don't match
- bool NeedsUpdate() {
- bool rc = dirty
- || (tcw.PixelFmt == PixelPal4 && palette_hash != pal_hash_16[tcw.PalSelect])
- || (tcw.PixelFmt == PixelPal8 && palette_hash != pal_hash_256[tcw.PalSelect >> 4]);
- return rc;
- }
-
- void Delete()
+void TextureCacheData::CheckCustomTexture()
+{
+ if (custom_image_data != NULL)
{
- if (pData) {
- #if FEAT_HAS_SOFTREND
- _mm_free(pData);
- pData = 0;
- #else
- die("softrend disabled, invalid codepath");
- #endif
- }
-
- if (texID) {
- glcache.DeleteTextures(1, &texID);
- }
- if (lock_block)
- libCore_vramlock_Unlock_block(lock_block);
- lock_block=0;
+ UploadToGPU(GL_UNSIGNED_BYTE, custom_width, custom_height, custom_image_data);
+ delete [] custom_image_data;
+ custom_image_data = NULL;
}
-};
+}
+
+//true if : dirty or paletted texture and hashes don't match
+bool TextureCacheData::NeedsUpdate() {
+ bool rc = dirty
+ || (tcw.PixelFmt == PixelPal4 && palette_hash != pal_hash_16[tcw.PalSelect])
+ || (tcw.PixelFmt == PixelPal8 && palette_hash != pal_hash_256[tcw.PalSelect >> 4]);
+ return rc;
+}
+
+void TextureCacheData::Delete()
+{
+ if (pData) {
+ #if FEAT_HAS_SOFTREND
+ _mm_free(pData);
+ pData = 0;
+ #else
+ die("softrend disabled, invalid codepath");
+ #endif
+ }
+
+ if (texID) {
+ glcache.DeleteTextures(1, &texID);
+ }
+ if (lock_block)
+ libCore_vramlock_Unlock_block(lock_block);
+ lock_block=0;
+ if (custom_image_data != NULL)
+ delete [] custom_image_data;
+}
+
#include