Index: source/graphics/Material.h
===================================================================
--- source/graphics/Material.h	(revision 11947)
+++ source/graphics/Material.h	(working copy)
@@ -28,6 +28,17 @@
 class CMaterial
 {
 public:
+	
+	struct TextureSampler
+	{
+		TextureSampler(CStr &n, CTexturePtr t) : Name(n), Sampler(t) {}
+		
+		CStrIntern Name;
+		CTexturePtr Sampler;
+	};
+	
+	typedef std::vector<TextureSampler> SamplersVector;
+	
 	CMaterial();
 
 	// Whether this material's shaders use alpha blending, in which case
@@ -36,7 +47,6 @@
 	void SetUsesAlphaBlending(bool flag) { m_AlphaBlending = flag; }
  	bool UsesAlphaBlending() { return m_AlphaBlending; }
 
-	void SetDiffuseTexture(const CTexturePtr& texture);
 	const CTexturePtr& GetDiffuseTexture() const { return m_DiffuseTexture; }
 
 	void SetShaderEffect(const CStr& effect);
@@ -48,8 +58,17 @@
 	void AddStaticUniform(const char* key, const CVector4D& value);
 	const CShaderUniforms& GetStaticUniforms() const { return m_StaticUniforms; }
 
+	void AddSampler(const TextureSampler& texture);
+	const SamplersVector& GetSamplers() const { return m_Samplers; }
+
 private:
+	
+	// This pointer is kept to make it easier for the fixed pipeline to 
+	// access the only texture it's interested in.
 	CTexturePtr m_DiffuseTexture;
+	
+	SamplersVector m_Samplers;
+	
 	CStrIntern m_ShaderEffect;
 	CShaderDefines m_ShaderDefines;
 	CShaderUniforms m_StaticUniforms;
Index: source/graphics/ObjectEntry.h
===================================================================
--- source/graphics/ObjectEntry.h	(revision 11947)
+++ source/graphics/ObjectEntry.h	(working copy)
@@ -32,6 +32,8 @@
 #include "ps/CStr.h"
 #include "ps/Overlay.h"
 
+#include "graphics/ObjectBase.h"
+
 class CObjectEntry
 {
 public:
@@ -46,7 +48,8 @@
 	// different variations of the actor.
 	CObjectBase* m_Base;
 
-	VfsPath m_TextureName;
+	// samplers list
+	std::vector<CObjectBase::Samp> m_Samplers;
 	// model name
 	VfsPath m_ModelName;
 	// colour (used when doing alpha-channel colouring, but not doing player-colour)
Index: source/graphics/ObjectBase.h
===================================================================
--- source/graphics/ObjectBase.h	(revision 11947)
+++ source/graphics/ObjectBase.h	(working copy)
@@ -60,6 +60,14 @@
 		// name of the model file - art/actors/props/sword.xml or whatever
 		CStrW m_ModelName;
 	};
+	
+	struct Samp
+	{
+		// identifier name of sampler in GLSL shaders
+		CStr m_SamplerName;
+		// path to load from
+		VfsPath m_SamplerFile;
+	};
 
 	struct Decal
 	{
@@ -79,24 +87,24 @@
 		CStr m_VariantName; // lowercase name
 		int m_Frequency;
 		VfsPath m_ModelFilename;
-		VfsPath m_TextureFilename;
 		Decal m_Decal;
 		VfsPath m_Particles;
 		CStr m_Color;
 
 		std::vector<Anim> m_Anims;
 		std::vector<Prop> m_Props;
+		std::vector<Samp> m_Samplers;
 	};
 
 	struct Variation
 	{
-		VfsPath texture;
 		VfsPath model;
 		Decal decal;
 		VfsPath particles;
 		CStr color;
 		std::multimap<CStr, Prop> props;
 		std::multimap<CStr, Anim> anims;
+		std::multimap<CStr, Samp> samplers;
 	};
 
 	CObjectBase(CObjectManager& objectManager);
Index: source/graphics/Material.cpp
===================================================================
--- source/graphics/Material.cpp	(revision 11947)
+++ source/graphics/Material.cpp	(working copy)
@@ -27,11 +27,6 @@
 {
 }
 
-void CMaterial::SetDiffuseTexture(const CTexturePtr& texture)
-{
-	m_DiffuseTexture = texture;
-}
-
 void CMaterial::SetShaderEffect(const CStr& effect)
 {
 	m_ShaderEffect = CStrIntern(effect);
@@ -46,3 +41,10 @@
 {
 	m_StaticUniforms.Add(key, value);
 }
+
+void CMaterial::AddSampler(const TextureSampler& texture)
+{
+	m_Samplers.push_back(texture);
+	if (texture.Name == CStrIntern("baseTex"))
+		m_DiffuseTexture = texture.Sampler;
+}
\ No newline at end of file
Index: source/graphics/ObjectBase.cpp
===================================================================
--- source/graphics/ObjectBase.cpp	(revision 11947)
+++ source/graphics/ObjectBase.cpp	(working copy)
@@ -62,6 +62,7 @@
 	EL(prop);
 	EL(mesh);
 	EL(texture);
+	EL(textures);
 	EL(colour);
 	EL(decal);
 	EL(particles);
@@ -147,9 +148,22 @@
 					{
 						currentVariant->m_ModelFilename = VfsPath("art/meshes") / option.GetText().FromUTF8();
 					}
-					else if (option_name == el_texture)
+					else if (option_name == el_textures)
 					{
-						currentVariant->m_TextureFilename = VfsPath("art/textures/skins") / option.GetText().FromUTF8();
+						XERO_ITER_EL(option, textures_element)
+						{
+							ENSURE(textures_element.GetNodeName() == el_texture);
+							
+							Samp samp;
+							XERO_ITER_ATTR(textures_element, se)
+							{
+								if (se.Name == at_file)
+									samp.m_SamplerFile = VfsPath("art/textures/skins") / se.Value.FromUTF8();
+								else if (se.Name == at_name)
+									samp.m_SamplerName = se.Value;
+							}
+							currentVariant->m_Samplers.push_back(samp);
+						}
 					}
 					else if (option_name == el_decal)
 					{
@@ -396,9 +410,6 @@
 
 		// Apply its data:
 
-		if (! var.m_TextureFilename.empty())
-			variation.texture = var.m_TextureFilename;
-
 		if (! var.m_ModelFilename.empty())
 			variation.model = var.m_ModelFilename;
 
@@ -430,6 +441,12 @@
 		// and then insert the new ones:
 		for (std::vector<CObjectBase::Anim>::iterator it = var.m_Anims.begin(); it != var.m_Anims.end(); ++it)
 			variation.anims.insert(make_pair(it->m_AnimName, *it));
+		
+		// Same for samplers, though perhaps not strictly necessary:
+		for (std::vector<CObjectBase::Samp>::iterator it = var.m_Samplers.begin(); it != var.m_Samplers.end(); ++it)
+			variation.samplers.erase(it->m_SamplerName);
+		for (std::vector<CObjectBase::Samp>::iterator it = var.m_Samplers.begin(); it != var.m_Samplers.end(); ++it)
+			variation.samplers.insert(make_pair(it->m_SamplerName, *it));
 	}
 
 	return variation;
Index: source/graphics/ObjectEntry.cpp
===================================================================
--- source/graphics/ObjectEntry.cpp	(revision 11947)
+++ source/graphics/ObjectEntry.cpp	(working copy)
@@ -60,7 +60,10 @@
 
 	// Copy the chosen data onto this model:
 
-	m_TextureName = variation.texture;
+	//m_TextureName = variation.texture;
+	for (std::multimap<CStr, CObjectBase::Samp>::iterator it = variation.samplers.begin(); it != variation.samplers.end(); ++it)
+		m_Samplers.push_back(it->second);
+	
 	m_ModelName = variation.model;
 
 	if (! variation.color.empty())
@@ -76,8 +79,18 @@
 
 	if (variation.decal.m_SizeX && variation.decal.m_SizeZ)
 	{
-		CTextureProperties textureProps(m_TextureName);
+		std::multimap<CStr, CObjectBase::Samp>::iterator iter;
+		
+		iter = variation.samplers.find("baseTex");
 
+		if (iter == variation.samplers.end())
+		{
+			LOGERROR(L"Actor '%ls' tries to create a decal but has no 'baseTex' sampler entry", m_Base->m_ShortName.c_str());
+			return false;
+		}
+
+		CTextureProperties textureProps(iter->second.m_SamplerFile);
+
 		// Decals should be transparent, so clamp to the border (default 0,0,0,0)
 		textureProps.SetWrap(GL_CLAMP_TO_BORDER);
 
@@ -121,13 +134,20 @@
 	model->SetMaterial(g_Renderer.GetMaterialManager().LoadMaterial(m_Base->m_Material));
 	model->GetMaterial().AddStaticUniform("objectColor", CVector4D(m_Color.r, m_Color.g, m_Color.b, m_Color.a));
 	model->InitModel(modeldef);
+	
+	std::vector<CObjectBase::Samp>::iterator samp;
+	for (samp = m_Samplers.begin(); samp != m_Samplers.end(); ++samp)
+	{
+		CTextureProperties textureProps(samp->m_SamplerFile);
+		textureProps.SetWrap(GL_CLAMP_TO_EDGE);
+		CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
+		// if we've loaded this model we're probably going to render it soon, so prefetch its texture. 
+		// All textures are prefetched even in the fixed pipeline, including the normal maps etc.
+		// TODO: Should check which renderpath is selected and only preload the necessary textures.
+		texture->Prefetch(); 
+		model->GetMaterial().AddSampler(CMaterial::TextureSampler(samp->m_SamplerName, texture));
+	}
 
-	CTextureProperties textureProps(m_TextureName);
-	textureProps.SetWrap(GL_CLAMP_TO_EDGE);
-	CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
-	texture->Prefetch(); // if we've loaded this model we're probably going to render it soon, so prefetch its texture
-	model->GetMaterial().SetDiffuseTexture(texture);
-
 	// calculate initial object space bounds, based on vertex positions
 	model->CalcStaticObjectBounds();
 
Index: source/renderer/RenderModifiers.h
===================================================================
--- source/renderer/RenderModifiers.h	(revision 11947)
+++ source/renderer/RenderModifiers.h	(working copy)
@@ -63,17 +63,6 @@
 	virtual void BeginPass(const CShaderProgramPtr& shader) = 0;
 
 	/**
-	 * PrepareTexture: Called before rendering models that use the given
-	 * texture.
-	 *
-	 * Must be implemented by derived classes.
-	 *
-	 * @param pass The current pass number (pass == 0 is the first pass)
-	 * @param texture The texture used by subsequent models
-	 */
-	virtual void PrepareTexture(const CShaderProgramPtr& shader, CTexture& texture) = 0;
-
-	/**
 	 * PrepareModel: Called before rendering the given model.
 	 *
 	 * Default behaviour does nothing.
@@ -133,14 +122,12 @@
 
 	// Implementation
 	void BeginPass(const CShaderProgramPtr& shader);
-	void PrepareTexture(const CShaderProgramPtr& shader, CTexture& texture);
 	void PrepareModel(const CShaderProgramPtr& shader, CModel* model);
 
 private:
 	CShaderProgram::Binding m_BindingInstancingTransform;
 	CShaderProgram::Binding m_BindingShadingColor;
 	CShaderProgram::Binding m_BindingPlayerColor;
-	CShaderProgram::Binding m_BindingBaseTex;
 };
 
 #endif // INCLUDED_RENDERMODIFIERS
Index: source/renderer/ModelRenderer.cpp
===================================================================
--- source/renderer/ModelRenderer.cpp	(revision 11947)
+++ source/renderer/ModelRenderer.cpp	(working copy)
@@ -26,6 +26,7 @@
 
 #include "graphics/Color.h"
 #include "graphics/LightEnv.h"
+#include "graphics/Material.h"
 #include "graphics/Model.h"
 #include "graphics/ModelDef.h"
 #include "graphics/ShaderManager.h"
@@ -467,6 +468,9 @@
 			else
 			{
 				// Sort model list by modeldef+texture, for batching
+				// TODO: This only sorts by base texture. While this is an OK approximation
+				// for most cases (as related samplers are usually used together), it would be better
+				// to take all the samplers into account when sorting here.
 				std::sort(it->second.begin(), it->second.end(), SMRBatchModel());
 
 				// Add a tech bucket pointing at this model list
@@ -531,6 +535,20 @@
 		PROFILE3("rendering bucketed submissions");
 
 		size_t idxTechStart = 0;
+		
+		// This vector keeps track of texture changes during rendering. It is kept outside the
+		// loops to avoid excessive reallocations. The token allocation of 64 elements 
+		// should be plenty, though it is reallocated below (at a cost) if necessary.
+		std::vector<CTexture*> currentTexs;
+		currentTexs.reserve(64);
+		
+		// texBindings holds the identifier bindings in the shader, which can no longer be defined 
+		// statically in the ShaderRenderModifier class. texBindingNames uses interned strings to
+		// keep track of when bindings need to be reevaluated.
+		std::vector<CShaderProgram::Binding> texBindings;
+		texBindings.reserve(64);
+		std::vector<CStrIntern> texBindingNames;
+		texBindingNames.reserve(64);
 
 		while (idxTechStart < techBuckets.size())
 		{
@@ -555,12 +573,16 @@
 				modifier->BeginPass(shader);
 
 				m->vertexRenderer->BeginPass(streamflags);
-
-				CTexture* currentTex = NULL;
+				
+				// When the shader technique changes, textures need to be
+				// rebound, so ensure there are no remnants from the last pass.
+				// (the vector size is set to 0, but memory is not freed)
+				currentTexs.clear();
+				texBindings.clear();
+				texBindingNames.clear();
+				
 				CModelDef* currentModeldef = NULL;
 				CShaderUniforms currentStaticUniforms;
-				// (Texture needs to be rebound after binding a new shader, so we
-				// can't move currentTex outside of this loop to reduce state changes)
 
 				for (size_t idx = idxTechStart; idx < idxTechEnd; ++idx)
 				{
@@ -573,14 +595,46 @@
 						if (flags && !(model->GetFlags() & flags))
 							continue;
 
-						// Bind texture when it changes
-						CTexture* newTex = model->GetMaterial().GetDiffuseTexture().get();
-						if (newTex != currentTex)
+						CMaterial::SamplersVector samplers = model->GetMaterial().GetSamplers();
+						size_t samplersNum = samplers.size();
+						
+						// make sure the vectors are the right virtual sizes, and also
+						// reallocate if there are more samplers than expected.
+						if (currentTexs.size() != samplersNum)
 						{
-							currentTex = newTex;
-							modifier->PrepareTexture(shader, *currentTex);
+							currentTexs.resize(samplersNum, NULL);
+							texBindings.resize(samplersNum, CShaderProgram::Binding());
+							texBindingNames.resize(samplersNum, CStrIntern());
 						}
+						
+						// bind the samplers to the shader
+						for (size_t s = 0; s < samplersNum; ++s)
+						{
+							CMaterial::TextureSampler &samp = samplers[s];
+							
+							CShaderProgram::Binding bind = texBindings[s];
+							// check that the handles are current
+							// and reevaluate them if necessary
+							if (texBindingNames[s] == samp.Name && bind.Active())
+							{
+								bind = texBindings[s];
+							}
+							else
+							{
+								bind = shader->GetTextureBinding(samp.Name.c_str());		
+								texBindings[s] = bind;
+								texBindingNames[s] = samp.Name;
+							}
 
+							// same with the actual sampler bindings
+							CTexture* newTex = samp.Sampler.get();
+							if (bind.Active() && newTex != currentTexs[s])
+							{
+								shader->BindTexture(bind, samp.Sampler->GetHandle());
+								currentTexs[s] = newTex;
+							}
+						}
+
 						// Bind modeldef when it changes
 						CModelDef* newModeldef = model->GetModelDef().get();
 						if (newModeldef != currentModeldef)
Index: source/renderer/RenderModifiers.cpp
===================================================================
--- source/renderer/RenderModifiers.cpp	(revision 11947)
+++ source/renderer/RenderModifiers.cpp	(working copy)
@@ -103,15 +103,8 @@
 	m_BindingInstancingTransform = shader->GetUniformBinding("instancingTransform");
 	m_BindingShadingColor = shader->GetUniformBinding("shadingColor");
 	m_BindingPlayerColor = shader->GetUniformBinding("playerColor");
-	m_BindingBaseTex = shader->GetTextureBinding("baseTex");
 }
 
-void ShaderRenderModifier::PrepareTexture(const CShaderProgramPtr& shader, CTexture& texture)
-{
-	if (m_BindingBaseTex.Active())
-		shader->BindTexture(m_BindingBaseTex, texture.GetHandle());
-}
-
 void ShaderRenderModifier::PrepareModel(const CShaderProgramPtr& shader, CModel* model)
 {
 	if (m_BindingInstancingTransform.Active())
