-
Notifications
You must be signed in to change notification settings - Fork 567
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
Adds support for invalidating a single rectangle. #817
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall this looks like a good direction, a couple comments inline.
My biggest concern is around scrolling; the logic for propagating invalidations is different for that than for most other containment.
Apologies my bandwidth for reviewing this in more detail is more limited than usual.
So I had a first stab at getting scrolling working. I'm not super happy with it (which is why I only implemented As far as I understand, the way scrolling eventually works is the following: the scroll widget will maintain a buffer that contains the painted contents of its children. That buffer will be somewhat bigger than the viewport. When the This means that:
AFAICT, these two constraints (and especially the second) means that the |
By the way, the current implementation has a buglet, in that there is a situation where it invalidates too much (and I don't know how to fix it without more drastic changes): if the scroll has a I'm not planning to do anything about this right away, I just wanted to right it down so I don't forget... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some notes and questions, but I think this is in good shape should should be ready to go soon!
@@ -0,0 +1,67 @@ | |||
// Copyright 2020 The xi-editor Authors. | |||
// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the way I might consider implementing this is not as a separate widget, but as part of WidgetPod
's paint
method? It's possible this doesn't make sense, but it's my first instinct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I originally tried to imitate the implementation of debug_paint_layout
(i.e. using EnvScope
and WidgetPod::paint
instead of a new widget). The problem is that, unlike drawing layout-rect boundaries, we don't want to recursively draw the invalidation regions for the children. It just occurred to me, though, that I can probably use Painter
instead of a new widget.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see what you mean.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually thinking about this more (and further into my review), maybe it does make sense to draw them recursively? This would clarify where the invalidation calls that produced the final result came from?
In this world, I might also just draw outlines of the invalidated regions, so that we don't end up stacking up the alpha channel and obscuring the view.
Thanks for the review! I'll do a round or two of clean-up and then remove the [WIP]. I'd still feel better if someone briefly tested windows/mac, though... |
1 similar comment
Thanks for the review! I'll do a round or two of clean-up and then remove the [WIP]. I'd still feel better if someone briefly tested windows/mac, though... |
The remaining CI failures appear to be linking errors that I can't reproduce locally... |
Fixes #402. Widgets can request partial invalidation using `request_paint_rect`. All of the partial invalidation requests will be combined into a single bounding rectangle, and passed to the system. When painting widgets, they have access to the dirty region. With this commit, widgets that paint outside their paint rects can cause graphical glitches. These widgets are buggy, but their bugs weren't so visible before (see #628).
Ok, seems like a rebase resolved it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay the code looks generally good but playing with the example I'm not sure I'm seeing the expected behaviour (on mac).
let color = env.get_debug_color(self.debug_color).with_alpha(0.5); | ||
let rect = ctx.region().to_rect(); | ||
ctx.fill(rect, &color); | ||
self.debug_color += 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's the rationale for changing the color, here? to me it makes sense to have the color be unchanged between paint cycles.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without the color change, every time a region gets invalidated it gets painted with a blue (say) overlay. The overall effect is that the entire window is covered with a blue overlay all the time, so you can't see what was just invalidated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused; wouldn't that only be the case if the entire view was invalidated?
ctx.fill(Circle::new(*data, RADIUS), &Color::BLACK); | ||
}) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so on macOS, this example is not displaying any visible minimal invalidation; on click the whole screen is tinted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no, on mac it is just a window with a single solid background color. I'll dig in a little bit...
@@ -0,0 +1,67 @@ | |||
// Copyright 2020 The xi-editor Authors. | |||
// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually thinking about this more (and further into my review), maybe it does make sense to draw them recursively? This would clarify where the invalidation calls that produced the final result came from?
In this world, I might also just draw outlines of the invalidated regions, so that we don't end up stacking up the alpha channel and obscuring the view.
I'll try out a few options for the debug visualization. I don't think recursive drawing will work well, because there's just one single invalidation rect for the entire window, and so any individual widget's invalidation rect is just its paint rect intersected with the global rect -- there's no interesting recursive structure like there is for the layout rects. I'll also try doing outlines. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I found two small issues keeping the examples from working on mac.
With these changes, this works as intended.
One other thing I noticed digging in a bit more deeply is that animation always invalidates everything; I think this is okay for now but we should open an issue about it when this gets merged.
druid-shell/examples/invalidate.rs
Outdated
fn connect(&mut self, handle: &WindowHandle) { | ||
self.handle = handle.clone(); | ||
} | ||
|
||
fn paint(&mut self, piet: &mut Piet, rect: Rect) -> bool { | ||
self.update_color_and_rect(); | ||
piet.fill(rect, &self.color); | ||
|
||
self.handle.invalidate_rect(self.rect); | ||
false | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
on mac, invalidation calls that happen during drawing are ignored.
I would suggest changing this to use a timer, something like:
fn connect(&mut self, handle: &WindowHandle) {
self.handle = handle.clone();
self.handle.request_timer(std::time::Duration::from_millis(60));
}
fn timer(&mut self, _id: TimerToken) {
self.update_color_and_rect();
self.handle.invalidate_rect(self.rect);
self.handle.request_timer(std::time::Duration::from_millis(60));
}
fn paint(&mut self, piet: &mut Piet, rect: Rect) -> bool {
piet.fill(rect, &self.color);
false
}
Thanks! I fixed those, and also some other issues with propagating a too-large invalid region in druid (in cairo, the drawing context is automatically clipped, so I wasn't seeing the issue). Regarding the animation, there was a reason I hadn't done it yet, but I can't remember why... |
I wouldn't worry about the animation, it will be sort of tricky to get right with the current API. The longer-term plan is to build a higher-level API on top of what we currently have, and when we do that we can revisit. I guess it would be possible to make some specific improvements now, though; but this would require turning |
Also I'm not sure I can tell the difference between the recursive v. non-recursive outlines; I defer to your judgement. |
Ok, so I've gone with non-recursive outlines then. It's easy enough to change later anyway. Also, I'll open an issue about |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great, thanks for your patience on this, I'm satisfied. I'll ping @raphlinus and see if he wants to take a look, I think we're looking good though.
Just out of curiousity, have you used this and found a measurable improvement in performance?
Oh, one last thing: it might be worth adding this to the wasm examples? in |
I think it's automatic, right? Edit: although now that I'm testing the wasm example, the whole thing seems to get invalidated... |
Ok, I think I may have fixed the wasm example. I am a bit puzzled by the two calls to I had a toy example showing improved performance for small invalidations when drawing is expensive. I can try cleaning it up and adding it as an example. But my real motivation for doing this needs more work before it can benefit... |
I can't speak to the wasm stuff; maybe @elrnv can. In any case I'm happy to merge this now, thanks again for your patience! |
I am not sure why we need to finish calls. For reference, I believe the code @jneem is referring to is in the render function fn render(&self) -> bool {
self.context
.clear_rect(0.0, 0.0, self.get_width() as f64, self.get_height() as f64);
let mut piet_ctx = piet_common::Piet::new(self.context.clone(), self.window.clone());
let want_anim_frame = self.handler.borrow_mut().paint(&mut piet_ctx);
if let Err(e) = piet_ctx.finish() {
log::error!("piet error on render: {:?}", e);
}
let res = piet_ctx.finish();
if let Err(e) = res {
log::error!("EndDraw error: {:?}", e);
}
want_anim_frame
} This part is over a year old. It does look redundant to me. After trying the examples without the second |
This adds support in druid-shell for invalidating a single rectangle.
Caveats/questions:
invalidate
methods be deprecated? Or should I just keep the name and introduce the rect parameter?