slint-ui / slint

Slint is a declarative GUI toolkit to build native user interfaces for Rust, C++, or JavaScript apps.
https://slint.dev
Other
17.73k stars 614 forks source link

swrenderer: Window background change don't mark the whole area as dirty for partial rendering #5219

Open KortanZ opened 6 months ago

KortanZ commented 6 months ago

I'm using ESP32C3 and following the Official MCU guide to build a demo. It works but i encounter two problems.

My Cargo.toml slint part

[dependencies.slint]
version = "1.5.1"
default-features = false
features = ["compat-1-2", "unsafe-single-threaded", "libm", "renderer-software"]
[build-dependencies]
slint-build = "1.5.1"

My .slint file

import { VerticalBox, Switch } from "std-widgets.slint";
export component Demo inherits Window{
    width: 128px;
    height: 128px;
    in-out property<bool> check: false;
    background: sw.checked ? #db073d:#07dba5;
    VerticalBox {
        alignment: center;
        Text {
            text: "Bg color test";
            font-size: 15px;
            horizontal-alignment: center;
        }
        sw := Switch {
            checked: check;
            text: "Switch";
        }
        Slider {
            minimum: 0;
            value: sw.checked ? 0 : 100;
            maximum: 100;
        }
    }
}

I'm using linebuffer method with super loop approach by following the guide.

  1. Can't use Slider widget, when add Slider in my .slint, compiler will error out with:

    called Result::unwrap() on an Err value: CompileError(["/home/kortan/Documents/code/rust/esp32/slint/ui/main.slint:21: Unknown type Slider"])

  2. When toggled switch, not full background freshed, only switch area changed.

    https://github.com/slint-ui/slint/assets/12783634/9ceffc3a-a483-4240-8a08-949f6900917b

    And when config animate to background, display get even weird.

    import { VerticalBox, Switch } from "std-widgets.slint";
    export component Demo inherits Window{
    width: 128px;
    height: 128px;
    in-out property<bool> check: false;
    background: sw.checked ? #db073d:#07dba5;
    animate background {
         duration: 300ms;
    }
    VerticalBox {
        alignment: center;
        Text {
            text: "Bg color test";
            font-size: 15px;
            horizontal-alignment: center;
        }
        sw := Switch {
            checked: check;
            text: "Switch";
        }
    }
    }

https://github.com/slint-ui/slint/assets/12783634/bf587c83-a24f-455d-ae07-76ee24a3602f

hunger commented 6 months ago

Can't use Slider widget, when add Slider in my .slint, compiler will error out with:

Did you import the Slider? Adding it into the import {...} should work.

When toggled switch, not full background freshed, only switch area changed.

That sounds like a bug to me...

KortanZ commented 6 months ago

Did you import the Slider? Adding it into the import {...} should work.

Just realize that i've made a really dumb question. Sorry for that.

That sounds like a bug to me...

The slint code works well on slintpad. So there may be something wrong inside draw_if_needed.

KortanZ commented 6 months ago

After update to slint 1.6.0, things are not getting better much.

https://github.com/slint-ui/slint/assets/12783634/1cde03bc-f676-496a-8625-c8fea856240c

tronical commented 6 months ago

How are you rendering to the screen? We made a change in 1.6 regarding the dirty regions. Render now returns the region that has multiple rectangles, all of which need to be flushed to the screen. Do you use the return value of render() somewhere?

KortanZ commented 6 months ago

@tronical

How are you rendering to the screen?

My whole project code is here, everything about slint is in ui.rs, the render loop looks like this:

    loop {
        slint::platform::update_timers_and_animations();
        window.draw_if_needed(|renderer| {
            renderer.render_by_line(DisplayWrapper{
                display: &mut display,
                line_buffer: &mut line_buffer
            });
        });

        if !window.has_active_animations() {
            if let Some(duration) = slint::platform::duration_until_next_timer_update() {
                Timer::after(Duration::from_millis(duration.as_millis() as u64)).await;
                continue;
            }
        }
        Timer::after(Duration::from_millis(10)).await;
    }

and the LineBufferProvider implemention is like:

struct DisplayWrapper<'a, T>{
    display: &'a mut T,
    line_buffer: &'a mut [slint::platform::software_renderer::Rgb565Pixel],
}

impl<T: DrawTarget<Color = Rgb565>>
    slint::platform::software_renderer::LineBufferProvider for DisplayWrapper<'_, T>
{
    type TargetPixel = slint::platform::software_renderer::Rgb565Pixel;
    fn process_line(
        &mut self,
        line: usize,
        range: core::ops::Range<usize>,
        render_fn: impl FnOnce(&mut [Self::TargetPixel]),
    ) {
        // Render into the line
        render_fn(&mut self.line_buffer[range.clone()]);

        self.display.fill_contiguous(
            &Rectangle::new(Point::new(range.start as _, line as _), Size::new(range.len() as _, 1)),
            self.line_buffer[range.clone()].iter().map(|p| RawU16::new(p.0).into())
        ).map_err(drop).unwrap();
    }
}

Do you use the return value of render() somewhere?

No, I'm using render_by_line() instead and not using return value at all.

tronical commented 6 months ago

Thanks for confirming. So it can’t be that. Does the background change work if you remove the animation on the background property? (Not a solution of course, but helps narrow down a theory I have)

KortanZ commented 6 months ago

@tronical

Does the background change work if you remove the animation on the background property?

https://github.com/slint-ui/slint/assets/12783634/6eddea0a-02bb-438e-859b-45bdd7504d38

Just like when using slint 1.5.1 as i shown on top of this issue, the only difference between animated version and no animation version is that no animation version could change the color correctly rather than a unexpected deeper or lighter color.

tronical commented 6 months ago

Ouch, ok, so my theory of it being related to the time is not true either :(. Since you're calling render_by_line, could you try to see via debug output what the values are for the line: usize parameter? When toggling the switch, you should get the function invoked also with line values that start at the top of the screen (so zero and onwards).

KortanZ commented 6 months ago

@tronical I've added debug output in process_line, and line: usize was start from 70 and end at 82 when toggling the switch. So it seems that only color changed area have been re-rendered.

ogoffart commented 6 months ago

The problem here is in the Slint implementation of the partial rendering for the software renderer. The change in the Window background color is not making any area dirty.

As a workaround, you can use a top level Rectangle with a background.

KortanZ commented 6 months ago

@ogoffart Thank you for your solution : ). I think there are two problem now. One is dirty marking that you mentioned, and the other one is animate problem. As shown in video clip, animation not work as expected either. When configured animate, color dosen't animate but only show a darker or lighter color instead.

ogoffart commented 6 months ago

I think this is the same problem: the background color is not being tracked, so even if it animates, it doesn't mark anything dirty and so nothing gets repainted. Only other part of the UI that changes get repainted.

KortanZ commented 6 months ago

@ogoffart

I think this is the same problem: the background color is not being tracked, so even if it animates, it doesn't mark anything dirty and so nothing gets repainted. Only other part of the UI that changes get repainted.

You are right, i've tested your workaround, and animation works well too.

tronical commented 1 week ago

The skia renderer with partial rendering is affected, too.