Linkgraph: Scroll overlay pixel cache instead of clearing it on scroll

Reduce cost of preparing/drawing cache
Improve missing station checks in RefreshDrawCache
Remove need for checks in DrawLinks
Don't rebase overlay cache coordinate to screen in viewport map mode
pull/642/head
Jonathan G Rennison 3 months ago
parent 6c30e88890
commit 7a5e8dd991

@ -116,16 +116,25 @@ void LinkGraphOverlay::RebuildCache(bool incremental)
this->cached_links.clear();
this->cached_stations.clear();
this->last_update_number = GetWindowUpdateNumber();
this->rebuild_counter++;
}
if (this->company_mask == 0) return;
const Rect old_cached_region = this->cached_region;
DrawPixelInfo dpi;
bool cache_all = false;
if (this->window->viewport) {
const Viewport *vp = this->window->viewport;
const int pixel_margin = 256;
const int vp_margin = ScaleByZoom(pixel_margin, vp->zoom);
this->GetWidgetDpi(&dpi, pixel_margin);
if (vp->zoom < ZOOM_LVL_DRAW_MAP) {
this->GetWidgetDpi(&dpi, pixel_margin);
} else {
dpi.left = UnScaleByZoomLower(vp->virtual_left - vp_margin, vp->zoom);
dpi.top = UnScaleByZoomLower(vp->virtual_top - vp_margin, vp->zoom);
dpi.width = UnScaleByZoom(vp->virtual_width + vp_margin * 2, vp->zoom);
dpi.height = UnScaleByZoom(vp->virtual_height + vp_margin * 2, vp->zoom);
}
this->cached_region = Rect({ vp->virtual_left - vp_margin, vp->virtual_top - vp_margin,
vp->virtual_left + vp->virtual_width + vp_margin, vp->virtual_top + vp->virtual_height + vp_margin });
} else {
@ -237,6 +246,26 @@ void LinkGraphOverlay::RebuildCache(bool incremental)
this->cached_links.push_back({ iter.first.first, iter.first.second, iter.second.from_pt, iter.second.to_pt, iter.second.prop });
}
if (incremental && (this->cached_stations.size() > previous_cached_stations_count || this->cached_links.size() > previous_cached_links_count)) {
/* Check if newly added stations/links are visible in previous cached area */
DrawPixelInfo old_dpi;
old_dpi.left = old_cached_region.left;
old_dpi.top = old_cached_region.top;
old_dpi.width = old_cached_region.right - old_cached_region.left;
old_dpi.height = old_cached_region.bottom - old_cached_region.top;
auto check_found = [&]() -> bool {
for (size_t i = previous_cached_stations_count; i < this->cached_stations.size(); i++) {
if (this->IsPointVisible(this->cached_stations[i].pt, &old_dpi)) return true;
}
for (size_t i = previous_cached_links_count; i < this->cached_links.size(); i++) {
if (this->IsLinkVisible(this->cached_links[i].from_pt, this->cached_links[i].to_pt, &old_dpi)) return true;
}
return false;
};
if (check_found()) this->rebuild_counter++;
}
if (previous_cached_stations_count > 0) {
std::inplace_merge(this->cached_stations.begin(), this->cached_stations.begin() + previous_cached_stations_count, this->cached_stations.end(),
[](const StationSupplyInfo &a, const StationSupplyInfo &b) {
@ -364,17 +393,32 @@ inline bool LinkGraphOverlay::IsLinkVisible(Point pta, Point ptb, const DrawPixe
void LinkGraphOverlay::RefreshDrawCache()
{
static const Point INVALID_POINT = Point{ INT_MIN / 2, INT_MIN / 2 };
for (StationSupplyList::iterator i(this->cached_stations.begin()); i != this->cached_stations.end(); ++i) {
const Station *st = Station::GetIfValid(i->id);
if (st == nullptr) continue;
if (st == nullptr) {
i->pt = INVALID_POINT;
continue;
}
i->pt = this->GetStationMiddle(st);
Point new_pt = this->GetStationMiddle(st);
if (i->pt.x != new_pt.x || i->pt.y != new_pt.y) {
i->pt = new_pt;
}
}
for (LinkList::iterator i(this->cached_links.begin()); i != this->cached_links.end(); ++i) {
const Station *sta = Station::GetIfValid(i->from_id);
if (sta == nullptr) continue;
if (sta == nullptr) {
i->from_pt = i->to_pt = INVALID_POINT;
continue;
}
const Station *stb = Station::GetIfValid(i->to_id);
if (stb == nullptr) continue;
if (stb == nullptr) {
i->from_pt = i->to_pt = INVALID_POINT;
continue;
}
i->from_pt = this->GetStationMiddle(sta);
i->to_pt = this->GetStationMiddle(stb);
@ -389,7 +433,7 @@ void LinkGraphOverlay::PrepareDraw()
if (this->dirty) {
this->RebuildCache();
}
if (this->last_update_number != GetWindowUpdateNumber()) {
if (this->last_update_number != GetWindowUpdateNumber() && this->window->viewport->zoom < ZOOM_LVL_DRAW_MAP) {
this->last_update_number = GetWindowUpdateNumber();
this->RefreshDrawCache();
}
@ -414,8 +458,6 @@ void LinkGraphOverlay::DrawLinks(const DrawPixelInfo *dpi) const
int width = ScaleGUITrad(this->scale);
for (const auto &i : this->cached_links) {
if (!this->IsLinkVisible(i.from_pt, i.to_pt, dpi, width + 2)) continue;
if (!Station::IsValidID(i.from_id)) continue;
if (!Station::IsValidID(i.to_id)) continue;
this->DrawContent(dpi, i.from_pt, i.to_pt, i.prop);
}
}

@ -87,10 +87,12 @@ public:
void SetDirty() { this->dirty = true; }
/** Get a bitmask of the currently shown cargoes. */
CargoTypes GetCargoMask() { return this->cargo_mask; }
CargoTypes GetCargoMask() const { return this->cargo_mask; }
/** Get a bitmask of the currently shown companies. */
CompanyMask GetCompanyMask() { return this->company_mask; }
CompanyMask GetCompanyMask() const { return this->company_mask; }
uint64_t GetRebuildCounter() const { return this->rebuild_counter; }
protected:
Window *window; ///< Window to be drawn into.
@ -102,7 +104,8 @@ protected:
Rect cached_region; ///< Region covered by cached_links and cached_stations.
uint scale; ///< Width of link lines.
bool dirty; ///< Set if overlay should be rebuilt.
uint64_t last_update_number = 0; ///< Last window update number
uint64_t last_update_number = 0; ///< Last window update number
uint64_t rebuild_counter = 0; ///< Rebuild counter
Point GetStationMiddle(const Station *st) const;

@ -566,6 +566,32 @@ static void ScrollPlanPixelCache(Viewport *vp, int offset_x, int offset_y)
if (clear) ClearViewportPlanPixelCache(vp);
}
static void ScrollOrInvalidateOverlayPixelCache(Viewport *vp, int offset_x, int offset_y)
{
if (vp->overlay_pixel_cache.empty()) return;
if (vp->zoom < ZOOM_LVL_DRAW_MAP || vp->last_overlay_rebuild_counter != vp->overlay->GetRebuildCounter()) {
vp->overlay_pixel_cache.clear();
return;
}
bool clear = ScrollViewportPixelCacheGeneric(vp, vp->overlay_pixel_cache, offset_x, offset_y, 1, [](Viewport *vp, int x, int y, int width, int height) {
DrawPixelInfo overlay_dpi;
overlay_dpi.dst_ptr = vp->overlay_pixel_cache.data() + x + (y * vp->width);
overlay_dpi.height = height;
overlay_dpi.width = width;
overlay_dpi.pitch = vp->width;
overlay_dpi.zoom = ZOOM_LVL_NORMAL;
overlay_dpi.left = UnScaleByZoomLower(vp->virtual_left, vp->zoom) + x;
overlay_dpi.top = UnScaleByZoomLower(vp->virtual_top, vp->zoom) + y;
Blitter_8bppDrawing blitter;
BlitterFactory::TemporaryCurrentBlitterOverride current_blitter(&blitter);
vp->overlay->Draw(&overlay_dpi);
});
if (clear) vp->overlay_pixel_cache.clear();
}
void ClearViewportCache(Viewport *vp)
{
if (vp->zoom >= ZOOM_LVL_DRAW_MAP) {
@ -870,7 +896,11 @@ static void SetViewportPosition(Window *w, int x, int y, bool force_update_overl
vp->virtual_top = y;
UpdateViewportDirtyBlockLeftMargin(vp);
if (force_update_overlay || IsViewportOverlayOutsideCachedRegion(w)) RebuildViewportOverlay(w, true);
bool have_overlay = w->viewport->overlay != nullptr &&
w->viewport->overlay->GetCompanyMask() != 0 &&
w->viewport->overlay->GetCargoMask() != 0;
if (have_overlay && (force_update_overlay || !w->viewport->overlay->CacheStillValid())) RebuildViewportOverlay(w, true);
/* 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)
@ -913,6 +943,7 @@ 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, move_offset.x, move_offset.y, scope_dumper().WindowInfo(w));
ScrollViewportLandPixelCache(vp, move_offset.x, move_offset.y);
ScrollPlanPixelCache(vp, move_offset.x, move_offset.y);
if (have_overlay) ScrollOrInvalidateOverlayPixelCache(vp, move_offset.x, move_offset.y);
w->viewport->update_vehicles = true;
DoSetViewportPosition((Window *) w->z_front, move_offset, left, top, width, height);
ClearViewportCache(w->viewport);
@ -3816,16 +3847,6 @@ static void ViewportProcessParentSprites(ViewportDrawerDynamic *vdd, uint data_i
}
}
static bool CheckViewportOverlayPixelRefresh(Viewport *vp)
{
size_t screen_area = vp->ScreenArea();
if (vp->last_overlay_update_number == GetWindowUpdateNumber() && vp->overlay_pixel_cache.size() == screen_area) return false;
vp->last_overlay_update_number = GetWindowUpdateNumber();
vp->overlay_pixel_cache.assign(screen_area, 0xD7);
return true;
}
static void ViewportDoDrawPhase2(Viewport *vp, ViewportDrawerDynamic *vdd);
static void ViewportDoDrawPhase3(Viewport *vp);
static void ViewportDoDrawRenderJob(Viewport *vp, ViewportDrawerDynamic *vdd);
@ -3868,15 +3889,19 @@ void ViewportDoDraw(Viewport *vp, int left, int top, int right, int bottom, uint
if (vp->overlay != nullptr && vp->overlay->GetCargoMask() != 0 && vp->overlay->GetCompanyMask() != 0) {
vp->overlay->PrepareDraw();
if (vp->zoom >= ZOOM_LVL_DRAW_MAP && CheckViewportOverlayPixelRefresh(vp)) {
if (vp->zoom >= ZOOM_LVL_DRAW_MAP && (vp->overlay_pixel_cache.empty() || vp->last_overlay_rebuild_counter != vp->overlay->GetRebuildCounter())) {
vp->last_overlay_rebuild_counter = vp->overlay->GetRebuildCounter();
vp->overlay_pixel_cache.assign(vp->ScreenArea(), 0xD7);
DrawPixelInfo overlay_dpi;
overlay_dpi.dst_ptr = vp->overlay_pixel_cache.data();
overlay_dpi.height = vp->height;
overlay_dpi.width = vp->width;
overlay_dpi.pitch = vp->width;
overlay_dpi.zoom = ZOOM_LVL_NORMAL;
overlay_dpi.left = vp->left;
overlay_dpi.top = vp->top;
overlay_dpi.left = UnScaleByZoomLower(vp->virtual_left, vp->zoom);
overlay_dpi.top = UnScaleByZoomLower(vp->virtual_top, vp->zoom);
Blitter_8bppDrawing blitter;
BlitterFactory::TemporaryCurrentBlitterOverride current_blitter(&blitter);
@ -4283,6 +4308,7 @@ void UpdateViewportSizeZoom(Viewport *vp)
} else {
vp->land_pixel_cache.assign(vp->ScreenArea(), 0xD7);
}
vp->overlay_pixel_cache.clear();
vp->plan_pixel_cache.clear();
} else {
vp->map_draw_vehicles_cache.vehicle_pixels.clear();
@ -5154,17 +5180,6 @@ void RebuildViewportOverlay(Window *w, bool incremental)
}
}
bool IsViewportOverlayOutsideCachedRegion(Window *w)
{
if (w->viewport->overlay != nullptr &&
w->viewport->overlay->GetCompanyMask() != 0 &&
w->viewport->overlay->GetCargoMask() != 0) {
return !w->viewport->overlay->CacheStillValid();
} else {
return false;
}
}
/**
* Scrolls the viewport in a window to a given location.
* @param x Desired x location of the map to scroll to (world coordinate).
@ -6567,12 +6582,23 @@ Point GetViewportStationMiddle(const Viewport *vp, const Station *st)
{
int x = TileX(st->xy) * TILE_SIZE;
int y = TileY(st->xy) * TILE_SIZE;
int z = GetSlopePixelZ(Clamp(x, 0, MapSizeX() * TILE_SIZE - 1), Clamp(y, 0, MapSizeY() * TILE_SIZE - 1));
Point p = RemapCoords(x, y, z);
p.x = UnScaleByZoom(p.x - vp->virtual_left, vp->zoom) + vp->left;
p.y = UnScaleByZoom(p.y - vp->virtual_top, vp->zoom) + vp->top;
return p;
/* Be faster/less precise in viewport map mode, sub-pixel precision is not needed.
* Don't rebase point into screen coordinates in viewport map mode.
*/
if (vp->zoom < ZOOM_LVL_DRAW_MAP) {
int z = GetSlopePixelZ(Clamp(x, 0, MapSizeX() * TILE_SIZE - 1), Clamp(y, 0, MapSizeY() * TILE_SIZE - 1));
Point p = RemapCoords(x, y, z);
p.x = UnScaleByZoom(p.x - vp->virtual_left, vp->zoom) + vp->left;
p.y = UnScaleByZoom(p.y - vp->virtual_top, vp->zoom) + vp->top;
return p;
} else {
int z = st->xy < MapSize() ? TILE_HEIGHT * TileHeight(st->xy) : 0;
Point p = RemapCoords(x, y, z);
p.x = UnScaleByZoomLower(p.x, vp->zoom);
p.y = UnScaleByZoomLower(p.y, vp->zoom);
return p;
}
}
/** Helper class for getting the best sprite sorter. */

@ -103,7 +103,6 @@ bool ScrollWindowTo(int x, int y, int z, Window *w, bool instant = false);
void UpdateActiveScrollingViewport(Window *w);
void RebuildViewportOverlay(Window *w, bool incremental);
bool IsViewportOverlayOutsideCachedRegion(Window *w);
bool ScrollMainWindowToTile(TileIndex tile, bool instant = false);
bool ScrollMainWindowTo(int x, int y, int z = -1, bool instant = false);

@ -61,7 +61,7 @@ struct Viewport {
bool is_dirty = false;
bool is_drawn = false;
bool update_vehicles = false;
uint64_t last_overlay_update_number = 0;
uint64_t last_overlay_rebuild_counter = 0;
uint64_t last_plan_update_number = 0;
ViewPortMapDrawVehiclesCache map_draw_vehicles_cache;
std::vector<byte> land_pixel_cache;

Loading…
Cancel
Save