diff --git a/simgear/scene/material/mat.cxx b/simgear/scene/material/mat.cxx index 57bbc26a..9531d983 100644 --- a/simgear/scene/material/mat.cxx +++ b/simgear/scene/material/mat.cxx @@ -546,6 +546,17 @@ std::string SGMaterial::get_one_texture(int setIndex, int texIndex) return texturePath; } +std::size_t SGMaterial::get_num_textures(int setIndex) +{ + if (_status.empty()) { + SG_LOG( SG_GENERAL, SG_WARN, "No material available."); + return 0; + } + + SGMaterial::_internal_state st = _status[setIndex % _status.size()]; + return st.texture_paths.size(); +} + void SGMaterial::buildEffectProperties(const SGReaderWriterOptions* options) { using namespace osg; diff --git a/simgear/scene/material/mat.hxx b/simgear/scene/material/mat.hxx index 88e8cd2f..4ccf2375 100644 --- a/simgear/scene/material/mat.hxx +++ b/simgear/scene/material/mat.hxx @@ -147,11 +147,15 @@ public: */ std::string get_one_texture(int setIndex, int texIndex); + /** + * Get the number of textures defined in this set + */ + std::size_t get_num_textures(int setIndex); /** * Get the number of textures assigned to this material. */ - inline int get_num() const { return _status.size(); } + inline int get_num_texture_sets() const { return _status.size(); } /** diff --git a/simgear/scene/material/matlib.cxx b/simgear/scene/material/matlib.cxx index c8371454..ce98a2ce 100644 --- a/simgear/scene/material/matlib.cxx +++ b/simgear/scene/material/matlib.cxx @@ -331,9 +331,10 @@ SGMaterialCache::Atlas SGMaterialLib::getMaterialTextureAtlas(SGVec2f center, co { SGMaterialCache::Atlas atlas; - // Non-VPB does not use the Atlas, so save some both and return + // Non-VPB does not use the Atlas, so save some effort and return if (! SGSceneFeatures::instance()->getVPBActive()) return atlas; + // A simple key to the atlas is just the list of textures. std::string id; const_landclass_map_iterator lc_iter = landclasslib.begin(); for (; lc_iter != landclasslib.end(); ++lc_iter) { @@ -347,7 +348,7 @@ SGMaterialCache::Atlas SGMaterialLib::getMaterialTextureAtlas(SGVec2f center, co if (atlas_iter != _atlasCache.end()) return atlas_iter->second; - // Cache lookup failure - generate a new atlas, but only if we have a change of reading any textures + // Cache lookup failure - generate a new atlas, but only if we have a chance of reading any textures if (options == 0) { return atlas; } @@ -356,10 +357,12 @@ SGMaterialCache::Atlas SGMaterialLib::getMaterialTextureAtlas(SGVec2f center, co if (landclasslib.size() > 128) SG_LOG(SG_TERRAIN, SG_ALERT, "Too many landclass entries for uniform arrays"); - atlas.dimensions = new osg::Uniform(osg::Uniform::Type::FLOAT_VEC4, "dimensionsArray", 128); - atlas.ambient = new osg::Uniform(osg::Uniform::Type::FLOAT_VEC4, "ambientArray", 128); - atlas.diffuse = new osg::Uniform(osg::Uniform::Type::FLOAT_VEC4, "diffuseArray", 128); - atlas.specular = new osg::Uniform(osg::Uniform::Type::FLOAT_VEC4, "specularArray", 128); + atlas.textureLookup1 = new osg::Uniform(osg::Uniform::Type::FLOAT_VEC4, "fg_textureLookup1", 128); + atlas.textureLookup2 = new osg::Uniform(osg::Uniform::Type::FLOAT_VEC4, "fg_textureLookup2", 128); + atlas.dimensions = new osg::Uniform(osg::Uniform::Type::FLOAT_VEC4, "fg_dimensionsArray", 128); + atlas.ambient = new osg::Uniform(osg::Uniform::Type::FLOAT_VEC4, "fg_ambientArray", 128); + atlas.diffuse = new osg::Uniform(osg::Uniform::Type::FLOAT_VEC4, "fg_diffuseArray", 128); + atlas.specular = new osg::Uniform(osg::Uniform::Type::FLOAT_VEC4, "fg_specularArray", 128); atlas.image->setMaxAnisotropy(SGSceneFeatures::instance()->getTextureFilter()); atlas.image->setResizeNonPowerOfTwoHint(false); @@ -367,53 +370,85 @@ SGMaterialCache::Atlas SGMaterialLib::getMaterialTextureAtlas(SGVec2f center, co atlas.image->setWrap(osg::Texture::WRAP_S,osg::Texture::REPEAT); atlas.image->setWrap(osg::Texture::WRAP_T,osg::Texture::REPEAT); - unsigned int index = 0; + unsigned int imageIndex = 0u; // Index into the image + unsigned int materialLookupIndex = 0u; // Index into the material lookup lc_iter = landclasslib.begin(); for (; lc_iter != landclasslib.end(); ++lc_iter) { - int i = lc_iter->first; + + int landclass = lc_iter->first; SGMaterial* mat = find(lc_iter->second.first, center); - atlas.index[i] = index; - atlas.waterAtlas[i] = lc_iter->second.second; + atlas.index[landclass] = materialLookupIndex; + atlas.waterAtlas[landclass] = lc_iter->second.second; + unsigned int textureList[SGMaterialCache::MAX_TEXTURES]; if (mat != NULL) { - // Just get the first texture in the first texture-set for the moment. - // Should add some variability in texture-set in the future. - const std::string texture = mat->get_one_texture(0,0); - if (! texture.empty()) { - if (texture.empty()) continue; + if (mat->get_num_textures(0) > SGMaterialCache::MAX_TEXTURES) { + SG_LOG(SG_GENERAL, SG_ALERT, "Unable to build texture atlas for landclass " + << landclass << " aka " << mat->get_names()[0] + << " too many textures: " << mat->get_num_textures(0) + << " (maximum " << SGMaterialCache::MAX_TEXTURES << ")"); + continue; + } - SGPath texturePath = SGPath("Textures"); - //texturePath.append(texture); - std::string fullPath = SGModelLib::findDataFile(texture, options, texturePath); + atlas.dimensions->setElement(materialLookupIndex, osg::Vec4f(mat->get_xsize(), mat->get_ysize(), mat->get_shininess(), 1.0f)); + atlas.ambient->setElement(materialLookupIndex, mat->get_ambient()); + atlas.diffuse->setElement(materialLookupIndex, mat->get_diffuse()); + atlas.specular->setElement(materialLookupIndex, mat->get_specular()); - if (fullPath.empty()) { - SG_LOG(SG_GENERAL, SG_ALERT, "Cannot find texture \"" - << texture << "\" in Textures folders when creating texture atlas"); - } - else - { - // Copy the texture into the atlas in the appropriate place - osg::ref_ptr subtexture = osgDB::readRefImageFile(fullPath, options); + // Add any missing textures into the atlas image + for (std::size_t i = 0; i < mat->get_num_textures(0); ++i) { + const std::string texture = mat->get_one_texture(0,i); + if (! texture.empty()) { - if (subtexture && subtexture->valid()) { + SGPath texturePath = SGPath("Textures"); + std::string fullPath = SGModelLib::findDataFile(texture, options, texturePath); - if ((subtexture->s() != 2048) || (subtexture->t() != 2048)) { - subtexture->scaleImage(2048,2048,1); - } - - atlas.image->setImage(index,subtexture); - atlas.dimensions->setElement(index, osg::Vec4f(mat->get_xsize(), mat->get_ysize(), mat->get_shininess(), 1.0f)); - atlas.ambient->setElement(index, mat->get_ambient()); - atlas.diffuse->setElement(index, mat->get_diffuse()); - atlas.specular->setElement(index, mat->get_specular()); + if (fullPath.empty()) { + SG_LOG(SG_GENERAL, SG_ALERT, "Cannot find texture \"" + << texture << "\" in Textures folders when creating texture atlas"); + textureList[i] = -2; + continue; } + if (atlas.textureMap.find(fullPath) == atlas.textureMap.end()) { + // Copy the texture into the atlas in the appropriate place + osg::ref_ptr subtexture = osgDB::readRefImageFile(fullPath, options); + + if (subtexture && subtexture->valid()) { + + if ((subtexture->s() != 2048) || (subtexture->t() != 2048)) { + subtexture->scaleImage(2048,2048,1); + } + + atlas.image->setImage(imageIndex,subtexture); + atlas.textureMap[fullPath] = imageIndex; + ++imageIndex; + } + } + + // At this point we know that the texture is present in the + // atlas and referenced in the textureMap, so add it to the materialLookup + textureList[i] = atlas.textureMap[fullPath]; + //SG_LOG(SG_TERRAIN, SG_ALERT, "materialLookup Index: " << (materialLookupIndex * SGMaterialCache::MAX_TEXTURES + i) << " " << atlas.textureMap[fullPath] << " " << fullPath); + } else { + textureList[i] = -2; } } + + // Fill out the rest of the array + for (std::size_t i = mat->get_num_textures(0); i < SGMaterialCache::MAX_TEXTURES; ++i) { + textureList[i] = -2; + } + + // We now have a textureList containing the full set of textures. Pack the relevant ones into the Vec4 of the index Uniform. + // This is a bit of a hack to maintain compatibility with the WS2.0 material definitions, as the material definitions use the + // 11-15th textures for the various overlay textures for terrain-default.eff, we do the same for ws30.eff + atlas.textureLookup1->setElement(materialLookupIndex, osg::Vec4f( (float) (textureList[0] / 255.0), (float) (textureList[11] / 255.0), (float) (textureList[12] / 255.0), (float) (textureList[13] / 255.0))); + atlas.textureLookup2->setElement(materialLookupIndex, osg::Vec4f( (float) (textureList[14] / 255.0), (float) (textureList[15] / 255.0), 0.0,0.0)); } - ++index; + ++materialLookupIndex; } // Cache for future lookups diff --git a/simgear/scene/material/matlib.hxx b/simgear/scene/material/matlib.hxx index e80a1ca1..a63e1544 100644 --- a/simgear/scene/material/matlib.hxx +++ b/simgear/scene/material/matlib.hxx @@ -50,20 +50,38 @@ class SGMaterialCache : public osg::Referenced { public: - // An atlas, consisting of a landclass index, an atlas image and set - // of texture1D containing further data + // A texture Atlas with multiple levels of indirection: + + // - A given landclass maps to an index in the materialLookup. + // - The materialLookup results in a set of texture indexes that + // represent the textures referenced by the texture-set in the material + // - the texture indexes index into the Atlas itself. + + // Mapping of landclass numbers to indexes within the atlas + // materialLookup typedef std::map AtlasIndex; + + // Mapping of texture filenames to their index in the Atlas image itself. + typedef std::map TextureMap; + + // The Texture array itself typedef osg::ref_ptr AtlasImage; typedef std::map WaterAtlas; + // Maximum number of textures per texture-set for the Atlas. + static const unsigned int MAX_TEXTURES = 16; + typedef struct { AtlasIndex index; AtlasImage image; + osg::ref_ptr textureLookup1; + osg::ref_ptr textureLookup2; osg::ref_ptr dimensions; osg::ref_ptr ambient; osg::ref_ptr diffuse; osg::ref_ptr specular; WaterAtlas waterAtlas; + TextureMap textureMap; } Atlas; // Constructor diff --git a/simgear/scene/tgdb/VPBTechnique.cxx b/simgear/scene/tgdb/VPBTechnique.cxx index 74e59d56..6cc8a39e 100644 --- a/simgear/scene/tgdb/VPBTechnique.cxx +++ b/simgear/scene/tgdb/VPBTechnique.cxx @@ -1257,10 +1257,10 @@ void VPBTechnique::generateGeometry(BufferData& buffer, Locator* masterLocator, SG_LOG(SG_TERRAIN, SG_DEBUG, "Tile Level " << _terrainTile->getTileID().level << " width " << buffer._width << " height " << buffer._height); - osg::ref_ptr twu = new osg::Uniform("tile_width", buffer._width); + osg::ref_ptr twu = new osg::Uniform("fg_tileWidth", buffer._width); landStateSet->addUniform(twu); waterStateSet->addUniform(twu); - osg::ref_ptr thu = new osg::Uniform("tile_height", buffer._height); + osg::ref_ptr thu = new osg::Uniform("fg_tileHeight", buffer._height); landStateSet->addUniform(thu); waterStateSet->addUniform(thu); @@ -1322,7 +1322,7 @@ void VPBTechnique::applyColorLayers(BufferData& buffer, Locator* masterLocator) osg::Texture2D* texture = SGLoadTexture2D(SGPath(orthotexture), _options, true, true); osg::StateSet* stateset = buffer._landGeode->getOrCreateStateSet(); stateset->setTextureAttributeAndModes(0, texture); - stateset->addUniform(new osg::Uniform("photoScenery", true)); + stateset->addUniform(new osg::Uniform("fg_photoScenery", true)); } else { SG_LOG(SG_TERRAIN, SG_DEBUG, "Unable to find " << orthotexture); photoScenery = false; @@ -1354,7 +1354,14 @@ void VPBTechnique::applyColorLayers(BufferData& buffer, Locator* masterLocator) for (unsigned int t = 0; t < (unsigned int) image->t(); t++) { osg::Vec4d c = image->getColor(s, t); int i = int(round(c.x() * 255.0)); - c.set(c.x(), (double) (atlasIndex[i] / 255.0), c.z(), c.w() ); + + // Get texture 0 for this material to save a Uniform lookup in the shader for + // maximal performance on single-texture shaders + osg::Vec4f texture1; + if (! atlas.textureLookup1->getElement(atlasIndex[i], texture1)) { + SG_LOG(SG_TERRAIN, SG_ALERT, "Failed to do texture lookup for landclass: " << atlasIndex[i]); + } + c.set((double) (atlasIndex[i] / 255.0), texture1.r(), texture1.g(), texture1.b()); image->setColor(c, s, t); } } @@ -1377,13 +1384,16 @@ void VPBTechnique::applyColorLayers(BufferData& buffer, Locator* masterLocator) osg::StateSet* stateset = buffer._landGeode->getOrCreateStateSet(); stateset->setTextureAttributeAndModes(0, texture2D, osg::StateAttribute::ON); stateset->setTextureAttributeAndModes(1, atlas.image, osg::StateAttribute::ON); - stateset->addUniform(new osg::Uniform("photoScenery", false)); + stateset->addUniform(new osg::Uniform("fg_photoScenery", false)); stateset->addUniform(atlas.dimensions); stateset->addUniform(atlas.ambient); stateset->addUniform(atlas.diffuse); stateset->addUniform(atlas.specular); - stateset->addUniform(new osg::Uniform("zUpTransform", osg::Matrixf(osg::Matrix::inverse(makeZUpFrameRelative(loc))))); - stateset->addUniform(new osg::Uniform("modelOffset", buffer._transform->getMatrix().getTrans())); + stateset->addUniform(atlas.textureLookup1); + stateset->addUniform(atlas.textureLookup2); + stateset->addUniform(new osg::Uniform("fg_zUpTransform", osg::Matrixf(osg::Matrix::inverse(makeZUpFrameRelative(loc))))); + stateset->addUniform(new osg::Uniform("fg_modelOffset", (osg::Vec3f) buffer._transform->getMatrix().getTrans())); + //SG_LOG(SG_TERRAIN, SG_ALERT, "modeOffset:" << buffer._transform->getMatrix().getTrans().length() << " " << buffer._transform->getMatrix().getTrans()); } }