mirror of
https://github.com/wiiu-env/ScreenshotWUPS.git
synced 2025-02-16 19:49:11 +01:00
Implement threaded screenshot saving
This commit is contained in:
parent
675815c195
commit
f96a28caf2
@ -13,3 +13,9 @@ typedef enum {
|
|||||||
IMAGE_SOURCE_TV = 1,
|
IMAGE_SOURCE_TV = 1,
|
||||||
IMAGE_SOURCE_DRC = 2,
|
IMAGE_SOURCE_DRC = 2,
|
||||||
} ImageSourceEnum;
|
} ImageSourceEnum;
|
||||||
|
|
||||||
|
enum ScreenshotState {
|
||||||
|
SCREENSHOT_STATE_READY,
|
||||||
|
SCREENSHOT_STATE_REQUESTED,
|
||||||
|
SCREENSHOT_STATE_SAVING,
|
||||||
|
};
|
||||||
|
@ -2,27 +2,41 @@
|
|||||||
#include "fs/FSUtils.h"
|
#include "fs/FSUtils.h"
|
||||||
#include "retain_vars.hpp"
|
#include "retain_vars.hpp"
|
||||||
#include "screenshot_utils.h"
|
#include "screenshot_utils.h"
|
||||||
|
#include "thread.h"
|
||||||
#include "utils/input.h"
|
#include "utils/input.h"
|
||||||
#include "utils/logger.h"
|
#include "utils/logger.h"
|
||||||
#include <coreinit/time.h>
|
#include <coreinit/cache.h>
|
||||||
#include <gx2/surface.h>
|
#include <gx2/surface.h>
|
||||||
#include <padscore/wpad.h>
|
#include <padscore/wpad.h>
|
||||||
#include <vpad/input.h>
|
#include <vpad/input.h>
|
||||||
#include <wups.h>
|
#include <wups.h>
|
||||||
|
|
||||||
static bool takeScreenshotTV = false;
|
|
||||||
static bool takeScreenshotDRC = false;
|
|
||||||
static uint8_t screenshotCoolDown = 0;
|
|
||||||
|
|
||||||
DECL_FUNCTION(int32_t, VPADRead, VPADChan chan, VPADStatus *buffer, uint32_t buffer_size, VPADReadError *error) {
|
DECL_FUNCTION(int32_t, VPADRead, VPADChan chan, VPADStatus *buffer, uint32_t buffer_size, VPADReadError *error) {
|
||||||
|
|
||||||
VPADReadError real_error;
|
VPADReadError real_error;
|
||||||
int32_t result = real_VPADRead(chan, buffer, buffer_size, &real_error);
|
int32_t result = real_VPADRead(chan, buffer, buffer_size, &real_error);
|
||||||
|
|
||||||
if (gEnabled) {
|
if (gEnabled && (gTakeScreenshotTV == SCREENSHOT_STATE_READY || gTakeScreenshotDRC == SCREENSHOT_STATE_READY)) {
|
||||||
if (result > 0 && real_error == VPAD_READ_SUCCESS && (buffer[0].hold == gButtonCombo) && screenshotCoolDown == 0 && OSIsHomeButtonMenuEnabled()) {
|
if (result > 0 && real_error == VPAD_READ_SUCCESS) {
|
||||||
takeScreenshotTV = gImageSource == IMAGE_SOURCE_TV_AND_DRC || gImageSource == IMAGE_SOURCE_TV;
|
if (((buffer[0].trigger & 0x000FFFFF) == gButtonCombo)) {
|
||||||
takeScreenshotDRC = gImageSource == IMAGE_SOURCE_TV_AND_DRC || gImageSource == IMAGE_SOURCE_DRC;
|
if (!OSIsHomeButtonMenuEnabled()) {
|
||||||
screenshotCoolDown = 60;
|
DEBUG_FUNCTION_LINE("Screenshots are disabled");
|
||||||
|
} else {
|
||||||
|
if (gImageSource == IMAGE_SOURCE_TV_AND_DRC || gImageSource == IMAGE_SOURCE_TV) {
|
||||||
|
if (gTakeScreenshotTV == SCREENSHOT_STATE_READY) {
|
||||||
|
DEBUG_FUNCTION_LINE("Requested screenshot for TV!");
|
||||||
|
gTakeScreenshotTV = SCREENSHOT_STATE_REQUESTED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (gImageSource == IMAGE_SOURCE_TV_AND_DRC || gImageSource == IMAGE_SOURCE_DRC) {
|
||||||
|
if (gTakeScreenshotDRC == SCREENSHOT_STATE_READY) {
|
||||||
|
DEBUG_FUNCTION_LINE("Requested screenshot for DRC!");
|
||||||
|
gTakeScreenshotDRC = SCREENSHOT_STATE_REQUESTED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OSMemoryBarrier();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,7 +49,7 @@ DECL_FUNCTION(int32_t, VPADRead, VPADChan chan, VPADStatus *buffer, uint32_t buf
|
|||||||
DECL_FUNCTION(void, WPADRead, WPADChan chan, WPADStatusProController *data) {
|
DECL_FUNCTION(void, WPADRead, WPADChan chan, WPADStatusProController *data) {
|
||||||
real_WPADRead(chan, data);
|
real_WPADRead(chan, data);
|
||||||
|
|
||||||
if (gEnabled && screenshotCoolDown == 0 && OSIsHomeButtonMenuEnabled()) {
|
if (gEnabled && OSIsHomeButtonMenuEnabled() && (gTakeScreenshotTV == SCREENSHOT_STATE_READY || gTakeScreenshotDRC == SCREENSHOT_STATE_READY)) {
|
||||||
if (data[0].err == 0) {
|
if (data[0].err == 0) {
|
||||||
if (data[0].extensionType != 0xFF) {
|
if (data[0].extensionType != 0xFF) {
|
||||||
bool takeScreenshot = false;
|
bool takeScreenshot = false;
|
||||||
@ -58,9 +72,19 @@ DECL_FUNCTION(void, WPADRead, WPADChan chan, WPADStatusProController *data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (takeScreenshot) {
|
if (takeScreenshot) {
|
||||||
takeScreenshotTV = gImageSource == IMAGE_SOURCE_TV_AND_DRC || gImageSource == IMAGE_SOURCE_TV;
|
if (gImageSource == IMAGE_SOURCE_TV_AND_DRC || gImageSource == IMAGE_SOURCE_TV) {
|
||||||
takeScreenshotDRC = gImageSource == IMAGE_SOURCE_TV_AND_DRC || gImageSource == IMAGE_SOURCE_DRC;
|
if (gTakeScreenshotTV == SCREENSHOT_STATE_READY) {
|
||||||
screenshotCoolDown = 60;
|
DEBUG_FUNCTION_LINE("Requested screenshot for TV!");
|
||||||
|
gTakeScreenshotTV = SCREENSHOT_STATE_REQUESTED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (gImageSource == IMAGE_SOURCE_TV_AND_DRC || gImageSource == IMAGE_SOURCE_DRC) {
|
||||||
|
if (gTakeScreenshotDRC == SCREENSHOT_STATE_READY) {
|
||||||
|
DEBUG_FUNCTION_LINE("Requested screenshot for DRC!");
|
||||||
|
gTakeScreenshotDRC = SCREENSHOT_STATE_REQUESTED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OSMemoryBarrier();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -68,20 +92,24 @@ DECL_FUNCTION(void, WPADRead, WPADChan chan, WPADStatusProController *data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DECL_FUNCTION(void, GX2CopyColorBufferToScanBuffer, const GX2ColorBuffer *colorBuffer, GX2ScanTarget scan_target) {
|
DECL_FUNCTION(void, GX2CopyColorBufferToScanBuffer, const GX2ColorBuffer *colorBuffer, GX2ScanTarget scan_target) {
|
||||||
|
if (gEnabled) {
|
||||||
if ((takeScreenshotTV || takeScreenshotDRC)) {
|
if (scan_target == GX2_SCAN_TARGET_TV && colorBuffer != nullptr && gTakeScreenshotTV == SCREENSHOT_STATE_REQUESTED) {
|
||||||
if (scan_target == GX2_SCAN_TARGET_TV && colorBuffer != nullptr && takeScreenshotTV) {
|
|
||||||
DEBUG_FUNCTION_LINE("Lets take a screenshot from TV.");
|
DEBUG_FUNCTION_LINE("Lets take a screenshot from TV.");
|
||||||
takeScreenshot((GX2ColorBuffer *) colorBuffer, scan_target, gTVSurfaceFormat, gOutputFormat, gQuality);
|
if (!takeScreenshot((GX2ColorBuffer *) colorBuffer, scan_target, gTVSurfaceFormat, gOutputFormat, gQuality)) {
|
||||||
takeScreenshotTV = false;
|
gTakeScreenshotTV = SCREENSHOT_STATE_READY;
|
||||||
} else if (scan_target == GX2_SCAN_TARGET_DRC0 && colorBuffer != nullptr && takeScreenshotDRC) {
|
} else {
|
||||||
|
gTakeScreenshotTV = SCREENSHOT_STATE_SAVING;
|
||||||
|
}
|
||||||
|
} else if (scan_target == GX2_SCAN_TARGET_DRC0 && colorBuffer != nullptr && gTakeScreenshotDRC == SCREENSHOT_STATE_REQUESTED) {
|
||||||
DEBUG_FUNCTION_LINE("Lets take a screenshot from DRC.");
|
DEBUG_FUNCTION_LINE("Lets take a screenshot from DRC.");
|
||||||
takeScreenshot((GX2ColorBuffer *) colorBuffer, scan_target, gDRCSurfaceFormat, gOutputFormat, gQuality);
|
if (!takeScreenshot((GX2ColorBuffer *) colorBuffer, scan_target, gDRCSurfaceFormat, gOutputFormat, gQuality)) {
|
||||||
takeScreenshotDRC = false;
|
gTakeScreenshotDRC = SCREENSHOT_STATE_READY;
|
||||||
|
} else {
|
||||||
|
gTakeScreenshotDRC = SCREENSHOT_STATE_SAVING;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (screenshotCoolDown > 0) {
|
|
||||||
screenshotCoolDown--;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
real_GX2CopyColorBufferToScanBuffer(colorBuffer, scan_target);
|
real_GX2CopyColorBufferToScanBuffer(colorBuffer, scan_target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "retain_vars.hpp"
|
#include "retain_vars.hpp"
|
||||||
|
#include "thread.h"
|
||||||
#include "utils/WUPSConfigItemButtonCombo.h"
|
#include "utils/WUPSConfigItemButtonCombo.h"
|
||||||
#include "utils/logger.h"
|
#include "utils/logger.h"
|
||||||
#include <coreinit/cache.h>
|
#include <coreinit/cache.h>
|
||||||
@ -7,7 +8,6 @@
|
|||||||
#include <malloc.h>
|
#include <malloc.h>
|
||||||
#include <nn/acp.h>
|
#include <nn/acp.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vpad/input.h>
|
|
||||||
#include <wups.h>
|
#include <wups.h>
|
||||||
#include <wups/config/WUPSConfigItemBoolean.h>
|
#include <wups/config/WUPSConfigItemBoolean.h>
|
||||||
#include <wups/config/WUPSConfigItemIntegerRange.h>
|
#include <wups/config/WUPSConfigItemIntegerRange.h>
|
||||||
@ -277,9 +277,12 @@ ON_APPLICATION_START() {
|
|||||||
} else {
|
} else {
|
||||||
gShortNameEn.clear();
|
gShortNameEn.clear();
|
||||||
}
|
}
|
||||||
|
startFSIOThreads();
|
||||||
VPADSetTVMenuInvalid(VPAD_CHAN_0, true);
|
VPADSetTVMenuInvalid(VPAD_CHAN_0, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ON_APPLICATION_REQUESTS_EXIT() {
|
ON_APPLICATION_REQUESTS_EXIT() {
|
||||||
|
stopFSIOThreads();
|
||||||
deinitLogging();
|
deinitLogging();
|
||||||
}
|
}
|
||||||
|
@ -2,4 +2,4 @@
|
|||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
|
||||||
#define VERSION "v0.1"
|
#define VERSION "v0.1"
|
||||||
#define VERSION_FULL VERSION VERSION_EXTRA
|
#define VERSION_FULL VERSION VERSION_EXTRA
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include "retain_vars.hpp"
|
#include "retain_vars.hpp"
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
GX2SurfaceFormat gTVSurfaceFormat = GX2_SURFACE_FORMAT_UNORM_R8_G8_B8_A8;
|
GX2SurfaceFormat gTVSurfaceFormat = GX2_SURFACE_FORMAT_UNORM_R8_G8_B8_A8;
|
||||||
GX2SurfaceFormat gDRCSurfaceFormat = GX2_SURFACE_FORMAT_UNORM_R8_G8_B8_A8;
|
GX2SurfaceFormat gDRCSurfaceFormat = GX2_SURFACE_FORMAT_UNORM_R8_G8_B8_A8;
|
||||||
ImageSourceEnum gImageSource = IMAGE_SOURCE_TV_AND_DRC;
|
ImageSourceEnum gImageSource = IMAGE_SOURCE_TV_AND_DRC;
|
||||||
@ -7,4 +8,7 @@ bool gEnabled = true;
|
|||||||
uint32_t gButtonCombo = 0;
|
uint32_t gButtonCombo = 0;
|
||||||
int32_t gQuality = 90;
|
int32_t gQuality = 90;
|
||||||
ImageOutputFormatEnum gOutputFormat = IMAGE_OUTPUT_FORMAT_JPEG;
|
ImageOutputFormatEnum gOutputFormat = IMAGE_OUTPUT_FORMAT_JPEG;
|
||||||
std::string gShortNameEn;
|
std::string gShortNameEn;
|
||||||
|
|
||||||
|
ScreenshotState gTakeScreenshotTV = SCREENSHOT_STATE_READY;
|
||||||
|
ScreenshotState gTakeScreenshotDRC = SCREENSHOT_STATE_READY;
|
@ -2,7 +2,6 @@
|
|||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include <gx2/surface.h>
|
#include <gx2/surface.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <wups.h>
|
|
||||||
|
|
||||||
extern bool gEnabled;
|
extern bool gEnabled;
|
||||||
extern ImageSourceEnum gImageSource;
|
extern ImageSourceEnum gImageSource;
|
||||||
@ -11,4 +10,7 @@ extern GX2SurfaceFormat gDRCSurfaceFormat;
|
|||||||
extern uint32_t gButtonCombo;
|
extern uint32_t gButtonCombo;
|
||||||
extern int32_t gQuality;
|
extern int32_t gQuality;
|
||||||
extern ImageOutputFormatEnum gOutputFormat;
|
extern ImageOutputFormatEnum gOutputFormat;
|
||||||
extern std::string gShortNameEn;
|
extern std::string gShortNameEn;
|
||||||
|
|
||||||
|
extern ScreenshotState gTakeScreenshotTV;
|
||||||
|
extern ScreenshotState gTakeScreenshotDRC;
|
@ -1,10 +1,10 @@
|
|||||||
#include "screenshot_utils.h"
|
#include "screenshot_utils.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "fs/FSUtils.h"
|
|
||||||
#include "retain_vars.hpp"
|
#include "retain_vars.hpp"
|
||||||
|
#include "thread.h"
|
||||||
#include "utils/StringTools.h"
|
#include "utils/StringTools.h"
|
||||||
#include <coreinit/title.h>
|
|
||||||
#include "utils/utils.h"
|
#include "utils/utils.h"
|
||||||
|
#include <coreinit/cache.h>
|
||||||
#include <gd.h>
|
#include <gd.h>
|
||||||
#include <gx2/event.h>
|
#include <gx2/event.h>
|
||||||
#include <gx2/mem.h>
|
#include <gx2/mem.h>
|
||||||
@ -127,10 +127,13 @@ static bool copyBuffer(GX2ColorBuffer *sourceBuffer, GX2ColorBuffer *targetBuffe
|
|||||||
GX2InitColorBufferRegs(targetBuffer);
|
GX2InitColorBufferRegs(targetBuffer);
|
||||||
|
|
||||||
// Let's allocate the memory.
|
// Let's allocate the memory.
|
||||||
|
// cannot be in unknown regions for GX2 like 0xBCAE1000
|
||||||
targetBuffer->surface.image = MEMAllocFromMappedMemoryForGX2Ex(targetBuffer->surface.imageSize, targetBuffer->surface.alignment);
|
targetBuffer->surface.image = MEMAllocFromMappedMemoryForGX2Ex(targetBuffer->surface.imageSize, targetBuffer->surface.alignment);
|
||||||
if (targetBuffer->surface.image == nullptr) {
|
if (targetBuffer->surface.image == nullptr) {
|
||||||
DEBUG_FUNCTION_LINE_ERR("Failed to allocate memory for the surface image");
|
DEBUG_FUNCTION_LINE_ERR("Failed to allocate %d bytes for the surface image", targetBuffer->surface.imageSize);
|
||||||
return false;
|
return false;
|
||||||
|
} else {
|
||||||
|
DEBUG_FUNCTION_LINE("Allocated %d bytes for the surface image", targetBuffer->surface.imageSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
GX2Invalidate(GX2_INVALIDATE_MODE_CPU, targetBuffer->surface.image, targetBuffer->surface.imageSize);
|
GX2Invalidate(GX2_INVALIDATE_MODE_CPU, targetBuffer->surface.image, targetBuffer->surface.imageSize);
|
||||||
@ -149,19 +152,18 @@ static bool copyBuffer(GX2ColorBuffer *sourceBuffer, GX2ColorBuffer *targetBuffe
|
|||||||
|
|
||||||
tempSurface.image = MEMAllocFromMappedMemoryForGX2Ex(tempSurface.imageSize, tempSurface.alignment);
|
tempSurface.image = MEMAllocFromMappedMemoryForGX2Ex(tempSurface.imageSize, tempSurface.alignment);
|
||||||
if (tempSurface.image == nullptr) {
|
if (tempSurface.image == nullptr) {
|
||||||
DEBUG_FUNCTION_LINE_ERR("failed to allocate data for resolving AA");
|
DEBUG_FUNCTION_LINE_ERR("Failed to allocate %d bytes for resolving AA", tempSurface.imageSize);
|
||||||
if (targetBuffer->surface.image != nullptr) {
|
if (targetBuffer->surface.image != nullptr) {
|
||||||
MEMFreeToMappedMemory(targetBuffer->surface.image);
|
MEMFreeToMappedMemory(targetBuffer->surface.image);
|
||||||
targetBuffer->surface.image = nullptr;
|
targetBuffer->surface.image = nullptr;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
} else {
|
||||||
|
DEBUG_FUNCTION_LINE("Allocated %d bytes for the surface image", targetBuffer->surface.imageSize);
|
||||||
}
|
}
|
||||||
GX2ResolveAAColorBuffer(sourceBuffer, &tempSurface, 0, 0);
|
GX2ResolveAAColorBuffer(sourceBuffer, &tempSurface, 0, 0);
|
||||||
GX2CopySurface(&tempSurface, 0, 0, &targetBuffer->surface, 0, 0);
|
GX2CopySurface(&tempSurface, 0, 0, &targetBuffer->surface, 0, 0);
|
||||||
|
|
||||||
// Sync CPU and GPU
|
|
||||||
GX2DrawDone();
|
|
||||||
|
|
||||||
if (tempSurface.image != nullptr) {
|
if (tempSurface.image != nullptr) {
|
||||||
MEMFreeToMappedMemory(tempSurface.image);
|
MEMFreeToMappedMemory(tempSurface.image);
|
||||||
tempSurface.image = nullptr;
|
tempSurface.image = nullptr;
|
||||||
@ -181,144 +183,79 @@ bool takeScreenshot(GX2ColorBuffer *srcBuffer, GX2ScanTarget scanTarget, GX2Surf
|
|||||||
}
|
}
|
||||||
|
|
||||||
GX2ColorBuffer colorBuffer;
|
GX2ColorBuffer colorBuffer;
|
||||||
GX2ColorBuffer *saveBuffer = nullptr;
|
|
||||||
|
|
||||||
// keep dimensions
|
// keep dimensions
|
||||||
uint32_t width = srcBuffer->surface.width;
|
uint32_t width = srcBuffer->surface.width;
|
||||||
uint32_t height = srcBuffer->surface.height;
|
uint32_t height = srcBuffer->surface.height;
|
||||||
|
|
||||||
OSCalendarTime output;
|
|
||||||
OSTicksToCalendarTime(OSGetTime(), &output);
|
|
||||||
std::string buffer = string_format("%s%016llX", WIIU_SCREENSHOT_PATH, OSGetTitleID());
|
|
||||||
if (!gShortNameEn.empty()) {
|
|
||||||
buffer += string_format(" (%s)", gShortNameEn.c_str());
|
|
||||||
}
|
|
||||||
buffer += string_format("/%04d-%02d-%02d/", output.tm_year, output.tm_mon + 1, output.tm_mday);
|
|
||||||
|
|
||||||
auto dir = opendir(buffer.c_str());
|
|
||||||
if (dir) {
|
|
||||||
closedir(dir);
|
|
||||||
} else {
|
|
||||||
if (!FSUtils::CreateSubfolder(buffer.c_str())) {
|
|
||||||
DEBUG_FUNCTION_LINE_ERR("Failed to create dir: %s", buffer.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string fullPath = string_format("%s%04d-%02d-%02d_%02d.%02d.%02d_",
|
|
||||||
buffer.c_str(), output.tm_year, output.tm_mon + 1,
|
|
||||||
output.tm_mday, output.tm_hour, output.tm_min, output.tm_sec);
|
|
||||||
|
|
||||||
if (scanTarget == GX2_SCAN_TARGET_DRC) {
|
|
||||||
fullPath += "DRC";
|
|
||||||
} else if (scanTarget == GX2_SCAN_TARGET_TV) {
|
|
||||||
fullPath += "TV";
|
|
||||||
} else if (scanTarget == GX2_SCAN_TARGET_DRC1) {
|
|
||||||
fullPath += "DRC2";
|
|
||||||
} else {
|
|
||||||
DEBUG_FUNCTION_LINE_ERR("Invalid scanTarget %d", scanTarget);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (outputFormat) {
|
|
||||||
case IMAGE_OUTPUT_FORMAT_JPEG:
|
|
||||||
fullPath += ".jpg";
|
|
||||||
break;
|
|
||||||
case IMAGE_OUTPUT_FORMAT_PNG:
|
|
||||||
fullPath += ".png";
|
|
||||||
break;
|
|
||||||
case IMAGE_OUTPUT_FORMAT_BMP:
|
|
||||||
fullPath += ".bmp";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
DEBUG_FUNCTION_LINE_WARN("Invalid output format, use jpeg instead");
|
|
||||||
fullPath += ".jpg";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool convertRGBToSRGB = false;
|
bool convertRGBToSRGB = false;
|
||||||
if ((outputBufferSurfaceFormat & 0x400)) {
|
if ((outputBufferSurfaceFormat & 0x400)) {
|
||||||
DEBUG_FUNCTION_LINE_VERBOSE("Images needs to be converted to SRGB");
|
DEBUG_FUNCTION_LINE_VERBOSE("Images needs to be converted to SRGB");
|
||||||
convertRGBToSRGB = true;
|
convertRGBToSRGB = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool valid;
|
bool res;
|
||||||
bool cancel = false;
|
|
||||||
do {
|
auto threadPriority = OSGetThreadPriority(OSGetCurrentThread()) + 1;
|
||||||
// At first, we need to copy the buffer to fit our resolution.
|
if (threadPriority != OSGetThreadPriority(gThreadData.thread)) {
|
||||||
if (saveBuffer == nullptr) {
|
DEBUG_FUNCTION_LINE_ERR("INFO! Set thread priority to %d", threadPriority);
|
||||||
do {
|
if (!OSSetThreadPriority(gThreadData.thread, threadPriority)) {
|
||||||
valid = copyBuffer(srcBuffer, &colorBuffer, width, height);
|
DEBUG_FUNCTION_LINE_WARN("Failed to set thread priority");
|
||||||
// If the copying failed, we don't have enough memory. Let's decrease the resolution.
|
|
||||||
if (!valid) {
|
|
||||||
if (height >= 1080) {
|
|
||||||
width = 1280;
|
|
||||||
height = 720;
|
|
||||||
DEBUG_FUNCTION_LINE("Switching to 720p.");
|
|
||||||
} else if (height >= 720) {
|
|
||||||
width = 854;
|
|
||||||
height = 480;
|
|
||||||
DEBUG_FUNCTION_LINE("Switching to 480p.");
|
|
||||||
} else if (height >= 480) {
|
|
||||||
width = 640;
|
|
||||||
height = 360;
|
|
||||||
DEBUG_FUNCTION_LINE("Switching to 360p.");
|
|
||||||
} else {
|
|
||||||
// Cancel the screenshot if the resolution would be too low.
|
|
||||||
cancel = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// On success save the pointer.
|
|
||||||
saveBuffer = &colorBuffer;
|
|
||||||
}
|
|
||||||
} while (!valid);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check if we should proceed
|
SaveScreenshotMessage *param;
|
||||||
if (cancel) {
|
|
||||||
// Free the memory on error.
|
bool valid = copyBuffer(srcBuffer, &colorBuffer, width, height);
|
||||||
if (colorBuffer.surface.image != nullptr) {
|
if (!valid) {
|
||||||
MEMFreeToMappedMemory(colorBuffer.surface.image);
|
if (width == 1920 && height == 1080) {
|
||||||
colorBuffer.surface.image = nullptr;
|
DEBUG_FUNCTION_LINE("Try to fallback to 720p");
|
||||||
}
|
valid = copyBuffer(srcBuffer, &colorBuffer, 1280, 720);
|
||||||
return false;
|
width = 1280;
|
||||||
|
height = 720;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flush out destinations caches
|
|
||||||
GX2Invalidate(GX2_INVALIDATE_MODE_COLOR_BUFFER, colorBuffer.surface.image, colorBuffer.surface.imageSize);
|
|
||||||
|
|
||||||
// Wait for GPU to finish
|
|
||||||
GX2DrawDone();
|
|
||||||
|
|
||||||
valid = saveTextureAsPicture(fullPath, (uint8_t *) saveBuffer->surface.image, width, height, saveBuffer->surface.pitch, saveBuffer->surface.format, outputFormat, convertRGBToSRGB, quality);
|
|
||||||
|
|
||||||
// Free the colorbuffer copy.
|
|
||||||
if (colorBuffer.surface.image != nullptr) {
|
|
||||||
MEMFreeToMappedMemory(colorBuffer.surface.image);
|
|
||||||
colorBuffer.surface.image = nullptr;
|
|
||||||
saveBuffer = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// When taking the screenshot failed, decrease the resolution again ~.
|
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
if (height >= 1080) {
|
DEBUG_FUNCTION_LINE_ERR("Failed to copy color buffer");
|
||||||
width = 1280;
|
goto error;
|
||||||
height = 720;
|
|
||||||
DEBUG_FUNCTION_LINE("Switching to 720p.");
|
|
||||||
} else if (height >= 720) {
|
|
||||||
width = 854;
|
|
||||||
height = 480;
|
|
||||||
DEBUG_FUNCTION_LINE("Switching to 480p.");
|
|
||||||
} else if (height >= 480) {
|
|
||||||
width = 640;
|
|
||||||
height = 360;
|
|
||||||
DEBUG_FUNCTION_LINE("Switching to 360p.");
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} while (!valid);
|
}
|
||||||
|
|
||||||
|
// Flush out destinations caches
|
||||||
|
GX2Invalidate(GX2_INVALIDATE_MODE_COLOR_BUFFER, colorBuffer.surface.image, colorBuffer.surface.imageSize);
|
||||||
|
|
||||||
|
// Wait for GPU to finish
|
||||||
|
GX2DrawDone();
|
||||||
|
|
||||||
|
param = (SaveScreenshotMessage *) malloc(sizeof(SaveScreenshotMessage));
|
||||||
|
if (!param) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
param->sourceBuffer = (uint8_t *) colorBuffer.surface.image;
|
||||||
|
param->width = width;
|
||||||
|
param->height = height;
|
||||||
|
param->pitch = colorBuffer.surface.pitch;
|
||||||
|
param->outputFormat = outputFormat;
|
||||||
|
param->convertRGBtoSRGB = convertRGBToSRGB;
|
||||||
|
param->quality = quality;
|
||||||
|
param->format = colorBuffer.surface.format;
|
||||||
|
param->scanTarget = scanTarget;
|
||||||
|
|
||||||
|
res = sendMessageToThread(param);
|
||||||
|
if (!res) {
|
||||||
|
free(param);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
OSMemoryBarrier();
|
||||||
return true;
|
return true;
|
||||||
|
error:
|
||||||
|
DEBUG_FUNCTION_LINE_ERR("Taking screenshot failed");
|
||||||
|
// Free the colorbuffer copy.
|
||||||
|
if (colorBuffer.surface.image != nullptr) {
|
||||||
|
MEMFreeToMappedMemory(colorBuffer.surface.image);
|
||||||
|
colorBuffer.surface.image = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
196
src/thread.cpp
Normal file
196
src/thread.cpp
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
#include "thread.h"
|
||||||
|
#include "fs/FSUtils.h"
|
||||||
|
#include "retain_vars.hpp"
|
||||||
|
#include "screenshot_utils.h"
|
||||||
|
#include "utils/StringTools.h"
|
||||||
|
#include "utils/logger.h"
|
||||||
|
#include <coreinit/cache.h>
|
||||||
|
#include <coreinit/title.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <malloc.h>
|
||||||
|
#include <memory/mappedmemory.h>
|
||||||
|
|
||||||
|
FSIOThreadData gThreadData;
|
||||||
|
bool gThreadsRunning;
|
||||||
|
|
||||||
|
bool getPath(GX2ScanTarget scanTarget, ImageOutputFormatEnum outputFormat, std::string &path) {
|
||||||
|
OSCalendarTime output;
|
||||||
|
OSTicksToCalendarTime(OSGetTime(), &output);
|
||||||
|
std::string buffer = string_format("%s%016llX", WIIU_SCREENSHOT_PATH, OSGetTitleID());
|
||||||
|
if (!gShortNameEn.empty()) {
|
||||||
|
buffer += string_format(" (%s)", gShortNameEn.c_str());
|
||||||
|
}
|
||||||
|
buffer += string_format("/%04d-%02d-%02d/", output.tm_year, output.tm_mon + 1, output.tm_mday);
|
||||||
|
|
||||||
|
auto dir = opendir(buffer.c_str());
|
||||||
|
if (dir) {
|
||||||
|
closedir(dir);
|
||||||
|
} else {
|
||||||
|
if (!FSUtils::CreateSubfolder(buffer.c_str())) {
|
||||||
|
DEBUG_FUNCTION_LINE_ERR("Failed to create dir: %s", buffer.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
path += string_format("%s%04d-%02d-%02d_%02d.%02d.%02d.%03d_",
|
||||||
|
buffer.c_str(), output.tm_year, output.tm_mon + 1,
|
||||||
|
output.tm_mday, output.tm_hour, output.tm_min, output.tm_sec, output.tm_msec);
|
||||||
|
|
||||||
|
if (scanTarget == GX2_SCAN_TARGET_DRC) {
|
||||||
|
path += "DRC";
|
||||||
|
} else if (scanTarget == GX2_SCAN_TARGET_TV) {
|
||||||
|
path += "TV";
|
||||||
|
} else if (scanTarget == GX2_SCAN_TARGET_DRC1) {
|
||||||
|
path += "DRC2";
|
||||||
|
} else {
|
||||||
|
DEBUG_FUNCTION_LINE_ERR("Invalid scanTarget %d", scanTarget);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (outputFormat) {
|
||||||
|
case IMAGE_OUTPUT_FORMAT_JPEG:
|
||||||
|
path += ".jpg";
|
||||||
|
break;
|
||||||
|
case IMAGE_OUTPUT_FORMAT_PNG:
|
||||||
|
path += ".png";
|
||||||
|
break;
|
||||||
|
case IMAGE_OUTPUT_FORMAT_BMP:
|
||||||
|
path += ".bmp";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
DEBUG_FUNCTION_LINE_WARN("Invalid output format, use jpeg instead");
|
||||||
|
path += ".jpg";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int32_t fsIOthreadCallback([[maybe_unused]] int argc, const char **argv) {
|
||||||
|
auto *magic = ((FSIOThreadData *) argv);
|
||||||
|
|
||||||
|
constexpr int32_t messageSize = sizeof(magic->messages) / sizeof(magic->messages[0]);
|
||||||
|
OSInitMessageQueue(&magic->queue, magic->messages, messageSize);
|
||||||
|
OSMessage recv;
|
||||||
|
while (OSReceiveMessage(&magic->queue, &recv, OS_MESSAGE_FLAGS_BLOCKING)) {
|
||||||
|
if (recv.args[0] == FS_IO_QUEUE_COMMAND_STOP) {
|
||||||
|
DEBUG_FUNCTION_LINE_VERBOSE("Received break command! Stop thread");
|
||||||
|
break;
|
||||||
|
} else if (recv.args[0] == FS_IO_QUEUE_COMMAND_PROCESS_FS_COMMAND) {
|
||||||
|
auto *message = (SaveScreenshotMessage *) recv.message;
|
||||||
|
|
||||||
|
std::string path;
|
||||||
|
bool success = false;
|
||||||
|
if (getPath(message->scanTarget, message->outputFormat, path)) {
|
||||||
|
DEBUG_FUNCTION_LINE("Saving to %s", path.c_str());
|
||||||
|
auto res = saveTextureAsPicture(path, message->sourceBuffer, message->width, message->height, message->pitch, message->format, message->outputFormat, message->convertRGBtoSRGB, message->quality);
|
||||||
|
if (res) {
|
||||||
|
DEBUG_FUNCTION_LINE("Saving screenshot was successful");
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DEBUG_FUNCTION_LINE_ERR("Failed to get and create path");
|
||||||
|
}
|
||||||
|
if (!success) {
|
||||||
|
DEBUG_FUNCTION_LINE("Saving screenshot failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message->scanTarget == GX2_SCAN_TARGET_TV) {
|
||||||
|
gTakeScreenshotTV = SCREENSHOT_STATE_READY;
|
||||||
|
} else if (message->scanTarget == GX2_SCAN_TARGET_DRC) {
|
||||||
|
gTakeScreenshotDRC = SCREENSHOT_STATE_READY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free the colorbuffer copy.
|
||||||
|
if (message->sourceBuffer != nullptr) {
|
||||||
|
MEMFreeToMappedMemory(message->sourceBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
OSMemoryBarrier();
|
||||||
|
free(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sendMessageToThread(SaveScreenshotMessage *param) {
|
||||||
|
auto *curThread = &gThreadData;
|
||||||
|
if (curThread->setup) {
|
||||||
|
OSMessage send;
|
||||||
|
send.message = param;
|
||||||
|
send.args[0] = FS_IO_QUEUE_COMMAND_PROCESS_FS_COMMAND;
|
||||||
|
OSMemoryBarrier();
|
||||||
|
return OSSendMessage(&curThread->queue, &send, OS_MESSAGE_FLAGS_NONE);
|
||||||
|
} else {
|
||||||
|
DEBUG_FUNCTION_LINE_ERR("Thread not setup");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void startFSIOThreads() {
|
||||||
|
auto stackSize = 16 * 1024;
|
||||||
|
|
||||||
|
auto *threadData = &gThreadData;
|
||||||
|
memset(threadData, 0, sizeof(*threadData));
|
||||||
|
threadData->setup = false;
|
||||||
|
threadData->thread = (OSThread *) memalign(8, sizeof(OSThread));
|
||||||
|
if (!threadData->thread) {
|
||||||
|
DEBUG_FUNCTION_LINE_ERR("Failed to allocate threadData");
|
||||||
|
OSFatal("ScreenshotPlugin: Failed to allocate IO Thread");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
threadData->stack = (uint8_t *) memalign(0x20, stackSize);
|
||||||
|
if (!threadData->thread) {
|
||||||
|
free(threadData->thread);
|
||||||
|
DEBUG_FUNCTION_LINE_ERR("Failed to allocate threadData stack");
|
||||||
|
OSFatal("ScreenshotPlugin: Failed to allocate IO Thread stack");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OSMemoryBarrier();
|
||||||
|
|
||||||
|
if (!OSCreateThread(threadData->thread, &fsIOthreadCallback, 1, (char *) threadData, reinterpret_cast<void *>((uint32_t) threadData->stack + stackSize), stackSize, 31, OS_THREAD_ATTRIB_AFFINITY_ANY)) {
|
||||||
|
free(threadData->thread);
|
||||||
|
free(threadData->stack);
|
||||||
|
threadData->setup = false;
|
||||||
|
DEBUG_FUNCTION_LINE_ERR("failed to create threadData");
|
||||||
|
OSFatal("ContentRedirectionModule: Failed to create threadData");
|
||||||
|
}
|
||||||
|
|
||||||
|
strncpy(threadData->threadName, "ScreenshotPlugin IO Thread", sizeof(threadData->threadName) - 1);
|
||||||
|
OSSetThreadName(threadData->thread, threadData->threadName);
|
||||||
|
OSResumeThread(threadData->thread);
|
||||||
|
threadData->setup = true;
|
||||||
|
|
||||||
|
gThreadsRunning = true;
|
||||||
|
OSMemoryBarrier();
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopFSIOThreads() {
|
||||||
|
if (!gThreadsRunning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto *thread = &gThreadData;
|
||||||
|
if (!thread->setup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
OSMessage message;
|
||||||
|
message.args[0] = FS_IO_QUEUE_COMMAND_STOP;
|
||||||
|
OSSendMessage(&thread->queue, &message, OS_MESSAGE_FLAGS_BLOCKING);
|
||||||
|
|
||||||
|
if (OSIsThreadSuspended(thread->thread)) {
|
||||||
|
OSResumeThread(thread->thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
OSJoinThread(thread->thread, nullptr);
|
||||||
|
if (thread->stack) {
|
||||||
|
free(thread->stack);
|
||||||
|
thread->stack = nullptr;
|
||||||
|
}
|
||||||
|
if (thread->thread) {
|
||||||
|
free(thread->thread);
|
||||||
|
thread->thread = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
gThreadsRunning = false;
|
||||||
|
}
|
39
src/thread.h
Normal file
39
src/thread.h
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "common.h"
|
||||||
|
#include <coreinit/messagequeue.h>
|
||||||
|
#include <coreinit/semaphore.h>
|
||||||
|
#include <coreinit/thread.h>
|
||||||
|
#include <gx2/enum.h>
|
||||||
|
#include <memory.h>
|
||||||
|
|
||||||
|
struct FSIOThreadData {
|
||||||
|
OSThread *thread;
|
||||||
|
void *stack;
|
||||||
|
OSMessageQueue queue;
|
||||||
|
OSMessage messages[2];
|
||||||
|
bool setup;
|
||||||
|
char threadName[0x50];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SaveScreenshotMessage {
|
||||||
|
uint8_t *sourceBuffer;
|
||||||
|
uint32_t width;
|
||||||
|
uint32_t height;
|
||||||
|
uint32_t pitch;
|
||||||
|
GX2SurfaceFormat format;
|
||||||
|
ImageOutputFormatEnum outputFormat;
|
||||||
|
bool convertRGBtoSRGB;
|
||||||
|
int quality;
|
||||||
|
GX2ScanTarget scanTarget;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern FSIOThreadData gThreadData;
|
||||||
|
extern bool gThreadsRunning;
|
||||||
|
|
||||||
|
#define FS_IO_QUEUE_COMMAND_STOP 0x13371337
|
||||||
|
#define FS_IO_QUEUE_COMMAND_PROCESS_FS_COMMAND 0x42424242
|
||||||
|
#define FS_IO_QUEUE_SYNC_RESULT 0x43434343
|
||||||
|
|
||||||
|
bool sendMessageToThread(SaveScreenshotMessage *param);
|
||||||
|
void startFSIOThreads();
|
||||||
|
void stopFSIOThreads();
|
Loading…
x
Reference in New Issue
Block a user