From f96a28caf281d60ee8bdff4f5c6d65ea1271c640 Mon Sep 17 00:00:00 2001 From: Maschell Date: Sun, 22 Jan 2023 14:45:20 +0100 Subject: [PATCH] Implement threaded screenshot saving --- src/common.h | 6 ++ src/function_patcher.cpp | 76 ++++++++++----- src/main.cpp | 5 +- src/main.h | 2 +- src/retain_vars.cpp | 6 +- src/retain_vars.hpp | 6 +- src/screenshot_utils.cpp | 195 +++++++++++++------------------------- src/thread.cpp | 196 +++++++++++++++++++++++++++++++++++++++ src/thread.h | 39 ++++++++ 9 files changed, 373 insertions(+), 158 deletions(-) create mode 100644 src/thread.cpp create mode 100644 src/thread.h diff --git a/src/common.h b/src/common.h index bc22c19..96c7e24 100644 --- a/src/common.h +++ b/src/common.h @@ -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, +}; diff --git a/src/function_patcher.cpp b/src/function_patcher.cpp index 727eb96..f07a192 100644 --- a/src/function_patcher.cpp +++ b/src/function_patcher.cpp @@ -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 +#include #include #include #include #include -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); } diff --git a/src/main.cpp b/src/main.cpp index 8684d5b..7fa0186 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,6 @@ #include "main.h" #include "retain_vars.hpp" +#include "thread.h" #include "utils/WUPSConfigItemButtonCombo.h" #include "utils/logger.h" #include @@ -7,7 +8,6 @@ #include #include #include -#include #include #include #include @@ -277,9 +277,12 @@ ON_APPLICATION_START() { } else { gShortNameEn.clear(); } + startFSIOThreads(); VPADSetTVMenuInvalid(VPAD_CHAN_0, true); } + ON_APPLICATION_REQUESTS_EXIT() { + stopFSIOThreads(); deinitLogging(); } diff --git a/src/main.h b/src/main.h index a87e236..db78b93 100644 --- a/src/main.h +++ b/src/main.h @@ -2,4 +2,4 @@ #include "version.h" #define VERSION "v0.1" -#define VERSION_FULL VERSION VERSION_EXTRA \ No newline at end of file +#define VERSION_FULL VERSION VERSION_EXTRA diff --git a/src/retain_vars.cpp b/src/retain_vars.cpp index cb9ad22..229cba4 100644 --- a/src/retain_vars.cpp +++ b/src/retain_vars.cpp @@ -1,5 +1,6 @@ #include "retain_vars.hpp" #include + 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; \ No newline at end of file +std::string gShortNameEn; + +ScreenshotState gTakeScreenshotTV = SCREENSHOT_STATE_READY; +ScreenshotState gTakeScreenshotDRC = SCREENSHOT_STATE_READY; \ No newline at end of file diff --git a/src/retain_vars.hpp b/src/retain_vars.hpp index c9c745f..f553314 100644 --- a/src/retain_vars.hpp +++ b/src/retain_vars.hpp @@ -2,7 +2,6 @@ #include "common.h" #include #include -#include 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; \ No newline at end of file +extern std::string gShortNameEn; + +extern ScreenshotState gTakeScreenshotTV; +extern ScreenshotState gTakeScreenshotDRC; \ No newline at end of file diff --git a/src/screenshot_utils.cpp b/src/screenshot_utils.cpp index 9ed5b61..c231ebf 100644 --- a/src/screenshot_utils.cpp +++ b/src/screenshot_utils.cpp @@ -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 #include "utils/utils.h" +#include #include #include #include @@ -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; } diff --git a/src/thread.cpp b/src/thread.cpp new file mode 100644 index 0000000..a05a81c --- /dev/null +++ b/src/thread.cpp @@ -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 +#include +#include +#include +#include + +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((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; +} \ No newline at end of file diff --git a/src/thread.h b/src/thread.h new file mode 100644 index 0000000..bb06377 --- /dev/null +++ b/src/thread.h @@ -0,0 +1,39 @@ +#pragma once +#include "common.h" +#include +#include +#include +#include +#include + +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(); \ No newline at end of file