GUI2/Canvas: discard individual canvas texture in favor of direct-to-screen rendering

This is a lot more efficient since it allows the GPU to parallelize draws AND it magically
fixes the bad alpha rendering we've been having!

Fixes #1568
Fixes #1744
This commit is contained in:
Charles Dang 2018-05-22 19:17:24 +11:00
parent cdea2e70cf
commit 02858f2259
7 changed files with 30 additions and 175 deletions

View file

@ -1354,135 +1354,56 @@ void text_shape::draw(
canvas::canvas() canvas::canvas()
: shapes_() : shapes_()
, drawn_shapes_()
, draw_func_(nullptr) , draw_func_(nullptr)
, blur_depth_(0) , blur_depth_(0)
, w_(0) , w_(0)
, h_(0) , h_(0)
, texture_(nullptr)
, renderer_(CVideo::get_singleton().get_renderer()) , renderer_(CVideo::get_singleton().get_renderer())
, variables_() , variables_()
, functions_() , functions_()
, is_dirty_(true)
, size_changed_(true) , size_changed_(true)
, cache_invalidated_(false)
{ {
} }
canvas::canvas(canvas&& c) canvas::canvas(canvas&& c)
: shapes_(std::move(c.shapes_)) : shapes_(std::move(c.shapes_))
, drawn_shapes_(std::move(c.drawn_shapes_))
, draw_func_(c.draw_func_) , draw_func_(c.draw_func_)
, blur_depth_(c.blur_depth_) , blur_depth_(c.blur_depth_)
, w_(c.w_) , w_(c.w_)
, h_(c.h_) , h_(c.h_)
, texture_(std::move(c.texture_))
, renderer_(std::exchange(c.renderer_, nullptr)) , renderer_(std::exchange(c.renderer_, nullptr))
, variables_(c.variables_) , variables_(c.variables_)
, functions_(c.functions_) , functions_(c.functions_)
, is_dirty_(c.is_dirty_)
, size_changed_(c.size_changed_) , size_changed_(c.size_changed_)
, cache_invalidated_(c.cache_invalidated_)
{ {
} }
canvas::~canvas() canvas::~canvas()
{ {
SDL_SetRenderTarget(renderer_, nullptr);
} }
void canvas::draw(const bool force) void canvas::draw()
{ {
log_scope2(log_gui_draw, "Canvas: drawing."); log_scope2(log_gui_draw, "Canvas: drawing.");
if(!is_dirty_ && !force && !texture_.null()) {
DBG_GUI_D << "Canvas: nothing to draw.\n";
return;
}
if(is_dirty_) { if(size_changed_) {
get_screen_size_variables(variables_); get_screen_size_variables(variables_);
variables_.add("width", wfl::variant(w_)); variables_.add("width", wfl::variant(w_));
variables_.add("height", wfl::variant(h_)); variables_.add("height", wfl::variant(h_));
} }
// Do we have a custom drawing function?
const bool have_custom_draw = draw_func_ != nullptr;
// If cached texture is null or size has changed, throw it out and create a new one.
const bool do_texture_reset = !texture_ || size_changed_;
// Reset texture, if applicable.
if(do_texture_reset) {
DBG_GUI_D << "Canvas: resetting canvas.\n";
texture_.reset(w_, h_, SDL_TEXTUREACCESS_TARGET);
size_changed_ = false;
}
// Something went wrong! Bail! The texture ctor will print the error if applicable.
if(!texture_) {
return;
}
// Set the render target. *Must* be called after the above block in case the texture's
// been recreated or else the game will crash upon trying to write to a null texture.
render_target_setter target_setter(texture_);
// Clear the texture, if applicable. *Must* be called after setting the render target
// since SDL_RenderClear operates on the current target - in this case the canvas texture.
// Calling it prior would clear the screen.
//
// There are three cases in which this should happen:
//
// - The texture was reset:
// This prevents weird graphics bleed-through with certain driver configurations.
//
// - The cache was invalidated:
// This means we're drawing all the canvas shapes, so we want a clean texture. Since
// drawn shapes are removed from drawing queue once they've been rendered, only clearing
// the texture if this is true allows subsequently added shapes to be drawn on top of
// the already-rendered ones.
//
// - A custom drawing function was set:
// Custom drawing functions don't use shapes, so we always want a clean texture prior
// to calling those.
if(do_texture_reset || cache_invalidated_ || have_custom_draw) {
set_draw_color(renderer_, 0, 0, 0, 0);
SDL_RenderClear(renderer_); // TODO: move to its own wrapper.
}
// If we have a custom drawing function, call it now and exit. // If we have a custom drawing function, call it now and exit.
if(have_custom_draw) { if(draw_func_) {
draw_func_(texture_); draw_func_(w_, h_);
set_is_dirty(false);
return; return;
} }
// If the cache was invalidated, restore all the available shapes from the drawn drawn cache.
if(cache_invalidated_) {
if(shapes_.empty()) {
shapes_.swap(drawn_shapes_);
} else {
std::copy(drawn_shapes_.begin(), drawn_shapes_.end(), std::inserter(shapes_, shapes_.begin()));
drawn_shapes_.clear();
}
}
// Draw shapes. // Draw shapes.
for(auto& shape : shapes_) { for(auto& shape : shapes_) {
lg::scope_logger inner_scope_logging_object__(log_gui_draw, "Canvas: draw shape."); lg::scope_logger inner_scope_logging_object__(log_gui_draw, "Canvas: draw shape.");
shape->draw(w_, h_, renderer_, variables_); shape->draw(w_, h_, renderer_, variables_);
} }
// The shapes have been drawn and the draw result has been cached. Clear the list.
std::copy(shapes_.begin(), shapes_.end(), std::back_inserter(drawn_shapes_));
shapes_.clear();
set_is_dirty(false);
} }
void canvas::render() void canvas::render()
@ -1516,9 +1437,6 @@ void canvas::render()
} }
} }
#endif #endif
// Copy the entire texture to the full viewport.
CVideo::get_singleton().render_copy(texture_);
} }
void canvas::parse_cfg(const config& cfg) void canvas::parse_cfg(const config& cfg)
@ -1567,38 +1485,16 @@ void canvas::parse_cfg(const config& cfg)
} }
} }
void canvas::clear_shapes(const bool force) void canvas::clear_shapes()
{ {
if(force) { shapes_.clear();
shapes_.clear();
drawn_shapes_.clear();
} else {
auto conditional = [](const shape_ptr s)->bool { return !s->immutable(); };
auto iter = std::remove_if(shapes_.begin(), shapes_.end(), conditional);
shapes_.erase(iter, shapes_.end());
iter = std::remove_if(drawn_shapes_.begin(), drawn_shapes_.end(), conditional);
drawn_shapes_.erase(iter, drawn_shapes_.end());
}
} }
void canvas::set_size(unsigned w, unsigned h) void canvas::set_size(unsigned w, unsigned h)
{ {
update_size(w_, w); w_ = w;
update_size(h_, h); h_ = h;
size_changed_ = true;
invalidate_cache();
}
void canvas::update_size(unsigned int& value, unsigned int new_value)
{
if(value != new_value) {
value = new_value;
size_changed_ = true;
set_is_dirty(true);
}
} }
/***** ***** ***** ***** ***** SHAPE ***** ***** ***** ***** *****/ /***** ***** ***** ***** ***** SHAPE ***** ***** ***** ***** *****/

View file

@ -93,15 +93,12 @@ public:
~canvas(); ~canvas();
using draw_func_t = std::function<void(texture&)>; using draw_func_t = std::function<void(unsigned, unsigned)>;
/** /**
* Draws the canvas. * Draws the canvas.
*
* @param force If the canvas isn't dirty it isn't redrawn
* unless force is set to true.
*/ */
void draw(const bool force = false); void draw();
/** /**
* Copies the canvas texture to the screen renderer. * Copies the canvas texture to the screen renderer.
@ -123,10 +120,9 @@ public:
* http://www.wesnoth.org/wiki/GUICanvasWML for * http://www.wesnoth.org/wiki/GUICanvasWML for
* more information. * more information.
*/ */
void set_cfg(const config& cfg, const bool force = false) void set_cfg(const config& cfg)
{ {
clear_shapes(force); clear_shapes();
invalidate_cache();
parse_cfg(cfg); parse_cfg(cfg);
} }
@ -159,23 +155,12 @@ public:
void set_variable(const std::string& key, const wfl::variant& value) void set_variable(const std::string& key, const wfl::variant& value)
{ {
variables_.add(key, value); variables_.add(key, value);
set_is_dirty(true);
invalidate_cache();
}
void set_is_dirty(const bool is_dirty)
{
is_dirty_ = is_dirty;
} }
private: private:
/** Vector with the shapes to draw. */ /** Vector with the shapes to draw. */
std::vector<shape_ptr> shapes_; std::vector<shape_ptr> shapes_;
/** All shapes which have been already drawn. Kept around in case
* the cache needs to be invalidated. */
std::vector<shape_ptr> drawn_shapes_;
/** Optional custom drawing function. */ /** Optional custom drawing function. */
draw_func_t draw_func_; draw_func_t draw_func_;
@ -195,9 +180,6 @@ private:
/** Height of the canvas. */ /** Height of the canvas. */
unsigned h_; unsigned h_;
/** The texture onto which items are drawn. */
texture texture_;
/** A pointer to the window renderer. */ /** A pointer to the window renderer. */
SDL_Renderer* renderer_; SDL_Renderer* renderer_;
@ -207,15 +189,9 @@ private:
/** Action function definitions for the canvas. */ /** Action function definitions for the canvas. */
wfl::action_function_symbol_table functions_; wfl::action_function_symbol_table functions_;
/** The dirty state of the canvas. */
bool is_dirty_;
/** Whether canvas dimensions changed. */ /** Whether canvas dimensions changed. */
bool size_changed_; bool size_changed_;
/** Whether to completely clear the canvas texture of any previously drawn content. */
bool cache_invalidated_;
/** /**
* Parses a config object. * Parses a config object.
* *
@ -228,15 +204,7 @@ private:
*/ */
void parse_cfg(const config& cfg); void parse_cfg(const config& cfg);
void clear_shapes(const bool force); void clear_shapes();
void invalidate_cache()
{
cache_invalidated_ = true;
}
/** Small helper to handle size variable update logic. */
void update_size(unsigned int& value, unsigned int new_value);
}; };
} // namespace gui2 } // namespace gui2

View file

@ -350,7 +350,6 @@ void story_viewer::draw_floating_image(window& window, floating_image_list::cons
// Needed to make the background redraw correctly. // Needed to make the background redraw correctly.
window_canvas.append_cfg(cfg); window_canvas.append_cfg(cfg);
window_canvas.set_is_dirty(true);
++image_iter; ++image_iter;

View file

@ -54,7 +54,7 @@ minimap::minimap(const implementation::builder_minimap& builder)
, terrain_(nullptr) , terrain_(nullptr)
, map_(nullptr) , map_(nullptr)
{ {
get_canvas(0).set_draw_function(std::bind(&minimap::canvas_draw_background, this, _1)); get_canvas(0).set_draw_function(std::bind(&minimap::canvas_draw_background, this, _1, _2));
} }
void minimap::set_active(const bool /*active*/) void minimap::set_active(const bool /*active*/)
@ -91,15 +91,12 @@ void minimap::set_map_data(const std::string& map_data)
map_.reset(nullptr); map_.reset(nullptr);
ERR_CF << "Error while loading the map: " << e.message << '\n'; ERR_CF << "Error while loading the map: " << e.message << '\n';
} }
// Flag the background canvas as dirty so the minimap is redrawn.
get_canvas(0).set_is_dirty(true);
} }
void minimap::canvas_draw_background(texture& tex) void minimap::canvas_draw_background(unsigned dst_w, unsigned dst_h)
{ {
if(map_) { if(map_) {
image::render_minimap(tex, *map_, nullptr, nullptr, nullptr, true); image::render_minimap(dst_w, dst_h, *map_, nullptr, nullptr, nullptr, true);
} }
} }

View file

@ -21,7 +21,6 @@
class config; class config;
class gamemap; class gamemap;
class texture;
namespace gui2 namespace gui2
{ {
@ -91,7 +90,7 @@ private:
std::unique_ptr<gamemap> map_; std::unique_ptr<gamemap> map_;
/** Drawing function passed to the background canvas. */ /** Drawing function passed to the background canvas. */
void canvas_draw_background(texture& tex); void canvas_draw_background(unsigned dst_w, unsigned dst_h);
/** Inherited from styled_widget, implemented by REGISTER_WIDGET. */ /** Inherited from styled_widget, implemented by REGISTER_WIDGET. */
virtual const std::string& get_control_type() const override; virtual const std::string& get_control_type() const override;

View file

@ -37,21 +37,19 @@ static lg::log_domain log_display("display");
namespace image namespace image
{ {
void render_minimap(texture& tex, const gamemap& map, const team* vw, const unit_map* units, const std::map<map_location, unsigned int>* reach_map, bool ignore_terrain_disabled) void render_minimap(unsigned dst_w,
unsigned dst_h,
const gamemap& map,
const team* vw,
const unit_map* units,
const std::map<map_location, unsigned int>* reach_map,
bool ignore_terrain_disabled)
{ {
if(tex.null()) {
return;
}
CVideo& video = CVideo::get_singleton(); CVideo& video = CVideo::get_singleton();
display* disp = display::get_singleton(); display* disp = display::get_singleton();
const bool is_blindfolded = disp && disp->is_blindfolded(); const bool is_blindfolded = disp && disp->is_blindfolded();
// Validate that the passed texture is the current render target.
// TODO: if it's not, maybe set it here?
assert(SDL_GetRenderTarget(video.get_renderer()) == tex);
const terrain_type_data& tdata = *map.tdata(); const terrain_type_data& tdata = *map.tdata();
// Drawing mode flags. // Drawing mode flags.
@ -314,10 +312,9 @@ void render_minimap(texture& tex, const gamemap& map, const team* vw, const unit
} }
texture::info src_info = minimap.get_info(); texture::info src_info = minimap.get_info();
texture::info dst_info = tex.get_info();
const double wratio = dst_info.w * 1.0 / src_info.w; const double wratio = dst_w * 1.0 / src_info.w;
const double hratio = dst_info.h * 1.0 / src_info.h; const double hratio = dst_h * 1.0 / src_info.h;
const double ratio = std::min<double>(wratio, hratio); const double ratio = std::min<double>(wratio, hratio);

View file

@ -20,9 +20,7 @@
#include <map> #include <map>
class gamemap; class gamemap;
class surface;
class team; class team;
class texture;
class unit_map; class unit_map;
struct map_location; struct map_location;
class gamemap; class gamemap;
@ -30,9 +28,10 @@ class gamemap;
namespace image namespace image
{ {
/** /**
* Renders the minimap to the given texture. * Renders the minimap to the screen.
*/ */
void render_minimap(texture& tex, void render_minimap(unsigned dst_w,
unsigned dst_h,
const gamemap& map, const gamemap& map,
const team* vw = nullptr, const team* vw = nullptr,
const unit_map* units = nullptr, const unit_map* units = nullptr,