mirror of
https://github.com/hrydgard/ppsspp.git
synced 2024-12-18 02:48:28 +00:00
Initial support for saving textures to PNGs.
This commit is contained in:
parent
cf53948cf6
commit
5dbc2b9267
@ -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) {
|
||||
|
@ -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_;
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user