mirror of
https://github.com/hrydgard/ppsspp.git
synced 2025-04-02 11:01:50 -04:00
Perform screenshot processing (including image encode) on background tasks
This commit is contained in:
parent
14ee85139d
commit
b421c0791f
8 changed files with 117 additions and 62 deletions
|
@ -7,6 +7,22 @@
|
|||
#include "Common/Thread/Channel.h"
|
||||
#include "Common/Thread/ThreadManager.h"
|
||||
|
||||
// Nobody needs to wait for this (except threadpool shutdown).
|
||||
template<class T>
|
||||
class IndependentTask : public Task {
|
||||
public:
|
||||
IndependentTask(TaskType type, TaskPriority prio, T func) : func_(std::move(func)), type_(type), prio_(prio) {}
|
||||
TaskType Type() const override { return type_; }
|
||||
TaskPriority Priority() const override { return prio_; }
|
||||
void Run() override {
|
||||
func_();
|
||||
}
|
||||
private:
|
||||
T func_;
|
||||
TaskType type_;
|
||||
TaskPriority prio_;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
class PromiseTask : public Task {
|
||||
public:
|
||||
|
|
|
@ -471,8 +471,7 @@ double g_lastSaveTime = -1.0;
|
|||
Enqueue(Operation(SAVESTATE_SAVE_SCREENSHOT, filename, -1, nullptr, nullptr));
|
||||
}
|
||||
|
||||
bool CanRewind()
|
||||
{
|
||||
bool CanRewind() {
|
||||
return !rewindStates.Empty();
|
||||
}
|
||||
|
||||
|
@ -966,11 +965,9 @@ double g_lastSaveTime = -1.0;
|
|||
|
||||
bool readbackImage = false;
|
||||
|
||||
for (size_t i = 0, n = operations.size(); i < n; ++i) {
|
||||
Operation &op = operations[i];
|
||||
for (const auto &op : operations) {
|
||||
CChunkFileReader::Error result;
|
||||
Status callbackResult;
|
||||
bool tempResult;
|
||||
std::string callbackMessage;
|
||||
std::string title;
|
||||
|
||||
|
@ -1056,7 +1053,8 @@ double g_lastSaveTime = -1.0;
|
|||
break;
|
||||
|
||||
case SAVESTATE_VERIFY:
|
||||
tempResult = CChunkFileReader::Verify(state) == CChunkFileReader::ERROR_NONE;
|
||||
{
|
||||
int tempResult = CChunkFileReader::Verify(state) == CChunkFileReader::ERROR_NONE;
|
||||
callbackResult = tempResult ? Status::SUCCESS : Status::FAILURE;
|
||||
if (tempResult) {
|
||||
INFO_LOG(Log::SaveState, "Verified save state system");
|
||||
|
@ -1064,6 +1062,7 @@ double g_lastSaveTime = -1.0;
|
|||
ERROR_LOG(Log::SaveState, "Save state system verification failed");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SAVESTATE_REWIND:
|
||||
INFO_LOG(Log::SaveState, "Rewinding to recent savestate snapshot");
|
||||
|
@ -1093,18 +1092,32 @@ double g_lastSaveTime = -1.0;
|
|||
|
||||
case SAVESTATE_SAVE_SCREENSHOT:
|
||||
{
|
||||
_dbg_assert_(!op.callback);
|
||||
|
||||
int maxResMultiplier = 2;
|
||||
tempResult = TakeGameScreenshot(nullptr, op.filename, ScreenshotFormat::JPG, SCREENSHOT_DISPLAY, maxResMultiplier);
|
||||
callbackResult = tempResult ? Status::SUCCESS : Status::FAILURE;
|
||||
if (!tempResult) {
|
||||
ScreenshotResult tempResult = TakeGameScreenshot(nullptr, op.filename, ScreenshotFormat::JPG, SCREENSHOT_DISPLAY, maxResMultiplier, [](bool success) {
|
||||
if (success) {
|
||||
screenshotFailures = 0;
|
||||
}
|
||||
});
|
||||
|
||||
switch (tempResult) {
|
||||
case ScreenshotResult::ScreenshotNotPossible:
|
||||
// Try again soon, for a short while.
|
||||
callbackResult = Status::FAILURE;
|
||||
WARN_LOG(Log::SaveState, "Failed to take a screenshot for the savestate! (%s) The savestate will lack an icon.", op.filename.c_str());
|
||||
if (coreState != CORE_STEPPING_CPU && screenshotFailures++ < SCREENSHOT_FAILURE_RETRIES) {
|
||||
// Requeue for next frame (if we were stepping, no point, will just spam errors quickly).
|
||||
SaveScreenshot(op.filename);
|
||||
}
|
||||
} else {
|
||||
screenshotFailures = 0;
|
||||
break;
|
||||
case ScreenshotResult::DelayedResult:
|
||||
case ScreenshotResult::Success:
|
||||
// We might not know if the file write succeeded yet though.
|
||||
callbackResult = Status::SUCCESS;
|
||||
break;
|
||||
}
|
||||
|
||||
readbackImage = true;
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -26,7 +26,10 @@
|
|||
#include "Common/File/FileUtil.h"
|
||||
#include "Common/File/Path.h"
|
||||
#include "Common/Log.h"
|
||||
#include "Common/System/System.h"
|
||||
#include "Common/System/Display.h"
|
||||
#include "Common/System/NativeApp.h"
|
||||
#include "Common/Thread/Promise.h"
|
||||
#include "Core/Screenshot.h"
|
||||
#include "Core/System.h"
|
||||
#include "GPU/Common/GPUDebugInterface.h"
|
||||
|
@ -328,7 +331,7 @@ static GPUDebugBuffer ApplyRotation(const GPUDebugBuffer &buf, DisplayRotation r
|
|||
return rotated;
|
||||
}
|
||||
|
||||
bool TakeGameScreenshot(Draw::DrawContext *draw, const Path &filename, ScreenshotFormat fmt, ScreenshotType type, int maxRes) {
|
||||
ScreenshotResult TakeGameScreenshot(Draw::DrawContext *draw, const Path &filename, ScreenshotFormat fmt, ScreenshotType type, int maxRes, std::function<void(bool success)> callback) {
|
||||
GPUDebugBuffer buf;
|
||||
u32 w = (u32)-1;
|
||||
u32 h = (u32)-1;
|
||||
|
@ -336,10 +339,10 @@ bool TakeGameScreenshot(Draw::DrawContext *draw, const Path &filename, Screensho
|
|||
if (type == SCREENSHOT_DISPLAY || type == SCREENSHOT_RENDER) {
|
||||
if (!gpuDebug) {
|
||||
ERROR_LOG(Log::System, "Can't take screenshots when GPU not running");
|
||||
return false;
|
||||
return ScreenshotResult::ScreenshotNotPossible;
|
||||
}
|
||||
if (!gpuDebug->GetCurrentFramebuffer(buf, type == SCREENSHOT_RENDER ? GPU_DBG_FRAMEBUF_RENDER : GPU_DBG_FRAMEBUF_DISPLAY, maxRes)) {
|
||||
return false;
|
||||
return ScreenshotResult::ScreenshotNotPossible;
|
||||
}
|
||||
w = maxRes > 0 ? 480 * maxRes : PSP_CoreParameter().renderWidth;
|
||||
h = maxRes > 0 ? 272 * maxRes : PSP_CoreParameter().renderHeight;
|
||||
|
@ -347,25 +350,35 @@ bool TakeGameScreenshot(Draw::DrawContext *draw, const Path &filename, Screensho
|
|||
_dbg_assert_(draw);
|
||||
GPUDebugBuffer temp;
|
||||
if (!::GetOutputFramebuffer(draw, temp)) {
|
||||
return false;
|
||||
return ScreenshotResult::ScreenshotNotPossible;
|
||||
}
|
||||
buf = ApplyRotation(temp, g_display.rotation);
|
||||
} else {
|
||||
_dbg_assert_(draw);
|
||||
if (!GetOutputFramebuffer(draw, buf)) {
|
||||
return false;
|
||||
return ScreenshotResult::ScreenshotNotPossible;
|
||||
}
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
g_threadManager.EnqueueTask(new IndependentTask(TaskType::CPU_COMPUTE, TaskPriority::LOW,
|
||||
[buf = std::move(buf), callback = std::move(callback), filename, fmt, w, h]() {
|
||||
u8 *flipbuffer = nullptr;
|
||||
u32 width = w, height = h;
|
||||
const u8 *buffer = ConvertBufferToScreenshot(buf, false, flipbuffer, width, height);
|
||||
bool success = Save888RGBScreenshot(filename, fmt, buffer, width, height);
|
||||
delete[] flipbuffer;
|
||||
System_RunOnMainThread([success, callback = std::move(callback)]() {
|
||||
callback(success);
|
||||
});
|
||||
}));
|
||||
return ScreenshotResult::DelayedResult;
|
||||
}
|
||||
u8 *flipbuffer = nullptr;
|
||||
const u8 *buffer = ConvertBufferToScreenshot(buf, false, flipbuffer, w, h);
|
||||
if (!buffer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = Save888RGBScreenshot(filename, fmt, buffer, w, h);
|
||||
delete[] flipbuffer;
|
||||
return true;
|
||||
return success ? ScreenshotResult::Success : ScreenshotResult::FailedToWriteFile;
|
||||
}
|
||||
|
||||
bool Save888RGBScreenshot(const Path &filename, ScreenshotFormat fmt, const u8 *bufferRGB888, int w, int h) {
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
|
||||
#include "Common/File/Path.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
struct GPUDebugBuffer;
|
||||
namespace Draw {
|
||||
class DrawContext;
|
||||
|
@ -29,6 +31,8 @@ enum class ScreenshotFormat {
|
|||
JPG,
|
||||
};
|
||||
|
||||
// NOTE: The first two may need rotation, depending on the backend and screen orientation.
|
||||
// This is handled internally in TakeGameScreenshot().
|
||||
enum ScreenshotType {
|
||||
// What's being show on screen (e.g. including FPS, etc.)
|
||||
SCREENSHOT_OUTPUT,
|
||||
|
@ -38,10 +42,19 @@ enum ScreenshotType {
|
|||
SCREENSHOT_RENDER,
|
||||
};
|
||||
|
||||
enum class ScreenshotResult {
|
||||
ScreenshotNotPossible,
|
||||
DelayedResult, // This specifies that the actual result is one of the two below and will arrive in the callback.
|
||||
// These result can be delayed and arrive in the callback, if one is specified.
|
||||
FailedToWriteFile,
|
||||
Success,
|
||||
};
|
||||
|
||||
const u8 *ConvertBufferToScreenshot(const GPUDebugBuffer &buf, bool alpha, u8 *&temp, u32 &w, u32 &h);
|
||||
|
||||
// Can only be used while in game.
|
||||
bool TakeGameScreenshot(Draw::DrawContext *draw, const Path &filename, ScreenshotFormat fmt, ScreenshotType type, int maxRes = -1);
|
||||
// If the callback is passed in, the saving action happens on a background thread.
|
||||
ScreenshotResult TakeGameScreenshot(Draw::DrawContext *draw, const Path &filename, ScreenshotFormat fmt, ScreenshotType type, int maxRes = -1, std::function<void(bool success)> callback = nullptr);
|
||||
|
||||
bool Save888RGBScreenshot(const Path &filename, ScreenshotFormat fmt, const u8 *bufferRGB888, int w, int h);
|
||||
bool Save8888RGBAScreenshot(const Path &filename, const u8 *bufferRGBA8888, int w, int h);
|
||||
|
|
|
@ -156,7 +156,7 @@ struct GPUDebugBuffer {
|
|||
u32 GetRawPixel(int x, int y) const;
|
||||
void SetRawPixel(int x, int y, u32 c);
|
||||
|
||||
u8 *GetDataWrite() {
|
||||
u8 *GetData() {
|
||||
return data_;
|
||||
}
|
||||
|
||||
|
|
|
@ -1384,7 +1384,7 @@ bool SoftGPU::GetCurrentFramebuffer(GPUDebugBuffer &buffer, GPUDebugFramebufferT
|
|||
buffer.Allocate(size.x, size.y, fmt);
|
||||
|
||||
const int depth = fmt == GE_FORMAT_8888 ? 4 : 2;
|
||||
u8 *dst = buffer.GetDataWrite();
|
||||
u8 *dst = buffer.GetData();
|
||||
const int byteWidth = size.x * depth;
|
||||
for (int16_t y = 0; y < size.y; ++y) {
|
||||
memcpy(dst, src, byteWidth);
|
||||
|
@ -1404,7 +1404,7 @@ bool SoftGPU::GetCurrentDepthbuffer(GPUDebugBuffer &buffer) {
|
|||
|
||||
const int depth = 2;
|
||||
const u8 *src = depthbuf.data;
|
||||
u8 *dst = buffer.GetDataWrite();
|
||||
u8 *dst = buffer.GetData();
|
||||
for (int16_t y = 0; y < size.y; ++y) {
|
||||
memcpy(dst, src, size.x * depth);
|
||||
dst += size.x * depth;
|
||||
|
@ -1430,7 +1430,7 @@ bool SoftGPU::GetCurrentStencilbuffer(GPUDebugBuffer &buffer) {
|
|||
DrawingCoords size = GetTargetSize(gstate.FrameBufStride());
|
||||
buffer.Allocate(size.x, size.y, GPU_DBG_FORMAT_8BIT);
|
||||
|
||||
u8 *row = buffer.GetDataWrite();
|
||||
u8 *row = buffer.GetData();
|
||||
for (int16_t y = 0; y < size.y; ++y) {
|
||||
for (int16_t x = 0; x < size.x; ++x) {
|
||||
row[x] = GetPixelStencil(gstate.FrameBufFormat(), gstate.FrameBufStride(), x, y);
|
||||
|
@ -1445,13 +1445,12 @@ bool SoftGPU::GetCurrentTexture(GPUDebugBuffer &buffer, int level, bool *isFrame
|
|||
return Rasterizer::GetCurrentTexture(buffer, level);
|
||||
}
|
||||
|
||||
bool SoftGPU::GetCurrentClut(GPUDebugBuffer &buffer)
|
||||
{
|
||||
bool SoftGPU::GetCurrentClut(GPUDebugBuffer &buffer) {
|
||||
const u32 bpp = gstate.getClutPaletteFormat() == GE_CMODE_32BIT_ABGR8888 ? 4 : 2;
|
||||
const u32 pixels = 1024 / bpp;
|
||||
|
||||
buffer.Allocate(pixels, 1, (GEBufferFormat)gstate.getClutPaletteFormat());
|
||||
memcpy(buffer.GetDataWrite(), clut, 1024);
|
||||
memcpy(buffer.GetData(), clut, 1024);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -949,12 +949,14 @@ static void TakeScreenshot(Draw::DrawContext *draw) {
|
|||
}
|
||||
|
||||
// First, find a free filename.
|
||||
int i = 0;
|
||||
|
||||
std::string gameId = g_paramSFO.GetDiscID();
|
||||
const std::string gameId = g_paramSFO.GetDiscID();
|
||||
|
||||
Path filename;
|
||||
while (i < 10000){
|
||||
int i = 0;
|
||||
// TODO: This is really potentially way too slow if there are a lot of images!
|
||||
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
if (g_Config.bScreenshotsAsPNG)
|
||||
filename = path / StringFromFormat("%s_%05d.png", gameId.c_str(), i);
|
||||
else
|
||||
|
@ -965,29 +967,27 @@ static void TakeScreenshot(Draw::DrawContext *draw) {
|
|||
i++;
|
||||
}
|
||||
|
||||
ScreenshotType type = SCREENSHOT_OUTPUT;
|
||||
if (g_Config.iScreenshotMode == (int)ScreenshotMode::GameImage) {
|
||||
type = SCREENSHOT_DISPLAY;
|
||||
}
|
||||
const ScreenshotType type = g_Config.iScreenshotMode == (int)ScreenshotMode::GameImage ? SCREENSHOT_DISPLAY : SCREENSHOT_OUTPUT;
|
||||
|
||||
bool success = TakeGameScreenshot(draw, filename, g_Config.bScreenshotsAsPNG ? ScreenshotFormat::PNG : ScreenshotFormat::JPG, type);
|
||||
if (success) {
|
||||
g_OSD.Show(OSDType::MESSAGE_FILE_LINK, filename.ToVisualString(), 0.0f, "screenshot_link");
|
||||
if (System_GetPropertyBool(SYSPROP_CAN_SHOW_FILE)) {
|
||||
g_OSD.SetClickCallback("screenshot_link", [](bool clicked, void *data) -> void {
|
||||
Path *path = reinterpret_cast<Path *>(data);
|
||||
if (clicked) {
|
||||
System_ShowFileInFolder(*path);
|
||||
} else {
|
||||
delete path;
|
||||
}
|
||||
}, new Path(filename));
|
||||
const ScreenshotResult result = TakeGameScreenshot(draw, filename, g_Config.bScreenshotsAsPNG ? ScreenshotFormat::PNG : ScreenshotFormat::JPG, type, -1, [filename](bool success) {
|
||||
if (success) {
|
||||
g_OSD.Show(OSDType::MESSAGE_FILE_LINK, filename.ToVisualString(), 0.0f, "screenshot_link");
|
||||
if (System_GetPropertyBool(SYSPROP_CAN_SHOW_FILE)) {
|
||||
g_OSD.SetClickCallback("screenshot_link", [](bool clicked, void *data) -> void {
|
||||
Path *path = reinterpret_cast<Path *>(data);
|
||||
if (clicked) {
|
||||
System_ShowFileInFolder(*path);
|
||||
} else {
|
||||
delete path;
|
||||
}
|
||||
}, new Path(filename));
|
||||
}
|
||||
} else {
|
||||
auto err = GetI18NCategory(I18NCat::ERRORS);
|
||||
g_OSD.Show(OSDType::MESSAGE_ERROR, err->T("Could not save screenshot file"));
|
||||
WARN_LOG(Log::System, "Failed to take screenshot.");
|
||||
}
|
||||
} else {
|
||||
auto err = GetI18NCategory(I18NCat::ERRORS);
|
||||
g_OSD.Show(OSDType::MESSAGE_ERROR, err->T("Could not save screenshot file"));
|
||||
WARN_LOG(Log::System, "Failed to take screenshot.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void CallbackPostRender(UIContext *dc, void *userdata) {
|
||||
|
|
|
@ -182,21 +182,22 @@ ScreenRenderFlags ReportScreen::render(ScreenRenderMode mode) {
|
|||
File::CreateDir(path);
|
||||
}
|
||||
screenshotFilename_ = path / ".reporting.jpg";
|
||||
if (TakeGameScreenshot(screenManager()->getDrawContext(), screenshotFilename_, ScreenshotFormat::JPG, SCREENSHOT_DISPLAY, 4)) {
|
||||
// Redo the views already, now with a screenshot included.
|
||||
RecreateViews();
|
||||
} else {
|
||||
// Good news (?), the views are good as-is without a screenshot.
|
||||
screenshotFilename_.clear();
|
||||
}
|
||||
ScreenshotResult ignored = TakeGameScreenshot(screenManager()->getDrawContext(), screenshotFilename_, ScreenshotFormat::JPG, SCREENSHOT_RENDER, 4, [this](bool success) {
|
||||
if (success) {
|
||||
// Redo the views already, now with a screenshot included.
|
||||
RecreateViews();
|
||||
} else {
|
||||
// Good news (?), the views are good as-is without a screenshot.
|
||||
screenshotFilename_.clear();
|
||||
}
|
||||
});
|
||||
tookScreenshot_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
// We take the screenshot first, then we start rendering.
|
||||
// We are the only screen visible so this avoid starting and then trying to resume a backbuffer render pass.
|
||||
ScreenRenderFlags flags = UIScreen::render(mode);
|
||||
|
||||
const ScreenRenderFlags flags = UIScreen::render(mode);
|
||||
return flags;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue