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:
parent
cdea2e70cf
commit
02858f2259
7 changed files with 30 additions and 175 deletions
|
@ -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 ***** ***** ***** ***** *****/
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Reference in a new issue