/*
* Shader.cs
* Authors: August Zinsser
*
* Copyright August Zinsser 2007
* This program is distributed under the terms of the GNU General Public License
*/
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Reflection;
namespace Pina3D
{
///
/// Holds parameters and makes calls to .fx files for vertex and pixel shaders. To use a shader, call the GetPasses() function
/// for a specific shader and then perform rendering between pass.Begin(...) and pass.end(...) flags.
///
public class Shader : IDisposable
{
///
/// An enum for supported techniques for rendering static meshes
///
public class StaticTechniques
{
public const string Blank = "Blank";
public const string TexturedLambert = "TexturedLambert";
public const string DiffuseNormal = "DiffuseNormal";
public const string MultiTexturedLambert = "MultiTexturedLambert";
}
protected string mTechniqueName; // Name of the technique to invoke from the .fx file
protected Effect mXNAEffect; // Internal XNA Effect Shader
protected Material mMaterial; // A reference to the material that holds many shader properties (like color, textures)
public List EffectPasses { get { return GenerateEffectPasses(); } }
///
/// Create a 3D shader from a given material.
///
/// A reference to a material which holds the surface properties to use in shaders
public Shader(ref Material materialRef)
{
// Save the handle to the material
mMaterial = materialRef;
// Assume the "Blank" technique unless changed later
mTechniqueName = StaticTechniques.Blank;
// Find the most appropriate XNAEffect
PickEffect();
}
///
/// Starts the active technique
///
public void Begin()
{
mXNAEffect.Begin();
}
///
/// Ends the active technique
///
public void End()
{
mXNAEffect.End();
}
///
/// Dispose the internal effect
///
public virtual void Dispose()
{
if (mXNAEffect != null)
mXNAEffect.Dispose();
}
///
/// Loads and compiles an fx file embedded in this assembly.
///
/// Name of the file (with extension), assumed to be in the Shaders folder.
///
public static Effect LoadEmbeddedFXFile(string fileName)
{
string correctFile = "Pina3D.Shaders." + fileName;
// Load the .fx files from this assembly's metadata
Stream fxStream = null;
Assembly assembly = Assembly.GetExecutingAssembly();
string[] resourceNames = assembly.GetManifestResourceNames();
foreach (string resourceName in resourceNames)
{
if (resourceName.ToLower() == correctFile.ToLower())
{
// Attach the stream and extract the fx file
fxStream = assembly.GetManifestResourceStream(resourceName);
if (fxStream != null)
{
// Compile the fx code
CompiledEffect compiledEffect = Effect.CompileEffectFromFile(fxStream, null, null, CompilerOptions.None, TargetPlatform.Windows);
if (!compiledEffect.Success)
{
string errors = "The fx file " + resourceName + " contains the following errors and cannot be compiled: \n";
errors += compiledEffect.ErrorsAndWarnings + "\n";
throw new InvalidProgramException(errors);
}
// TODO: Figure out what an effect pool is
EffectPool effectPool = new EffectPool();
return new Effect(Pina.Graphics.GraphicsDevice, compiledEffect.GetEffectCode(), CompilerOptions.None, effectPool);
}
}
}
throw new FileLoadException("Could not load the embedded fx file " + correctFile);
}
///
/// Assigns mXNAEffect based on the other variables in this instance
///
private void PickEffect()
{
// Select the appropriate fx file, first by attempting to match the technique name
string correctFile = "";
switch (mTechniqueName)
{
case StaticTechniques.TexturedLambert:
case StaticTechniques.MultiTexturedLambert:
correctFile = "StaticMesh.fx";
break;
}
if (correctFile == "")
{
// Now examine the reference Material
if (mMaterial != null)
{
correctFile = "StaticMesh.fx";
}
}
// TODO: load the files once, don't reload and recompile it every time a Shader generates effect passes!
mXNAEffect = LoadEmbeddedFXFile(correctFile);
}
///
/// (Re)assigns all the EffectParameters based on the information contained in the reference Material or data internal to this Shader and then
/// generates the appropriate EffectPass passes.
///
///
private List GenerateEffectPasses()
{
// Remember which properties are supported
bool difMap = mMaterial.UseDiffuseMap;
bool norMap = mMaterial.UseNormalMap;
bool multiMap = mMaterial.UseMultipleDiffuseMaps;
// Query global scene parameters
mXNAEffect.Parameters["xWorldMat"].SetValue(Pina.WorldMatrix);
mXNAEffect.Parameters["xViewProjMat"].SetValue(Pina.ViewProjectionMatrix);
mXNAEffect.Parameters["xLightDir"].SetValue(Pina.GlobalLightDirection);
mXNAEffect.Parameters["xLightPower"].SetValue(Pina.GlobalLightPower);
mXNAEffect.Parameters["xLightAmbient"].SetValue(Pina.GlobalLightAmbient);
mXNAEffect.Parameters["xLightColor"].SetValue(Pina.GlobalLightColor.ToVector4());
// Assign the colors present in the reference material
mXNAEffect.Parameters["xAmbientColor"].SetValue(mMaterial.AmbientColor.ToVector4());
mXNAEffect.Parameters["xDiffuseColor"].SetValue(mMaterial.DiffuseColor.ToVector4());
// Assign the effect parameters present in the reference material
if (difMap)
mXNAEffect.Parameters["xDiffuseMap"].SetValue(mMaterial.DiffuseMap);
if (norMap)
mXNAEffect.Parameters["xNormalMap"].SetValue(mMaterial.NormalMap);
if (multiMap)
{
mXNAEffect.Parameters["xDiffuseMap1"].SetValue(mMaterial.DiffuseMap1 != null ? mMaterial.DiffuseMap1 : mMaterial.DiffuseMap);
mXNAEffect.Parameters["xDiffuseMap2"].SetValue(mMaterial.DiffuseMap2 != null ? mMaterial.DiffuseMap2 : mMaterial.DiffuseMap);
mXNAEffect.Parameters["xDiffuseMap3"].SetValue(mMaterial.DiffuseMap3 != null ? mMaterial.DiffuseMap3 : mMaterial.DiffuseMap);
}
// Select a technique based on the parameters in use
if (difMap && !norMap && !multiMap)
{
mTechniqueName = StaticTechniques.TexturedLambert;
}
else if (difMap && norMap && !multiMap)
{
mTechniqueName = StaticTechniques.DiffuseNormal;
}
else if (multiMap && !norMap)
{
// TODO (awz): This requires ps2 provide legacy support for ps1
mTechniqueName = StaticTechniques.MultiTexturedLambert;
}
else
{
// Not a valid configuration, so just render it blank
mTechniqueName = StaticTechniques.Blank;
System.Diagnostics.Debug.Assert(false, "Unsupported material options!\n" + mMaterial.UsageSettings);
}
// Update the effect
mXNAEffect.CurrentTechnique = mXNAEffect.Techniques[mTechniqueName];
mXNAEffect.CommitChanges();
// Get the passes
List retVal = new List();
for (int i = 0; i < mXNAEffect.CurrentTechnique.Passes.Count; i++)
retVal.Add(mXNAEffect.CurrentTechnique.Passes[i]);
// Return said passes
return retVal;
}
}
}