diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 7569cea1f..6b5cf460c 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -168,6 +168,7 @@ namespace MWRender , mFieldOfViewOverridden(false) { resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); + resourceSystem->getSceneManager()->setShaderPath(resourcePath + "/shaders"); osg::ref_ptr sceneRoot = new SceneUtil::LightManager; sceneRoot->setLightingMask(Mask_Lighting); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 0ac32f65e..2508cdc82 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -44,6 +44,10 @@ add_component_dir (resource scenemanager keyframemanager imagemanager bulletshapemanager bulletshape niffilemanager objectcache multiobjectcache resourcesystem resourcemanager ) +add_component_dir (shader + shadermanager shadervisitor + ) + add_component_dir (sceneutil clone attach visitor util statesetupdater controller skeleton riggeometry lightcontroller lightmanager lightutil positionattitudetransform workqueue unrefqueue diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index e78cc7cb2..f4846a8a0 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -22,6 +22,9 @@ #include #include +#include +#include + #include "imagemanager.hpp" #include "niffilemanager.hpp" #include "objectcache.hpp" @@ -205,6 +208,7 @@ namespace Resource SceneManager::SceneManager(const VFS::Manager *vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) : ResourceManager(vfs) + , mShaderManager(new Shader::ShaderManager) , mInstanceCache(new MultiObjectCache) , mImageManager(imageManager) , mNifFileManager(nifFileManager) @@ -221,6 +225,11 @@ namespace Resource // this has to be defined in the .cpp file as we can't delete incomplete types } + void SceneManager::setShaderPath(const std::string &path) + { + mShaderManager->setShaderPath(path); + } + /// @brief Callback to read image files from the VFS. class ImageReadCallback : public osgDB::ReadFileCallback { @@ -329,6 +338,9 @@ namespace Resource SetFilterSettingsControllerVisitor setFilterSettingsControllerVisitor(mMinFilter, mMagFilter, mMaxAnisotropy); loaded->accept(setFilterSettingsControllerVisitor); + Shader::ShaderVisitor shaderVisitor(*mShaderManager.get(), "objects_vertex.glsl", "objects_fragment.glsl"); + loaded->accept(shaderVisitor); + // share state mSharedStateMutex.lock(); osgDB::Registry::instance()->getOrCreateSharedStateManager()->share(loaded.get()); diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 987b7898e..d86c746d7 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -21,6 +22,11 @@ namespace osgUtil class IncrementalCompileOperation; } +namespace Shader +{ + class ShaderManager; +} + namespace Resource { @@ -34,6 +40,8 @@ namespace Resource SceneManager(const VFS::Manager* vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager); ~SceneManager(); + void setShaderPath(const std::string& path); + /// Get a read-only copy of this scene "template" /// @note If the given filename does not exist or fails to load, an error marker mesh will be used instead. /// If even the error marker mesh can not be found, an exception is thrown. @@ -97,6 +105,7 @@ namespace Resource osg::ref_ptr createInstance(const std::string& name); + std::auto_ptr mShaderManager; osg::ref_ptr mInstanceCache; OpenThreads::Mutex mSharedStateMutex; diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp new file mode 100644 index 000000000..fc9a1f722 --- /dev/null +++ b/components/shader/shadermanager.cpp @@ -0,0 +1,81 @@ +#include "shadermanager.hpp" + +#include +#include + +#include +#include +#include + +#include + +namespace Shader +{ + + void ShaderManager::setShaderPath(const std::string &path) + { + mPath = path; + } + + osg::ref_ptr ShaderManager::getShader(const std::string &shaderTemplate, const ShaderManager::DefineMap &defines, osg::Shader::Type shaderType) + { + OpenThreads::ScopedLock lock(mMutex); + + // read the template if we haven't already + TemplateMap::iterator templateIt = mShaderTemplates.find(shaderTemplate); + if (templateIt == mShaderTemplates.end()) + { + boost::filesystem::path p = (boost::filesystem::path(mPath) / shaderTemplate); + boost::filesystem::ifstream stream; + stream.open(p); + if (stream.fail()) + { + std::cerr << "Failed to open " << p.string() << std::endl; + return NULL; + } + std::stringstream buffer; + buffer << stream.rdbuf(); + + templateIt = mShaderTemplates.insert(std::make_pair(shaderTemplate, buffer.str())).first; + } + + ShaderMap::iterator shaderIt = mShaders.find(std::make_pair(shaderTemplate, defines)); + if (shaderIt == mShaders.end()) + { + std::string shaderSource = templateIt->second; + + const char escapeCharacter = '@'; + size_t foundPos = 0; + while ((foundPos = shaderSource.find(escapeCharacter)) != std::string::npos) + { + size_t endPos = shaderSource.find_first_of(" \n\r()[].;", foundPos); + if (endPos == std::string::npos) + { + std::cerr << "Unexpected EOF" << std::endl; + return NULL; + } + std::string define = shaderSource.substr(foundPos+1, endPos - (foundPos+1)); + DefineMap::const_iterator defineFound = defines.find(define); + if (defineFound == defines.end()) + { + std::cerr << "Undefined " << define << " in shader " << shaderTemplate << std::endl; + return NULL; + } + else + { + shaderSource.replace(foundPos, endPos-foundPos, defineFound->second); + } + } + + osg::ref_ptr shader (new osg::Shader(shaderType)); + shader->setShaderSource(shaderSource); + // Assign a unique name to allow the SharedStateManager to compare shaders efficiently + static unsigned int counter = 0; + shader->setName(boost::lexical_cast(counter++)); + + shaderIt = mShaders.insert(std::make_pair(std::make_pair(shaderTemplate, defines), shader)).first; + } + return shaderIt->second; + } + +} diff --git a/components/shader/shadermanager.hpp b/components/shader/shadermanager.hpp new file mode 100644 index 000000000..e0ec3fe5f --- /dev/null +++ b/components/shader/shadermanager.hpp @@ -0,0 +1,49 @@ +#ifndef OPENMW_COMPONENTS_SHADERMANAGER_H +#define OPENMW_COMPONENTS_SHADERMANAGER_H + +#include +#include + +#include + +#include + +#include + +namespace Shader +{ + + /// @brief Reads shader template files and turns them into a concrete shader, based on a list of define's. + /// @par Shader templates can get the value of a define with the syntax @define. + class ShaderManager + { + public: + void setShaderPath(const std::string& path); + + typedef std::map DefineMap; + + /// Create or retrieve a shader instance. + /// @param shaderTemplate The filename of the shader template. + /// @param defines Define values that can be retrieved by the shader template. + /// @param shaderType The type of shader (usually vertex or fragment shader). + /// @note May return NULL on failure. + /// @note Thread safe. + osg::ref_ptr getShader(const std::string& shaderTemplate, const DefineMap& defines, osg::Shader::Type shaderType); + + private: + std::string mPath; + + // + typedef std::map TemplateMap; + TemplateMap mShaderTemplates; + + typedef std::pair MapKey; + typedef std::map > ShaderMap; + ShaderMap mShaders; + + OpenThreads::Mutex mMutex; + }; + +} + +#endif diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp new file mode 100644 index 000000000..235e63d95 --- /dev/null +++ b/components/shader/shadervisitor.cpp @@ -0,0 +1,195 @@ +#include "shadervisitor.hpp" + +#include + +#include +#include +#include + +#include + +#include + +#include "shadermanager.hpp" + +namespace Shader +{ + + ShaderVisitor::ShaderRequirements::ShaderRequirements() + : mColorMaterial(false) + , mVertexColorMode(GL_AMBIENT_AND_DIFFUSE) + , mTexStageRequiringTangents(-1) + { + } + + ShaderVisitor::ShaderVisitor(ShaderManager& shaderManager, const std::string &defaultVsTemplate, const std::string &defaultFsTemplate) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mShaderManager(shaderManager) + , mDefaultVsTemplate(defaultVsTemplate) + , mDefaultFsTemplate(defaultFsTemplate) + { + mRequirements.push_back(ShaderRequirements()); + } + + void ShaderVisitor::apply(osg::Node& node) + { + if (node.getStateSet()) + { + pushRequirements(); + applyStateSet(node.getStateSet()); + traverse(node); + popRequirements(); + } + else + traverse(node); + } + + void ShaderVisitor::applyStateSet(osg::StateSet* stateset) + { + const osg::StateSet::TextureAttributeList& texAttributes = stateset->getTextureAttributeList(); + for(unsigned int unit=0;unitgetTextureAttribute(unit, osg::StateAttribute::TEXTURE); + if (attr) + { + const osg::Texture* texture = attr->asTexture(); + if (texture) + { + if (!texture->getName().empty()) + { + mRequirements.back().mTextures[unit] = texture->getName(); + if (texture->getName() == "normalMap") + { + mRequirements.back().mTexStageRequiringTangents = unit; + // normal maps are by default off since the FFP can't render them, now that we'll use shaders switch to On + stateset->setTextureMode(unit, GL_TEXTURE_2D, osg::StateAttribute::ON); + } + } + else + std::cerr << "ShaderVisitor encountered unknown texture " << texture << std::endl; + } + } + // remove state that has no effect when rendering with shaders + stateset->removeTextureAttribute(unit, osg::StateAttribute::TEXENV); + } + + const osg::StateSet::AttributeList& attributes = stateset->getAttributeList(); + for (osg::StateSet::AttributeList::const_iterator it = attributes.begin(); it != attributes.end(); ++it) + { + if (it->first.first == osg::StateAttribute::MATERIAL) + { + const osg::Material* mat = static_cast(it->second.first.get()); + mRequirements.back().mVertexColorMode = mat->getColorMode(); + } + } + } + + void ShaderVisitor::pushRequirements() + { + mRequirements.push_back(mRequirements.back()); + } + + void ShaderVisitor::popRequirements() + { + mRequirements.pop_back(); + } + + void ShaderVisitor::createProgram(const ShaderRequirements &reqs, osg::StateSet *stateset) + { + ShaderManager::DefineMap defineMap; + const char* defaultTextures[] = { "diffuseMap", "normalMap", "emissiveMap", "darkMap", "detailMap" }; + for (unsigned int i=0; i::const_iterator texIt = reqs.mTextures.begin(); texIt != reqs.mTextures.end(); ++texIt) + { + defineMap[texIt->second] = "1"; + defineMap[texIt->second + std::string("UV")] = boost::lexical_cast(texIt->first); + } + + if (!reqs.mColorMaterial) + defineMap["colorMode"] = "0"; + else + { + switch (reqs.mVertexColorMode) + { + default: + case GL_AMBIENT_AND_DIFFUSE: + defineMap["colorMode"] = "2"; + break; + case GL_AMBIENT: + defineMap["colorMode"] = "1"; + break; + } + } + + osg::ref_ptr vertexShader (mShaderManager.getShader(mDefaultVsTemplate, defineMap, osg::Shader::VERTEX)); + osg::ref_ptr fragmentShader (mShaderManager.getShader(mDefaultFsTemplate, defineMap, osg::Shader::FRAGMENT)); + + if (vertexShader && fragmentShader) + { + osg::ref_ptr program (new osg::Program); + program->addShader(vertexShader); + program->addShader(fragmentShader); + + stateset->setAttributeAndModes(program, osg::StateAttribute::ON); + + for (std::map::const_iterator texIt = reqs.mTextures.begin(); texIt != reqs.mTextures.end(); ++texIt) + { + stateset->addUniform(new osg::Uniform(texIt->second.c_str(), texIt->first), osg::StateAttribute::ON); + } + } + } + + void ShaderVisitor::apply(osg::Geometry& geometry) + { + bool needPop = (geometry.getStateSet() != NULL); + if (geometry.getStateSet()) + { + pushRequirements(); + applyStateSet(geometry.getStateSet()); + } + + if (!mRequirements.empty()) + { + const ShaderRequirements& reqs = mRequirements.back(); + if (reqs.mTexStageRequiringTangents != -1) + { + osg::ref_ptr generator (new osgUtil::TangentSpaceGenerator); + generator->generate(&geometry, reqs.mTexStageRequiringTangents); + + geometry.setTexCoordArray(7, generator->getTangentArray(), osg::Array::BIND_PER_VERTEX); + } + + // TODO: find a better place for the stateset + createProgram(reqs, geometry.getOrCreateStateSet()); + } + + if (needPop) + popRequirements(); + } + + void ShaderVisitor::apply(osg::Drawable& drawable) + { + // non-Geometry drawable (e.g. particle system) + bool needPop = (drawable.getStateSet() != NULL); + + if (drawable.getStateSet()) + { + pushRequirements(); + applyStateSet(drawable.getStateSet()); + } + + if (!mRequirements.empty()) + { + // TODO: find a better place for the stateset + createProgram(mRequirements.back(), drawable.getOrCreateStateSet()); + } + + if (needPop) + popRequirements(); + } + +} diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp new file mode 100644 index 000000000..507f1417e --- /dev/null +++ b/components/shader/shadervisitor.hpp @@ -0,0 +1,54 @@ +#ifndef OPENMW_COMPONENTS_SHADERVISITOR_H +#define OPENMW_COMPONENTS_SHADERVISITOR_H + +#include + +namespace Shader +{ + + class ShaderManager; + + /// @brief Adjusts the given subgraph to render using shaders. + class ShaderVisitor : public osg::NodeVisitor + { + public: + ShaderVisitor(ShaderManager& shaderManager, const std::string& defaultVsTemplate, const std::string& defaultFsTemplate); + + virtual void apply(osg::Node& node); + + virtual void apply(osg::Drawable& drawable); + virtual void apply(osg::Geometry& geometry); + + void applyStateSet(osg::StateSet* stateset); + + void pushRequirements(); + void popRequirements(); + + private: + ShaderManager& mShaderManager; + + struct ShaderRequirements + { + ShaderRequirements(); + + // + std::map mTextures; + + bool mColorMaterial; + // osg::Material::ColorMode + int mVertexColorMode; + + // -1 == no tangents required + int mTexStageRequiringTangents; + }; + std::vector mRequirements; + + std::string mDefaultVsTemplate; + std::string mDefaultFsTemplate; + + void createProgram(const ShaderRequirements& reqs, osg::StateSet* stateset); + }; + +} + +#endif diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index fc4706c1f..edb5b51f2 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -6,6 +6,8 @@ set(SHADER_FILES water_vertex.glsl water_fragment.glsl water_nm.png + objects_vertex.glsl + objects_fragment.glsl ) copy_all_files(${CMAKE_CURRENT_SOURCE_DIR} ${DDIR} "${SHADER_FILES}") diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl new file mode 100644 index 000000000..9b42c30a7 --- /dev/null +++ b/files/shaders/objects_fragment.glsl @@ -0,0 +1,15 @@ +#version 120 + +#if @diffuseMap +uniform sampler2D diffuseMap; +varying vec2 diffuseMapUV; +#endif + +void main() +{ +#if @diffuseMap + gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV); +#else + gl_FragData[0] = vec4(1,1,1,1); +#endif +} diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl new file mode 100644 index 000000000..17e8f3fc4 --- /dev/null +++ b/files/shaders/objects_vertex.glsl @@ -0,0 +1,14 @@ +#version 120 + +#if @diffuseMap +varying vec2 diffuseMapUV; +#endif + +void main(void) +{ + gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; + +#if @diffuseMap + diffuseMapUV = gl_MultiTexCoord@diffuseMapUV.xy; +#endif +}