Skip to content

feat(screenshot): Add threaded JPEG/PNG screenshots without game stalls#1785

Open
bobtista wants to merge 22 commits intoTheSuperHackers:mainfrom
bobtista:bobtista/compressed-screenshot-f11
Open

feat(screenshot): Add threaded JPEG/PNG screenshots without game stalls#1785
bobtista wants to merge 22 commits intoTheSuperHackers:mainfrom
bobtista:bobtista/compressed-screenshot-f11

Conversation

@bobtista
Copy link

@bobtista bobtista commented Nov 3, 2025

Summary

Replaces the old BMP screenshot with compressed JPEG screenshots that don't stall the game, and adds PNG support.

Closes #1555
Closes #106 ... sort of

Adds a new screenshot function using the stb_image_write library with background threading:

  • F12 - Compressed JPEG screenshot (no stalling, ~600KB files)
  • Ctrl+F12 - Lossless PNG screenshot

Notes

  • stb_image-write is public domain licensed, single-header, zero compilation needed
  • Captures frame buffer on main thread (~1-2ms)
  • Spawns detached thread for JPEG/PNG compression and disk I/O
  • JPEG quality configurable via Options.ini (default 80)
  • Works for both Generals and Zero Hour

Testing

  • Screenshots save correctly with F12 (jpg) and Ctrl+F12 (png)
  • No game stalling during screenshot
  • Files appear in correct directory with sequential numbering
    sshot003

@bobtista bobtista force-pushed the bobtista/compressed-screenshot-f11 branch 4 times, most recently from 20a3df1 to 37bd840 Compare November 3, 2025 04:32
@Stubbjax
Copy link

Stubbjax commented Nov 3, 2025

Some initial thoughts:

  • Keep variants of the same function to a single hotkey; instead toggle image format via a new setting in Options.ini
  • Apply image compression / quality via a new setting in Options.ini
  • Can we consolidate this logic in core instead of repeating the implementation twice?

@xezon
Copy link

xezon commented Nov 3, 2025

Agree with Stubbjax.

JPG 90 is big file. Better make it default 80.

Replace BMP screenshot with PNG screenshot. PNG is lossless compressed and always better than BMP.

Make F12 take JPG 80 screenshot. Make CTRL+F12 take PNG screenshot. Make JPG Quality adjustable.

Remove the old BMP code(s) and only use the new code for screenshot.

@xezon
Copy link

xezon commented Nov 3, 2025

Regarding Github formatting:

When you write

Addresses #1555

Then it will not close this report when this is merged.

Please read up on it here:
https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/linking-a-pull-request-to-an-issue#closing-multiple-issues

@xezon xezon added Enhancement Is new feature or request Major Severity: Minor < Major < Critical < Blocker Gen Relates to Generals ZH Relates to Zero Hour labels Nov 3, 2025
@L3-M L3-M added this to the GenTool features replication milestone Nov 3, 2025
@L3-M L3-M added the Input label Nov 3, 2025
@bobtista
Copy link
Author

bobtista commented Nov 3, 2025

Some initial thoughts:

  • Keep variants of the same function to a single hotkey; instead toggle image format via a new setting in Options.ini
  • Apply image compression / quality via a new setting in Options.ini
  • Can we consolidate this logic in core instead of repeating the implementation twice?

RE moving logic to core, I moved what I could to core - but there are a lot more files that need to be moved to core before this can be moved there eg WWVegas/WW3D2/*

@bobtista bobtista force-pushed the bobtista/compressed-screenshot-f11 branch 3 times, most recently from 3535e1e to efc773f Compare November 3, 2025 17:45
@bobtista bobtista force-pushed the bobtista/compressed-screenshot-f11 branch 2 times, most recently from 977a6dc to f8162f3 Compare November 3, 2025 23:07
@bobtista bobtista force-pushed the bobtista/compressed-screenshot-f11 branch from d7e8a8d to d197bdd Compare November 5, 2025 18:04
@bobtista bobtista force-pushed the bobtista/compressed-screenshot-f11 branch from d197bdd to 9669966 Compare November 11, 2025 21:27
@xezon
Copy link

xezon commented Nov 22, 2025

Needs rebase.

@bobtista bobtista force-pushed the bobtista/compressed-screenshot-f11 branch from 9669966 to 4897b0b Compare November 22, 2025 18:10
@bobtista
Copy link
Author

Needs rebase.

Done

Skyaero42
Skyaero42 previously approved these changes Nov 22, 2025
Copy link

@Skyaero42 Skyaero42 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me

@bobtista bobtista force-pushed the bobtista/compressed-screenshot-f11 branch from 9c99306 to de35e57 Compare December 3, 2025 16:48
@bobtista bobtista force-pushed the bobtista/compressed-screenshot-f11 branch from 93bd190 to 516acb2 Compare February 16, 2026 18:47
Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

34 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

@bobtista bobtista changed the title feat(screenshot): Add threaded JPEG screenshots on F11 without game stalls feat(screenshot): Add threaded JPEG/PNG screenshots without game stalls Feb 16, 2026
Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

34 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

34 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Bool m_clientRetaliationModeEnabled;
Bool m_doubleClickAttackMove;
Bool m_rightMouseAlwaysScrolls;
Int m_jpegQuality;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m_jpegQuality is never initialized in the constructor

Every neighboring member in this struct (m_rightMouseAlwaysScrolls at GlobalData.cpp:647, m_useWaterPlane at GlobalData.cpp:648, etc.) is explicitly initialized in the GlobalData::GlobalData() constructor, but m_jpegQuality is not. It only gets set later in parseGameDataDefinition via optionPref.getJPEGQuality().

This means m_jpegQuality holds an indeterminate value between construction and the parseGameDataDefinition call. While screenshots are unlikely to be taken in that window during normal gameplay, this is still a correctness defect that should be fixed for consistency and safety. The same issue exists in the GeneralsMD mirror file.

Add initialization in both GlobalData::GlobalData() constructors, for example after m_rightMouseAlwaysScrolls = FALSE;:

m_jpegQuality = 80;
Prompt To Fix With AI
This is a comment left during a code review.
Path: Generals/Code/GameEngine/Include/Common/GlobalData.h
Line: 145:145

Comment:
**`m_jpegQuality` is never initialized in the constructor**

Every neighboring member in this struct (`m_rightMouseAlwaysScrolls` at `GlobalData.cpp:647`, `m_useWaterPlane` at `GlobalData.cpp:648`, etc.) is explicitly initialized in the `GlobalData::GlobalData()` constructor, but `m_jpegQuality` is not. It only gets set later in `parseGameDataDefinition` via `optionPref.getJPEGQuality()`.

This means `m_jpegQuality` holds an indeterminate value between construction and the `parseGameDataDefinition` call. While screenshots are unlikely to be taken in that window during normal gameplay, this is still a correctness defect that should be fixed for consistency and safety. The same issue exists in the GeneralsMD mirror file.

Add initialization in both `GlobalData::GlobalData()` constructors, for example after `m_rightMouseAlwaysScrolls = FALSE;`:
```cpp
m_jpegQuality = 80;
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +207 to 215
target_sources(g_gameenginedevice PRIVATE
# Source/W3DDevice/GameClient/stb_image_write_impl.cpp
)

set_source_files_properties(
# Source/W3DDevice/GameClient/stb_image_write_impl.cpp
PROPERTIES
SKIP_PRECOMPILE_HEADERS ON
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty CMake commands left behind after commenting out source

These target_sources and set_source_files_properties blocks are entirely commented out inside, resulting in empty CMake commands. Since stb_image_write_impl.cpp is compiled in the Core CMakeLists now, consider removing these empty shells entirely rather than leaving commented-out scaffolding. The same applies to the GeneralsMD mirror CMakeLists.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: Generals/Code/GameEngineDevice/CMakeLists.txt
Line: 207:215

Comment:
**Empty CMake commands left behind after commenting out source**

These `target_sources` and `set_source_files_properties` blocks are entirely commented out inside, resulting in empty CMake commands. Since `stb_image_write_impl.cpp` is compiled in the Core CMakeLists now, consider removing these empty shells entirely rather than leaving commented-out scaffolding. The same applies to the GeneralsMD mirror CMakeLists.

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

Add takeScreenShotCompressed() to Display interface
Implement threaded JPEG screenshot for Generals

Link stb library to GameEngineDevice targets

Remove excessive comments from screenshot implementation
Add shared screenshot implementation in Core
Move W3DScreenshot implementation to game-specific directories

Fix include order for VC6 precompiled headers

Remove default parameter from function definition

Move STB implementation to separate file to avoid PCH issues

Include screenshot implementation directly in W3DDisplay.cpp to avoid PCH issues

Use Windows constants and switch statement in screenshot code

Use vcpkg for stb dependency with FetchContent fallback
Move stb_image_write_impl.cpp from game-specific directories to Core since
it's identical between Generals and GeneralsMD and contains only STB library
implementation code with no game-specific logic.
…o nullptr, strcpy to strlcpy, fix thread leak
@bobtista bobtista force-pushed the bobtista/compressed-screenshot-f11 branch from c4673ea to 45c0339 Compare February 16, 2026 21:34
Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

34 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +152 to +154
UnicodeString ufileName;
ufileName.translate(leafname);
TheInGameUI->message(TheGameText->fetch("GUI:ScreenCapture"), ufileName.str());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Success message shown even when screenshot fails

If CreateThread fails (line 144), the image data and thread data are cleaned up but the function continues to display the "screen captured" message to the user at line 154. Consider moving the TheInGameUI->message(...) call into the if (hThread) block so it only displays on success, or at least log the failure more visibly. The same applies to the GeneralsMD mirror file.

Prompt To Fix With AI
This is a comment left during a code review.
Path: Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScreenshot.cpp
Line: 152:154

Comment:
**Success message shown even when screenshot fails**

If `CreateThread` fails (line 144), the image data and thread data are cleaned up but the function continues to display the "screen captured" message to the user at line 154. Consider moving the `TheInGameUI->message(...)` call into the `if (hThread)` block so it only displays on success, or at least log the failure more visibly. The same applies to the GeneralsMD mirror file.

How can I resolve this? If you propose a fix, please make it concise.

@greptile-apps
Copy link

greptile-apps bot commented Feb 16, 2026

Additional Comments (1)

Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp
Dead CreateBMPFile function left behind

The old takeScreenShot method (which was the only caller of CreateBMPFile) has been removed, leaving this ~75-line static function as dead code. It will produce a compiler warning for unused static function. Consider removing it since BMP screenshot functionality is fully replaced by the new JPEG/PNG implementation. The same applies to the GeneralsMD mirror file.

Prompt To Fix With AI
This is a comment left during a code review.
Path: Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp
Line: 2807:2880

Comment:
**Dead `CreateBMPFile` function left behind**

The old `takeScreenShot` method (which was the only caller of `CreateBMPFile`) has been removed, leaving this ~75-line static function as dead code. It will produce a compiler warning for unused static function. Consider removing it since BMP screenshot functionality is fully replaced by the new JPEG/PNG implementation. The same applies to the GeneralsMD mirror file.

How can I resolve this? If you propose a fix, please make it concise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement Is new feature or request Gen Relates to Generals Input Major Severity: Minor < Major < Critical < Blocker ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add JPG/PNG Screenshot with no stalls BMP Screenshot taken with F12 can stall game significantly

6 participants