From 195071efc7f69359358d7dde50837facc6763884 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 18 Feb 2014 16:44:37 +0100 Subject: [PATCH] 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). --- apps/openmw/mwrender/renderingmanager.cpp | 5 + apps/openmw/mwrender/terrainstorage.cpp | 17 +-- apps/openmw/mwrender/terrainstorage.hpp | 18 +-- apps/openmw/mwworld/store.hpp | 4 + components/terrain/chunk.cpp | 11 +- components/terrain/chunk.hpp | 5 +- components/terrain/defs.hpp | 7 + components/terrain/quadtreenode.cpp | 167 +++++++++++----------- components/terrain/quadtreenode.hpp | 16 +++ components/terrain/storage.hpp | 27 ++-- components/terrain/world.cpp | 69 +++++++++ components/terrain/world.hpp | 42 +++++- 12 files changed, 264 insertions(+), 124 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index cf97afa7a..68bf957c8 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -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); diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index 8597c762c..ec26b1872 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -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& positions, + std::vector& normals, + std::vector& 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 colors; - colors.resize(numVerts*numVerts*4); - std::vector positions; + colours.resize(numVerts*numVerts*4); positions.resize(numVerts*numVerts*3); - std::vector 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, diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index f84da5c5d..b1acdb785 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -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& positions, + std::vector& normals, + std::vector& 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 diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 7bd00d6bf..6b99c0a0c 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -388,6 +388,8 @@ namespace MWWorld typedef std::vector::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 <exl = 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; diff --git a/components/terrain/chunk.cpp b/components/terrain/chunk.cpp index a05066313..139694ee5 100644 --- a/components/terrain/chunk.cpp +++ b/components/terrain/chunk.cpp @@ -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); diff --git a/components/terrain/chunk.hpp b/components/terrain/chunk.hpp index d74c65ba6..a6477df28 100644 --- a/components/terrain/chunk.hpp +++ b/components/terrain/chunk.hpp @@ -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); diff --git a/components/terrain/defs.hpp b/components/terrain/defs.hpp index 9f0b8c09b..c14608e76 100644 --- a/components/terrain/defs.hpp +++ b/components/terrain/defs.hpp @@ -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 diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index 44bf320e4..721771ff3 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -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,100 +291,110 @@ 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) + bool wantToDisplay = mSize <= mTerrain->getMaxBatchSize() && mLodLevel <= wantedLod; + + if (wantToDisplay) { // Wanted LOD is small enough to render this node in one chunk - if (!mChunk) + if (mLoadState == LS_Unloaded) { - mChunk = new Chunk(this, mLodLevel); - mChunk->setVisibilityFlags(mTerrain->getVisiblityFlags()); - mChunk->setCastShadows(true); - mSceneNode->attachObject(mChunk); - - mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled()); - mMaterialGenerator->enableSplitShadows(mTerrain->getSplitShadowsEnabled()); + mLoadState = LS_Loading; + mTerrain->queueLoad(this); + } - if (mSize == 1) - { - ensureLayerInfo(); - mChunk->setMaterial(mMaterialGenerator->generate(mChunk->getMaterial())); - } - else + 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 + // further away than the original node's bounding box, possibly causing a child to switch to a *lower* LOD + // than the original node. + // In short, we'd sometimes get a switch to a lesser detail when actually moving closer. + // This wouldn't be so bad, but unfortunately it also breaks LOD edge connections if a neighbour + // node hasn't split yet, and has a higher LOD than our node's child: + // ----- ----- ------------ + // | LOD | LOD | | + // | 1 | 1 | | + // |-----|-----| LOD 0 | + // | LOD | LOD | | + // | 0 | 0 | | + // ----- ----- ------------ + // To prevent this, nodes of the same size need to always select the same LOD, which is basically what we're + // doing here. + // But this "solution" does increase triangle overhead, so eventually we need to find a more clever way. + //mChunk->setAdditionalLod(wantedLod - mLodLevel); + + if (!mChunk->getVisible() && hasChildren()) { - ensureCompositeMap(); - mMaterialGenerator->setCompositeMap(mCompositeMap->getName()); - mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap(mChunk->getMaterial())); + // Make sure child scene nodes are detached + mSceneNode->removeAllChildren(); + + // TODO: unload + //for (int i=0; i<4; ++i) + // mChildren[i]->unload(); } + + mChunk->setVisible(true); } + } - // 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 - // further away than the original node's bounding box, possibly causing a child to switch to a *lower* LOD - // than the original node. - // In short, we'd sometimes get a switch to a lesser detail when actually moving closer. - // This wouldn't be so bad, but unfortunately it also breaks LOD edge connections if a neighbour - // node hasn't split yet, and has a higher LOD than our node's child: - // ----- ----- ------------ - // | LOD | LOD | | - // | 1 | 1 | | - // |-----|-----| LOD 0 | - // | LOD | LOD | | - // | 0 | 0 | | - // ----- ----- ------------ - // To prevent this, nodes of the same size need to always select the same LOD, which is basically what we're - // doing here. - // 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 (wantToDisplay && mLoadState != LS_Loaded) + { + // 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()) { - // 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); + for (int i=0; i<4; ++i) + if (mChildren[i]->hasChunk()) + mChildren[i]->update(cameraPos); } } - else + if (!wantToDisplay) { - // Wanted LOD is too detailed to be rendered in one chunk, - // so split it up by delegating to child nodes - if (hadChunk) + // We do not want to display this node - delegate to children + if (mChunk) + mChunk->setVisible(false); + 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) + mChildren[i]->update(cameraPos); } - assert(hasChildren() && "Leaf node's LOD needs to be 0"); - for (int i=0; i<4; ++i) - mChildren[i]->update(cameraPos); } } -void QuadTreeNode::destroyChunks(bool children) +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::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() diff --git a/components/terrain/quadtreenode.hpp b/components/terrain/quadtreenode.hpp index 32c7dd56a..0bba1024c 100644 --- a/components/terrain/quadtreenode.hpp +++ b/components/terrain/quadtreenode.hpp @@ -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 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; diff --git a/components/terrain/storage.hpp b/components/terrain/storage.hpp index d7fb78492..1b1968f1a 100644 --- a/components/terrain/storage.hpp +++ b/components/terrain/storage.hpp @@ -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& positions, + std::vector& normals, + std::vector& 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 diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index e633c1a00..87baeff84 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -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(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(res->getData()); + + const LoadRequestData requestData = Ogre::any_cast(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; + } } diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index 31bd51ab1..d552bccb0 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #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 mPositions; + std::vector mNormals; + std::vector mColours; + // Since we can't create a texture from a different thread, this only holds the raw texel data + std::vector< std::vector > mBlendmaps; + std::vector mLayerList; + + unsigned long time; + + friend std::ostream& operator<<(std::ostream& o, const LoadResponseData& r) + { return o; } + }; + } #endif