Terrain: geometry is now loaded in background threads.

TODO: background load layer textures and blendmaps.
"Distant land" setting has been removed for now (i.e. always enabled).
actorid
scrawl 11 years ago
parent b3fed853ae
commit 195071efc7

@ -651,6 +651,11 @@ void RenderingManager::setGlare(bool glare)
void RenderingManager::requestMap(MWWorld::CellStore* cell)
{
// FIXME: move to other method
// TODO: probably not needed when crossing a cell border. Could delay the map render until we are loaded.
if (mTerrain)
mTerrain->syncLoad();
if (cell->getCell()->isExterior())
{
assert(mTerrain);

@ -164,9 +164,9 @@ namespace MWRender
}
void TerrainStorage::fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align,
Ogre::HardwareVertexBufferSharedPtr vertexBuffer,
Ogre::HardwareVertexBufferSharedPtr normalBuffer,
Ogre::HardwareVertexBufferSharedPtr colourBuffer)
std::vector<float>& positions,
std::vector<float>& normals,
std::vector<Ogre::uint8>& colours)
{
// LOD level n means every 2^n-th vertex is kept
size_t increment = 1 << lodLevel;
@ -180,11 +180,8 @@ namespace MWRender
size_t numVerts = size*(ESM::Land::LAND_SIZE-1)/increment + 1;
std::vector<uint8_t> colors;
colors.resize(numVerts*numVerts*4);
std::vector<float> positions;
colours.resize(numVerts*numVerts*4);
positions.resize(numVerts*numVerts*3);
std::vector<float> normals;
normals.resize(numVerts*numVerts*3);
Ogre::Vector3 normal;
@ -270,7 +267,7 @@ namespace MWRender
color.a = 1;
Ogre::uint32 rsColor;
Ogre::Root::getSingleton().getRenderSystem()->convertColourValue(color, &rsColor);
memcpy(&colors[vertX*numVerts*4 + vertY*4], &rsColor, sizeof(Ogre::uint32));
memcpy(&colours[vertX*numVerts*4 + vertY*4], &rsColor, sizeof(Ogre::uint32));
++vertX;
}
@ -283,10 +280,6 @@ namespace MWRender
assert(vertX_ == numVerts); // Ensure we covered whole area
}
assert(vertY_ == numVerts); // Ensure we covered whole area
vertexBuffer->writeData(0, vertexBuffer->getSizeInBytes(), &positions[0], true);
normalBuffer->writeData(0, normalBuffer->getSizeInBytes(), &normals[0], true);
colourBuffer->writeData(0, colourBuffer->getSizeInBytes(), &colors[0], true);
}
TerrainStorage::UniqueTextureId TerrainStorage::getVtexIndexAt(int cellX, int cellY,

@ -19,8 +19,8 @@ namespace MWRender
/// Get bounds of the whole terrain in cell units
virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY);
/// Get the minimum and maximum heights of a terrain chunk.
/// @note Should only be called for chunks <= 1 cell, i.e. leafs of the quad tree.
/// Get the minimum and maximum heights of a terrain region.
/// @note Will only be called for chunks with size = minBatchSize, i.e. leafs of the quad tree.
/// Larger chunks can simply merge AABB of children.
/// @param size size of the chunk in cell units
/// @param center center of the chunk in cell units
@ -30,16 +30,18 @@ namespace MWRender
virtual bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max);
/// Fill vertex buffers for a terrain chunk.
/// @note May be called from background threads. Make sure to only call thread-safe functions from here!
/// @note returned colors need to be in render-system specific format! Use RenderSystem::convertColourValue.
/// @param lodLevel LOD level, 0 = most detailed
/// @param size size of the terrain chunk in cell units
/// @param center center of the chunk in cell units
/// @param vertexBuffer buffer to write vertices
/// @param normalBuffer buffer to write vertex normals
/// @param colourBuffer buffer to write vertex colours
/// @param positions buffer to write vertices
/// @param normals buffer to write vertex normals
/// @param colours buffer to write vertex colours
virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align,
Ogre::HardwareVertexBufferSharedPtr vertexBuffer,
Ogre::HardwareVertexBufferSharedPtr normalBuffer,
Ogre::HardwareVertexBufferSharedPtr colourBuffer);
std::vector<float>& positions,
std::vector<float>& normals,
std::vector<Ogre::uint8>& colours);
/// Create textures holding layer blend values for a terrain chunk.
/// @note The terrain chunk shouldn't be larger than one cell since otherwise we might

@ -388,6 +388,8 @@ namespace MWWorld
typedef std::vector<ESM::LandTexture>::const_iterator iterator;
// Must be threadsafe! Called from terrain background loading threads.
// Not a big deal here, since ESM::LandTexture can never be modified or inserted/erased
const ESM::LandTexture *search(size_t index, size_t plugin) const {
assert(plugin < mStatic.size());
const LandTextureList &ltexl = mStatic[plugin];
@ -487,6 +489,8 @@ namespace MWWorld
return iterator(mStatic.end());
}
// Must be threadsafe! Called from terrain background loading threads.
// Not a big deal here, since ESM::Land can never be modified or inserted/erased
ESM::Land *search(int x, int y) const {
ESM::Land land;
land.mX = x, land.mY = y;

@ -10,9 +10,9 @@
namespace Terrain
{
Chunk::Chunk(QuadTreeNode* node, short lodLevel)
Chunk::Chunk(QuadTreeNode* node, const LoadResponseData& data)
: mNode(node)
, mVertexLod(lodLevel)
, mVertexLod(node->getNativeLodLevel())
, mAdditionalLod(0)
{
mVertexData = OGRE_NEW Ogre::VertexData;
@ -20,6 +20,8 @@ namespace Terrain
unsigned int verts = mNode->getTerrain()->getStorage()->getCellVertices();
size_t lodLevel = mNode->getNativeLodLevel();
// Set the total number of vertices
size_t numVertsOneSide = mNode->getSize() * (verts-1);
numVertsOneSide /= 1 << lodLevel;
@ -52,8 +54,9 @@ namespace Terrain
mColourBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_COLOUR),
mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC);
mNode->getTerrain()->getStorage()->fillVertexBuffers(lodLevel, mNode->getSize(), mNode->getCenter(), mNode->getTerrain()->getAlign(),
mVertexBuffer, mNormalBuffer, mColourBuffer);
mVertexBuffer->writeData(0, mVertexBuffer->getSizeInBytes(), &data.mPositions[0], true);
mNormalBuffer->writeData(0, mNormalBuffer->getSizeInBytes(), &data.mNormals[0], true);
mColourBuffer->writeData(0, mColourBuffer->getSizeInBytes(), &data.mColours[0], true);
mVertexData->vertexBufferBinding->setBinding(0, mVertexBuffer);
mVertexData->vertexBufferBinding->setBinding(1, mNormalBuffer);

@ -8,6 +8,7 @@ namespace Terrain
{
class QuadTreeNode;
struct LoadResponseData;
/**
* @brief Renders a chunk of terrain, either using alpha splatting or a composite map.
@ -15,8 +16,8 @@ namespace Terrain
class Chunk : public Ogre::Renderable, public Ogre::MovableObject
{
public:
/// @param lodLevel LOD level for the vertex buffer.
Chunk (QuadTreeNode* node, short lodLevel);
Chunk (QuadTreeNode* node, const LoadResponseData& data);
virtual ~Chunk();
void setMaterial (const Ogre::MaterialPtr& material);

@ -36,6 +36,13 @@ namespace Terrain
}
}
struct LayerInfo
{
std::string mDiffuseMap;
std::string mNormalMap;
bool mParallax; // Height info in normal map alpha channel?
bool mSpecular; // Specular info in diffuse map alpha channel?
};
}
#endif

@ -153,6 +153,7 @@ QuadTreeNode::QuadTreeNode(World* terrain, ChildDirection dir, float size, const
, mParent(parent)
, mTerrain(terrain)
, mChunk(NULL)
, mLoadState(LS_Unloaded)
{
mBounds.setNull();
for (int i=0; i<4; ++i)
@ -266,8 +267,6 @@ void QuadTreeNode::update(const Ogre::Vector3 &cameraPos)
float dist = distance(mWorldBounds, cameraPos);
bool distantLand = mTerrain->getDistantLandEnabled();
// Make sure our scene node is attached
if (!mSceneNode->isInSceneGraph())
{
@ -292,46 +291,21 @@ void QuadTreeNode::update(const Ogre::Vector3 &cameraPos)
else if (dist > cellWorldSize)
wantedLod = 1;
bool hadChunk = hasChunk();
if (!distantLand && dist > 8192*2)
{
if (mIsActive)
{
destroyChunks(true);
mIsActive = false;
}
return;
}
mIsActive = true;
if (mSize <= mTerrain->getMaxBatchSize() && mLodLevel <= wantedLod)
{
// Wanted LOD is small enough to render this node in one chunk
if (!mChunk)
{
mChunk = new Chunk(this, mLodLevel);
mChunk->setVisibilityFlags(mTerrain->getVisiblityFlags());
mChunk->setCastShadows(true);
mSceneNode->attachObject(mChunk);
bool wantToDisplay = mSize <= mTerrain->getMaxBatchSize() && mLodLevel <= wantedLod;
mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled());
mMaterialGenerator->enableSplitShadows(mTerrain->getSplitShadowsEnabled());
if (mSize == 1)
if (wantToDisplay)
{
ensureLayerInfo();
mChunk->setMaterial(mMaterialGenerator->generate(mChunk->getMaterial()));
}
else
// Wanted LOD is small enough to render this node in one chunk
if (mLoadState == LS_Unloaded)
{
ensureCompositeMap();
mMaterialGenerator->setCompositeMap(mCompositeMap->getName());
mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap(mChunk->getMaterial()));
}
mLoadState = LS_Loading;
mTerrain->queueLoad(this);
}
if (mLoadState == LS_Loaded)
{
// Additional (index buffer) LOD is currently disabled.
// This is due to a problem with the LOD selection when a node splits.
// After splitting, the distance is measured from the children's bounding boxes, which are possibly
@ -352,40 +326,75 @@ void QuadTreeNode::update(const Ogre::Vector3 &cameraPos)
// But this "solution" does increase triangle overhead, so eventually we need to find a more clever way.
//mChunk->setAdditionalLod(wantedLod - mLodLevel);
mChunk->setVisible(true);
if (!hadChunk && hasChildren())
if (!mChunk->getVisible() && hasChildren())
{
// Make sure child scene nodes are detached
mSceneNode->removeAllChildren();
// If distant land is enabled, keep the chunks around in case we need them again,
// otherwise, prefer low memory usage
if (!distantLand)
for (int i=0; i<4; ++i)
mChildren[i]->destroyChunks(true);
// TODO: unload
//for (int i=0; i<4; ++i)
// mChildren[i]->unload();
}
mChunk->setVisible(true);
}
else
}
if (wantToDisplay && mLoadState != LS_Loaded)
{
// Wanted LOD is too detailed to be rendered in one chunk,
// so split it up by delegating to child nodes
if (hadChunk)
// We want to display, but aren't ready yet. Perhaps our child nodes are ready?
// TODO: this doesn't check child-child nodes...
if (hasChildren())
{
// If distant land is enabled, keep the chunks around in case we need them again,
// otherwise, prefer low memory usage
if (!distantLand)
destroyChunks(false);
else if (mChunk)
mChunk->setVisible(false);
for (int i=0; i<4; ++i)
if (mChildren[i]->hasChunk())
mChildren[i]->update(cameraPos);
}
assert(hasChildren() && "Leaf node's LOD needs to be 0");
}
if (!wantToDisplay)
{
// We do not want to display this node - delegate to children
if (mChunk)
mChunk->setVisible(false);
if (hasChildren())
{
for (int i=0; i<4; ++i)
mChildren[i]->update(cameraPos);
}
}
}
void QuadTreeNode::load(const LoadResponseData &data)
{
assert (!mChunk);
std::cout << "loading " << std::endl;
mChunk = new Chunk(this, data);
mChunk->setVisibilityFlags(mTerrain->getVisiblityFlags());
mChunk->setCastShadows(true);
mSceneNode->attachObject(mChunk);
mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled());
mMaterialGenerator->enableSplitShadows(mTerrain->getSplitShadowsEnabled());
if (mSize == 1)
{
ensureLayerInfo();
mChunk->setMaterial(mMaterialGenerator->generate(mChunk->getMaterial()));
}
else
{
ensureCompositeMap();
mMaterialGenerator->setCompositeMap(mCompositeMap->getName());
mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap(mChunk->getMaterial()));
}
mChunk->setVisible(false);
mLoadState = LS_Loaded;
}
void QuadTreeNode::destroyChunks(bool children)
void QuadTreeNode::unload()
{
if (mChunk)
{
@ -411,9 +420,7 @@ void QuadTreeNode::destroyChunks(bool children)
mCompositeMap.setNull();
}
}
else if (children && hasChildren())
for (int i=0; i<4; ++i)
mChildren[i]->destroyChunks(true);
mLoadState = LS_Unloaded;
}
void QuadTreeNode::updateIndexBuffers()

@ -15,6 +15,7 @@ namespace Terrain
class World;
class Chunk;
class MaterialGenerator;
struct LoadResponseData;
enum Direction
{
@ -33,6 +34,13 @@ namespace Terrain
Root
};
enum LoadState
{
LS_Unloaded,
LS_Loading,
LS_Loaded
};
/**
* @brief A node in the quad tree for our terrain. Depending on LOD,
* a node can either choose to render itself in one batch (merging its children),
@ -124,10 +132,18 @@ namespace Terrain
/// @param quads collect quads here so they can be deleted later
void prepareForCompositeMap(Ogre::TRect<float> area);
/// Create a chunk for this node from the given data.
void load (const LoadResponseData& data);
void unload();
LoadState getLoadState() { return mLoadState; }
private:
// Stored here for convenience in case we need layer list again
MaterialGenerator* mMaterialGenerator;
LoadState mLoadState;
/// Is this node (or any of its child nodes) currently configured to render itself?
/// (only relevant when distant land is disabled, otherwise whole terrain is always rendered)
bool mIsActive;

@ -7,15 +7,6 @@
namespace Terrain
{
struct LayerInfo
{
std::string mDiffuseMap;
std::string mNormalMap;
bool mParallax; // Height info in normal map alpha channel?
bool mSpecular; // Specular info in diffuse map alpha channel?
};
/// We keep storage of terrain data abstract here since we need different implementations for game and editor
class Storage
{
@ -26,8 +17,8 @@ namespace Terrain
/// Get bounds of the whole terrain in cell units
virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) = 0;
/// Get the minimum and maximum heights of a terrain chunk.
/// @note Should only be called for chunks <= 1 cell, i.e. leafs of the quad tree.
/// Get the minimum and maximum heights of a terrain region.
/// @note Will only be called for chunks with size = minBatchSize, i.e. leafs of the quad tree.
/// Larger chunks can simply merge AABB of children.
/// @param size size of the chunk in cell units
/// @param center center of the chunk in cell units
@ -37,16 +28,18 @@ namespace Terrain
virtual bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max) = 0;
/// Fill vertex buffers for a terrain chunk.
/// @note May be called from background threads. Make sure to only call thread-safe functions from here!
/// @note returned colors need to be in render-system specific format! Use RenderSystem::convertColourValue.
/// @param lodLevel LOD level, 0 = most detailed
/// @param size size of the terrain chunk in cell units
/// @param center center of the chunk in cell units
/// @param vertexBuffer buffer to write vertices
/// @param normalBuffer buffer to write vertex normals
/// @param colourBuffer buffer to write vertex colours
/// @param positions buffer to write vertices
/// @param normals buffer to write vertex normals
/// @param colours buffer to write vertex colours
virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align,
Ogre::HardwareVertexBufferSharedPtr vertexBuffer,
Ogre::HardwareVertexBufferSharedPtr normalBuffer,
Ogre::HardwareVertexBufferSharedPtr colourBuffer) = 0;
std::vector<float>& positions,
std::vector<float>& normals,
std::vector<Ogre::uint8>& colours) = 0;
/// Create textures holding layer blend values for a terrain chunk.
/// @note The terrain chunk shouldn't be larger than one cell since otherwise we might

@ -64,6 +64,8 @@ namespace Terrain
, mMinX(0)
, mMaxY(0)
, mMinY(0)
, mChunksLoading(0)
, mWorkQueueChannel(0)
{
#if TERRAIN_USE_SHADER == 0
if (mShaders)
@ -103,10 +105,19 @@ namespace Terrain
//loadingListener->indicateProgress();
mRootNode->initNeighbours();
//loadingListener->indicateProgress();
Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue();
mWorkQueueChannel = wq->getChannel("LargeTerrain");
wq->addRequestHandler(mWorkQueueChannel, this);
wq->addResponseHandler(mWorkQueueChannel, this);
}
World::~World()
{
Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue();
wq->removeRequestHandler(mWorkQueueChannel, this);
wq->removeResponseHandler(mWorkQueueChannel, this);
delete mRootNode;
delete mStorage;
}
@ -445,4 +456,62 @@ namespace Terrain
}
}
void World::syncLoad()
{
while (mChunksLoading)
{
OGRE_THREAD_SLEEP(0);
Ogre::Root::getSingleton().getWorkQueue()->processResponses();
}
}
Ogre::WorkQueue::Response* World::handleRequest(const Ogre::WorkQueue::Request *req, const Ogre::WorkQueue *srcQ)
{
const LoadRequestData data = Ogre::any_cast<LoadRequestData>(req->getData());
QuadTreeNode* node = data.mNode;
LoadResponseData* responseData = new LoadResponseData();
Ogre::Timer timer;
getStorage()->fillVertexBuffers(node->getNativeLodLevel(), node->getSize(), node->getCenter(), getAlign(),
responseData->mPositions, responseData->mNormals, responseData->mColours);
std::cout << "THREAD" << std::endl;
responseData->time = timer.getMicroseconds();
return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData));
}
void World::handleResponse(const Ogre::WorkQueue::Response *res, const Ogre::WorkQueue *srcQ)
{
static unsigned long time = 0;
if (res->succeeded())
{
LoadResponseData* data = Ogre::any_cast<LoadResponseData*>(res->getData());
const LoadRequestData requestData = Ogre::any_cast<LoadRequestData>(res->getRequest()->getData());
requestData.mNode->load(*data);
time += data->time;
delete data;
std::cout << "RESPONSE, reqs took ms" << time/1000.f << std::endl;
}
--mChunksLoading;
}
void World::queueLoad(QuadTreeNode *node)
{
LoadRequestData data;
data.mNode = node;
data.mPack = getShadersEnabled();
const Ogre::uint16 loadRequestId = 1;
Ogre::Root::getSingleton().getWorkQueue()->addRequest(mWorkQueueChannel, loadRequestId, Ogre::Any(data));
++mChunksLoading;
}
}

@ -5,6 +5,7 @@
#include <OgreHardwareVertexBuffer.h>
#include <OgreAxisAlignedBox.h>
#include <OgreTexture.h>
#include <OgreWorkQueue.h>
#include "defs.hpp"
@ -26,7 +27,7 @@ namespace Terrain
* Cracks at LOD transitions are avoided using stitching.
* @note Multiple cameras are not supported yet
*/
class World
class World : public Ogre::WorkQueue::RequestHandler, public Ogre::WorkQueue::ResponseHandler
{
public:
/// @note takes ownership of \a storage
@ -85,7 +86,16 @@ namespace Terrain
Alignment getAlign() { return mAlign; }
/// Wait until all background loading is complete.
void syncLoad();
private:
// Called from a background worker thread
Ogre::WorkQueue::Response* handleRequest(const Ogre::WorkQueue::Request* req, const Ogre::WorkQueue* srcQ);
// Called from the main thread
void handleResponse(const Ogre::WorkQueue::Response* res, const Ogre::WorkQueue* srcQ);
Ogre::uint16 mWorkQueueChannel;
bool mDistantLand;
bool mShaders;
bool mShadows;
@ -99,6 +109,9 @@ namespace Terrain
int mVisibilityFlags;
/// The number of chunks currently loading in a background thread. If 0, we have finished loading!
int mChunksLoading;
Ogre::SceneManager* mSceneMgr;
Ogre::SceneManager* mCompositeMapSceneMgr;
@ -141,6 +154,9 @@ namespace Terrain
void convertPosition (Ogre::Vector3& pos);
void convertBounds (Ogre::AxisAlignedBox& bounds);
// Adds a WorkQueue request to load a chunk for this node in the background.
void queueLoad (QuadTreeNode* node);
private:
// Index buffers are shared across terrain batches where possible. There is one index buffer for each
// combination of LOD deltas and index buffer LOD we may need.
@ -152,6 +168,30 @@ namespace Terrain
Ogre::TexturePtr mCompositeMapRenderTexture;
};
struct LoadRequestData
{
QuadTreeNode* mNode;
bool mPack;
friend std::ostream& operator<<(std::ostream& o, const LoadRequestData& r)
{ return o; }
};
struct LoadResponseData
{
std::vector<float> mPositions;
std::vector<float> mNormals;
std::vector<Ogre::uint8> mColours;
// Since we can't create a texture from a different thread, this only holds the raw texel data
std::vector< std::vector<Ogre::uint8> > mBlendmaps;
std::vector<Terrain::LayerInfo> mLayerList;
unsigned long time;
friend std::ostream& operator<<(std::ostream& o, const LoadResponseData& r)
{ return o; }
};
}
#endif

Loading…
Cancel
Save