// Project: XnaGraphicEngine, File: RenderToTexture.cs // Namespace: XnaGraphicEngine.Shaders, Class: RenderToTexture // Path: C:\code\XnaGraphicEngine\Shaders, Author: Abi // Code lines: 463, Size of file: 12,89 KB // Creation date: 12.09.2006 07:20 // Last modified: 22.10.2006 18:52 // Generated with Commenter by abi.exDream.com #region Using directives using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using System; using System.Collections; using System.Text; using XnaGraphicEngine.Graphics; using XnaGraphicEngine.Helpers; using XnaGraphicEngine.Game; using Texture = XnaGraphicEngine.Graphics.Texture; using Model = XnaGraphicEngine.Graphics.Model; using XnaTexture = Microsoft.Xna.Framework.Graphics.Texture2D; #endregion namespace XnaGraphicEngine.Shaders { /// /// Render to texture helper class based on the Texture class. /// This class allows to render stuff onto textures, if thats not /// supported, it will just not work and report an engine log message. /// This class is required for most PostScreenShaders. /// public class RenderToTexture : Texture { #region Variables /// /// Our render target we are going to render to. Much easier than in MDX /// where you have to use Surfaces, etc. Also supports the Xbox360 model /// of resolving the render target texture before we can use it, otherwise /// the RenderToTexture class would not work on the Xbox360. /// RenderTarget2D renderTarget = null; /// /// Z buffer surface for shadow mapping render targets that do not /// fit in our resolution. Usually unused! /// DepthStencilBuffer zBufferSurface = null; /// /// ZBuffer surface /// /// Surface public DepthStencilBuffer ZBufferSurface { get { return zBufferSurface; } // get } // ZBufferSurface /// /// Posible size types for creating a RenderToTexture object. /// public enum SizeType { /// /// Uses the full screen size for this texture /// FullScreen, /// /// Use full screen and force z buffer (sometimes needed for correct rendering). /// FullScreenWithZBuffer, /// /// Uses half the full screen size, e.g. 800x600 becomes 400x300 /// HalfScreen, /// /// Use half screen and force z buffer (sometimes needed for correct rendering). /// HalfScreenWithZBuffer, /// /// Uses a quarter of the full screen size, e.g. 800x600 becomes 200x150 /// QuarterScreen, /// /// Shadow map texture, usually 1024x1024, but can also be better /// like 2048x2048 or 4096x4096. /// ShadowMap, } // enum SizeTypes /// /// Size type /// private SizeType sizeType; /// /// Calc size /// private void CalcSize() { switch (sizeType) { case SizeType.FullScreen: case SizeType.FullScreenWithZBuffer: texWidth = BaseGame.Width; texHeight = BaseGame.Height; break; case SizeType.HalfScreen: case SizeType.HalfScreenWithZBuffer: texWidth = BaseGame.Width / 2; texHeight = BaseGame.Height / 2; break; case SizeType.QuarterScreen: texWidth = BaseGame.Width / 4; texHeight = BaseGame.Height / 4; break; case SizeType.ShadowMap: texWidth = 1024; texHeight = 1024; break; } // switch CalcHalfPixelSize(); } // CalcSize() /// /// Does this texture use some high percision format? Better than 8 bit color? /// private bool usesHighPercisionFormat = false; #endregion #region Properties /// /// Render target /// /// Render target 2D public RenderTarget2D RenderTarget { get { return renderTarget; } // get } // RenderTarget /// /// Override how to get XnaTexture, we have to resolve the render target /// for supporting the Xbox, which requires calling Resolve first! /// After that you can call this property to get the current texture. /// /// XnaTexture public override XnaTexture XnaTexture { get { if (alreadyResolved) internalXnaTexture = renderTarget.GetTexture(); return internalXnaTexture; } // get } // XnaTexture /// /// Does this texture use some high percision format? Better than 8 bit color? /// public bool UsesHighPercisionFormat { get { return usesHighPercisionFormat; } // get } // UsesHighPercisionFormat /// /// Is render target valid? Will be false if loading failed. /// public override bool Valid { get { return loaded && renderTarget != null; } // get } // Valid #endregion #region Constructors /// /// Id for each created RenderToTexture for the generated filename. /// private static int RenderToTextureGlobalInstanceId = 0; /// /// Creates an offscreen texture with the specified size which /// can be used for render to texture. /// public RenderToTexture(SizeType setSizeType) { sizeType = setSizeType; CalcSize(); texFilename = "RenderToTexture instance " + RenderToTextureGlobalInstanceId++; Create(); } // RenderToTexture(setSizeType) #endregion #region Create /// /// Check if we can use a specific surface format for render targets. /// /// /// private bool CheckRenderTargetFormat(SurfaceFormat format) { return BaseGame.Device.CreationParameters.Adapter.CheckDeviceFormat( BaseGame.Device.CreationParameters.DeviceType, BaseGame.Device.DisplayMode.Format, ResourceUsage.None, QueryUsages.None, ResourceType.RenderTarget, format); } // CheckRenderTargetFormat(format) /// /// Create /// private void Create() { /*obs #if XBOX360 // Limit to current resolution on Xbox360 (we won't create a depth // buffer below) if (texWidth > BaseGame.Width) texWidth = BaseGame.Width; if (texHeight > BaseGame.Height) texHeight = BaseGame.Height; #endif */ SurfaceFormat format = SurfaceFormat.Color; // Try to use R32F format for shadow mapping if possible (ps20), // else just use A8R8G8B8 format for shadow mapping and // for normal RenderToTextures too. if (sizeType == SizeType.ShadowMap && BaseGame.CanUsePS20) { // Can do R32F format? if (CheckRenderTargetFormat(SurfaceFormat.Single)) format = SurfaceFormat.Single; // Else try R16F format, thats still much better than A8R8G8B8 else if (CheckRenderTargetFormat(SurfaceFormat.HalfSingle)) format = SurfaceFormat.HalfSingle; // And check a couple more formats (mainly for the Xbox360 support) else if (CheckRenderTargetFormat(SurfaceFormat.HalfVector2)) format = SurfaceFormat.HalfVector2; else if (CheckRenderTargetFormat(SurfaceFormat.Luminance16)) format = SurfaceFormat.Luminance16; // Else nothing found, well, then just use the 8 bit Color format. /*obs #if XBOX360 // Try to force Surface format on the Xbox360, CheckRenderTargetFormat // does not work on the Xbox at all! format = SurfaceFormat.Single; #endif */ } // if (sizeType) try { // Create render target of specified size. renderTarget = new RenderTarget2D( BaseGame.Device, texWidth, texHeight, 1, format, BaseGame.MultiSampleType, BaseGame.MultiSampleQuality); if (format != SurfaceFormat.Color) usesHighPercisionFormat = true; // Unsupported on Xbox360, will crash with InvalidOperationException //obs: #if !XBOX360 // Create z buffer surface for shadow map render targets // if they don't fit in our current resolution. if (sizeType == SizeType.FullScreenWithZBuffer || sizeType == SizeType.HalfScreenWithZBuffer || sizeType == SizeType.ShadowMap && (texWidth > BaseGame.Width || texHeight > BaseGame.Height)) { zBufferSurface = new DepthStencilBuffer( BaseGame.Device, texWidth, texHeight, // Lets use the same stuff as the back buffer. BaseGame.BackBufferDepthFormat, // Don't use multisampling, render target does not support that. //obs: MultiSampleType.None, 0); BaseGame.MultiSampleType, BaseGame.MultiSampleQuality); } // if //#endif loaded = true; } // try catch (Exception ex) { // Everything failed, make this unuseable. Log.Write("Creating RenderToTexture failed: " + ex.ToString()); renderTarget = null; internalXnaTexture = null; loaded = false; } // catch } // Create() #endregion #region Dispose /// /// Dispose /// public override void Dispose() { base.Dispose(); if (renderTarget != null) renderTarget.Dispose(); renderTarget = null; loaded = false; } // Dispose() #endregion #region Load /// /// Load in case device got lost. /// public override void Load() { // Just call Create! if (renderTarget == null) Create(); } // Load() #endregion #region Clear /// /// Clear render target (call SetRenderTarget first) /// public void Clear(Color clearColor) { if (loaded == false || renderTarget == null) return; BaseGame.Device.Clear( ClearOptions.Target | ClearOptions.DepthBuffer, clearColor, 1.0f, 0); } // Clear(clearColor) #endregion #region Set render target /// /// Set render target to this texture to render stuff on it. /// public bool SetRenderTarget() { if (loaded == false || renderTarget == null) return false; BaseGame.SetRenderTarget(renderTarget, false); return true; } // SetRenderTarget() #endregion #region Resolve /// /// Make sure we don't call XnaTexture before resolving for the first time! /// bool alreadyResolved = false; /// /// Resolve render target. For windows developers this method may seem /// strange, why not just use the rendertarget's texture? Well, this is /// just for the Xbox360 support. The Xbox requires that you call Resolve /// first before using the rendertarget texture. The reason for that is /// copying the data over from the EPRAM to the video memory, for more /// details read the XNA docs. /// Note: This method will only work if the render target was set before /// with SetRenderTarget, else an exception will be thrown to ensure /// correct calling order. /// public void Resolve() { // Make sure this render target is currently set! if (BaseGame.CurrentRenderTarget != renderTarget) throw new Exception( "You can't call Resolve without first setting the render target!"); alreadyResolved = true; BaseGame.Device.ResolveRenderTarget(0); } // Resolve() #endregion #region Unit Testing #if DEBUG /// /// Test create render to texture /// static public void TestCreateRenderToTexture() { Model testModel = null; RenderToTexture renderToTexture = null; TestGame.Start( "TestCreateRenderToTexture", delegate { testModel = new Model("Building"); renderToTexture = new RenderToTexture( //SizeType.QuarterScreen); SizeType.HalfScreen); //SizeType.FullScreen); //SizeType.ShadowMap); }, delegate { bool renderToTextureWay = Input.Keyboard.IsKeyUp(Keys.Space) && Input.GamePadAPressed == false; if (renderToTextureWay) { // Set render target to our texture renderToTexture.SetRenderTarget(); // Clear background renderToTexture.Clear(Color.Blue); // Draw background lines BaseGame.DrawLine(new Point(0, 200), new Point(200, 0), Color.Blue); BaseGame.DrawLine(new Point(0, 0), new Point(400, 400), Color.Red); BaseGame.FlushLineManager2D(); // And draw object testModel.Render(Matrix.CreateScale(1.5f)); BaseGame.MeshRenderManager.Render(); // Do we need to resolve? renderToTexture.Resolve(); //BaseGame.Device.ResolveRenderTarget(0); // Reset background buffer BaseGame.ResetRenderTarget(true); } // if (renderToTextureWay) else { // Copy backbuffer way, render stuff normally first // Clear background BaseGame.Device.Clear(Color.Blue); // Draw background lines BaseGame.DrawLine(new Point(0, 200), new Point(200, 0), Color.Blue); BaseGame.DrawLine(new Point(0, 0), new Point(400, 400), Color.Red); BaseGame.FlushLineManager2D(); // And draw object testModel.Render(Matrix.CreateScale(1.5f)); BaseGame.MeshRenderManager.Render(); } // else // Show render target in a rectangle on our screen renderToTexture.RenderOnScreen( //tst: new Rectangle(100, 100, 256, 256)); //BaseGame.ResolutionRect); //no need: BaseGame.UI.FlushUI(); TextureFont.WriteText(2, 30, "renderToTexture.Width=" + renderToTexture.Width); TextureFont.WriteText(2, 60, "renderToTexture.Height=" + renderToTexture.Height); TextureFont.WriteText(2, 90, "renderToTexture.Valid=" + renderToTexture.Valid); TextureFont.WriteText(2, 120, "renderToTexture.XnaTexture=" + renderToTexture.XnaTexture); TextureFont.WriteText(2, 150, "renderToTexture.ZBufferSurface=" + renderToTexture.ZBufferSurface); TextureFont.WriteText(2, 180, "renderToTexture.Filename=" + renderToTexture.Filename); }); } // TestCreateRenderToTexture() #endif #endregion } // class RenderToTexture } // namespace XnaGraphicEngine.Shaders