From 1d926816b587c75e43cf6824e29f06efb42e18fb Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 5 Mar 2014 21:45:43 +0100 Subject: [PATCH] Terrain: background load blendmaps & layer textures. Refactor QuadTree update. --- apps/openmw/mwrender/renderingmanager.cpp | 19 ++- apps/openmw/mwrender/renderingmanager.hpp | 4 + apps/openmw/mwrender/terrainstorage.cpp | 47 ++++-- apps/openmw/mwrender/terrainstorage.hpp | 20 ++- apps/openmw/mwworld/scene.cpp | 4 +- components/terrain/chunk.cpp | 1 + components/terrain/defs.hpp | 9 ++ components/terrain/material.cpp | 4 + components/terrain/quadtreenode.cpp | 181 +++++++++++++--------- components/terrain/quadtreenode.hpp | 17 +- components/terrain/storage.hpp | 15 +- components/terrain/world.cpp | 80 ++++++---- components/terrain/world.hpp | 30 +++- 13 files changed, 292 insertions(+), 139 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index b67875405b..7c70b17f07 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -651,13 +651,20 @@ void RenderingManager::setGlare(bool glare) mSkyManager->setGlare(glare); } -void RenderingManager::requestMap(MWWorld::CellStore* cell) +void RenderingManager::updateTerrain() { - // 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) + { + // Avoid updating with dims.getCenter for each cell. Player position should be good enough + mTerrain->update(mRendering.getCamera()->getRealPosition()); mTerrain->syncLoad(); + // need to update again so the chunks that were just loaded can be made visible + mTerrain->update(mRendering.getCamera()->getRealPosition()); + } +} +void RenderingManager::requestMap(MWWorld::CellStore* cell) +{ if (cell->getCell()->isExterior()) { assert(mTerrain); @@ -666,9 +673,6 @@ void RenderingManager::requestMap(MWWorld::CellStore* cell) Ogre::Vector2 center (cell->getCell()->getGridX() + 0.5, cell->getCell()->getGridY() + 0.5); dims.merge(mTerrain->getWorldBoundingBox(center)); - if (dims.isFinite()) - mTerrain->update(dims.getCenter()); - mLocalMap->requestMap(cell, dims.getMinimum().z, dims.getMaximum().z); } else @@ -1059,8 +1063,7 @@ void RenderingManager::enableTerrain(bool enable) } mTerrain->setVisible(true); } - else - if (mTerrain) + else if (mTerrain) mTerrain->setVisible(false); } diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 64ec029ce8..115a94786e 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -180,6 +180,10 @@ public: void removeWaterRippleEmitter (const MWWorld::Ptr& ptr); void updateWaterRippleEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr); + void updateTerrain (); + ///< update the terrain according to the player position. Usually done automatically, but should be done manually + /// before calling requestMap + void requestMap (MWWorld::CellStore* cell); ///< request the local map for a cell diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index ec26b18720..2558c95c59 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -13,6 +14,8 @@ #include "../mwbase/environment.hpp" #include "../mwworld/esmstore.hpp" +#include + namespace MWRender { @@ -329,8 +332,24 @@ namespace MWRender return texture; } + void TerrainStorage::getBlendmaps (const std::vector& nodes, std::vector& out, bool pack) + { + for (std::vector::const_iterator it = nodes.begin(); it != nodes.end(); ++it) + { + out.push_back(Terrain::LayerCollection()); + out.back().mTarget = *it; + getBlendmapsImpl((*it)->getSize(), (*it)->getCenter(), pack, out.back().mBlendmaps, out.back().mLayers); + } + } + void TerrainStorage::getBlendmaps(float chunkSize, const Ogre::Vector2 &chunkCenter, - bool pack, std::vector &blendmaps, std::vector &layerList) + bool pack, std::vector &blendmaps, std::vector &layerList) + { + getBlendmapsImpl(chunkSize, chunkCenter, pack, blendmaps, layerList); + } + + void TerrainStorage::getBlendmapsImpl(float chunkSize, const Ogre::Vector2 &chunkCenter, + bool pack, std::vector &blendmaps, std::vector &layerList) { // TODO - blending isn't completely right yet; the blending radius appears to be // different at a cell transition (2 vertices, not 4), so we may need to create a larger blendmap @@ -375,16 +394,14 @@ namespace MWRender // Second iteration - create and fill in the blend maps const int blendmapSize = ESM::Land::LAND_TEXTURE_SIZE+1; - std::vector data; - data.resize(blendmapSize * blendmapSize * channels, 0); for (int i=0; iloadRawData(stream, blendmapSize, blendmapSize, format); - blendmaps.push_back(map); + blendmaps.push_back(Ogre::PixelBox(blendmapSize, blendmapSize, 1, format, pData)); } } @@ -527,6 +540,12 @@ namespace MWRender info.mSpecular = true; } + // This wasn't cached, so the textures are probably not loaded either. + // Background load them so they are hopefully already loaded once we need them! + Ogre::ResourceBackgroundQueue::getSingleton().load("Texture", info.mDiffuseMap, "General"); + if (!info.mNormalMap.empty()) + Ogre::ResourceBackgroundQueue::getSingleton().load("Texture", info.mNormalMap, "General"); + mLayerInfoMap[texture] = info; return info; diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index b1acdb7859..b5e6012f3a 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -46,6 +46,7 @@ namespace MWRender /// 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 /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. + /// @note May be called from *one* background thread. /// @param chunkSize size of the terrain chunk in cell units /// @param chunkCenter center of the chunk in cell units /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - @@ -54,9 +55,21 @@ namespace MWRender /// @param blendmaps created blendmaps will be written here /// @param layerList names of the layer textures used will be written here virtual void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, - std::vector& blendmaps, + std::vector& blendmaps, std::vector& layerList); + /// Retrieve pixel data for textures holding layer blend values for terrain chunks and layer texture information. + /// This variant is provided to eliminate the overhead of virtual function calls when retrieving a large number of blendmaps at once. + /// @note The terrain chunks shouldn't be larger than one cell since otherwise we might + /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. + /// @note May be called from *one* background thread. + /// @param nodes A collection of nodes for which to retrieve the aforementioned data + /// @param out Output vector + /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - + /// otherwise, each texture contains blend values for one layer only. Shader-based rendering + /// can utilize packing, FFP can't. + virtual void getBlendmaps (const std::vector& nodes, std::vector& out, bool pack); + virtual float getHeightAt (const Ogre::Vector3& worldPos); virtual Terrain::LayerInfo getDefaultLayer(); @@ -86,6 +99,11 @@ namespace MWRender std::map mLayerInfoMap; Terrain::LayerInfo getLayerInfo(const std::string& texture); + + // Non-virtual + void getBlendmapsImpl (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, + std::vector& blendmaps, + std::vector& layerList); }; } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 40004807b4..caae72c137 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -195,10 +195,12 @@ namespace MWWorld mechMgr->updateCell(old, player); mechMgr->watchActor(player); - MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell); + mRendering.updateTerrain(); for (CellStoreCollection::iterator active = mActiveCells.begin(); active!=mActiveCells.end(); ++active) mRendering.requestMap(*active); + + MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell); } void Scene::changeToVoid() diff --git a/components/terrain/chunk.cpp b/components/terrain/chunk.cpp index ed640be5f2..0820dcc732 100644 --- a/components/terrain/chunk.cpp +++ b/components/terrain/chunk.cpp @@ -110,6 +110,7 @@ namespace Terrain void Chunk::getRenderOperation(Ogre::RenderOperation& op) { assert (!mIndexData->indexBuffer.isNull() && "Trying to render, but no index buffer set!"); + assert(!mMaterial.isNull() && "Trying to render, but no material set!"); op.useIndexes = true; op.operationType = Ogre::RenderOperation::OT_TRIANGLE_LIST; op.vertexData = mVertexData; diff --git a/components/terrain/defs.hpp b/components/terrain/defs.hpp index 1a081c969d..6859376532 100644 --- a/components/terrain/defs.hpp +++ b/components/terrain/defs.hpp @@ -3,6 +3,7 @@ namespace Terrain { + class QuadTreeNode; /// The alignment of the terrain enum Alignment @@ -51,6 +52,14 @@ namespace Terrain bool mParallax; // Height info in normal map alpha channel? bool mSpecular; // Specular info in diffuse map alpha channel? }; + + struct LayerCollection + { + QuadTreeNode* mTarget; + // Since we can't create a texture from a different thread, this only holds the raw texel data + std::vector mBlendmaps; + std::vector mLayers; + }; } #endif diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 78b17909a5..faa73a9864 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -48,11 +48,15 @@ namespace Terrain Ogre::MaterialPtr MaterialGenerator::generate(Ogre::MaterialPtr mat) { + assert(!mLayerList.empty() && "Can't create material with no layers"); + return create(mat, false, false); } Ogre::MaterialPtr MaterialGenerator::generateForCompositeMapRTT(Ogre::MaterialPtr mat) { + assert(!mLayerList.empty() && "Can't create material with no layers"); + return create(mat, true, false); } diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index 9139706061..56cfc2a745 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -144,7 +144,6 @@ namespace QuadTreeNode::QuadTreeNode(World* terrain, ChildDirection dir, float size, const Ogre::Vector2 ¢er, QuadTreeNode* parent) : mMaterialGenerator(NULL) - , mIsActive(false) , mIsDummy(false) , mSize(size) , mLodLevel(Log2(mSize)) @@ -262,11 +261,13 @@ const Ogre::AxisAlignedBox& QuadTreeNode::getWorldBoundingBox() return mWorldBounds; } -void QuadTreeNode::update(const Ogre::Vector3 &cameraPos) +bool QuadTreeNode::update(const Ogre::Vector3 &cameraPos) { - const Ogre::AxisAlignedBox& bounds = getBoundingBox(); - if (bounds.isNull()) - return; + if (isDummy()) + return true; + + if (mBounds.isNull()) + return true; float dist = distance(mWorldBounds, cameraPos); @@ -291,11 +292,9 @@ void QuadTreeNode::update(const Ogre::Vector3 &cameraPos) wantedLod = 3; else if (dist > cellWorldSize*2) wantedLod = 2; - else if (dist > cellWorldSize) + else if (dist > cellWorldSize * 1.42) // < sqrt2 so the 3x3 grid around player is always highest lod wantedLod = 1; - mIsActive = true; - bool wantToDisplay = mSize <= mTerrain->getMaxBatchSize() && mLodLevel <= wantedLod; if (wantToDisplay) @@ -305,6 +304,7 @@ void QuadTreeNode::update(const Ogre::Vector3 &cameraPos) { mLoadState = LS_Loading; mTerrain->queueLoad(this); + return false; } if (mLoadState == LS_Loaded) @@ -331,47 +331,60 @@ void QuadTreeNode::update(const Ogre::Vector3 &cameraPos) if (!mChunk->getVisible() && hasChildren()) { - // Make sure child scene nodes are detached - mSceneNode->removeAllChildren(); - - // TODO: unload - //for (int i=0; i<4; ++i) - // mChildren[i]->unload(); + for (int i=0; i<4; ++i) + mChildren[i]->unload(true); } - mChunk->setVisible(true); + + return true; } + return false; // LS_Loading } - if (wantToDisplay && mLoadState != LS_Loaded) + // We do not want to display this node - delegate to children if they are already loaded + if (!wantToDisplay && hasChildren()) { - // 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 (mChunk) { + // Are children already loaded? + bool childrenLoaded = true; for (int i=0; i<4; ++i) - if (mChildren[i]->hasChunk()) - mChildren[i]->update(cameraPos); + if (!mChildren[i]->update(cameraPos)) + childrenLoaded = false; + + if (!childrenLoaded) + { + // Make sure child scene nodes are detached until all children are loaded + mSceneNode->removeAllChildren(); + } + else + { + // Delegation went well, we can unload now + unload(); + + for (int i=0; i<4; ++i) + { + if (!mChildren[i]->getSceneNode()->isInSceneGraph()) + mSceneNode->addChild(mChildren[i]->getSceneNode()); + } + } + return true; } - } - if (!wantToDisplay) - { - // We do not want to display this node - delegate to children - if (mChunk) - mChunk->setVisible(false); - if (hasChildren()) + else { + bool success = true; for (int i=0; i<4; ++i) - mChildren[i]->update(cameraPos); + success = mChildren[i]->update(cameraPos) & success; + return success; } } + return false; } void QuadTreeNode::load(const LoadResponseData &data) { assert (!mChunk); - std::cout << "loading " << std::endl; mChunk = new Chunk(mTerrain->getBufferCache().getUVBuffer(), mBounds, data); mChunk->setVisibilityFlags(mTerrain->getVisiblityFlags()); mChunk->setCastShadows(true); @@ -380,50 +393,49 @@ void QuadTreeNode::load(const LoadResponseData &data) mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled()); mMaterialGenerator->enableSplitShadows(mTerrain->getSplitShadowsEnabled()); - if (mSize == 1) - { - ensureLayerInfo(); - mChunk->setMaterial(mMaterialGenerator->generate(mChunk->getMaterial())); - } - else + if (mTerrain->areLayersLoaded()) { - ensureCompositeMap(); - mMaterialGenerator->setCompositeMap(mCompositeMap->getName()); - mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap(mChunk->getMaterial())); + if (mSize == 1) + { + mChunk->setMaterial(mMaterialGenerator->generate(mChunk->getMaterial())); + } + else + { + ensureCompositeMap(); + mMaterialGenerator->setCompositeMap(mCompositeMap->getName()); + mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap(mChunk->getMaterial())); + } } - + // else: will be loaded in loadMaterials() after background thread has finished loading layers mChunk->setVisible(false); mLoadState = LS_Loaded; } -void QuadTreeNode::unload() +void QuadTreeNode::unload(bool recursive) { if (mChunk) { - Ogre::MaterialManager::getSingleton().remove(mChunk->getMaterial()->getName()); mSceneNode->detachObject(mChunk); delete mChunk; mChunk = NULL; - // destroy blendmaps - if (mMaterialGenerator) - { - const std::vector& list = mMaterialGenerator->getBlendmapList(); - for (std::vector::const_iterator it = list.begin(); it != list.end(); ++it) - Ogre::TextureManager::getSingleton().remove((*it)->getName()); - mMaterialGenerator->setBlendmapList(std::vector()); - mMaterialGenerator->setLayerList(std::vector()); - mMaterialGenerator->setCompositeMap(""); - } if (!mCompositeMap.isNull()) { Ogre::TextureManager::getSingleton().remove(mCompositeMap->getName()); mCompositeMap.setNull(); } + + // Do *not* set this when we are still loading! + mLoadState = LS_Unloaded; + } + + if (recursive && hasChildren()) + { + for (int i=0; i<4; ++i) + mChildren[i]->unload(); } - mLoadState = LS_Unloaded; } void QuadTreeNode::updateIndexBuffers() @@ -479,17 +491,53 @@ size_t QuadTreeNode::getActualLodLevel() return mLodLevel /* + mChunk->getAdditionalLod() */; } -void QuadTreeNode::ensureLayerInfo() +void QuadTreeNode::loadLayers(const LayerCollection& collection) { - if (mMaterialGenerator->hasLayers()) + assert (!mMaterialGenerator->hasLayers()); + + std::vector blendTextures; + for (std::vector::const_iterator it = collection.mBlendmaps.begin(); it != collection.mBlendmaps.end(); ++it) + { + // TODO: clean up blend textures on destruction + static int count=0; + Ogre::TexturePtr map = Ogre::TextureManager::getSingleton().createManual("terrain/blend/" + + Ogre::StringConverter::toString(count++), Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + Ogre::TEX_TYPE_2D, it->getWidth(), it->getHeight(), 0, it->format); + + Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(it->data, it->getWidth()*it->getHeight()*Ogre::PixelUtil::getNumElemBytes(it->format), true)); + map->loadRawData(stream, it->getWidth(), it->getHeight(), it->format); + blendTextures.push_back(map); + } + + mMaterialGenerator->setLayerList(collection.mLayers); + mMaterialGenerator->setBlendmapList(blendTextures); +} + +void QuadTreeNode::loadMaterials() +{ + if (isDummy()) return; - std::vector blendmaps; - std::vector layerList; - mTerrain->getStorage()->getBlendmaps(mSize, mCenter, mTerrain->getShadersEnabled(), blendmaps, layerList); + // Load children first since we depend on them when creating a composite map + if (hasChildren()) + { + for (int i=0; i<4; ++i) + mChildren[i]->loadMaterials(); + } - mMaterialGenerator->setLayerList(layerList); - mMaterialGenerator->setBlendmapList(blendmaps); + if (mChunk) + { + if (mSize == 1) + { + mChunk->setMaterial(mMaterialGenerator->generate(mChunk->getMaterial())); + } + else + { + ensureCompositeMap(); + mMaterialGenerator->setCompositeMap(mCompositeMap->getName()); + mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap(mChunk->getMaterial())); + } + } } void QuadTreeNode::prepareForCompositeMap(Ogre::TRect area) @@ -525,8 +573,7 @@ void QuadTreeNode::prepareForCompositeMap(Ogre::TRect area) } else { - ensureLayerInfo(); - + // TODO: when to destroy? Ogre::MaterialPtr material = mMaterialGenerator->generateForCompositeMapRTT(Ogre::MaterialPtr()); makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, material); } @@ -570,13 +617,3 @@ void QuadTreeNode::applyMaterials() for (int i=0; i<4; ++i) mChildren[i]->applyMaterials(); } - -void QuadTreeNode::setVisible(bool visible) -{ - if (!visible && mChunk) - mChunk->setVisible(false); - - if (hasChildren()) - for (int i=0; i<4; ++i) - mChildren[i]->setVisible(visible); -} diff --git a/components/terrain/quadtreenode.hpp b/components/terrain/quadtreenode.hpp index 134bc2d347..c575894879 100644 --- a/components/terrain/quadtreenode.hpp +++ b/components/terrain/quadtreenode.hpp @@ -51,8 +51,6 @@ namespace Terrain QuadTreeNode (World* terrain, ChildDirection dir, float size, const Ogre::Vector2& center, QuadTreeNode* parent); ~QuadTreeNode(); - void setVisible(bool visible); - /// Rebuild all materials void applyMaterials(); @@ -100,7 +98,9 @@ namespace Terrain World* getTerrain() { return mTerrain; } /// Adjust LODs for the given camera position, possibly splitting up chunks or merging them. - void update (const Ogre::Vector3& cameraPos); + /// @param force Always choose to render this node, even if not the perfect LOD. + /// @return Did we (or all of our children) choose to render? + bool update (const Ogre::Vector3& cameraPos); /// Adjust index buffers of chunks to stitch together chunks of different LOD, so that cracks are avoided. /// Call after QuadTreeNode::update! @@ -122,13 +122,17 @@ namespace Terrain /// Add a textured quad to a specific 2d area in the composite map scenemanager. /// Only nodes with size <= 1 can be rendered with alpha blending, so larger nodes will simply /// call this method on their children. + /// @note Do not call this before World::areLayersLoaded() == true /// @param area area in image space to put the quad /// @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(); + void unload(bool recursive=false); + void loadLayers (const LayerCollection& collection); + /// This is recursive! Call it once on the root node after all leafs have loaded layers. + void loadMaterials(); LoadState getLoadState() { return mLoadState; } @@ -138,10 +142,6 @@ namespace Terrain 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; - bool mIsDummy; float mSize; size_t mLodLevel; // LOD if we were to render this node in one chunk @@ -162,7 +162,6 @@ namespace Terrain Ogre::TexturePtr mCompositeMap; - void ensureLayerInfo(); void ensureCompositeMap(); }; diff --git a/components/terrain/storage.hpp b/components/terrain/storage.hpp index 1b1968f1ac..d3770ea57a 100644 --- a/components/terrain/storage.hpp +++ b/components/terrain/storage.hpp @@ -44,6 +44,7 @@ namespace Terrain /// 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 /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. + /// @note May be called from background threads. Make sure to only call thread-safe functions from here! /// @param chunkSize size of the terrain chunk in cell units /// @param chunkCenter center of the chunk in cell units /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - @@ -52,9 +53,21 @@ namespace Terrain /// @param blendmaps created blendmaps will be written here /// @param layerList names of the layer textures used will be written here virtual void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, - std::vector& blendmaps, + std::vector& blendmaps, std::vector& layerList) = 0; + /// Retrieve pixel data for textures holding layer blend values for terrain chunks and layer texture information. + /// This variant is provided to eliminate the overhead of virtual function calls when retrieving a large number of blendmaps at once. + /// @note The terrain chunks shouldn't be larger than one cell since otherwise we might + /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. + /// @note May be called from background threads. Make sure to only call thread-safe functions from here! + /// @param nodes A collection of nodes for which to retrieve the aforementioned data + /// @param out Output vector + /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - + /// otherwise, each texture contains blend values for one layer only. Shader-based rendering + /// can utilize packing, FFP can't. + virtual void getBlendmaps (const std::vector& nodes, std::vector& out, bool pack) = 0; + virtual float getHeightAt (const Ogre::Vector3& worldPos) = 0; virtual LayerInfo getDefaultLayer() = 0; diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index 69748a7215..844144d7c4 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -7,7 +7,6 @@ #include #include #include -#include // TEMP #include "storage.hpp" #include "quadtreenode.hpp" @@ -52,6 +51,9 @@ namespace namespace Terrain { + const Ogre::uint REQ_ID_CHUNK = 1; + const Ogre::uint REQ_ID_LAYERS = 2; + World::World(Ogre::SceneManager* sceneMgr, Storage* storage, int visibilityFlags, bool distantLand, bool shaders, Alignment align, float minBatchSize, float maxBatchSize) : mStorage(storage) @@ -70,6 +72,7 @@ namespace Terrain , mChunksLoading(0) , mWorkQueueChannel(0) , mCache(storage->getCellVertices()) + , mLayerLoadPending(true) { #if TERRAIN_USE_SHADER == 0 if (mShaders) @@ -102,8 +105,12 @@ namespace Terrain mRootSceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(); + // While building the quadtree, remember leaf nodes since we need to load their layers + LayersRequestData data; + data.mPack = getShadersEnabled(); + mRootNode = new QuadTreeNode(this, Root, size, Ogre::Vector2(centerX, centerY), NULL); - buildQuadTree(mRootNode); + buildQuadTree(mRootNode, data.mNodes); //loadingListener->indicateProgress(); mRootNode->initAabb(); //loadingListener->indicateProgress(); @@ -114,6 +121,9 @@ namespace Terrain mWorkQueueChannel = wq->getChannel("LargeTerrain"); wq->addRequestHandler(mWorkQueueChannel, this); wq->addResponseHandler(mWorkQueueChannel, this); + + // Start loading layers in the background (for leaf nodes) + wq->addRequest(mWorkQueueChannel, REQ_ID_LAYERS, Ogre::Any(data)); } World::~World() @@ -126,7 +136,7 @@ namespace Terrain delete mStorage; } - void World::buildQuadTree(QuadTreeNode *node) + void World::buildQuadTree(QuadTreeNode *node, std::vector& leafs) { float halfSize = node->getSize()/2.f; @@ -142,6 +152,7 @@ namespace Terrain Ogre::Vector3(halfSize*cellWorldSize, halfSize*cellWorldSize, maxZ)); convertBounds(bounds); node->setBoundingBox(bounds); + leafs.push_back(node); } else node->markAsDummy(); // no data available for this node, skip it @@ -164,10 +175,10 @@ namespace Terrain node->createChild(SE, halfSize, node->getCenter() + Ogre::Vector2(halfSize/2.f, -halfSize/2.f)); node->createChild(NW, halfSize, node->getCenter() + Ogre::Vector2(-halfSize/2.f, halfSize/2.f)); node->createChild(NE, halfSize, node->getCenter() + halfSize/2.f); - buildQuadTree(node->getChild(SW)); - buildQuadTree(node->getChild(SE)); - buildQuadTree(node->getChild(NW)); - buildQuadTree(node->getChild(NE)); + buildQuadTree(node->getChild(SW), leafs); + buildQuadTree(node->getChild(SE), leafs); + buildQuadTree(node->getChild(NW), leafs); + buildQuadTree(node->getChild(NE), leafs); // if all children are dummy, we are also dummy for (int i=0; i<4; ++i) @@ -267,7 +278,7 @@ namespace Terrain void World::syncLoad() { - while (mChunksLoading) + while (mChunksLoading || mLayerLoadPending) { OGRE_THREAD_SLEEP(0); Ogre::Root::getSingleton().getWorkQueue()->processResponses(); @@ -276,27 +287,36 @@ namespace Terrain Ogre::WorkQueue::Response* World::handleRequest(const Ogre::WorkQueue::Request *req, const Ogre::WorkQueue *srcQ) { - const LoadRequestData data = Ogre::any_cast(req->getData()); + if (req->getType() == REQ_ID_CHUNK) + { + const LoadRequestData data = Ogre::any_cast(req->getData()); - QuadTreeNode* node = data.mNode; + QuadTreeNode* node = data.mNode; - LoadResponseData* responseData = new LoadResponseData(); + LoadResponseData* responseData = new LoadResponseData(); - Ogre::Timer timer; - getStorage()->fillVertexBuffers(node->getNativeLodLevel(), node->getSize(), node->getCenter(), getAlign(), - responseData->mPositions, responseData->mNormals, responseData->mColours); + getStorage()->fillVertexBuffers(node->getNativeLodLevel(), node->getSize(), node->getCenter(), getAlign(), + responseData->mPositions, responseData->mNormals, responseData->mColours); - std::cout << "THREAD" << std::endl; + return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData)); + } + else // REQ_ID_LAYERS + { + const LayersRequestData data = Ogre::any_cast(req->getData()); + + LayersResponseData* responseData = new LayersResponseData(); - responseData->time = timer.getMicroseconds(); + getStorage()->getBlendmaps(data.mNodes, responseData->mLayerCollections, data.mPack); - return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData)); + 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()) + assert(res->succeeded() && "Response failure not handled"); + + if (res->getRequest()->getType() == REQ_ID_CHUNK) { LoadResponseData* data = Ogre::any_cast(res->getData()); @@ -304,23 +324,31 @@ namespace Terrain requestData.mNode->load(*data); - time += data->time; - delete data; - std::cout << "RESPONSE, reqs took ms" << time/1000.f << std::endl; + --mChunksLoading; + } + else // REQ_ID_LAYERS + { + LayersResponseData* data = Ogre::any_cast(res->getData()); + + for (std::vector::iterator it = data->mLayerCollections.begin(); it != data->mLayerCollections.end(); ++it) + { + it->mTarget->loadLayers(*it); + } + + mRootNode->loadMaterials(); + + mLayerLoadPending = false; } - --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)); + Ogre::Root::getSingleton().getWorkQueue()->addRequest(mWorkQueueChannel, REQ_ID_CHUNK, Ogre::Any(data)); ++mChunksLoading; } } diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index 3547387444..26a6d034dc 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -122,15 +122,20 @@ namespace Terrain /// Maximum size of a terrain batch along one side (in cell units) float mMaxBatchSize; - void buildQuadTree(QuadTreeNode* node); + void buildQuadTree(QuadTreeNode* node, std::vector& leafs); BufferCache mCache; + // Are layers for leaf nodes loaded? This is done once at startup (but in a background thread) + bool mLayerLoadPending; + public: // ----INTERNAL---- Ogre::SceneManager* getCompositeMapSceneManager() { return mCompositeMapSceneMgr; } BufferCache& getBufferCache() { return mCache; } + bool areLayersLoaded() { return !mLayerLoadPending; } + // Delete all quads void clearCompositeMapSceneManager(); void renderCompositeMap (Ogre::TexturePtr target); @@ -151,7 +156,6 @@ namespace Terrain struct LoadRequestData { QuadTreeNode* mNode; - bool mPack; friend std::ostream& operator<<(std::ostream& o, const LoadRequestData& r) { return o; } @@ -162,16 +166,28 @@ namespace Terrain 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; } }; + struct LayersRequestData + { + std::vector mNodes; + bool mPack; + + friend std::ostream& operator<<(std::ostream& o, const LayersRequestData& r) + { return o; } + }; + + struct LayersResponseData + { + std::vector mLayerCollections; + + friend std::ostream& operator<<(std::ostream& o, const LayersResponseData& r) + { return o; } + }; + } #endif