summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatric Stout <truebrain@openttd.org>2021-02-17 21:19:32 +0100
committerGitHub <noreply@github.com>2021-02-17 21:19:32 +0100
commit8e0d48a0f6b2900da9561126af73a046afb58d77 (patch)
treeb9142771d13c32b7d428259dd82fb943ed2391c2
parent8bd2349ab6d25a16b8ffc4e9b4ea541ca8d29610 (diff)
downloadopenttd-8e0d48a0f6b2900da9561126af73a046afb58d77.tar.xz
Fix: [SDL2] simplify what to redraw to prevent tearing (#8685)
When there are a lot of rects to redraw, of which one of the last ones is almost the full screen, visual tearing happens over the vertical axis. This is most visible when scrolling the map. This can be prevented by using less rects. To simplify the situation, and as solutions like OpenGL need this anyway, keep a single rect that shows the biggest size that updates everything correctly. Although this means it needs a bit more time redrawing where it is strictly seen not needed, it also means less commands have to be executed in the backend. In the end, this is a trade-off, and from experiments it seems the approach of this commit gives a better result.
-rw-r--r--src/core/geometry_func.cpp22
-rw-r--r--src/core/geometry_func.hpp12
-rw-r--r--src/video/sdl2_v.cpp38
3 files changed, 46 insertions, 26 deletions
diff --git a/src/core/geometry_func.cpp b/src/core/geometry_func.cpp
index 89e70fb5b..dccef72ec 100644
--- a/src/core/geometry_func.cpp
+++ b/src/core/geometry_func.cpp
@@ -26,3 +26,25 @@ Dimension maxdim(const Dimension &d1, const Dimension &d2)
d.height = std::max(d1.height, d2.height);
return d;
}
+
+/**
+ * Compute the bounding rectangle around two rectangles.
+ * @param r1 First rectangle.
+ * @param r2 Second rectangle.
+ * @return The bounding rectangle, the smallest rectangle that contains both arguments.
+ */
+Rect BoundingRect(const Rect &r1, const Rect &r2)
+{
+ /* If either the first or the second is empty, return the other. */
+ if (IsEmptyRect(r1)) return r2;
+ if (IsEmptyRect(r2)) return r1;
+
+ Rect r;
+
+ r.top = std::min(r1.top, r2.top);
+ r.bottom = std::max(r1.bottom, r2.bottom);
+ r.left = std::min(r1.left, r2.left);
+ r.right = std::max(r1.right, r2.right);
+
+ return r;
+}
diff --git a/src/core/geometry_func.hpp b/src/core/geometry_func.hpp
index cd136488a..41df35945 100644
--- a/src/core/geometry_func.hpp
+++ b/src/core/geometry_func.hpp
@@ -14,4 +14,16 @@
Dimension maxdim(const Dimension &d1, const Dimension &d2);
+/**
+ * Check if a rectangle is empty.
+ * @param r Rectangle to check.
+ * @return True if and only if the rectangle doesn't define space.
+ */
+static inline bool IsEmptyRect(const Rect &r)
+{
+ return (r.left | r.top | r.right | r.bottom) == 0;
+}
+
+Rect BoundingRect(const Rect &r1, const Rect &r2);
+
#endif /* GEOMETRY_FUNC_HPP */
diff --git a/src/video/sdl2_v.cpp b/src/video/sdl2_v.cpp
index ce6bbc93f..d41f79d54 100644
--- a/src/video/sdl2_v.cpp
+++ b/src/video/sdl2_v.cpp
@@ -17,6 +17,8 @@
#include "../progress.h"
#include "../core/random_func.hpp"
#include "../core/math_func.hpp"
+#include "../core/mem_func.hpp"
+#include "../core/geometry_func.hpp"
#include "../fileio_func.h"
#include "../framerate_type.h"
#include "../window_func.h"
@@ -54,19 +56,12 @@ static SDL_Palette *_sdl_palette;
static bool _cursor_new_in_window = false;
#endif
-#define MAX_DIRTY_RECTS 100
-static SDL_Rect _dirty_rects[MAX_DIRTY_RECTS];
-static int _num_dirty_rects;
+static Rect _dirty_rect;
void VideoDriver_SDL::MakeDirty(int left, int top, int width, int height)
{
- if (_num_dirty_rects < MAX_DIRTY_RECTS) {
- _dirty_rects[_num_dirty_rects].x = left;
- _dirty_rects[_num_dirty_rects].y = top;
- _dirty_rects[_num_dirty_rects].w = width;
- _dirty_rects[_num_dirty_rects].h = height;
- }
- _num_dirty_rects++;
+ Rect r = {left, top, left + width, top + height};
+ _dirty_rect = BoundingRect(_dirty_rect, r);
}
static void UpdatePalette()
@@ -133,7 +128,7 @@ static void Paint()
{
PerformanceMeasurer framerate(PFE_VIDEO);
- if (_num_dirty_rects == 0) return;
+ if (IsEmptyRect(_dirty_rect) && _cur_palette.count_dirty == 0) return;
if (_cur_palette.count_dirty != 0) {
Blitter *blitter = BlitterFactory::GetCurrentBlitter();
@@ -156,23 +151,14 @@ static void Paint()
_cur_palette.count_dirty = 0;
}
- if (_num_dirty_rects > MAX_DIRTY_RECTS) {
- if (_sdl_surface != _sdl_real_surface) {
- SDL_BlitSurface(_sdl_surface, nullptr, _sdl_real_surface, nullptr);
- }
+ SDL_Rect r = { _dirty_rect.left, _dirty_rect.top, _dirty_rect.right - _dirty_rect.left, _dirty_rect.bottom - _dirty_rect.top };
- SDL_UpdateWindowSurface(_sdl_window);
- } else {
- if (_sdl_surface != _sdl_real_surface) {
- for (int i = 0; i < _num_dirty_rects; i++) {
- SDL_BlitSurface(_sdl_surface, &_dirty_rects[i], _sdl_real_surface, &_dirty_rects[i]);
- }
- }
-
- SDL_UpdateWindowSurfaceRects(_sdl_window, _dirty_rects, _num_dirty_rects);
+ if (_sdl_surface != _sdl_real_surface) {
+ SDL_BlitSurface(_sdl_surface, &r, _sdl_real_surface, &r);
}
+ SDL_UpdateWindowSurfaceRects(_sdl_window, &r, 1);
- _num_dirty_rects = 0;
+ MemSetT(&_dirty_rect, 0);
}
static void PaintThread()
@@ -356,7 +342,7 @@ bool VideoDriver_SDL::CreateMainSurface(uint w, uint h, bool resize)
* gotten smaller, reset our dirty rects. GameSizeChanged() a bit lower
* will mark the whole screen dirty again anyway, but this time with the
* new dimensions. */
- _num_dirty_rects = 0;
+ MemSetT(&_dirty_rect, 0);
_screen.width = _sdl_surface->w;
_screen.height = _sdl_surface->h;