30 #include <TargetConditionals.h>
38 #include <SDL3/SDL_render.h>
45 SDL_Surface* Android_AP_getFrameBuffer();
50 #define LOG_DP LOG_STREAM(info, log_display)
51 #define ERR_DP LOG_STREAM(err, log_display)
52 #define WRN_DP LOG_STREAM(warn, log_display)
53 #define DBG_DP LOG_STREAM(debug, log_display)
58 std::unique_ptr<sdl::window> window;
64 texture current_render_target_ = {};
66 bool headless_ =
false;
67 bool testing_ =
false;
68 point test_resolution_ = {1024, 768};
69 int refresh_rate_ = 0;
70 point game_canvas_size_ = {0, 0};
73 rect input_area_ = {};
95 LOG_DP <<
"initializing video";
96 if(SDL_WasInit(SDL_INIT_VIDEO)) {
97 throw error(
"video subsystem already initialized");
99 if(!SDL_InitSubSystem(SDL_INIT_VIDEO)) {
100 ERR_DP <<
"Could not initialize SDL_video: " << SDL_GetError();
101 throw error(
"Video initialization failed");
118 throw error(
"unrecognized fake type passed to video::init");
124 LOG_DP <<
"deinitializing video";
129 render_texture_.reset();
130 current_render_target_.reset();
136 if(SDL_WasInit(SDL_INIT_VIDEO)) {
137 LOG_DP <<
"quitting SDL video subsystem";
138 SDL_QuitSubSystem(SDL_INIT_VIDEO);
140 if(SDL_WasInit(SDL_INIT_VIDEO)) {
142 throw error(
"video subsystem still initialized after deinit");
158 LOG_DP <<
"running headless";
161 game_canvas_size_ = {800,600};
175 throw(
"trying to update test framebuffer with no window");
178 bool changed =
false;
182 if (render_texture_) {
183 SDL_PropertiesID props = SDL_GetTextureProperties(render_texture_.get());
184 int w = SDL_GetNumberProperty(props, SDL_PROP_TEXTURE_WIDTH_NUMBER, 0);
185 int h = SDL_GetNumberProperty(props, SDL_PROP_TEXTURE_HEIGHT_NUMBER, 0);
186 if (
w != test_resolution_.x ||
h != test_resolution_.y) {
188 LOG_DP <<
"destroying old render texture";
189 render_texture_.reset();
192 if (!render_texture_) {
193 LOG_DP <<
"creating offscreen render texture";
194 render_texture_.assign(SDL_CreateTexture(
196 SDL_PIXELFORMAT_ARGB8888,
197 SDL_TEXTUREACCESS_TARGET,
198 test_resolution_.x, test_resolution_.y
200 LOG_DP <<
"updated render target to " << test_resolution_.x
201 <<
"x" << test_resolution_.y;
206 game_canvas_size_ = test_resolution_;
207 input_area_ = {{}, test_resolution_};
218 throw error(
"trying to update framebuffer with no window");
225 bool changed =
false;
228 SDL_SetRenderTarget(*window,
nullptr);
231 point osize(window->get_output_size());
232 max_scale_ = std::min(
241 int def_scale = std::min(
244 scale = std::min(max_scale_, def_scale);
246 int min_scale = std::min(
254 if (pixel_scale_ !=
scale) {
255 pixel_scale_ =
scale;
260 point lsize(window->get_logical_size());
261 point wsize(window->get_size());
262 if (lsize.x != osize.x /
scale || lsize.y != osize.y /
scale) {
264 LOG_DP <<
"reducing pixel scale from desired "
269 LOG_DP <<
"overriding logical size";
270 LOG_DP <<
" old lsize: " << lsize;
271 LOG_DP <<
" old wsize: " << wsize;
272 LOG_DP <<
" old osize: " << osize;
273 window->set_logical_size(osize.x /
scale, osize.y /
scale);
274 lsize = window->get_logical_size();
275 wsize = window->get_size();
276 osize = window->get_output_size();
277 LOG_DP <<
" new lsize: " << lsize;
278 LOG_DP <<
" new wsize: " << wsize;
279 LOG_DP <<
" new osize: " << osize;
281 SDL_GetRenderScale(*window, &sx, &sy);
282 LOG_DP <<
" render scale: " << sx <<
", " << sy;
285 game_canvas_size_ = lsize;
288 if (render_texture_) {
289 SDL_PropertiesID props = SDL_GetTextureProperties(render_texture_.get());
290 int w = SDL_GetNumberProperty(props, SDL_PROP_TEXTURE_WIDTH_NUMBER, 0);
291 int h = SDL_GetNumberProperty(props, SDL_PROP_TEXTURE_HEIGHT_NUMBER, 0);
292 if (
w != osize.x ||
h != osize.y) {
294 LOG_DP <<
"destroying old render texture";
295 render_texture_.reset();
298 render_texture_.set_draw_size(lsize);
301 if (!render_texture_) {
302 LOG_DP <<
"creating offscreen render texture";
303 render_texture_.assign(SDL_CreateTexture(
305 SDL_PIXELFORMAT_ARGB8888,
306 SDL_TEXTUREACCESS_TARGET,
310 render_texture_.set_draw_size(lsize);
318 input_area_ = {{}, wsize};
321 if (active_area.
size() != osize) {
322 LOG_DP <<
"render target offset: LT " << active_area.
origin() <<
" RB "
323 << osize - active_area.
size() - active_area.
origin();
326 (active_area.
origin() * wsize) / osize,
327 (active_area.
size() * wsize) / osize
329 LOG_DP <<
"input area: " << input_area_;
337 LOG_DP <<
"creating test window " << test_resolution_.x
338 <<
"x" << test_resolution_.y;
340 uint32_t window_flags = 0;
341 window_flags |= SDL_WINDOW_HIDDEN;
345 "", test_resolution_.x, test_resolution_.y,
357 #if defined(__APPLE__) && TARGET_OS_IPHONE
365 uint32_t window_flags = 0;
368 window_flags |= SDL_WINDOW_RESIZABLE;
369 window_flags |= SDL_WINDOW_HIGH_PIXEL_DENSITY;
372 window_flags |= SDL_WINDOW_MAXIMIZED;
376 LOG_DP <<
"hiding main window";
377 window_flags |= SDL_WINDOW_HIDDEN;
383 SDL_DisplayMode mode;
384 mode.format = SDL_PIXELFORMAT_UNKNOWN;
387 mode.refresh_rate = 0;
388 mode.internal =
nullptr;
390 SDL_SetWindowFullscreenMode(*window, &mode);
395 assert(!render_texture_);
401 const SDL_DisplayMode* currentDisplayMode = SDL_GetCurrentDisplayMode(window->get_display_index());
402 refresh_rate_ = currentDisplayMode->refresh_rate != 0 ? currentDisplayMode->refresh_rate : 60;
405 window->set_size(
w,
h);
419 return test_resolution_;
422 return window->get_output_size();
428 return test_resolution_;
430 return window->get_size();
435 return {0, 0, game_canvas_size_.x, game_canvas_size_.y};
440 return game_canvas_size_;
445 return current_render_target_.draw_size();
450 return {0, 0, current_render_target_.w(), current_render_target_.h()};
463 return (osize - (
scale * dsize)) / 2;
469 return {0, 0,
p.x,
p.y};
475 point dsize = current_render_target_.draw_size();
476 point osize = current_render_target_.get_raw_size();
499 return refresh_rate_;
505 if(
auto preferred =
prefs::get().refresh_rate(); preferred > 0) {
506 return std::min(preferred, refresh_rate_);
508 return refresh_rate_;
514 SDL_DisplayID
id = SDL_GetDisplayForWindow(*window);
516 return SDL_GetDisplayContentScale(
id);
522 return SDL_GetWindowPixelDensity(*window);
526 return SDL_GetWindowDisplayScale(*window);
532 ERR_DP <<
"failed to set render target to "
533 <<
static_cast<void*
>(
t.get()) <<
' '
534 <<
t.draw_size() <<
" / " <<
t.get_raw_size();
535 ERR_DP <<
"last SDL error: " << SDL_GetError();
536 throw error(
"failed to set render target");
538 current_render_target_ =
t;
547 DBG_DP <<
"rendering to window / screen";
548 window->set_logical_size(game_canvas_size_);
549 }
else if (
t == render_texture_) {
550 DBG_DP <<
"rendering to primary buffer";
551 window->set_logical_size(game_canvas_size_);
553 DBG_DP <<
"rendering to custom target "
554 <<
static_cast<void*
>(
t.get()) <<
' '
555 <<
t.draw_size() <<
" / " <<
t.get_raw_size();
556 window->set_logical_size(
t.w(),
t.h());
574 assert(current_render_target_ == SDL_GetRenderTarget(
get_renderer()));
576 return current_render_target_;
584 if(headless_ || testing_) {
590 WRN_DP <<
"trying to render with no window";
596 if(SDL_GetRenderTarget(*window) != render_texture_) {
597 ERR_DP <<
"trying to render screen, but current render texture is "
598 <<
static_cast<void*
>(SDL_GetRenderTarget(*window))
599 <<
" | " <<
static_cast<void*
>(current_render_target_.get())
600 <<
". It should be " <<
static_cast<void*
>(render_texture_.get());
601 throw error(
"tried to render screen from wrong render target");
608 SDL_SetRenderDrawColor(*window, 0u, 0u, 0u, 0u);
611 SDL_RenderClear(*window);
614 SDL_RenderTexture(*window, render_texture_,
nullptr,
nullptr);
617 SDL_RenderPresent(*window);
626 WRN_DP <<
"trying to read pixels with no window";
631 texture& target = current_render_target_;
634 if (target != SDL_GetRenderTarget(*window)) {
635 SDL_Texture*
t = SDL_GetRenderTarget(*window);
636 ERR_DP <<
"render target " <<
static_cast<void*
>(target.
get())
638 <<
" doesn't match window render target "
639 <<
static_cast<void*
>(
t);
640 throw error(
"unexpected render target while reading pixels");
647 if (r_clipped != *r) {
648 DBG_DP <<
"modifying pixel read area from " << *r
649 <<
" to " << r_clipped;
658 surface s = SDL_RenderReadPixels(*window, &o);
665 WRN_DP <<
"trying to read pixels with no window";
679 window->set_title(title);
685 window->set_icon(icon);
708 const char*
const drvname = SDL_GetCurrentVideoDriver();
709 return drvname ? drvname :
"<not initialized>";
714 std::vector<std::string> res;
715 int num_drivers = SDL_GetNumVideoDrivers();
717 for(
int n = 0;
n < num_drivers; ++
n) {
718 const char* drvname = SDL_GetVideoDriver(
n);
719 res.emplace_back(drvname ? drvname :
"<invalid driver>");
732 return window && (window->get_flags() & flags) != 0;
752 std::vector<point> result;
763 const int display_index = window->get_display_index();
766 SDL_GetFullscreenDisplayModes(display_index, &modes);
777 SDL_GetDisplayBounds(display_index, &bounds);
780 result.push_back(min_res);
783 if(include_current) {
787 std::sort(result.begin(), result.end());
788 result.erase(std::unique(result.begin(), result.end()), result.end());
796 return test_resolution_;
798 return window->get_size();
803 if (headless_ || testing_) {
808 window->full_screen();
833 throw error(
"tried to set resolution with no window");
837 LOG_DP <<
"resizing test resolution to " << resolution;
838 test_resolution_ = resolution;
843 window->set_size(resolution.x, resolution.y);
849 LOG_DP <<
"updating resolution to " << resolution;
862 LOG_DP <<
"updating video buffers";
870 std::vector<std::pair<std::string, std::string>> res;
874 std::string renderer_name = SDL_GetRendererName(rnd) ? SDL_GetRendererName(rnd) :
"<unknown>";
875 SDL_PropertiesID props = SDL_GetRendererProperties(rnd);
877 res.emplace_back(
"Renderer", renderer_name);
878 res.emplace_back(
"VSync", SDL_GetNumberProperty(props, SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER, 0) != 0 ?
"on" :
"off");
void set_resolution(const point &res)
The wrapper class for the SDL_Window class.
Wrapper class to encapsulate creation and management of an SDL_Texture.
point draw_size() const
The size of the texture in draw-space.
point get_raw_size() const
The raw internal texture size.
SDL_Texture * get() const
Interfaces for manipulating version numbers of engine, add-ons, etc.
Standard logging facilities (interface).
void install_keyboard_dismiss_toolbar()
void invalidate_all()
Mark the entire screen as requiring redraw.
void flush_cache()
Purges all image caches.
const int min_window_height
const int max_pixel_scale
const int def_window_width
const int min_window_width
const int max_window_height
const int max_window_width
const int def_window_height
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
bool window_has_mouse_focus()
True iff the window has mouse focus.
bool headless()
The game is running headless.
rect draw_area()
The current drawable area.
rect output_area()
{0, 0, output_size().x, output_size().y}
void set_window_title(const std::string &title)
Sets the title of the main window.
void clear_render_target()
Reset the render target to the main window / screen.
rect to_output(const rect &r)
Convert coordinates in draw space to coordinates in render space.
std::vector< std::pair< std::string, std::string > > renderer_report()
Provides diagnostic information about the current renderer for the build_info API.
static bool window_has_flags(uint32_t flags)
Tests whether the given flags are currently set on the SDL window.
static bool update_framebuffer()
void reset_render_target()
Reset the render target to the primary render buffer.
point output_size()
Returns the size of the final render target.
std::vector< point > get_available_resolutions(const bool include_current)
Returns the list of available screen resolutions.
static bool update_test_framebuffer()
Returns true if the buffer was changed.
void set_window_icon(surface &icon)
Sets the icon of the main window.
int get_max_pixel_scale()
bool window_is_visible()
True iff the window is not hidden.
void force_render_target(const texture &t)
Set the render target, without any provided way of setting it back.
point game_canvas_size()
The size of the game canvas, in drawing coordinates / game pixels.
SDL_Window * get_window()
bool window_has_focus()
True iff the window has mouse or input focus.
point window_size()
Returns the size of the window in display units / screen coordinates.
bool has_window()
Whether the game has set up a window to render into.
bool testing()
The game is running unit tests.
std::vector< std::string > enumerate_drivers()
A list of available video drivers.
rect game_canvas()
The game canvas area, in drawing coordinates.
static point draw_offset()
int get_pixel_scale()
Get the current active pixel scale multiplier.
static void init_window(bool hidden=false)
point current_resolution()
The current window size in desktop coordinates.
int native_refresh_rate()
The native refresh rate of display, not taking any user preferences into account.
void init(fake type)
Initialize the video subsystem.
void set_fullscreen(bool fullscreen)
Set the fullscreen state.
void deinit()
Deinitialize the video subsystem.
bool set_resolution(const point &resolution)
Set the window resolution.
int current_refresh_rate()
The refresh rate of the screen.
surface read_pixels_low_res(rect *r)
The same as read_pixels, but returns a low-resolution surface suitable for use with the old drawing s...
std::string current_driver()
The current video driver in use, or else "<not initialized>".
surface read_pixels(rect *r)
Copy back a portion of the render target that is already drawn.
void toggle_fullscreen()
Toggle fullscreen mode.
SDL_Renderer * get_renderer()
void update_buffers(bool autoupdate)
Update buffers to match current resolution and pixel scale settings.
texture get_render_target()
Get the current render target.
fake
For describing the type of faked display, if any.
static void init_test_window()
point draw_size()
The size of the current render target in drawing coordinates.
rect input_area()
Returns the input area of the window, in display coordinates.
void scale(size_t factor, const uint32_t *src, uint32_t *trg, int srcWidth, int srcHeight, ColorFormat colFmt, const ScalerCfg &cfg=ScalerCfg(), int yFirst=0, int yLast=std::numeric_limits< int >::max())
Contains a wrapper class for the SDL_Window class.
An abstract description of a rectangle with integer coordinates.
void clip(const rect &r)
Clip this rectangle by the given rectangle.
constexpr point origin() const
constexpr point size() const
An error specifically indicating video subsystem problems.
static map_location::direction n
static map_location::direction s
surface scale_surface(const surface &surf, int w, int h)
Scale a surface using alpha-weighted modified bilinear filtering Note: causes artifacts with alpha gr...
static lg::log_domain log_display("display")