From e21cb615a4445bd314267fab307e56f2bd3eb2f1 Mon Sep 17 00:00:00 2001 From: Schogol Date: Sat, 25 Jul 2026 22:29:43 +0200 Subject: [PATCH 1/5] Add interactive occlusion + WINDOW_HIDDEN throttle test to TrinityALTest SwapChain.OcclusionThrottleProbe presents the primary swapchain to the visible test window in a loop and logs loop-fps vs actual render-fps per window state. It detects occlusion from window geometry (IsWindowOccluded: sample 5 points on the window, skipping see-through overlays like the Alt+Tab switcher; minimized counts as hidden) and, while occluded, emulates the WINDOW_HIDDEN throttle (20ms sleep + render 1-in-50) to show rendering collapse to ~1 fps and recover when visible. Manual/interactive (needs a real GPU + visible window), run with: --interactive --gtest_filter=SwapChain.OcclusionThrottleProbe then cover / minimize / restore the window and read occlusion_throttle_test.log. Used to establish that DXGI_STATUS_OCCLUDED is unavailable for flip-model swapchains (both Present and Present(0, DXGI_PRESENT_TEST) stay S_OK while covered) and that Win32 window-geometry occlusion is a reliable substitute. --- trinityal/tests/SwapChain.cpp | 139 ++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) diff --git a/trinityal/tests/SwapChain.cpp b/trinityal/tests/SwapChain.cpp index 312c25c08..97dc42ab0 100644 --- a/trinityal/tests/SwapChain.cpp +++ b/trinityal/tests/SwapChain.cpp @@ -118,6 +118,145 @@ TEST_F( SwapChain, CanPresentSwapChain ) } } +namespace +{ +// Win32 occlusion check (DXGI-independent): true if the window is minimized, or every sampled point is +// covered by another opaque top-level window. Mirrors the validated Python prototype: sample 5 points +// (centre + 4 inset corners), and at each find the topmost OPAQUE window (skipping see-through overlays: +// WS_EX_LAYERED / WS_EX_TRANSPARENT / WS_EX_TOOLWINDOW, e.g. the Alt+Tab switcher). +bool PointTopIsWindow( HWND target, int px, int py ) +{ + struct Ctx + { + int px, py; + HWND result; + } ctx = { px, py, nullptr }; + ::EnumWindows( + []( HWND hwnd, LPARAM lp ) -> BOOL { + Ctx* c = reinterpret_cast( lp ); + if( !::IsWindowVisible( hwnd ) || ::IsIconic( hwnd ) ) + return TRUE; + if( ::GetWindowLongW( hwnd, GWL_EXSTYLE ) & ( WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW ) ) + return TRUE; + RECT r; + if( !::GetWindowRect( hwnd, &r ) ) + return TRUE; + if( c->px >= r.left && c->px < r.right && c->py >= r.top && c->py < r.bottom ) + { + c->result = hwnd; // topmost opaque window covering this point + return FALSE; + } + return TRUE; + }, + reinterpret_cast( &ctx ) ); + return ctx.result == target; +} + +bool IsWindowOccluded( HWND hwnd ) +{ + if( !hwnd ) + return false; + if( ::IsIconic( hwnd ) ) + return true; // minimized + RECT r; + if( !::GetWindowRect( hwnd, &r ) ) + return false; + const int w = r.right - r.left; + const int h = r.bottom - r.top; + if( w <= 0 || h <= 0 ) + return true; + const double fx[5] = { 0.5, 0.12, 0.88, 0.12, 0.88 }; + const double fy[5] = { 0.5, 0.12, 0.12, 0.88, 0.88 }; + for( int i = 0; i < 5; ++i ) + { + if( PointTopIsWindow( hwnd, r.left + int( fx[i] * w ), r.top + int( fy[i] * h ) ) ) + return false; // our window is visible at this point + } + return true; // every sampled point covered by another opaque window +} +} + +// Interactive occlusion probe: presents the primary swapchain (bound to the visible 640x480 window) in a +// loop and logs the DXGI Present HRESULT + fps. Cover / minimize / half-cover the window and read the log; +// DXGI_STATUS_OCCLUDED should appear only when the window is fully covered or minimized. +// TrinityALTest_dx12_internal.exe --interactive --gtest_filter=SwapChain.OcclusionThrottleProbe +TEST_F( SwapChain, OcclusionThrottleProbe ) +{ + ENSURE_GPU_OR_SKIP + + FILE* log = nullptr; + fopen_s( &log, "occlusion_throttle_test.log", "w" ); + if( log ) + { + fprintf( log, "-- WINDOW_HIDDEN throttle demo: covered/minimized -> Win32-occlusion-gated throttle " + "(20ms sleep + render 1/50, matching TriDevice::Throttle + ShouldSkipFrame). " + "Click window + press a key to exit --\n" ); + fflush( log ); + } + + LARGE_INTEGER freq; + QueryPerformanceFrequency( &freq ); + LARGE_INTEGER lastLog; + QueryPerformanceCounter( &lastLog ); + int frame = 0; // loop iterations + int renders = 0; // actual renders since last log + unsigned skip = 0; + uint32_t g = 0; + + auto body = [&] { + const bool occluded = IsWindowOccluded( (HWND)WithWindow::GetWindowHandle() ); + + // Emulate TriDevice::ShouldSkipFrame() for WINDOW_HIDDEN: render only 1 frame in 50. + bool doRender = true; + if( occluded ) + doRender = ( ( skip++ % 50 ) == 0 ); + else + skip = 0; + + if( doRender ) + { + ASSERT_HRESULT_SUCCEEDED( renderContext->BeginScene() ); + ASSERT_HRESULT_SUCCEEDED( renderContext->PushDepthStencil() ); + ASSERT_HRESULT_SUCCEEDED( renderContext->SetDepthStencil( Tr2TextureAL() ) ); + ASSERT_HRESULT_SUCCEEDED( renderContext->PushRenderTarget() ); + ASSERT_HRESULT_SUCCEEDED( renderContext->SetRenderTarget( renderContext->GetDefaultBackBuffer() ) ); + ASSERT_HRESULT_SUCCEEDED( renderContext->Clear( Tr2RenderContextEnum::CLEARFLAGS_TARGET, 0xff000000 | ( ( g & 0xff ) << 8 ), 1.0f ) ); + ASSERT_HRESULT_SUCCEEDED( renderContext->PopRenderTarget() ); + ASSERT_HRESULT_SUCCEEDED( renderContext->PopDepthStencil() ); + ASSERT_HRESULT_SUCCEEDED( renderContext->EndScene() ); + ASSERT_HRESULT_SUCCEEDED( renderContext->Present() ); + ++renders; + } + + // Emulate TriDevice::Throttle() sleep for WINDOW_HIDDEN (20ms). + if( occluded ) + ::Sleep( 20 ); + + ++frame; + ++g; + + if( frame % 30 == 0 && log ) + { + LARGE_INTEGER now; + QueryPerformanceCounter( &now ); + const double elapsed = double( now.QuadPart - lastLog.QuadPart ) / double( freq.QuadPart ); + lastLog = now; + const double loopFps = elapsed > 0.0 ? 30.0 / elapsed : 0.0; + const double renderFps = elapsed > 0.0 ? renders / elapsed : 0.0; + renders = 0; + fprintf( log, "frame %6d | %-8s throttle=%-3s | loopFps=%9.1f | renderFps=%8.1f\n", frame, occluded ? "OCCLUDED" : "visible", occluded ? "ON" : "off", loopFps, renderFps ); + fflush( log ); + } + }; + + RunLoop( body ); + + if( log ) + { + fclose( log ); + } +} + TEST_F( SwapChain, CanRecreateSwapChain ) { ENSURE_GPU_OR_SKIP From fbb4717ddf02d606a38cd8228fe522ffa675fe60 Mon Sep 17 00:00:00 2001 From: Schogol Date: Fri, 11 Sep 2026 00:12:20 +0200 Subject: [PATCH 2/5] Probe: mirror the accumulating R2-sampled occlusion detection Match the revised Tr2MainWindow detection exactly: 8 quasirandom sample points per 250ms tick from the R2 (plastic ratio) sequence, hidden only after 32 consecutive covered samples, one EnumWindows pass per tick collecting the opaque occluder rects above us in Z-order, DWM-cloaked windows skipped, off-monitor points counted as covered. Log state transitions with time-in-state and the covered-sample counter, and add a samples= column to the periodic fps lines, so engage latency (~1s covered) and next-tick release are visible in the log. --- trinityal/tests/SwapChain.cpp | 169 ++++++++++++++++++++++++++-------- 1 file changed, 131 insertions(+), 38 deletions(-) diff --git a/trinityal/tests/SwapChain.cpp b/trinityal/tests/SwapChain.cpp index 97dc42ab0..9ae62c04a 100644 --- a/trinityal/tests/SwapChain.cpp +++ b/trinityal/tests/SwapChain.cpp @@ -118,67 +118,135 @@ TEST_F( SwapChain, CanPresentSwapChain ) } } +#include +#include + +#pragma comment( lib, "dwmapi.lib" ) + namespace { -// Win32 occlusion check (DXGI-independent): true if the window is minimized, or every sampled point is -// covered by another opaque top-level window. Mirrors the validated Python prototype: sample 5 points -// (centre + 4 inset corners), and at each find the topmost OPAQUE window (skipping see-through overlays: -// WS_EX_LAYERED / WS_EX_TRANSPARENT / WS_EX_TOOLWINDOW, e.g. the Alt+Tab switcher). -bool PointTopIsWindow( HWND target, int px, int py ) +// Win32 occlusion check (DXGI-independent), mirroring the Tr2MainWindow_Windows.cpp fix exactly: each +// 250ms tick tests a batch of quasirandomly placed points against the opaque top-level windows above us +// in Z-order (one EnumWindows pass per tick). The window only counts as hidden once every sample of +// OCCLUSION_SAMPLES_REQUIRED consecutive samples was covered (or it is minimized); a single visible +// sample resets the run. +const int OCCLUSION_SAMPLES_PER_TICK = 8; +const int OCCLUSION_SAMPLES_REQUIRED = 32; // 4 fully covered ticks (~1s) before the throttle engages + +// Sample positions come from the R2 quasirandom sequence (generalised golden ratio phi2, the "plastic +// ratio"): successive points are evenly distributed over the window without aligning to window geometry. +const double INVERSE_PHI2 = 1.0 / 1.32471795724474602596; +double g_occlusionSeqX = 0.5; +double g_occlusionSeqY = 0.5; +int g_occlusionCoveredSamples = 0; + +// Suspended UWP/store apps keep a WS_VISIBLE top-level window in the Z-order that draws nothing; without +// this check such a window would count as an occluder and could throttle a perfectly visible client. +bool IsWindowCloaked( HWND hwnd ) +{ + DWORD cloaked = 0; + return SUCCEEDED( ::DwmGetWindowAttribute( hwnd, DWMWA_CLOAKED, &cloaked, sizeof( cloaked ) ) ) && cloaked != 0; +} + +// One EnumWindows pass per tick: collect the rects of every opaque, uncloaked window above hwnd in +// Z-order. EnumWindows enumerates topmost first, so collection stops at hwnd - nothing below it can +// occlude it - and each sample point then costs only a few rect tests. +void CollectOccludersAbove( HWND hwnd, std::vector& rects ) { struct Ctx { - int px, py; - HWND result; - } ctx = { px, py, nullptr }; + HWND target; + std::vector* rects; + } ctx = { hwnd, &rects }; ::EnumWindows( - []( HWND hwnd, LPARAM lp ) -> BOOL { + []( HWND other, LPARAM lp ) -> BOOL { Ctx* c = reinterpret_cast( lp ); - if( !::IsWindowVisible( hwnd ) || ::IsIconic( hwnd ) ) + if( other == c->target ) + return FALSE; + if( !::IsWindowVisible( other ) || ::IsIconic( other ) ) return TRUE; - if( ::GetWindowLongW( hwnd, GWL_EXSTYLE ) & ( WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW ) ) + if( ::GetWindowLongW( other, GWL_EXSTYLE ) & ( WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW ) ) return TRUE; - RECT r; - if( !::GetWindowRect( hwnd, &r ) ) + if( IsWindowCloaked( other ) ) return TRUE; - if( c->px >= r.left && c->px < r.right && c->py >= r.top && c->py < r.bottom ) - { - c->result = hwnd; // topmost opaque window covering this point - return FALSE; - } + RECT r; + if( ::GetWindowRect( other, &r ) ) + c->rects->push_back( r ); return TRUE; }, reinterpret_cast( &ctx ) ); - return ctx.result == target; } -bool IsWindowOccluded( HWND hwnd ) +// Per-tick occlusion sample; returns whether the window should currently count as hidden. Stateful: the +// covered-sample run accumulates across ticks (see block comment above). +bool SampleWindowOcclusion( HWND hwnd ) { if( !hwnd ) + { + g_occlusionCoveredSamples = 0; return false; + } if( ::IsIconic( hwnd ) ) - return true; // minimized + { + g_occlusionCoveredSamples = 0; + return true; // minimized is unambiguous, no sample accumulation needed + } RECT r; if( !::GetWindowRect( hwnd, &r ) ) + { + g_occlusionCoveredSamples = 0; return false; + } const int w = r.right - r.left; const int h = r.bottom - r.top; if( w <= 0 || h <= 0 ) + { + g_occlusionCoveredSamples = 0; return true; - const double fx[5] = { 0.5, 0.12, 0.88, 0.12, 0.88 }; - const double fy[5] = { 0.5, 0.12, 0.12, 0.88, 0.88 }; - for( int i = 0; i < 5; ++i ) + } + + std::vector occluders; + CollectOccludersAbove( hwnd, occluders ); + + for( int i = 0; i < OCCLUSION_SAMPLES_PER_TICK; ++i ) { - if( PointTopIsWindow( hwnd, r.left + int( fx[i] * w ), r.top + int( fy[i] * h ) ) ) - return false; // our window is visible at this point + g_occlusionSeqX += INVERSE_PHI2; + g_occlusionSeqX -= std::floor( g_occlusionSeqX ); + g_occlusionSeqY += INVERSE_PHI2 * INVERSE_PHI2; + g_occlusionSeqY -= std::floor( g_occlusionSeqY ); + const POINT pt = { r.left + LONG( g_occlusionSeqX * w ), r.top + LONG( g_occlusionSeqY * h ) }; + bool covered = false; + for( const RECT& occluder : occluders ) + { + if( ::PtInRect( &occluder, pt ) ) + { + covered = true; + break; + } + } + // A point no occluder covers can still be invisible: outside every monitor (window dragged or + // stranded off-screen). Count those as covered, as the topmost-window-at-point variant did. + if( !covered && ::MonitorFromPoint( pt, MONITOR_DEFAULTTONULL ) == nullptr ) + covered = true; + if( !covered ) + { + g_occlusionCoveredSamples = 0; + return false; + } } - return true; // every sampled point covered by another opaque window + if( g_occlusionCoveredSamples < OCCLUSION_SAMPLES_REQUIRED ) + g_occlusionCoveredSamples += OCCLUSION_SAMPLES_PER_TICK; + return g_occlusionCoveredSamples >= OCCLUSION_SAMPLES_REQUIRED; } } // Interactive occlusion probe: presents the primary swapchain (bound to the visible 640x480 window) in a -// loop and logs the DXGI Present HRESULT + fps. Cover / minimize / half-cover the window and read the log; -// DXGI_STATUS_OCCLUDED should appear only when the window is fully covered or minimized. +// loop while sampling occlusion exactly like the Tr2MainWindow WINDOW_HIDDEN fix: every 250ms tick, 8 +// quasirandom points are tested against the opaque windows above us in Z-order; the throttle engages only +// after 32 consecutive covered samples (~1s fully covered) and releases on the first visible sample. +// While "hidden" it emulates the WINDOW_HIDDEN behaviour (20ms sleep + render 1/50, matching +// TriDevice::Throttle + ShouldSkipFrame). Cover / half-cover / minimize the window and read the log; +// state transitions are logged with the covered-sample counter. // TrinityALTest_dx12_internal.exe --interactive --gtest_filter=SwapChain.OcclusionThrottleProbe TEST_F( SwapChain, OcclusionThrottleProbe ) { @@ -188,9 +256,11 @@ TEST_F( SwapChain, OcclusionThrottleProbe ) fopen_s( &log, "occlusion_throttle_test.log", "w" ); if( log ) { - fprintf( log, "-- WINDOW_HIDDEN throttle demo: covered/minimized -> Win32-occlusion-gated throttle " - "(20ms sleep + render 1/50, matching TriDevice::Throttle + ShouldSkipFrame). " - "Click window + press a key to exit --\n" ); + fprintf( log, "-- WINDOW_HIDDEN throttle demo (accumulating R2-sampled occlusion, %d samples per " + "250ms tick, hidden at %d consecutive covered samples). Covered/minimized -> 20ms " + "sleep + render 1/50, matching TriDevice::Throttle + ShouldSkipFrame. " + "Click window + press a key to exit --\n", + OCCLUSION_SAMPLES_PER_TICK, OCCLUSION_SAMPLES_REQUIRED ); fflush( log ); } @@ -198,13 +268,36 @@ TEST_F( SwapChain, OcclusionThrottleProbe ) QueryPerformanceFrequency( &freq ); LARGE_INTEGER lastLog; QueryPerformanceCounter( &lastLog ); + LARGE_INTEGER lastTick = lastLog; + LARGE_INTEGER stateSince = lastLog; int frame = 0; // loop iterations int renders = 0; // actual renders since last log unsigned skip = 0; uint32_t g = 0; + bool occluded = false; auto body = [&] { - const bool occluded = IsWindowOccluded( (HWND)WithWindow::GetWindowHandle() ); + // Sample on the same 250ms cadence as the Tr2MainWindow occlusion timer. + LARGE_INTEGER now; + QueryPerformanceCounter( &now ); + if( double( now.QuadPart - lastTick.QuadPart ) / double( freq.QuadPart ) >= 0.25 ) + { + lastTick = now; + const bool nowOccluded = SampleWindowOcclusion( (HWND)WithWindow::GetWindowHandle() ); + if( nowOccluded != occluded ) + { + if( log ) + { + const double inState = double( now.QuadPart - stateSince.QuadPart ) / double( freq.QuadPart ); + fprintf( log, "== transition %s -> %s (%.2fs in previous state, coveredSamples=%d)\n", + occluded ? "OCCLUDED" : "visible", nowOccluded ? "OCCLUDED" : "visible", inState, + g_occlusionCoveredSamples ); + fflush( log ); + } + stateSince = now; + } + occluded = nowOccluded; + } // Emulate TriDevice::ShouldSkipFrame() for WINDOW_HIDDEN: render only 1 frame in 50. bool doRender = true; @@ -237,14 +330,14 @@ TEST_F( SwapChain, OcclusionThrottleProbe ) if( frame % 30 == 0 && log ) { - LARGE_INTEGER now; - QueryPerformanceCounter( &now ); - const double elapsed = double( now.QuadPart - lastLog.QuadPart ) / double( freq.QuadPart ); - lastLog = now; + LARGE_INTEGER logNow; + QueryPerformanceCounter( &logNow ); + const double elapsed = double( logNow.QuadPart - lastLog.QuadPart ) / double( freq.QuadPart ); + lastLog = logNow; const double loopFps = elapsed > 0.0 ? 30.0 / elapsed : 0.0; const double renderFps = elapsed > 0.0 ? renders / elapsed : 0.0; renders = 0; - fprintf( log, "frame %6d | %-8s throttle=%-3s | loopFps=%9.1f | renderFps=%8.1f\n", frame, occluded ? "OCCLUDED" : "visible", occluded ? "ON" : "off", loopFps, renderFps ); + fprintf( log, "frame %6d | %-8s throttle=%-3s | samples=%2d | loopFps=%9.1f | renderFps=%8.1f\n", frame, occluded ? "OCCLUDED" : "visible", occluded ? "ON" : "off", g_occlusionCoveredSamples, loopFps, renderFps ); fflush( log ); } }; From 0216693855cbc3aacea8ac76573aba52373762c9 Mon Sep 17 00:00:00 2001 From: Schogol Date: Fri, 11 Sep 2026 00:24:18 +0200 Subject: [PATCH 3/5] Probe: shareable log, clean exit when the render window is closed - Open the log with _fsopen(_SH_DENYWR) instead of fopen_s, which denies all sharing; the log can now be read and tailed during a run. - The window class uses DefWindowProc, so the close button destroys the HWND without posting WM_QUIT and the loop previously spun on forever (a destroyed window samples as "visible" and unthrottled, at ~30000 fps). Detect the destroyed window, log it, and post WM_QUIT so DoLoopProcessing ends the RunLoop. --- trinityal/tests/SwapChain.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/trinityal/tests/SwapChain.cpp b/trinityal/tests/SwapChain.cpp index 9ae62c04a..7d87efc01 100644 --- a/trinityal/tests/SwapChain.cpp +++ b/trinityal/tests/SwapChain.cpp @@ -120,6 +120,7 @@ TEST_F( SwapChain, CanPresentSwapChain ) #include #include +#include #pragma comment( lib, "dwmapi.lib" ) @@ -252,8 +253,9 @@ TEST_F( SwapChain, OcclusionThrottleProbe ) { ENSURE_GPU_OR_SKIP - FILE* log = nullptr; - fopen_s( &log, "occlusion_throttle_test.log", "w" ); + // _fsopen with _SH_DENYWR rather than fopen_s (which denies all sharing) so the log can be read + // and tailed while the probe is running. + FILE* log = _fsopen( "occlusion_throttle_test.log", "w", _SH_DENYWR ); if( log ) { fprintf( log, "-- WINDOW_HIDDEN throttle demo (accumulating R2-sampled occlusion, %d samples per " @@ -277,6 +279,20 @@ TEST_F( SwapChain, OcclusionThrottleProbe ) bool occluded = false; auto body = [&] { + // Closing the render window ends the probe. The window class uses DefWindowProc, so the close + // button destroys the HWND but posts no WM_QUIT; without this check the loop would spin forever, + // with the destroyed window sampling as "visible" (GetWindowRect fails) and thus unthrottled. + if( !::IsWindow( (HWND)WithWindow::GetWindowHandle() ) ) + { + if( log ) + { + fprintf( log, "-- render window closed, exiting --\n" ); + fflush( log ); + } + ::PostQuitMessage( 0 ); // DoLoopProcessing() sees WM_QUIT and ends the RunLoop + return; + } + // Sample on the same 250ms cadence as the Tr2MainWindow occlusion timer. LARGE_INTEGER now; QueryPerformanceCounter( &now ); From 94b34f0a9e588f21101c7b8f9bf297f31884febc Mon Sep 17 00:00:00 2001 From: Schogol Date: Fri, 11 Sep 2026 00:40:59 +0200 Subject: [PATCH 4/5] Probe: raise occlusion threshold to 64 consecutive covered samples (~2s) --- trinityal/tests/SwapChain.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/trinityal/tests/SwapChain.cpp b/trinityal/tests/SwapChain.cpp index 7d87efc01..7bf27cc75 100644 --- a/trinityal/tests/SwapChain.cpp +++ b/trinityal/tests/SwapChain.cpp @@ -132,7 +132,7 @@ namespace // OCCLUSION_SAMPLES_REQUIRED consecutive samples was covered (or it is minimized); a single visible // sample resets the run. const int OCCLUSION_SAMPLES_PER_TICK = 8; -const int OCCLUSION_SAMPLES_REQUIRED = 32; // 4 fully covered ticks (~1s) before the throttle engages +const int OCCLUSION_SAMPLES_REQUIRED = 64; // 8 fully covered ticks (~2s) before the throttle engages // Sample positions come from the R2 quasirandom sequence (generalised golden ratio phi2, the "plastic // ratio"): successive points are evenly distributed over the window without aligning to window geometry. @@ -244,7 +244,7 @@ bool SampleWindowOcclusion( HWND hwnd ) // Interactive occlusion probe: presents the primary swapchain (bound to the visible 640x480 window) in a // loop while sampling occlusion exactly like the Tr2MainWindow WINDOW_HIDDEN fix: every 250ms tick, 8 // quasirandom points are tested against the opaque windows above us in Z-order; the throttle engages only -// after 32 consecutive covered samples (~1s fully covered) and releases on the first visible sample. +// after 64 consecutive covered samples (~2s fully covered) and releases on the first visible sample. // While "hidden" it emulates the WINDOW_HIDDEN behaviour (20ms sleep + render 1/50, matching // TriDevice::Throttle + ShouldSkipFrame). Cover / half-cover / minimize the window and read the log; // state transitions are logged with the covered-sample counter. From 64e3bab55f4fad9b28274829abf75ce532904463 Mon Sep 17 00:00:00 2001 From: Schogol Date: Sun, 11 Oct 2026 03:30:49 +0200 Subject: [PATCH 5/5] Probe: use float accumulators for the R2 sequence, matching the fix --- trinityal/tests/SwapChain.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/trinityal/tests/SwapChain.cpp b/trinityal/tests/SwapChain.cpp index 7bf27cc75..aa7443638 100644 --- a/trinityal/tests/SwapChain.cpp +++ b/trinityal/tests/SwapChain.cpp @@ -136,9 +136,9 @@ const int OCCLUSION_SAMPLES_REQUIRED = 64; // 8 fully covered ticks (~2s) before // Sample positions come from the R2 quasirandom sequence (generalised golden ratio phi2, the "plastic // ratio"): successive points are evenly distributed over the window without aligning to window geometry. -const double INVERSE_PHI2 = 1.0 / 1.32471795724474602596; -double g_occlusionSeqX = 0.5; -double g_occlusionSeqY = 0.5; +const float INVERSE_PHI2 = 1.0f / 1.32471795724474602596f; +float g_occlusionSeqX = 0.5f; +float g_occlusionSeqY = 0.5f; int g_occlusionCoveredSamples = 0; // Suspended UWP/store apps keep a WS_VISIBLE top-level window in the Z-order that draws nothing; without