Browse Source

Partial support for caching to detect updated tiles

Ivan Savenko 2 years ago
parent
commit
4fdf3d4a87

+ 3 - 0
client/mapRenderer/IMapRendererContext.h

@@ -34,6 +34,9 @@ public:
 	/// returns true if chosen coordinates exist on map
 	virtual bool isInMap(const int3 & coordinates) const = 0;
 
+//	/// returns true if selected tile has animation and should not be cached
+//	virtual bool tileAnimated(const int3 & coordinates) const = 0;
+
 	/// returns tile by selected coordinates. Coordinates MUST be valid
 	virtual const TerrainTile & getMapTile(const int3 & coordinates) const = 0;
 

+ 147 - 22
client/mapRenderer/MapRenderer.cpp

@@ -11,7 +11,7 @@
 #include "StdInc.h"
 #include "MapRenderer.h"
 
-#include "MapRendererContext.h"
+#include "IMapRendererContext.h"
 #include "mapHandler.h"
 
 #include "../CGameInfo.h"
@@ -92,7 +92,7 @@ MapTileStorage::MapTileStorage(size_t capacity)
 {
 }
 
-void MapTileStorage::load(size_t index, const std::string & filename)
+void MapTileStorage::load(size_t index, const std::string & filename, EImageBlitMode blitMode)
 {
 	auto & terrainAnimations = animations[index];
 
@@ -100,6 +100,9 @@ void MapTileStorage::load(size_t index, const std::string & filename)
 	{
 		entry = std::make_unique<CAnimation>(filename);
 		entry->preload();
+
+		for(size_t i = 0; i < entry->size(); ++i)
+			entry->getImage(i)->setBlitMode(blitMode);
 	}
 
 	for(size_t i = 0; i < terrainAnimations[0]->size(); ++i)
@@ -122,7 +125,7 @@ MapRendererTerrain::MapRendererTerrain()
 	: storage(VLC->terrainTypeHandler->objects.size())
 {
 	for(const auto & terrain : VLC->terrainTypeHandler->objects)
-		storage.load(terrain->getIndex(), terrain->tilesFilename);
+		storage.load(terrain->getIndex(), terrain->tilesFilename, EImageBlitMode::OPAQUE);
 }
 
 void MapRendererTerrain::renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates)
@@ -146,15 +149,23 @@ void MapRendererTerrain::renderTile(const IMapRendererContext & context, Canvas
 		image->shiftPalette(242, 14, context.terrainImageIndex(14));
 	}
 
-	image->setBlitMode(EImageBlitMode::OPAQUE);
 	target.draw(image, Point(0, 0));
 }
 
+uint8_t MapRendererTerrain::checksum(const IMapRendererContext & context, const int3 & coordinates)
+{
+	const TerrainTile & mapTile = context.getMapTile(coordinates);
+
+	if(mapTile.terType->getId() == ETerrainId::LAVA || mapTile.terType->getId() == ETerrainId::WATER)
+		return context.terrainImageIndex(250);
+	return 0xff-1;
+}
+
 MapRendererRiver::MapRendererRiver()
 	: storage(VLC->riverTypeHandler->objects.size())
 {
 	for(const auto & river : VLC->riverTypeHandler->objects)
-		storage.load(river->getIndex(), river->tilesFilename);
+		storage.load(river->getIndex(), river->tilesFilename, EImageBlitMode::COLORKEY);
 }
 
 void MapRendererRiver::renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates)
@@ -191,11 +202,22 @@ void MapRendererRiver::renderTile(const IMapRendererContext & context, Canvas &
 	target.draw(image, Point(0, 0));
 }
 
+uint8_t MapRendererRiver::checksum(const IMapRendererContext & context, const int3 & coordinates)
+{
+	const TerrainTile & mapTile = context.getMapTile(coordinates);
+
+	if(mapTile.riverType->getId() == River::WATER_RIVER ||
+	   mapTile.riverType->getId() == River::MUD_RIVER ||
+	   mapTile.riverType->getId() == River::LAVA_RIVER)
+		return context.terrainImageIndex(250);
+	return 0xff-1;
+}
+
 MapRendererRoad::MapRendererRoad()
 	: storage(VLC->roadTypeHandler->objects.size())
 {
 	for(const auto & road : VLC->roadTypeHandler->objects)
-		storage.load(road->getIndex(), road->tilesFilename);
+		storage.load(road->getIndex(), road->tilesFilename, EImageBlitMode::COLORKEY);
 }
 
 void MapRendererRoad::renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates)
@@ -228,6 +250,11 @@ void MapRendererRoad::renderTile(const IMapRendererContext & context, Canvas & t
 	}
 }
 
+uint8_t MapRendererRoad::checksum(const IMapRendererContext & context, const int3 & coordinates)
+{
+	return 0;
+}
+
 MapRendererBorder::MapRendererBorder()
 {
 	animation = std::make_unique<CAnimation>("EDG");
@@ -278,6 +305,11 @@ void MapRendererBorder::renderTile(const IMapRendererContext & context, Canvas &
 	target.draw(image, Point(0, 0));
 }
 
+uint8_t MapRendererBorder::checksum(const IMapRendererContext & context, const int3 & coordinates)
+{
+	return 0;
+}
+
 MapRendererFow::MapRendererFow()
 {
 	fogOfWarFullHide = std::make_unique<CAnimation>("TSHRC");
@@ -285,6 +317,9 @@ MapRendererFow::MapRendererFow()
 	fogOfWarPartialHide = std::make_unique<CAnimation>("TSHRE");
 	fogOfWarPartialHide->preload();
 
+	for(size_t i = 0; i < fogOfWarFullHide->size(); ++i)
+		fogOfWarFullHide->getImage(i)->setBlitMode(EImageBlitMode::OPAQUE);
+
 	static const std::vector<int> rotations = {22, 15, 2, 13, 12, 16, 28, 17, 20, 19, 7, 24, 26, 25, 30, 32, 27};
 
 	size_t size = fogOfWarPartialHide->size(0); //group size after next rotation
@@ -321,6 +356,15 @@ void MapRendererFow::renderTile(const IMapRendererContext & context, Canvas & ta
 	}
 }
 
+uint8_t MapRendererFow::checksum(const IMapRendererContext & context, const int3 & coordinates)
+{
+	const NeighborTilesInfo neighborInfo(context, coordinates);
+	int retBitmapID = neighborInfo.getBitmapID();
+	if(retBitmapID < 0)
+		return 0xff-1;
+	return retBitmapID;
+}
+
 std::shared_ptr<CAnimation> MapRendererObjects::getBaseAnimation(const CGObjectInstance* obj)
 {
 	const auto & info = obj->appearance;
@@ -485,6 +529,36 @@ void MapRendererObjects::renderTile(const IMapRendererContext & context, Canvas
 	}
 }
 
+uint8_t MapRendererObjects::checksum(const IMapRendererContext & context, const int3 & coordinates)
+{
+	for(const auto & objectID : context.getObjects(coordinates))
+	{
+		const auto * objectInstance = context.getObject(objectID);
+		size_t groupIndex = context.objectGroupIndex(objectInstance->id);
+		Point offsetPixels = context.objectImageOffset(objectInstance->id, coordinates);
+
+		auto base = getBaseAnimation(objectInstance);
+		auto flag = getFlagAnimation(objectInstance);
+
+		if (base && base->size(groupIndex) > 1)
+		{
+			auto imageIndex = context.objectImageIndex(objectID, base->size(groupIndex));
+			auto image = base->getImage(imageIndex, groupIndex);
+			if ( offsetPixels.x < image->dimensions().x && offsetPixels.y < image->dimensions().y)
+				return context.objectImageIndex(objectID, 250);
+		}
+
+		if (flag && flag->size(groupIndex) > 1)
+		{
+			auto imageIndex = context.objectImageIndex(objectID, flag->size(groupIndex));
+			auto image = flag->getImage(imageIndex, groupIndex);
+			if ( offsetPixels.x < image->dimensions().x && offsetPixels.y < image->dimensions().y)
+				return context.objectImageIndex(objectID, 250);
+		}
+	}
+	return 0xff-1;
+}
+
 MapRendererDebug::MapRendererDebug()
 	: imageGrid(IImage::createFromFile("debug/grid", EImageBlitMode::ALPHA))
 	, imageBlockable(IImage::createFromFile("debug/blocked", EImageBlitMode::ALPHA))
@@ -521,28 +595,33 @@ void MapRendererDebug::renderTile(const IMapRendererContext & context, Canvas &
 	}
 }
 
+uint8_t MapRendererDebug::checksum(const IMapRendererContext & context, const int3 & coordinates)
+{
+	return 0;
+}
+
 MapRendererPath::MapRendererPath()
 	: pathNodes(new CAnimation("ADAG"))
 {
 	pathNodes->preload();
 }
 
-void MapRendererPath::renderImage(Canvas & target, bool reachableToday, size_t imageIndex)
+size_t MapRendererPath::selectImageReachability(bool reachableToday, size_t imageIndex)
 {
 	const static size_t unreachableTodayOffset = 25;
 
-	if(reachableToday)
-		target.draw(pathNodes->getImage(imageIndex), Point(0, 0));
-	else
-		target.draw(pathNodes->getImage(imageIndex + unreachableTodayOffset), Point(0, 0));
+	if(!reachableToday)
+		return unreachableTodayOffset + imageIndex;
+
+	return imageIndex;
 }
 
-void MapRendererPath::renderImageCross(Canvas & target, bool reachableToday, const int3 & curr)
+size_t MapRendererPath::selectImageCross(bool reachableToday, const int3 & curr)
 {
-	renderImage(target, reachableToday, 0);
+	return selectImageReachability(reachableToday, 0);
 }
 
-void MapRendererPath::renderImageArrow(Canvas & target, bool reachableToday, const int3 & curr, const int3 & prev, const int3 & next)
+size_t MapRendererPath::selectImageArrow(bool reachableToday, const int3 & curr, const int3 & prev, const int3 & next)
 {
 	// Vector directions
 	//  0   1   2
@@ -571,10 +650,18 @@ void MapRendererPath::renderImageArrow(Canvas & target, bool reachableToday, con
 	size_t leaveDirection = (prev.x - curr.x + 1) + 3 * (prev.y - curr.y + 1);
 	size_t imageIndex = directionToArrowIndex[enterDirection][leaveDirection];
 
-	renderImage(target, reachableToday, imageIndex);
+	return selectImageReachability(reachableToday, imageIndex);
 }
 
 void MapRendererPath::renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates)
+{
+	size_t imageID = selectImage(context, coordinates);
+
+	if (imageID < pathNodes->size())
+		target.draw(pathNodes->getImage(imageID), Point(0,0));
+}
+
+size_t MapRendererPath::selectImage(const IMapRendererContext & context, const int3 & coordinates)
 {
 	const auto & functor = [&](const CGPathNode & node)
 	{
@@ -583,31 +670,70 @@ void MapRendererPath::renderTile(const IMapRendererContext & context, Canvas & t
 
 	const auto * path = context.currentPath();
 	if(!path)
-		return;
+		return std::numeric_limits<size_t>::max();
 
 	const auto & iter = boost::range::find_if(path->nodes, functor);
 
 	if(iter == path->nodes.end())
-		return;
+		return std::numeric_limits<size_t>::max();
 
 	bool reachableToday = iter->turns == 0;
 	if(iter == path->nodes.begin())
-		renderImageCross(target, reachableToday, iter->coord);
+		return selectImageCross(reachableToday, iter->coord);
 
 	auto next = iter + 1;
 	auto prev = iter - 1;
 
 	// start of path - current hero location
 	if(next == path->nodes.end())
-		return;
+		return std::numeric_limits<size_t>::max();
 
 	bool pathContinuous = iter->coord.areNeighbours(next->coord) && iter->coord.areNeighbours(prev->coord);
 	bool embarking = iter->action == CGPathNode::EMBARK || iter->action == CGPathNode::DISEMBARK;
 
 	if(pathContinuous && !embarking)
-		renderImageArrow(target, reachableToday, iter->coord, prev->coord, next->coord);
+		return selectImageArrow(reachableToday, iter->coord, prev->coord, next->coord);
+
+	return selectImageCross(reachableToday, iter->coord);
+}
+
+uint8_t MapRendererPath::checksum(const IMapRendererContext & context, const int3 & coordinates)
+{
+	return selectImage(context, coordinates) & 0xff;
+}
+
+MapRenderer::TileChecksum MapRenderer::getTileChecksum(const IMapRendererContext & context, const int3 & coordinates)
+{
+	// computes basic checksum to determine whether tile needs an update
+	// if any component gives different value, tile will be updated
+	TileChecksum result;
+	boost::range::fill(result, std::numeric_limits<uint8_t>::max());
+
+	if(!context.isInMap(coordinates))
+	{
+		result[0] = rendererBorder.checksum(context, coordinates);
+		return result;
+	}
+
+	const NeighborTilesInfo neighborInfo(context, coordinates);
+
+	if(!context.isVisible(coordinates) && neighborInfo.areAllHidden())
+	{
+		result[7] = rendererFow.checksum(context, coordinates);
+	}
 	else
-		renderImageCross(target, reachableToday, iter->coord);
+	{
+		result[1] = rendererTerrain.checksum(context, coordinates);
+		result[2] = rendererRiver.checksum(context, coordinates);
+		result[3] = rendererRoad.checksum(context, coordinates);
+		result[4] = rendererObjects.checksum(context, coordinates);
+		result[5] = rendererPath.checksum(context, coordinates);
+		result[6] = rendererDebug.checksum(context, coordinates);
+
+		if(!context.isVisible(coordinates))
+			result[7] = rendererFow.checksum(context, coordinates);
+	}
+	return result;
 }
 
 void MapRenderer::renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates)
@@ -636,5 +762,4 @@ void MapRenderer::renderTile(const IMapRendererContext & context, Canvas & targe
 		if(!context.isVisible(coordinates))
 			rendererFow.renderTile(context, target, coordinates);
 	}
-
 }

+ 26 - 6
client/mapRenderer/MapRenderer.h

@@ -9,8 +9,6 @@
  */
 #pragma once
 
-#include "MapRendererContext.h"
-
 VCMI_LIB_NAMESPACE_BEGIN
 
 class int3;
@@ -22,6 +20,8 @@ VCMI_LIB_NAMESPACE_END
 class CAnimation;
 class IImage;
 class Canvas;
+class IMapRendererContext;
+enum class EImageBlitMode : uint8_t;
 
 class MapTileStorage
 {
@@ -30,7 +30,7 @@ class MapTileStorage
 
 public:
 	explicit MapTileStorage(size_t capacity);
-	void load(size_t index, const std::string & filename);
+	void load(size_t index, const std::string & filename, EImageBlitMode blitMode);
 	std::shared_ptr<IImage> find(size_t fileIndex, size_t rotationIndex, size_t imageIndex);
 };
 
@@ -40,6 +40,8 @@ class MapRendererTerrain
 
 public:
 	MapRendererTerrain();
+
+	uint8_t checksum(const IMapRendererContext & context, const int3 & coordinates);
 	void renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates);
 };
 
@@ -49,6 +51,8 @@ class MapRendererRiver
 
 public:
 	MapRendererRiver();
+
+	uint8_t checksum(const IMapRendererContext & context, const int3 & coordinates);
 	void renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates);
 };
 
@@ -58,6 +62,8 @@ class MapRendererRoad
 
 public:
 	MapRendererRoad();
+
+	uint8_t checksum(const IMapRendererContext & context, const int3 & coordinates);
 	void renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates);
 };
 
@@ -77,6 +83,7 @@ class MapRendererObjects
 	void renderObject(const IMapRendererContext & context, Canvas & target, const int3 & coordinates, const CGObjectInstance * obj);
 
 public:
+	uint8_t checksum(const IMapRendererContext & context, const int3 & coordinates);
 	void renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates);
 };
 
@@ -88,6 +95,8 @@ class MapRendererBorder
 
 public:
 	MapRendererBorder();
+
+	uint8_t checksum(const IMapRendererContext & context, const int3 & coordinates);
 	void renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates);
 };
 
@@ -98,6 +107,8 @@ class MapRendererFow
 
 public:
 	MapRendererFow();
+
+	uint8_t checksum(const IMapRendererContext & context, const int3 & coordinates);
 	void renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates);
 };
 
@@ -105,12 +116,15 @@ class MapRendererPath
 {
 	std::unique_ptr<CAnimation> pathNodes;
 
-	void renderImage(Canvas & target, bool reachableToday, size_t imageIndex);
-	void renderImageCross(Canvas & target, bool reachableToday, const int3 & curr);
-	void renderImageArrow(Canvas & target, bool reachableToday, const int3 & curr, const int3 & prev, const int3 & next);
+	size_t selectImageReachability(bool reachableToday, size_t imageIndex);
+	size_t selectImageCross(bool reachableToday, const int3 & curr);
+	size_t selectImageArrow(bool reachableToday, const int3 & curr, const int3 & prev, const int3 & next);
+	size_t selectImage(const IMapRendererContext & context, const int3 & coordinates);
 
 public:
 	MapRendererPath();
+
+	uint8_t checksum(const IMapRendererContext & context, const int3 & coordinates);
 	void renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates);
 };
 
@@ -122,6 +136,7 @@ class MapRendererDebug
 public:
 	MapRendererDebug();
 
+	uint8_t checksum(const IMapRendererContext & context, const int3 & coordinates);
 	void renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates);
 };
 
@@ -131,6 +146,7 @@ class MapRendererOverlay
 public:
 	MapRendererOverlay();
 
+	uint8_t checksum(const IMapRendererContext & context, const int3 & coordinates);
 	void renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates);
 };
 
@@ -146,5 +162,9 @@ class MapRenderer
 	MapRendererDebug rendererDebug;
 
 public:
+	using TileChecksum = std::array<uint8_t, 8>;
+
+	TileChecksum getTileChecksum(const IMapRendererContext & context, const int3 & coordinates);
+
 	void renderTile(const IMapRendererContext & context, Canvas & target, const int3 & coordinates);
 };

+ 3 - 5
client/mapRenderer/MapView.cpp

@@ -14,7 +14,6 @@
 #include "MapViewModel.h"
 #include "MapViewCache.h"
 #include "MapViewController.h"
-#include "MapRendererContext.h"
 #include "mapHandler.h"
 
 #include "../adventureMap/CAdvMapInt.h"
@@ -48,8 +47,7 @@ std::shared_ptr<MapViewModel> MapView::createModel(const Point & dimensions) con
 
 MapView::MapView(const Point & offset, const Point & dimensions)
 	: model(createModel(dimensions))
-	, context(new MapRendererContext())
-	, controller(new MapViewController(context, model))
+	, controller(new MapViewController(model))
 	, tilesCache(new MapViewCache(model))
 {
 	pos += offset;
@@ -65,8 +63,8 @@ void MapView::show(SDL_Surface * to)
 	CSDL_Ext::CClipRectGuard guard(to, pos);
 
 	controller->update(GH.mainFPSmng->getElapsedMilliseconds());
-	tilesCache->update(context);
-	tilesCache->render(context, targetClipped);
+	tilesCache->update(controller->getContext());
+	tilesCache->render(controller->getContext(), targetClipped);
 }
 
 void MapView::showAll(SDL_Surface * to)

+ 0 - 2
client/mapRenderer/MapView.h

@@ -11,7 +11,6 @@
 
 #include "../gui/CIntObject.h"
 
-class MapRendererContext;
 class MapViewController;
 class MapViewModel;
 class MapViewCache;
@@ -20,7 +19,6 @@ class MapViewCache;
 class MapView : public CIntObject
 {
 	std::shared_ptr<MapViewModel> model;
-	std::shared_ptr<MapRendererContext> context;
 	std::unique_ptr<MapViewCache> tilesCache;
 	std::shared_ptr<MapViewController> controller;
 

+ 40 - 4
client/mapRenderer/MapViewCache.cpp

@@ -11,6 +11,7 @@
 #include "StdInc.h"
 #include "MapViewCache.h"
 
+#include "IMapRendererContext.h"
 #include "MapRenderer.h"
 #include "MapViewModel.h"
 
@@ -24,6 +25,7 @@ MapViewCache::~MapViewCache() = default;
 
 MapViewCache::MapViewCache(const std::shared_ptr<MapViewModel> & model)
 	: model(model)
+	, cachedLevel(0)
 	, mapRenderer(new MapRenderer())
 	, iconsStorage(new CAnimation("VwSymbol"))
 	, intermediate(new Canvas(Point(32, 32)))
@@ -32,6 +34,9 @@ MapViewCache::MapViewCache(const std::shared_ptr<MapViewModel> & model)
 	iconsStorage->preload();
 	for(size_t i = 0; i < iconsStorage->size(); ++i)
 		iconsStorage->getImage(i)->setBlitMode(EImageBlitMode::COLORKEY);
+
+	Point visibleSize = model->getTilesVisibleDimensions();
+	terrainChecksum.resize(boost::extents[visibleSize.x][visibleSize.y]);
 }
 
 Canvas MapViewCache::getTile(const int3 & coordinates)
@@ -39,7 +44,7 @@ Canvas MapViewCache::getTile(const int3 & coordinates)
 	return Canvas(*terrain, model->getCacheTileArea(coordinates));
 }
 
-std::shared_ptr<IImage> MapViewCache::getOverlayImageForTile(const std::shared_ptr<MapRendererContext> & context, const int3 & coordinates)
+std::shared_ptr<IImage> MapViewCache::getOverlayImageForTile(const std::shared_ptr<const IMapRendererContext> & context, const int3 & coordinates)
 {
 	size_t imageIndex = context->overlayImageIndex(coordinates);
 
@@ -48,8 +53,28 @@ std::shared_ptr<IImage> MapViewCache::getOverlayImageForTile(const std::shared_p
 	return nullptr;
 }
 
-void MapViewCache::updateTile(const std::shared_ptr<MapRendererContext> & context, const int3 & coordinates)
+void MapViewCache::updateTile(const std::shared_ptr<const IMapRendererContext> & context, const int3 & coordinates)
 {
+	int cacheX = (terrainChecksum.shape()[0] + coordinates.x) % terrainChecksum.shape()[0];
+	int cacheY = (terrainChecksum.shape()[1] + coordinates.y) % terrainChecksum.shape()[1];
+
+	auto & oldCacheEntry = terrainChecksum[cacheX][cacheY];
+	TileChecksum newCacheEntry;
+
+	newCacheEntry.tileX = coordinates.x;
+	newCacheEntry.tileY = coordinates.y;
+	newCacheEntry.checksum = mapRenderer->getTileChecksum(*context, coordinates);
+
+	if (cachedLevel == coordinates.z && oldCacheEntry == newCacheEntry)
+	{
+		// debug code to check caching - cached tiles will slowly fade to black
+		//static const auto overlay = IImage::createFromFile("debug/cached");
+		//Canvas target = getTile(coordinates);
+		//target.draw(overlay, Point(0,0));
+
+		return;
+	}
+
 	Canvas target = getTile(coordinates);
 
 	if(model->getSingleTileSize() == Point(32, 32))
@@ -61,18 +86,29 @@ void MapViewCache::updateTile(const std::shared_ptr<MapRendererContext> & contex
 		mapRenderer->renderTile(*context, *intermediate, coordinates);
 		target.drawScaled(*intermediate, Point(0, 0), model->getSingleTileSize());
 	}
+
+	oldCacheEntry = newCacheEntry;
 }
 
-void MapViewCache::update(const std::shared_ptr<MapRendererContext> & context)
+void MapViewCache::update(const std::shared_ptr<const IMapRendererContext> & context)
 {
 	Rect dimensions = model->getTilesTotalRect();
 
+	if (dimensions.w != terrainChecksum.shape()[0] || dimensions.h != terrainChecksum.shape()[1])
+	{
+		boost::multi_array<TileChecksum, 2> newCache;
+		newCache.resize(boost::extents[dimensions.w][dimensions.h]);
+		std::swap(newCache, terrainChecksum);
+	}
+
 	for(int y = dimensions.top(); y < dimensions.bottom(); ++y)
 		for(int x = dimensions.left(); x < dimensions.right(); ++x)
 			updateTile(context, {x, y, model->getLevel()});
+
+	cachedLevel = model->getLevel();
 }
 
-void MapViewCache::render(const std::shared_ptr<MapRendererContext> & context, Canvas & target)
+void MapViewCache::render(const std::shared_ptr<const IMapRendererContext> & context, Canvas & target)
 {
 	Rect dimensions = model->getTilesTotalRect();
 

+ 20 - 6
client/mapRenderer/MapViewCache.h

@@ -17,14 +17,28 @@ class IImage;
 class CAnimation;
 class Canvas;
 class MapRenderer;
-class MapRendererContext;
-//class MapViewController;
+class IMapRendererContext;
 class MapViewModel;
 
 /// Class responsible for rendering of entire map view
 /// uses rendering parameters provided by owner class
 class MapViewCache
 {
+	struct TileChecksum
+	{
+		int tileX = std::numeric_limits<int>::min();
+		int tileY = std::numeric_limits<int>::min();
+		std::array<uint8_t, 8> checksum {};
+
+		bool operator == (const TileChecksum & other) const
+		{
+			return tileX == other.tileX && tileY == other.tileY && checksum == other.checksum;
+		}
+	};
+
+	boost::multi_array<TileChecksum, 2> terrainChecksum;
+	int cachedLevel;
+
 	std::shared_ptr<MapViewModel> model;
 
 	std::unique_ptr<Canvas> terrain;
@@ -35,16 +49,16 @@ class MapViewCache
 	std::unique_ptr<CAnimation> iconsStorage;
 
 	Canvas getTile(const int3 & coordinates);
-	void updateTile(const std::shared_ptr<MapRendererContext> & context, const int3 & coordinates);
+	void updateTile(const std::shared_ptr<const IMapRendererContext> & context, const int3 & coordinates);
 
-	std::shared_ptr<IImage> getOverlayImageForTile(const std::shared_ptr<MapRendererContext> & context, const int3 & coordinates);
+	std::shared_ptr<IImage> getOverlayImageForTile(const std::shared_ptr<const IMapRendererContext> & context, const int3 & coordinates);
 public:
 	explicit MapViewCache(const std::shared_ptr<MapViewModel> & model);
 	~MapViewCache();
 
 	/// updates internal terrain cache according to provided time delta
-	void update(const std::shared_ptr<MapRendererContext> & context);
+	void update(const std::shared_ptr<const IMapRendererContext> & context);
 
 	/// renders updated terrain cache onto provided canvas
-	void render(const std::shared_ptr<MapRendererContext> &context, Canvas & target);
+	void render(const std::shared_ptr<const IMapRendererContext> &context, Canvas & target);
 };

+ 7 - 2
client/mapRenderer/MapViewController.cpp

@@ -41,12 +41,17 @@ void MapViewController::setTileSize(const Point & tileSize)
 	model->setTileSize(tileSize);
 }
 
-MapViewController::MapViewController(std::shared_ptr<MapRendererContext> context, std::shared_ptr<MapViewModel> model)
-	: context(std::move(context))
+MapViewController::MapViewController(std::shared_ptr<MapViewModel> model)
+	: context(new MapRendererContext())
 	, model(std::move(model))
 {
 }
 
+std::shared_ptr<const IMapRendererContext> MapViewController::getContext() const
+{
+	return context;
+}
+
 void MapViewController::update(uint32_t timeDelta)
 {
 	// confirmed to match H3 for

+ 4 - 1
client/mapRenderer/MapViewController.h

@@ -17,6 +17,7 @@ struct ObjectPosInfo;
 VCMI_LIB_NAMESPACE_END
 
 class MapViewModel;
+class IMapRendererContext;
 class MapRendererContext;
 
 /// Class responsible for updating view state,
@@ -38,7 +39,9 @@ private:
 	void onHeroRotated(const CGHeroInstance * obj, const int3 & from, const int3 & dest) override;
 
 public:
-	MapViewController(std::shared_ptr<MapRendererContext> context, std::shared_ptr<MapViewModel> model);
+	explicit MapViewController(std::shared_ptr<MapViewModel> model);
+
+	std::shared_ptr<const IMapRendererContext> getContext() const;
 
 	void setViewCenter(const int3 & position);
 	void setViewCenter(const Point & position, int level);