Custom textures loading and dumping

This commit is contained in:
Flyinghead 2018-12-30 18:42:55 +01:00
parent 031a40ec81
commit dd280ee24d
8 changed files with 625 additions and 347 deletions

View File

@ -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);

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
#include "CustomTexture.h"
#include <algorithm>
#include <sstream>
#include <sys/types.h>
#include <dirent.h>
#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();
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
#ifndef CORE_REND_GLES_CUSTOMTEXTURE_H_
#define CORE_REND_GLES_CUSTOMTEXTURE_H_
#include <string>
#include <set>
#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<u32> unknown_hashes;
cThread loader_thread;
cResetEvent wakeup_thread;
std::vector<struct TextureCacheData *> work_queue;
cMutex work_queue_mutex;
};
#endif /* CORE_REND_GLES_CUSTOMTEXTURE_H_ */

View File

@ -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
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;
}

View File

@ -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 pixel_type> class PixelBuffer;
typedef void TexConvFP(PixelBuffer<u16>* pb,u8* p_in,u32 Width,u32 Height);
typedef void TexConvFP32(PixelBuffer<u32>* 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();
};

View File

@ -1,8 +1,13 @@
#include <sstream>
#include <sys/stat.h>
#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<u16>* pb,u8* p_in,u32 Width,u32 Height);
typedef void TexConvFP32(PixelBuffer<u32>* 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;
@ -211,40 +230,8 @@ static void dumpTexture(int texID, int w, int h, GLuint textype, void *temp_tex_
}
//Texture Cache :)
struct TextureCacheData
void TextureCacheData::PrintTextureName()
{
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
void PrintTextureName()
{
printf("Texture: %s ",tex?tex->name:"?format?");
if (tcw.VQ_Comp)
@ -261,16 +248,11 @@ struct TextureCacheData
printf(" %dx%d @ 0x%X",8<<tsp.TexU,8<<tsp.TexV,tcw.TexAddr<<3);
printf(" id=%d\n", texID);
}
}
bool IsPaletted()
{
return tcw.PixelFmt == PixelPal4 || tcw.PixelFmt == PixelPal8;
}
//Create GL texture from tsp/tcw
void Create(bool isGL)
{
//Create GL texture from tsp/tcw
void TextureCacheData::Create(bool isGL)
{
//ask GL for texture ID
if (isGL) {
texID = glcache.GenTexture();
@ -352,7 +334,7 @@ struct TextureCacheData
}
else
{
verify(tex->TW != NULL || tex->TW32 != NULL)
verify(tex->TW != NULL || tex->TW32 != NULL);
if (tcw.MipMapped)
sa+=MipPoint[tsp.TexU]*tex->bpp/2;
texconv = tex->TW;
@ -367,10 +349,17 @@ struct TextureCacheData
texconv = NULL;
texconv32 = NULL;
}
}
}
void Update()
{
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;
@ -417,6 +406,10 @@ struct TextureCacheData
return;
}
}
if (settings.rend.CustomTextures)
{
custom_texture.LoadCustomTextureAsync(this);
}
void *temp_tex_buffer = NULL;
u32 upscaled_w = w;
@ -495,18 +488,12 @@ struct TextureCacheData
if (texID) {
//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,
temp_tex_buffer);
#else
glTexImage2D(GL_TEXTURE_2D, 0,comps , upscaled_w, upscaled_h, 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);
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
@ -537,18 +524,44 @@ struct TextureCacheData
die("Soft rend disabled, invalid code path");
#endif
}
}
}
//true if : dirty or paletted texture and hashes don't match
bool NeedsUpdate() {
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, width, height, 0, comps, actual_textype,
temp_tex_buffer);
#else
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);
}
void TextureCacheData::CheckCustomTexture()
{
if (custom_image_data != NULL)
{
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 Delete()
{
void TextureCacheData::Delete()
{
if (pData) {
#if FEAT_HAS_SOFTREND
_mm_free(pData);
@ -564,8 +577,10 @@ struct TextureCacheData
if (lock_block)
libCore_vramlock_Unlock_block(lock_block);
lock_block=0;
}
};
if (custom_image_data != NULL)
delete [] custom_image_data;
}
#include <map>
map<u64,TextureCacheData> 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)
// {

View File

@ -724,6 +724,8 @@ struct settings_t
int TextureUpscale;
int MaxFilteredTextureSize;
f32 ExtraDepthScale;
bool CustomTextures;
bool DumpTextures;
} rend;
struct

View File

@ -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 = "<group>"; };
AE2A2D7621D6851E004B308D /* Lzma2Dec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Lzma2Dec.h; sourceTree = "<group>"; };
AE2A2D7721D6851E004B308D /* 7zDec.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = 7zDec.c; sourceTree = "<group>"; };
AE2A2D8121D7DB77004B308D /* CustomTexture.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CustomTexture.cpp; sourceTree = "<group>"; };
AE2A2D8221D7DB78004B308D /* CustomTexture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CustomTexture.h; sourceTree = "<group>"; };
AE4FF4E0211588B5009BF202 /* font.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = font.png; path = ../../../linux/font.png; sourceTree = "<group>"; };
AE649BB42188689000EF4A81 /* xxhash.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = xxhash.c; sourceTree = "<group>"; };
AE649BB52188689000EF4A81 /* xxhash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = xxhash.h; sourceTree = "<group>"; };
@ -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 */,