Skip to content

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

Open
bobtista wants to merge 5 commits into
TheSuperHackers:mainfrom
bobtista:bobtista/compressed-screenshot-f11
Open

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

Conversation

@bobtista
Copy link
Copy Markdown

@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
Copy Markdown

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
Copy Markdown

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
Copy Markdown

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
Copy Markdown
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
Comment thread Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h Outdated
Comment thread Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScreenshot.cpp Outdated
Comment thread Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScreenshot.cpp Outdated
Comment thread Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScreenshot.cpp Outdated
@bobtista bobtista force-pushed the bobtista/compressed-screenshot-f11 branch 2 times, most recently from 977a6dc to f8162f3 Compare November 3, 2025 23:07
Comment thread cmake/stb.cmake Outdated
@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
Copy Markdown

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
Copy Markdown
Author

Needs rebase.

Done

Comment thread Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DScreenshot.h Outdated
Comment thread Generals/Code/GameEngine/Include/Common/MessageStream.h Outdated
Comment thread Generals/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp Outdated
Comment thread Generals/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp Outdated
Comment thread GeneralsMD/Code/GameEngine/Include/Common/MessageStream.h Outdated
Comment thread Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp Outdated
Skyaero42
Skyaero42 previously approved these changes Nov 22, 2025
Copy link
Copy Markdown

@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
@Skyaero42
Copy link
Copy Markdown

This needs rebasing before review.

@bobtista bobtista force-pushed the bobtista/compressed-screenshot-f11 branch from 7d40c12 to 93bd190 Compare February 16, 2026 17:59
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Feb 16, 2026

Greptile Summary

This PR replaces the old BMP screenshot system with a threaded JPEG/PNG implementation using stb_image_write, adding Ctrl+F12 for lossless PNG alongside the existing F12 JPEG shortcut, with JPEG quality configurable via Options.ini.

  • New W3DScreenshot.cpp extracts screenshot logic out of W3DDisplay.cpp into a standalone translation unit; the STB implementation file lives in Core and is included by both game targets via the corei_gameenginedevice_private INTERFACE.
  • Frame-buffer capture still happens on the main thread (~1–2 ms for surface copy + pixel swizzle), while compression and disk I/O are offloaded to a detached Windows thread; all previously flagged issues (missing DESTROY_MESSAGE, uninitialized m_jpegQuality, thread-failure memory leak, premature success message) are resolved in the current revision.

Confidence Score: 5/5

The change is safe to merge; the new threading model correctly segregates main-thread surface capture from background compression/IO, and every previously identified defect has been fixed in this revision.

All the concrete bugs identified in earlier review rounds — missing DESTROY_MESSAGE for the JPEG case, uninitialized m_jpegQuality, user-preference quality never applied, memory leak on thread creation failure, and the premature success message — are fully addressed in the current code. The build wiring (Core INTERFACE library carrying stb_image_write_impl.cpp into both game targets) is architecturally sound. No new functional defects were found.

No files require special attention. W3DScreenshot.cpp in both Generals/ and GeneralsMD/ is the highest-churn new code and has been well reviewed through multiple iterations.

Important Files Changed

Filename Overview
Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScreenshot.cpp New file implementing threaded JPEG/PNG capture; all previously flagged issues are resolved (DESTROY_MESSAGE, memory cleanup on thread failure, success message gated on thread creation, quality fallback to m_jpegQuality).
GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScreenshot.cpp Mirror of Generals W3DScreenshot.cpp for Zero Hour; identical logic, only header comment differs. All issues resolved in both copies.
cmake/stb.cmake Adds STB via FetchContent when vcpkg doesn't provide it; add_library(stb INTERFACE) is unconditional so the target is always defined, and Stb_INCLUDE_DIR is populated in both paths.
Core/GameEngineDevice/Source/W3DDevice/GameClient/stb_image_write_impl.cpp Single-purpose translation unit that defines STB_IMAGE_WRITE_IMPLEMENTATION; compiled into both game targets via the corei_gameenginedevice_private INTERFACE.
Core/GameEngine/Source/Common/OptionPreferences.cpp Adds getJPEGQuality() reading JPEGQuality key from Options.ini; clamps result to [1, 100] with default 80 when key is absent.
Generals/Code/GameEngine/Source/Common/GlobalData.cpp Initializes m_jpegQuality to 80 in constructor and loads it from OptionPreferences in parseGameDataDefinition; mirrors correctly in GeneralsMD.
Generals/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp Registers F12 for JPEG screenshot and Ctrl+F12 for PNG; both bindings use the guarded if(map->m_key == MK_NONE) pattern.
Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp Both MSG_META_TAKE_SCREENSHOT and MSG_META_TAKE_SCREENSHOT_PNG cases correctly set disp = DESTROY_MESSAGE before break.
Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DScreenshot.h Minimal shared header declaring W3D_TakeCompressedScreenshot with default quality=0, ensuring the m_jpegQuality fallback triggers when called from W3DDisplay::takeScreenShot.

Sequence Diagram

sequenceDiagram
    participant User
    participant MetaMap
    participant CommandXlat
    participant W3DDisplay
    participant W3DScreenshot
    participant BGThread

    User->>MetaMap: Press F12 (or Ctrl+F12)
    MetaMap->>CommandXlat: MSG_META_TAKE_SCREENSHOT(_PNG)
    CommandXlat->>W3DDisplay: "takeScreenShot(JPEG|PNG)"
    W3DDisplay->>W3DScreenshot: W3D_TakeCompressedScreenshot(format)
    W3DScreenshot->>W3DScreenshot: Get back buffer surface copy
    W3DScreenshot->>W3DScreenshot: Lock surface, swizzle BGR to RGB pixels
    W3DScreenshot->>W3DScreenshot: Unlock and release surface
    W3DScreenshot->>BGThread: CreateThread(screenshotThreadFunc, data)
    W3DScreenshot->>User: "TheInGameUI->message(GUI:ScreenCapture)"
    BGThread->>BGThread: stbi_write_jpg / stbi_write_png
    BGThread->>BGThread: delete imageData, delete threadData
Loading

Reviews (14): Last reviewed commit: "fix(stb): Create stb target on both vcpk..." | Re-trigger Greptile

Copy link
Copy Markdown

@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, 7 comments

Edit Code Review Agent Settings | Greptile

Comment thread Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScreenshot.cpp Outdated
Comment thread Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScreenshot.cpp Outdated
Comment thread Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScreenshot.cpp Outdated
Comment thread Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DScreenshot.h
@bobtista bobtista force-pushed the bobtista/compressed-screenshot-f11 branch from 93bd190 to 516acb2 Compare February 16, 2026 18:47
Copy link
Copy Markdown

@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

Comment thread Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp Outdated
Comment thread Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DScreenshot.h
@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
Copy Markdown

@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

Comment thread Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/OptionsMenu.cpp Outdated
Copy link
Copy Markdown

@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 thread Generals/Code/GameEngine/Include/Common/GlobalData.h
Comment thread Generals/Code/GameEngineDevice/CMakeLists.txt Outdated
@bobtista bobtista force-pushed the bobtista/compressed-screenshot-f11 branch from c4673ea to 45c0339 Compare February 16, 2026 21:34
Copy link
Copy Markdown

@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 thread Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DScreenshot.cpp Outdated
@greptile-apps
Copy link
Copy Markdown

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.

@bobtista bobtista force-pushed the bobtista/compressed-screenshot-f11 branch from 65867f4 to 7704e08 Compare February 24, 2026 18:06
Copy link
Copy Markdown

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

33 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment thread cmake/stb.cmake
Copy link
Copy Markdown

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

33 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment thread cmake/stb.cmake
@bobtista bobtista force-pushed the bobtista/compressed-screenshot-f11 branch from 8779ab1 to b709c32 Compare February 25, 2026 00:03
Comment thread Core/GameEngine/Source/Common/OptionPreferences.cpp Outdated
@bobtista bobtista force-pushed the bobtista/compressed-screenshot-f11 branch from 35ba6d6 to 2e284e0 Compare April 1, 2026 19:34
@bobtista
Copy link
Copy Markdown
Author

bobtista commented Apr 3, 2026

Any more changes requested? Or is this good to merge?

@githubawn
Copy link
Copy Markdown

githubawn commented Apr 10, 2026

LGTM, frametime analysis shows zero stutter.

Nitpick 1: The screenshot naming logic seems to be incrementing per file extension (or filling the lowest available index) rather than using a global counter. This could break 'Sort by Name' in the destination folder because a newer PNG might get a lower index than an older JPG.

Nitpick 2: The shortcut for uncompressed images has been changed from F12 to Ctrl+F12. This took me a whole few seconds to get used to. But for users this might be preferred.

Neither of these are blockers for me personally.

@Skyaero42
Copy link
Copy Markdown

Nitpick 1: The screenshot naming logic seems to be incrementing per file extension (or filling the lowest available index) rather than using a global counter. This could break 'Sort by Name' in the destination folder because a newer PNG might get a lower index than an older JPG.

Maybe the filename should contain the date and time (incl milliseconds) instead as a sequencer

@bobtista bobtista force-pushed the bobtista/compressed-screenshot-f11 branch from 2e284e0 to fa826df Compare April 24, 2026 00:39
@bobtista
Copy link
Copy Markdown
Author

bobtista commented May 21, 2026

JPEG

I'm happy to add this now or as a follow on PR, would be good to get this merged though. Edit: Done, went with Skyaero's suggestion.

Comment thread cmake/stb.cmake
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

8 participants