From ef9f6aa7f3640f35747d4991f63d85d4b42b2511 Mon Sep 17 00:00:00 2001 From: dP Date: Wed, 29 Mar 2023 21:47:07 +0400 Subject: [PATCH] Port dirty block handling from JGRPP --- src/citymania/cm_minimap.cpp | 9 + src/citymania/cm_minimap.hpp | 7 + src/citymania/cm_tooltips.cpp | 26 +- src/console_cmds.cpp | 22 ++ src/core/math_func.hpp | 24 ++ src/gfx.cpp | 478 +++++++++++++++++++++++++++------- src/gfx_func.h | 11 +- src/main_gui.cpp | 3 +- src/network/network_gui.cpp | 13 + src/newgrf_gui.cpp | 11 + src/screenshot.cpp | 1 + src/smallmap_gui.cpp | 9 + src/station_gui.cpp | 2 +- src/station_gui.h | 2 +- src/toolbar_gui.cpp | 15 ++ src/viewport.cpp | 118 +++++++-- src/viewport_func.h | 1 + src/viewport_type.h | 31 +++ src/widget.cpp | 77 +++++- src/widget_type.h | 32 ++- src/window.cpp | 103 ++++---- src/window_gui.h | 35 ++- 22 files changed, 837 insertions(+), 193 deletions(-) diff --git a/src/citymania/cm_minimap.cpp b/src/citymania/cm_minimap.cpp index ce6140c467..aac3ae4cd1 100644 --- a/src/citymania/cm_minimap.cpp +++ b/src/citymania/cm_minimap.cpp @@ -1812,6 +1812,15 @@ public: { for (NWidgetBase *child_wid = this->head; child_wid != nullptr; child_wid = child_wid->next) child_wid->Draw(w); } + + void FillDirtyWidgets(std::vector &dirty_widgets) override + { + if (this->base_flags & WBF_DIRTY) { + dirty_widgets.push_back(this); + } else { + for (NWidgetBase *child_wid = this->head; child_wid != nullptr; child_wid = child_wid->next) child_wid->FillDirtyWidgets(dirty_widgets); + } + } }; /** Widget parts of the smallmap display. */ diff --git a/src/citymania/cm_minimap.hpp b/src/citymania/cm_minimap.hpp index d9471a10e3..a0b48b5a5e 100644 --- a/src/citymania/cm_minimap.hpp +++ b/src/citymania/cm_minimap.hpp @@ -45,6 +45,12 @@ void BuildLandLegend(); void BuildOwnerLegend(); + +//struct Mi +// inline uint16 Kdtree_MinimapSignXYFunc(TownID tid, int dim) { return (dim == 0) ? TileX(Town::Get(tid)->xy) : TileY(Town::Get(tid)->xy); } +// typedef Kdtree TownKdtree; + + class NWidgetSmallmapDisplay; /** Class managing the smallmap window. */ @@ -178,6 +184,7 @@ protected: void DrawMapIndicators() const; void DrawSmallMapColumn(void *dst, uint xc, uint yc, int pitch, int reps, int start_pos, int end_pos, int y, int end_y, Blitter *blitter) const; void DrawVehicles(const DrawPixelInfo *dpi, Blitter *blitter) const; + void DrawIndustryProduction(const DrawPixelInfo *dpi) const; void DrawTowns(const DrawPixelInfo *dpi) const; void DrawSmallMap(DrawPixelInfo *dpi) const; diff --git a/src/citymania/cm_tooltips.cpp b/src/citymania/cm_tooltips.cpp index bd81bd0f44..b37d9e88d4 100644 --- a/src/citymania/cm_tooltips.cpp +++ b/src/citymania/cm_tooltips.cpp @@ -507,21 +507,21 @@ bool ShowStationRatingTooltip(Window *parent, const Station *st, const CargoSpec } /* copied from window.cpp */ -static bool MayBeShown(const Window *w) -{ - /* If we're not modal, everything is okay. */ - if (!HasModalProgress()) return true; +// static bool MayBeShown(const Window *w) +// { +// /* If we're not modal, everything is okay. */ +// if (!HasModalProgress()) return true; - switch (w->window_class) { - case WC_MAIN_WINDOW: ///< The background, i.e. the game. - case WC_MODAL_PROGRESS: ///< The actual progress window. - case WC_CONFIRM_POPUP_QUERY: ///< The abort window. - return true; +// switch (w->window_class) { +// case WC_MAIN_WINDOW: ///< The background, i.e. the game. +// case WC_MODAL_PROGRESS: ///< The actual progress window. +// case WC_CONFIRM_POPUP_QUERY: ///< The abort window. +// return true; - default: - return false; - } -} +// default: +// return false; +// } +// } Window *FindHoverableWindowFromPt(int x, int y) { diff --git a/src/console_cmds.cpp b/src/console_cmds.cpp index 2f7d944494..ba2c298826 100644 --- a/src/console_cmds.cpp +++ b/src/console_cmds.cpp @@ -2520,6 +2520,26 @@ DEF_CONSOLE_CMD(ConDumpInfo) return false; } +DEF_CONSOLE_CMD(ConGfxDebug) +{ + if (argc < 1 || argc > 2) { + IConsolePrint(CC_HELP, "Debug: gfx flags. Usage: 'gfx_debug []'"); + IConsolePrint(CC_HELP, " 1: GDF_SHOW_WINDOW_DIRTY"); + IConsolePrint(CC_HELP, " 2: GDF_SHOW_WIDGET_DIRTY"); + IConsolePrint(CC_HELP, " 4: GDF_SHOW_RECT_DIRTY"); + return true; + } + + extern uint32 _gfx_debug_flags; + if (argc == 1) { + IConsolePrint(CC_DEFAULT, "Gfx debug flags: {:X}", _gfx_debug_flags); + } else { + _gfx_debug_flags = strtoul(argv[1], nullptr, 16); + } + + return true; +} + /******************************* * console command registration *******************************/ @@ -2671,4 +2691,6 @@ void IConsoleStdLibRegister() IConsole::CmdRegister("cmstartrecord", citymania::ConStartRecord); IConsole::CmdRegister("cmstoprecord", citymania::ConStopRecord); IConsole::CmdRegister("cmgamestats", citymania::ConGameStats); + + IConsole::CmdRegister("gfx_debug", ConGfxDebug); } diff --git a/src/core/math_func.hpp b/src/core/math_func.hpp index 8f9b5626c9..c22d51d42d 100644 --- a/src/core/math_func.hpp +++ b/src/core/math_func.hpp @@ -301,6 +301,30 @@ static inline int DivAwayFromZero(int a, uint b) } } +/** + * Computes a / b rounded towards negative infinity for b > 0. + * @param a Numerator + * @param b Denominator + * @return Quotient, rounded towards negative infinity + */ +template +static inline T DivTowardsNegativeInf(T a, T b) +{ + return (a / b) - (a % b < 0 ? 1 : 0); +} + +/** + * Computes a / b rounded towards positive infinity for b > 0. + * @param a Numerator + * @param b Denominator + * @return Quotient, rounded towards positive infinity + */ +template +static inline T DivTowardsPositiveInf(T a, T b) +{ + return (a / b) + (a % b > 0 ? 1 : 0); +} + uint32 IntSqrt(uint32 num); #endif /* MATH_FUNC_HPP */ diff --git a/src/gfx.cpp b/src/gfx.cpp index 2c2dceb517..e6765db622 100644 --- a/src/gfx.cpp +++ b/src/gfx.cpp @@ -21,6 +21,10 @@ #include "window_func.h" #include "newgrf_debug.h" #include "thread.h" +#include "widget_type.h" +#include "window_gui.h" +#include "framerate_type.h" +#include "transparency.h" #include "core/backup_type.hpp" #include "viewport_func.h" @@ -76,16 +80,23 @@ int _gui_scale_cfg; ///< GUI scale in config. * * @ingroup dirty */ -static Rect _invalid_rect; static const byte *_colour_remap_ptr; static byte _string_colourremap[3]; ///< Recoloursprite for stringdrawing. The grf loader ensures that #ST_FONT sprites only use colours 0 to 2. static const uint DIRTY_BLOCK_HEIGHT = 8; static const uint DIRTY_BLOCK_WIDTH = 64; -static uint _dirty_bytes_per_line = 0; -static byte *_dirty_blocks = nullptr; extern uint _dirty_block_colour; +static bool _whole_screen_dirty = false; +bool _gfx_draw_active = false; +static std::vector _dirty_blocks; +static std::vector _pending_dirty_blocks; +enum GfxDebugFlags { + GDF_SHOW_WINDOW_DIRTY, + GDF_SHOW_WIDGET_DIRTY, + GDF_SHOW_RECT_DIRTY, +}; +uint32 _gfx_debug_flags; void GfxScroll(int left, int top, int width, int height, int xo, int yo) { @@ -1510,12 +1521,7 @@ void GetBroadestDigit(uint *front, uint *next, FontSize size) void ScreenSizeChanged() { - _dirty_bytes_per_line = CeilDiv(_screen.width, DIRTY_BLOCK_WIDTH); - _dirty_blocks = ReallocT(_dirty_blocks, _dirty_bytes_per_line * CeilDiv(_screen.height, DIRTY_BLOCK_HEIGHT)); - - /* check the dirty rect */ - if (_invalid_rect.right >= _screen.width) _invalid_rect.right = _screen.width; - if (_invalid_rect.bottom >= _screen.height) _invalid_rect.bottom = _screen.height; + MarkWholeScreenDirty(); /* screen size changed and the old bitmap is invalid now, so we don't want to undraw it */ _cursor.visible = false; @@ -1631,6 +1637,72 @@ void RedrawScreenRect(int left, int top, int right, int bottom) VideoDriver::GetInstance()->MakeDirty(left, top, right - left, bottom - top); } +static std::vector _dirty_viewport_occlusions; +static const Viewport *_dirty_viewport; +static NWidgetDisplay _dirty_viewport_disp_flags; + +static void DrawDirtyViewport(uint occlusion, int left, int top, int right, int bottom) +{ + for(; occlusion < _dirty_viewport_occlusions.size(); occlusion++) { + const Rect &occ = _dirty_viewport_occlusions[occlusion]; + if (right > occ.left && + bottom > occ.top && + left < occ.right && + top < occ.bottom) { + /* occlusion and draw rectangle intersect with each other */ + int x; + + if (left < (x = occ.left)) { + DrawDirtyViewport(occlusion + 1, left, top, x, bottom); + DrawDirtyViewport(occlusion, x, top, right, bottom); + return; + } + + if (right > (x = occ.right)) { + DrawDirtyViewport(occlusion, left, top, x, bottom); + DrawDirtyViewport(occlusion + 1, x, top, right, bottom); + return; + } + + if (top < (x = occ.top)) { + DrawDirtyViewport(occlusion + 1, left, top, right, x); + DrawDirtyViewport(occlusion, left, x, right, bottom); + return; + } + + if (bottom > (x = occ.bottom)) { + DrawDirtyViewport(occlusion, left, top, right, x); + DrawDirtyViewport(occlusion + 1, left, x, right, bottom); + return; + } + + return; + } + } + + if (_game_mode == GM_MENU) { + RedrawScreenRect(left, top, right, bottom); + } else { + extern void ViewportDrawChk(const Viewport *vp, int left, int top, int right, int bottom); + ViewportDrawChk(_dirty_viewport, left, top, right, bottom); + + if (_dirty_viewport_disp_flags & (ND_SHADE_GREY | ND_SHADE_DIMMED)) { + GfxFillRect(left, top, right, bottom, + (_dirty_viewport_disp_flags & ND_SHADE_DIMMED) ? PALETTE_TO_TRANSPARENT : PALETTE_NEWSPAPER, FILLRECT_RECOLOUR); + } + VideoDriver::GetInstance()->MakeDirty(left, top, right - left, bottom - top); + } +} + +static void DrawOverlappedWindowWithClipping(Window *w, int left, int top, int right, int bottom, DrawOverlappedWindowFlags flags) +{ + extern void DrawOverlappedWindow(Window *w, int left, int top, int right, int bottom, DrawOverlappedWindowFlags flags); + + if (right < 0 || bottom < 0 || left >= _screen.width || top >= _screen.height) return; + + DrawOverlappedWindow(w, std::max(0, left), std::max(0, top), std::min(_screen.width, right), std::min(_screen.height, bottom), flags); +} + /** * Repaints the rectangle blocks which are marked as 'dirty'. * @@ -1640,79 +1712,309 @@ void RedrawScreenRect(int left, int top, int right, int bottom) */ void DrawDirtyBlocks() { - byte *b = _dirty_blocks; - const int w = Align(_screen.width, DIRTY_BLOCK_WIDTH); - const int h = Align(_screen.height, DIRTY_BLOCK_HEIGHT); - int x; - int y; + static std::vector dirty_widgets; - y = 0; - do { - x = 0; - do { - if (*b != 0) { - int left; - int top; - int right = x + DIRTY_BLOCK_WIDTH; - int bottom = y; - byte *p = b; - int h2; + _gfx_draw_active = true; - /* First try coalescing downwards */ - do { - *p = 0; - p += _dirty_bytes_per_line; - bottom += DIRTY_BLOCK_HEIGHT; - } while (bottom != h && *p != 0); + if (_whole_screen_dirty) { + RedrawScreenRect(0, 0, _screen.width, _screen.height); + for (Window *w : Window::IterateFromBack()) { + w->flags &= ~(WF_DIRTY | WF_WIDGETS_DIRTY | WF_DRAG_DIRTIED); + } + _whole_screen_dirty = false; + } else { + bool cleared_overlays = false; + auto clear_overlays = [&]() { + if (cleared_overlays) return; + if (_cursor.visible) UndrawMouseCursor(); + if (_networking) NetworkUndrawChatMessage(); + cleared_overlays = true; + }; - /* Try coalescing to the right too. */ - h2 = (bottom - y) / DIRTY_BLOCK_HEIGHT; - assert(h2 > 0); - p = b; + DrawPixelInfo bk; + Backup dpi_backup(_cur_dpi, &bk, FILE_LINE); - while (right != w) { - byte *p2 = ++p; - int h = h2; - /* Check if a full line of dirty flags is set. */ - do { - if (!*p2) goto no_more_coalesc; - p2 += _dirty_bytes_per_line; - } while (--h != 0); + for (Window *w : Window::IterateFromBack()) { + w->flags &= ~WF_DRAG_DIRTIED; + if (!MayBeShown(w)) continue; - /* Wohoo, can combine it one step to the right! - * Do that, and clear the bits. */ - right += DIRTY_BLOCK_WIDTH; + if (w->viewport != nullptr) w->viewport->is_drawn = false; - h = h2; - p2 = p; - do { - *p2 = 0; - p2 += _dirty_bytes_per_line; - } while (--h != 0); + if (w->flags & WF_DIRTY) { + clear_overlays(); + DrawOverlappedWindowFlags flags = DOWF_MARK_DIRTY; + if (unlikely(HasBit(_gfx_debug_flags, GDF_SHOW_WINDOW_DIRTY))) { + flags |= DOWF_SHOW_DEBUG; } - no_more_coalesc: - - left = x; - top = y; - - if (left < _invalid_rect.left ) left = _invalid_rect.left; - if (top < _invalid_rect.top ) top = _invalid_rect.top; - if (right > _invalid_rect.right ) right = _invalid_rect.right; - if (bottom > _invalid_rect.bottom) bottom = _invalid_rect.bottom; - - if (left < right && top < bottom) { - RedrawScreenRect(left, top, right, bottom); + DrawOverlappedWindowWithClipping(w, w->left, w->top, w->left + w->width, w->top + w->height, flags); + w->flags &= ~(WF_DIRTY | WF_WIDGETS_DIRTY); + } else if (w->flags & WF_WIDGETS_DIRTY) { + if (w->nested_root != nullptr) { + clear_overlays(); + w->nested_root->FillDirtyWidgets(dirty_widgets); + for (NWidgetBase *widget : dirty_widgets) { + DrawOverlappedWindowFlags flags = DOWF_MARK_DIRTY; + if (unlikely(HasBit(_gfx_debug_flags, GDF_SHOW_WIDGET_DIRTY))) { + flags |= DOWF_SHOW_DEBUG; + } + DrawOverlappedWindowWithClipping(w, w->left + widget->pos_x, w->top + widget->pos_y, w->left + widget->pos_x + widget->current_x, w->top + widget->pos_y + widget->current_y, flags); + } + dirty_widgets.clear(); } - + w->flags &= ~WF_WIDGETS_DIRTY; } - } while (b++, (x += DIRTY_BLOCK_WIDTH) != w); - } while (b += -(int)(w / DIRTY_BLOCK_WIDTH) + _dirty_bytes_per_line, (y += DIRTY_BLOCK_HEIGHT) != h); + if (w->viewport != nullptr && !w->IsShaded()) { + Viewport *vp = w->viewport; + if (vp->is_drawn) { + vp->ClearDirty(); + } else if (vp->is_dirty) { + clear_overlays(); + PerformanceAccumulator framerate(PFE_DRAWWORLD); + _cur_dpi->left = 0; + _cur_dpi->top = 0; + _cur_dpi->width = _screen.width; + _cur_dpi->height = _screen.height; + _cur_dpi->pitch = _screen.pitch; + _cur_dpi->dst_ptr = _screen.dst_ptr; + _cur_dpi->zoom = ZOOM_LVL_NORMAL; + + _dirty_viewport = vp; + _dirty_viewport_disp_flags = w->viewport_widget->disp_flags; + TransparencyOptionBits to_backup = _transparency_opt; + if (_dirty_viewport_disp_flags & ND_NO_TRANSPARENCY) { + _transparency_opt &= (1 << TO_SIGNS) | (1 << TO_LOADING); // Disable all transparency, except textual stuff + } + + { + int left = vp->left; + int top = vp->top; + int right = vp->left + vp->width; + int bottom = vp->top + vp->height; + _dirty_viewport_occlusions.clear(); + bool skip = true; + for (const Window *v : Window::IterateFromBack()) { + if (skip) { + if (v == w) skip = false; + continue; + } + if (MayBeShown(v) && + right > v->left && + bottom > v->top && + left < v->left + v->width && + top < v->top + v->height) { + _dirty_viewport_occlusions.push_back({ v->left, v->top, v->left + v->width, v->top + v->height }); + } + } + for (const Rect &r : _dirty_blocks) { + if (right > r.left && + bottom > r.top && + left < r.right && + top < r.bottom) { + _dirty_viewport_occlusions.push_back({ r.left, r.top, r.right, r.bottom }); + } + } + } + + const uint grid_w = vp->dirty_blocks_per_row; + const uint grid_h = vp->dirty_blocks_per_column; + + uint pos = 0; + uint x = 0; + do { + uint y = 0; + do { + if (vp->dirty_blocks[pos]) { + uint left = x; + uint top = y; + uint right = x + 1; + uint bottom = y; + uint p = pos; + + /* First try coalescing downwards */ + do { + vp->dirty_blocks[p] = false; + p++; + bottom++; + } while (bottom != grid_h && vp->dirty_blocks[p]); + + /* Try coalescing to the right too. */ + uint block_h = (bottom - y); + p = pos; + + while (right != grid_w) { + uint p2 = (p += grid_h); + uint check_h = block_h; + /* Check if a full line of dirty flags is set. */ + do { + if (!vp->dirty_blocks[p2]) goto no_more_coalesc; + p2++; + } while (--check_h != 0); + + /* Wohoo, can combine it one step to the right! + * Do that, and clear the bits. */ + right++; + + check_h = block_h; + p2 = p; + do { + vp->dirty_blocks[p2] = false; + p2++; + } while (--check_h != 0); + } + no_more_coalesc: + + assert(_cur_dpi == &bk); + int draw_left = std::max(0, ((left == 0) ? 0 : vp->dirty_block_left_margin + (left << vp->GetDirtyBlockWidthShift())) + vp->left); + int draw_top = std::max(0, (top << vp->GetDirtyBlockHeightShift()) + vp->top); + int draw_right = std::min(_screen.width, std::min((right << vp->GetDirtyBlockWidthShift()) + vp->dirty_block_left_margin, vp->width) + vp->left); + int draw_bottom = std::min(_screen.height, std::min(bottom << vp->GetDirtyBlockHeightShift(), vp->height) + vp->top); + if (draw_left < draw_right && draw_top < draw_bottom) { + DrawDirtyViewport(0, draw_left, draw_top, draw_right, draw_bottom); + } + } + } while (pos++, ++y != grid_h); + } while (++x != grid_w); + + _transparency_opt = to_backup; + w->viewport->ClearDirty(); + } + } + } + + dpi_backup.Restore(); + + for (const Rect &r : _dirty_blocks) { + RedrawScreenRect(r.left, r.top, r.right, r.bottom); + } + if (unlikely(HasBit(_gfx_debug_flags, GDF_SHOW_RECT_DIRTY))) { + for (const Rect &r : _dirty_blocks) { + GfxFillRect(r.left, r.top, r.right, r.bottom, _string_colourmap[++_dirty_block_colour & 0xF], FILLRECT_CHECKER); + } + } + } + + _dirty_blocks.clear(); + while (!_pending_dirty_blocks.empty()) { + for (const Rect &r : _pending_dirty_blocks) { + AddDirtyBlock(r.left, r.top, r.right, r.bottom); + } + _pending_dirty_blocks.clear(); + for (const Rect &r : _dirty_blocks) { + RedrawScreenRect(r.left, r.top, r.right, r.bottom); + } + _dirty_blocks.clear(); + } + _gfx_draw_active = false; ++_dirty_block_colour; - _invalid_rect.left = w; - _invalid_rect.top = h; - _invalid_rect.right = 0; - _invalid_rect.bottom = 0; +} + +void UnsetDirtyBlocks(int left, int top, int right, int bottom) +{ + if (_whole_screen_dirty) return; + + for (uint i = 0; i < _dirty_blocks.size(); i++) { + Rect &r = _dirty_blocks[i]; + if (left < r.right && + right > r.left && + top < r.bottom && + bottom > r.top) { + /* overlap of some sort */ + if (left <= r.left && + right >= r.right && + top <= r.top && + bottom >= r.bottom) { + /* dirty rect entirely in subtraction area */ + r = _dirty_blocks.back(); + _dirty_blocks.pop_back(); + i--; + continue; + } + if (r.left < left) { + Rect n = { left, r.top, r.right, r.bottom }; + r.right = left; + _dirty_blocks.push_back(n); + continue; + } + if (r.right > right) { + Rect n = { r.left, r.top, right, r.bottom }; + r.left = right; + _dirty_blocks.push_back(n); + continue; + } + if (r.top < top) { + Rect n = { r.left, top, r.right, r.bottom }; + r.bottom = top; + _dirty_blocks.push_back(n); + continue; + } + if (r.bottom > bottom) { + Rect n = { r.left, r.top, r.right, bottom }; + r.top = bottom; + _dirty_blocks.push_back(n); + continue; + } + } + } +} + +static void AddDirtyBlocks(uint start, int left, int top, int right, int bottom) +{ + if (bottom <= top || right <= left) return; + + for (; start < _dirty_blocks.size(); start++) { + Rect &r = _dirty_blocks[start]; + if (left <= r.right && + right >= r.left && + top <= r.bottom && + bottom >= r.top) { + /* overlap or contact of some sort */ + if (left >= r.left && + right <= r.right && + top >= r.top && + bottom <= r.bottom) { + /* entirely contained by existing */ + return; + } + if (left <= r.left && + right >= r.right && + top <= r.top && + bottom >= r.bottom) { + /* entirely contains existing */ + r = _dirty_blocks.back(); + _dirty_blocks.pop_back(); + start--; + continue; + } + if (left < r.left && right > r.left) { + int middle = r.left; + AddDirtyBlocks(start, left, top, middle, bottom); + AddDirtyBlocks(start, middle, top, right, bottom); + return; + } + if (right > r.right && left < r.right) { + int middle = r.right; + AddDirtyBlocks(start, left, top, middle, bottom); + AddDirtyBlocks(start, middle, top, right, bottom); + return; + } + + if (top < r.top && bottom > r.top) { + int middle = r.top; + AddDirtyBlocks(start, left, top, right, middle); + AddDirtyBlocks(start, left, middle, right, bottom); + return; + } + + if (bottom > r.bottom && top < r.bottom) { + int middle = r.bottom; + AddDirtyBlocks(start, left, top, right, middle); + AddDirtyBlocks(start, left, middle, right, bottom); + return; + } + } + } + _dirty_blocks.push_back({ left, top, right, bottom }); } /** @@ -1729,39 +2031,19 @@ void DrawDirtyBlocks() */ void AddDirtyBlock(int left, int top, int right, int bottom) { - byte *b; - int width; - int height; + if (_whole_screen_dirty) return; if (left < 0) left = 0; if (top < 0) top = 0; if (right > _screen.width) right = _screen.width; if (bottom > _screen.height) bottom = _screen.height; - if (left >= right || top >= bottom) return; + AddDirtyBlocks(0, left, top, right, bottom); +} - if (left < _invalid_rect.left ) _invalid_rect.left = left; - if (top < _invalid_rect.top ) _invalid_rect.top = top; - if (right > _invalid_rect.right ) _invalid_rect.right = right; - if (bottom > _invalid_rect.bottom) _invalid_rect.bottom = bottom; - - left /= DIRTY_BLOCK_WIDTH; - top /= DIRTY_BLOCK_HEIGHT; - - b = _dirty_blocks + top * _dirty_bytes_per_line + left; - - width = ((right - 1) / DIRTY_BLOCK_WIDTH) - left + 1; - height = ((bottom - 1) / DIRTY_BLOCK_HEIGHT) - top + 1; - - assert(width > 0 && height > 0); - - do { - int i = width; - - do b[--i] = 0xFF; while (i != 0); - - b += _dirty_bytes_per_line; - } while (--height != 0); +void AddPendingDirtyBlocks(int left, int top, int right, int bottom) +{ + _pending_dirty_blocks.push_back({ left, top, right, bottom }); } /** @@ -1772,7 +2054,7 @@ void AddDirtyBlock(int left, int top, int right, int bottom) */ void MarkWholeScreenDirty() { - AddDirtyBlock(0, 0, _screen.width, _screen.height); + _whole_screen_dirty = true; citymania::SetClientListDirty(); } diff --git a/src/gfx_func.h b/src/gfx_func.h index fa9f3103c2..30e96aad0e 100644 --- a/src/gfx_func.h +++ b/src/gfx_func.h @@ -21,7 +21,7 @@ * (this is btw. also possible if needed). This is used to avoid a * flickering of the screen by the video driver constantly repainting it. * - * This whole mechanism is controlled by an rectangle defined in #_invalid_rect. This + * This whole mechanism was controlled by an rectangle defined in #_invalid_rect. This * rectangle defines the area on the screen which must be repaint. If a new object * needs to be repainted this rectangle is extended to 'catch' the object on the * screen. At some point (which is normally uninteresting for patch writers) this @@ -32,7 +32,6 @@ * rectangle information. Then a new round begins by marking objects "dirty". * * @see VideoDriver::MakeDirty - * @see _invalid_rect * @see _screen */ @@ -161,6 +160,8 @@ const char *GetCharAtPosition(const char *str, int x, FontSize start_fontsize = void DrawDirtyBlocks(); void AddDirtyBlock(int left, int top, int right, int bottom); +void AddPendingDirtyBlocks(int left, int top, int right, int bottom); +void UnsetDirtyBlocks(int left, int top, int right, int bottom); void MarkWholeScreenDirty(); bool CopyPalette(Palette &local_palette, bool force_copy = false); @@ -182,6 +183,12 @@ static inline int CenterBounds(int min, int max, int size) } /* window.cpp */ +enum DrawOverlappedWindowFlags { + DOWF_NONE = 0, + DOWF_MARK_DIRTY = 1 << 0, + DOWF_SHOW_DEBUG = 1 << 1, +}; +DECLARE_ENUM_AS_BIT_SET(DrawOverlappedWindowFlags) void DrawOverlappedWindowForAll(int left, int top, int right, int bottom); void SetMouseCursorBusy(bool busy); diff --git a/src/main_gui.cpp b/src/main_gui.cpp index d02193db7e..42a52bc8bc 100644 --- a/src/main_gui.cpp +++ b/src/main_gui.cpp @@ -133,6 +133,7 @@ bool DoZoomInOutWindow(ZoomStateChange how, Window *w) if (vp != nullptr) { // the vp can be null when how == ZOOM_NONE vp->virtual_left = w->viewport->scrollpos_x; vp->virtual_top = w->viewport->scrollpos_y; + UpdateViewportSizeZoom(vp); } /* Update the windows that have zoom-buttons to perhaps disable their buttons */ w->InvalidateData(); @@ -176,6 +177,7 @@ void FixTitleGameZoom(int zoom_adjust) vp->virtual_width = ScaleByZoom(vp->width, vp->zoom); vp->virtual_height = ScaleByZoom(vp->height, vp->zoom); + UpdateViewportSizeZoom(vp); } static const struct NWidgetPart _nested_main_window_widgets[] = { @@ -599,5 +601,4 @@ void GameSizeChanged() _cur_resolution.height = _screen.height; ScreenSizeChanged(); RelocateAllWindows(_screen.width, _screen.height); - MarkWholeScreenDirty(); } diff --git a/src/network/network_gui.cpp b/src/network/network_gui.cpp index 1afcdfb66a..6bb6793c42 100644 --- a/src/network/network_gui.cpp +++ b/src/network/network_gui.cpp @@ -210,6 +210,19 @@ public: return nullptr; } + void FillDirtyWidgets(std::vector &dirty_widgets) override + { + if (this->base_flags & WBF_DIRTY) { + dirty_widgets.push_back(this); + } else { + int i = 0; + for (NWidgetBase *child_wid = this->head; child_wid != nullptr; child_wid = child_wid->next) { + if (!this->visible[i++]) continue; + child_wid->FillDirtyWidgets(dirty_widgets); + } + } + } + /** * Checks whether the given widget is actually visible. * @param widget the widget to check for visibility diff --git a/src/newgrf_gui.cpp b/src/newgrf_gui.cpp index 7b6bb4ec79..daa51712e9 100644 --- a/src/newgrf_gui.cpp +++ b/src/newgrf_gui.cpp @@ -1782,6 +1782,17 @@ public: this->acs->Draw(w); this->inf->Draw(w); } + + void FillDirtyWidgets(std::vector &dirty_widgets) override + { + if (this->base_flags & WBF_DIRTY) { + dirty_widgets.push_back(this); + } else { + if (this->editable) this->avs->FillDirtyWidgets(dirty_widgets); + this->acs->FillDirtyWidgets(dirty_widgets); + this->inf->FillDirtyWidgets(dirty_widgets); + } + } }; const uint NWidgetNewGRFDisplay::INTER_COLUMN_SPACING = 12; diff --git a/src/screenshot.cpp b/src/screenshot.cpp index bec7cc7c6b..22d7103539 100644 --- a/src/screenshot.cpp +++ b/src/screenshot.cpp @@ -797,6 +797,7 @@ void SetupScreenshotViewport(ScreenshotType t, Viewport *vp, uint32 width, uint3 break; } } + UpdateViewportSizeZoom(vp); } /** diff --git a/src/smallmap_gui.cpp b/src/smallmap_gui.cpp index ffdd71fe75..4e0a50e519 100644 --- a/src/smallmap_gui.cpp +++ b/src/smallmap_gui.cpp @@ -1829,6 +1829,15 @@ public: { for (NWidgetBase *child_wid = this->head; child_wid != nullptr; child_wid = child_wid->next) child_wid->Draw(w); } + + void FillDirtyWidgets(std::vector &dirty_widgets) override + { + if (this->base_flags & WBF_DIRTY) { + dirty_widgets.push_back(this); + } else { + for (NWidgetBase *child_wid = this->head; child_wid != nullptr; child_wid = child_wid->next) child_wid->FillDirtyWidgets(dirty_widgets); + } + } }; /** Widget parts of the smallmap display. */ diff --git a/src/station_gui.cpp b/src/station_gui.cpp index aa4762e9d9..f3eed4bbda 100644 --- a/src/station_gui.cpp +++ b/src/station_gui.cpp @@ -152,7 +152,7 @@ static void FindStationsAroundSelection() * If it is needed actually make the window for redrawing. * @param w the window to check. */ -void CheckRedrawStationCoverage(const Window *w) +void CheckRedrawStationCoverage(Window *w) { /* CityMania code begin */ if (citymania::UseImprovedStationJoin()) { diff --git a/src/station_gui.h b/src/station_gui.h index 47bca019f6..1a486dafa6 100644 --- a/src/station_gui.h +++ b/src/station_gui.h @@ -26,7 +26,7 @@ enum StationCoverageType { int DrawStationCoverageAreaText(int left, int right, int top, StationCoverageType sct, int rad, bool supplies); int DrawStationAuthorityText(int left, int right, int top); -void CheckRedrawStationCoverage(const Window *w); +void CheckRedrawStationCoverage(Window *w); using StationPickerCmdProc = std::function; diff --git a/src/toolbar_gui.cpp b/src/toolbar_gui.cpp index afa24d3e51..d0ca4380a6 100644 --- a/src/toolbar_gui.cpp +++ b/src/toolbar_gui.cpp @@ -1578,6 +1578,21 @@ public: return nullptr; } + + void FillDirtyWidgets(std::vector &dirty_widgets) override + { + if (this->base_flags & WBF_DIRTY) { + dirty_widgets.push_back(this); + } else { + for (NWidgetBase *child_wid = this->head; child_wid != nullptr; child_wid = child_wid->next) { + if (child_wid->type == NWID_SPACER) continue; + if (!this->visible[((NWidgetCore*)child_wid)->index]) continue; + + child_wid->FillDirtyWidgets(dirty_widgets); + } + } + } + /** * Get the arrangement of the buttons for the toolbar. * @param width the new width of the toolbar. diff --git a/src/viewport.cpp b/src/viewport.cpp index 9995d08531..16d47f6ecc 100644 --- a/src/viewport.cpp +++ b/src/viewport.cpp @@ -214,7 +214,7 @@ struct ViewportDrawer { citymania::TileHighlight cm_highlight; }; -static bool MarkViewportDirty(const Viewport *vp, int left, int top, int right, int bottom); +static bool MarkViewportDirty(Viewport *vp, int left, int top, int right, int bottom); static ViewportDrawer _vd; @@ -246,7 +246,7 @@ void DeleteWindowViewport(Window *w) if (w->viewport == nullptr) return; delete w->viewport->overlay; - free(w->viewport); + delete w->viewport; w->viewport = nullptr; } @@ -267,8 +267,9 @@ void InitializeWindowViewport(Window *w, int x, int y, { assert(w->viewport == nullptr); - ViewportData *vp = CallocT(1); + ViewportData *vp = new ViewportData(); + vp->overlay = nullptr; vp->left = x + w->left; vp->top = y + w->top; vp->width = width; @@ -276,9 +277,13 @@ void InitializeWindowViewport(Window *w, int x, int y, vp->zoom = static_cast(Clamp(zoom, _settings_client.gui.zoom_min, _settings_client.gui.zoom_max)); + vp->virtual_left = 0; + vp->virtual_top = 0; vp->virtual_width = ScaleByZoom(width, zoom); vp->virtual_height = ScaleByZoom(height, zoom); + UpdateViewportSizeZoom(vp); + Point pt; if (follow_flags & 0x80000000) { @@ -303,8 +308,6 @@ void InitializeWindowViewport(Window *w, int x, int y, vp->overlay = nullptr; w->viewport = vp; - vp->virtual_left = 0; // pt.x; - vp->virtual_top = 0; // pt.y; } static Point _vp_move_offs; @@ -375,6 +378,15 @@ static void DoSetViewportPosition(Window::IteratorToFront it, int left, int top, } } +inline void UpdateViewportDirtyBlockLeftMargin(Viewport *vp) +{ + // if (vp->zoom >= ZOOM_LVL_DRAW_MAP) { + // vp->dirty_block_left_margin = 0; + // } else { + vp->dirty_block_left_margin = UnScaleByZoomLower((-vp->virtual_left) & 127, vp->zoom); + // } +} + static void SetViewportPosition(Window *w, int x, int y) { Viewport *vp = w->viewport; @@ -385,6 +397,7 @@ static void SetViewportPosition(Window *w, int x, int y) vp->virtual_left = x; vp->virtual_top = y; + UpdateViewportDirtyBlockLeftMargin(vp); /* Viewport is bound to its left top corner, so it must be rounded down (UnScaleByZoomLower) * else glitch described in FS#1412 will happen (offset by 1 pixel with zoom level > NORMAL) @@ -1234,8 +1247,8 @@ static void ViewportAddLandscape() * - Right column is column of upper_right (rounded up) and one column to the right. * Note: Integer-division does not round down for negative numbers, so ensure rounding with another increment/decrement. */ - int left_column = (upper_left.y - upper_left.x) / (int)TILE_SIZE - 2; - int right_column = (upper_right.y - upper_right.x) / (int)TILE_SIZE + 2; + int left_column = DivTowardsNegativeInf(upper_left.y - upper_left.x, (int)TILE_SIZE) - 1; + int right_column = DivTowardsPositiveInf(upper_right.y - upper_right.x, (int)TILE_SIZE) + 1; int potential_bridge_height = ZOOM_LVL_BASE * TILE_HEIGHT * _settings_game.construction.max_bridge_height; @@ -1243,7 +1256,7 @@ static void ViewportAddLandscape() * The first row that could possibly be visible is the row above upper_left (if it is at height 0). * Due to integer-division not rounding down for negative numbers, we need another decrement. */ - int row = (upper_left.x + upper_left.y) / (int)TILE_SIZE - 2; + int row = DivTowardsNegativeInf(upper_left.y + upper_left.x, (int)TILE_SIZE) - 1; bool last_row = false; for (; !last_row; row++) { last_row = true; @@ -1717,7 +1730,7 @@ static void ViewportDrawBoundingBoxes(const ParentSpriteToSortVector *psd) /** * Draw/colour the blocks that have been redrawn. */ -static void ViewportDrawDirtyBlocks() +void ViewportDrawDirtyBlocks() { Blitter *blitter = BlitterFactory::GetCurrentBlitter(); const DrawPixelInfo *dpi = _cur_dpi; @@ -1789,8 +1802,8 @@ void ViewportDoDraw(const Viewport *vp, int left, int top, int right, int bottom _vd.dpi.pitch = old_dpi->pitch; _vd.last_child = nullptr; - int x = UnScaleByZoom(_vd.dpi.left - (vp->virtual_left & mask), vp->zoom) + vp->left; - int y = UnScaleByZoom(_vd.dpi.top - (vp->virtual_top & mask), vp->zoom) + vp->top; + int x = UnScaleByZoomLower(_vd.dpi.left - (vp->virtual_left & mask), vp->zoom) + vp->left; + int y = UnScaleByZoomLower(_vd.dpi.top - (vp->virtual_top & mask), vp->zoom) + vp->top; _vd.dpi.dst_ptr = BlitterFactory::GetCurrentBlitter()->MoveTo(old_dpi->dst_ptr, x - old_dpi->left, y - old_dpi->top); @@ -1845,7 +1858,16 @@ void ViewportDoDraw(const Viewport *vp, int left, int top, int right, int bottom _vd.child_screen_sprites_to_draw.clear(); } -static inline void ViewportDraw(const Viewport *vp, int left, int top, int right, int bottom) +void ViewportDrawChk(const Viewport *vp, int left, int top, int right, int bottom) { + ViewportDoDraw(vp, + ScaleByZoom(left - vp->left, vp->zoom) + vp->virtual_left, + ScaleByZoom(top - vp->top, vp->zoom) + vp->virtual_top, + ScaleByZoom(right - vp->left, vp->zoom) + vp->virtual_left, + ScaleByZoom(bottom - vp->top, vp->zoom) + vp->virtual_top + ); +} + +static inline void ViewportDraw(Viewport *vp, int left, int top, int right, int bottom) { if (right <= vp->left || bottom <= vp->top) return; @@ -1859,6 +1881,8 @@ static inline void ViewportDraw(const Viewport *vp, int left, int top, int right if (top < vp->top) top = vp->top; if (bottom > vp->top + vp->height) bottom = vp->top + vp->height; + vp->is_drawn = true; + ViewportDoDraw(vp, ScaleByZoom(left - vp->left, vp->zoom) + vp->virtual_left, ScaleByZoom(top - vp->top, vp->zoom) + vp->virtual_top, @@ -1959,6 +1983,32 @@ void UpdateViewportPosition(Window *w) } } +void UpdateViewportSizeZoom(Viewport *vp) +{ + vp->dirty_blocks_per_column = CeilDiv(vp->height, vp->GetDirtyBlockHeight()); + vp->dirty_blocks_per_row = CeilDiv(vp->width, vp->GetDirtyBlockWidth()); + uint size = vp->dirty_blocks_per_row * vp->dirty_blocks_per_column; + vp->dirty_blocks.assign(size, false); + UpdateViewportDirtyBlockLeftMargin(vp); + + // if (vp->zoom >= ZOOM_LVL_DRAW_MAP) { + // memset(vp->map_draw_vehicles_cache.done_hash_bits, 0, sizeof(vp->map_draw_vehicles_cache.done_hash_bits)); + // vp->map_draw_vehicles_cache.vehicle_pixels.assign(vp->ScreenArea(), false); + + // if (BlitterFactory::GetCurrentBlitter()->GetScreenDepth() == 32) { + // vp->land_pixel_cache.assign(vp->ScreenArea() * 4, 0xD7); + // } else { + // vp->land_pixel_cache.assign(vp->ScreenArea(), 0xD7); + // } + // } else { + // vp->map_draw_vehicles_cache.vehicle_pixels.clear(); + // vp->land_pixel_cache.clear(); + // vp->land_pixel_cache.shrink_to_fit(); + // } + // vp->update_vehicles = true; + // FillViewportCoverageRect(); +} + /** * Marks a viewport as dirty for repaint if it displays (a part of) the area the needs to be repainted. * @param vp The viewport to mark as dirty @@ -1969,7 +2019,7 @@ void UpdateViewportPosition(Window *w) * @return true if the viewport contains a dirty block * @ingroup dirty */ -static bool MarkViewportDirty(const Viewport *vp, int left, int top, int right, int bottom) +static bool MarkViewportDirty(Viewport *vp, int left, int top, int right, int bottom) { /* Rounding wrt. zoom-out level */ right += (1 << vp->zoom) - 1; @@ -1989,12 +2039,40 @@ static bool MarkViewportDirty(const Viewport *vp, int left, int top, int right, if (top >= vp->virtual_height) return false; - AddDirtyBlock( - UnScaleByZoomLower(left, vp->zoom) + vp->left, - UnScaleByZoomLower(top, vp->zoom) + vp->top, - UnScaleByZoom(right, vp->zoom) + vp->left + 1, - UnScaleByZoom(bottom, vp->zoom) + vp->top + 1 - ); + uint x = std::max(0, UnScaleByZoomLower(left, vp->zoom) - vp->dirty_block_left_margin) >> vp->GetDirtyBlockWidthShift(); + uint y = UnScaleByZoomLower(top, vp->zoom) >> vp->GetDirtyBlockHeightShift(); + uint w = (std::max(0, UnScaleByZoomLower(right, vp->zoom) - 1 - vp->dirty_block_left_margin) >> vp->GetDirtyBlockWidthShift()) + 1 - x; + uint h = ((UnScaleByZoom(bottom, vp->zoom) - 1) >> vp->GetDirtyBlockHeightShift()) + 1 - y; + + // TODO somehow not needed in jgrpp + assert(x < vp->dirty_blocks_per_row); + assert(y < vp->dirty_blocks_per_column); + h -= std::max((int)y + (int)h - (int)vp->dirty_blocks_per_column, 0); + w -= std::max((int)x + (int)w - (int)vp->dirty_blocks_per_row, 0); + + uint column_skip = vp->dirty_blocks_per_column - h; + uint pos = (x * vp->dirty_blocks_per_column) + y; + for (uint i = 0; i < w; i++) { + for (uint j = 0; j < h; j++) { + vp->dirty_blocks[pos] = true; + pos++; + } + pos += column_skip; + } + vp->is_dirty = true; + + /*if (unlikely(vp->zoom >= ZOOM_LVL_DRAW_MAP && !(flags & VMDF_NOT_LANDSCAPE))) { + uint l = UnScaleByZoomLower(left, vp->zoom); + uint t = UnScaleByZoomLower(top, vp->zoom); + uint w = UnScaleByZoom(right, vp->zoom) - l; + uint h = UnScaleByZoom(bottom, vp->zoom) - t; + uint bitdepth = BlitterFactory::GetCurrentBlitter()->GetScreenDepth() / 8; + uint8 *land_cache = vp->land_pixel_cache.data() + ((l + (t * vp->width)) * bitdepth); + while (--h) { + memset(land_cache, 0xD7, (size_t)w * bitdepth); + land_cache += vp->width * bitdepth; + } + }*/ return true; } @@ -2012,7 +2090,7 @@ bool MarkAllViewportsDirty(int left, int top, int right, int bottom) { bool dirty = false; - for (const Window *w : Window::Iterate()) { + for (Window *w : Window::Iterate()) { Viewport *vp = w->viewport; if (vp != nullptr) { assert(vp->width != 0); diff --git a/src/viewport_func.h b/src/viewport_func.h index 308117ce25..f58ea0b951 100644 --- a/src/viewport_func.h +++ b/src/viewport_func.h @@ -26,6 +26,7 @@ Viewport *IsPtInWindowViewport(const Window *w, int x, int y); Point TranslateXYToTileCoord(const Viewport *vp, int x, int y, bool clamp_to_map = true); Point GetTileBelowCursor(); void UpdateViewportPosition(Window *w); +void UpdateViewportSizeZoom(Viewport *vp); bool MarkAllViewportsDirty(int left, int top, int right, int bottom); diff --git a/src/viewport_type.h b/src/viewport_type.h index 73d3747018..07859340d0 100644 --- a/src/viewport_type.h +++ b/src/viewport_type.h @@ -14,6 +14,8 @@ #include "strings_type.h" #include "table/strings.h" +#include + class LinkGraphOverlay; /** @@ -32,6 +34,35 @@ struct Viewport { ZoomLevel zoom; ///< The zoom level of the viewport. LinkGraphOverlay *overlay; + + std::vector dirty_blocks; + uint dirty_blocks_per_column; + uint dirty_blocks_per_row; + uint8 dirty_block_left_margin; + bool is_dirty = false; + bool is_drawn = false; + + uint GetDirtyBlockWidthShift() const { return this->GetDirtyBlockShift(); } + uint GetDirtyBlockHeightShift() const { return this->GetDirtyBlockShift(); } + uint GetDirtyBlockWidth() const { return 1 << this->GetDirtyBlockWidthShift(); } + uint GetDirtyBlockHeight() const { return 1 << this->GetDirtyBlockHeightShift(); } + + void ClearDirty() + { + if (this->is_dirty) { + this->dirty_blocks.assign(this->dirty_blocks.size(), false); + this->is_dirty = false; + } + this->is_drawn = false; + } + +private: + uint GetDirtyBlockShift() const + { + // if (this->zoom >= ZOOM_LVL_DRAW_MAP) return 3; + if (this->zoom >= ZOOM_LVL_OUT_8X) return 4; + return 7 - this->zoom; + } }; /** Location information about a sign as seen on the viewport */ diff --git a/src/widget.cpp b/src/widget.cpp index 57baa0c031..33e0c2bd9d 100644 --- a/src/widget.cpp +++ b/src/widget.cpp @@ -1022,11 +1022,10 @@ NWidgetBase::NWidgetBase(WidgetType tp) : ZeroedMemoryAllocator() * Mark the widget as 'dirty' (in need of repaint). * @param w Window owning the widget. */ -void NWidgetBase::SetDirty(const Window *w) const +void NWidgetBase::SetDirty(Window *w) { - int abs_left = w->left + this->pos_x; - int abs_top = w->top + this->pos_y; - AddDirtyBlock(abs_left, abs_top, abs_left + this->current_x, abs_top + this->current_y); + this->base_flags |= WBF_DIRTY; + w->flags |= WF_DIRTY; } /** @@ -1217,6 +1216,11 @@ NWidgetCore *NWidgetCore::GetWidgetFromPos(int x, int y) return (IsInsideBS(x, this->pos_x, this->current_x) && IsInsideBS(y, this->pos_y, this->current_y)) ? this : nullptr; } +void NWidgetCore::FillDirtyWidgets(std::vector &dirty_widgets) +{ + if (this->base_flags & WBF_DIRTY) dirty_widgets.push_back(this); +} + /** * Constructor container baseclass. * @param tp Type of the container. @@ -1376,6 +1380,8 @@ void NWidgetStacked::FillNestedArray(NWidgetBase **array, uint length) void NWidgetStacked::Draw(const Window *w) { + if (this->IsOutsideDrawArea()) return; + this->base_flags &= ~WBF_DIRTY; if (this->shown_plane >= SZSP_BEGIN) return; int plane = 0; @@ -1403,6 +1409,21 @@ NWidgetCore *NWidgetStacked::GetWidgetFromPos(int x, int y) return nullptr; } +void NWidgetStacked::FillDirtyWidgets(std::vector &dirty_widgets) +{ + if (this->base_flags & WBF_DIRTY) { + dirty_widgets.push_back(this); + } else { + int plane = 0; + for (NWidgetBase *child_wid = this->head; child_wid != nullptr; plane++, child_wid = child_wid->next) { + if (plane == this->shown_plane) { + child_wid->FillDirtyWidgets(dirty_widgets); + return; + } + } + } +} + /** * Select which plane to show (for #NWID_SELECTION only). * @param plane Plane number to display. @@ -1447,6 +1468,8 @@ void NWidgetPIPContainer::SetPIP(uint8 pip_pre, uint8 pip_inter, uint8 pip_post) void NWidgetPIPContainer::Draw(const Window *w) { + if (this->IsOutsideDrawArea()) return; + this->base_flags &= ~WBF_DIRTY; for (NWidgetBase *child_wid = this->head; child_wid != nullptr; child_wid = child_wid->next) { child_wid->Draw(w); } @@ -1463,6 +1486,17 @@ NWidgetCore *NWidgetPIPContainer::GetWidgetFromPos(int x, int y) return nullptr; } +void NWidgetPIPContainer::FillDirtyWidgets(std::vector &dirty_widgets) +{ + if (this->base_flags & WBF_DIRTY) { + dirty_widgets.push_back(this); + } else { + for (NWidgetBase *child_wid = this->head; child_wid != nullptr; child_wid = child_wid->next) { + child_wid->FillDirtyWidgets(dirty_widgets); + } + } +} + /** Horizontal container widget. */ NWidgetHorizontal::NWidgetHorizontal(NWidContainerFlags flags) : NWidgetPIPContainer(NWID_HORIZONTAL, flags) { @@ -1834,7 +1868,7 @@ void NWidgetSpacer::Draw(const Window *w) /* Spacer widget is never visible. */ } -void NWidgetSpacer::SetDirty(const Window *w) const +void NWidgetSpacer::SetDirty(Window *w) { /* Spacer widget never need repainting. */ } @@ -1844,6 +1878,11 @@ NWidgetCore *NWidgetSpacer::GetWidgetFromPos(int x, int y) return nullptr; } +void NWidgetSpacer::FillDirtyWidgets(std::vector &dirty_widgets) +{ + /* Spacer widget never need repainting. */ +} + NWidgetMatrix::NWidgetMatrix() : NWidgetPIPContainer(NWID_MATRIX, NC_EQUALSIZE), index(-1), clicked(-1), count(-1) { } @@ -2001,8 +2040,17 @@ NWidgetCore *NWidgetMatrix::GetWidgetFromPos(int x, int y) return child->GetWidgetFromPos(x, y); } +void NWidgetMatrix::FillDirtyWidgets(std::vector &dirty_widgets) +{ + if (this->base_flags & WBF_DIRTY) { + dirty_widgets.push_back(this); + } +} + /* virtual */ void NWidgetMatrix::Draw(const Window *w) { + if (this->IsOutsideDrawArea()) return; + this->base_flags &= ~WBF_DIRTY; /* Fill the background. */ GfxFillRect(this->pos_x, this->pos_y, this->pos_x + this->current_x - 1, this->pos_y + this->current_y - 1, _colour_gradient[this->colour & 0xF][5]); @@ -2226,6 +2274,8 @@ void NWidgetBackground::FillNestedArray(NWidgetBase **array, uint length) void NWidgetBackground::Draw(const Window *w) { + if (this->IsOutsideDrawArea()) return; + this->base_flags &= ~WBF_DIRTY; if (this->current_x == 0 || this->current_y == 0) return; Rect r = this->GetCurrentRect(); @@ -2271,6 +2321,15 @@ NWidgetCore *NWidgetBackground::GetWidgetFromPos(int x, int y) return nwid; } +void NWidgetBackground::FillDirtyWidgets(std::vector &dirty_widgets) +{ + if (this->base_flags & WBF_DIRTY) { + dirty_widgets.push_back(this); + } else { + if (this->child != nullptr) this->child->FillDirtyWidgets(dirty_widgets); + } +} + NWidgetBase *NWidgetBackground::GetWidgetOfType(WidgetType tp) { NWidgetBase *nwid = nullptr; @@ -2296,7 +2355,9 @@ void NWidgetViewport::SetupSmallestSize(Window *w, bool init_array) void NWidgetViewport::Draw(const Window *w) { + this->base_flags &= ~WBF_DIRTY; if (this->current_x == 0 || this->current_y == 0) return; + if (this->IsOutsideDrawArea()) return; if (this->disp_flags & ND_NO_TRANSPARENCY) { TransparencyOptionBits to_backup = _transparency_opt; @@ -2323,6 +2384,7 @@ void NWidgetViewport::Draw(const Window *w) void NWidgetViewport::InitializeViewport(Window *w, uint32 follow_flags, ZoomLevel zoom) { InitializeWindowViewport(w, this->pos_x, this->pos_y, this->current_x, this->current_y, follow_flags, zoom); + w->viewport_widget = this; } /** @@ -2340,6 +2402,7 @@ void NWidgetViewport::UpdateViewportCoordinates(Window *w) vp->virtual_width = ScaleByZoom(vp->width, vp->zoom); vp->virtual_height = ScaleByZoom(vp->height, vp->zoom); + UpdateViewportSizeZoom(vp); } } @@ -2490,6 +2553,8 @@ void NWidgetScrollbar::SetupSmallestSize(Window *w, bool init_array) void NWidgetScrollbar::Draw(const Window *w) { + if (this->IsOutsideDrawArea()) return; + this->base_flags &= ~WBF_DIRTY; if (this->current_x == 0 || this->current_y == 0) return; Rect r = this->GetCurrentRect(); @@ -2838,6 +2903,8 @@ void NWidgetLeaf::SetupSmallestSize(Window *w, bool init_array) void NWidgetLeaf::Draw(const Window *w) { + if (this->IsOutsideDrawArea()) return; + this->base_flags &= ~WBF_DIRTY; if (this->current_x == 0 || this->current_y == 0) return; /* Setup a clipping rectangle... for WWT_EMPTY or WWT_TEXT, an extra scaled pixel is allowed vertically in case text shadow encroaches. */ diff --git a/src/widget_type.h b/src/widget_type.h index 8c05a321f7..d91a4b7933 100644 --- a/src/widget_type.h +++ b/src/widget_type.h @@ -17,6 +17,8 @@ #include "gfx_type.h" #include "window_type.h" +#include + static const int WIDGET_LIST_END = -1; ///< indicate the end of widgets' list for vararg functions /** Bits of the #WWT_MATRIX widget data. */ @@ -41,7 +43,7 @@ enum ArrowWidgetValues { /** * Window widget types, nested widget types, and nested widget part types. */ -enum WidgetType { +enum WidgetType : uint8 { /* Window widget types. */ WWT_EMPTY, ///< Empty widget, place holder to reserve space in widget array @@ -107,6 +109,14 @@ enum WidgetType { NWID_PUSHBUTTON_DROPDOWN = NWID_BUTTON_DROPDOWN | WWB_PUSHBUTTON, }; +/** + * Base widget flags. + */ +enum WidgetBaseFlags : uint8 { + WBF_DIRTY = 1 << 0, ///< Widget is dirty. +}; +DECLARE_ENUM_AS_BIT_SET(WidgetBaseFlags) + /** Different forms of sizing nested widgets, using NWidgetBase::AssignSizePosition() */ enum SizingType { ST_SMALLEST, ///< Initialize nested widget tree to smallest size. Also updates \e current_x and \e current_y. @@ -170,7 +180,8 @@ public: inline uint GetVerticalStepSize(SizingType sizing) const; virtual void Draw(const Window *w) = 0; - virtual void SetDirty(const Window *w) const; + virtual void FillDirtyWidgets(std::vector &dirty_widgets) = 0; + virtual void SetDirty(Window *w); Rect GetCurrentRect() const { @@ -183,6 +194,7 @@ public: } WidgetType type; ///< Type of the widget / nested widget. + WidgetBaseFlags base_flags; ///< Widget base flags uint fill_x; ///< Horizontal fill stepsize (from initial size, \c 0 means not resizable). uint fill_y; ///< Vertical fill stepsize (from initial size, \c 0 means not resizable). uint resize_x; ///< Horizontal resize step (\c 0 means not resizable). @@ -205,6 +217,14 @@ public: RectPadding padding; ///< Padding added to the widget. Managed by parent container widget. (parent container may swap left and right for RTL) RectPadding uz_padding; ///< Unscaled padding, for resize calculation. + inline bool IsOutsideDrawArea() const + { + extern DrawPixelInfo *_cur_dpi; + if ((int)(this->pos_x + this->current_x) <= _cur_dpi->left || (int)(this->pos_x) >= _cur_dpi->left + _cur_dpi->width) return true; + if ((int)(this->pos_y + this->current_y) <= _cur_dpi->top || (int)(this->pos_y) >= _cur_dpi->top + _cur_dpi->height) return true; + return false; + } + protected: inline void StoreSizePosition(SizingType sizing, uint x, uint y, uint given_width, uint given_height); }; @@ -331,6 +351,7 @@ public: bool IsHighlighted() const override; TextColour GetHighlightColour() const override; void SetHighlighted(TextColour highlight_colour) override; + void FillDirtyWidgets(std::vector &dirty_widgets) override; NWidgetDisplay disp_flags; ///< Flags that affect display and interaction with the widget. Colours colour; ///< Colour of this widget. @@ -451,6 +472,7 @@ public: void Draw(const Window *w) override; NWidgetCore *GetWidgetFromPos(int x, int y) override; + void FillDirtyWidgets(std::vector &dirty_widgets) override; void SetDisplayedPlane(int plane); @@ -479,6 +501,7 @@ public: void Draw(const Window *w) override; NWidgetCore *GetWidgetFromPos(int x, int y) override; + void FillDirtyWidgets(std::vector &dirty_widgets) override; protected: NWidContainerFlags flags; ///< Flags of the container. @@ -549,6 +572,7 @@ public: void FillNestedArray(NWidgetBase **array, uint length) override; NWidgetCore *GetWidgetFromPos(int x, int y) override; + void FillDirtyWidgets(std::vector &dirty_widgets) override; void Draw(const Window *w) override; protected: int index; ///< If non-negative, index in the #Window::nested_array. @@ -578,8 +602,9 @@ public: void FillNestedArray(NWidgetBase **array, uint length) override; void Draw(const Window *w) override; - void SetDirty(const Window *w) const override; + void SetDirty(Window *w) override; NWidgetCore *GetWidgetFromPos(int x, int y) override; + void FillDirtyWidgets(std::vector &dirty_widgets) override; }; /** @@ -603,6 +628,7 @@ public: void Draw(const Window *w) override; NWidgetCore *GetWidgetFromPos(int x, int y) override; NWidgetBase *GetWidgetOfType(WidgetType tp) override; + void FillDirtyWidgets(std::vector &dirty_widgets) override; private: NWidgetPIPContainer *child; ///< Child widget. diff --git a/src/window.cpp b/src/window.cpp index 1bf67348ec..ab846726d0 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -622,7 +622,7 @@ void Window::RaiseButtons(bool autoraise) * Invalidate a widget, i.e. mark it as being changed and in need of redraw. * @param widget_index the widget to redraw. */ -void Window::SetWidgetDirty(byte widget_index) const +void Window::SetWidgetDirty(byte widget_index) { /* Sometimes this function is called before the window is even fully initialized */ if (this->nested_array == nullptr) return; @@ -889,27 +889,6 @@ static void DispatchMouseWheelEvent(Window *w, NWidgetCore *nwid, int wheel) } } -/** - * Returns whether a window may be shown or not. - * @param w The window to consider. - * @return True iff it may be shown, otherwise false. - */ -static bool MayBeShown(const Window *w) -{ - /* If we're not modal, everything is okay. */ - if (!HasModalProgress()) return true; - - switch (w->window_class) { - case WC_MAIN_WINDOW: ///< The background, i.e. the game. - case WC_MODAL_PROGRESS: ///< The actual progress window. - case WC_CONFIRM_POPUP_QUERY: ///< The abort window. - return true; - - default: - return false; - } -} - /** * Generate repaint events for the visible part of window w within the rectangle. * @@ -921,8 +900,9 @@ static bool MayBeShown(const Window *w) * @param top Top edge of the rectangle that should be repainted * @param right Right edge of the rectangle that should be repainted * @param bottom Bottom edge of the rectangle that should be repainted + * @param gfx_dirty Whether to mark gfx dirty */ -static void DrawOverlappedWindow(Window *w, int left, int top, int right, int bottom) +void DrawOverlappedWindow(Window *w, int left, int top, int right, int bottom, DrawOverlappedWindowFlags flags) { Window::IteratorToFront it(w); ++it; @@ -937,26 +917,26 @@ static void DrawOverlappedWindow(Window *w, int left, int top, int right, int bo int x; if (left < (x = v->left)) { - DrawOverlappedWindow(w, left, top, x, bottom); - DrawOverlappedWindow(w, x, top, right, bottom); + DrawOverlappedWindow(w, left, top, x, bottom, flags); + DrawOverlappedWindow(w, x, top, right, bottom, flags); return; } if (right > (x = v->left + v->width)) { - DrawOverlappedWindow(w, left, top, x, bottom); - DrawOverlappedWindow(w, x, top, right, bottom); + DrawOverlappedWindow(w, left, top, x, bottom, flags); + DrawOverlappedWindow(w, x, top, right, bottom, flags); return; } if (top < (x = v->top)) { - DrawOverlappedWindow(w, left, top, right, x); - DrawOverlappedWindow(w, left, x, right, bottom); + DrawOverlappedWindow(w, left, top, right, x, flags); + DrawOverlappedWindow(w, left, x, right, bottom, flags); return; } if (bottom > (x = v->top + v->height)) { - DrawOverlappedWindow(w, left, top, right, x); - DrawOverlappedWindow(w, left, x, right, bottom); + DrawOverlappedWindow(w, left, top, right, x, flags); + DrawOverlappedWindow(w, left, x, right, bottom, flags); return; } @@ -974,6 +954,14 @@ static void DrawOverlappedWindow(Window *w, int left, int top, int right, int bo dp->dst_ptr = BlitterFactory::GetCurrentBlitter()->MoveTo(_screen.dst_ptr, left, top); dp->zoom = ZOOM_LVL_NORMAL; w->OnPaint(); + if (unlikely(flags & DOWF_SHOW_DEBUG)) { + extern void ViewportDrawDirtyBlocks(); + ViewportDrawDirtyBlocks(); + } + if (flags & DOWF_MARK_DIRTY) { + VideoDriver::GetInstance()->MakeDirty(left, top, right - left, bottom - top); + UnsetDirtyBlocks(left, top, right, bottom); + } } /** @@ -997,19 +985,34 @@ void DrawOverlappedWindowForAll(int left, int top, int right, int bottom) left < w->left + w->width && top < w->top + w->height) { /* Window w intersects with the rectangle => needs repaint */ - DrawOverlappedWindow(w, std::max(left, w->left), std::max(top, w->top), std::min(right, w->left + w->width), std::min(bottom, w->top + w->height)); + DrawOverlappedWindow(w, std::max(left, w->left), std::max(top, w->top), std::min(right, w->left + w->width), std::min(bottom, w->top + w->height), DOWF_NONE); } } _cur_dpi = old_dpi; } + +/** + * Mark entire window as dirty (in need of re-paint) + * @ingroup dirty + */ +void Window::SetDirty() +{ + this->flags |= WF_DIRTY; +} + /** * Mark entire window as dirty (in need of re-paint) * @ingroup dirty */ -void Window::SetDirty() const +void Window::SetDirtyAsBlocks() { - AddDirtyBlock(this->left, this->top, this->left + this->width, this->top + this->height); + extern bool _gfx_draw_active; + if (_gfx_draw_active) { + AddPendingDirtyBlocks(this->left, this->top, this->left + this->width, this->top + this->height); + } else { + AddDirtyBlock(this->left, this->top, this->left + this->width, this->top + this->height); + } } /** @@ -1020,7 +1023,7 @@ void Window::SetDirty() const */ void Window::ReInit(int rx, int ry) { - this->SetDirty(); // Mark whole current window as dirty. + this->SetDirtyAsBlocks(); // Mark whole current window as dirty. /* Save current size. */ int window_width = this->width; @@ -1135,7 +1138,7 @@ void Window::Close() this->CloseChildWindows(); - this->SetDirty(); + this->SetDirtyAsBlocks(); Window::closed_windows.push_back(this); } @@ -2102,7 +2105,7 @@ void ResizeWindow(Window *w, int delta_x, int delta_y, bool clamp_to_screen) if (new_bottom >= (int)_screen.height) delta_y -= Ceil(new_bottom - _screen.height, std::max(1U, w->nested_root->resize_y)); } - w->SetDirty(); + w->SetDirtyAsBlocks(); uint new_xinc = std::max(0, (w->nested_root->resize_x == 0) ? 0 : (int)(w->nested_root->current_x - w->nested_root->smallest_x) + delta_x); uint new_yinc = std::max(0, (w->nested_root->resize_y == 0) ? 0 : (int)(w->nested_root->current_y - w->nested_root->smallest_y) + delta_y); @@ -2118,7 +2121,14 @@ void ResizeWindow(Window *w, int delta_x, int delta_y, bool clamp_to_screen) /* Always call OnResize to make sure everything is initialised correctly if it needs to be. */ w->OnResize(); - w->SetDirty(); + + // TODO incapsulate into a function + extern bool _gfx_draw_active; + if (_gfx_draw_active) { + AddPendingDirtyBlocks(w->left, w->top, w->left + w->width, w->top + w->height); + } else { + w->SetDirty(); + } } /** @@ -2166,7 +2176,10 @@ static EventState HandleWindowDragging() break; } - w->SetDirty(); + if (!(w->flags & WF_DRAG_DIRTIED)) { + w->flags |= WF_DRAG_DIRTIED; + w->SetDirtyAsBlocks(); + } int x = _cursor.pos.x + _drag_delta.x; int y = _cursor.pos.y + _drag_delta.y; @@ -2299,7 +2312,7 @@ static EventState HandleWindowDragging() _drag_delta.y += y; if ((w->flags & WF_SIZING_LEFT) && x != 0) { _drag_delta.x -= x; // x > 0 -> window gets longer -> left-edge moves to left -> subtract x to get new position. - w->SetDirty(); + w->SetDirtyAsBlocks(); w->left -= x; // If dragging left edge, move left window edge in opposite direction by the same amount. /* ResizeWindow() below ensures marking new position as dirty. */ } else { @@ -3164,7 +3177,7 @@ void UpdateWindows() */ void SetWindowDirty(WindowClass cls, WindowNumber number) { - for (const Window *w : Window::Iterate()) { + for (Window *w : Window::Iterate()) { if (w->window_class == cls && w->window_number == number) w->SetDirty(); } } @@ -3177,7 +3190,7 @@ void SetWindowDirty(WindowClass cls, WindowNumber number) */ void SetWindowWidgetDirty(WindowClass cls, WindowNumber number, byte widget_index) { - for (const Window *w : Window::Iterate()) { + for (Window *w : Window::Iterate()) { if (w->window_class == cls && w->window_number == number) { w->SetWidgetDirty(widget_index); } @@ -3190,7 +3203,7 @@ void SetWindowWidgetDirty(WindowClass cls, WindowNumber number, byte widget_inde */ void SetWindowClassesDirty(WindowClass cls) { - for (const Window *w : Window::Iterate()) { + for (Window *w : Window::Iterate()) { if (w->window_class == cls) w->SetDirty(); } } @@ -3362,7 +3375,7 @@ void CloseConstructionWindows() } } - for (const Window *w : Window::Iterate()) w->SetDirty(); + for (Window *w : Window::Iterate()) w->SetDirty(); } /** Close all always on-top windows to get an empty screen */ @@ -3486,7 +3499,7 @@ int PositionNetworkChatWindow(Window *w) */ void ChangeVehicleViewports(VehicleID from_index, VehicleID to_index) { - for (const Window *w : Window::Iterate()) { + for (Window *w : Window::Iterate()) { if (w->viewport != nullptr && w->viewport->follow_vehicle == from_index) { w->viewport->follow_vehicle = to_index; w->SetDirty(); diff --git a/src/window_gui.h b/src/window_gui.h index e3ed431936..f1609f5f65 100644 --- a/src/window_gui.h +++ b/src/window_gui.h @@ -165,7 +165,7 @@ enum SortButtonState { /** * Window flags. */ -enum WindowFlags { +enum WindowFlags : uint16 { WF_TIMEOUT = 1 << 0, ///< Window timeout counter. WF_DRAGGING = 1 << 3, ///< Window is being dragged. @@ -177,6 +177,9 @@ enum WindowFlags { WF_WHITE_BORDER = 1 << 8, ///< Window white border counter bit mask. WF_HIGHLIGHTED = 1 << 9, ///< Window has a widget that has a highlight. WF_CENTERED = 1 << 10, ///< Window is centered and shall stay centered after ReInit. + WF_DIRTY = 1 << 11, ///< Whole window is dirty, and requires repainting. + WF_WIDGETS_DIRTY = 1 << 12, ///< One or more widgets are dirty, and require repainting. + WF_DRAG_DIRTIED = 1 << 13, ///< The window has already been marked dirty as blocks as part of the current drag operation }; DECLARE_ENUM_AS_BIT_SET(WindowFlags) @@ -254,7 +257,8 @@ public: Owner owner; ///< The owner of the content shown in this window. Company colour is acquired from this variable. ViewportData *viewport; ///< Pointer to viewport data, if present. - const NWidgetCore *nested_focus; ///< Currently focused nested widget, or \c nullptr if no nested widget has focus. + NWidgetViewport *viewport_widget; ///< Pointer to viewport widget, if present. + NWidgetCore *nested_focus; ///< Currently focused nested widget, or \c nullptr if no nested widget has focus. SmallMap querystrings; ///< QueryString associated to WWT_EDITBOX widgets. NWidgetBase *nested_root; ///< Root of the nested tree. NWidgetBase **nested_array; ///< Array of pointers into the tree. Do not access directly, use #Window::GetWidget() instead. @@ -438,7 +442,7 @@ public: void RaiseButtons(bool autoraise = false); void CDECL SetWidgetsDisabledState(bool disab_stat, int widgets, ...); void CDECL SetWidgetsLoweredState(bool lowered_stat, int widgets, ...); - void SetWidgetDirty(byte widget_index) const; + void SetWidgetDirty(byte widget_index); void DrawWidgets() const; void DrawViewport() const; @@ -449,7 +453,8 @@ public: virtual void Close(); static void DeleteClosedWindows(); - void SetDirty() const; + void SetDirty(); + void SetDirtyAsBlocks(); void ReInit(int rx = 0, int ry = 0); /** Is window shaded currently? */ @@ -961,4 +966,26 @@ private: WindowPopupType type; }; +/** + * Returns whether a window may be shown or not. + * @param w The window to consider. + * @return True iff it may be shown, otherwise false. + */ +inline bool MayBeShown(const Window *w) +{ + /* If we're not modal, everything is okay. */ + extern bool _in_modal_progress; + if (likely(!_in_modal_progress)) return true; + + switch (w->window_class) { + case WC_MAIN_WINDOW: ///< The background, i.e. the game. + case WC_MODAL_PROGRESS: ///< The actual progress window. + case WC_CONFIRM_POPUP_QUERY: ///< The abort window. + return true; + + default: + return false; + } +} + #endif /* WINDOW_GUI_H */