From 5dbc2b9267e6f3b7f97dc128ba071ad816897176 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 30 Apr 2016 15:03:39 -0700 Subject: [PATCH] Initial support for saving textures to PNGs. --- GPU/Common/TextureReplacer.cpp | 84 ++++++++++++++++++++++++++++++- GPU/Common/TextureReplacer.h | 5 +- GPU/Directx9/TextureCacheDX9.cpp | 2 +- GPU/GLES/TextureCache.cpp | 7 ++- GPU/Vulkan/TextureCacheVulkan.cpp | 2 +- 5 files changed, 91 insertions(+), 9 deletions(-) diff --git a/GPU/Common/TextureReplacer.cpp b/GPU/Common/TextureReplacer.cpp index 67c1beff6..adc6c737b 100644 --- a/GPU/Common/TextureReplacer.cpp +++ b/GPU/Common/TextureReplacer.cpp @@ -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 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) { diff --git a/GPU/Common/TextureReplacer.h b/GPU/Common/TextureReplacer.h index 5d2ce7026..fd02d6db5 100644 --- a/GPU/Common/TextureReplacer.h +++ b/GPU/Common/TextureReplacer.h @@ -19,6 +19,7 @@ #include #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 saveBuf; bool enabled_; std::string gameID_; std::string basePath_; diff --git a/GPU/Directx9/TextureCacheDX9.cpp b/GPU/Directx9/TextureCacheDX9.cpp index 31bd902e8..957cc14fd 100644 --- a/GPU/Directx9/TextureCacheDX9.cpp +++ b/GPU/Directx9/TextureCacheDX9.cpp @@ -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)); } } diff --git a/GPU/GLES/TextureCache.cpp b/GPU/GLES/TextureCache.cpp index d5a959991..a864d4511 100644 --- a/GPU/GLES/TextureCache.cpp +++ b/GPU/GLES/TextureCache.cpp @@ -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)); } } diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index 82d9a93f1..f31c66e8f 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -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);