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

Excessive redraw/dirty areas on pointer events (swrender on embedded) #7432

Open
bobziuchkovski opened this issue Jan 22, 2025 · 3 comments
Open
Labels
a:renderer-software Software Renderer (mO,bF) optimization

Comments

@bobziuchkovski
Copy link

bobziuchkovski commented Jan 22, 2025

Bug Description

What is the bug?

PointerReleased/PointerExited events after clicking a button result in the full screen being re-rendered and marked as dirty with the software renderer.

What behavior did you expect, and what happened instead?

I expected only the button area to be rendered and marked dirty on release, but instead the full screen is being rendered and marked dirty. This occurs only on PointerReleased/PointerExited. The PointerPressed event on the button does exactly what I'd expect -- render and mark only the button area as dirty, including the subsequent animation updates of the button area.

Steps to reproduce the issue

Dispatch a series of PointerPressed, PointerReleased, and PointerExited events to a button, noting the resulting dirty areas from SoftwareRenderer::render when redrawing.

Any error messages or logs, if available.

My app log output. Note that the initial SoftwareRenderer::render results after dispatching PointerPressed return a dirty area that's 464x100, which is the button area. This is what I expect. However, the SoftwareRenderer::render results after dispatching PointerReleased and PointerExited events always returns a dirty area representing the full screen, 480x320.

Image

Video of this in action is posted here: https://youtu.be/Q77ma-mtZVA?si=XU-Lybd8xA-5LFRA

Reproducible Code (if applicable)

Touch event/repaint loop:

let window = MinimalSoftwareWindow::new(RepaintBufferType::ReusedBuffer);

// ... other code ...

loop {
    slint::platform::update_timers_and_animations();
    let mut dirty_region = PhysicalRegion::default();
    let dirty = window.draw_if_needed(|renderer| {
        dirty_region = renderer.render(
            framebuf.as_mut_slice(),
            DISPLAY_LAYOUT.logical_width() as usize,
        );
    });
    if dirty {
        lcd.repaint_region(framebuf.as_ref(), &dirty_region.into()).await;
    }

    let wait_ms = if window.has_active_animations() {
        0
    } else {
        slint::platform::duration_until_next_timer_update()
            .map(|d| d.as_millis())
            .unwrap_or(3600)
    };

    match select(TOUCH_EVENTS.receive(), Timer::after_millis(wait_ms as u64)).await {
        Either::First(touch_event) => {
            let window_event: WindowEvent = touch_event.into();
            let released = matches!(window_event, WindowEvent::PointerReleased { .. });
            info!("Dispatching {:?}", window_event);
            window.dispatch_event(window_event);
            if released {
                info!("Dispatching {:?}", WindowEvent::PointerExited);
                window.dispatch_event(WindowEvent::PointerExited);
            }
        }
        Either::Second(_) => {}
    }
}

Slint UI code:

import { Button, VerticalBox, AboutSlint } from "std-widgets.slint";

export component AppWindow inherits Window {
    width: 480px;
    height: 320px;
    default-font-size: 64px;
    property <int> count: 0;
    VerticalBox {
        Button {
            height: 100px;
            text: count;
            clicked => {
                count += 1;
            }
        }
        AboutSlint { }
     }
}

Environment Details

  • Slint Version: 1.9.2
  • Platform/OS: xtensa-esp32s3-none-elf (embedded mcu)
  • Programming Language: Rust
  • Backend/Renderer: Software

Product Impact

What are you building with Slint?

An open source nitrox analyzer for scuba diving. Will be publishing to Github, but right now it's in its infancy.

How critical is this issue for your product (e.g., blocker, inconvenience)? This helps us prioritize the issue effectively.

It's a performance-related inconvenience. Full screen repaints take ~100ms, so tapping in quick succession can be noticeably laggy.

@bobziuchkovski bobziuchkovski added bug Something isn't working need triaging Issue that the owner of the area still need to triage labels Jan 22, 2025
@tronical
Copy link
Member

@ogoffart and I suspect that this is due to an if() inside the Button implementation, which - upon condition change - ends up destroying a sub-tree of items and causing a full refresh :(.

@tronical
Copy link
Member

Related test that covers this:

// Currently we redraw when a condition becomes false because we don't track the position otherwise

@ogoffart ogoffart added optimization a:renderer-software Software Renderer (mO,bF) and removed bug Something isn't working need triaging Issue that the owner of the area still need to triage labels Jan 24, 2025
@ogoffart
Copy link
Member

ogoffart commented Jan 24, 2025

As @tronical mentioned, the Button element has an if that becomes false if the window looses focus (happens with the MouseExit event)

if (root.has-focus && root.enabled) : FocusBorder {

And this cause the free_graphics_resources to force a full rendering.

// We don't have a way to determine the screen region of the delete items, what's in the cache is relative. So
// as a last resort, refresh everything.
self.force_screen_refresh.set(true)

Ideally, if we would cache the absolute rectangle, we'd be able to only mark the region previously covered by these items as dirty.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a:renderer-software Software Renderer (mO,bF) optimization
Projects
None yet
Development

No branches or pull requests

3 participants