Initial support for saving textures to PNGs.

This commit is contained in:
Unknown W. Brackets 2016-04-30 15:03:39 -07:00
parent cf53948cf6
commit 5dbc2b9267
5 changed files with 91 additions and 9 deletions

View File

@ -15,7 +15,13 @@
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#ifndef USING_QT_UI
// TODO: Make this <libpng17/png.h> and change the include path? Or move to Core?
#include "ext/libpng17/png.h"
#endif
#include "ext/xxhash.h"
#include "Common/ColorConv.h"
#include "Common/FileUtil.h"
#include "Core/Config.h"
#include "Core/System.h"
@ -81,14 +87,88 @@ ReplacedTexture TextureReplacer::FindReplacement(u32 hash) {
return result;
}
void TextureReplacer::NotifyTextureDecoded(u32 hash, const void *data, int pitch, int w, int h, ReplacedTextureFormat fmt) {
#ifndef USING_QT_UI
static bool WriteTextureToPNG(png_imagep image, const std::string &filename, int convert_to_8bit, const void *buffer, png_int_32 row_stride, const void *colormap) {
FILE *fp = File::OpenCFile(filename, "wb");
if (!fp) {
ERROR_LOG(COMMON, "Unable to open texture file for writing.");
return false;
}
if (png_image_write_to_stdio(image, fp, convert_to_8bit, buffer, row_stride, colormap)) {
if (fclose(fp) != 0) {
ERROR_LOG(COMMON, "Texture file write failed.");
return false;
}
return true;
} else {
ERROR_LOG(COMMON, "Texture PNG encode failed.");
fclose(fp);
remove(filename.c_str());
return false;
}
}
#endif
void TextureReplacer::NotifyTextureDecoded(u32 hash, u32 addr, const void *data, int pitch, int w, int h, ReplacedTextureFormat fmt) {
_assert_msg_(G3D, enabled_, "Replacement not enabled");
if (!g_Config.bSaveNewTextures) {
// Ignore.
return;
}
// TODO
char hashname[8 + 4 + 1] = {};
snprintf(hashname, sizeof(hashname), "%08x.png", hash);
const std::string filename = basePath_ + hashname;
// TODO: Check for ini ignored or aliased textures.
if (File::Exists(filename)) {
// Must've been decoded and saved as a new texture already.
return;
}
#ifdef USING_QT_UI
ERROR_LOG(G3D, "Replacement texture saving not implemented for Qt");
#else
if (fmt != ReplacedTextureFormat::F_8888) {
saveBuf.resize((pitch * h) / sizeof(u16));
switch (fmt) {
case ReplacedTextureFormat::F_5650:
ConvertRGBA565ToRGBA8888(saveBuf.data(), (const u16 *)data, (pitch * h) / sizeof(u16));
break;
case ReplacedTextureFormat::F_5551:
ConvertRGBA5551ToRGBA8888(saveBuf.data(), (const u16 *)data, (pitch * h) / sizeof(u16));
break;
case ReplacedTextureFormat::F_4444:
ConvertRGBA4444ToRGBA8888(saveBuf.data(), (const u16 *)data, (pitch * h) / sizeof(u16));
break;
case ReplacedTextureFormat::F_8888_BGRA:
ConvertBGRA8888ToRGBA8888(saveBuf.data(), (const u32 *)data, (pitch * h) / sizeof(u32));
break;
}
data = saveBuf.data();
}
// Only save the hashed portion of the PNG.
LookupHashRange(addr, w, h);
png_image png;
memset(&png, 0, sizeof(png));
png.version = PNG_IMAGE_VERSION;
png.format = PNG_FORMAT_RGBA;
png.width = w;
png.height = h;
bool success = WriteTextureToPNG(&png, filename, 0, data, pitch, nullptr);
png_image_free(&png);
if (png.warning_or_error >= 2) {
ERROR_LOG(COMMON, "Saving screenshot to PNG produced errors.");
} else if (success) {
NOTICE_LOG(G3D, "Saving texture for replacement: %08x / %dx%d", hash, w, h);
}
#endif
}
bool TextureReplacer::LookupHashRange(u32 addr, int &w, int &h) {

View File

@ -19,6 +19,7 @@
#include <vector>
#include "Common/Common.h"
#include "Common/MemoryUtil.h"
#include "GPU/ge_constants.h"
class TextureCacheCommon;
@ -29,6 +30,7 @@ enum class ReplacedTextureFormat {
F_5551,
F_4444,
F_8888,
F_8888_BGRA,
};
// These must match the constants in TextureCacheCommon.
@ -99,11 +101,12 @@ public:
ReplacedTexture FindReplacement(u32 hash);
void NotifyTextureDecoded(u32 hash, const void *data, int pitch, int w, int h, ReplacedTextureFormat fmt);
void NotifyTextureDecoded(u32 hash, u32 addr, const void *data, int pitch, int w, int h, ReplacedTextureFormat fmt);
protected:
bool LookupHashRange(u32 addr, int &w, int &h);
SimpleBuf<u32> saveBuf;
bool enabled_;
std::string gameID_;
std::string basePath_;

View File

@ -1674,7 +1674,7 @@ void TextureCacheDX9::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &re
if (replacer.Enabled()) {
int bpp = dstFmt == D3DFMT_A8R8G8B8 ? 4 : 2;
replacer.NotifyTextureDecoded(entry.fullhash, pixelData, w * bpp, w, h, FromD3D9Format(dstFmt));
replacer.NotifyTextureDecoded(entry.fullhash, entry.addr, pixelData, w * bpp, w, h, FromD3D9Format(dstFmt));
}
}

View File

@ -993,7 +993,7 @@ bool TextureCache::SetOffsetTexture(u32 offset) {
return false;
}
ReplacedTextureFormat FromGLESFormat(GLenum fmt) {
ReplacedTextureFormat FromGLESFormat(GLenum fmt, bool useBGRA = false) {
// TODO: 16-bit formats are incorrect, since swizzled.
switch (fmt) {
case GL_UNSIGNED_SHORT_5_6_5:
@ -1004,7 +1004,7 @@ ReplacedTextureFormat FromGLESFormat(GLenum fmt) {
return ReplacedTextureFormat::F_4444;
case GL_UNSIGNED_BYTE:
default:
return ReplacedTextureFormat::F_8888;
return useBGRA ? ReplacedTextureFormat::F_8888_BGRA : ReplacedTextureFormat::F_8888;
}
}
@ -1813,8 +1813,7 @@ void TextureCache::LoadTextureLevel(TexCacheEntry &entry, ReplacedTexture &repla
if (replacer.Enabled()) {
int bpp = dstFmt == GL_UNSIGNED_BYTE ? 4 : 2;
// TODO: BGRA.
replacer.NotifyTextureDecoded(entry.fullhash, pixelData, w * bpp, w, h, FromGLESFormat(dstFmt));
replacer.NotifyTextureDecoded(entry.fullhash, entry.addr, pixelData, (useUnpack ? bufw : w) * bpp, w, h, FromGLESFormat(dstFmt, useBGRA));
}
}

View File

@ -1348,7 +1348,7 @@ void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) {
} else {
LoadTextureLevel(*entry, (uint8_t *)data, stride, i, scaleFactor, dstFmt);
if (replacer.Enabled()) {
replacer.NotifyTextureDecoded(entry->fullhash, data, stride, mipWidth, mipHeight, FromVulkanFormat(actualFmt));
replacer.NotifyTextureDecoded(entry->fullhash, texaddr, data, stride, mipWidth, mipHeight, FromVulkanFormat(actualFmt));
}
}
entry->vkTex->texture_->UploadMip(i, mipWidth, mipHeight, texBuf, bufferOffset, stride / bpp);