From aa0c1ba2e07fbb7be5f9daa49efbe1c5d533749d Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Thu, 20 May 2021 18:03:11 +0100 Subject: [PATCH] Only update vehicle image when in the vicinity of a viewport This reduces the performance impact of expensive NewGRF graphics chains. --- src/vehicle.cpp | 135 ++++++++++++++++++++++++++++++++++++++------ src/vehicle_base.h | 134 ++++++++++++++++++++++++++++--------------- src/vehicle_func.h | 2 +- src/viewport.cpp | 21 ++++++- src/viewport_type.h | 2 + 5 files changed, 228 insertions(+), 66 deletions(-) diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 17af6f66d0..fdd62d8ad3 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -890,6 +890,49 @@ static void UpdateVehicleViewportHash(Vehicle *v, int x, int y) } } +struct ViewportHashDeferredItem { + Vehicle *v; + int new_hash; + int old_hash; +}; +static std::vector _viewport_hash_deferred; + +static void UpdateVehicleViewportHashDeferred(Vehicle *v, int x, int y) +{ + int old_x = v->coord.left; + int old_y = v->coord.top; + + int new_hash = (x == INVALID_COORD) ? INVALID_COORD : GEN_HASH(x, y); + int old_hash = (old_x == INVALID_COORD) ? INVALID_COORD : GEN_HASH(old_x, old_y); + + if (new_hash != old_hash) { + _viewport_hash_deferred.push_back({ v, new_hash, old_hash }); + } +} + +static void ProcessDeferredUpdateVehicleViewportHashes() +{ + for (const ViewportHashDeferredItem &item : _viewport_hash_deferred) { + Vehicle *v = item.v; + + /* remove from hash table? */ + if (item.old_hash != INVALID_COORD) { + if (v->hash_viewport_next != nullptr) v->hash_viewport_next->hash_viewport_prev = v->hash_viewport_prev; + *v->hash_viewport_prev = v->hash_viewport_next; + } + + /* insert into hash table? */ + if (item.new_hash != INVALID_COORD) { + Vehicle **new_hash = &_vehicle_viewport_hash[item.new_hash]; + v->hash_viewport_next = *new_hash; + if (v->hash_viewport_next != nullptr) v->hash_viewport_next->hash_viewport_prev = &v->hash_viewport_next; + v->hash_viewport_prev = new_hash; + *new_hash = v; + } + } + _viewport_hash_deferred.clear(); +} + void ResetVehicleHash() { for (Vehicle *v : Vehicle::Iterate()) { v->hash_tile_current = nullptr; } @@ -1686,9 +1729,11 @@ struct ViewportHashBound { int xl, xu, yl, yu; }; -static ViewportHashBound GetViewportHashBound(int l, int r, int t, int b) { - int xl = (l - (70 * ZOOM_LVL_BASE)) >> (7 + ZOOM_LVL_SHIFT); - int xu = (r ) >> (7 + ZOOM_LVL_SHIFT); +static const int VHB_BASE_MARGIN = 70; + +static ViewportHashBound GetViewportHashBound(int l, int r, int t, int b, int x_margin, int y_margin) { + int xl = (l - ((VHB_BASE_MARGIN + x_margin) * ZOOM_LVL_BASE)) >> (7 + ZOOM_LVL_SHIFT); + int xu = (r + (x_margin * ZOOM_LVL_BASE)) >> (7 + ZOOM_LVL_SHIFT); /* compare after shifting instead of before, so that lower bits don't affect comparison result */ if (xu - xl < (1 << 6)) { xl &= 0x3F; @@ -1699,8 +1744,8 @@ static ViewportHashBound GetViewportHashBound(int l, int r, int t, int b) { xu = 0x3F; } - int yl = (t - (70 * ZOOM_LVL_BASE)) >> (6 + ZOOM_LVL_SHIFT); - int yu = (b ) >> (6 + ZOOM_LVL_SHIFT); + int yl = (t - ((VHB_BASE_MARGIN + y_margin) * ZOOM_LVL_BASE)) >> (6 + ZOOM_LVL_SHIFT); + int yu = (b + (y_margin * ZOOM_LVL_BASE)) >> (6 + ZOOM_LVL_SHIFT); /* compare after shifting instead of before, so that lower bits don't affect comparison result */ if (yu - yl < (1 << 6)) { yl = (yl & 0x3F) << 6; @@ -1713,11 +1758,8 @@ static ViewportHashBound GetViewportHashBound(int l, int r, int t, int b) { return { xl, xu, yl, yu }; }; -/** - * Add the vehicle sprites that should be drawn at a part of the screen. - * @param dpi Rectangle being drawn. - */ -void ViewportAddVehicles(DrawPixelInfo *dpi) +template +void ViewportAddVehiclesIntl(DrawPixelInfo *dpi) { /* The bounding rectangle */ const int l = dpi->left; @@ -1726,19 +1768,45 @@ void ViewportAddVehicles(DrawPixelInfo *dpi) const int b = dpi->top + dpi->height; /* The hash area to scan */ - const ViewportHashBound vhb = GetViewportHashBound(l, r, t, b); + const ViewportHashBound vhb = GetViewportHashBound(l, r, t, b, + update_vehicles ? MAX_VEHICLE_PIXEL_X - VHB_BASE_MARGIN : 0, update_vehicles ? MAX_VEHICLE_PIXEL_Y - VHB_BASE_MARGIN : 0); + + const int ul = l - (MAX_VEHICLE_PIXEL_X * ZOOM_LVL_BASE); + const int ur = r + (MAX_VEHICLE_PIXEL_X * ZOOM_LVL_BASE); + const int ut = t - (MAX_VEHICLE_PIXEL_Y * ZOOM_LVL_BASE); + const int ub = b + (MAX_VEHICLE_PIXEL_Y * ZOOM_LVL_BASE); for (int y = vhb.yl;; y = (y + (1 << 6)) & (0x3F << 6)) { for (int x = vhb.xl;; x = (x + 1) & 0x3F) { const Vehicle *v = _vehicle_viewport_hash[x + y]; // already masked & 0xFFF while (v != nullptr) { - if (v->IsDrawn() && - l <= v->coord.right && - t <= v->coord.bottom && - r >= v->coord.left && - b >= v->coord.top) { - DoDrawVehicle(v); + if (v->IsDrawn()) { + if (update_vehicles && + HasBit(v->vcache.cached_veh_flags, VCF_IMAGE_REFRESH) && + ul <= v->coord.right && + ut <= v->coord.bottom && + ur >= v->coord.left && + ub >= v->coord.top) { + Vehicle *v_mutable = const_cast(v); + Direction current_direction = v_mutable->GetMapImageDirection(); + switch (v->type) { + case VEH_TRAIN: Train::From(v_mutable)->UpdateImageState(current_direction, v_mutable->sprite_seq); break; + case VEH_ROAD: RoadVehicle::From(v_mutable)->UpdateImageState(current_direction, v_mutable->sprite_seq); break; + case VEH_SHIP: Ship::From(v_mutable)->UpdateImageState(current_direction, v_mutable->sprite_seq); break; + case VEH_AIRCRAFT: Aircraft::From(v_mutable)->UpdateImageState(current_direction, v_mutable->sprite_seq); break; + default: break; + } + v_mutable->UpdateSpriteSeqBound(); + v_mutable->UpdateViewportDeferred(); + } + + if (l <= v->coord.right && + t <= v->coord.bottom && + r >= v->coord.left && + b >= v->coord.top) { + DoDrawVehicle(v); + } } v = v->hash_viewport_next; } @@ -1748,6 +1816,22 @@ void ViewportAddVehicles(DrawPixelInfo *dpi) if (y == vhb.yu) break; } + + if (update_vehicles) ProcessDeferredUpdateVehicleViewportHashes(); +} + +/** + * Add the vehicle sprites that should be drawn at a part of the screen. + * @param dpi Rectangle being drawn. + * @param update_vehicles Whether to update vehicles around drawing rectangle. + */ +void ViewportAddVehicles(DrawPixelInfo *dpi, bool update_vehicles) +{ + if (update_vehicles) { + ViewportAddVehiclesIntl(dpi); + } else { + ViewportAddVehiclesIntl(dpi); + } } void ViewportMapDrawVehicles(DrawPixelInfo *dpi, Viewport *vp) @@ -1759,7 +1843,7 @@ void ViewportMapDrawVehicles(DrawPixelInfo *dpi, Viewport *vp) const int b = vp->virtual_top + vp->virtual_height; /* The hash area to scan */ - const ViewportHashBound vhb = GetViewportHashBound(l, r, t, b); + const ViewportHashBound vhb = GetViewportHashBound(l, r, t, b, 0, 0); Blitter *blitter = BlitterFactory::GetCurrentBlitter(); for (int y = vhb.yl;; y = (y + (1 << 6)) & (0x3F << 6)) { @@ -2489,6 +2573,21 @@ void Vehicle::UpdateViewport(bool dirty) } } +void Vehicle::UpdateViewportDeferred() +{ + Rect new_coord = ConvertRect(this->sprite_seq_bounds); + + Point pt = RemapCoords(this->x_pos + this->x_offs, this->y_pos + this->y_offs, this->z_pos); + new_coord.left += pt.x; + new_coord.top += pt.y; + new_coord.right += pt.x + 2 * ZOOM_LVL_BASE; + new_coord.bottom += pt.y + 2 * ZOOM_LVL_BASE; + + UpdateVehicleViewportHashDeferred(this, new_coord.left, new_coord.top); + + this->coord = new_coord; +} + /** * Update the position of the vehicle, and update the viewport. */ diff --git a/src/vehicle_base.h b/src/vehicle_base.h index 99ce1ec105..63c4df32aa 100644 --- a/src/vehicle_base.h +++ b/src/vehicle_base.h @@ -23,6 +23,7 @@ #include "timetable.h" #include "base_consist.h" #include "newgrf_cache_check.h" +#include "landscape.h" #include "network/network.h" #include #include @@ -882,6 +883,7 @@ public: } void UpdateViewport(bool dirty); + void UpdateViewportDeferred(); void UpdatePositionAndViewport(); void MarkAllViewportsDirty() const; @@ -1185,6 +1187,19 @@ public: IterateWrapper Orders() const { return IterateWrapper(this->orders.list); } }; +inline bool IsPointInViewportVehicleRedrawArea(const std::vector &viewport_redraw_rects, const Point &pt) +{ + for (const Rect &r : viewport_redraw_rects) { + if (pt.x >= r.left && + pt.x <= r.right && + pt.y >= r.top && + pt.y <= r.bottom) { + return true; + } + } + return false; +} + /** * Class defining several overloaded accessors so we don't * have to cast vehicle types that often @@ -1327,57 +1342,55 @@ struct SpecializedVehicle : public Vehicle { return (const T *)v; } - /** - * Update vehicle sprite- and position caches - * @param force_update Force updating the vehicle on the viewport. - * @param update_delta Also update the delta? - */ - inline void UpdateViewport(bool force_update, bool update_delta) +private: + inline uint16 GetVehicleCurvature() const { - /* Skip updating sprites on dedicated servers without screen */ - if (_network_dedicated) return; + uint16 curvature = 0; + if (this->Previous() != nullptr) { + SB(curvature, 0, 4, this->Previous()->direction); + if (this->Previous()->Previous() != nullptr) SB(curvature, 4, 4, this->Previous()->Previous()->direction); + } + if (this->Next() != nullptr) { + SB(curvature, 8, 4, this->Next()->direction); + if (this->Next()->Next() != nullptr) SB(curvature, 12, 4, this->Next()->Next()->direction); + } + return curvature; + } - auto get_vehicle_curvature = [&]() -> uint16 { - uint16 curvature = 0; - if (this->Previous() != nullptr) { - SB(curvature, 0, 4, this->Previous()->direction); - if (this->Previous()->Previous() != nullptr) SB(curvature, 4, 4, this->Previous()->Previous()->direction); + inline bool CheckVehicleCurvature() const { + if (!(EXPECTED_TYPE == VEH_TRAIN || EXPECTED_TYPE == VEH_ROAD)) return false; + if (likely(!HasBit(this->vcache.cached_veh_flags, VCF_IMAGE_CURVATURE))) return false; + return this->vcache.cached_image_curvature != this->GetVehicleCurvature(); + }; + +public: + inline void UpdateImageState(Direction current_direction, VehicleSpriteSeq &seq) + { + ClrBit(this->vcache.cached_veh_flags, VCF_IMAGE_REFRESH); + _sprite_group_resolve_check_veh_check = true; + if (EXPECTED_TYPE == VEH_TRAIN || EXPECTED_TYPE == VEH_ROAD) _sprite_group_resolve_check_veh_curvature_check = true; + ((T *)this)->T::GetImage(current_direction, EIT_ON_MAP, &seq); + if (EXPECTED_TYPE == VEH_TRAIN || EXPECTED_TYPE == VEH_ROAD) { + SB(this->vcache.cached_veh_flags, VCF_IMAGE_REFRESH_NEXT, 1, _sprite_group_resolve_check_veh_check ? 0 : 1); + if (unlikely(!_sprite_group_resolve_check_veh_curvature_check)) { + SetBit(this->vcache.cached_veh_flags, VCF_IMAGE_CURVATURE); + this->vcache.cached_image_curvature = this->GetVehicleCurvature(); } - if (this->Next() != nullptr) { - SB(curvature, 8, 4, this->Next()->direction); - if (this->Next()->Next() != nullptr) SB(curvature, 12, 4, this->Next()->Next()->direction); - } - return curvature; - }; + _sprite_group_resolve_check_veh_curvature_check = false; + this->cur_image_valid_dir = current_direction; + } else { + this->cur_image_valid_dir = _sprite_group_resolve_check_veh_check ? current_direction : INVALID_DIR; + } + _sprite_group_resolve_check_veh_check = false; + } - auto check_vehicle_curvature = [&]() -> bool { - if (!(EXPECTED_TYPE == VEH_TRAIN || EXPECTED_TYPE == VEH_ROAD)) return false; - if (likely(!HasBit(this->vcache.cached_veh_flags, VCF_IMAGE_CURVATURE))) return false; - return this->vcache.cached_image_curvature != get_vehicle_curvature(); - }; - - /* Explicitly choose method to call to prevent vtable dereference - - * it gives ~3% runtime improvements in games with many vehicles */ - if (update_delta) ((T *)this)->T::UpdateDeltaXY(); +private: + inline void UpdateViewportNormalViewportMode(bool force_update, Point pt) + { const Direction current_direction = ((T *)this)->GetMapImageDirection(); - if (this->cur_image_valid_dir != current_direction || check_vehicle_curvature()) { - _sprite_group_resolve_check_veh_check = true; - if (EXPECTED_TYPE == VEH_TRAIN || EXPECTED_TYPE == VEH_ROAD) _sprite_group_resolve_check_veh_curvature_check = true; + if (this->cur_image_valid_dir != current_direction || this->CheckVehicleCurvature()) { VehicleSpriteSeq seq; - ((T *)this)->T::GetImage(current_direction, EIT_ON_MAP, &seq); - if (EXPECTED_TYPE == VEH_TRAIN || EXPECTED_TYPE == VEH_ROAD) { - ClrBit(this->vcache.cached_veh_flags, VCF_IMAGE_REFRESH); - SB(this->vcache.cached_veh_flags, VCF_IMAGE_REFRESH_NEXT, 1, _sprite_group_resolve_check_veh_check ? 0 : 1); - if (unlikely(!_sprite_group_resolve_check_veh_curvature_check)) { - SetBit(this->vcache.cached_veh_flags, VCF_IMAGE_CURVATURE); - this->vcache.cached_image_curvature = get_vehicle_curvature(); - } - _sprite_group_resolve_check_veh_curvature_check = false; - this->cur_image_valid_dir = current_direction; - } else { - this->cur_image_valid_dir = _sprite_group_resolve_check_veh_check ? current_direction : INVALID_DIR; - } - _sprite_group_resolve_check_veh_check = false; + this->UpdateImageState(current_direction, seq); if (force_update || this->sprite_seq != seq) { this->sprite_seq = seq; this->UpdateSpriteSeqBound(); @@ -1393,6 +1406,37 @@ struct SpecializedVehicle : public Vehicle { } } +public: + /** + * Update vehicle sprite- and position caches + * @param force_update Force updating the vehicle on the viewport. + * @param update_delta Also update the delta? + */ + inline void UpdateViewport(bool force_update, bool update_delta) + { + /* Skip updating sprites on dedicated servers without screen */ + if (_network_dedicated) return; + + /* Explicitly choose method to call to prevent vtable dereference - + * it gives ~3% runtime improvements in games with many vehicles */ + if (update_delta) ((T *)this)->T::UpdateDeltaXY(); + + extern std::vector _viewport_vehicle_normal_redraw_rects; + extern std::vector _viewport_vehicle_map_redraw_rects; + + Point pt = RemapCoords(this->x_pos + this->x_offs, this->y_pos + this->y_offs, this->z_pos); + if (EXPECTED_TYPE >= VEH_COMPANY_END || IsPointInViewportVehicleRedrawArea(_viewport_vehicle_normal_redraw_rects, pt)) { + UpdateViewportNormalViewportMode(force_update, pt); + return; + } + + SetBit(this->vcache.cached_veh_flags, VCF_IMAGE_REFRESH); + + if (force_update) { + this->Vehicle::UpdateViewport(IsPointInViewportVehicleRedrawArea(_viewport_vehicle_map_redraw_rects, pt)); + } + } + /** * Returns an iterable ensemble of all valid vehicles of type T * @param from index of the first vehicle to consider diff --git a/src/vehicle_func.h b/src/vehicle_func.h index 73943b3c87..6df1292733 100644 --- a/src/vehicle_func.h +++ b/src/vehicle_func.h @@ -127,7 +127,7 @@ void ResetVehicleColourMap(); byte GetBestFittingSubType(Vehicle *v_from, Vehicle *v_for, CargoID dest_cargo_type); -void ViewportAddVehicles(DrawPixelInfo *dpi); +void ViewportAddVehicles(DrawPixelInfo *dpi, bool update_vehicles); void ViewportMapDrawVehicles(DrawPixelInfo *dpi, Viewport *vp); void ShowNewGrfVehicleError(EngineID engine, StringID part1, StringID part2, GRFBugs bug_type, bool critical); diff --git a/src/viewport.cpp b/src/viewport.cpp index 85925f19de..261e40c8ae 100644 --- a/src/viewport.cpp +++ b/src/viewport.cpp @@ -272,6 +272,8 @@ static ViewportDrawer _vd; static std::vector _viewport_window_cache; static std::vector _viewport_coverage_rects; +std::vector _viewport_vehicle_normal_redraw_rects; +std::vector _viewport_vehicle_map_redraw_rects; RouteStepsMap _vp_route_steps; RouteStepsMap _vp_route_steps_last_mark_dirty; @@ -347,6 +349,8 @@ static Point MapXYZToViewport(const Viewport *vp, int x, int y, int z) static void FillViewportCoverageRect() { _viewport_coverage_rects.resize(_viewport_window_cache.size()); + _viewport_vehicle_normal_redraw_rects.clear(); + _viewport_vehicle_map_redraw_rects.clear(); for (uint i = 0; i < _viewport_window_cache.size(); i++) { const Viewport *vp = _viewport_window_cache[i]; @@ -355,6 +359,17 @@ static void FillViewportCoverageRect() r.top = vp->virtual_top; r.right = vp->virtual_left + vp->virtual_width + (1 << vp->zoom) - 1; r.bottom = vp->virtual_top + vp->virtual_height + (1 << vp->zoom) - 1; + + if (vp->zoom >= ZOOM_LVL_DRAW_MAP) { + _viewport_vehicle_map_redraw_rects.push_back(r); + } else { + _viewport_vehicle_normal_redraw_rects.push_back({ + r.left - (MAX_VEHICLE_PIXEL_X * ZOOM_LVL_BASE), + r.top - (MAX_VEHICLE_PIXEL_Y * ZOOM_LVL_BASE), + r.right + (MAX_VEHICLE_PIXEL_X * ZOOM_LVL_BASE), + r.bottom + (MAX_VEHICLE_PIXEL_Y * ZOOM_LVL_BASE), + }); + } } } @@ -716,6 +731,8 @@ static void SetViewportPosition(Window *w, int x, int y, bool force_update_overl SCOPE_INFO_FMT([&], "DoSetViewportPosition: %d, %d, %d, %d, %d, %d, %s", left, top, width, height, _vp_move_offs.x, _vp_move_offs.y, scope_dumper().WindowInfo(w)); DoSetViewportPosition((Window *) w->z_front, left, top, width, height); ClearViewportCache(w->viewport); + FillViewportCoverageRect(); + w->viewport->update_vehicles = true; } } } @@ -3281,7 +3298,7 @@ void ViewportDoDraw(Viewport *vp, int left, int top, int right, int bottom) } else { /* Classic rendering. */ ViewportAddLandscape(); - ViewportAddVehicles(&_vd.dpi); + ViewportAddVehicles(&_vd.dpi, vp->update_vehicles); ViewportAddKdtreeSigns(&_vd.dpi, false); @@ -3473,7 +3490,6 @@ void UpdateViewportPosition(Window *w) SetViewportPosition(w, w->viewport->scrollpos_x, w->viewport->scrollpos_y, update_overlay); } - FillViewportCoverageRect(); } void UpdateViewportSizeZoom(Viewport *vp) @@ -3497,6 +3513,7 @@ void UpdateViewportSizeZoom(Viewport *vp) vp->land_pixel_cache.clear(); vp->land_pixel_cache.shrink_to_fit(); } + vp->update_vehicles = true; FillViewportCoverageRect(); } diff --git a/src/viewport_type.h b/src/viewport_type.h index 94f7b5839b..1ffadea877 100644 --- a/src/viewport_type.h +++ b/src/viewport_type.h @@ -60,6 +60,7 @@ struct Viewport { uint8 dirty_block_left_margin; bool is_dirty = false; bool is_drawn = false; + bool update_vehicles = false; ViewPortMapDrawVehiclesCache map_draw_vehicles_cache; std::vector land_pixel_cache; @@ -75,6 +76,7 @@ struct Viewport { this->is_dirty = false; } this->is_drawn = false; + this->update_vehicles = false; } private: