Fix superflous shader setting updates (#4800)
authorShadowNinja <ShadowNinja@users.noreply.github.com>
Tue, 22 Nov 2016 14:05:39 +0000 (09:05 -0500)
committerZeno- <kde.psych@gmail.com>
Tue, 22 Nov 2016 14:05:39 +0000 (00:05 +1000)
This improves rendering performance by ~40%

src/game.cpp
src/shader.cpp
src/shader.h

index e0ffa1be5bb4a7560081536a4248a26ad6912b1d..16287fe0d807c44ce25d171c0b8e8305da792c1f 100644 (file)
@@ -888,40 +888,73 @@ public:
        }
 };
 
+
+// before 1.8 there isn't a "integer interface", only float
+#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
+typedef f32 SamplerLayer_t;
+#else
+typedef s32 SamplerLayer_t;
+#endif
+
+
 class GameGlobalShaderConstantSetter : public IShaderConstantSetter
 {
        Sky *m_sky;
        bool *m_force_fog_off;
        f32 *m_fog_range;
+       bool m_fog_enabled;
+       CachedPixelShaderSetting<float, 4> m_sky_bg_color;
+       CachedPixelShaderSetting<float> m_fog_distance;
+       CachedVertexShaderSetting<float> m_animation_timer_vertex;
+       CachedPixelShaderSetting<float> m_animation_timer_pixel;
+       CachedPixelShaderSetting<float> m_day_night_ratio;
+       CachedPixelShaderSetting<float, 3> m_eye_position_pixel;
+       CachedVertexShaderSetting<float, 3> m_eye_position_vertex;
+       CachedPixelShaderSetting<float, 3> m_minimap_yaw;
+       CachedPixelShaderSetting<SamplerLayer_t> m_base_texture;
+       CachedPixelShaderSetting<SamplerLayer_t> m_normal_texture;
+       CachedPixelShaderSetting<SamplerLayer_t> m_texture_flags;
        Client *m_client;
-       bool m_fogEnabled;
 
 public:
        void onSettingsChange(const std::string &name)
        {
                if (name == "enable_fog")
-                       m_fogEnabled = g_settings->getBool("enable_fog");
+                       m_fog_enabled = g_settings->getBool("enable_fog");
        }
 
-       static void SettingsCallback(const std::string &name, void *userdata)
+       static void settingsCallback(const std::string &name, void *userdata)
        {
                reinterpret_cast<GameGlobalShaderConstantSetter*>(userdata)->onSettingsChange(name);
        }
 
+       void setSky(Sky *sky) { m_sky = sky; }
+
        GameGlobalShaderConstantSetter(Sky *sky, bool *force_fog_off,
                        f32 *fog_range, Client *client) :
                m_sky(sky),
                m_force_fog_off(force_fog_off),
                m_fog_range(fog_range),
+               m_sky_bg_color("skyBgColor"),
+               m_fog_distance("fogDistance"),
+               m_animation_timer_vertex("animationTimer"),
+               m_animation_timer_pixel("animationTimer"),
+               m_day_night_ratio("dayNightRatio"),
+               m_eye_position_pixel("eyePosition"),
+               m_eye_position_vertex("eyePosition"),
+               m_minimap_yaw("yawVec"),
+               m_base_texture("baseTexture"),
+               m_normal_texture("normalTexture"),
+               m_texture_flags("textureFlags"),
                m_client(client)
        {
-               g_settings->registerChangedCallback("enable_fog", SettingsCallback, this);
-               m_fogEnabled = g_settings->getBool("enable_fog");
+               g_settings->registerChangedCallback("enable_fog", settingsCallback, this);
+               m_fog_enabled = g_settings->getBool("enable_fog");
        }
 
        ~GameGlobalShaderConstantSetter()
        {
-               g_settings->deregisterChangedCallback("enable_fog", SettingsCallback, this);
+               g_settings->deregisterChangedCallback("enable_fog", settingsCallback, this);
        }
 
        virtual void onSetConstants(video::IMaterialRendererServices *services,
@@ -939,54 +972,92 @@ public:
                        bgcolorf.b,
                        bgcolorf.a,
                };
-               services->setPixelShaderConstant("skyBgColor", bgcolorfa, 4);
+               m_sky_bg_color.set(bgcolorfa, services);
 
                // Fog distance
                float fog_distance = 10000 * BS;
 
-               if (m_fogEnabled && !*m_force_fog_off)
+               if (m_fog_enabled && !*m_force_fog_off)
                        fog_distance = *m_fog_range;
 
-               services->setPixelShaderConstant("fogDistance", &fog_distance, 1);
+               m_fog_distance.set(&fog_distance, services);
 
-               // Day-night ratio
-               u32 daynight_ratio = m_client->getEnv().getDayNightRatio();
-               float daynight_ratio_f = (float)daynight_ratio / 1000.0;
-               services->setPixelShaderConstant("dayNightRatio", &daynight_ratio_f, 1);
+               float daynight_ratio = (float)m_client->getEnv().getDayNightRatio() / 1000.f;
+               m_day_night_ratio.set(&daynight_ratio, services);
 
                u32 animation_timer = porting::getTimeMs() % 100000;
-               float animation_timer_f = (float)animation_timer / 100000.0;
-               services->setPixelShaderConstant("animationTimer", &animation_timer_f, 1);
-               services->setVertexShaderConstant("animationTimer", &animation_timer_f, 1);
+               float animation_timer_f = (float)animation_timer / 100000.f;
+               m_animation_timer_vertex.set(&animation_timer_f, services);
+               m_animation_timer_pixel.set(&animation_timer_f, services);
 
-               LocalPlayer *player = m_client->getEnv().getLocalPlayer();
-               v3f eye_position = player->getEyePosition();
-               services->setPixelShaderConstant("eyePosition", (irr::f32 *)&eye_position, 3);
-               services->setVertexShaderConstant("eyePosition", (irr::f32 *)&eye_position, 3);
-
-               v3f minimap_yaw_vec = m_client->getMapper()->getYawVec();
-               services->setPixelShaderConstant("yawVec", (irr::f32 *)&minimap_yaw_vec, 3);
+               float eye_position_array[3];
+               v3f epos = m_client->getEnv().getLocalPlayer()->getEyePosition();
+#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
+               eye_position_array[0] = epos.X;
+               eye_position_array[1] = epos.Y;
+               eye_position_array[2] = epos.Z;
+#else
+               epos.getAs3Values(eye_position_array);
+#endif
+               m_eye_position_pixel.set(eye_position_array, services);
+               m_eye_position_vertex.set(eye_position_array, services);
 
-               // Uniform sampler layers
-               // before 1.8 there isn't a "integer interface", only float
+               float minimap_yaw_array[3];
+               v3f minimap_yaw = m_client->getMapper()->getYawVec();
 #if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
-               f32 layer0 = 0;
-               f32 layer1 = 1;
-               f32 layer2 = 2;
-               services->setPixelShaderConstant("baseTexture" , (irr::f32 *)&layer0, 1);
-               services->setPixelShaderConstant("normalTexture" , (irr::f32 *)&layer1, 1);
-               services->setPixelShaderConstant("textureFlags" , (irr::f32 *)&layer2, 1);
+               minimap_yaw_array[0] = minimap_yaw.X;
+               minimap_yaw_array[1] = minimap_yaw.Y;
+               minimap_yaw_array[2] = minimap_yaw.Z;
 #else
-               s32 layer0 = 0;
-               s32 layer1 = 1;
-               s32 layer2 = 2;
-               services->setPixelShaderConstant("baseTexture" , (irr::s32 *)&layer0, 1);
-               services->setPixelShaderConstant("normalTexture" , (irr::s32 *)&layer1, 1);
-               services->setPixelShaderConstant("textureFlags" , (irr::s32 *)&layer2, 1);
+               minimap_yaw.getAs3Values(minimap_yaw_array);
 #endif
+               m_minimap_yaw.set(minimap_yaw_array, services);
+
+               SamplerLayer_t base_tex = 0,
+                               normal_tex = 1,
+                               flags_tex = 2;
+               m_base_texture.set(&base_tex, services);
+               m_normal_texture.set(&normal_tex, services);
+               m_texture_flags.set(&flags_tex, services);
        }
 };
 
+
+class GameGlobalShaderConstantSetterFactory : public IShaderConstantSetterFactory
+{
+       Sky *m_sky;
+       bool *m_force_fog_off;
+       f32 *m_fog_range;
+       Client *m_client;
+       std::vector<GameGlobalShaderConstantSetter *> created_nosky;
+public:
+       GameGlobalShaderConstantSetterFactory(bool *force_fog_off,
+                       f32 *fog_range, Client *client) :
+               m_sky(NULL),
+               m_force_fog_off(force_fog_off),
+               m_fog_range(fog_range),
+               m_client(client)
+       {}
+
+       void setSky(Sky *sky) {
+               m_sky = sky;
+               for (size_t i = 0; i < created_nosky.size(); ++i) {
+                       created_nosky[i]->setSky(m_sky);
+               }
+               created_nosky.clear();
+       }
+
+       virtual IShaderConstantSetter* create()
+       {
+               GameGlobalShaderConstantSetter *scs = new GameGlobalShaderConstantSetter(
+                               m_sky, m_force_fog_off, m_fog_range, m_client);
+               if (!m_sky)
+                       created_nosky.push_back(scs);
+               return scs;
+       }
+};
+
+
 bool nodePlacementPrediction(Client &client,
                const ItemDefinition &playeritem_def, v3s16 nodepos, v3s16 neighbourpos)
 {
@@ -1695,6 +1766,9 @@ private:
        Hud *hud;
        Mapper *mapper;
 
+       GameRunData runData;
+       VolatileRunFlags flags;
+
        /* 'cache'
           This class does take ownership/responsibily for cleaning up etc of any of
           these items (e.g. device)
@@ -1886,6 +1960,18 @@ bool Game::startup(bool *kill,
 
        smgr->getParameters()->setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
 
+       memset(&runData, 0, sizeof(runData));
+       runData.time_from_last_punch = 10.0;
+       runData.profiler_max_page = 3;
+       runData.update_wielded_item_trigger = true;
+
+       memset(&flags, 0, sizeof(flags));
+       flags.show_chat = true;
+       flags.show_hud = true;
+       flags.show_debug = g_settings->getBool("show_debug");
+       flags.invert_mouse = g_settings->getBool("invert_mouse");
+       flags.first_loop_after_window_activation = true;
+
        if (!init(map_dir, address, port, gamespec))
                return false;
 
@@ -1902,34 +1988,15 @@ void Game::run()
        RunStats stats              = { 0 };
        CameraOrientation cam_view_target  = { 0 };
        CameraOrientation cam_view  = { 0 };
-       GameRunData runData         = { 0 };
        FpsControl draw_times       = { 0 };
-       VolatileRunFlags flags      = { 0 };
        f32 dtime; // in seconds
 
-       runData.time_from_last_punch  = 10.0;
-       runData.profiler_max_page = 3;
-       runData.update_wielded_item_trigger = true;
-
-       flags.show_chat = true;
-       flags.show_hud = true;
-       flags.show_minimap = g_settings->getBool("enable_minimap");
-       flags.show_debug = g_settings->getBool("show_debug");
-       flags.invert_mouse = g_settings->getBool("invert_mouse");
-       flags.first_loop_after_window_activation = true;
-
        /* Clear the profiler */
        Profiler::GraphValues dummyvalues;
        g_profiler->graphGet(dummyvalues);
 
        draw_times.last_time = device->getTimer()->getTime();
 
-       shader_src->addGlobalConstantSetter(new GameGlobalShaderConstantSetter(
-                       sky,
-                       &flags.force_fog_off,
-                       &runData.fog_range,
-                       client));
-
        set_light_table(g_settings->getFloat("display_gamma"));
 
 #ifdef __ANDROID__
@@ -2169,6 +2236,10 @@ bool Game::createClient(const std::string &playername,
                return false;
        }
 
+       GameGlobalShaderConstantSetterFactory *scsf = new GameGlobalShaderConstantSetterFactory(
+                       &flags.force_fog_off, &runData.fog_range, client);
+       shader_src->addShaderConstantSetterFactory(scsf);
+
        // Update cached textures, meshes and materials
        client->afterContentReceived(device);
 
@@ -2193,6 +2264,7 @@ bool Game::createClient(const std::string &playername,
        /* Skybox
         */
        sky = new Sky(smgr->getRootSceneNode(), smgr, -1, texture_src);
+       scsf->setSky(sky);
        skybox = NULL;  // This is used/set later on in the main run loop
 
        local_inventory = new Inventory(itemdef_manager);
index b0131c952f0365f2565999e0851222b14c72a179..170b0354d27574a9b386b46c4171b11b9a14f329 100644 (file)
@@ -167,29 +167,27 @@ private:
        }
 };
 
+
 /*
        ShaderCallback: Sets constants that can be used in shaders
 */
 
-class IShaderConstantSetterRegistry
-{
-public:
-       virtual ~IShaderConstantSetterRegistry(){};
-       virtual void onSetConstants(video::IMaterialRendererServices *services,
-                       bool is_highlevel, const std::string &name) = 0;
-};
-
 class ShaderCallback : public video::IShaderConstantSetCallBack
 {
-       IShaderConstantSetterRegistry *m_scsr;
-       std::string m_name;
+       std::vector<IShaderConstantSetter*> m_setters;
 
 public:
-       ShaderCallback(IShaderConstantSetterRegistry *scsr, const std::string &name):
-               m_scsr(scsr),
-               m_name(name)
-       {}
-       ~ShaderCallback() {}
+       ShaderCallback(const std::vector<IShaderConstantSetterFactory*> &factories)
+       {
+               for (u32 i = 0; i < factories.size(); ++i)
+                       m_setters.push_back(factories[i]->create());
+       }
+
+       ~ShaderCallback()
+       {
+               for (u32 i = 0; i < m_setters.size(); ++i)
+                       delete m_setters[i];
+       }
 
        virtual void OnSetConstants(video::IMaterialRendererServices *services, s32 userData)
        {
@@ -198,18 +196,25 @@ public:
 
                bool is_highlevel = userData;
 
-               m_scsr->onSetConstants(services, is_highlevel, m_name);
+               for (u32 i = 0; i < m_setters.size(); ++i)
+                       m_setters[i]->onSetConstants(services, is_highlevel);
        }
 };
 
+
 /*
        MainShaderConstantSetter: Set basic constants required for almost everything
 */
 
 class MainShaderConstantSetter : public IShaderConstantSetter
 {
+       CachedVertexShaderSetting<float, 16> m_world_view_proj;
+       CachedVertexShaderSetting<float, 16> m_world;
+
 public:
-       MainShaderConstantSetter(IrrlichtDevice *device)
+       MainShaderConstantSetter() :
+               m_world_view_proj("mWorldViewProj"),
+               m_world("mWorld")
        {}
        ~MainShaderConstantSetter() {}
 
@@ -219,31 +224,40 @@ public:
                video::IVideoDriver *driver = services->getVideoDriver();
                sanity_check(driver);
 
-               // set clip matrix
+               // 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);
+               if (is_highlevel)
+                       m_world_view_proj.set(*reinterpret_cast<float(*)[16]>(worldViewProj.pointer()), services);
                else
-                       services->setVertexShaderConstant(worldViewProj.pointer(), 4, 4);
+                       services->setVertexShaderConstant(worldViewProj.pointer(), 0, 4);
 
-               // set world matrix
+               // Set world matrix
                core::matrix4 world = driver->getTransform(video::ETS_WORLD);
-               if(is_highlevel)
-                       services->setVertexShaderConstant("mWorld", world.pointer(), 16);
+               if (is_highlevel)
+                       m_world.set(*reinterpret_cast<float(*)[16]>(world.pointer()), services);
                else
-                       services->setVertexShaderConstant(world.pointer(), 8, 4);
+                       services->setVertexShaderConstant(world.pointer(), 4, 4);
 
        }
 };
 
+
+class MainShaderConstantSetterFactory : public IShaderConstantSetterFactory
+{
+public:
+       virtual IShaderConstantSetter* create()
+               { return new MainShaderConstantSetter(); }
+};
+
+
 /*
        ShaderSource
 */
 
-class ShaderSource : public IWritableShaderSource, public IShaderConstantSetterRegistry
+class ShaderSource : public IWritableShaderSource
 {
 public:
        ShaderSource(IrrlichtDevice *device);
@@ -286,22 +300,17 @@ public:
        // Shall be called from the main thread.
        void rebuildShaders();
 
-       void addGlobalConstantSetter(IShaderConstantSetter *setter)
+       void addShaderConstantSetterFactory(IShaderConstantSetterFactory *setter)
        {
-               m_global_setters.push_back(setter);
+               m_setter_factories.push_back(setter);
        }
 
-       void onSetConstants(video::IMaterialRendererServices *services,
-                       bool is_highlevel, const std::string &name);
-
 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
@@ -316,9 +325,11 @@ private:
        // Queued shader fetches (to be processed by the main thread)
        RequestQueue<std::string, u32, u8, u8> m_get_shader_queue;
 
-       // Global constant setters
-       // TODO: Delete these in the destructor
-       std::vector<IShaderConstantSetter*> m_global_setters;
+       // Global constant setter factories
+       std::vector<IShaderConstantSetterFactory *> m_setter_factories;
+
+       // Shader callbacks
+       std::vector<ShaderCallback *> m_callbacks;
 };
 
 IWritableShaderSource* createShaderSource(IrrlichtDevice *device)
@@ -331,8 +342,8 @@ IWritableShaderSource* createShaderSource(IrrlichtDevice *device)
 */
 ShaderInfo generate_shader(std::string name,
                u8 material_type, u8 drawtype,
-               IrrlichtDevice *device,
-               video::IShaderConstantSetCallBack *callback,
+               IrrlichtDevice *device, std::vector<ShaderCallback *> &callbacks,
+               const std::vector<IShaderConstantSetterFactory*> &setter_factories,
                SourceShaderCache *sourcecache);
 
 /*
@@ -348,28 +359,24 @@ ShaderSource::ShaderSource(IrrlichtDevice *device):
 {
        assert(m_device); // Pre-condition
 
-       m_shader_callback = new ShaderCallback(this, "default");
-
        m_main_thread = thr_get_current_thread_id();
 
        // Add a dummy ShaderInfo as the first index, named ""
        m_shaderinfo_cache.push_back(ShaderInfo());
 
        // Add main global constant setter
-       addGlobalConstantSetter(new MainShaderConstantSetter(device));
+       addShaderConstantSetterFactory(new MainShaderConstantSetterFactory());
 }
 
 ShaderSource::~ShaderSource()
 {
-       for (std::vector<IShaderConstantSetter*>::iterator iter = m_global_setters.begin();
-                       iter != m_global_setters.end(); ++iter) {
+       for (std::vector<ShaderCallback *>::iterator iter = m_callbacks.begin();
+                       iter != m_callbacks.end(); ++iter) {
                delete *iter;
        }
-       m_global_setters.clear();
-
-       if (m_shader_callback) {
-               m_shader_callback->drop();
-               m_shader_callback = NULL;
+       for (std::vector<IShaderConstantSetterFactory *>::iterator iter = m_setter_factories.begin();
+                       iter != m_setter_factories.end(); ++iter) {
+               delete *iter;
        }
 }
 
@@ -445,8 +452,8 @@ u32 ShaderSource::getShaderIdDirect(const std::string &name,
                return 0;
        }
 
-       ShaderInfo info = generate_shader(name, material_type, drawtype, m_device,
-                       m_shader_callback, &m_sourcecache);
+       ShaderInfo info = generate_shader(name, material_type, drawtype,
+                       m_device, m_callbacks, m_setter_factories, &m_sourcecache);
 
        /*
                Add shader to caches (add dummy shaders too)
@@ -511,22 +518,16 @@ void ShaderSource::rebuildShaders()
                ShaderInfo *info = &m_shaderinfo_cache[i];
                if(info->name != ""){
                        *info = generate_shader(info->name, info->material_type,
-                                       info->drawtype, m_device, m_shader_callback, &m_sourcecache);
+                                       info->drawtype, m_device, m_callbacks,
+                                       m_setter_factories, &m_sourcecache);
                }
        }
 }
 
-void ShaderSource::onSetConstants(video::IMaterialRendererServices *services,
-               bool is_highlevel, const std::string &name)
-{
-       for(u32 i=0; i<m_global_setters.size(); i++){
-               IShaderConstantSetter *setter = m_global_setters[i];
-               setter->onSetConstants(services, is_highlevel);
-       }
-}
 
 ShaderInfo generate_shader(std::string name, u8 material_type, u8 drawtype,
-               IrrlichtDevice *device, video::IShaderConstantSetCallBack *callback,
+               IrrlichtDevice *device, std::vector<ShaderCallback *> &callbacks,
+               const std::vector<IShaderConstantSetterFactory*> &setter_factories,
                SourceShaderCache *sourcecache)
 {
        ShaderInfo shaderinfo;
@@ -766,6 +767,7 @@ ShaderInfo generate_shader(std::string name, u8 material_type, u8 drawtype,
                geometry_program = shaders_header + geometry_program;
                geometry_program_ptr = geometry_program.c_str();
        }
+       ShaderCallback *cb = new ShaderCallback(setter_factories);
        s32 shadermat = -1;
        if(is_highlevel){
                infostream<<"Compiling high level shaders for "<<name<<std::endl;
@@ -782,7 +784,7 @@ ShaderInfo generate_shader(std::string name, u8 material_type, u8 drawtype,
                        scene::EPT_TRIANGLES,      // Geometry shader input
                        scene::EPT_TRIANGLE_STRIP, // Geometry shader output
                        0,                         // Support maximum number of vertices
-                       callback,                  // Set-constant callback
+                       cb, // Set-constant callback
                        shaderinfo.base_material,  // Base material
                        1                          // Userdata passed to callback
                        );
@@ -794,6 +796,7 @@ ShaderInfo generate_shader(std::string name, u8 material_type, u8 drawtype,
                        dumpShaderProgram(warningstream, "Vertex", vertex_program);
                        dumpShaderProgram(warningstream, "Pixel", pixel_program);
                        dumpShaderProgram(warningstream, "Geometry", geometry_program);
+                       delete cb;
                        return shaderinfo;
                }
        }
@@ -802,7 +805,7 @@ ShaderInfo generate_shader(std::string name, u8 material_type, u8 drawtype,
                shadermat = gpu->addShaderMaterial(
                        vertex_program_ptr,   // Vertex shader program
                        pixel_program_ptr,    // Pixel shader program
-                       callback,             // Set-constant callback
+                       cb, // Set-constant callback
                        shaderinfo.base_material,  // Base material
                        0                     // Userdata passed to callback
                        );
@@ -814,9 +817,11 @@ ShaderInfo generate_shader(std::string name, u8 material_type, u8 drawtype,
                                        <<std::endl;
                        dumpShaderProgram(warningstream, "Vertex", vertex_program);
                        dumpShaderProgram(warningstream,"Pixel", pixel_program);
+                       delete cb;
                        return shaderinfo;
                }
        }
+       callbacks.push_back(cb);
 
        // HACK, TODO: investigate this better
        // Grab the material renderer once more so minetest doesn't crash on exit
index b8aa88bce17f92db0c8f66fccb914bff89cfafb7..766871f02405fc0ae37afe99fafd1f92e9a19622 100644 (file)
@@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #ifndef SHADER_HEADER
 #define SHADER_HEADER
 
+#include <IMaterialRendererServices.h>
 #include "irrlichttypes_extrabloated.h"
 #include "threads.h"
 #include <string>
@@ -43,8 +44,7 @@ class IGameDef;
 std::string getShaderPath(const std::string &name_of_shader,
                const std::string &filename);
 
-struct ShaderInfo
-{
+struct ShaderInfo {
        std::string name;
        video::E_MATERIAL_TYPE base_material;
        video::E_MATERIAL_TYPE material;
@@ -66,20 +66,66 @@ namespace irr { namespace video {
        class IMaterialRendererServices;
 } }
 
-class IShaderConstantSetter
-{
+
+class IShaderConstantSetter {
 public:
        virtual ~IShaderConstantSetter(){};
        virtual void onSetConstants(video::IMaterialRendererServices *services,
                        bool is_highlevel) = 0;
 };
 
+
+class IShaderConstantSetterFactory {
+public:
+       virtual ~IShaderConstantSetterFactory() {};
+       virtual IShaderConstantSetter* create() = 0;
+};
+
+
+template <typename T, std::size_t count=1>
+class CachedShaderSetting {
+       const char *m_name;
+       T m_sent[count];
+       bool has_been_set;
+       bool is_pixel;
+protected:
+       CachedShaderSetting(const char *name, bool is_pixel) :
+               m_name(name), has_been_set(false), is_pixel(is_pixel)
+       {}
+public:
+       void set(const T value[count], video::IMaterialRendererServices *services)
+       {
+               if (has_been_set && std::equal(m_sent, m_sent + count, value))
+                       return;
+               if (is_pixel)
+                       services->setPixelShaderConstant(m_name, value, count);
+               else
+                       services->setVertexShaderConstant(m_name, value, count);
+               std::copy(value, value + count, m_sent);
+               has_been_set = true;
+       }
+};
+
+template <typename T, std::size_t count = 1>
+class CachedPixelShaderSetting : public CachedShaderSetting<T, count> {
+public:
+       CachedPixelShaderSetting(const char *name) :
+               CachedShaderSetting<T, count>(name, true){}
+};
+
+template <typename T, std::size_t count = 1>
+class CachedVertexShaderSetting : public CachedShaderSetting<T, count> {
+public:
+       CachedVertexShaderSetting(const char *name) :
+               CachedShaderSetting<T, count>(name, false){}
+};
+
+
 /*
        ShaderSource creates and caches shaders.
 */
 
-class IShaderSource
-{
+class IShaderSource {
 public:
        IShaderSource(){}
        virtual ~IShaderSource(){}
@@ -90,8 +136,7 @@ public:
                const u8 material_type, const u8 drawtype){return 0;}
 };
 
-class IWritableShaderSource : public IShaderSource
-{
+class IWritableShaderSource : public IShaderSource {
 public:
        IWritableShaderSource(){}
        virtual ~IWritableShaderSource(){}
@@ -105,7 +150,7 @@ public:
        virtual void insertSourceShader(const std::string &name_of_shader,
                const std::string &filename, const std::string &program)=0;
        virtual void rebuildShaders()=0;
-       virtual void addGlobalConstantSetter(IShaderConstantSetter *setter)=0;
+       virtual void addShaderConstantSetterFactory(IShaderConstantSetterFactory *setter) = 0;
 };
 
 IWritableShaderSource* createShaderSource(IrrlichtDevice *device);