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()
|
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 ***** ***** ***** ***** *****/
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Add table
Reference in a new issue