Implement threaded screenshot saving

This commit is contained in:
Maschell 2023-01-22 14:45:20 +01:00
parent 675815c195
commit f96a28caf2
9 changed files with 373 additions and 158 deletions

View File

@ -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,
};

View File

@ -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);
}

View File

@ -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();
}

View File

@ -2,4 +2,4 @@
#include "version.h"
#define VERSION "v0.1"
#define VERSION_FULL VERSION VERSION_EXTRA
#define VERSION_FULL VERSION VERSION_EXTRA

View File

@ -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;

View File

@ -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;

View File

@ -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
View 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
View 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();