ShaderSource and silly example shaders
authorKahrl <kahrl@gmx.net>
Mon, 19 Mar 2012 01:59:12 +0000 (02:59 +0100)
committerPerttu Ahola <celeron55@gmail.com>
Sat, 1 Dec 2012 22:46:18 +0000 (00:46 +0200)
15 files changed:
client/shaders/the_darkness_of_light/opengl_fragment.asm [new file with mode: 0644]
client/shaders/the_darkness_of_light/opengl_fragment.glsl [new file with mode: 0644]
client/shaders/the_darkness_of_light/opengl_vertex.asm [new file with mode: 0644]
client/shaders/the_darkness_of_light/opengl_vertex.glsl [new file with mode: 0644]
src/CMakeLists.txt
src/client.cpp
src/client.h
src/defaultsettings.cpp
src/game.cpp
src/gamedef.h
src/mapblock_mesh.cpp
src/server.cpp
src/server.h
src/shader.cpp [new file with mode: 0644]
src/shader.h [new file with mode: 0644]

diff --git a/client/shaders/the_darkness_of_light/opengl_fragment.asm b/client/shaders/the_darkness_of_light/opengl_fragment.asm
new file mode 100644 (file)
index 0000000..8297f8e
--- /dev/null
@@ -0,0 +1,17 @@
+!!ARBfp1.0\r
+\r
+#Input\r
+ATTRIB inTexCoord = fragment.texcoord;      # texture coordinates\r
+ATTRIB inColor = fragment.color.primary; # interpolated diffuse color\r
+\r
+#Output\r
+OUTPUT outColor = result.color;\r
+\r
+TEMP texelColor;\r
+TXP texelColor, inTexCoord, texture, 2D; \r
+MUL texelColor, texelColor, inColor;  # multiply with color   \r
+SUB outColor, {1.0,1.0,1.0,1.0}, texelColor;\r
+MOV outColor.w, 1.0;\r
+\r
+END\r
+\r
diff --git a/client/shaders/the_darkness_of_light/opengl_fragment.glsl b/client/shaders/the_darkness_of_light/opengl_fragment.glsl
new file mode 100644 (file)
index 0000000..e447918
--- /dev/null
@@ -0,0 +1,9 @@
+\r
+uniform sampler2D myTexture;\r
+\r
+void main (void)\r
+{\r
+    vec4 col = texture2D(myTexture, vec2(gl_TexCoord[0]));\r
+    col *= gl_Color;\r
+    gl_FragColor = vec4(1.0-col.r, 1.0-col.g, 1.0-col.b, 1.0);\r
+}\r
diff --git a/client/shaders/the_darkness_of_light/opengl_vertex.asm b/client/shaders/the_darkness_of_light/opengl_vertex.asm
new file mode 100644 (file)
index 0000000..adfee13
--- /dev/null
@@ -0,0 +1,38 @@
+!!ARBvp1.0\r
+\r
+#input\r
+ATTRIB InPos = vertex.position;\r
+ATTRIB InColor = vertex.color;\r
+ATTRIB InNormal = vertex.normal;\r
+ATTRIB InTexCoord = vertex.texcoord;\r
+\r
+#output\r
+OUTPUT OutPos = result.position;\r
+OUTPUT OutColor = result.color;\r
+OUTPUT OutTexCoord = result.texcoord;\r
+\r
+PARAM MVP[4] = { state.matrix.mvp }; # modelViewProjection matrix.\r
+TEMP Temp;\r
+TEMP TempColor;\r
+TEMP TempCompare;\r
+\r
+#transform position to clip space \r
+DP4 Temp.x, MVP[0], InPos;\r
+DP4 Temp.y, MVP[1], InPos;\r
+DP4 Temp.z, MVP[2], InPos;\r
+DP4 Temp.w, MVP[3], InPos;\r
+\r
+# check if normal.y > 0.5\r
+SLT TempCompare, InNormal, {0.5,0.5,0.5,0.5};\r
+MUL TempCompare.z, TempCompare.y, 0.5;\r
+SUB TempCompare.x, 1.0, TempCompare.z;\r
+MOV TempCompare.y, TempCompare.x;\r
+MOV TempCompare.z, TempCompare.x;\r
+\r
+# calculate light color\r
+MUL OutColor, InColor, TempCompare;\r
+MOV OutColor.w, 1.0;          # we want alpha to be always 1\r
+MOV OutTexCoord, InTexCoord; # store texture coordinate\r
+MOV OutPos, Temp;\r
+\r
+END\r
diff --git a/client/shaders/the_darkness_of_light/opengl_vertex.glsl b/client/shaders/the_darkness_of_light/opengl_vertex.glsl
new file mode 100644 (file)
index 0000000..0182c85
--- /dev/null
@@ -0,0 +1,16 @@
+\r
+uniform mat4 mWorldViewProj;\r
+uniform mat4 mInvWorld;\r
+uniform mat4 mTransWorld;\r
+\r
+void main(void)\r
+{\r
+       gl_Position = mWorldViewProj * gl_Vertex;\r
+       \r
+       if(gl_Normal.y > 0.5)\r
+               gl_FrontColor = gl_BackColor = gl_Color;\r
+       else\r
+               gl_FrontColor = gl_BackColor = gl_Color * 0.5;\r
+\r
+       gl_TexCoord[0] = gl_MultiTexCoord0;\r
+}\r
index e1639b46fefab5f4ea123222e3dd86325b63d0ed..3830ef3b66636a862690bbe0a7eb836b83ee8b81 100644 (file)
@@ -262,6 +262,7 @@ set(minetest_SRCS
        client.cpp
        filecache.cpp
        tile.cpp
+       shader.cpp
        game.cpp
        main.cpp
 )
index c0c513fefa81c52dc52cdeeb2fa307dc7e40f374..865cf71ee449f8975e2c6f21b6c8323cc8919743 100644 (file)
@@ -33,6 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "nodemetadata.h"
 #include "nodedef.h"
 #include "itemdef.h"
+#include "shader.h"
 #include <IFileSystem.h>
 #include "sha1.h"
 #include "base64.h"
@@ -228,12 +229,14 @@ Client::Client(
                std::string password,
                MapDrawControl &control,
                IWritableTextureSource *tsrc,
+               IWritableShaderSource *shsrc,
                IWritableItemDefManager *itemdef,
                IWritableNodeDefManager *nodedef,
                ISoundManager *sound,
                MtEventManager *event
 ):
        m_tsrc(tsrc),
+       m_shsrc(shsrc),
        m_itemdef(itemdef),
        m_nodedef(nodedef),
        m_sound(sound),
@@ -2456,6 +2459,9 @@ void Client::afterContentReceived()
        if(g_settings->getBool("enable_texture_atlas"))
                m_tsrc->buildMainAtlas(this);
 
+       // Rebuild shaders
+       m_shsrc->rebuildShaders();
+
        // Update node aliases
        infostream<<"- Updating node aliases"<<std::endl;
        m_nodedef->updateAliases(m_itemdef);
@@ -2512,6 +2518,10 @@ ITextureSource* Client::getTextureSource()
 {
        return m_tsrc;
 }
+IShaderSource* Client::getShaderSource()
+{
+       return m_shsrc;
+}
 u16 Client::allocateUnknownNodeId(const std::string &name)
 {
        errorstream<<"Client::allocateUnknownNodeId(): "
index b4b7af7c3347d74ad95e16481e711ba1b6983870..f85e8ac7be643a5af87b0f7d049780e347675022 100644 (file)
@@ -39,6 +39,7 @@ struct MeshMakeData;
 class MapBlockMesh;
 class IGameDef;
 class IWritableTextureSource;
+class IWritableShaderSource;
 class IWritableItemDefManager;
 class IWritableNodeDefManager;
 //class IWritableCraftDefManager;
@@ -174,6 +175,7 @@ public:
                        std::string password,
                        MapDrawControl &control,
                        IWritableTextureSource *tsrc,
+                       IWritableShaderSource *shsrc,
                        IWritableItemDefManager *itemdef,
                        IWritableNodeDefManager *nodedef,
                        ISoundManager *sound,
@@ -305,6 +307,7 @@ public:
        virtual INodeDefManager* getNodeDefManager();
        virtual ICraftDefManager* getCraftDefManager();
        virtual ITextureSource* getTextureSource();
+       virtual IShaderSource* getShaderSource();
        virtual u16 allocateUnknownNodeId(const std::string &name);
        virtual ISoundManager* getSoundManager();
        virtual MtEventManager* getEventManager();
@@ -337,6 +340,7 @@ private:
        IntervalLimiter m_map_timer_and_unload_interval;
 
        IWritableTextureSource *m_tsrc;
+       IWritableShaderSource *m_shsrc;
        IWritableItemDefManager *m_itemdef;
        IWritableNodeDefManager *m_nodedef;
        ISoundManager *m_sound;
index 4fde4b5b330fb9005eef1a1e8215a875fcdb7572..3145a90e5d8031d1095dbf1105ffc8d8b444afa8 100644 (file)
@@ -92,6 +92,7 @@ void set_default_settings(Settings *settings)
        settings->setDefault("smooth_lighting", "true");
        settings->setDefault("enable_texture_atlas", "true");
        settings->setDefault("texture_path", "");
+       settings->setDefault("shader_path", "");
        settings->setDefault("video_driver", "opengl");
        settings->setDefault("free_move", "false");
        settings->setDefault("continuous_forward", "false");
@@ -103,6 +104,7 @@ void set_default_settings(Settings *settings)
        settings->setDefault("view_bobbing_amount", "1.0");
        settings->setDefault("enable_3d_clouds", "true");
        settings->setDefault("opaque_water", "false");
+       settings->setDefault("enable_shaders", "2");
        settings->setDefault("console_color", "(0,0,0)");
        settings->setDefault("console_alpha", "200");
        settings->setDefault("enable_sound", "true");
index a38ffa13cac3270f69d3ee6695f99236634d243a..1339afbf0a7d594c461ef78d942ffbe50621c501 100644 (file)
@@ -50,6 +50,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "main.h" // For g_settings
 #include "itemdef.h"
 #include "tile.h" // For TextureSource
+#include "shader.h" // For ShaderSource
 #include "logoutputbuffer.h"
 #include "subgame.h"
 #include "quicktune_shortcutter.h"
@@ -876,6 +877,9 @@ void the_game(
        // Create texture source
        IWritableTextureSource *tsrc = createTextureSource(device);
        
+       // Create shader source
+       IWritableShaderSource *shsrc = createShaderSource(device);
+       
        // These will be filled by data received from the server
        // Create item definition manager
        IWritableItemDefManager *itemdef = createItemDefManager();
@@ -943,7 +947,7 @@ void the_game(
        MapDrawControl draw_control;
 
        Client client(device, playername.c_str(), password, draw_control,
-                       tsrc, itemdef, nodedef, sound, &eventmgr);
+                       tsrc, shsrc, itemdef, nodedef, sound, &eventmgr);
        
        // Client acts as our GameDef
        IGameDef *gamedef = &client;
@@ -1422,6 +1426,11 @@ void the_game(
                /* Process ItemDefManager's queue */
                itemdef->processQueue(gamedef);
 
+               /*
+                       Process ShaderSource's queue
+               */
+               shsrc->processQueue();
+
                /*
                        Random calculations
                */
@@ -3002,9 +3011,11 @@ void the_game(
        
        if(!sound_is_dummy)
                delete sound;
+
+       delete tsrc;
+       delete shsrc;
        delete nodedef;
        delete itemdef;
-       delete tsrc;
 }
 
 
index 87918d726c4fa7ca05547a6f4e4582a3cfe5aee6..6fc99b9f10302ac7bf86e8838aa94c8636f7b838 100644 (file)
@@ -28,6 +28,7 @@ class INodeDefManager;
 class ICraftDefManager;
 class ITextureSource;
 class ISoundManager;
+class IShaderSource;
 class MtEventManager;
 class IRollbackReportSink;
 
@@ -48,6 +49,8 @@ public:
        // This is always thread-safe, but referencing the irrlicht texture
        // pointers in other threads than main thread will make things explode.
        virtual ITextureSource* getTextureSource()=0;
+
+       virtual IShaderSource* getShaderSource()=0;
        
        // Used for keeping track of names/ids of unknown nodes
        virtual u16 allocateUnknownNodeId(const std::string &name)=0;
@@ -70,6 +73,7 @@ public:
        ICraftDefManager* cdef(){return getCraftDefManager();}
        ITextureSource* tsrc(){return getTextureSource();}
        ISoundManager* sound(){return getSoundManager();}
+       IShaderSource* shsrc(){return getShaderSource();}
        MtEventManager* event(){return getEventManager();}
        IRollbackReportSink* rollback(){return getRollbackReportSink();}
 };
index fdeb31f4da5abc642f65816fd046dbfea82a6f3e..cbc38ddb5114ada20836ab6612c8a64ba24a8973 100644 (file)
@@ -28,6 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "mesh.h"
 #include "content_mapblock.h"
 #include "noise.h"
+#include "shader.h"
 #include "settings.h"
 #include "util/directiontables.h"
 
@@ -1011,6 +1012,8 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data):
                Convert MeshCollector to SMesh
                Also store animation info
        */
+       video::E_MATERIAL_TYPE shadermat = m_gamedef->getShaderSource()->
+                       getShader("the_darkness_of_light").material;
        for(u32 i = 0; i < collector.prebuffers.size(); i++)
        {
                PreMeshBuffer &p = collector.prebuffers[i];
@@ -1077,6 +1080,9 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data):
                material.setTexture(0, p.tile.texture.atlas);
                p.tile.applyMaterialOptions(material);
 
+               //if(material.MaterialType == video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF)
+                       material.MaterialType = shadermat;
+
                // Create meshbuffer
 
                // This is a "Standard MeshBuffer",
index a793c6e2a74f1839198b63425871cfb6fea77ea6..2449f423659f21418b2664e76d7aa0485bc4303a 100644 (file)
@@ -4688,6 +4688,10 @@ ITextureSource* Server::getTextureSource()
 {
        return NULL;
 }
+IShaderSource* Server::getShaderSource()
+{
+       return NULL;
+}
 u16 Server::allocateUnknownNodeId(const std::string &name)
 {
        return m_nodedef->allocateDummy(name);
index f770fa3d44ba170e859f7a7b0690bcfe3903b6d9..ce826ae529a62dee58cb2a40467a99d85ccc0ece 100644 (file)
@@ -560,6 +560,7 @@ public:
        virtual INodeDefManager* getNodeDefManager();
        virtual ICraftDefManager* getCraftDefManager();
        virtual ITextureSource* getTextureSource();
+       virtual IShaderSource* getShaderSource();
        virtual u16 allocateUnknownNodeId(const std::string &name);
        virtual ISoundManager* getSoundManager();
        virtual MtEventManager* getEventManager();
diff --git a/src/shader.cpp b/src/shader.cpp
new file mode 100644 (file)
index 0000000..ba0b860
--- /dev/null
@@ -0,0 +1,731 @@
+/*
+Minetest-c55
+Copyright (C) 2012 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2012 Kahrl <kahrl@gmx.net>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "shader.h"
+#include "irrlichttypes_extrabloated.h"
+#include "debug.h"
+#include "main.h" // for g_settings
+#include "filesys.h"
+#include "util/container.h"
+#include "util/thread.h"
+#include "settings.h"
+#include <iterator>
+#include <ICameraSceneNode.h>
+#include <IGPUProgrammingServices.h>
+#include <IMaterialRenderer.h>
+#include <IMaterialRendererServices.h>
+#include <IShaderConstantSetCallBack.h>
+#include "EShaderTypes.h"
+#include "log.h"
+#include "gamedef.h"
+
+/*
+       A cache from shader name to shader path
+*/
+MutexedMap<std::string, std::string> g_shadername_to_path_cache;
+
+/*
+       Gets the path to a shader by first checking if the file
+         name_of_shader/filename
+       exists in shader_path and if not, using the data path.
+
+       If not found, returns "".
+
+       Utilizes a thread-safe cache.
+*/
+std::string getShaderPath(const std::string &name_of_shader,
+               const std::string &filename)
+{
+       std::string combined = name_of_shader + DIR_DELIM + filename;
+       std::string fullpath = "";
+       /*
+               Check from cache
+       */
+       bool incache = g_shadername_to_path_cache.get(combined, &fullpath);
+       if(incache)
+               return fullpath;
+
+       /*
+               Check from shader_path
+       */
+       std::string shader_path = g_settings->get("shader_path");
+       if(shader_path != "")
+       {
+               std::string testpath = shader_path + DIR_DELIM + combined;
+               if(fs::PathExists(testpath))
+                       fullpath = testpath;
+       }
+
+       /*
+               Check from default data directory
+       */
+       if(fullpath == "")
+       {
+               std::string rel_path = std::string("client") + DIR_DELIM
+                               + "shaders" + DIR_DELIM
+                               + name_of_shader + DIR_DELIM
+                               + filename;
+               std::string testpath = porting::path_share + DIR_DELIM + rel_path;
+               if(fs::PathExists(testpath))
+                       fullpath = testpath;
+       }
+
+       // Add to cache (also an empty result is cached)
+       g_shadername_to_path_cache.set(combined, fullpath);
+
+       // Finally return it
+       return fullpath;
+}
+
+/*
+       SourceShaderCache: A cache used for storing source shaders.
+*/
+
+class SourceShaderCache
+{
+public:
+       void insert(const std::string &name_of_shader,
+                       const std::string &filename,
+                       const std::string &program,
+                       bool prefer_local)
+       {
+               std::string combined = name_of_shader + DIR_DELIM + filename;
+               // Try to use local shader instead if asked to
+               if(prefer_local){
+                       std::string path = getShaderPath(name_of_shader, filename);
+                       if(path != ""){
+                               std::string p = readFile(path);
+                               if(p != ""){
+                                       m_programs[combined] = p;
+                                       return;
+                               }
+                       }
+               }
+               m_programs[combined] = program;
+       }
+       std::string get(const std::string &name_of_shader,
+                       const std::string &filename)
+       {
+               std::string combined = name_of_shader + DIR_DELIM + filename;
+               core::map<std::string, std::string>::Node *n;
+               n = m_programs.find(combined);
+               if(n)
+                       return n->getValue();
+               return "";
+       }
+       // Primarily fetches from cache, secondarily tries to read from filesystem
+       std::string getOrLoad(const std::string &name_of_shader,
+                       const std::string &filename)
+       {
+               std::string combined = name_of_shader + DIR_DELIM + filename;
+               core::map<std::string, std::string>::Node *n;
+               n = m_programs.find(combined);
+               if(n)
+                       return n->getValue();
+               std::string path = getShaderPath(name_of_shader, filename);
+               if(path == ""){
+                       infostream<<"SourceShaderCache::getOrLoad(): No path found for \""
+                                       <<combined<<"\""<<std::endl;
+                       return "";
+               }
+               infostream<<"SourceShaderCache::getOrLoad(): Loading path \""<<path
+                               <<"\""<<std::endl;
+               std::string p = readFile(path);
+               if(p != ""){
+                       m_programs[combined] = p;
+                       return p;
+               }
+               return "";
+       }
+private:
+       core::map<std::string, std::string> m_programs;
+       std::string readFile(const std::string &path)
+       {
+               std::ifstream is(path.c_str(), std::ios::binary);
+               if(!is.is_open())
+                       return "";
+               std::ostringstream tmp_os;
+               tmp_os << is.rdbuf();
+               return tmp_os.str();
+       }
+};
+
+/*
+       ShaderCallback: Sets constants that can be used in shaders
+*/
+
+class ShaderCallback : public video::IShaderConstantSetCallBack
+{
+public:
+       ShaderCallback(IrrlichtDevice *device): m_device(device) {}
+       ~ShaderCallback() {}
+
+       virtual void OnSetConstants(video::IMaterialRendererServices *services, s32 userData)
+       {
+               video::IVideoDriver *driver = services->getVideoDriver();
+               assert(driver);
+
+               bool is_highlevel = userData;
+
+               // set inverted world matrix
+               core::matrix4 invWorld = driver->getTransform(video::ETS_WORLD);
+               invWorld.makeInverse();
+               if(is_highlevel)
+                       services->setVertexShaderConstant("mInvWorld", invWorld.pointer(), 16);
+               else
+                       services->setVertexShaderConstant(invWorld.pointer(), 0, 4);
+
+               // set clip matrix
+               core::matrix4 worldViewProj;
+               worldViewProj = driver->getTransform(video::ETS_PROJECTION);
+               worldViewProj *= driver->getTransform(video::ETS_VIEW);
+               worldViewProj *= driver->getTransform(video::ETS_WORLD);
+               if(is_highlevel)
+                       services->setVertexShaderConstant("mWorldViewProj", worldViewProj.pointer(), 16);
+               else
+                       services->setVertexShaderConstant(worldViewProj.pointer(), 4, 4);
+
+               // set transposed world matrix
+               core::matrix4 world = driver->getTransform(video::ETS_WORLD);
+               world = world.getTransposed();
+               if(is_highlevel)
+                       services->setVertexShaderConstant("mTransWorld", world.pointer(), 16);
+               else
+                       services->setVertexShaderConstant(world.pointer(), 8, 4);
+       }
+
+private:
+       IrrlichtDevice *m_device;
+};
+
+/*
+       ShaderSource
+*/
+
+class ShaderSource : public IWritableShaderSource
+{
+public:
+       ShaderSource(IrrlichtDevice *device);
+       ~ShaderSource();
+
+       /*
+               Gets a shader material id from cache or
+               - if main thread, from getShaderIdDirect
+               - if other thread, adds to request queue and waits for main thread
+       */
+       u32 getShaderId(const std::string &name);
+
+       /*
+               - If shader material specified by name is found from cache,
+                 return the cached id.
+               - Otherwise generate the shader material, add to cache and return id.
+
+               The id 0 points to a null shader. Its material is EMT_SOLID.
+       */
+       u32 getShaderIdDirect(const std::string &name);
+
+       // Finds out the name of a cached shader.
+       std::string getShaderName(u32 id);
+
+       /*
+               If shader specified by the name pointed by the id doesn't
+               exist, create it, then return the cached shader.
+
+               Can be called from any thread. If called from some other thread
+               and not found in cache, the call is queued to the main thread
+               for processing.
+       */
+       ShaderInfo getShader(u32 id);
+
+       ShaderInfo getShader(const std::string &name)
+       {
+               return getShader(getShaderId(name));
+       }
+
+       // Processes queued shader requests from other threads.
+       // Shall be called from the main thread.
+       void processQueue();
+
+       // Insert a shader program into the cache without touching the
+       // filesystem. Shall be called from the main thread.
+       void insertSourceShader(const std::string &name_of_shader,
+               const std::string &filename, const std::string &program);
+
+       // Rebuild shaders from the current set of source shaders
+       // Shall be called from the main thread.
+       void rebuildShaders();
+
+private:
+
+       // The id of the thread that is allowed to use irrlicht directly
+       threadid_t m_main_thread;
+       // The irrlicht device
+       IrrlichtDevice *m_device;
+       // The set-constants callback
+       ShaderCallback *m_shader_callback;
+
+       // Cache of source shaders
+       // This should be only accessed from the main thread
+       SourceShaderCache m_sourcecache;
+
+       // A shader id is index in this array.
+       // The first position contains a dummy shader.
+       core::array<ShaderInfo> m_shaderinfo_cache;
+       // Maps a shader name to an index in the former.
+       core::map<std::string, u32> m_name_to_id;
+       // The two former containers are behind this mutex
+       JMutex m_shaderinfo_cache_mutex;
+
+       // Queued shader fetches (to be processed by the main thread)
+       RequestQueue<std::string, u32, u8, u8> m_get_shader_queue;
+};
+
+IWritableShaderSource* createShaderSource(IrrlichtDevice *device)
+{
+       return new ShaderSource(device);
+}
+
+/*
+       Generate shader given the shader name.
+*/
+ShaderInfo generate_shader(std::string name, IrrlichtDevice *device,
+               video::IShaderConstantSetCallBack *callback,
+               SourceShaderCache *sourcecache);
+
+/*
+       Load shader programs
+*/
+void load_shaders(std::string name, SourceShaderCache *sourcecache,
+               video::E_DRIVER_TYPE drivertype, s32 enable_shaders,
+               std::string &vertex_program, std::string &pixel_program,
+               std::string &geometry_program, bool &is_highlevel);
+
+ShaderSource::ShaderSource(IrrlichtDevice *device):
+               m_device(device)
+{
+       assert(m_device);
+
+       m_shader_callback = new ShaderCallback(device);
+
+       m_shaderinfo_cache_mutex.Init();
+
+       m_main_thread = get_current_thread_id();
+
+       // Add a dummy ShaderInfo as the first index, named ""
+       m_shaderinfo_cache.push_back(ShaderInfo());
+       m_name_to_id[""] = 0;
+}
+
+ShaderSource::~ShaderSource()
+{
+       //m_shader_callback->drop();
+}
+
+u32 ShaderSource::getShaderId(const std::string &name)
+{
+       //infostream<<"getShaderId(): \""<<name<<"\""<<std::endl;
+
+       {
+               /*
+                       See if shader already exists
+               */
+               JMutexAutoLock lock(m_shaderinfo_cache_mutex);
+               core::map<std::string, u32>::Node *n;
+               n = m_name_to_id.find(name);
+               if(n != NULL)
+                       return n->getValue();
+       }
+
+       /*
+               Get shader
+       */
+       if(get_current_thread_id() == m_main_thread){
+               return getShaderIdDirect(name);
+       } else {
+               infostream<<"getShaderId(): Queued: name=\""<<name<<"\""<<std::endl;
+
+               // We're gonna ask the result to be put into here
+               ResultQueue<std::string, u32, u8, u8> result_queue;
+
+               // Throw a request in
+               m_get_shader_queue.add(name, 0, 0, &result_queue);
+
+               infostream<<"Waiting for shader from main thread, name=\""
+                               <<name<<"\""<<std::endl;
+
+               try{
+                       // Wait result for a second
+                       GetResult<std::string, u32, u8, u8>
+                                       result = result_queue.pop_front(1000);
+
+                       // Check that at least something worked OK
+                       assert(result.key == name);
+
+                       return result.item;
+               }
+               catch(ItemNotFoundException &e){
+                       infostream<<"Waiting for shader timed out."<<std::endl;
+                       return 0;
+               }
+       }
+
+       infostream<<"getShaderId(): Failed"<<std::endl;
+
+       return 0;
+}
+
+/*
+       This method generates all the shaders
+*/
+u32 ShaderSource::getShaderIdDirect(const std::string &name)
+{
+       //infostream<<"getShaderIdDirect(): name=\""<<name<<"\""<<std::endl;
+
+       // Empty name means shader 0
+       if(name == ""){
+               infostream<<"getShaderIdDirect(): name is empty"<<std::endl;
+               return 0;
+       }
+
+       /*
+               Calling only allowed from main thread
+       */
+       if(get_current_thread_id() != m_main_thread){
+               errorstream<<"ShaderSource::getShaderIdDirect() "
+                               "called not from main thread"<<std::endl;
+               return 0;
+       }
+
+       /*
+               See if shader already exists
+       */
+       {
+               JMutexAutoLock lock(m_shaderinfo_cache_mutex);
+
+               core::map<std::string, u32>::Node *n;
+               n = m_name_to_id.find(name);
+               if(n != NULL){
+                       /*infostream<<"getShaderIdDirect(): \""<<name
+                                       <<"\" found in cache"<<std::endl;*/
+                       return n->getValue();
+               }
+       }
+
+       /*infostream<<"getShaderIdDirect(): \""<<name
+                       <<"\" NOT found in cache. Creating it."<<std::endl;*/
+
+       ShaderInfo info = generate_shader(name, m_device,
+                       m_shader_callback, &m_sourcecache);
+
+       /*
+               Add shader to caches (add dummy shaders too)
+       */
+
+       JMutexAutoLock lock(m_shaderinfo_cache_mutex);
+
+       u32 id = m_shaderinfo_cache.size();
+       m_shaderinfo_cache.push_back(info);
+       m_name_to_id.insert(name, id);
+
+       /*infostream<<"getShaderIdDirect(): "
+                       <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;*/
+
+       return id;
+}
+
+std::string ShaderSource::getShaderName(u32 id)
+{
+       JMutexAutoLock lock(m_shaderinfo_cache_mutex);
+
+       if(id >= m_shaderinfo_cache.size()){
+               errorstream<<"ShaderSource::getShaderName(): id="<<id
+                               <<" >= m_shaderinfo_cache.size()="
+                               <<m_shaderinfo_cache.size()<<std::endl;
+               return "";
+       }
+
+       return m_shaderinfo_cache[id].name;
+}
+
+ShaderInfo ShaderSource::getShader(u32 id)
+{
+       JMutexAutoLock lock(m_shaderinfo_cache_mutex);
+
+       if(id >= m_shaderinfo_cache.size())
+               return ShaderInfo();
+
+       return m_shaderinfo_cache[id];
+}
+
+void ShaderSource::processQueue()
+{
+       /*
+               Fetch shaders
+       */
+       if(m_get_shader_queue.size() > 0){
+               GetRequest<std::string, u32, u8, u8>
+                               request = m_get_shader_queue.pop();
+
+               /*infostream<<"ShaderSource::processQueue(): "
+                               <<"got shader request with "
+                               <<"name=\""<<request.key<<"\""
+                               <<std::endl;*/
+
+               GetResult<std::string, u32, u8, u8>
+                               result;
+               result.key = request.key;
+               result.callers = request.callers;
+               result.item = getShaderIdDirect(request.key);
+
+               request.dest->push_back(result);
+       }
+}
+
+void ShaderSource::insertSourceShader(const std::string &name_of_shader,
+               const std::string &filename, const std::string &program)
+{
+       /*infostream<<"ShaderSource::insertSourceShader(): "
+                       "name_of_shader=\""<<name_of_shader<<"\", "
+                       "filename=\""<<filename<<"\""<<std::endl;*/
+
+       assert(get_current_thread_id() == m_main_thread);
+
+       m_sourcecache.insert(name_of_shader, filename, program, true);
+}
+
+void ShaderSource::rebuildShaders()
+{
+       JMutexAutoLock lock(m_shaderinfo_cache_mutex);
+
+       /*// Oh well... just clear everything, they'll load sometime.
+       m_shaderinfo_cache.clear();
+       m_name_to_id.clear();*/
+
+       /*
+               FIXME: Old shader materials can't be deleted in Irrlicht,
+               or can they?
+               (This would be nice to do in the destructor too)
+       */
+
+       // Recreate shaders
+       for(u32 i=0; i<m_shaderinfo_cache.size(); i++){
+               ShaderInfo *info = &m_shaderinfo_cache[i];
+               *info = generate_shader(info->name, m_device,
+                               m_shader_callback, &m_sourcecache);
+       }
+}
+ShaderInfo generate_shader(std::string name, IrrlichtDevice *device,
+               video::IShaderConstantSetCallBack *callback,
+               SourceShaderCache *sourcecache)
+{
+       /*infostream<<"generate_shader(): "
+                       "\""<<name<<"\""<<std::endl;*/
+
+       ShaderInfo shaderinfo;
+       shaderinfo.name = name;
+       shaderinfo.material = video::EMT_SOLID;
+
+       /*
+               Get the base material
+       */
+       std::string base_material_name = sourcecache->getOrLoad(name, "base.txt");
+       for(s32 i = 0; video::sBuiltInMaterialTypeNames[i] != 0; i++){
+               if(video::sBuiltInMaterialTypeNames[i] == base_material_name){
+                       shaderinfo.material = (video::E_MATERIAL_TYPE) i;
+                       break;
+               }
+       }
+
+       // 0 = off, 1 = assembly shaders only, 2 = highlevel or assembly
+       s32 enable_shaders = g_settings->getS32("enable_shaders");
+       if(enable_shaders <= 0)
+               return shaderinfo;
+
+       video::IVideoDriver* driver = device->getVideoDriver();
+       assert(driver);
+
+       video::IGPUProgrammingServices *gpu = driver->getGPUProgrammingServices();
+       if(!gpu){
+               errorstream<<"generate_shader(): "
+                               "failed to generate \""<<name<<"\", "
+                               "GPU programming not supported."
+                               <<std::endl;
+               return shaderinfo;
+       }
+
+       // Choose shader language depending on driver type and settings
+       // Then load shaders
+       std::string vertex_program;
+       std::string pixel_program;
+       std::string geometry_program;
+       bool is_highlevel;
+       load_shaders(name, sourcecache, driver->getDriverType(),
+                       enable_shaders, vertex_program, pixel_program,
+                       geometry_program, is_highlevel);
+
+       // Check hardware/driver support
+       if(vertex_program != "" &&
+                       !driver->queryFeature(video::EVDF_VERTEX_SHADER_1_1) &&
+                       !driver->queryFeature(video::EVDF_ARB_VERTEX_PROGRAM_1)){
+               infostream<<"generate_shader(): vertex shaders disabled "
+                               "because of missing driver/hardware support."
+                               <<std::endl;
+               vertex_program = "";
+       }
+       if(pixel_program != "" &&
+                       !driver->queryFeature(video::EVDF_PIXEL_SHADER_1_1) &&
+                       !driver->queryFeature(video::EVDF_ARB_FRAGMENT_PROGRAM_1)){
+               infostream<<"generate_shader(): pixel shaders disabled "
+                               "because of missing driver/hardware support."
+                               <<std::endl;
+               pixel_program = "";
+       }
+       if(geometry_program != "" &&
+                       !driver->queryFeature(video::EVDF_GEOMETRY_SHADER)){
+               infostream<<"generate_shader(): geometry shaders disabled "
+                               "because of missing driver/hardware support."
+                               <<std::endl;
+               geometry_program = "";
+       }
+
+       // If no shaders are used, don't make a separate material type
+       if(vertex_program == "" && pixel_program == "" && geometry_program == "")
+               return shaderinfo;
+
+       // Call addHighLevelShaderMaterial() or addShaderMaterial()
+       const c8* vertex_program_ptr = 0;
+       const c8* pixel_program_ptr = 0;
+       const c8* geometry_program_ptr = 0;
+       if(vertex_program != "")
+               vertex_program_ptr = vertex_program.c_str();
+       if(pixel_program != "")
+               pixel_program_ptr = pixel_program.c_str();
+       if(geometry_program != "")
+               geometry_program_ptr = geometry_program.c_str();
+       s32 shadermat = -1;
+       if(is_highlevel){
+               infostream<<"Compiling high level shaders for "<<name<<std::endl;
+               shadermat = gpu->addHighLevelShaderMaterial(
+                       vertex_program_ptr,   // Vertex shader program
+                       "vertexMain",         // Vertex shader entry point
+                       video::EVST_VS_1_1,   // Vertex shader version
+                       pixel_program_ptr,    // Pixel shader program
+                       "pixelMain",          // Pixel shader entry point
+                       video::EPST_PS_1_1,   // Pixel shader version
+                       geometry_program_ptr, // Geometry shader program
+                       "geometryMain",       // Geometry shader entry point
+                       video::EGST_GS_4_0,   // Geometry shader version
+                       scene::EPT_TRIANGLES,      // Geometry shader input
+                       scene::EPT_TRIANGLE_STRIP, // Geometry shader output
+                       0,                         // Support maximum number of vertices
+                       callback,             // Set-constant callback
+                       shaderinfo.material,  // Base material
+                       1                     // Userdata passed to callback
+                       );
+
+               if(shadermat == -1){
+                       errorstream<<"generate_shader(): "
+                                       "failed to generate \""<<name<<"\", "
+                                       "addHighLevelShaderMaterial failed."
+                                       <<std::endl;
+                       return shaderinfo;
+               }
+       }
+       else{
+               infostream<<"Compiling assembly shaders for "<<name<<std::endl;
+               shadermat = gpu->addShaderMaterial(
+                       vertex_program_ptr,   // Vertex shader program
+                       pixel_program_ptr,    // Pixel shader program
+                       callback,             // Set-constant callback
+                       shaderinfo.material,  // Base material
+                       0                     // Userdata passed to callback
+                       );
+
+               if(shadermat == -1){
+                       errorstream<<"generate_shader(): "
+                                       "failed to generate \""<<name<<"\", "
+                                       "addShaderMaterial failed."
+                                       <<std::endl;
+                       return shaderinfo;
+               }
+       }
+
+       // HACK, TODO: investigate this better
+       // Grab the material renderer once more so minetest doesn't crash on exit
+       driver->getMaterialRenderer(shadermat)->grab();
+
+       // Apply the newly created material type
+       shaderinfo.material = (video::E_MATERIAL_TYPE) shadermat;
+       return shaderinfo;
+}
+
+void load_shaders(std::string name, SourceShaderCache *sourcecache,
+               video::E_DRIVER_TYPE drivertype, s32 enable_shaders,
+               std::string &vertex_program, std::string &pixel_program,
+               std::string &geometry_program, bool &is_highlevel)
+{
+       vertex_program = "";
+       pixel_program = "";
+       geometry_program = "";
+       is_highlevel = false;
+
+       if(enable_shaders >= 2){
+               // Look for high level shaders
+               if(drivertype == video::EDT_DIRECT3D9){
+                       // Direct3D 9: HLSL
+                       // (All shaders in one file)
+                       vertex_program = sourcecache->getOrLoad(name, "d3d9.hlsl");
+                       pixel_program = vertex_program;
+                       geometry_program = vertex_program;
+               }
+               else if(drivertype == video::EDT_OPENGL){
+                       // OpenGL: GLSL
+                       vertex_program = sourcecache->getOrLoad(name, "opengl_vertex.glsl");
+                       pixel_program = sourcecache->getOrLoad(name, "opengl_fragment.glsl");
+                       geometry_program = sourcecache->getOrLoad(name, "opengl_geometry.glsl");
+               }
+               if(vertex_program != "" || pixel_program != "" || geometry_program != ""){
+                       is_highlevel = true;
+                       return;
+               }
+       }
+
+       if(enable_shaders >= 1){
+               // Look for assembly shaders
+               if(drivertype == video::EDT_DIRECT3D8){
+                       // Direct3D 8 assembly shaders
+                       vertex_program = sourcecache->getOrLoad(name, "d3d8_vertex.asm");
+                       pixel_program = sourcecache->getOrLoad(name, "d3d8_pixel.asm");
+               }
+               else if(drivertype == video::EDT_DIRECT3D9){
+                       // Direct3D 9 assembly shaders
+                       vertex_program = sourcecache->getOrLoad(name, "d3d9_vertex.asm");
+                       pixel_program = sourcecache->getOrLoad(name, "d3d9_pixel.asm");
+               }
+               else if(drivertype == video::EDT_OPENGL){
+                       // OpenGL assembly shaders
+                       vertex_program = sourcecache->getOrLoad(name, "opengl_vertex.asm");
+                       pixel_program = sourcecache->getOrLoad(name, "opengl_fragment.asm");
+               }
+               if(vertex_program != "" || pixel_program != "")
+                       return;
+       }
+}
diff --git a/src/shader.h b/src/shader.h
new file mode 100644 (file)
index 0000000..d6a4253
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+Minetest-c55
+Copyright (C) 2012 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2012 Kahrl <kahrl@gmx.net>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#ifndef SHADER_HEADER
+#define SHADER_HEADER
+
+#include "irrlichttypes_extrabloated.h"
+#include "threads.h"
+#include <string>
+
+class IGameDef;
+
+/*
+       shader.{h,cpp}: Shader handling stuff.
+*/
+
+/*
+       Gets the path to a shader by first checking if the file
+         name_of_shader/filename
+       exists in shader_path and if not, using the data path.
+
+       If not found, returns "".
+
+       Utilizes a thread-safe cache.
+*/
+std::string getShaderPath(const std::string &name_of_shader,
+               const std::string &filename);
+
+struct ShaderInfo
+{
+       std::string name;
+       video::E_MATERIAL_TYPE material;
+
+       ShaderInfo(): name(""), material(video::EMT_SOLID) {}
+};
+
+/*
+       ShaderSource creates and caches shaders.
+*/
+
+class IShaderSource
+{
+public:
+       IShaderSource(){}
+       virtual ~IShaderSource(){}
+       virtual u32 getShaderId(const std::string &name){return 0;}
+       virtual u32 getShaderIdDirect(const std::string &name){return 0;}
+       virtual std::string getShaderName(u32 id){return "";}
+       virtual ShaderInfo getShader(u32 id){return ShaderInfo();}
+       virtual ShaderInfo getShader(const std::string &name){return ShaderInfo();}
+};
+
+class IWritableShaderSource : public IShaderSource
+{
+public:
+       IWritableShaderSource(){}
+       virtual ~IWritableShaderSource(){}
+       virtual u32 getShaderId(const std::string &name){return 0;}
+       virtual u32 getShaderIdDirect(const std::string &name){return 0;}
+       virtual std::string getShaderName(u32 id){return "";}
+       virtual ShaderInfo getShader(u32 id){return ShaderInfo();}
+       virtual ShaderInfo getShader(const std::string &name){return ShaderInfo();}
+
+       virtual void processQueue()=0;
+       virtual void insertSourceShader(const std::string &name_of_shader,
+               const std::string &filename, const std::string &program)=0;
+       virtual void rebuildShaders()=0;
+};
+
+IWritableShaderSource* createShaderSource(IrrlichtDevice *device);
+
+#endif