Skip to content

Commit 129e3d2

Browse files
Shadetailclaude
andcommitted
Region picker: HDR preview via D3D11 + ImGui, no more GDI
The drag-to-select picker now freezes the app's own capture texture and presents it on a second scRGB FP16 swapchain with its own ImGui context, so HDR desktops preview at their true nit values (8-bit SDR captures are viewed through an _SRGB SRV). Same gestures and return contract as before; the call site waits for a post-hide capture frame so the frozen frame excludes the HDRScopes window. Drops msimg32. Font building is shared with the main window via util/UiFonts.h. Suggested by Kaldaien. Also fixed: retargeting between same-resolution HDR/SDR outputs kept the old-format capture texture, so CopyResource silently failed and the scopes showed the previous monitor's last frame. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent c160a4b commit 129e3d2

12 files changed

Lines changed: 416 additions & 140 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ so every release ships with a readable summary.
66

77
## v1.0.7 — 2026-09-06
88

9+
- The "Select region on screen (drag)" picker now shows the frozen screen in
10+
true HDR: it draws the app's own capture on an scRGB swapchain instead of
11+
taking a GDI screenshot, which could only ever be SDR (and tone-mapped HDR
12+
desktops into the bargain). Same gestures as before — drag to select,
13+
release / Enter to confirm, Esc / right-click to cancel. Thanks to Kaldaien
14+
(Special K / SKIV) for pointing out the GDI path and suggesting the
15+
D3D11 route.
16+
- Fixed: switching the captured monitor between an HDR and an SDR output of
17+
the same resolution left the scopes (and now the picker) showing the old
18+
monitor's last frame until the capture was re-created.
919
- The top readout can now also show the average luminance of the scoped
1020
region — the mean Rec.709 luminance of every pixel, in nits, i.e. the
1121
frame's overall light level (what SKIV's details panel calls Avg

‎CMakeLists.txt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ add_executable(HDRScopes WIN32
5050
target_include_directories(HDRScopes PRIVATE src resources)
5151
target_link_libraries(HDRScopes PRIVATE
5252
imgui
53-
d3d11 dxgi d3dcompiler dxguid dwmapi msimg32 winmm winhttp shell32
53+
d3d11 dxgi d3dcompiler dxguid dwmapi winmm winhttp shell32
5454
)
5555
target_compile_definitions(HDRScopes PRIVATE UNICODE _UNICODE NOMINMAX WIN32_LEAN_AND_MEAN)
5656

‎README.md‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,10 @@ need that.
8787
<img align="right" width="260" src="docs/img/controls.png" alt="Controls popup">
8888

8989
- **Controls** (top-right button) opens the settings popup: capture source
90-
(monitor / window / dragged screen region), quality, and per-panel scope
91-
options. Rest the mouse on any control for an explanation (tooltips can be
92-
turned off under Preferences); right-click any control to reset just it.
90+
(monitor / window / dragged screen region — the drag picker previews the
91+
frozen screen in true HDR), quality, and per-panel scope options. Rest the
92+
mouse on any control for an explanation (tooltips can be turned off under
93+
Preferences); right-click any control to reset just it.
9394
- **Layouts** — the `1 / 2 / 4` buttons switch layout presets; each panel has
9495
a scope picker. Right-click `2` or `4` (or toggle Controls > Layout > "Stack
9596
panels vertically") to flip the multi-panel layouts between side-by-side and

‎docs/ARCHITECTURE.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ probe). `ScopeFactory` + `ScopePanel` host the 1/2/4-up layout.
2020
`settings.ini` next to the exe — local-first/portable — falling back to
2121
`%LOCALAPPDATA%\HDRScopes` when the exe's folder isn't writable; a pre-1.0.1
2222
file there still loads and migrates on first save.
23+
- `capture/RegionPicker` is the drag-to-select region picker: a topmost popup
24+
over the captured output with its own scRGB FP16 swapchain and a second
25+
ImGui context on the app's D3D11 device (no GDI). The frozen frame is a
26+
copy of `CaptureSource`'s texture, so on HDR outputs it is shown at its
27+
true nit values (8-bit SDR captures are viewed through an `_SRGB` SRV).
2328
- `util/SdrWhite` queries the Windows SDR-white level.
2429
- `util/UpdateCheck` is the startup update check: a background WinHTTP GET of
2530
`github.com/<repo>/releases/latest` with redirects disabled — the newest

‎src/capture/CaptureSource.cpp‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,15 @@ void CaptureSource::ReleaseDuplicator() {
166166
}
167167

168168
bool CaptureSource::EnsureTexture(UINT w, UINT h, DXGI_FORMAT fmt) {
169-
if (tex_ && width_ == w && height_ == h && format_ == fmt) return true;
169+
// Compare against the texture's own description, not format_: a retarget
170+
// rewrites format_ from the new duplicator before the first frame arrives,
171+
// so an HDR<->SDR switch between same-resolution outputs used to keep the
172+
// old-format texture and every CopyResource into it silently failed.
173+
if (tex_) {
174+
D3D11_TEXTURE2D_DESC cur = {};
175+
tex_->GetDesc(&cur);
176+
if (cur.Width == w && cur.Height == h && cur.Format == fmt) return true;
177+
}
170178
tex_.Reset();
171179
srv_.Reset();
172180

@@ -231,6 +239,7 @@ bool CaptureSource::AcquireFrame(UINT timeoutMs) {
231239
if (EnsureTexture(sdesc.Width, sdesc.Height, sdesc.Format)) {
232240
context_->CopyResource(tex_.Get(), srcTex.Get());
233241
hasFrame_ = true;
242+
++frameSerial_;
234243
}
235244
}
236245
}

‎src/capture/CaptureSource.h‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ class CaptureSource {
3838
bool IsHDR() const { return isHDR_; }
3939
DXGI_FORMAT Format() const { return format_; }
4040
bool HasFrame() const { return hasFrame_; }
41+
// A duplicator exists for the selected output. After a failed retarget the
42+
// previous output's frame is still held (HasFrame stays true) but not live.
43+
bool IsLive() const { return dup_ != nullptr; }
44+
// Bumps each time a new desktop frame is copied (cursor-only updates don't
45+
// count), so callers can tell a genuinely fresh frame from a reused one.
46+
UINT64 FrameSerial() const { return frameSerial_; }
4147

4248
// Enumerate outputs for the picker UI. Returns count; fills names/rects.
4349
struct OutputInfo { std::wstring name; RECT rect; bool hdr; int index; };
@@ -64,6 +70,7 @@ class CaptureSource {
6470
bool isHDR_ = false;
6571
bool hasFrame_ = false;
6672
bool frameHeld_ = false;
73+
UINT64 frameSerial_ = 0;
6774
int curOutputIndex_ = 0;
6875
ULONGLONG lastDupAttemptMs_ = 0;
6976
};

0 commit comments

Comments
 (0)