Offscreen Surfaces and Screenshots

  CU_MDX_OffscreenSurfaces.zip (119.2 KiB, 2,588 hits)


  CUnitFramework.zip (101.1 KiB, 20,443 hits)

Extract the C-Unit Framework
inside the project (not solution) directory.

This tutorial uses the October 2005 SDK version. Microsoft changed functionality after this version and I never got around to updating the code.

Surfaces are basically containers that hold image data. Backbuffers are surfaces and each mip level of a texture is a surface. We can use Surfaces to modify existing image data, such as Texture mip levels if the existing mip level is not detailed enough. Off-screen surfaces are useful because they do not have the same size restrictions that Textures have. Surfaces allow for the display of images that have larger dimensions than the video card’s supported maximum Texture size.

Surface

Off-screen surfaces are useful when we want to display large images such as a title or logo image or a 2d backgound image. If we were to make, for example Tetris or a 2d fighting game, off-screen surfaces would be a good way to display the background. Since off-screen surfaces are usually stored in system memory, they do not take up valuable video memory so we can store whatever size image we want.

Since surfaces are just containers for image data, we can also use them to take screenshots as we’ll see later on.

///
/// This event will be fired immediately after the Direct3D device has been
/// reset, which will happen after a lost device scenario, a window resize, and a
/// fullscreen toggle. This is the best location to create_ Pool.Default resources
/// since these resources need to be reloaded whenever the device is reset. Resources
/// created here should be released in the OnLostDevice callback.
///

/// The Direct3D device
private void OnResetDevice( Device device )
{
   // Clip…
           
   // Create surface
   m_imageInfo = TextureLoader.ImageInformationFromFile( Utility.GetMediaFile( “cunit.jpg” ) );
   if ( m_surface != null )
   {
       m_surface.Dispose();
   }
   m_surface = device.CreateOffscreenPlainSurface( m_imageInfo.Width, m_imageInfo.Height,
       m_framework.CurrentSettings.AdapterFormat, Pool.Default );
   SurfaceLoader.FromFile( m_surface, Utility.GetMediaFile( “cunit.jpg” ), Filter.None, 0 );

   // Clip…
}

We create_ a surface by calling Device.CreateOffscreenPlainSurface. Since we want to create_ a Surface with the same dimensions as the image, we get the dimensions of the image by calling TextureLoader.ImageInformationFromFile, which stores the dimensions in a ImageInformation structure. Once the Surface is created, we load in the image data with SurfaceLoader.FromFile.

///
/// Renders the current frame.
///

/// The Direct3D device
/// Time elapsed since last frame
private void RenderFrame( Device device, float elapsedTime )
{
   // Clip…

   device.Clear( ClearFlags.Target | ClearFlags.ZBuffer, Color.Black, 1.0f, 0 );
   device.BeginScene();

   Surface backbuffer = device.GetBackBuffer( 0, 0, BackBufferType.Mono );
   device.StretchRectangle( m_surface, new Rectangle( 0, 0, m_imageInfo.Width, m_imageInfo.Height ),
       backbuffer, new Rectangle( 0, 0, BackBufferWidth, BackBufferHeight ),
       TextureFilter.None );
   backbuffer.Dispose();

   // Clip…

   device.EndScene();
   device.Present();
}

To display our Surface to the screen, we have to copy its image data into the backbuffer. First, we get the backbuffer Surface with Device.GetBackBuffer. Then, we copy over our image data into the backbuffer surface by calling Device.StretchRectangle. Note however that although MSDN says we can put null for the Rectangle parameters to use the entire Surface, I was getting an error when I put either null or Rectangle.Empty. As a result, I use the actual dimensions of my source and destination Surfaces.

/// Takes a screen shot
public void TakeScreenShot()
{
string fileName = DateTime.Now.ToString( “yyyyMMdd” ) + “_” + DateTime.Now.TimeOfDay.ToString();
// Remove colons and junk
fileName = fileName.Remove( 11, 1 );
fileName = fileName.Remove( 13, 1 );
fileName = fileName.Remove( 15, 1 );
Surface backbuffer = Device.GetBackBuffer( 0, 0, BackBufferType.Mono );
SurfaceLoader.Save( fileName + “.tga”, ImageFileFormat.Tga, backbuffer );
backbuffer.Dispose();
}

Surfaces are not only useful for loading image data, they can also be used to save out image data, a.k.a. take a screenshot. All we do to save a screen shot is get the backbuffer surface with Device.GetBackBuffer and then save it to a file with SurfaceLoader.Save. All the string manipulation code creates the filename based on the current time so our saved screenshot does not overwrite any previously saved screen shot.

///
/// This function will be called after the Direct3D device has
/// entered a lost state and before Device.Reset() is called. Resources created
/// in the OnResetDevice callback should be released here, which generally includes all
/// Pool.Default resources.
///

private void OnLostDevice()
{
if ( m_surface != null )
{
m_surface.Dispose();
m_surface = null;
}
// Clip…
}

Don’t forget to Dispose your resources!