Skip to content

Commit 37bd840

Browse files
committed
Use Win32 CreateThread for VC6 compatibility and add GUIEditDisplay stub
1 parent 5e4d22a commit 37bd840

File tree

4 files changed

+88
-78
lines changed

4 files changed

+88
-78
lines changed

Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp

Lines changed: 43 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,7 @@ static void drawFramerateBar(void);
3939
#include <windows.h>
4040
#include <io.h>
4141
#include <time.h>
42-
#include <thread>
43-
#include <memory>
4442

45-
// TheSuperHackers @bobtista 02/11/2025 STB for image encoding
4643
#define STB_IMAGE_WRITE_IMPLEMENTATION
4744
#include <stb_image_write.h>
4845

@@ -3028,12 +3025,33 @@ void W3DDisplay::takeScreenShot(void)
30283025
TheInGameUI->message(TheGameText->fetch("GUI:ScreenCapture"), ufileName.str());
30293026
}
30303027

3031-
/// TheSuperHackers @bobtista 02/11/2025 Save compressed screenshot (JPEG/PNG) to file without stalling the game
3032-
/// This implementation captures the frame buffer on the main thread, then spawns a background thread
3033-
/// to compress and save the image, allowing the game to continue running smoothly.
3028+
struct ScreenshotThreadData
3029+
{
3030+
unsigned char* imageData;
3031+
unsigned int width;
3032+
unsigned int height;
3033+
char pathname[1024];
3034+
char leafname[256];
3035+
};
3036+
3037+
static DWORD WINAPI screenshotThreadFunc(LPVOID param)
3038+
{
3039+
ScreenshotThreadData* data = (ScreenshotThreadData*)param;
3040+
3041+
int result = stbi_write_jpg(data->pathname, data->width, data->height, 3, data->imageData, 90);
3042+
3043+
if (!result) {
3044+
OutputDebugStringA("Failed to write screenshot JPEG\n");
3045+
}
3046+
3047+
delete [] data->imageData;
3048+
delete data;
3049+
3050+
return 0;
3051+
}
3052+
30343053
void W3DDisplay::takeScreenShotCompressed(void)
30353054
{
3036-
// TheSuperHackers @bobtista 02/11/2025 Find next available filename
30373055
char leafname[256];
30383056
char pathname[1024];
30393057
static int frame_number = 1;
@@ -3047,7 +3065,6 @@ void W3DDisplay::takeScreenShotCompressed(void)
30473065
done = true;
30483066
}
30493067

3050-
// TheSuperHackers @bobtista 02/11/2025 Get the back buffer and create a copy
30513068
SurfaceClass* surface = DX8Wrapper::_Get_DX8_Back_Buffer();
30523069
SurfaceClass::SurfaceDescription surfaceDesc;
30533070
surface->Get_Description(surfaceDesc);
@@ -3075,53 +3092,40 @@ void W3DDisplay::takeScreenShotCompressed(void)
30753092
unsigned int width = surfaceDesc.Width;
30763093
unsigned int height = surfaceDesc.Height;
30773094

3078-
// TheSuperHackers @bobtista 02/11/2025 Allocate buffer for RGB image data
3079-
// Using shared_ptr for automatic cleanup in the background thread
3080-
std::shared_ptr<unsigned char> imageData(new unsigned char[3 * width * height],
3081-
std::default_delete<unsigned char[]>());
3082-
unsigned char* image = imageData.get();
3095+
unsigned char* image = new unsigned char[3 * width * height];
30833096

3084-
// TheSuperHackers @bobtista 02/11/2025 Copy and convert BGRA to RGB
30853097
for (y = 0; y < height; y++)
30863098
{
30873099
for (x = 0; x < width; x++)
30883100
{
30893101
index = 3 * (x + y * width);
30903102
index2 = y * lrect.Pitch + 4 * x;
30913103

3092-
image[index] = *((unsigned char*)lrect.pBits + index2 + 2); // R
3093-
image[index + 1] = *((unsigned char*)lrect.pBits + index2 + 1); // G
3094-
image[index + 2] = *((unsigned char*)lrect.pBits + index2 + 0); // B
3104+
image[index] = *((unsigned char*)lrect.pBits + index2 + 2);
3105+
image[index + 1] = *((unsigned char*)lrect.pBits + index2 + 1);
3106+
image[index + 2] = *((unsigned char*)lrect.pBits + index2 + 0);
30953107
}
30963108
}
30973109

30983110
surfaceCopy->Unlock();
30993111
surfaceCopy->Release_Ref();
31003112
surfaceCopy = NULL;
31013113

3102-
// TheSuperHackers @bobtista 02/11/2025 Make a copy of the pathname for the background thread
3103-
std::string pathnameCopy(pathname);
3104-
std::string leafnameCopy(leafname);
3105-
3106-
// TheSuperHackers @bobtista 02/11/2025 Spawn background thread to compress and save the image
3107-
// This allows the game to continue running without freezing
3108-
std::thread([imageData, width, height, pathnameCopy, leafnameCopy]() {
3109-
// TheSuperHackers @bobtista 02/11/2025 Write JPEG with quality 90 (range: 1-100)
3110-
// stbi_write_jpg expects image data with Y-axis going down, which matches our data
3111-
int result = stbi_write_jpg(pathnameCopy.c_str(), width, height, 3, imageData.get(), 90);
3112-
3113-
if (!result) {
3114-
// TheSuperHackers @bobtista 02/11/2025 Log error if write failed
3115-
// Note: Can't show UI message from background thread
3116-
OutputDebugStringA("Failed to write screenshot JPEG\n");
3117-
}
3118-
3119-
// TheSuperHackers @bobtista 02/11/2025 imageData will be automatically cleaned up when shared_ptr goes out of scope
3120-
}).detach(); // TheSuperHackers @bobtista 02/11/2025 Detach thread to run independently
3121-
3122-
// TheSuperHackers @bobtista 02/11/2025 Show message to user immediately (file is being saved in background)
3114+
ScreenshotThreadData* threadData = new ScreenshotThreadData();
3115+
threadData->imageData = image;
3116+
threadData->width = width;
3117+
threadData->height = height;
3118+
strcpy(threadData->pathname, pathname);
3119+
strcpy(threadData->leafname, leafname);
3120+
3121+
DWORD threadId;
3122+
HANDLE hThread = CreateThread(NULL, 0, screenshotThreadFunc, threadData, 0, &threadId);
3123+
if (hThread) {
3124+
CloseHandle(hThread);
3125+
}
3126+
31233127
UnicodeString ufileName;
3124-
ufileName.translate(leafnameCopy.c_str());
3128+
ufileName.translate(leafname);
31253129
TheInGameUI->message(TheGameText->fetch("GUI:ScreenCapture"), ufileName.str());
31263130
}
31273131

Generals/Code/Tools/GUIEdit/Include/GUIEditDisplay.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ class GUIEditDisplay : public Display
102102
virtual void drawVideoBuffer( VideoBuffer *buffer, Int startX, Int startY,
103103
Int endX, Int endY ) { }
104104
virtual void takeScreenShot(void){ }
105+
virtual void takeScreenShotCompressed(void){ }
105106
virtual void toggleMovieCapture(void) {}
106107

107108
// methods that we need to stub

GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp

Lines changed: 43 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,7 @@ static void drawFramerateBar(void);
3939
#include <windows.h>
4040
#include <io.h>
4141
#include <time.h>
42-
#include <thread>
43-
#include <memory>
4442

45-
// TheSuperHackers @bobtista 02/11/2025 STB for image encoding
4643
#define STB_IMAGE_WRITE_IMPLEMENTATION
4744
#include <stb_image_write.h>
4845

@@ -3147,12 +3144,33 @@ void W3DDisplay::takeScreenShot(void)
31473144
TheInGameUI->message(TheGameText->fetch("GUI:ScreenCapture"), ufileName.str());
31483145
}
31493146

3150-
/// TheSuperHackers @bobtista 02/11/2025 Save compressed screenshot (JPEG/PNG) to file without stalling the game
3151-
/// This implementation captures the frame buffer on the main thread, then spawns a background thread
3152-
/// to compress and save the image, allowing the game to continue running smoothly.
3147+
struct ScreenshotThreadData
3148+
{
3149+
unsigned char* imageData;
3150+
unsigned int width;
3151+
unsigned int height;
3152+
char pathname[1024];
3153+
char leafname[256];
3154+
};
3155+
3156+
static DWORD WINAPI screenshotThreadFunc(LPVOID param)
3157+
{
3158+
ScreenshotThreadData* data = (ScreenshotThreadData*)param;
3159+
3160+
int result = stbi_write_jpg(data->pathname, data->width, data->height, 3, data->imageData, 90);
3161+
3162+
if (!result) {
3163+
OutputDebugStringA("Failed to write screenshot JPEG\n");
3164+
}
3165+
3166+
delete [] data->imageData;
3167+
delete data;
3168+
3169+
return 0;
3170+
}
3171+
31533172
void W3DDisplay::takeScreenShotCompressed(void)
31543173
{
3155-
// TheSuperHackers @bobtista 02/11/2025 Find next available filename
31563174
char leafname[256];
31573175
char pathname[1024];
31583176
static int frame_number = 1;
@@ -3166,7 +3184,6 @@ void W3DDisplay::takeScreenShotCompressed(void)
31663184
done = true;
31673185
}
31683186

3169-
// TheSuperHackers @bobtista 02/11/2025 Get the back buffer and create a copy
31703187
SurfaceClass* surface = DX8Wrapper::_Get_DX8_Back_Buffer();
31713188
SurfaceClass::SurfaceDescription surfaceDesc;
31723189
surface->Get_Description(surfaceDesc);
@@ -3194,53 +3211,40 @@ void W3DDisplay::takeScreenShotCompressed(void)
31943211
unsigned int width = surfaceDesc.Width;
31953212
unsigned int height = surfaceDesc.Height;
31963213

3197-
// TheSuperHackers @bobtista 02/11/2025 Allocate buffer for RGB image data
3198-
// Using shared_ptr for automatic cleanup in the background thread
3199-
std::shared_ptr<unsigned char> imageData(new unsigned char[3 * width * height],
3200-
std::default_delete<unsigned char[]>());
3201-
unsigned char* image = imageData.get();
3214+
unsigned char* image = new unsigned char[3 * width * height];
32023215

3203-
// TheSuperHackers @bobtista 02/11/2025 Copy and convert BGRA to RGB
32043216
for (y = 0; y < height; y++)
32053217
{
32063218
for (x = 0; x < width; x++)
32073219
{
32083220
index = 3 * (x + y * width);
32093221
index2 = y * lrect.Pitch + 4 * x;
32103222

3211-
image[index] = *((unsigned char*)lrect.pBits + index2 + 2); // R
3212-
image[index + 1] = *((unsigned char*)lrect.pBits + index2 + 1); // G
3213-
image[index + 2] = *((unsigned char*)lrect.pBits + index2 + 0); // B
3223+
image[index] = *((unsigned char*)lrect.pBits + index2 + 2);
3224+
image[index + 1] = *((unsigned char*)lrect.pBits + index2 + 1);
3225+
image[index + 2] = *((unsigned char*)lrect.pBits + index2 + 0);
32143226
}
32153227
}
32163228

32173229
surfaceCopy->Unlock();
32183230
surfaceCopy->Release_Ref();
32193231
surfaceCopy = NULL;
32203232

3221-
// TheSuperHackers @bobtista 02/11/2025 Make a copy of the pathname for the background thread
3222-
std::string pathnameCopy(pathname);
3223-
std::string leafnameCopy(leafname);
3224-
3225-
// TheSuperHackers @bobtista 02/11/2025 Spawn background thread to compress and save the image
3226-
// This allows the game to continue running without freezing
3227-
std::thread([imageData, width, height, pathnameCopy, leafnameCopy]() {
3228-
// TheSuperHackers @bobtista 02/11/2025 Write JPEG with quality 90 (range: 1-100)
3229-
// stbi_write_jpg expects image data with Y-axis going down, which matches our data
3230-
int result = stbi_write_jpg(pathnameCopy.c_str(), width, height, 3, imageData.get(), 90);
3231-
3232-
if (!result) {
3233-
// TheSuperHackers @bobtista 02/11/2025 Log error if write failed
3234-
// Note: Can't show UI message from background thread
3235-
OutputDebugStringA("Failed to write screenshot JPEG\n");
3236-
}
3237-
3238-
// TheSuperHackers @bobtista 02/11/2025 imageData will be automatically cleaned up when shared_ptr goes out of scope
3239-
}).detach(); // TheSuperHackers @bobtista 02/11/2025 Detach thread to run independently
3240-
3241-
// TheSuperHackers @bobtista 02/11/2025 Show message to user immediately (file is being saved in background)
3233+
ScreenshotThreadData* threadData = new ScreenshotThreadData();
3234+
threadData->imageData = image;
3235+
threadData->width = width;
3236+
threadData->height = height;
3237+
strcpy(threadData->pathname, pathname);
3238+
strcpy(threadData->leafname, leafname);
3239+
3240+
DWORD threadId;
3241+
HANDLE hThread = CreateThread(NULL, 0, screenshotThreadFunc, threadData, 0, &threadId);
3242+
if (hThread) {
3243+
CloseHandle(hThread);
3244+
}
3245+
32423246
UnicodeString ufileName;
3243-
ufileName.translate(leafnameCopy.c_str());
3247+
ufileName.translate(leafname);
32443248
TheInGameUI->message(TheGameText->fetch("GUI:ScreenCapture"), ufileName.str());
32453249
}
32463250

GeneralsMD/Code/Tools/GUIEdit/Include/GUIEditDisplay.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ class GUIEditDisplay : public Display
102102
virtual void drawVideoBuffer( VideoBuffer *buffer, Int startX, Int startY,
103103
Int endX, Int endY ) { }
104104
virtual void takeScreenShot(void){ }
105+
virtual void takeScreenShotCompressed(void){ }
105106
virtual void toggleMovieCapture(void) {}
106107

107108
// methods that we need to stub

0 commit comments

Comments
 (0)