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()
: shapes_()
, drawn_shapes_()
, draw_func_(nullptr)
, blur_depth_(0)
, w_(0)
, h_(0)
, texture_(nullptr)
, renderer_(CVideo::get_singleton().get_renderer())
, variables_()
, functions_()
, is_dirty_(true)
, size_changed_(true)
, cache_invalidated_(false)
{
}
canvas::canvas(canvas&& c)
: shapes_(std::move(c.shapes_))
, drawn_shapes_(std::move(c.drawn_shapes_))
, draw_func_(c.draw_func_)
, blur_depth_(c.blur_depth_)
, w_(c.w_)
, h_(c.h_)
, texture_(std::move(c.texture_))
, renderer_(std::exchange(c.renderer_, nullptr))
, variables_(c.variables_)
, functions_(c.functions_)
, is_dirty_(c.is_dirty_)
, size_changed_(c.size_changed_)
, cache_invalidated_(c.cache_invalidated_)
{
}
canvas::~canvas()
{
SDL_SetRenderTarget(renderer_, nullptr);
}
void canvas::draw(const bool force)
void canvas::draw()
{
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_);
variables_.add("width", wfl::variant(w_));
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(have_custom_draw) {
draw_func_(texture_);
set_is_dirty(false);
if(draw_func_) {
draw_func_(w_, h_);
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.
for(auto& shape : shapes_) {
lg::scope_logger inner_scope_logging_object__(log_gui_draw, "Canvas: draw shape.");
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()
@ -1516,9 +1437,6 @@ void canvas::render()
}
}
#endif
// Copy the entire texture to the full viewport.
CVideo::get_singleton().render_copy(texture_);
}
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();
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());
}
shapes_.clear();
}
void canvas::set_size(unsigned w, unsigned h)
{
update_size(w_, w);
update_size(h_, h);
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);
}
w_ = w;
h_ = h;
size_changed_ = true;
}
/***** ***** ***** ***** ***** SHAPE ***** ***** ***** ***** *****/

View file

@ -93,15 +93,12 @@ public:
~canvas();
using draw_func_t = std::function<void(texture&)>;
using draw_func_t = std::function<void(unsigned, unsigned)>;
/**
* 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.
@ -123,10 +120,9 @@ public:
* http://www.wesnoth.org/wiki/GUICanvasWML for
* more information.
*/
void set_cfg(const config& cfg, const bool force = false)
void set_cfg(const config& cfg)
{
clear_shapes(force);
invalidate_cache();
clear_shapes();
parse_cfg(cfg);
}
@ -159,23 +155,12 @@ public:
void set_variable(const std::string& key, const wfl::variant& value)
{
variables_.add(key, value);
set_is_dirty(true);
invalidate_cache();
}
void set_is_dirty(const bool is_dirty)
{
is_dirty_ = is_dirty;
}
private:
/** Vector with the shapes to draw. */
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. */
draw_func_t draw_func_;
@ -195,9 +180,6 @@ private:
/** Height of the canvas. */
unsigned h_;
/** The texture onto which items are drawn. */
texture texture_;
/** A pointer to the window renderer. */
SDL_Renderer* renderer_;
@ -207,15 +189,9 @@ private:
/** Action function definitions for the canvas. */
wfl::action_function_symbol_table functions_;
/** The dirty state of the canvas. */
bool is_dirty_;
/** Whether canvas dimensions changed. */
bool size_changed_;
/** Whether to completely clear the canvas texture of any previously drawn content. */
bool cache_invalidated_;
/**
* Parses a config object.
*
@ -228,15 +204,7 @@ private:
*/
void parse_cfg(const config& cfg);
void clear_shapes(const bool force);
void invalidate_cache()
{
cache_invalidated_ = true;
}
/** Small helper to handle size variable update logic. */
void update_size(unsigned int& value, unsigned int new_value);
void clear_shapes();
};
} // 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.
window_canvas.append_cfg(cfg);
window_canvas.set_is_dirty(true);
++image_iter;

View file

@ -54,7 +54,7 @@ minimap::minimap(const implementation::builder_minimap& builder)
, terrain_(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*/)
@ -91,15 +91,12 @@ void minimap::set_map_data(const std::string& map_data)
map_.reset(nullptr);
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_) {
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 gamemap;
class texture;
namespace gui2
{
@ -91,7 +90,7 @@ private:
std::unique_ptr<gamemap> map_;
/** 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. */
virtual const std::string& get_control_type() const override;

View file

@ -37,21 +37,19 @@ static lg::log_domain log_display("display");
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();
display* disp = display::get_singleton();
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();
// 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 dst_info = tex.get_info();
const double wratio = dst_info.w * 1.0 / src_info.w;
const double hratio = dst_info.h * 1.0 / src_info.h;
const double wratio = dst_w * 1.0 / src_info.w;
const double hratio = dst_h * 1.0 / src_info.h;
const double ratio = std::min<double>(wratio, hratio);

View file

@ -20,9 +20,7 @@
#include <map>
class gamemap;
class surface;
class team;
class texture;
class unit_map;
struct map_location;
class gamemap;
@ -30,9 +28,10 @@ class gamemap;
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 team* vw = nullptr,
const unit_map* units = nullptr,