diff --git a/app/src/main/runtime/display/XServerDisplayActivity.java b/app/src/main/runtime/display/XServerDisplayActivity.java index cc095b365..987d2fbff 100644 --- a/app/src/main/runtime/display/XServerDisplayActivity.java +++ b/app/src/main/runtime/display/XServerDisplayActivity.java @@ -624,17 +624,9 @@ private int getGlobalRefreshRateOverride() { return Math.max(0, preferences.getInt("refresh_rate_override", 0)); } - private int getDxvkFrameRateOverride() { - int perGameRate = getPerGameRefreshRateOverride(); - if (perGameRate > 0) { - return perGameRate; - } - - int globalRate = getGlobalRefreshRateOverride(); - if (globalRate > 0) { - return globalRate; - } - return 0; + // FPS-limit slider takes priority over the refresh-rate override. + private int getEffectiveFpsLimit() { + return runtimeFpsLimit > 0 ? runtimeFpsLimit : getRefreshRateOverride(); } private int parsePositiveInt(String value) { @@ -680,10 +672,11 @@ private void applyPreferredRefreshRate() { Runnable applyRefresh = () -> { if (isFinishing() || isDestroyed()) return; - float hz = RefreshRateUtils.applyPreferredRefreshRate(this, getRefreshRateOverride(), runtimeFpsLimit); + int effectiveFpsLimit = getEffectiveFpsLimit(); + float hz = RefreshRateUtils.applyPreferredRefreshRate(this, getRefreshRateOverride(), effectiveFpsLimit); if (xServer != null) { xServer.getFramePaceClock().setDisplayRefreshHz(hz); - xServer.getFramePaceClock().setCapActive(runtimeFpsLimit > 0); + xServer.getFramePaceClock().setCapActive(effectiveFpsLimit > 0); } }; @@ -2334,11 +2327,23 @@ private void handleCapturedPointer(MotionEvent event) { } private int[] getCapturedPointerDelta(MotionEvent event) { - float dx = event.getAxisValue(MotionEvent.AXIS_RELATIVE_X); - float dy = event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y); + // Sum batched samples; skipping history drops movement at low refresh rates. + final int historySize = event.getHistorySize(); + float dx = 0.0f; + float dy = 0.0f; + for (int i = 0; i < historySize; i++) { + dx += event.getHistoricalAxisValue(MotionEvent.AXIS_RELATIVE_X, i); + dy += event.getHistoricalAxisValue(MotionEvent.AXIS_RELATIVE_Y, i); + } + dx += event.getAxisValue(MotionEvent.AXIS_RELATIVE_X); + dy += event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y); if (dx == 0.0f && dy == 0.0f) { - dx = event.getX(); - dy = event.getY(); + for (int i = 0; i < historySize; i++) { + dx += event.getHistoricalX(i); + dy += event.getHistoricalY(i); + } + dx += event.getX(); + dy += event.getY(); } dx *= globalCursorSpeed; dy *= globalCursorSpeed; @@ -4274,7 +4279,7 @@ public void onGyroscopeCardExpandedChanged(boolean expanded) { public void onFPSLimitChanged(int limit) { runtimeFpsLimit = Math.max(0, limit); if (xServerView != null) { - xServerView.getRenderer().setFpsLimit(runtimeFpsLimit); + xServerView.getRenderer().setFpsLimit(getEffectiveFpsLimit()); } applyPreferredRefreshRate(); if (shortcut != null) { @@ -6963,11 +6968,11 @@ private void setupUI() { String savedFpsLimit = shortcut.getExtra("fpsLimit", "0"); try { runtimeFpsLimit = Integer.parseInt(savedFpsLimit); - renderer.setFpsLimit(runtimeFpsLimit); } catch (NumberFormatException e) { runtimeFpsLimit = 0; } } + renderer.setFpsLimit(getEffectiveFpsLimit()); applyScreenEffects(); xServer.setRenderer(renderer); @@ -7560,8 +7565,7 @@ private void extractGraphicsDriverFiles() { File rootDir = imageFs.getRootDir(); if (dxwrapper.contains("dxvk")) { - int refreshRateOverride = getDxvkFrameRateOverride(); - DXVKConfigUtils.setEnvVars(this, dxwrapperConfig, envVars, refreshRateOverride); + DXVKConfigUtils.setEnvVars(this, dxwrapperConfig, envVars); String version = dxwrapperConfig.get("version"); if (version.equals("1.11.1-sarek")) { Log.d("GraphicsDriverExtraction", "Disabling Wrapper PATCH_OPCONSTCOMP SPIR-V pass"); diff --git a/app/src/main/runtime/display/renderer/VulkanRenderer.java b/app/src/main/runtime/display/renderer/VulkanRenderer.java index 058986a45..a804fe339 100644 --- a/app/src/main/runtime/display/renderer/VulkanRenderer.java +++ b/app/src/main/runtime/display/renderer/VulkanRenderer.java @@ -130,6 +130,10 @@ public VulkanRenderer(XServerSurfaceView view, XServer xServer) { this.xServer = xServer; this.effectComposer = new EffectComposer(this); this.rootCursorDrawable = createRootCursorDrawable(); + this.coalescedRenderCallback = frameTimeNanos -> { + renderRequested.set(false); + xServerView.requestRender(); + }; } public void destroy() { @@ -163,13 +167,21 @@ public void destroy() { } } + private volatile Choreographer mainChoreographer; + private final Choreographer.FrameCallback coalescedRenderCallback; + public void requestRenderCoalesced() { if (renderRequested.compareAndSet(false, true)) { - mainHandler.post(() -> - Choreographer.getInstance().postFrameCallback(frameTimeNanos -> { - renderRequested.set(false); - xServerView.requestRender(); - })); + // Post directly (thread-safe): a handler hop arms past the next doFrame and halves the visible cursor rate. + Choreographer choreographer = mainChoreographer; + if (choreographer != null) { + choreographer.postFrameCallback(coalescedRenderCallback); + } else { + mainHandler.post(() -> { + mainChoreographer = Choreographer.getInstance(); + mainChoreographer.postFrameCallback(coalescedRenderCallback); + }); + } } } diff --git a/app/src/main/shared/android/RefreshRateUtils.java b/app/src/main/shared/android/RefreshRateUtils.java index 505e86756..2960a3110 100644 --- a/app/src/main/shared/android/RefreshRateUtils.java +++ b/app/src/main/shared/android/RefreshRateUtils.java @@ -302,7 +302,8 @@ public static float applyPreferredRefreshRate(Activity activity, int requestedHz int modeId = resolvePreferredDisplayModeId(activity, effectiveRequestedHz); float refreshRate = resolvePreferredRefreshRate(activity, effectiveRequestedHz); params.preferredDisplayModeId = modeId; - params.preferredRefreshRate = refreshRate; + // Must be 0 when a modeId is set. + params.preferredRefreshRate = modeId != 0 ? 0f : refreshRate; activity.getWindow().setAttributes(params); Log.d( TAG,