1
0

QtBiomeVisualiser: Switched caching to entire regions.

This speeds up the rendering preparation for small zooms.
This commit is contained in:
Mattes D 2014-10-27 23:58:09 +01:00
parent cbb637187e
commit c53b7e5d38
15 changed files with 409 additions and 249 deletions

View File

@ -1,8 +1,8 @@
#include "Globals.h"
#include "BiomeView.h"
#include "QtChunk.h"
#include <QPainter>
#include <QResizeEvent>
#include "Region.h"
@ -40,7 +40,7 @@ BiomeView::BiomeView(QWidget * parent) :
redraw();
// Add a chunk-update callback mechanism:
connect(&m_Cache, SIGNAL(chunkAvailable(int, int)), this, SLOT(chunkAvailable(int, int)));
connect(&m_Cache, SIGNAL(regionAvailable(int, int)), this, SLOT(regionAvailable(int, int)));
// Allow mouse and keyboard interaction:
setFocusPolicy(Qt::StrongFocus);
@ -143,9 +143,15 @@ void BiomeView::redraw()
void BiomeView::chunkAvailable(int a_ChunkX, int a_ChunkZ)
void BiomeView::regionAvailable(int a_RegionX, int a_RegionZ)
{
drawChunk(a_ChunkX, a_ChunkZ);
for (int z = 0; z < 32; z++)
{
for (int x = 0; x < 32; x++)
{
drawChunk(a_RegionX * 32 + x, a_RegionZ * 32 + z);
}
}
update();
}
@ -175,8 +181,11 @@ void BiomeView::drawChunk(int a_ChunkX, int a_ChunkZ)
return;
}
//fetch the chunk:
ChunkPtr chunk = m_Cache.fetch(a_ChunkX, a_ChunkZ);
// Fetch the region:
int regionX;
int regionZ;
Region::chunkToRegion(a_ChunkX, a_ChunkZ, regionX, regionZ);
RegionPtr region = m_Cache.fetch(regionX, regionZ);
// Figure out where on the screen this chunk should be drawn:
// first find the center chunk
@ -228,9 +237,15 @@ void BiomeView::drawChunk(int a_ChunkX, int a_ChunkZ)
// If the chunk is valid, use its data; otherwise use the empty placeholder:
const uchar * src = m_EmptyChunkImage;
if (chunk.get() != nullptr)
if (region.get() != nullptr)
{
src = chunk->getImage();
int relChunkX = a_ChunkX - regionX * 32;
int relChunkZ = a_ChunkZ - regionZ * 32;
Chunk & chunk = region->getRelChunk(relChunkX, relChunkZ);
if (chunk.isValid())
{
src = chunk.getImage();
}
}
// Blit or scale-blit the image:
@ -317,11 +332,12 @@ void BiomeView::mouseMoveEvent(QMouseEvent * a_Event)
// Update the status bar info text:
int blockX = floor((a_Event->x() - width() / 2) / m_Zoom + m_X);
int blockZ = floor((a_Event->y() - height() / 2) / m_Zoom + m_Z);
int chunkX, chunkZ;
int relX = blockX, relY, relZ = blockZ;
cChunkDef::AbsoluteToRelative(relX, relY, relZ, chunkX, chunkZ);
auto chunk = m_Cache.fetch(chunkX, chunkZ);
int biome = (chunk.get() != nullptr) ? chunk->getBiome(relX, relZ) : biInvalidBiome;
int regionX, regionZ;
Region::blockToRegion(blockX, blockZ, regionX, regionZ);
int relX = blockX - regionX * 512;
int relZ = blockZ - regionZ * 512;
auto region = m_Cache.fetch(regionX, regionZ);
int biome = (region.get() != nullptr) ? region->getRelBiome(relX, relZ) : biInvalidBiome;
emit hoverChanged(blockX, blockZ, biome);
}

View File

@ -2,7 +2,7 @@
#include <QWidget>
#include <memory>
#include "ChunkCache.h"
#include "RegionCache.h"
#include "ChunkSource.h"
@ -51,8 +51,8 @@ public slots:
/** Redraw the entire widget area. */
void redraw();
/** A specified chunk has become available, redraw it. */
void chunkAvailable(int a_ChunkX, int a_ChunkZ);
/** A specified region has become available, redraw it. */
void regionAvailable(int a_RegionX, int a_RegionZ);
/** Reloads the current chunk source and redraws the entire workspace. */
void reload();
@ -62,7 +62,7 @@ protected:
double m_Zoom;
/** Cache for the loaded chunk data. */
ChunkCache m_Cache;
RegionCache m_Cache;
/** The entire view's contents in an offscreen image. */
QImage m_Image;

View File

@ -1,126 +0,0 @@
#include "Globals.h"
#include "ChunkCache.h"
#include <QMutexLocker>
#include <QThreadPool>
#include "ChunkSource.h"
#include "ChunkLoader.h"
ChunkCache::ChunkCache(QObject * parent) :
super(parent)
{
m_Cache.setMaxCost(1024 * 1024 * 1024); // 1 GiB of memory for the cache
}
ChunkPtr ChunkCache::fetch(int a_ChunkX, int a_ChunkZ)
{
// Retrieve from the cache:
quint32 hash = getChunkHash(a_ChunkX, a_ChunkZ);
ChunkPtr * res;
{
QMutexLocker lock(&m_Mtx);
res = m_Cache[hash];
// If succesful and chunk loaded, return the retrieved value:
if ((res != nullptr) && (*res)->isValid())
{
return *res;
}
}
// If the chunk is in cache but not valid, it means it has been already queued for rendering, do nothing now:
if (res != nullptr)
{
return ChunkPtr(nullptr);
}
// There's no such item in the cache, create it now:
res = new ChunkPtr(new Chunk);
if (res == nullptr)
{
return ChunkPtr(nullptr);
}
{
QMutexLocker lock(&m_Mtx);
m_Cache.insert(hash, res, sizeof(Chunk));
}
// Queue the chunk for rendering:
queueChunkRender(a_ChunkX, a_ChunkZ, *res);
// Return failure, the chunk is not yet rendered:
return ChunkPtr(nullptr);
}
void ChunkCache::setChunkSource(std::shared_ptr<ChunkSource> a_ChunkSource)
{
// Replace the chunk source:
m_ChunkSource = a_ChunkSource;
// Clear the cache:
QMutexLocker lock(&m_Mtx);
m_Cache.clear();
}
void ChunkCache::reload()
{
assert(m_ChunkSource.get() != nullptr);
// Reload the chunk source:
m_ChunkSource->reload();
// Clear the cache:
QMutexLocker lock(&m_Mtx);
m_Cache.clear();
}
void ChunkCache::gotChunk(int a_ChunkX, int a_ChunkZ)
{
emit chunkAvailable(a_ChunkX, a_ChunkZ);
}
quint32 ChunkCache::getChunkHash(int a_ChunkX, int a_ChunkZ)
{
// Simply join the two coords into a single int
// The coords will never be larger than 16-bits, so we can do this safely
return (((static_cast<quint32>(a_ChunkX) & 0xffff) << 16) | (static_cast<quint32>(a_ChunkZ) & 0xffff));
}
void ChunkCache::queueChunkRender(int a_ChunkX, int a_ChunkZ, ChunkPtr & a_Chunk)
{
// Create a new loader task:
ChunkLoader * loader = new ChunkLoader(a_ChunkX, a_ChunkZ, a_Chunk, m_ChunkSource);
connect(loader, SIGNAL(loaded(int, int)), this, SLOT(gotChunk(int, int)));
QThreadPool::globalInstance()->start(loader);
}

View File

@ -1,29 +0,0 @@
#include "Globals.h"
#include "ChunkLoader.h"
#include "ChunkSource.h"
ChunkLoader::ChunkLoader(int a_ChunkX, int a_ChunkZ, ChunkPtr a_Chunk, ChunkSourcePtr a_ChunkSource) :
m_ChunkX(a_ChunkX),
m_ChunkZ(a_ChunkZ),
m_Chunk(a_Chunk),
m_ChunkSource(a_ChunkSource)
{
}
void ChunkLoader::run()
{
m_ChunkSource->getChunkBiomes(m_ChunkX, m_ChunkZ, m_Chunk);
emit loaded(m_ChunkX, m_ChunkZ);
}

View File

@ -1,45 +0,0 @@
#pragma once
#include <QObject>
#include <QRunnable>
#include <memory>
// fwd:
class Chunk;
typedef std::shared_ptr<Chunk> ChunkPtr;
class ChunkSource;
typedef std::shared_ptr<ChunkSource> ChunkSourcePtr;
class ChunkLoader :
public QObject,
public QRunnable
{
Q_OBJECT
public:
ChunkLoader(int a_ChunkX, int a_ChunkZ, ChunkPtr a_Chunk, ChunkSourcePtr a_ChunkSource);
virtual ~ChunkLoader() {}
signals:
void loaded(int a_ChunkX, int a_ChunkZ);
protected:
virtual void run() override;
private:
int m_ChunkX, m_ChunkZ;
ChunkPtr m_Chunk;
ChunkSourcePtr m_ChunkSource;
};

View File

@ -24,14 +24,14 @@ BioGenSource::BioGenSource(cIniFilePtr a_IniFile) :
void BioGenSource::getChunkBiomes(int a_ChunkX, int a_ChunkZ, ChunkPtr a_DestChunk)
void BioGenSource::getChunkBiomes(int a_ChunkX, int a_ChunkZ, Chunk & a_DestChunk)
{
cChunkDef::BiomeMap biomes;
{
QMutexLocker lock(&m_Mtx);
m_BiomeGen->GenBiomes(a_ChunkX, a_ChunkZ, biomes);
}
a_DestChunk->setBiomes(biomes);
a_DestChunk.setBiomes(biomes);
}
@ -160,7 +160,7 @@ AnvilSource::AnvilSource(QString a_WorldRegionFolder) :
void AnvilSource::getChunkBiomes(int a_ChunkX, int a_ChunkZ, ChunkPtr a_DestChunk)
void AnvilSource::getChunkBiomes(int a_ChunkX, int a_ChunkZ, Chunk & a_DestChunk)
{
// Load the compressed data:
AString compressedChunkData = getCompressedChunkData(a_ChunkX, a_ChunkZ);
@ -200,7 +200,7 @@ void AnvilSource::getChunkBiomes(int a_ChunkX, int a_ChunkZ, ChunkPtr a_DestChun
{
biomeMap[i] = (EMCSBiome)GetBEInt(beBiomes + 4 * i);
}
a_DestChunk->setBiomes(biomeMap);
a_DestChunk.setBiomes(biomeMap);
return;
}
@ -216,7 +216,7 @@ void AnvilSource::getChunkBiomes(int a_ChunkX, int a_ChunkZ, ChunkPtr a_DestChun
{
biomeMap[i] = EMCSBiome(vanillaBiomes[i]);
}
a_DestChunk->setBiomes(biomeMap);
a_DestChunk.setBiomes(biomeMap);
}

View File

@ -26,7 +26,7 @@ public:
/** Fills the a_DestChunk with the biomes for the specified coords.
It is expected to be thread-safe and re-entrant. Usually QThread::idealThreadCount() threads are used. */
virtual void getChunkBiomes(int a_ChunkX, int a_ChunkZ, ChunkPtr a_DestChunk) = 0;
virtual void getChunkBiomes(int a_ChunkX, int a_ChunkZ, Chunk & a_DestChunk) = 0;
/** Forces a fresh reload of the source. Useful mainly for the generator, whose underlying definition file may have been changed. */
virtual void reload() = 0;
@ -45,7 +45,7 @@ public:
BioGenSource(cIniFilePtr a_IniFile);
// ChunkSource overrides:
virtual void getChunkBiomes(int a_ChunkX, int a_ChunkZ, ChunkPtr a_DestChunk) override;
virtual void getChunkBiomes(int a_ChunkX, int a_ChunkZ, Chunk & a_DestChunk) override;
virtual void reload(void) override;
protected:
@ -70,7 +70,7 @@ public:
AnvilSource(QString a_WorldRegionFolder);
// ChunkSource overrides:
virtual void getChunkBiomes(int a_ChunkX, int a_ChunkZ, ChunkPtr a_DestChunk) override;
virtual void getChunkBiomes(int a_ChunkX, int a_ChunkZ, Chunk & a_DestChunk) override;
virtual void reload() override;
protected:

View File

@ -8,8 +8,8 @@
#include <QSettings>
#include <QDirIterator>
#include <QStatusBar>
#include "src/IniFile.h"
#include "ChunkSource.h"
#include "src/IniFile.h"
#include "src/Generating/BioGen.h"
#include "src/StringCompression.h"
#include "src/WorldStorage/FastNBT.h"

View File

@ -26,9 +26,6 @@ SOURCES +=\
../../src/OSSupport/CriticalSection.cpp \
../../src/OSSupport/IsThread.cpp \
../../src/BiomeDef.cpp \
ChunkCache.cpp \
ChunkSource.cpp \
ChunkLoader.cpp \
../../src/StringCompression.cpp \
../../src/WorldStorage/FastNBT.cpp \
../../lib/zlib/adler32.c \
@ -48,7 +45,11 @@ SOURCES +=\
../../lib/zlib/zutil.c \
GeneratorSetup.cpp \
QtBiomeVisualiser.cpp \
QtChunk.cpp
QtChunk.cpp \
RegionCache.cpp \
Region.cpp \
ChunkSource.cpp \
RegionLoader.cpp
HEADERS += MainWindow.h \
Globals.h \
@ -64,9 +65,6 @@ HEADERS += MainWindow.h \
../../src/OSSupport/CriticalSection.h \
../../src/OSSupport/IsThread.h \
../../src/BiomeDef.h \
ChunkCache.h \
ChunkSource.h \
ChunkLoader.h \
../../src/StringCompression.h \
../../src/WorldStorage/FastNBT.h \
../../lib/zlib/crc32.h \
@ -81,7 +79,11 @@ HEADERS += MainWindow.h \
../../lib/zlib/zlib.h \
../../lib/zlib/zutil.h \
GeneratorSetup.h \
QtChunk.h
QtChunk.h \
RegionCache.h \
Region.h \
ChunkSource.h \
RegionLoader.h
INCLUDEPATH += $$_PRO_FILE_PWD_ \
$$_PRO_FILE_PWD_/../../lib \

View File

@ -0,0 +1,72 @@
#include "Globals.h"
#include "Region.h"
Region::Region()
{
}
Chunk & Region::getRelChunk(int a_RelChunkX, int a_RelChunkZ)
{
ASSERT(a_RelChunkX >= 0);
ASSERT(a_RelChunkZ >= 0);
ASSERT(a_RelChunkX < 32);
ASSERT(a_RelChunkZ < 32);
return m_Chunks[a_RelChunkX + a_RelChunkZ * 32];
}
int Region::getRelBiome(int a_RelBlockX, int a_RelBlockZ)
{
ASSERT(a_RelBlockX >= 0);
ASSERT(a_RelBlockZ >= 0);
ASSERT(a_RelBlockX < 512);
ASSERT(a_RelBlockZ < 512);
int chunkX = a_RelBlockX / 16;
int chunkZ = a_RelBlockZ / 16;
Chunk & chunk = m_Chunks[chunkX + 32 * chunkZ];
if (chunk.isValid())
{
return chunk.getBiome(a_RelBlockX - 16 * chunkX, a_RelBlockZ - 16 * chunkZ);
}
else
{
return biInvalidBiome;
}
}
void Region::blockToRegion(int a_BlockX, int a_BlockZ, int & a_RegionX, int & a_RegionZ)
{
a_RegionX = static_cast<int>(std::floor(static_cast<float>(a_BlockX) / 512));
a_RegionZ = static_cast<int>(std::floor(static_cast<float>(a_BlockZ) / 512));
}
void Region::chunkToRegion(int a_ChunkX, int a_ChunkZ, int & a_RegionX, int & a_RegionZ)
{
a_RegionX = static_cast<int>(std::floor(static_cast<float>(a_ChunkX) / 32));
a_RegionZ = static_cast<int>(std::floor(static_cast<float>(a_ChunkZ) / 32));
}

View File

@ -0,0 +1,44 @@
#pragma once
#include "QtChunk.h"
class Region
{
public:
Region();
/** Retrieves the chunk with the specified relative coords. */
Chunk & getRelChunk(int a_RelChunkX, int a_RelChunkZ);
/** Returns true iff the chunk data for all chunks has been loaded.
This doesn't mean that all the chunks are valid, only that the entire region has been processed and should
be displayed. */
bool isValid(void) const { return m_IsValid; }
/** Returns the biome in the block coords relative to this region.
Returns biInvalidBiome if the underlying chunk is not valid. */
int getRelBiome(int a_RelBlockX, int a_RelBlockZ);
/** Converts block coordinates into region coordinates. */
static void blockToRegion(int a_BlockX, int a_BlockZ, int & a_RegionX, int & a_RegionZ);
/** Converts chunk coordinates into region coordinates. */
static void chunkToRegion(int a_ChunkX, int a_ChunkZ, int & a_RegionX, int & a_RegionZ);
protected:
Chunk m_Chunks[32 * 32];
/** True iff the data for all the chunks has been loaded.
This doesn't mean that all the chunks are valid, only that the entire region has been processed and should
be displayed. */
bool m_IsValid;
};

View File

@ -0,0 +1,138 @@
#include "Globals.h"
#include "RegionCache.h"
#include <QMutexLocker>
#include <QThreadPool>
#include "ChunkSource.h"
#include "RegionLoader.h"
#include "Region.h"
RegionCache::RegionCache(QObject * parent) :
super(parent)
{
m_Cache.setMaxCost(1024 * 1024 * 1024); // 1 GiB of memory for the cache
}
RegionPtr RegionCache::fetch(int a_RegionX, int a_RegionZ)
{
// Retrieve from the cache:
quint32 hash = getRegionHash(a_RegionX, a_RegionZ);
RegionPtr * res;
{
QMutexLocker lock(&m_Mtx);
res = m_Cache[hash];
// If succesful and region loaded, return the retrieved value:
if ((res != nullptr) && (*res)->isValid())
{
return *res;
}
}
// If the region is in cache but not valid, it means it has been already queued for rendering, do nothing now:
if (res != nullptr)
{
return RegionPtr(nullptr);
}
// There's no such item in the cache, create it now:
try
{
res = new RegionPtr(new Region);
}
catch (const std::bad_alloc &)
{
/* Allocation failed (32-bit process hit the 2 GiB barrier?)
This may happen even with the cache set to 1 GiB, because it contains shared ptrs and so they may be
held by another place in the code even when they are removed from cache.
*/
return RegionPtr(nullptr);
}
if (res == nullptr)
{
return RegionPtr(nullptr);
}
{
QMutexLocker lock(&m_Mtx);
m_Cache.insert(hash, res, sizeof(Region));
}
// Queue the region for rendering:
queueRegionRender(a_RegionX, a_RegionZ, *res);
// Return failure, the region is not yet rendered:
return RegionPtr(nullptr);
}
void RegionCache::setChunkSource(std::shared_ptr<ChunkSource> a_ChunkSource)
{
// Replace the chunk source:
m_ChunkSource = a_ChunkSource;
// Clear the cache:
QMutexLocker lock(&m_Mtx);
m_Cache.clear();
}
void RegionCache::reload()
{
assert(m_ChunkSource.get() != nullptr);
// Reload the chunk source:
m_ChunkSource->reload();
// Clear the cache:
QMutexLocker lock(&m_Mtx);
m_Cache.clear();
}
void RegionCache::gotRegion(int a_RegionX, int a_RegionZ)
{
emit regionAvailable(a_RegionX, a_RegionZ);
}
quint32 RegionCache::getRegionHash(int a_RegionX, int a_RegionZ)
{
// Simply join the two coords into a single int
// The coords will never be larger than 16-bits, so we can do this safely
return (((static_cast<quint32>(a_RegionX) & 0xffff) << 16) | (static_cast<quint32>(a_RegionZ) & 0xffff));
}
void RegionCache::queueRegionRender(int a_RegionX, int a_RegionZ, RegionPtr & a_Region)
{
// Create a new loader task:
RegionLoader * loader = new RegionLoader(a_RegionX, a_RegionZ, a_Region, m_ChunkSource);
connect(loader, SIGNAL(loaded(int, int)), this, SLOT(gotRegion(int, int)));
QThreadPool::globalInstance()->start(loader);
}

View File

@ -9,8 +9,9 @@
class Chunk;
typedef std::shared_ptr<Chunk> ChunkPtr;
// fwd:
class Region;
typedef std::shared_ptr<Region> RegionPtr;
class ChunkSource;
@ -18,19 +19,19 @@ class ChunkSource;
/** Caches chunk data for reuse */
class ChunkCache :
/** Caches regions' chunk data for reuse */
class RegionCache :
public QObject
{
typedef QObject super;
Q_OBJECT
public:
explicit ChunkCache(QObject * parent = NULL);
explicit RegionCache(QObject * parent = NULL);
/** Retrieves the specified chunk from the cache.
Only returns valid chunks; if the chunk is invalid, queues it for rendering and returns an empty ptr. */
ChunkPtr fetch(int a_ChunkX, int a_ChunkZ);
/** Retrieves the specified region from the cache.
Only returns valid regions; if the region is invalid, queues it for rendering and returns an empty ptr. */
RegionPtr fetch(int a_RegionX, int a_RegionZ);
/** Replaces the chunk source used by the biome view to get the chunk biome data.
The cache is then invalidated. */
@ -43,16 +44,16 @@ public:
void reload();
signals:
void chunkAvailable(int a_ChunkX, int a_ChunkZ);
void regionAvailable(int a_RegionX, int a_RegionZ);
protected slots:
void gotChunk(int a_ChunkX, int a_ChunkZ);
void gotRegion(int a_RegionX, int a_RegionZ);
protected:
/** The cache of the chunks */
QCache<quint32, ChunkPtr> m_Cache;
QCache<quint32, RegionPtr> m_Cache;
/** Locks te cache against multithreaded access */
/** Locks the cache against multithreaded access */
QMutex m_Mtx;
/** The source used to get the biome data. */
@ -60,10 +61,10 @@ protected:
/** Returns the hash used by the chunk in the cache */
quint32 getChunkHash(int a_ChunkX, int a_ChunkZ);
quint32 getRegionHash(int a_RegionX, int a_RegionZ);
/** Queues the specified chunk for rendering by m_ChunkSource. */
void queueChunkRender(int a_ChunkX, int a_ChunkZ, ChunkPtr & a_Chunk);
/** Queues the specified region for rendering by m_RegionSource. */
void queueRegionRender(int a_RegionX, int a_RegionZ, RegionPtr & a_Region);
};

View File

@ -0,0 +1,37 @@
#include "Globals.h"
#include "RegionLoader.h"
#include "ChunkSource.h"
#include "Region.h"
RegionLoader::RegionLoader(int a_RegionX, int a_RegionZ, RegionPtr a_Region, ChunkSourcePtr a_ChunkSource) :
m_RegionX(a_RegionX),
m_RegionZ(a_RegionZ),
m_Region(a_Region),
m_ChunkSource(a_ChunkSource)
{
}
void RegionLoader::run()
{
// Load all the chunks in this region:
for (int z = 0; z < 32; z++)
{
for (int x = 0; x < 32; x++)
{
m_ChunkSource->getChunkBiomes(m_RegionX * 32 + x, m_RegionZ * 32 + z, m_Region->getRelChunk(x, z));
}
}
emit loaded(m_RegionX, m_RegionZ);
}

View File

@ -0,0 +1,50 @@
#pragma once
#include <QObject>
#include <QRunnable>
#include <memory>
// fwd:
class Region;
typedef std::shared_ptr<Region> RegionPtr;
class ChunkSource;
typedef std::shared_ptr<ChunkSource> ChunkSourcePtr;
class RegionLoader :
public QObject,
public QRunnable
{
Q_OBJECT
public:
RegionLoader(int a_RegionX, int a_RegionZ, RegionPtr a_Region, ChunkSourcePtr a_ChunkSource);
virtual ~RegionLoader() {}
signals:
void loaded(int a_RegionX, int a_RegionZ);
protected:
virtual void run() override;
private:
/** Coords of the region to be loaded. */
int m_RegionX, m_RegionZ;
/** The region to be loaded. */
RegionPtr m_Region;
/** The chunk source to be used for individual chunks within the region. */
ChunkSourcePtr m_ChunkSource;
};