1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-01-30 22:15:32 +00:00

Terrain: background load blendmaps & layer textures. Refactor QuadTree update.

This commit is contained in:
scrawl 2014-03-05 21:45:43 +01:00
parent 4328e08162
commit 1d926816b5
13 changed files with 296 additions and 143 deletions

View file

@ -651,13 +651,20 @@ void RenderingManager::setGlare(bool glare)
mSkyManager->setGlare(glare); mSkyManager->setGlare(glare);
} }
void RenderingManager::updateTerrain()
{
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) 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()) if (cell->getCell()->isExterior())
{ {
assert(mTerrain); 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); Ogre::Vector2 center (cell->getCell()->getGridX() + 0.5, cell->getCell()->getGridY() + 0.5);
dims.merge(mTerrain->getWorldBoundingBox(center)); dims.merge(mTerrain->getWorldBoundingBox(center));
if (dims.isFinite())
mTerrain->update(dims.getCenter());
mLocalMap->requestMap(cell, dims.getMinimum().z, dims.getMaximum().z); mLocalMap->requestMap(cell, dims.getMinimum().z, dims.getMaximum().z);
} }
else else
@ -1059,8 +1063,7 @@ void RenderingManager::enableTerrain(bool enable)
} }
mTerrain->setVisible(true); mTerrain->setVisible(true);
} }
else else if (mTerrain)
if (mTerrain)
mTerrain->setVisible(false); mTerrain->setVisible(false);
} }

View file

@ -180,6 +180,10 @@ public:
void removeWaterRippleEmitter (const MWWorld::Ptr& ptr); void removeWaterRippleEmitter (const MWWorld::Ptr& ptr);
void updateWaterRippleEmitterPtr (const MWWorld::Ptr& old, 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); void requestMap (MWWorld::CellStore* cell);
///< request the local map for a cell ///< request the local map for a cell

View file

@ -5,6 +5,7 @@
#include <OgreStringConverter.h> #include <OgreStringConverter.h>
#include <OgreRenderSystem.h> #include <OgreRenderSystem.h>
#include <OgreResourceGroupManager.h> #include <OgreResourceGroupManager.h>
#include <OgreResourceBackgroundQueue.h>
#include <OgreRoot.h> #include <OgreRoot.h>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
@ -13,6 +14,8 @@
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include <components/terrain/quadtreenode.hpp>
namespace MWRender namespace MWRender
{ {
@ -329,8 +332,24 @@ namespace MWRender
return texture; return texture;
} }
void TerrainStorage::getBlendmaps (const std::vector<Terrain::QuadTreeNode*>& nodes, std::vector<Terrain::LayerCollection>& out, bool pack)
{
for (std::vector<Terrain::QuadTreeNode*>::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, void TerrainStorage::getBlendmaps(float chunkSize, const Ogre::Vector2 &chunkCenter,
bool pack, std::vector<Ogre::TexturePtr> &blendmaps, std::vector<Terrain::LayerInfo> &layerList) bool pack, std::vector<Ogre::PixelBox> &blendmaps, std::vector<Terrain::LayerInfo> &layerList)
{
getBlendmapsImpl(chunkSize, chunkCenter, pack, blendmaps, layerList);
}
void TerrainStorage::getBlendmapsImpl(float chunkSize, const Ogre::Vector2 &chunkCenter,
bool pack, std::vector<Ogre::PixelBox> &blendmaps, std::vector<Terrain::LayerInfo> &layerList)
{ {
// TODO - blending isn't completely right yet; the blending radius appears to be // 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 // 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 // Second iteration - create and fill in the blend maps
const int blendmapSize = ESM::Land::LAND_TEXTURE_SIZE+1; const int blendmapSize = ESM::Land::LAND_TEXTURE_SIZE+1;
std::vector<Ogre::uchar> data;
data.resize(blendmapSize * blendmapSize * channels, 0);
for (int i=0; i<numBlendmaps; ++i) for (int i=0; i<numBlendmaps; ++i)
{ {
Ogre::PixelFormat format = pack ? Ogre::PF_A8B8G8R8 : Ogre::PF_A8; Ogre::PixelFormat format = pack ? Ogre::PF_A8B8G8R8 : Ogre::PF_A8;
static int count=0;
Ogre::TexturePtr map = Ogre::TextureManager::getSingleton().createManual("terrain/blend/" Ogre::uchar* pData =
+ Ogre::StringConverter::toString(count++), Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, OGRE_ALLOC_T(Ogre::uchar, blendmapSize*blendmapSize*channels, Ogre::MEMCATEGORY_GENERAL);
Ogre::TEX_TYPE_2D, blendmapSize, blendmapSize, 0, format); memset(pData, 0, blendmapSize*blendmapSize*channels);
for (int y=0; y<blendmapSize; ++y) for (int y=0; y<blendmapSize; ++y)
{ {
@ -396,16 +413,12 @@ namespace MWRender
int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0; int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0;
if (blendIndex == i) if (blendIndex == i)
data[y*blendmapSize*channels + x*channels + channel] = 255; pData[y*blendmapSize*channels + x*channels + channel] = 255;
else else
data[y*blendmapSize*channels + x*channels + channel] = 0; pData[y*blendmapSize*channels + x*channels + channel] = 0;
} }
} }
blendmaps.push_back(Ogre::PixelBox(blendmapSize, blendmapSize, 1, format, pData));
// All done, upload to GPU
Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&data[0], data.size()));
map->loadRawData(stream, blendmapSize, blendmapSize, format);
blendmaps.push_back(map);
} }
} }
@ -527,6 +540,12 @@ namespace MWRender
info.mSpecular = true; 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; mLayerInfoMap[texture] = info;
return info; return info;

View file

@ -46,6 +46,7 @@ namespace MWRender
/// Create textures holding layer blend values for a terrain chunk. /// 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 /// @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. /// 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 chunkSize size of the terrain chunk in cell units
/// @param chunkCenter center of the 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) - /// @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 blendmaps created blendmaps will be written here
/// @param layerList names of the layer textures used 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, virtual void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack,
std::vector<Ogre::TexturePtr>& blendmaps, std::vector<Ogre::PixelBox>& blendmaps,
std::vector<Terrain::LayerInfo>& layerList); std::vector<Terrain::LayerInfo>& 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<Terrain::QuadTreeNode*>& nodes, std::vector<Terrain::LayerCollection>& out, bool pack);
virtual float getHeightAt (const Ogre::Vector3& worldPos); virtual float getHeightAt (const Ogre::Vector3& worldPos);
virtual Terrain::LayerInfo getDefaultLayer(); virtual Terrain::LayerInfo getDefaultLayer();
@ -86,6 +99,11 @@ namespace MWRender
std::map<std::string, Terrain::LayerInfo> mLayerInfoMap; std::map<std::string, Terrain::LayerInfo> mLayerInfoMap;
Terrain::LayerInfo getLayerInfo(const std::string& texture); Terrain::LayerInfo getLayerInfo(const std::string& texture);
// Non-virtual
void getBlendmapsImpl (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack,
std::vector<Ogre::PixelBox>& blendmaps,
std::vector<Terrain::LayerInfo>& layerList);
}; };
} }

View file

@ -195,10 +195,12 @@ namespace MWWorld
mechMgr->updateCell(old, player); mechMgr->updateCell(old, player);
mechMgr->watchActor(player); mechMgr->watchActor(player);
MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell); mRendering.updateTerrain();
for (CellStoreCollection::iterator active = mActiveCells.begin(); active!=mActiveCells.end(); ++active) for (CellStoreCollection::iterator active = mActiveCells.begin(); active!=mActiveCells.end(); ++active)
mRendering.requestMap(*active); mRendering.requestMap(*active);
MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell);
} }
void Scene::changeToVoid() void Scene::changeToVoid()

View file

@ -110,6 +110,7 @@ namespace Terrain
void Chunk::getRenderOperation(Ogre::RenderOperation& op) void Chunk::getRenderOperation(Ogre::RenderOperation& op)
{ {
assert (!mIndexData->indexBuffer.isNull() && "Trying to render, but no index buffer set!"); 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.useIndexes = true;
op.operationType = Ogre::RenderOperation::OT_TRIANGLE_LIST; op.operationType = Ogre::RenderOperation::OT_TRIANGLE_LIST;
op.vertexData = mVertexData; op.vertexData = mVertexData;

View file

@ -3,6 +3,7 @@
namespace Terrain namespace Terrain
{ {
class QuadTreeNode;
/// The alignment of the terrain /// The alignment of the terrain
enum Alignment enum Alignment
@ -51,6 +52,14 @@ namespace Terrain
bool mParallax; // Height info in normal map alpha channel? bool mParallax; // Height info in normal map alpha channel?
bool mSpecular; // Specular info in diffuse 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<Ogre::PixelBox> mBlendmaps;
std::vector<LayerInfo> mLayers;
};
} }
#endif #endif

View file

@ -48,11 +48,15 @@ namespace Terrain
Ogre::MaterialPtr MaterialGenerator::generate(Ogre::MaterialPtr mat) Ogre::MaterialPtr MaterialGenerator::generate(Ogre::MaterialPtr mat)
{ {
assert(!mLayerList.empty() && "Can't create material with no layers");
return create(mat, false, false); return create(mat, false, false);
} }
Ogre::MaterialPtr MaterialGenerator::generateForCompositeMapRTT(Ogre::MaterialPtr mat) Ogre::MaterialPtr MaterialGenerator::generateForCompositeMapRTT(Ogre::MaterialPtr mat)
{ {
assert(!mLayerList.empty() && "Can't create material with no layers");
return create(mat, true, false); return create(mat, true, false);
} }

View file

@ -144,7 +144,6 @@ namespace
QuadTreeNode::QuadTreeNode(World* terrain, ChildDirection dir, float size, const Ogre::Vector2 &center, QuadTreeNode* parent) QuadTreeNode::QuadTreeNode(World* terrain, ChildDirection dir, float size, const Ogre::Vector2 &center, QuadTreeNode* parent)
: mMaterialGenerator(NULL) : mMaterialGenerator(NULL)
, mIsActive(false)
, mIsDummy(false) , mIsDummy(false)
, mSize(size) , mSize(size)
, mLodLevel(Log2(mSize)) , mLodLevel(Log2(mSize))
@ -262,11 +261,13 @@ const Ogre::AxisAlignedBox& QuadTreeNode::getWorldBoundingBox()
return mWorldBounds; return mWorldBounds;
} }
void QuadTreeNode::update(const Ogre::Vector3 &cameraPos) bool QuadTreeNode::update(const Ogre::Vector3 &cameraPos)
{ {
const Ogre::AxisAlignedBox& bounds = getBoundingBox(); if (isDummy())
if (bounds.isNull()) return true;
return;
if (mBounds.isNull())
return true;
float dist = distance(mWorldBounds, cameraPos); float dist = distance(mWorldBounds, cameraPos);
@ -291,11 +292,9 @@ void QuadTreeNode::update(const Ogre::Vector3 &cameraPos)
wantedLod = 3; wantedLod = 3;
else if (dist > cellWorldSize*2) else if (dist > cellWorldSize*2)
wantedLod = 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; wantedLod = 1;
mIsActive = true;
bool wantToDisplay = mSize <= mTerrain->getMaxBatchSize() && mLodLevel <= wantedLod; bool wantToDisplay = mSize <= mTerrain->getMaxBatchSize() && mLodLevel <= wantedLod;
if (wantToDisplay) if (wantToDisplay)
@ -305,6 +304,7 @@ void QuadTreeNode::update(const Ogre::Vector3 &cameraPos)
{ {
mLoadState = LS_Loading; mLoadState = LS_Loading;
mTerrain->queueLoad(this); mTerrain->queueLoad(this);
return false;
} }
if (mLoadState == LS_Loaded) if (mLoadState == LS_Loaded)
@ -331,47 +331,60 @@ void QuadTreeNode::update(const Ogre::Vector3 &cameraPos)
if (!mChunk->getVisible() && hasChildren()) if (!mChunk->getVisible() && hasChildren())
{ {
// Make sure child scene nodes are detached for (int i=0; i<4; ++i)
mSceneNode->removeAllChildren(); mChildren[i]->unload(true);
// TODO: unload
//for (int i=0; i<4; ++i)
// mChildren[i]->unload();
} }
mChunk->setVisible(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())
{
for (int i=0; i<4; ++i)
if (mChildren[i]->hasChunk())
mChildren[i]->update(cameraPos);
}
}
if (!wantToDisplay)
{
// We do not want to display this node - delegate to children
if (mChunk) if (mChunk)
mChunk->setVisible(false);
if (hasChildren())
{ {
// Are children already loaded?
bool childrenLoaded = true;
for (int i=0; i<4; ++i) for (int i=0; i<4; ++i)
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;
}
else
{
bool success = true;
for (int i=0; i<4; ++i)
success = mChildren[i]->update(cameraPos) & success;
return success;
}
}
return false;
} }
void QuadTreeNode::load(const LoadResponseData &data) void QuadTreeNode::load(const LoadResponseData &data)
{ {
assert (!mChunk); assert (!mChunk);
std::cout << "loading " << std::endl;
mChunk = new Chunk(mTerrain->getBufferCache().getUVBuffer(), mBounds, data); mChunk = new Chunk(mTerrain->getBufferCache().getUVBuffer(), mBounds, data);
mChunk->setVisibilityFlags(mTerrain->getVisiblityFlags()); mChunk->setVisibilityFlags(mTerrain->getVisiblityFlags());
mChunk->setCastShadows(true); mChunk->setCastShadows(true);
@ -380,9 +393,10 @@ void QuadTreeNode::load(const LoadResponseData &data)
mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled()); mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled());
mMaterialGenerator->enableSplitShadows(mTerrain->getSplitShadowsEnabled()); mMaterialGenerator->enableSplitShadows(mTerrain->getSplitShadowsEnabled());
if (mTerrain->areLayersLoaded())
{
if (mSize == 1) if (mSize == 1)
{ {
ensureLayerInfo();
mChunk->setMaterial(mMaterialGenerator->generate(mChunk->getMaterial())); mChunk->setMaterial(mMaterialGenerator->generate(mChunk->getMaterial()));
} }
else else
@ -391,41 +405,39 @@ void QuadTreeNode::load(const LoadResponseData &data)
mMaterialGenerator->setCompositeMap(mCompositeMap->getName()); mMaterialGenerator->setCompositeMap(mCompositeMap->getName());
mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap(mChunk->getMaterial())); mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap(mChunk->getMaterial()));
} }
}
// else: will be loaded in loadMaterials() after background thread has finished loading layers
mChunk->setVisible(false); mChunk->setVisible(false);
mLoadState = LS_Loaded; mLoadState = LS_Loaded;
} }
void QuadTreeNode::unload() void QuadTreeNode::unload(bool recursive)
{ {
if (mChunk) if (mChunk)
{ {
Ogre::MaterialManager::getSingleton().remove(mChunk->getMaterial()->getName());
mSceneNode->detachObject(mChunk); mSceneNode->detachObject(mChunk);
delete mChunk; delete mChunk;
mChunk = NULL; mChunk = NULL;
// destroy blendmaps
if (mMaterialGenerator)
{
const std::vector<Ogre::TexturePtr>& list = mMaterialGenerator->getBlendmapList();
for (std::vector<Ogre::TexturePtr>::const_iterator it = list.begin(); it != list.end(); ++it)
Ogre::TextureManager::getSingleton().remove((*it)->getName());
mMaterialGenerator->setBlendmapList(std::vector<Ogre::TexturePtr>());
mMaterialGenerator->setLayerList(std::vector<LayerInfo>());
mMaterialGenerator->setCompositeMap("");
}
if (!mCompositeMap.isNull()) if (!mCompositeMap.isNull())
{ {
Ogre::TextureManager::getSingleton().remove(mCompositeMap->getName()); Ogre::TextureManager::getSingleton().remove(mCompositeMap->getName());
mCompositeMap.setNull(); mCompositeMap.setNull();
} }
}
// Do *not* set this when we are still loading!
mLoadState = LS_Unloaded; mLoadState = LS_Unloaded;
} }
if (recursive && hasChildren())
{
for (int i=0; i<4; ++i)
mChildren[i]->unload();
}
}
void QuadTreeNode::updateIndexBuffers() void QuadTreeNode::updateIndexBuffers()
{ {
if (hasChunk()) if (hasChunk())
@ -479,17 +491,53 @@ size_t QuadTreeNode::getActualLodLevel()
return mLodLevel /* + mChunk->getAdditionalLod() */; return mLodLevel /* + mChunk->getAdditionalLod() */;
} }
void QuadTreeNode::ensureLayerInfo() void QuadTreeNode::loadLayers(const LayerCollection& collection)
{ {
if (mMaterialGenerator->hasLayers()) assert (!mMaterialGenerator->hasLayers());
std::vector<Ogre::TexturePtr> blendTextures;
for (std::vector<Ogre::PixelBox>::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; return;
std::vector<Ogre::TexturePtr> blendmaps; // Load children first since we depend on them when creating a composite map
std::vector<LayerInfo> layerList; if (hasChildren())
mTerrain->getStorage()->getBlendmaps(mSize, mCenter, mTerrain->getShadersEnabled(), blendmaps, layerList); {
for (int i=0; i<4; ++i)
mChildren[i]->loadMaterials();
}
mMaterialGenerator->setLayerList(layerList); if (mChunk)
mMaterialGenerator->setBlendmapList(blendmaps); {
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<float> area) void QuadTreeNode::prepareForCompositeMap(Ogre::TRect<float> area)
@ -525,8 +573,7 @@ void QuadTreeNode::prepareForCompositeMap(Ogre::TRect<float> area)
} }
else else
{ {
ensureLayerInfo(); // TODO: when to destroy?
Ogre::MaterialPtr material = mMaterialGenerator->generateForCompositeMapRTT(Ogre::MaterialPtr()); Ogre::MaterialPtr material = mMaterialGenerator->generateForCompositeMapRTT(Ogre::MaterialPtr());
makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, material); 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) for (int i=0; i<4; ++i)
mChildren[i]->applyMaterials(); 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);
}

View file

@ -51,8 +51,6 @@ namespace Terrain
QuadTreeNode (World* terrain, ChildDirection dir, float size, const Ogre::Vector2& center, QuadTreeNode* parent); QuadTreeNode (World* terrain, ChildDirection dir, float size, const Ogre::Vector2& center, QuadTreeNode* parent);
~QuadTreeNode(); ~QuadTreeNode();
void setVisible(bool visible);
/// Rebuild all materials /// Rebuild all materials
void applyMaterials(); void applyMaterials();
@ -100,7 +98,9 @@ namespace Terrain
World* getTerrain() { return mTerrain; } World* getTerrain() { return mTerrain; }
/// Adjust LODs for the given camera position, possibly splitting up chunks or merging them. /// 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. /// Adjust index buffers of chunks to stitch together chunks of different LOD, so that cracks are avoided.
/// Call after QuadTreeNode::update! /// Call after QuadTreeNode::update!
@ -122,13 +122,17 @@ namespace Terrain
/// Add a textured quad to a specific 2d area in the composite map scenemanager. /// 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 /// Only nodes with size <= 1 can be rendered with alpha blending, so larger nodes will simply
/// call this method on their children. /// 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 area area in image space to put the quad
/// @param quads collect quads here so they can be deleted later /// @param quads collect quads here so they can be deleted later
void prepareForCompositeMap(Ogre::TRect<float> area); void prepareForCompositeMap(Ogre::TRect<float> area);
/// Create a chunk for this node from the given data. /// Create a chunk for this node from the given data.
void load (const LoadResponseData& 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; } LoadState getLoadState() { return mLoadState; }
@ -138,10 +142,6 @@ namespace Terrain
LoadState mLoadState; 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; bool mIsDummy;
float mSize; float mSize;
size_t mLodLevel; // LOD if we were to render this node in one chunk size_t mLodLevel; // LOD if we were to render this node in one chunk
@ -162,7 +162,6 @@ namespace Terrain
Ogre::TexturePtr mCompositeMap; Ogre::TexturePtr mCompositeMap;
void ensureLayerInfo();
void ensureCompositeMap(); void ensureCompositeMap();
}; };

View file

@ -44,6 +44,7 @@ namespace Terrain
/// Create textures holding layer blend values for a terrain chunk. /// 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 /// @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. /// 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 chunkSize size of the terrain chunk in cell units
/// @param chunkCenter center of the 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) - /// @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 blendmaps created blendmaps will be written here
/// @param layerList names of the layer textures used 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, virtual void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack,
std::vector<Ogre::TexturePtr>& blendmaps, std::vector<Ogre::PixelBox>& blendmaps,
std::vector<LayerInfo>& layerList) = 0; std::vector<LayerInfo>& 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<QuadTreeNode*>& nodes, std::vector<LayerCollection>& out, bool pack) = 0;
virtual float getHeightAt (const Ogre::Vector3& worldPos) = 0; virtual float getHeightAt (const Ogre::Vector3& worldPos) = 0;
virtual LayerInfo getDefaultLayer() = 0; virtual LayerInfo getDefaultLayer() = 0;

View file

@ -7,7 +7,6 @@
#include <OgreRenderTexture.h> #include <OgreRenderTexture.h>
#include <OgreSceneNode.h> #include <OgreSceneNode.h>
#include <OgreRoot.h> #include <OgreRoot.h>
#include <OgreTimer.h> // TEMP
#include "storage.hpp" #include "storage.hpp"
#include "quadtreenode.hpp" #include "quadtreenode.hpp"
@ -52,6 +51,9 @@ namespace
namespace Terrain namespace Terrain
{ {
const Ogre::uint REQ_ID_CHUNK = 1;
const Ogre::uint REQ_ID_LAYERS = 2;
World::World(Ogre::SceneManager* sceneMgr, World::World(Ogre::SceneManager* sceneMgr,
Storage* storage, int visibilityFlags, bool distantLand, bool shaders, Alignment align, float minBatchSize, float maxBatchSize) Storage* storage, int visibilityFlags, bool distantLand, bool shaders, Alignment align, float minBatchSize, float maxBatchSize)
: mStorage(storage) : mStorage(storage)
@ -70,6 +72,7 @@ namespace Terrain
, mChunksLoading(0) , mChunksLoading(0)
, mWorkQueueChannel(0) , mWorkQueueChannel(0)
, mCache(storage->getCellVertices()) , mCache(storage->getCellVertices())
, mLayerLoadPending(true)
{ {
#if TERRAIN_USE_SHADER == 0 #if TERRAIN_USE_SHADER == 0
if (mShaders) if (mShaders)
@ -102,8 +105,12 @@ namespace Terrain
mRootSceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(); 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); mRootNode = new QuadTreeNode(this, Root, size, Ogre::Vector2(centerX, centerY), NULL);
buildQuadTree(mRootNode); buildQuadTree(mRootNode, data.mNodes);
//loadingListener->indicateProgress(); //loadingListener->indicateProgress();
mRootNode->initAabb(); mRootNode->initAabb();
//loadingListener->indicateProgress(); //loadingListener->indicateProgress();
@ -114,6 +121,9 @@ namespace Terrain
mWorkQueueChannel = wq->getChannel("LargeTerrain"); mWorkQueueChannel = wq->getChannel("LargeTerrain");
wq->addRequestHandler(mWorkQueueChannel, this); wq->addRequestHandler(mWorkQueueChannel, this);
wq->addResponseHandler(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() World::~World()
@ -126,7 +136,7 @@ namespace Terrain
delete mStorage; delete mStorage;
} }
void World::buildQuadTree(QuadTreeNode *node) void World::buildQuadTree(QuadTreeNode *node, std::vector<QuadTreeNode*>& leafs)
{ {
float halfSize = node->getSize()/2.f; float halfSize = node->getSize()/2.f;
@ -142,6 +152,7 @@ namespace Terrain
Ogre::Vector3(halfSize*cellWorldSize, halfSize*cellWorldSize, maxZ)); Ogre::Vector3(halfSize*cellWorldSize, halfSize*cellWorldSize, maxZ));
convertBounds(bounds); convertBounds(bounds);
node->setBoundingBox(bounds); node->setBoundingBox(bounds);
leafs.push_back(node);
} }
else else
node->markAsDummy(); // no data available for this node, skip it 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(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(NW, halfSize, node->getCenter() + Ogre::Vector2(-halfSize/2.f, halfSize/2.f));
node->createChild(NE, halfSize, node->getCenter() + halfSize/2.f); node->createChild(NE, halfSize, node->getCenter() + halfSize/2.f);
buildQuadTree(node->getChild(SW)); buildQuadTree(node->getChild(SW), leafs);
buildQuadTree(node->getChild(SE)); buildQuadTree(node->getChild(SE), leafs);
buildQuadTree(node->getChild(NW)); buildQuadTree(node->getChild(NW), leafs);
buildQuadTree(node->getChild(NE)); buildQuadTree(node->getChild(NE), leafs);
// if all children are dummy, we are also dummy // if all children are dummy, we are also dummy
for (int i=0; i<4; ++i) for (int i=0; i<4; ++i)
@ -267,7 +278,7 @@ namespace Terrain
void World::syncLoad() void World::syncLoad()
{ {
while (mChunksLoading) while (mChunksLoading || mLayerLoadPending)
{ {
OGRE_THREAD_SLEEP(0); OGRE_THREAD_SLEEP(0);
Ogre::Root::getSingleton().getWorkQueue()->processResponses(); Ogre::Root::getSingleton().getWorkQueue()->processResponses();
@ -275,6 +286,8 @@ namespace Terrain
} }
Ogre::WorkQueue::Response* World::handleRequest(const Ogre::WorkQueue::Request *req, const Ogre::WorkQueue *srcQ) Ogre::WorkQueue::Response* World::handleRequest(const Ogre::WorkQueue::Request *req, const Ogre::WorkQueue *srcQ)
{
if (req->getType() == REQ_ID_CHUNK)
{ {
const LoadRequestData data = Ogre::any_cast<LoadRequestData>(req->getData()); const LoadRequestData data = Ogre::any_cast<LoadRequestData>(req->getData());
@ -282,21 +295,28 @@ namespace Terrain
LoadResponseData* responseData = new LoadResponseData(); LoadResponseData* responseData = new LoadResponseData();
Ogre::Timer timer;
getStorage()->fillVertexBuffers(node->getNativeLodLevel(), node->getSize(), node->getCenter(), getAlign(), getStorage()->fillVertexBuffers(node->getNativeLodLevel(), node->getSize(), node->getCenter(), getAlign(),
responseData->mPositions, responseData->mNormals, responseData->mColours); 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<LayersRequestData>(req->getData());
responseData->time = timer.getMicroseconds(); LayersResponseData* responseData = new LayersResponseData();
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) void World::handleResponse(const Ogre::WorkQueue::Response *res, const Ogre::WorkQueue *srcQ)
{ {
static unsigned long time = 0; assert(res->succeeded() && "Response failure not handled");
if (res->succeeded())
if (res->getRequest()->getType() == REQ_ID_CHUNK)
{ {
LoadResponseData* data = Ogre::any_cast<LoadResponseData*>(res->getData()); LoadResponseData* data = Ogre::any_cast<LoadResponseData*>(res->getData());
@ -304,23 +324,31 @@ namespace Terrain
requestData.mNode->load(*data); requestData.mNode->load(*data);
time += data->time;
delete data; delete data;
std::cout << "RESPONSE, reqs took ms" << time/1000.f << std::endl;
}
--mChunksLoading; --mChunksLoading;
} }
else // REQ_ID_LAYERS
{
LayersResponseData* data = Ogre::any_cast<LayersResponseData*>(res->getData());
for (std::vector<LayerCollection>::iterator it = data->mLayerCollections.begin(); it != data->mLayerCollections.end(); ++it)
{
it->mTarget->loadLayers(*it);
}
mRootNode->loadMaterials();
mLayerLoadPending = false;
}
}
void World::queueLoad(QuadTreeNode *node) void World::queueLoad(QuadTreeNode *node)
{ {
LoadRequestData data; LoadRequestData data;
data.mNode = node; data.mNode = node;
data.mPack = getShadersEnabled();
const Ogre::uint16 loadRequestId = 1; Ogre::Root::getSingleton().getWorkQueue()->addRequest(mWorkQueueChannel, REQ_ID_CHUNK, Ogre::Any(data));
Ogre::Root::getSingleton().getWorkQueue()->addRequest(mWorkQueueChannel, loadRequestId, Ogre::Any(data));
++mChunksLoading; ++mChunksLoading;
} }
} }

View file

@ -122,15 +122,20 @@ namespace Terrain
/// Maximum size of a terrain batch along one side (in cell units) /// Maximum size of a terrain batch along one side (in cell units)
float mMaxBatchSize; float mMaxBatchSize;
void buildQuadTree(QuadTreeNode* node); void buildQuadTree(QuadTreeNode* node, std::vector<QuadTreeNode*>& leafs);
BufferCache mCache; BufferCache mCache;
// Are layers for leaf nodes loaded? This is done once at startup (but in a background thread)
bool mLayerLoadPending;
public: public:
// ----INTERNAL---- // ----INTERNAL----
Ogre::SceneManager* getCompositeMapSceneManager() { return mCompositeMapSceneMgr; } Ogre::SceneManager* getCompositeMapSceneManager() { return mCompositeMapSceneMgr; }
BufferCache& getBufferCache() { return mCache; } BufferCache& getBufferCache() { return mCache; }
bool areLayersLoaded() { return !mLayerLoadPending; }
// Delete all quads // Delete all quads
void clearCompositeMapSceneManager(); void clearCompositeMapSceneManager();
void renderCompositeMap (Ogre::TexturePtr target); void renderCompositeMap (Ogre::TexturePtr target);
@ -151,7 +156,6 @@ namespace Terrain
struct LoadRequestData struct LoadRequestData
{ {
QuadTreeNode* mNode; QuadTreeNode* mNode;
bool mPack;
friend std::ostream& operator<<(std::ostream& o, const LoadRequestData& r) friend std::ostream& operator<<(std::ostream& o, const LoadRequestData& r)
{ return o; } { return o; }
@ -162,16 +166,28 @@ namespace Terrain
std::vector<float> mPositions; std::vector<float> mPositions;
std::vector<float> mNormals; std::vector<float> mNormals;
std::vector<Ogre::uint8> mColours; 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) friend std::ostream& operator<<(std::ostream& o, const LoadResponseData& r)
{ return o; } { return o; }
}; };
struct LayersRequestData
{
std::vector<QuadTreeNode*> mNodes;
bool mPack;
friend std::ostream& operator<<(std::ostream& o, const LayersRequestData& r)
{ return o; }
};
struct LayersResponseData
{
std::vector<LayerCollection> mLayerCollections;
friend std::ostream& operator<<(std::ostream& o, const LayersResponseData& r)
{ return o; }
};
} }
#endif #endif