Offscreen rendering for windows.

Windows will now render to an offscreen buffer. They only render when
something changes, and the re-rendered area is kept to a minimum.

When the window needs to be redrawn to the screen, it simply copies
the appropriate region from the offscreen buffer.

This should greatly improve performance in cases where a window isn't
changing its contents, but needs to be redrawn frequently. For example
a pop-up dialog being drawn over animated terrain (see #7615).
This commit is contained in:
Tommy 2023-10-24 18:17:18 +13:00
parent 18c2ad3bf8
commit 0eada04f55
3 changed files with 128 additions and 14 deletions

View File

@ -378,10 +378,14 @@ void widget::draw_background()
return;
}
SDL_Rect dest = calculate_blitting_rectangle();
SDL_Rect clip = calculate_clipping_rectangle();
clip.x -= dest.x; clip.y -= dest.y;
// Set viewport and clip so we can draw in local coordinates.
rect dest = calculate_blitting_rectangle();
rect clip = calculate_clipping_rectangle();
// Presumably we are drawing to our window's render buffer.
point window_origin = get_window()->get_origin();
dest.shift(-window_origin);
auto view_setter = draw::set_viewport(dest);
clip.shift(-get_origin());
auto clip_setter = draw::reduce_clip(clip);
draw_debug_border();
@ -396,10 +400,14 @@ void widget::draw_children()
return;
}
SDL_Rect dest = calculate_blitting_rectangle();
SDL_Rect clip = calculate_clipping_rectangle();
clip.x -= dest.x; clip.y -= dest.y;
// Set viewport and clip so we can draw in local coordinates.
rect dest = calculate_blitting_rectangle();
rect clip = calculate_clipping_rectangle();
// Presumably we are drawing to our window's render buffer.
point window_origin = get_window()->get_origin();
dest.shift(-window_origin);
auto view_setter = draw::set_viewport(dest);
clip.shift(-get_origin());
auto clip_setter = draw::reduce_clip(clip);
impl_draw_children();
@ -413,10 +421,14 @@ void widget::draw_foreground()
return;
}
SDL_Rect dest = calculate_blitting_rectangle();
SDL_Rect clip = calculate_clipping_rectangle();
clip.x -= dest.x; clip.y -= dest.y;
// Set viewport and clip so we can draw in local coordinates.
rect dest = calculate_blitting_rectangle();
rect clip = calculate_clipping_rectangle();
// Presumably we are drawing to our window's render buffer.
point window_origin = get_window()->get_origin();
dest.shift(-window_origin);
auto view_setter = draw::set_viewport(dest);
clip.shift(-get_origin());
auto clip_setter = draw::reduce_clip(clip);
impl_draw_foreground();
@ -452,6 +464,7 @@ void widget::queue_redraw()
void widget::queue_redraw(const rect& region)
{
get_window()->queue_rerender(region);
draw_manager::invalidate_region(region);
}

View File

@ -646,15 +646,84 @@ void window::hide()
hidden_ = true;
}
void window::update_render_textures()
{
point draw = get_size();
point render = draw * video::get_pixel_scale();
// Check that the render buffer size is correct.
point buf_raw = render_buffer_.get_raw_size();
point buf_draw = render_buffer_.draw_size();
bool raw_size_changed = buf_raw.x != render.x || buf_raw.y != render.y;
bool draw_size_changed = buf_draw.x != draw.x || buf_draw.y != draw.y;
if (!raw_size_changed && !draw_size_changed) {
// buffers are fine
return;
}
if(raw_size_changed) {
LOG_DP << "regenerating window render buffer as " << render;
render_buffer_ = texture(render.x, render.y, SDL_TEXTUREACCESS_TARGET);
}
if(raw_size_changed || draw_size_changed) {
LOG_DP << "updating window render buffer draw size to " << draw;
render_buffer_.set_draw_size(draw);
}
// Clear the entire texture, just in case
for(int i = 0; i < 2; ++i) {
auto setter = draw::set_render_target(render_buffer_);
draw::fill(0,0,0,0);
}
queue_rerender();
}
void window::queue_rerender()
{
queue_rerender(get_rectangle());
}
void window::queue_rerender(const rect& screen_region)
{
// More than one region updating per-frame should be rare.
// Just rerender the minimal area that covers everything.
rect local_region = screen_region;
local_region.shift(-get_origin());
awaiting_rerender_.expand_to_cover(local_region);
}
void window::render()
{
update_render_textures();
if (awaiting_rerender_.empty()) {
return;
}
DBG_DP << "window::render() local " << awaiting_rerender_;
auto target_setter = draw::set_render_target(render_buffer_);
auto clip_setter = draw::override_clip(awaiting_rerender_);
draw();
awaiting_rerender_ = sdl::empty_rect;
}
bool window::expose(const rect& region)
{
DBG_DP << "window::expose " << region;
rect i = get_rectangle().intersect(region);
i.clip(draw::get_clip());
if (i.empty()) {
// Calculate the destination region we need to draw.
rect dst = get_rectangle().intersect(region);
dst.clip(draw::get_clip());
if (dst.empty()) {
return false;
}
draw();
// Blit from the pre-rendered buffer.
rect src = dst;
src.shift(-get_origin());
render_buffer_.set_src(src);
draw::blit(render_buffer_, dst);
render_buffer_.clear_src();
return true;
}

View File

@ -159,12 +159,44 @@ public:
*/
virtual void layout() override;
/** Called by draw_manager when it believes a redraw is necessary. */
/** Ensure the window's internal render buffer is up-to-date.
*
* This renders the window to an off-screen texture, which is then
* copied to the screen during expose().
*/
virtual void render() override;
private:
/** The internal render buffer used by render() and expose(). */
texture render_buffer_ = {};
/** The part of the window (if any) currently marked for rerender. */
rect awaiting_rerender_;
/** Ensure render textures are valid and correct. */
void update_render_textures();
public:
/**
* Called by draw_manager when it believes a redraw is necessary.
* Can be called multiple times per vsync.
*/
virtual bool expose(const rect& region) override;
/** The current draw location of the window, on the screen. */
virtual rect screen_location() override;
/**
* Queue a rerender of the internal render buffer.
*
* This does not request a repaint. Ordinarily use queue_redraw()
* on a widget, which will call this automatically.
*
* @param region The region to rerender in screen coordinates.
*/
void queue_rerender(const rect& region);
void queue_rerender();
/** The status of the window. */
enum class status {
NEW, /**< The window is new and not yet shown. */