Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add constructor with RenderSettings to ComposePanel #1377

Merged
merged 15 commits into from
Jun 14, 2024

Conversation

elijah-semyonov
Copy link

@elijah-semyonov elijah-semyonov commented May 28, 2024

Fixes JetBrains/compose-multiplatform#4870

Release Notes

Features - Desktop

  • Add constructor with RenderSettings to ComposePanel. Added a class RenderSettings with val isVsyncEnabled: Boolean?. When set to true gives a hint to renderer implementation of the particular ComposePanel to reduce the latency between the input and visual changes in exchange for possible screen tearing.

@m-sasha
Copy link
Member

m-sasha commented May 28, 2024

While I don't think this change is bad in and of itself (it's good to give more granularity to the vsync flag), I don't like how this is being used as a workaround for the presentation latency issue. It solves a very small aspect of the problem, and in a very inconvenient way.

  • It doesn't solve the issue for fully Compose (no Swing) apps.
  • It doesn't solve the issue for regular UI, where tearing looks bad.

I'd much rather we solved the underlying issue; if Swing/AWT can do it, why not us?

@@ -46,8 +47,29 @@ import org.jetbrains.skiko.SkiaLayerAnalytics
class ComposePanel @ExperimentalComposeUiApi constructor(
private val skiaLayerAnalytics: SkiaLayerAnalytics,
) : JLayeredPane() {
private var reducePresentationLatency: Boolean? = null
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with Ivan that vsyncEnabled is better. reducePresentationLatency doesn't hint at any downsides - why would anyone not want to reduce presentation latency?

But maybe consider going even farther and give it a (fake) enum, e.g. PresentationMode.VSYNC, PresentationMode.IMMEDIATE etc, if there's a chance we could have more modes...

@igordmn
Copy link
Collaborator

igordmn commented May 28, 2024

I'd much rather we solved the underlying issue; if Swing/AWT can do it, why not us?

I would too, but as we discussed, it is much harder, and I am not sure we are able to solve it in the foreseeble future. In the meantime, we can provide a property, that even makes sense outside of this issue.

@igordmn
Copy link
Collaborator

igordmn commented May 28, 2024

if Swing/AWT can do it, why not us?

If we just disable vsync, we will have:

  • tearing (depends on the system, the system window compositor can add additional synchronization)
  • animation issues
    • frame drops
    • inconsistent animation frame times (animations won't be smooth)
    • unlimited FPS (consumes CPU)
  • too early recomposition, that results the frame after the next to drop

Swing can have the same issues.

@m-sasha
Copy link
Member

m-sasha commented May 28, 2024

If we just disable vsync, we will have:

Are there no other solutions?

Swing can have the same issues.

But in practice, it doesn't. Swing has low-latency presentation and no tearing out-of-the-box.

@igordmn
Copy link
Collaborator

igordmn commented May 28, 2024

Are there no other solutions?

This is the nature of vsync. If we sync with it, we have 1-frame latency. We sync with it to solve the issues I mentioned.

Animations should always be in sync with v-sync, to avoid them. The solution might be to move input reaction changes out of vsync, but it can be not trivial. We might even need a new API that user should call explicitly, not sure.

@igordmn
Copy link
Collaborator

igordmn commented May 28, 2024

We also might have unnecessary frame lags in other places. Most probably it is easier to fix them than the vsync latency. But we need to profile the issue to understand if we have them.

@m-sasha
Copy link
Member

m-sasha commented May 28, 2024

This is the nature of vsync.

But, again, why is this problem not present in Swing, and (I assume) native frameworks?

Can we double buffer ourselves to avoid tearing?

We also might have unnecessary frame lags in other places.

The problem reproduces in pure Skiko, and it appears that most (possibly all) the delay comes from vsync.

@igordmn
Copy link
Collaborator

igordmn commented May 28, 2024

But, again, why is this problem not present in Swing, and (I assume) native frameworks?

Besides tearing I mentioned the animation issue, which Swing didn't solve. It doesn't sync it with vsync. I haven't investigated it properly in the past though. I discovered this when I tried to implement smooth animations on Swing, and didn't find a way.

I am not saying that there is no proper way, I am saying that a proper way needs time to investigate/design/implement. And I am all for doing that.

The problem reproduces in pure Skiko

What I meant that we can have issues in Compose, and solving them might also help. Skiko also can have additional lags besides that caused by vsync.

@igordmn
Copy link
Collaborator

igordmn commented May 28, 2024

But, again, why is this problem not present in Swing, and (I assume) native frameworks?

I would look at the Android sources (Choreographer). From my experience, it solves the latency and the animation issues. As I remember, it was introduced as Project Butter.

@m-sasha
Copy link
Member

m-sasha commented May 28, 2024

I am not saying that there is no proper way, I am saying that a proper way needs time to investigate/design/implement. And I am all for doing that.

Ok, understood.

What I meant that we can have issues in Compose, and solving them might also help. Skiko also can have additional lags besides that caused by vsync.

The experiments I've done show that this isn't the case. Or at least the contribution of other factors (in Skiko or Compose) to the latency are minimal.

  1. The latency in a pure Skiko reproducer is very similar to the latency of a Compose reproducer.
  2. The latency in pure Skiko with vsync turned off is negligible/acceptable - probably just the remaining, expected 1-frame delay.

@m-sasha
Copy link
Member

m-sasha commented May 28, 2024

Note that merely setting

device.layer.displaySyncEnabled = NO;

while keeping everything else related to vsync (e.g. calling DisplayLinkThrottler.waitVSync) also causes the problem to go away (but causes tearing).

@elijah-semyonov
Copy link
Author

Note that merely setting

device.layer.displaySyncEnabled = NO;

while keeping everything else related to vsync (e.g. calling DisplayLinkThrottler.waitVSync) also causes the problem to go away (but causes tearing).

Yes, that's true. The introduced flag doesn't affect throttler behavior.

@elijah-semyonov elijah-semyonov marked this pull request as draft May 28, 2024 13:04
@elijah-semyonov
Copy link
Author

Thank you for feedback and discussion, I'll do deeper investigation of other graphics backends and see if I can define a common denominator here.

@MohamedRejeb
Copy link

MohamedRejeb commented May 31, 2024

I noticed that even with vsync disabled, you can get better results with requesting redraw only 1 time per frame.
I'm on 60 fps so the frame duration is around 16ms. Calling skiaLayer.needRedraw() on each skiko onRender() call results calling onRender() each 8ms (2 times per frame). I did it manually and I called skiaLayer.needRedraw() each 16 ms and the results was better.
I'm not sure if there's a way to prevent calling draw method more than one time on each frame even before reaching the native calls, maybe it can be done on the FrameDispatcher.

@elijah-semyonov
Copy link
Author

elijah-semyonov commented Jun 11, 2024

I noticed that even with vsync disabled, you can get better results with requesting redraw only 1 time per frame. I'm on 60 fps so the frame duration is around 16ms. Calling skiaLayer.needRedraw() on each skiko onRender() call results calling onRender() each 8ms (2 times per frame). I did it manually and I called skiaLayer.needRedraw() each 16 ms and the results was better. I'm not sure if there's a way to prevent calling draw method more than one time on each frame even before reaching the native calls, maybe it can be done on the FrameDispatcher.

Which platform did you test it on?
It doesn't seem a valid behavior on macOS. We already use framerate throttling disregarding the vsyncEnabled (which control the presentation, not scheduling)

@elijah-semyonov elijah-semyonov marked this pull request as ready for review June 12, 2024 10:36
@elijah-semyonov
Copy link
Author

There is quite a lot of effects for fully exposing SkikoLayerProperties, so @igordmn and me decided to start small and fix the immediate issue with latency on macOS being a blocker for some users.

* by Swing.
*/
@ExperimentalComposeUiApi
class RenderingSettings(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add equals/hashCode to it?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nop, we don't do any comparisons/caching/using it as a key, etc.

@@ -78,6 +79,7 @@ import org.jetbrains.skiko.SkiaLayerAnalytics
* for window coordinate space.
* @property useSwingGraphics Flag indicating if offscreen rendering to Swing graphics is used.
* @property layerType The type of layer used for Popup/Dialog.
* @property renderingSettings The settings for rendering.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a note that it will work only with useSwingGraphics == true

Copy link
Author

@elijah-semyonov elijah-semyonov Jun 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's in the doc of RenderSettings.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general it's not true, because we can add settings, that will work with swing

@@ -338,7 +341,13 @@ internal class ComposeContainer(
return if (useSwingGraphics) {
SwingSkiaLayerComponent(mediator, renderDelegate, skiaLayerAnalytics)
} else {
WindowSkiaLayerComponent(mediator, windowContext, renderDelegate, skiaLayerAnalytics)
WindowSkiaLayerComponent(
mediator,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add explicit parameter names if it's not one-liner

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we add them when variables match parameter names or are obvious?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better to add it to be on the safe side during possible refactoring

Comment on lines 53 to 59
properties = run {
val defaultProperties = SkiaLayerProperties()

SkiaLayerProperties(
isVsyncEnabled = renderingSettings.isVsyncEnabled ?: defaultProperties.isVsyncEnabled,
)
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
properties = run {
val defaultProperties = SkiaLayerProperties()
SkiaLayerProperties(
isVsyncEnabled = renderingSettings.isVsyncEnabled ?: defaultProperties.isVsyncEnabled,
)
},
properties = SkiaLayerProperties(
isVsyncEnabled = renderingSettings.isVsyncEnabled ?: SkiaLayerProperties.Default.isVsyncEnabled,
),

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There can be multiple properties in future, this approach will reconstruct SkiaLayerProperties every single time. There is no Default in SkiaLayerProperties.Companion

elijah-semyonov and others added 4 commits June 12, 2024 13:04
…enderingSettings.desktop.kt

Co-authored-by: Ivan Matkov <ivan.matkov@jetbrains.com>
@elijah-semyonov elijah-semyonov changed the title Add constructor with reducePresentationLatency to ComposePanel Add constructor with RenderSettings to ComposePanel Jun 12, 2024
…omposePanel.desktop.kt

Co-authored-by: Ivan Matkov <ivan.matkov@jetbrains.com>
*/
@ExperimentalComposeUiApi
class RenderingSettings(
val isVsyncEnabled: Boolean?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better/cleaner to put this in a (fake) enum, e.g. VSyncMode.Enabled, VSyncMode.Disabled, and VsyncMode.Default. Although maybe it's overkill here; not sure.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd leave it as it is now (ExperimentalAPI after all), I haven't yet performed an investigation about different presentation modes on non-Metal rendering backends.

* by Swing.
*/
@ExperimentalComposeUiApi
class RenderingSettings(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe RenderSettings instead? A bit shorter and doesn't lose any meaning.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, sounds fine

@MohamedRejeb
Copy link

Btw the latency issue exists on windows as well (didn't try linux) and disabling vsync reduces the latency significantly there. So it's not only a macos thing.

@elijah-semyonov elijah-semyonov merged commit 03d732f into jb-main Jun 14, 2024
8 checks passed
@elijah-semyonov elijah-semyonov deleted the es/desktop-reduce-latency branch June 14, 2024 10:37
@igordmn
Copy link
Collaborator

igordmn commented Jul 1, 2024

@elijah-semyonov, please actualise Release Notes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Being able to create a ComposePanel with vsync disabled
5 participants