diff --git a/src/graphics/draw_calls.cpp b/src/graphics/draw_calls.cpp
index a452d7519..40ad6bc94 100644
--- a/src/graphics/draw_calls.cpp
+++ b/src/graphics/draw_calls.cpp
@@ -145,13 +145,6 @@ void DrawCalls::parseSceneManager(core::list<scene::ISceneNode*> &List,
         if (LODNode *node = dynamic_cast<LODNode *>(*I))
         {
             node->updateVisibility();
-            if (SP::sp_first_frame)
-            {
-                for (auto* child_node : node->getAllNodes())
-                {
-                    child_node->setVisible(true);
-                }
-            }
         }
         (*I)->updateAbsolutePosition();
         if (!(*I)->isVisible())
@@ -159,8 +152,7 @@ void DrawCalls::parseSceneManager(core::list<scene::ISceneNode*> &List,
 
         if (STKParticle *node = dynamic_cast<STKParticle*>(*I))
         {
-            if (!isCulledPrecise(cam, *I, irr_driver->getBoundingBoxesViz()) ||
-                SP::sp_first_frame)
+            if (!isCulledPrecise(cam, *I, irr_driver->getBoundingBoxesViz()))
                 CPUParticleManager::getInstance()->addParticleNode(node);
             continue;
         }
@@ -168,7 +160,7 @@ void DrawCalls::parseSceneManager(core::list<scene::ISceneNode*> &List,
         if (scene::IBillboardSceneNode *node =
             dynamic_cast<scene::IBillboardSceneNode*>(*I))
         {
-            if (!isCulledPrecise(cam, *I) || SP::sp_first_frame)
+            if (!isCulledPrecise(cam, *I))
                 CPUParticleManager::getInstance()->addBillboardNode(node);
             continue;
         }
@@ -176,8 +168,7 @@ void DrawCalls::parseSceneManager(core::list<scene::ISceneNode*> &List,
         if (STKTextBillboard *tb =
             dynamic_cast<STKTextBillboard*>(*I))
         {
-            if (!isCulledPrecise(cam, *I, irr_driver->getBoundingBoxesViz()) ||
-                SP::sp_first_frame)
+            if (!isCulledPrecise(cam, *I, irr_driver->getBoundingBoxesViz()))
                 TextBillboardDrawer::addTextBillboard(tb);
             continue;
         }
diff --git a/src/graphics/sp/sp_base.cpp b/src/graphics/sp/sp_base.cpp
index c16954cb4..3f3595e2d 100644
--- a/src/graphics/sp/sp_base.cpp
+++ b/src/graphics/sp/sp_base.cpp
@@ -120,8 +120,6 @@ void initSTKRenderer(ShaderBasedRenderer* sbr)
 // ----------------------------------------------------------------------------
 GLuint sp_mat_ubo[MAX_PLAYER_COUNT][3] = {};
 // ----------------------------------------------------------------------------
-bool sp_first_frame = false;
-// ----------------------------------------------------------------------------
 GLuint sp_fog_ubo = 0;
 // ----------------------------------------------------------------------------
 core::vector3df sp_wind_dir;
@@ -796,8 +794,7 @@ void addObject(SPMeshNode* node)
         model_matrix.transformBoxEx(bb);
         std::vector<bool> discard;
         discard.resize((g_handle_shadow ? 5 : 1), false);
-        for (int dc_type = 0; dc_type <
-            (g_handle_shadow ? 5 : sp_first_frame ? 0 : 1); dc_type++)
+        for (int dc_type = 0; dc_type < (g_handle_shadow ? 5 : 1); dc_type++)
         {
             for (int i = 0; i < 24; i += 4)
             {
@@ -822,7 +819,7 @@ void addObject(SPMeshNode* node)
                 }
             }
         }
-        if (sp_first_frame || g_handle_shadow ?
+        if (g_handle_shadow ?
             (discard[0] && discard[1] && discard[2] && discard[3] &&
             discard[4]) : discard[0])
         {
@@ -847,8 +844,7 @@ void addObject(SPMeshNode* node)
 
         mb->uploadGLMesh();
         // For first frame only need the vbo to be initialized
-        if (!added_for_skinning && node->getAnimationState() &&
-            !sp_first_frame)
+        if (!added_for_skinning && node->getAnimationState())
         {
             added_for_skinning = true;
             int skinning_offset = g_skinning_offset + node->getTotalJoints();
@@ -873,7 +869,7 @@ void addObject(SPMeshNode* node)
 
         for (int dc_type = 0; dc_type < (g_handle_shadow ? 5 : 1); dc_type++)
         {
-            if (!sp_first_frame && discard[dc_type])
+            if (discard[dc_type])
             {
                 continue;
             }
@@ -1071,7 +1067,6 @@ void updateModelMatrix()
 {
     // Make sure all textures (with handles) are loaded
     SPTextureManager::get()->checkForGLCommand(true/*before_scene*/);
-    SP::sp_first_frame = false;
     if (!sp_culling)
     {
         return;
@@ -1395,6 +1390,24 @@ SPMesh* convertEVTStandard(irr::scene::IMesh* mesh,
     return spm;
 }   // convertEVTStandard
 
+// ----------------------------------------------------------------------------
+void uploadSPM(irr::scene::IMesh* mesh)
+{
+    if (!CVS->isGLSL())
+    {
+        return;
+    }
+    SP::SPMesh* spm = dynamic_cast<SP::SPMesh*>(mesh);
+    if (spm)
+    {
+        for (u32 i = 0; i < spm->getMeshBufferCount(); i++)
+        {
+            SP::SPMeshBuffer* mb = spm->getSPMeshBuffer(i);
+            mb->uploadGLMesh();
+        }
+    }
+}   // uploadSPM
+
 }
 
 #endif
diff --git a/src/graphics/sp/sp_base.hpp b/src/graphics/sp/sp_base.hpp
index deced790d..d6284bf2b 100644
--- a/src/graphics/sp/sp_base.hpp
+++ b/src/graphics/sp/sp_base.hpp
@@ -87,7 +87,6 @@ class SPMeshBuffer;
 
 extern GLuint sp_mat_ubo[MAX_PLAYER_COUNT][3];
 extern GLuint sp_fog_ubo;
-extern bool sp_first_frame;
 extern std::array<GLuint, 1> sp_prefilled_tex;
 extern unsigned sp_solid_poly_count;
 extern unsigned sp_shadow_poly_count;
@@ -136,6 +135,8 @@ void drawBoundingBoxes();
 SPMesh* convertEVTStandard(irr::scene::IMesh* mesh,
                            const irr::video::SColor* color = NULL);
 // ----------------------------------------------------------------------------
+void uploadSPM(irr::scene::IMesh* mesh);
+// ----------------------------------------------------------------------------
 inline uint8_t srgbToLinear(float color_srgb)
 {
     int ret;
diff --git a/src/graphics/sp/sp_shader_manager.cpp b/src/graphics/sp/sp_shader_manager.cpp
index bc40664af..0ff6b9e53 100644
--- a/src/graphics/sp/sp_shader_manager.cpp
+++ b/src/graphics/sp/sp_shader_manager.cpp
@@ -137,8 +137,12 @@ SPShaderManager::SPShaderManager()
 // ----------------------------------------------------------------------------
 SPShaderManager::~SPShaderManager()
 {
-    m_shaders.clear();
     m_official_shaders.clear();
+    removeUnusedShaders();
+    for (auto p : m_shaders)
+    {
+        Log::error("SPShaderManager", "%s > 1 ref_count", p.first.c_str());
+    }
 }   // ~SPShaderManager
 
 // ----------------------------------------------------------------------------
diff --git a/src/graphics/sp/sp_texture_manager.cpp b/src/graphics/sp/sp_texture_manager.cpp
index 654f1f2e4..75f3b7dd1 100644
--- a/src/graphics/sp/sp_texture_manager.cpp
+++ b/src/graphics/sp/sp_texture_manager.cpp
@@ -88,6 +88,11 @@ SPTextureManager::~SPTextureManager()
         t.join();
     }
     m_threaded_load_obj.clear();
+    removeUnusedTextures();
+    for (auto p : m_textures)
+    {
+        Log::error("SPTextureManager", "%s > 1 ref_count", p.first.c_str());
+    }
 }   // ~SPTextureManager
 
 // ----------------------------------------------------------------------------
diff --git a/src/items/attachment.cpp b/src/items/attachment.cpp
index 3c012d58c..99be20a75 100644
--- a/src/items/attachment.cpp
+++ b/src/items/attachment.cpp
@@ -566,7 +566,8 @@ void Attachment::update(float dt)
         // Everything is done in the plugin.
         break;
     case ATTACH_NOLOKS_SWATTER:
-        // Should never be called, this symbols is only used as an index for
+    case ATTACH_SWATTER_ANIM:
+        // Should never be called, these symbols are only used as an index for
         // the model, Nolok's attachment type is ATTACH_SWATTER
         assert(false);
         break;
@@ -597,9 +598,6 @@ void Attachment::update(float dt)
             }
         }
         break;
-    case ATTACH_TINYTUX:
-        // Nothing to do for tinytux, this is all handled in EmergencyAnimation
-        break;
     case ATTACH_BUBBLEGUM_SHIELD:
     case ATTACH_NOLOK_BUBBLEGUM_SHIELD:
         if (m_time_left < 0)
diff --git a/src/items/attachment.hpp b/src/items/attachment.hpp
index a19c0c23b..154b7b984 100644
--- a/src/items/attachment.hpp
+++ b/src/items/attachment.hpp
@@ -60,10 +60,10 @@ public:
         ATTACH_BOMB,
         ATTACH_ANVIL,
         ATTACH_SWATTER,
-        // Note that the next symbol is only used as an index into the mesh
+        // Note that the next 2 symbols are only used as an index into the mesh
         // array; it will NEVER be actually assigned as an attachment type
         ATTACH_NOLOKS_SWATTER,
-        ATTACH_TINYTUX,
+        ATTACH_SWATTER_ANIM,
         ATTACH_BUBBLEGUM_SHIELD,
         ATTACH_NOLOK_BUBBLEGUM_SHIELD,
         ATTACH_MAX,
diff --git a/src/items/attachment_manager.cpp b/src/items/attachment_manager.cpp
index b49bf4bb8..0233885f1 100644
--- a/src/items/attachment_manager.cpp
+++ b/src/items/attachment_manager.cpp
@@ -21,6 +21,7 @@
 #include "graphics/irr_driver.hpp"
 #include "graphics/material.hpp"
 #include "graphics/material_manager.hpp"
+#include "graphics/sp/sp_base.hpp"
 #include "io/file_manager.hpp"
 
 AttachmentManager *attachment_manager = 0;
@@ -53,7 +54,7 @@ static const initAttachmentType iat[]=
     {Attachment::ATTACH_ANVIL,            "anchor.spm",           "anchor-attach-icon.png"       },
     {Attachment::ATTACH_SWATTER,          "swatter.spm",          "swatter-icon.png"             },
     {Attachment::ATTACH_NOLOKS_SWATTER,   "swatter_nolok.spm",    "swatter-icon.png"             },
-    {Attachment::ATTACH_TINYTUX,          "reset-button.spm",     "reset-attach-icon.png"        },
+    {Attachment::ATTACH_SWATTER_ANIM,     "swatter_anim.spm",     "swatter-icon.png"             },
     {Attachment::ATTACH_BUBBLEGUM_SHIELD, "bubblegum_shield.spm", "shield-icon.png"              },
     {Attachment::ATTACH_NOLOK_BUBBLEGUM_SHIELD, "bubblegum_shield_nolok.spm", "shield-icon.png"              },
     {Attachment::ATTACH_MAX,              "",                     ""                             },
@@ -76,23 +77,18 @@ AttachmentManager::~AttachmentManager()
     }
 }   // ~AttachmentManager
 
-//-----------------------------------------------------------------------------
-void AttachmentManager::removeTextures()
-{
-    for(int i=0; iat[i].attachment!=Attachment::ATTACH_MAX; i++)
-    {
-        // FIXME: free attachment textures
-    }   // for
-}   // removeTextures
-
 //-----------------------------------------------------------------------------
 void AttachmentManager::loadModels()
 {
     for(int i=0; iat[i].attachment!=Attachment::ATTACH_MAX; i++)
     {
         std::string full_path = file_manager->getAsset(FileManager::MODEL,iat[i].file);
-        m_attachments[iat[i].attachment]=irr_driver->getAnimatedMesh(full_path);
-        m_attachments[iat[i].attachment]->grab();
+        scene::IAnimatedMesh* mesh = irr_driver->getAnimatedMesh(full_path);
+        mesh->grab();
+#ifndef SERVER_ONLY
+        SP::uploadSPM(mesh);
+#endif
+        m_attachments[iat[i].attachment] = mesh;
         if(iat[i].icon_file)
         {
             std::string full_icon_path     =
diff --git a/src/items/attachment_manager.hpp b/src/items/attachment_manager.hpp
index c5d827ed7..7bb03baab 100644
--- a/src/items/attachment_manager.hpp
+++ b/src/items/attachment_manager.hpp
@@ -39,7 +39,6 @@ private:
 public:
                AttachmentManager() {};
               ~AttachmentManager();
-    void       removeTextures   ();
     void       loadModels       ();
     // ------------------------------------------------------------------------
     /** Returns the mest for a certain attachment.
diff --git a/src/items/item_manager.cpp b/src/items/item_manager.cpp
index 24bf8f4dc..9d2f96e47 100644
--- a/src/items/item_manager.cpp
+++ b/src/items/item_manager.cpp
@@ -25,8 +25,7 @@
 #include "config/stk_config.hpp"
 #include "config/user_config.hpp"
 #include "graphics/irr_driver.hpp"
-#include "graphics/material.hpp"
-#include "graphics/material_manager.hpp"
+#include "graphics/sp/sp_base.hpp"
 #include "io/file_manager.hpp"
 #include "karts/abstract_kart.hpp"
 #include "karts/controller/spare_tire_ai.hpp"
@@ -36,6 +35,7 @@
 #include "tracks/arena_graph.hpp"
 #include "tracks/arena_node.hpp"
 #include "tracks/track.hpp"
+#include "utils/random_generator.hpp"
 #include "utils/string_utils.hpp"
 
 #include <IMesh.h>
@@ -104,6 +104,9 @@ void ItemManager::loadDefaultItemMeshes()
                         "- aborting", name.c_str());
             exit(-1);
         }
+#ifndef SERVER_ONLY
+        SP::uploadSPM(mesh);
+#endif
         mesh->grab();
         m_item_mesh[i]            = mesh;
         node->get("glow", &(m_glow_color[i]));
@@ -114,7 +117,13 @@ void ItemManager::loadDefaultItemMeshes()
                               ? NULL
                               : irr_driver->getMesh(lowres_model_filename);
 
-        if (m_item_lowres_mesh[i]) m_item_lowres_mesh[i]->grab();
+        if (m_item_lowres_mesh[i])
+        {
+#ifndef SERVER_ONLY
+            SP::uploadSPM(m_item_lowres_mesh[i]);
+#endif
+            m_item_lowres_mesh[i]->grab();
+        }
     }   // for i
     delete root;
 }   // loadDefaultItemMeshes
diff --git a/src/items/swatter.cpp b/src/items/swatter.cpp
index 4241d6654..f2b73ce6a 100644
--- a/src/items/swatter.cpp
+++ b/src/items/swatter.cpp
@@ -32,7 +32,7 @@
 #include "graphics/explosion.hpp"
 #include "graphics/irr_driver.hpp"
 #include "io/file_manager.hpp"
-#include "items/attachment.hpp"
+#include "items/attachment_manager.hpp"
 #include "items/projectile_manager.hpp"
 #include "karts/controller/controller.hpp"
 #include "karts/explosion_animation.hpp"
@@ -74,8 +74,8 @@ Swatter::Swatter(AbstractKart *kart, bool was_bomb,
 
     if (m_removing_bomb)
     {
-        m_scene_node->setMesh(irr_driver->getAnimatedMesh(
-                        file_manager->getAsset(FileManager::MODEL,"swatter_anim.spm") ) );
+        m_scene_node->setMesh(attachment_manager
+            ->getMesh(Attachment::ATTACH_SWATTER_ANIM));
         m_scene_node->setRotation(core::vector3df(0.0, -180.0, 0.0));
         m_scene_node->setAnimationSpeed(0.9f);
         m_scene_node->setCurrentFrame(0.0f);
diff --git a/src/karts/controller/local_player_controller.cpp b/src/karts/controller/local_player_controller.cpp
index b3e8bb5a2..475ff5ad0 100644
--- a/src/karts/controller/local_player_controller.cpp
+++ b/src/karts/controller/local_player_controller.cpp
@@ -217,8 +217,7 @@ void LocalPlayerController::update(float dt)
         }
     }
 #endif
-    if (m_kart->getKartAnimation() && m_sound_schedule == false &&
-        m_kart->getAttachment()->getType() != Attachment::ATTACH_TINYTUX)
+    if (m_kart->getKartAnimation() && m_sound_schedule == false)
     {
         m_sound_schedule = true;
     }
diff --git a/src/karts/kart_model.cpp b/src/karts/kart_model.cpp
index 05f92794c..9f8271b2b 100644
--- a/src/karts/kart_model.cpp
+++ b/src/karts/kart_model.cpp
@@ -678,16 +678,7 @@ bool KartModel::loadModels(const KartProperties &kart_properties)
         std::string full_name = kart_properties.getKartDir() + obj.getFilename();
         obj.setModel(irr_driver->getMesh(full_name));
 #ifndef SERVER_ONLY
-        if (CVS->isGLSL())
-        {
-            for (u32 j = 0; j < obj.getModel()->getMeshBufferCount(); j++)
-            {
-                SP::SPMeshBuffer* mb = static_cast<SP::SPMeshBuffer*>
-                    (obj.getModel()->getMeshBuffer(j));
-                // Pre-upload gl meshes and textures for kart screen
-                mb->uploadGLMesh();
-            }
-        }
+        SP::uploadSPM(obj.getModel());
 #endif
         obj.getModel()->grab();
         irr_driver->grabAllTextures(obj.getModel());
@@ -725,16 +716,7 @@ bool KartModel::loadModels(const KartProperties &kart_properties)
             kart_properties.getKartDir()+m_wheel_filename[i];
         m_wheel_model[i] = irr_driver->getMesh(full_wheel);
 #ifndef SERVER_ONLY
-        if (CVS->isGLSL())
-        {
-            for (u32 j = 0; j < m_wheel_model[i]->getMeshBufferCount(); j++)
-            {
-                SP::SPMeshBuffer* mb = static_cast<SP::SPMeshBuffer*>
-                    (m_wheel_model[i]->getMeshBuffer(j));
-                // Pre-upload gl meshes and textures for kart screen
-                mb->uploadGLMesh();
-            }
-        }
+        SP::uploadSPM(m_wheel_model[i]);
 #endif
         // Grab all textures. This is done for the master only, so
         // the destructor will only free the textures if a master
diff --git a/src/tracks/model_definition_loader.cpp b/src/tracks/model_definition_loader.cpp
index 0fea06376..8ffd1730c 100644
--- a/src/tracks/model_definition_loader.cpp
+++ b/src/tracks/model_definition_loader.cpp
@@ -113,6 +113,7 @@ LODNode* ModelDefinitionLoader::instanciateAsLOD(const XMLNode* node, scene::ISc
 
                 m_track->handleAnimatedTextures(scene_node, *group[m].m_xml);
 
+                Track::uploadNodeVertexBuffer(scene_node);
                 lod_node->add(group[m].m_distance, scene_node, true);
             }
             else
@@ -136,6 +137,7 @@ LODNode* ModelDefinitionLoader::instanciateAsLOD(const XMLNode* node, scene::ISc
 
                 m_track->handleAnimatedTextures(scene_node, *group[m].m_xml);
 
+                Track::uploadNodeVertexBuffer(scene_node);
                 lod_node->add(group[m].m_distance, scene_node, true);
             }
         }
diff --git a/src/tracks/track.cpp b/src/tracks/track.cpp
index b39037420..93fe9322d 100644
--- a/src/tracks/track.cpp
+++ b/src/tracks/track.cpp
@@ -851,6 +851,7 @@ void Track::createPhysicsModel(unsigned int main_track_count)
     for(unsigned int i=main_track_count; i<m_all_nodes.size(); i++)
     {
         convertTrackToBullet(m_all_nodes[i]);
+        uploadNodeVertexBuffer(m_all_nodes[i]);
     }
     m_track_mesh->createPhysicalBody(m_friction);
     m_gfx_effect_mesh->createCollisionShape();
@@ -1423,6 +1424,7 @@ bool Track::loadMainTrack(const XMLNode &root)
     for(unsigned int i=0; i<m_all_nodes.size(); i++)
     {
         convertTrackToBullet(m_all_nodes[i]);
+        uploadNodeVertexBuffer(m_all_nodes[i]);
     }
 
     if (m_track_mesh == NULL)
@@ -2090,8 +2092,6 @@ void Track::loadTrackModel(bool reverse_track, unsigned int mode_id)
         }
         m_spherical_harmonics_textures.clear();
     }
-    // Draw all object visible in first frame to load all textures
-    SP::sp_first_frame = true;
 #endif   // !SERVER_ONLY
 }   // loadTrackModel
 
@@ -2563,3 +2563,19 @@ float Track::getAngle(int n) const
 {
     return DriveGraph::get()->getAngleToNext(n, 0);
 }   // getAngle
+
+//-----------------------------------------------------------------------------
+void Track::uploadNodeVertexBuffer(scene::ISceneNode *node)
+{
+#ifndef SERVER_ONLY
+    if (!CVS->isGLSL())
+    {
+        return;
+    }
+    SP::SPMeshNode* spmn = dynamic_cast<SP::SPMeshNode*>(node);
+    if (spmn)
+    {
+        SP::uploadSPM(spmn->getSPM());
+    }
+#endif
+}   // uploadNodeVertexBuffer
diff --git a/src/tracks/track.hpp b/src/tracks/track.hpp
index 47bd932e2..4117633ee 100644
--- a/src/tracks/track.hpp
+++ b/src/tracks/track.hpp
@@ -409,6 +409,9 @@ public:
      *  minutes(!) in debug mode to be computed. */
     static bool        m_dont_load_navmesh;
 
+    /** Static helper function to pre-upload vertex buffer in spm. */
+    static void uploadNodeVertexBuffer(scene::ISceneNode *node);
+
     static const float NOHIT;
 
                        Track             (const std::string &filename);
diff --git a/src/tracks/track_object_presentation.cpp b/src/tracks/track_object_presentation.cpp
index 8cae495f2..efe9731a8 100644
--- a/src/tracks/track_object_presentation.cpp
+++ b/src/tracks/track_object_presentation.cpp
@@ -585,17 +585,15 @@ void TrackObjectPresentationMesh::init(const XMLNode* xml_node,
         Track *track = Track::getCurrentTrack();
         if (track && track && xml_node)
             track->handleAnimatedTextures(m_node, *xml_node);
+        Track::uploadNodeVertexBuffer(node);
     }
     else
     {
-        bool displacing = false;
-        if (xml_node)
-            xml_node->get("displacing", &displacing);
-
         m_node = irr_driver->addMesh(m_mesh, m_model_file, parent, m_render_info);
         Track *track = Track::getCurrentTrack();
         if (track && xml_node)
             track->handleAnimatedTextures(m_node, *xml_node);
+        Track::uploadNodeVertexBuffer(m_node);
     }
 
     if(!enabled)