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 map TexCache; @@ -855,7 +870,10 @@ GLuint gl_GetTexture(TSP tsp, TCW tcw) if (tf->NeedsUpdate()) tf->Update(); else + { + tf->CheckCustomTexture(); TexCacheHits++; + } // if (os_GetSeconds() - LastTexCacheStats >= 2.0) // { diff --git a/core/types.h b/core/types.h index cbf1a70c4..6e5ec10fe 100644 --- a/core/types.h +++ b/core/types.h @@ -724,6 +724,8 @@ struct settings_t int TextureUpscale; int MaxFilteredTextureSize; f32 ExtraDepthScale; + bool CustomTextures; + bool DumpTextures; } rend; struct diff --git a/shell/apple/emulator-osx/reicast-osx.xcodeproj/project.pbxproj b/shell/apple/emulator-osx/reicast-osx.xcodeproj/project.pbxproj index 3fb54846f..08ee731e4 100644 --- a/shell/apple/emulator-osx/reicast-osx.xcodeproj/project.pbxproj +++ b/shell/apple/emulator-osx/reicast-osx.xcodeproj/project.pbxproj @@ -205,6 +205,7 @@ AE2A2D7E21D6851E004B308D /* Lzma2Dec.c in Sources */ = {isa = PBXBuildFile; fileRef = AE2A2D7021D6851D004B308D /* Lzma2Dec.c */; }; AE2A2D7F21D6851E004B308D /* 7zFile.c in Sources */ = {isa = PBXBuildFile; fileRef = AE2A2D7321D6851D004B308D /* 7zFile.c */; }; AE2A2D8021D6851E004B308D /* 7zDec.c in Sources */ = {isa = PBXBuildFile; fileRef = AE2A2D7721D6851E004B308D /* 7zDec.c */; }; + AE2A2D8321D7DB78004B308D /* CustomTexture.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AE2A2D8121D7DB77004B308D /* CustomTexture.cpp */; }; AE4FF4E1211588B5009BF202 /* font.png in Resources */ = {isa = PBXBuildFile; fileRef = AE4FF4E0211588B5009BF202 /* font.png */; }; AE649BB62188689000EF4A81 /* xxhash.c in Sources */ = {isa = PBXBuildFile; fileRef = AE649BB42188689000EF4A81 /* xxhash.c */; }; AE649BF3218C552500EF4A81 /* bitmath.c in Sources */ = {isa = PBXBuildFile; fileRef = AE649BCD218C552500EF4A81 /* bitmath.c */; }; @@ -657,6 +658,8 @@ AE2A2D7521D6851D004B308D /* 7zCrc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 7zCrc.h; sourceTree = ""; }; AE2A2D7621D6851E004B308D /* Lzma2Dec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Lzma2Dec.h; sourceTree = ""; }; AE2A2D7721D6851E004B308D /* 7zDec.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = 7zDec.c; sourceTree = ""; }; + AE2A2D8121D7DB77004B308D /* CustomTexture.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CustomTexture.cpp; sourceTree = ""; }; + AE2A2D8221D7DB78004B308D /* CustomTexture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CustomTexture.h; sourceTree = ""; }; AE4FF4E0211588B5009BF202 /* font.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = font.png; path = ../../../linux/font.png; sourceTree = ""; }; AE649BB42188689000EF4A81 /* xxhash.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = xxhash.c; sourceTree = ""; }; AE649BB52188689000EF4A81 /* xxhash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = xxhash.h; sourceTree = ""; }; @@ -1586,6 +1589,8 @@ 84B7BE9B1B72720200F9733F /* gles */ = { isa = PBXGroup; children = ( + AE2A2D8121D7DB77004B308D /* CustomTexture.cpp */, + AE2A2D8221D7DB78004B308D /* CustomTexture.h */, AE2A2D4921D6820D004B308D /* glcache.h */, 84B7BE9C1B72720200F9733F /* gldraw.cpp */, 84B7BE9D1B72720200F9733F /* gles.cpp */, @@ -2119,6 +2124,7 @@ 84B7BEB31B72720200F9733F /* coreio.cpp in Sources */, 84B7BF281B72720200F9733F /* dsp.cpp in Sources */, 84B7BF3D1B72720200F9733F /* Renderer_if.cpp in Sources */, + AE2A2D8321D7DB78004B308D /* CustomTexture.cpp in Sources */, 84B7BF191B72720200F9733F /* deflate.c in Sources */, 84B7BF7E1B72720200F9733F /* TexCache.cpp in Sources */, AEFF7F4E214D9D590068CE11 /* pico_dev_ppp.c in Sources */,