mirror of
https://github.com/wiiu-env/ScreenshotWUPS.git
synced 2024-11-22 06:29:15 +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_DRC = 2,
|
||||
} ImageSourceEnum;
|
||||
|
||||
enum ScreenshotState {
|
||||
SCREENSHOT_STATE_READY,
|
||||
SCREENSHOT_STATE_REQUESTED,
|
||||
SCREENSHOT_STATE_SAVING,
|
||||
};
|
||||
|
@ -2,27 +2,41 @@
|
||||
#include "fs/FSUtils.h"
|
||||
#include "retain_vars.hpp"
|
||||
#include "screenshot_utils.h"
|
||||
#include "thread.h"
|
||||
#include "utils/input.h"
|
||||
#include "utils/logger.h"
|
||||
#include <coreinit/time.h>
|
||||
#include <coreinit/cache.h>
|
||||
#include <gx2/surface.h>
|
||||
#include <padscore/wpad.h>
|
||||
#include <vpad/input.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) {
|
||||
|
||||
VPADReadError real_error;
|
||||
int32_t result = real_VPADRead(chan, buffer, buffer_size, &real_error);
|
||||
|
||||
if (gEnabled) {
|
||||
if (result > 0 && real_error == VPAD_READ_SUCCESS && (buffer[0].hold == gButtonCombo) && screenshotCoolDown == 0 && OSIsHomeButtonMenuEnabled()) {
|
||||
takeScreenshotTV = gImageSource == IMAGE_SOURCE_TV_AND_DRC || gImageSource == IMAGE_SOURCE_TV;
|
||||
takeScreenshotDRC = gImageSource == IMAGE_SOURCE_TV_AND_DRC || gImageSource == IMAGE_SOURCE_DRC;
|
||||
screenshotCoolDown = 60;
|
||||
if (gEnabled && (gTakeScreenshotTV == SCREENSHOT_STATE_READY || gTakeScreenshotDRC == SCREENSHOT_STATE_READY)) {
|
||||
if (result > 0 && real_error == VPAD_READ_SUCCESS) {
|
||||
if (((buffer[0].trigger & 0x000FFFFF) == gButtonCombo)) {
|
||||
if (!OSIsHomeButtonMenuEnabled()) {
|
||||
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) {
|
||||
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].extensionType != 0xFF) {
|
||||
bool takeScreenshot = false;
|
||||
@ -58,9 +72,19 @@ DECL_FUNCTION(void, WPADRead, WPADChan chan, WPADStatusProController *data) {
|
||||
}
|
||||
}
|
||||
if (takeScreenshot) {
|
||||
takeScreenshotTV = gImageSource == IMAGE_SOURCE_TV_AND_DRC || gImageSource == IMAGE_SOURCE_TV;
|
||||
takeScreenshotDRC = gImageSource == IMAGE_SOURCE_TV_AND_DRC || gImageSource == IMAGE_SOURCE_DRC;
|
||||
screenshotCoolDown = 60;
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -68,20 +92,24 @@ DECL_FUNCTION(void, WPADRead, WPADChan chan, WPADStatusProController *data) {
|
||||
}
|
||||
|
||||
DECL_FUNCTION(void, GX2CopyColorBufferToScanBuffer, const GX2ColorBuffer *colorBuffer, GX2ScanTarget scan_target) {
|
||||
|
||||
if ((takeScreenshotTV || takeScreenshotDRC)) {
|
||||
if (scan_target == GX2_SCAN_TARGET_TV && colorBuffer != nullptr && takeScreenshotTV) {
|
||||
if (gEnabled) {
|
||||
if (scan_target == GX2_SCAN_TARGET_TV && colorBuffer != nullptr && gTakeScreenshotTV == SCREENSHOT_STATE_REQUESTED) {
|
||||
DEBUG_FUNCTION_LINE("Lets take a screenshot from TV.");
|
||||
takeScreenshot((GX2ColorBuffer *) colorBuffer, scan_target, gTVSurfaceFormat, gOutputFormat, gQuality);
|
||||
takeScreenshotTV = false;
|
||||
} else if (scan_target == GX2_SCAN_TARGET_DRC0 && colorBuffer != nullptr && takeScreenshotDRC) {
|
||||
if (!takeScreenshot((GX2ColorBuffer *) colorBuffer, scan_target, gTVSurfaceFormat, gOutputFormat, gQuality)) {
|
||||
gTakeScreenshotTV = SCREENSHOT_STATE_READY;
|
||||
} 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.");
|
||||
takeScreenshot((GX2ColorBuffer *) colorBuffer, scan_target, gDRCSurfaceFormat, gOutputFormat, gQuality);
|
||||
takeScreenshotDRC = false;
|
||||
if (!takeScreenshot((GX2ColorBuffer *) colorBuffer, scan_target, gDRCSurfaceFormat, gOutputFormat, gQuality)) {
|
||||
gTakeScreenshotDRC = SCREENSHOT_STATE_READY;
|
||||
} else {
|
||||
gTakeScreenshotDRC = SCREENSHOT_STATE_SAVING;
|
||||
}
|
||||
}
|
||||
} else if (screenshotCoolDown > 0) {
|
||||
screenshotCoolDown--;
|
||||
}
|
||||
|
||||
real_GX2CopyColorBufferToScanBuffer(colorBuffer, scan_target);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "main.h"
|
||||
#include "retain_vars.hpp"
|
||||
#include "thread.h"
|
||||
#include "utils/WUPSConfigItemButtonCombo.h"
|
||||
#include "utils/logger.h"
|
||||
#include <coreinit/cache.h>
|
||||
@ -7,7 +8,6 @@
|
||||
#include <malloc.h>
|
||||
#include <nn/acp.h>
|
||||
#include <string>
|
||||
#include <vpad/input.h>
|
||||
#include <wups.h>
|
||||
#include <wups/config/WUPSConfigItemBoolean.h>
|
||||
#include <wups/config/WUPSConfigItemIntegerRange.h>
|
||||
@ -277,9 +277,12 @@ ON_APPLICATION_START() {
|
||||
} else {
|
||||
gShortNameEn.clear();
|
||||
}
|
||||
startFSIOThreads();
|
||||
VPADSetTVMenuInvalid(VPAD_CHAN_0, true);
|
||||
}
|
||||
|
||||
|
||||
ON_APPLICATION_REQUESTS_EXIT() {
|
||||
stopFSIOThreads();
|
||||
deinitLogging();
|
||||
}
|
||||
|
@ -2,4 +2,4 @@
|
||||
#include "version.h"
|
||||
|
||||
#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 <string>
|
||||
|
||||
GX2SurfaceFormat gTVSurfaceFormat = 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;
|
||||
@ -7,4 +8,7 @@ bool gEnabled = true;
|
||||
uint32_t gButtonCombo = 0;
|
||||
int32_t gQuality = 90;
|
||||
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 <gx2/surface.h>
|
||||
#include <string>
|
||||
#include <wups.h>
|
||||
|
||||
extern bool gEnabled;
|
||||
extern ImageSourceEnum gImageSource;
|
||||
@ -11,4 +10,7 @@ extern GX2SurfaceFormat gDRCSurfaceFormat;
|
||||
extern uint32_t gButtonCombo;
|
||||
extern int32_t gQuality;
|
||||
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 "common.h"
|
||||
#include "fs/FSUtils.h"
|
||||
#include "retain_vars.hpp"
|
||||
#include "thread.h"
|
||||
#include "utils/StringTools.h"
|
||||
#include <coreinit/title.h>
|
||||
#include "utils/utils.h"
|
||||
#include <coreinit/cache.h>
|
||||
#include <gd.h>
|
||||
#include <gx2/event.h>
|
||||
#include <gx2/mem.h>
|
||||
@ -127,10 +127,13 @@ static bool copyBuffer(GX2ColorBuffer *sourceBuffer, GX2ColorBuffer *targetBuffe
|
||||
GX2InitColorBufferRegs(targetBuffer);
|
||||
|
||||
// Let's allocate the memory.
|
||||
// cannot be in unknown regions for GX2 like 0xBCAE1000
|
||||
targetBuffer->surface.image = MEMAllocFromMappedMemoryForGX2Ex(targetBuffer->surface.imageSize, targetBuffer->surface.alignment);
|
||||
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;
|
||||
} 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);
|
||||
@ -149,19 +152,18 @@ static bool copyBuffer(GX2ColorBuffer *sourceBuffer, GX2ColorBuffer *targetBuffe
|
||||
|
||||
tempSurface.image = MEMAllocFromMappedMemoryForGX2Ex(tempSurface.imageSize, tempSurface.alignment);
|
||||
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) {
|
||||
MEMFreeToMappedMemory(targetBuffer->surface.image);
|
||||
targetBuffer->surface.image = nullptr;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
DEBUG_FUNCTION_LINE("Allocated %d bytes for the surface image", targetBuffer->surface.imageSize);
|
||||
}
|
||||
GX2ResolveAAColorBuffer(sourceBuffer, &tempSurface, 0, 0);
|
||||
GX2CopySurface(&tempSurface, 0, 0, &targetBuffer->surface, 0, 0);
|
||||
|
||||
// Sync CPU and GPU
|
||||
GX2DrawDone();
|
||||
|
||||
if (tempSurface.image != nullptr) {
|
||||
MEMFreeToMappedMemory(tempSurface.image);
|
||||
tempSurface.image = nullptr;
|
||||
@ -181,144 +183,79 @@ bool takeScreenshot(GX2ColorBuffer *srcBuffer, GX2ScanTarget scanTarget, GX2Surf
|
||||
}
|
||||
|
||||
GX2ColorBuffer colorBuffer;
|
||||
GX2ColorBuffer *saveBuffer = nullptr;
|
||||
|
||||
// keep dimensions
|
||||
uint32_t width = srcBuffer->surface.width;
|
||||
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;
|
||||
if ((outputBufferSurfaceFormat & 0x400)) {
|
||||
DEBUG_FUNCTION_LINE_VERBOSE("Images needs to be converted to SRGB");
|
||||
convertRGBToSRGB = true;
|
||||
}
|
||||
|
||||
bool valid;
|
||||
bool cancel = false;
|
||||
do {
|
||||
// At first, we need to copy the buffer to fit our resolution.
|
||||
if (saveBuffer == nullptr) {
|
||||
do {
|
||||
valid = copyBuffer(srcBuffer, &colorBuffer, width, height);
|
||||
// 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);
|
||||
bool res;
|
||||
|
||||
auto threadPriority = OSGetThreadPriority(OSGetCurrentThread()) + 1;
|
||||
if (threadPriority != OSGetThreadPriority(gThreadData.thread)) {
|
||||
DEBUG_FUNCTION_LINE_ERR("INFO! Set thread priority to %d", threadPriority);
|
||||
if (!OSSetThreadPriority(gThreadData.thread, threadPriority)) {
|
||||
DEBUG_FUNCTION_LINE_WARN("Failed to set thread priority");
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we should proceed
|
||||
if (cancel) {
|
||||
// Free the memory on error.
|
||||
if (colorBuffer.surface.image != nullptr) {
|
||||
MEMFreeToMappedMemory(colorBuffer.surface.image);
|
||||
colorBuffer.surface.image = nullptr;
|
||||
}
|
||||
return false;
|
||||
SaveScreenshotMessage *param;
|
||||
|
||||
bool valid = copyBuffer(srcBuffer, &colorBuffer, width, height);
|
||||
if (!valid) {
|
||||
if (width == 1920 && height == 1080) {
|
||||
DEBUG_FUNCTION_LINE("Try to fallback to 720p");
|
||||
valid = copyBuffer(srcBuffer, &colorBuffer, 1280, 720);
|
||||
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 (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 {
|
||||
return false;
|
||||
}
|
||||
DEBUG_FUNCTION_LINE_ERR("Failed to copy color buffer");
|
||||
goto error;
|
||||
}
|
||||
} 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;
|
||||
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…
Reference in New Issue
Block a user