@@ -179,6 +179,13 @@ public partial class OsuGameAndroid : OsuGame
179179 private IntPtr adpfDrawSession ;
180180 private IntPtr adpfUpdateSession ;
181181
182+ // Cached GameThread references stored when ADPF sessions are created. Avoids walking
183+ // Host?.DrawThread? / Host?.UpdateThread? / Host?.InputThread? on every frame callback
184+ // (3 levels of nullable dereference at 120 Hz × 3 threads = 360 null checks/s → 0).
185+ private osu . Framework . Threading . GameThread ? adpfDrawThread ;
186+ private osu . Framework . Threading . GameThread ? adpfUpdateThread ;
187+ private osu . Framework . Threading . GameThread ? adpfInputThread ;
188+
182189 // Input thread ADPF session. The input thread may poll at ~100 kHz (one cycle ≈ 10 µs),
183190 // which is far finer than the ADPF reporting granularity. Instead of calling
184191 // reportActualWorkDuration once per poll (which would flood the ADPF API), we accumulate
@@ -603,6 +610,9 @@ protected override void LoadComplete()
603610 // Silent no-op on API < 35 (resolved via dlsym).
604611 OboeAudioBridge . nADPFSetPreferPowerEfficiency ( adpfDrawSession , 0 ) ;
605612 Logger . Log ( $ "[osu!] ADPF session created for Draw thread (target={ targetNs / 1_000_000.0 : F2} ms)", LoggingTarget . Performance ) ;
613+ // Cache the thread reference now (we're already on the Draw thread)
614+ // so per-frame callbacks avoid the Host?.DrawThread? nullable chain.
615+ adpfDrawThread = Host ! . DrawThread ;
606616 // Subscribe per-frame reporting now that the session handle is valid.
607617 // FrameCompleted fires on the Draw thread itself, so reading
608618 // Host.DrawThread.Clock.ElapsedFrameTime is thread-safe.
@@ -622,6 +632,7 @@ protected override void LoadComplete()
622632 {
623633 OboeAudioBridge . nADPFSetPreferPowerEfficiency ( adpfUpdateSession , 0 ) ;
624634 Logger . Log ( $ "[osu!] ADPF session created for Update thread (target={ targetNs / 1_000_000.0 : F2} ms)", LoggingTarget . Performance ) ;
635+ adpfUpdateThread = Host ! . UpdateThread ;
625636 Host ! . UpdateThread ! . FrameCompleted += onUpdateFrameCompleted ;
626637 }
627638 }
@@ -643,6 +654,7 @@ protected override void LoadComplete()
643654 {
644655 OboeAudioBridge . nADPFSetPreferPowerEfficiency ( adpfInputSession , 0 ) ;
645656 Logger . Log ( $ "[osu!] ADPF session created for Input thread (target={ targetNs / 1_000_000.0 : F2} ms)", LoggingTarget . Performance ) ;
657+ adpfInputThread = Host ! . InputThread ;
646658 Host ! . InputThread ! . FrameCompleted += onInputFrameCompleted ;
647659 }
648660 }
@@ -1607,7 +1619,7 @@ private void onDrawFrameCompleted()
16071619
16081620 try
16091621 {
1610- double elapsedMs = Host ? . DrawThread ? . Clock . ElapsedFrameTime ?? 0 ;
1622+ double elapsedMs = adpfDrawThread ? . Clock . ElapsedFrameTime ?? 0 ;
16111623 if ( elapsedMs > 0 )
16121624 OboeAudioBridge . nADPFReportActualDuration ( adpfDrawSession , ( long ) ( elapsedMs * 1_000_000.0 ) ) ;
16131625 }
@@ -1624,7 +1636,7 @@ private void onUpdateFrameCompleted()
16241636
16251637 try
16261638 {
1627- double elapsedMs = Host ? . UpdateThread ? . Clock . ElapsedFrameTime ?? 0 ;
1639+ double elapsedMs = adpfUpdateThread ? . Clock . ElapsedFrameTime ?? 0 ;
16281640 if ( elapsedMs > 0 )
16291641 OboeAudioBridge . nADPFReportActualDuration ( adpfUpdateSession , ( long ) ( elapsedMs * 1_000_000.0 ) ) ;
16301642 }
@@ -1644,7 +1656,7 @@ private void onInputFrameCompleted()
16441656
16451657 try
16461658 {
1647- double elapsedMs = Host ? . InputThread ? . Clock . ElapsedFrameTime ?? 0 ;
1659+ double elapsedMs = adpfInputThread ? . Clock . ElapsedFrameTime ?? 0 ;
16481660 if ( elapsedMs <= 0 ) return ;
16491661
16501662 inputAdpfAccumulatedMs += elapsedMs ;
0 commit comments