Reimplement map screenshots.

They will remain broken until display.cpp switches over to using
textures in stead of surfaces to render.
This commit is contained in:
Tommy 2022-05-31 14:25:03 +12:00
parent 743a1b5655
commit d39a6fe619
5 changed files with 140 additions and 75 deletions

View File

@ -71,6 +71,7 @@
static lg::log_domain log_display("display");
#define ERR_DP LOG_STREAM(err, log_display)
#define WRN_DP LOG_STREAM(warn, log_display)
#define LOG_DP LOG_STREAM(info, log_display)
#define DBG_DP LOG_STREAM(debug, log_display)
@ -229,7 +230,6 @@ display::display(const display_context* dc,
, fps_handle_(0)
, invalidated_hexes_(0)
, drawn_hexes_(0)
, map_screenshot_surf_(nullptr)
, redraw_observers_()
, draw_coordinates_(false)
, draw_terrain_codes_(false)
@ -781,43 +781,46 @@ map_location display::minimap_location_on(int x, int y)
surface display::screenshot(bool map_screenshot)
{
if (!map_screenshot) {
LOG_DP << "taking ordinary screenshot" << std::endl;
return screen_.read_pixels();
} else {
if (get_map().empty()) {
ERR_DP << "No map loaded, cannot create a map screenshot.\n";
return nullptr;
}
SDL_Rect area = max_map_area();
map_screenshot_surf_ = surface(area.w, area.h);
if (map_screenshot_surf_ == nullptr) {
// Memory problem ?
ERR_DP << "Could not create screenshot surface, try zooming out.\n";
return nullptr;
}
// back up the current map view position and move to top-left
int old_xpos = xpos_;
int old_ypos = ypos_;
xpos_ = 0;
ypos_ = 0;
// we reroute render output to the screenshot surface and invalidate all
map_screenshot_= true;
invalidateAll_ = true;
DBG_DP << "draw() with map_screenshot\n";
draw(true,true);
// restore normal rendering
map_screenshot_= false;
xpos_ = old_xpos;
ypos_ = old_ypos;
// Clear map_screenshot_surf_ and return a new surface that contains the same data
surface surf(std::move(map_screenshot_surf_));
return surf;
}
if (get_map().empty()) {
ERR_DP << "No map loaded, cannot create a map screenshot.\n";
return nullptr;
}
// back up the current map view position and move to top-left
int old_xpos = xpos_;
int old_ypos = ypos_;
xpos_ = 0;
ypos_ = 0;
// Reroute render output to a separate texture until the end of scope.
SDL_Rect area = max_map_area();
if (area.w > 1 << 16 || area.h > 1 << 16) {
WRN_DP << "Excessively large map screenshot area" << std::endl;
}
LOG_DP << "creating " << area.w << " by " << area.h
<< " texture for map screenshot" << std::endl;
texture output_texture(area.w, area.h, SDL_TEXTUREACCESS_TARGET);
auto target_setter = video().set_render_target(output_texture);
auto clipper = video().set_clip(area);
map_screenshot_ = true;
invalidateAll_ = true;
DBG_DP << "draw() call for map screenshot\n";
draw(true, true);
map_screenshot_ = false;
// Restore map viewport position
xpos_ = old_xpos;
ypos_ = old_ypos;
// Read rendered pixels back as an SDL surface.
return video().read_pixels();
}
std::shared_ptr<gui::button> display::find_action_button(const std::string& id)

View File

@ -1034,8 +1034,6 @@ private:
int invalidated_hexes_;
int drawn_hexes_;
surface map_screenshot_surf_;
std::vector<std::function<void(display&)>> redraw_observers_;
/** Debug flag - overlay x,y coords on tiles */

View File

@ -24,36 +24,9 @@
#include <cassert>
#include <functional>
/**
* Sets the renderer output target to the specified texture.
*/
class render_target_setter
{
public:
explicit render_target_setter(texture& t)
: renderer_(CVideo::get_singleton().get_renderer())
, last_target_(nullptr)
{
if(renderer_) {
// Validate we can render to this texture.
assert(t.get_info().access == SDL_TEXTUREACCESS_TARGET);
last_target_ = SDL_GetRenderTarget(renderer_);
SDL_SetRenderTarget(renderer_, t);
}
}
~render_target_setter()
{
if(renderer_) {
SDL_SetRenderTarget(renderer_, last_target_);
}
}
private:
SDL_Renderer* renderer_;
SDL_Texture* last_target_; // TODO: use the texture wrapper?
};
// The render_target_setter class has been moved to CVideo.
// To obtain one, use CVideo::set_render_target().
//class render_target_setter
using sdl_rect_getter = void (*)(SDL_Renderer*, SDL_Rect*);
using sdl_rect_setter = int (*)(SDL_Renderer*, const SDL_Rect*);

View File

@ -500,6 +500,28 @@ void CVideo::delay(unsigned int milliseconds)
}
}
CVideo::render_target_setter CVideo::set_render_target(const texture& t)
{
return CVideo::render_target_setter(*this, t);
}
void CVideo::force_render_target(SDL_Texture* t)
{
SDL_SetRenderTarget(get_renderer(), t);
// The scale factor gets reset when the render target changes,
// so make sure it gets set back appropriately.
if (t == nullptr || t == render_texture_) {
// TODO: highdpi - sort out who owns this
window->set_logical_size(get_width(), get_height());
}
}
SDL_Texture* CVideo::get_render_target()
{
return SDL_GetRenderTarget(get_renderer());
}
// TODO: highdpi - separate drawing interface should also handle clipping
CVideo::clip_setter CVideo::set_clip(const SDL_Rect& clip)
@ -510,7 +532,11 @@ CVideo::clip_setter CVideo::set_clip(const SDL_Rect& clip)
void CVideo::force_clip(const SDL_Rect& clip)
{
// Set the clipping area both on the drawing surface,
SDL_SetClipRect(drawingSurface, &clip);
if (clip == sdl::empty_rect) {
SDL_SetClipRect(drawingSurface, nullptr);
} else {
SDL_SetClipRect(drawingSurface, &clip);
}
// and on the render target.
if (SDL_RenderSetClipRect(get_renderer(), &clip)) {
throw error("Failed to set render clip rect");
@ -519,13 +545,13 @@ void CVideo::force_clip(const SDL_Rect& clip)
SDL_Rect CVideo::get_clip() const
{
if (!drawingSurface) {
return sdl::empty_rect;
}
SDL_Rect clip;
SDL_RenderGetClipRect(*window, &clip);
SDL_Rect r_draw;
SDL_GetClipRect(drawingSurface, &r_draw);
return r_draw;
if (clip == sdl::empty_rect) {
return draw_area();
}
return clip;
}
SDL_Rect CVideo::clip_to_draw_area(const SDL_Rect* r) const

View File

@ -18,9 +18,11 @@
#include "events.hpp"
#include "exceptions.hpp"
#include "lua_jailbreak_exception.hpp"
#include "sdl/texture.hpp"
#include <SDL2/SDL_render.h>
#include <cassert>
#include <memory>
class surface;
@ -372,6 +374,8 @@ public:
void lock_flips(bool);
/***** ***** ***** ***** State management ***** ***** ****** *****/
/** A class to manage automatic restoration of the clipping region.
*
* While this can be constructed on its own, it is usually easier to
@ -421,6 +425,67 @@ public:
/** Get the current clipping area, in draw coordinates. */
SDL_Rect get_clip() const;
/** A class to manage automatic restoration of the render target.
*
* While this can be constructed on its own, it is usually easier to
* use the CVideo::set_render_target() member function.
*/
class render_target_setter
{
public:
explicit render_target_setter(CVideo& video, const texture& t)
: video_(video), last_target_(nullptr)
{
// Validate we can render to this texture.
assert(t.get_info().access == SDL_TEXTUREACCESS_TARGET);
last_target_ = video_.get_render_target();
video_.force_render_target(t);
}
~render_target_setter()
{
video_.force_render_target(last_target_);
}
private:
CVideo& video_;
SDL_Texture* last_target_;
};
/**
* Set the given texture as the active render target.
*
* All draw calls will draw to this texture until the returned object
* goes out of scope. Do not retain the render_target_setter longer
* than necessary.
*
* The provided texture must have been created with the
* SDL_TEXTUREACCESS_TARGET access mode.
*
* @param t The new render target. This must be a texture created
* with SDL_TEXTUREACCESS_TARGET.
* @returns A render_target_setter object. When this object is
* destroyed the render target will be restored to
* whatever it was before this call.
*/
render_target_setter set_render_target(const texture& t);
/**
* Set the render target, without any provided way of setting it back.
*
* @param t The new render target. This must be a texture created
* with SDL_TEXTUREACCESS_TARGET, or NULL to indicate
* the underlying window.
*/
void force_render_target(SDL_Texture* t);
/** Get the current render target.
*
* Returns NULL if the render target is the underlying window.
*/
SDL_Texture* get_render_target();
/***** ***** ***** ***** Help string functions ***** ***** ****** *****/
/**