Direct3D Rendering Cookbook

4.3 (3 reviews total)
By Justin Stenning
  • Instant online access to over 8,000+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Getting Started with Direct3D

About this book

The latest 3D graphics cards bring us amazing visuals in the latest games, from Indie to AAA titles. This is made possible on Microsoft® platforms including PC, Xbox consoles, and mobile devices thanks to Direct3D – a component of the DirectX API dedicated to exposing 3D graphics hardware to programmers. Microsoft DirectX is the graphics technology powering all of today's hottest games. The latest version— DirectX 11—features tessellation for film-like geometric detail, compute shaders for custom graphics effects, and improved multithreading for better hardware utilization. With it comes a number of fundamental game changing improvements to the way in which we render 3D graphics.

Direct3D Rendering Cookbook provides detailed .NET examples covering a wide range of advanced 3D rendering techniques available in Direct3D 11.2. With this book, you will learn how to use the new Visual Studio 2012 graphics content pipeline, how to perform character animation, how to use advanced hardware tessellation techniques, how to implement displacement mapping, perform image post-processing, and how to use compute shaders for general-purpose computing on GPUs.

After covering a few introductory topics about Direct3D 11.2 and working with the API using C# and SharpDX, we quickly ramp up to the implementation of a range of advanced rendering techniques, building upon the projects we create and the skills we learn in each subsequent chapter. Topics covered include using the new Visual Studio 2012 graphics content pipeline and graphics debugger, texture sampling, normal mapping, lighting and materials, loading meshes, character animation (vertex skinning), hardware tessellation, displacement mapping, using compute shaders for post-process effects, deferred rendering, and finally bringing all of this to Windows Store Apps for PC and mobile. After completing the recipes within Direct3D Rendering Cookbook, you will have an in-depth understanding of a range of advanced Direct3D rendering topics.

Publication date:
January 2014
Publisher
Packt
Pages
430
ISBN
9781849697101

 

Chapter 1. Getting Started with Direct3D

In this chapter, we will cover the following topics:

  • Components of Direct3D

  • Stages of the programmable pipeline

  • Introducing Direct3D 11.1 and 11.2

  • Building a Direct3D 11 application with C# and SharpDX

  • Initializing a Direct3D 11.1/11.2 device and swap chain

  • Debugging your Direct3D application

 

Introduction


Direct3D is the component of the DirectX API dedicated to exposing 3D graphics hardware to programmers on Microsoft platforms including PC, console, and mobile devices. It is a native API allowing you to create not only 3D graphics for games, scientific and general applications, but also to utilize the underlying hardware for General-purpose computing on graphics processing units (GPGPU).

Programming with Direct3D can be a daunting task, and although the differences between the unmanaged C++ API and the managed .NET SharpDX API (from now on referred to as the unmanaged and managed APIs respectively) are subtle, we will briefly highlight some of these while also gaining an understanding of the graphics pipeline.

We will then learn how to get started with programming for Direct3D using C# and SharpDX along with some useful debugging techniques.

Components of Direct3D

Direct3D is a part of the larger DirectX API comprised of many components that sits between applications and the graphics hardware drivers. Everything in Direct3D begins with the device and you create resources and interact with the graphics pipeline through various Component Object Model (COM) interfaces from there.

Device

The main role of the device is to enumerate the capabilities of the display adapter(s) and to create resources. Applications will typically only have a single device instantiated and must have at least one device to use the features of Direct3D.

Unlike previous versions of Direct3D, in Direct3D 11 the device is thread-safe. This means that resources can be created from any thread.

The device is accessed through the following interfaces/classes:

  • Managed: Direct3D11.Device (Direct3D 11), Direct3D11.Device1 (Direct3D 11.1), and Direct3D11.Device2 (Direct3D 11.2)

  • Unmanaged: ID3D11Device, ID3D11Device1, and ID3D11Device2

    Note

    Each subsequent version of the COM interface descends from the previous version; therefore, if you start with a Direct3D 11 device instance and query the interface for the Direct3D 11.2 implementation, you will still have access to the Direct3D 11 methods with the resulting device reference.

One important difference between the unmanaged and managed version of the APIs used throughout this book is that when creating resources on a device with the managed API, the appropriate class constructor is used with the first parameter passed in being a device instance, whereas the unmanaged API uses a Create method on the device interface.

For example, creating a new blend state would look like the following for the managed C# API:

var blendState = new BlendState(device, desc);

And like this for the unmanaged C++ API:

ID3D11BlendState* blendState;
HRESULT r = device->CreateBlendState(&desc, &blendState);

Tip

Downloading the example code

You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.

Further, a number of the managed classes use overloaded constructors and methods that only support valid parameter combinations, relying less on a programmer's deep understanding of the Direct3D API.

With Direct3D 11, Microsoft introduced Direct3D feature levels to manage the differences between video cards. The feature levels define a matrix of Direct3D features that are mandatory or optional for hardware devices to implement in order to meet the requirements for a specific feature level. The minimum feature level required for an application can be specified when creating a device instance, and the maximum feature level supported by the hardware device is available on the Device.FeatureLevel property. More information on feature levels and the features available at each level can be found at http://msdn.microsoft.com/en-us/library/windows/desktop/ff476876(v=vs.85).aspx.

Device context

The device context encapsulates all rendering functions. These include setting the pipeline state and generating rendering commands with resources created on the device.

Two types of device context exist in Direct3D 11, the immediate context and deferred context. These implement immediate rendering and deferred rendering respectively.

The interfaces/classes for both context types are:

  • Managed: Direct3D11.DeviceContext, Direct3D11.DeviceContext1, and Direct3D11.DeviceContext2

  • Unmanaged: ID3D11DeviceContext, ID3D11DeviceContext1, and ID3D11DeviceContext2

Immediate context

The immediate context provides access to data on the GPU and the ability to execute/playback command lists immediately against the device. Each device has a single immediate context and only one thread may access the context at the same time; however, multiple threads can interact with the immediate context provided appropriate thread synchronization is in place.

All commands to the underlying device eventually must pass through the immediate context if they are to be executed.

The immediate context is available on the device through the following methods/properties:

  • Managed: Device.ImmediateContext, Device1.ImmediateContext1, and Device2.ImmediateContext2

  • Unmanaged: ID3D11Device::GetImmediateContext, ID3D11Device1::GetImmediateContext1, and ID3D11Device2::GetImmediateContext2

Deferred context

The same rendering methods are available on a deferred context as for an immediate context; however, the commands are added to a queue called a command list for later execution upon the immediate context.

Using deferred contexts results in some additional overhead, and only begins to see benefits when parallelizing CPU-intensive tasks. For example, rendering the same simple scene for the six sides of a cubic environment map will not immediately see any performance benefits, and in fact will increase the time it takes to render a frame as compared to using the immediate context directly. However, render the same scene again with enough CPU load and it is possible to see some improvements over rendering directly on the immediate context. The usage of deferred contexts is no substitute for a well written engine and needs to be carefully evaluated to be correctly taken advantage of.

Multiple deferred context instances can be created and accessed from multiple threads; however, each may only be accessed by one thread at a time. For example, with the deferred contexts A and B, we can access both at the exact same time from threads 1 and 2 provided that thread 1 is only accessing deferred context A and thread 2 is only accessing deferred context B (or vice versa). Any sharing of contexts between threads requires thread synchronization.

The resulting command lists are not executed against the device until they are played back by an immediate context.

Note

If a device is created with the single-threaded device creation flag, an error will occur if you attempt to create a deferred context. The result of accessing Direct3D interfaces from multiple threads is also undefined.

A deferred context is created with:

  • Managed: new DeviceContext(device)

  • Unmanaged: ID3D11Device::CreateDeferredContext

Command lists

A command list stores a queue of Direct3D API commands for deferred execution or merging into another deferred context. They facilitate the efficient playback of a number of API commands queued from a device context.

A command list is represented by the ID3D11CommandList interface in unmanaged C++ and the Direct3D11.CommandList class in managed C# with SharpDX. They are created using:

  • Managed: DeviceContext.FinishCommandList

  • Unmanaged: ID3D11DeviceContext::FinishCommandList

Command lists are played back on the immediate context using:

  • Managed: DeviceContext.ExecuteCommandList

  • Unmanaged: ID3D11DeviceContext::ExecuteCommandList

Tip

Trying to execute a command list on a deferred context or trying to create a command list from an immediate context will result in an error.

Swap chains

A swap chain facilitates the creation of one or more back buffers. These buffers are used to store rendered data before being presented to an output display device. The swap chain takes care of the low-level presentation of this data and with Direct3D 11.1, supports stereoscopic 3D display behavior (left and right eye for 3D glasses/displays).

If the output of rendering is to be sent to an output connected to the current adapter, a swap chain is required.

Swap chains are part of the DirectX Graphics Infrastructure (DXGI) API, which is responsible for enumerating graphics adapters, display modes, defining buffer formats, sharing resources between processes, and finally (via the swap chain) presenting rendered frames to a window or output device for display.

A swap chain is represented by the following types:

  • Managed: SharpDX.DXGI.SwapChain and SharpDX.DXGI.SwapChain1

  • Unmanaged: IDXGISwapChain and IDXGISwapChain1

States

A number of state types exist to control the behavior of some fixed function stages of the pipeline and how samplers behave for shaders.

All shaders can accept several sampler states. The output merger can accept both, a blend state and depth-stencil state, and the rasterizer accepts a rasterizer state. The types used are shown in the following table.

Managed type (SharpDX.Direct3D11)

Unmanaged type

BlendState

ID3D11BlendState

BlendState1

ID3D11BlendState1

DepthStencilState

ID3D11DepthStencilState

RasterizerState

ID3D11RasterizerState

RasterizerState1

ID3D11RasterizerState1

SamplerState

ID3D11SamplerState

Resources

A resource is any buffer or texture that is used as an input and/or output from the Direct3D pipeline. A resource is consumed by creating one or more views to the resource and then binding them to stages of the pipeline.

Textures

A texture resource is a collection of elements known as texture pixels or texels—which represent the smallest unit of a texture that can be read or written to by the pipeline. A texel is generally comprised of between one and four components depending on which format is being used for the texture; for example, a format of Format.R32G32B32_Float is used to store three 32-bit floating point numbers in each texel whereas a format of Format.R8G8_UInt represents two 8-bit unsigned integers per texel. There is a special case when dealing with compressed formats (Format.BC) where the smallest unit consists of a block of 4 x 4 texels.

A texture resource can be created in a number of different formats as defined by the DXGI format enumeration (SharpDX.DXGI.Format and DXGI_FORMAT for managed/unmanaged, respectively). The format can be either applied at the time of creation, or specified when it is bound by a resource view to the pipeline.

Hardware device drivers may support different combinations of formats for different purposes, although there is a list of mandatory formats that the hardware must support depending on the version of Direct3D. The device's CheckFormatSupport method can be used to determine what resource type and usage a particular format supports on the current hardware.

Note

Textures do not just store image data. They are used for information, such as height-maps, displacement-maps, or for any data structure that needs to be read or written within a shader that can benefit from the speed benefits of hardware support for textures and texture sampling.

Types of texture resources include:

  • 1D Textures and 1D Texture Arrays

  • 2D Textures and 2D Texture Arrays

  • 3D Textures (or volume textures)

  • Unordered access textures

  • Read/Write textures

The following table maps the managed to unmanaged types for the different textures.

Managed type (SharpDX.Direct3D11)

Unmanaged type

Texture1D

ID3D11Texture1D

Texture2D

ID3D11Texture2D

Texture3D

ID3D11Texture3D

Arrays of 1D and 2D textures are configured with the subresource data associated with the description of the texture passed into the appropriate constructor. A common use for texture arrays is supporting Multiple Render Targets (MRT).

Resource views

Before a resource can be used within a stage of the pipeline it must first have a view. This view describes to the pipeline stages what format to expect the resource in and what region of the resource to access. The same resource can be bound to multiple stages of the pipeline using the same view, or by creating multiple resource views.

It is important to note that although a resource can be bound to multiple stages of the pipeline, there may be restrictions on whether the same resource can be bound for input and output at the same time. As an example, a Render Target View (RTV) and Shader Resource View (SRV) for the same resource both cannot be bound to the pipeline at the same time. When a conflict arises the read-only resource view will be automatically unbound from the pipeline, and if the debug layer is enabled, a warning message will be output to the debug output.

Using resources created with a typeless format, allows the same underlying resource to be represented by multiple resource views, where the compatible resolved format is defined by the view. For example, using a resource with both a Depth Stencil View (DSV) and SRV requires that the underlying resource be created with a format like Format.R32G8X24_Typeless. The SRV then specifies a format of Format.R32_Float_X8X24_Typeless, and finally the DSV is created with a format of Format.D32_Float_S8X24_UInt.

Some types of buffers can be provided to certain stages of the pipeline without a resource view, generally when the structure and format of the buffer is defined in some other way, for example, using state objects or structures within shader files.

Types of resource views include:

  • Depth Stencil View (DSV),

  • Render Target View (RTV),

  • Shader Resource View (SRV)

  • Unordered Access View (UAV)

  • Video decoder output view

  • Video processor input view

  • Video processor output view

The following table shows the managed and unmanaged types for the different resource views.

Managed type (SharpDX.Direct3D11)

Unmanaged type

DepthStencilView

ID3D11DepthStencilView

RenderTargetView

ID3D11RenderTargetView

ShaderResourceView

ID3D11ShaderResourceView

UnorderedAccessView

ID3D11UnorderedAccessView

VideoDecoderOutputView

ID3D11VideoDecoderOutputView

VideoProcessorInputView

ID3D11VideoProcessorInputView

VideoProcessorOutputView

ID3D11VideoProcessorOutputView

Buffers

A buffer resource is used to provide structured and unstructured data to stages of in the graphics pipeline.

Types of buffer resources include:

  • Vertex buffer

  • Index buffer

  • Constant buffer

  • Unordered access buffers

    • Byte address buffer

    • Structured buffer

    • Read/Write buffers

    • Append/Consume structured buffers

All buffers are represented by the SharpDX.Direct3D11.Buffer class (ID3D11Buffer for the unmanaged API). The usage is defined by how and where it is bound to the pipeline. The following table shows the binding flags for different buffers:

Buffer type

Managed BindFlags flags

Unmanaged D3D11_BIND_FLAG flags

Vertex buffer

VertexBuffer

D3D11_BIND_VERTEX_BUFFER

Index buffer

IndexBuffer

D3D11_BIND_INDEX_BUFFER

Constant buffer

ConstantBuffer

D3D11_BIND_CONSTANT_BUFFER

Unordered access buffers

UnorderedAccess

D3D11_BIND_UNORDERED_ACCESS

Unordered access buffers are further categorized into the following types using an additional option/miscellaneous flag within the buffer description as shown in the following table:

Buffer type

Managed ResourceOptionFlags flags

Unmanaged D3D11_RESOURCE_MISC_FLAG flags

Byte address buffer

BufferAllowRawViews

D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS

Structured buffer

BufferStructured

D3D11_RESOURCE_MISC_BUFFER_STRUCTURED

Read/Write buffers

Either use Byte address buffer / Structured buffer and then use RWBuffer or RWStructuredBuffer<MyStruct> instead of Buffer and StructuredBuffer<MyStruct> in HLSL.

Append/Consume buffers

A structured buffer and then use AppendStructuredBuffer or ConsumeStructuredBuffer in HLSL. Use UnorderedAccessViewBufferFlags.Append when creating the UAV.

Shaders and High Level Shader Language

The graphics pipeline is made up of fixed function and programmable stages. The programmable stages are referred to as shaders, and are programmed using small High Level Shader Language (HLSL) programs. The HLSL is implemented with a series of shader models, each building upon the previous version. Each shader model version supports a set of shader profiles, which represent the target pipeline stage to compile a shader. Direct3D 11 introduces Shader Model 5 (SM5), a superset of Shader Model 4 (SM4).

An example shader profile is ps_5_0, which indicates a shader program is for use in the pixel shader stage and requires SM5.

Stages of the programmable pipeline

All Direct3D operations take place via one of the two pipelines, known as pipelines for the fact that information flows in one direction from one stage to the next. For all drawing operations, the graphics pipeline is used (also known as drawing pipeline or rendering pipeline). To run compute shaders, the dispatch pipeline is used (aka DirectCompute pipeline or compute shader pipeline).

Although these two pipelines are conceptually separate. They cannot be active at the same time. Context switching between the two pipelines also incurs additional overhead so each pipeline should be used in blocks—for example, run any compute shaders to prepare data, perform all rendering, and finally post processing.

Methods related to stages of the pipeline are found on the device context. For the managed API, each stage is grouped into a property named after the pipeline stage. For example, for the vertex shader stage, deviceContext.VertexShader.SetShaderResources, whereas the unmanaged API groups the methods by a stage acronym directly on the device context, for example, deviceContext->VSSetShaderResources, where VS represents the vertex shader stage.

The graphics pipeline

The graphics pipeline is comprised of nine distinct stages that are generally used to create 2D raster representations of 3D scenes, that is, take our 3D model and turn it into what we see on the display. Four of these stages are fixed function and the remaining five programmable stages are called shaders (the following diagram shows the programmable stages as a circle). The output of each stage is taken as input into the next along with bound resources or in the case of the last stage, Output Merger (OM), the output is sent to one or more render targets. Not all of the stages are mandatory and keeping the number of stages involved to a minimum will generally result in faster rendering.

Optional tessellation support is provided by the three tessellation stages (two programmable and one fixed function): the hull shader, tessellator, and domain shader. The tessellation stages require a Direct3D feature level of 11.0 or later.

As of Direct3D 11.1, each programmable stage is able to read/write to an Unordered Access View (UAV). A UAV is a view of a buffer or texture resource that has been created with the BindFlags.UnorderedAccess flag (D3D11_BIND_UNORDERED_ACCESS from the D3D11_BIND_FLAG enumeration).

Direct3D Graphics Pipeline

Input Assembler (IA) stage

The IA stage reads primitive data (points, lines, and/or triangles) from buffers and assembles them into primitives for use in subsequent stages.

Usually one or more vertex buffers, and optionally an index buffer, are provided as input. An input layout tells the input assembler what structure to expect the vertex buffer in.

The vertex buffer itself is also optional, where a vertex shader only has a vertex ID as input (using the SV_VertexID shader system value input semantic) and then can either generate the vertex data procedurally or retrieve it from a resource using the vertex ID as an index. In this instance, the input assembler is not provided with an input layout or vertex buffer, and simply receives the number of vertices that will be drawn. For more information, see http://msdn.microsoft.com/en-us/library/windows/desktop/bb232912(v=vs.85).aspx.

Device context commands that act upon the input assembler directly are found on the DeviceContext.InputAssembler property, for example, DeviceContext.InputAssembler.SetVertexBuffers, or for unmanaged begin with IA, for example, ID3D11DeviceContext::IASetVertexBuffers.

Vertex Shader (VS) stage

The vertex shader allows per-vertex operations to be performed upon the vertices provided by the input assembler. Operations include manipulating per-vertex properties such as position, color, texture coordinate, and a vertex's normal.

A vertex can be comprised of up to sixteen 32-bit vectors (up to four components each). A minimal vertex usually consists of position, color, and the normal vector. In order to support larger sets of data or as an alternative to using a vertex buffer, the vertex shader can also retrieve data from a texture or UAV.

A vertex shader is required; even if no transform is needed, a shader must be provided that simply returns vertices without modifications.

Device context commands that are used to control the vertex shader stage are grouped within the DeviceContext.VertexShader property or for unmanaged begin with VS, for example, DeviceContext.VertexShader.SetShaderResources and ID3D11DeviceContext::VSSetShaderResources, respectively.

Hull Shader (HS) stage

The hull shader is the first stage of the three optional stages that together support hardware accelerated tessellation. The hull shader outputs control points and patches constant data that controls the fixed function tessellator stage. The shader also performs culling by excluding patches that do not require tessellation (by applying a tessellation factor of zero).

Unlike other shaders, the hull shader consists of two HLSL functions: the patch constant function, and hull shader function.

This shader stage requires that the IA stage has one of the patch list topologies set as its active primitive topology (for example, SharpDX.Direct3D.PrimitiveTopology.PatchListWith3ControlPoints for managed and D3D11_PRIMITIVE_TOPOLOGY_3_CONTROL_POINT_PATCHLIST for unmanaged).

Device context commands that control the hull shader stage are grouped within the DeviceContext.HullShader property or for unmanaged device begin with HS.

Tessellator stage

The tessellator stage is the second stage of the optional tessellation stages. This fixed function stage subdivides a quad, triangle, or line into smaller objects. The tessellation factor and type of division is controlled by the output of the hull shader stage.

Unlike all other fixed function stages the tessellator stage does not include any direct method of controlling its state. All required information is provided within the output of the hull shader stage and implied through the choice of primitive topology and configuration of the hull and domain shaders.

Domain Shader (DS) stage

The domain shader is the third and final stage of the optional tessellation stages. This programmable stage calculates the final vertex position of a subdivided point generated during tessellation.

The types of operations that take place within this shader stage are often fairly similar to a vertex shader when not using the tessellation stages.

Device context commands that control the domain shader stage are grouped by the DeviceContext.DomainShader property, or for unmanaged begin with DS.

Geometry Shader (GS) stage

The optional geometry shader stage runs shader code that takes an entire primitive or primitive with adjacency as input. The shader is able to generate new vertices on output (triangle strip, line strip, or point list).

Note

The geometry shader stage is unique in that its output can go to the rasterizer stage and/or be sent to a vertex buffer via the stream output stage (SO).

It is critical for performance that the amount of data sent into and out of the geometry shader is kept to a minimum. The geometry shader stage has the potential to slow down the rendering performance quite significantly.

Uses of the geometry shader might include rendering multiple faces of environment maps in a single pass (refer to Chapter 9, Rendering on Multiple Threads and Deferred Contexts), and point sprites/billboarding (commonly used in particle systems). Prior to Direct3D 11, the geometry shader could be used to implement tessellation.

Device context commands that control the geometry shader stages are grouped in the GeometryShader property, or for unmanaged begin with GS.

Stream Output (SO) stage

The stream output stage is an optional fixed function stage that is used to output geometry from the geometry shader into vertex buffers for re-use or further processing in another pass through the pipeline.

There are only two commands on the device context that control the stream output stage found on the StreamOutput property of the device content: GetTargets and SetTargets (unmanaged SOGetTargets and SOSetTargets).

Rasterizer stage (RS)

The rasterizer stage is a fixed function stage that converts the vector graphics (points, lines, and triangles) into raster graphics (pixels). This stage performs view frustum clipping, back-face culling, early depth/stencil tests, perspective divide (to convert our vertices from clip-space coordinates to normalized device coordinates), and maps vertices to the viewport. If a pixel shader is specified, this will be called by the rasterizer for each pixel, with the result of interpolating per-vertex values across each primitive passed as the pixel shader input.

There are additional interpolation modifiers that can be applied to the pixel shader input structure that tell the rasterizer stage the method of interpolation that should be used for each property (for more information see Interpolation Modifiers introduced in Shader Model 4 on MSDN at http://msdn.microsoft.com/en-us/library/windows/desktop/bb509668(v=vs.85).aspx#Remarks).

When using multisampling, the rasterizer stage can provide an additional coverage mask to the pixel shader that indicates which samples are covered by the pixel. This is provided within the SV_Coverage system-value input semantic. If the pixel shader specifies the SV_SampleIndex input semantic, instead of being called once per pixel by the rasterizer, it will be called once per sample per pixel (that is, a 4xMSAA render target would result in four calls to the pixel shader for each pixel).

Device context commands that control the rasterizer stage state are grouped in the Rasterizer property of the device context or for unmanaged begin with RS.

Pixel Shader (PS) stage

The final programmable stage is the pixel shader. This stage executes a shader program that performs per-pixel operations to determine the final color of each pixel. Operations that take place here include per-pixel lighting and post processing.

Device context commands that control the pixel shader stage are grouped by the PixelShader property or begin with PS for the unmanaged API.

Output Merger (OM) stage

The final stage of the graphics pipeline is the output merger stage. This fixed function stage generates the final rendered pixel color. You can bind a depth-stencil state to control z-buffering, and bind a blend state to control blending of pixel shader output with the render target.

Device context commands that control the state of the output merger stage are grouped by the OutputMerger property or for unmanaged begin with OM.

The dispatch pipeline

The dispatch pipeline is where compute shaders are executed. There is only one stage in this pipeline, the compute shader stage. The dispatch pipeline and graphics pipeline cannot run at the same time and there is an additional context change cost when switching between the two, therefore calls to the dispatch pipeline should be grouped together where possible.

Direct3D Dispatch/DirectCompute Pipeline

Compute Shader (CS) stage

The compute shader (also known as DirectCompute) is an optional programmable stage that executes a shader program upon multiple threads, optionally passing in a dispatch thread identifier (SV_DispatchThreadID) and up to three thread group identifier values as input (SV_GroupIndex, SV_GroupID, and SV_GroupThreadID). This shader supports a whole range of uses including post processing, physics simulation, AI, and GPGPU tasks.

Compute shader support is mandatory for hardware devices from feature level 11_0 onwards, and optionally available on hardware for feature levels 10_0 and 10_1.

The thread identifier is generally used as an index into a resource to perform an operation. The same shader program is run upon many thousands of threads at the same time, usually with each reading and/or writing to an element of a UAV resource.

Device context commands that control the compute shader stage are grouped in the ComputeShader property or begin with CS in the unmanaged API.

After the compute shader is prepared, it is executed by calling the Dispatch command on the device context, passing in the number of thread groups to use.

 

Introducing Direct3D 11.1 and 11.2


With the release of Windows 8 came a minor release of Direct3D, Version 11.1 and the DXGI API, Version 1.2. A number of features that do not require Windows Display Driver Model (WDDM) 1.2 were later made available for Windows 7 and Windows Server 2008 R2 with the Platform Update for Windows 7 SP1 and Windows Server 2008 R2 SP1.

Now with the release of Windows 8.1 in October 2013 and the arrival of the Xbox One not long after, Microsoft has provided another minor release of Direct3D, Version 11.2 and DXGI Version 1.3. These further updates are not available on previous versions of Windows 7 or Windows 8.

Direct3D 11.1 and DXGI 1.2 features

Direct3D 11.1 introduces a number of enhancements and additional features, including:

  • Unordered Access Views (UAVs) can now be used in any shader stage, not just the pixel and compute shaders

  • A larger number of UAVs can be used when you bind resources to the output merger stage

  • Support for reducing memory bandwidth and power consumption (HLSL minimum precision and swap chain dirty regions and scroll present parameters)

  • Shader tracing and compiler enhancements

  • Direct3D device sharing

  • Create larger constant buffers than a shader can access (by binding a subset of a constant buffer)

  • Support logical operations in a render target with new blend state options

  • Create SRV/RTV and UAVs to video resources so that Direct3D shaders can process video resources

  • Ability to use Direct3D in Session 0 processes (from background services)

  • Extended resource sharing for shared Texture2D resources

DXGI 1.2 enhancements include:

  • A new flip-model swap chain

  • Support for stereoscopic 3D displays

  • Restricting output to a specific display

  • Support for dirty rectangles and scrolled areas that can reduce memory bandwidth and power consumption

  • Events for notification of application occlusion status (that is, knowing when rendering is not necessary)

  • A new desktop duplication API that replaces the previous mirror drivers

  • Improved event-based synchronization to share resources

  • Additional debugging APIs

Direct3D 11.2 and DXGI 1.3 features

Direct3D 11.2 is a smaller incremental update by comparison and includes the following enhancements:

  • HLSL compilation within Windows Store apps under Windows 8.1. This feature was missing from Windows 8 Windows Store apps and now allows applications to compile shaders at runtime for Windows Store apps.

  • HLSL shader linking, adding support for precompiled HLSL functions that can be packaged into libraries and linked into shaders at runtime.

  • Support for tiled resources, large resources that use small amounts of physical memory—suitable for large terrains.

  • Ability to annotate graphics commands, sending strings and an integer value to Event Tracing for Windows (ETW).

DXGI 1.3 enhancements include:

  • Overlapping swap chains and scaling, for example, presenting a swap chain that is rendered at a lower resolution, then up-scaling and overlapping with a UI swap chain at the displays native resolution.

  • Trim device command, allowing memory to be released temporarily. Suitable for when an application is being suspended and to reduce the chances that it will be terminated to reclaim resources for other apps.

  • Ability to set the source size of the back buffer allowing the swap chain to be resized (smaller) without recreating the swap chain resources.

  • Ability to implement more flexible and lower frame latencies by specifying the maximum frame latency (number of frames that can be queued at one time) and retrieving a wait handle to use with WaitForSingleObjectEx before commencing the next frame's drawing commands.

 

Building a Direct3D 11 application with C# and SharpDX


In this recipe we will prepare a blank project that contains the appropriate SharpDX references and a minimal rendering loop. The project will initialize necessary Direct3D objects and then provide a basic rendering loop that sets the color of the rendering surface to Color.LightBlue.

Getting ready

Make sure you have Visual Studio 2012 Express for Windows Desktop or Professional and higher installed. Download the SharpDX binary package and have it at hand.

To simplify the recipes in this book, lets put all our projects in a single solution:

  1. Create a new Blank Solution in Visual Studio by navigating to File | New | Project… (Ctrl + Shift + N), search for and select Blank Solution by typing that in the search box at the top right of the New Project form (Ctrl + E).

  2. Enter a solution name and location and click on Ok.

    Note

    The recipes in this book will assume that the solution has been named D3DRendering.sln and that it is located in C:\Projects\D3DRendering.

  3. You should now have a new Blank Solution at C:\Projects\D3DRendering\D3DRendering.sln.

  4. Extract the contents of the SharpDX package into C:\Projects\D3DRendering\External. The C:\Projects\D3DRendering\External\Bin folder should now exist among others.

How to do it…

With the solution open, let's create a new project:

  1. Add a new Windows Form Application project to the solution with .NET Framework 4.5 selected.

  2. We will name the project Ch01_01EmptyProject.

  3. Add the SharpDX references to the project by selecting the project in the solution explorer and then navigate to PROJECT | Add Reference from the main menu. Now click on the Browse option on the left and click on the Browse... button in Reference Manager.

  4. For a Direct3D 11.1 project compatible with Windows 7, Windows 8, and Windows 8.1, navigate to C:\Projects\D3DRendering\External\Bin\DirectX11_1-net40 and select SharpDX.dll, SharpDX.DXGI.dll, and SharpDX.Direct3D11.dll.

  5. For a Direct3D 11.2 project compatible only with Windows 8.1, navigate to C:\Projects\D3DRendering\External\Bin\DirectX11_2-net40 and add the same references located there.

    Note

    SharpDX.dll, SharpDX.DXGI.dll, and SharpDX.Direct3D11.dll are the minimum references required to create Direct3D 11 applications with SharpDX.

  6. Click on Ok in Reference Manager to accept the changes.

  7. Add the following using directives to Program.cs:

    using SharpDX;
    using SharpDX.Windows;
    using SharpDX.DXGI;
    using SharpDX.Direct3D11;
    // Resolve name conflicts by explicitly stating the class to use:
    using Device = SharpDX.Direct3D11.Device;
  8. In the same source file, replace the Main() function with the following code to initialize our Direct3D device and swap chain.

    [STAThread]
    static void Main()
    {
        #region Direct3D Initialization
        // Create the window to render to
        Form1 form = new Form1();
        form.Text = "D3DRendering - EmptyProject";
        form.Width = 640;
        form.Height = 480;
    
        // Declare the device and swapChain vars
        Device device;
        SwapChain swapChain;
    
        // Create the device and swapchain
        Device.CreateWithSwapChain(
            SharpDX.Direct3D.DriverType.Hardware,
            DeviceCreationFlags.None,
            new [] {
                SharpDX.Direct3D.FeatureLevel.Level_11_1,
                SharpDX.Direct3D.FeatureLevel.Level_11_0,
                SharpDX.Direct3D.FeatureLevel.Level_10_1,
                SharpDX.Direct3D.FeatureLevel.Level_10_0,
            },
            new SwapChainDescription()
            {
                ModeDescription =
                    new ModeDescription(
                        form.ClientSize.Width,
                        form.ClientSize.Height,
                        new Rational(60, 1),
                        Format.R8G8B8A8_UNorm
                    ),
                SampleDescription = new SampleDescription(1,0),
                Usage = SharpDX.DXGI.Usage.BackBuffer | Usage.RenderTargetOutput,
                BufferCount = 1,
                Flags = SwapChainFlags.None,
                IsWindowed = true,
                OutputHandle = form.Handle,
                SwapEffect = SwapEffect.Discard,
            },
            out device, out swapChain
        );
    
    // Create references for backBuffer and renderTargetView
        var backBuffer = Texture2D.FromSwapChain<Texture2D>(swapChain, 0);
        var renderTargetView = new RenderTargetView(device, backBuffer);
    
        #endregion
    
    ...
    }
  9. Within the same Main() function, we now create a simple render loop using a SharpDX utility class SharpDX.Windows.RenderLoop that clears the render target with a light blue color.

    #region Render loop
    // Create and run the render loop
    RenderLoop.Run(form, () =>
    {
       // Clear the render target with light blue
      device.ImmediateContext.ClearRenderTargetView(
        renderTargetView,
        Color.LightBlue);
      // Execute rendering commands here...
    
      // Present the frame
      swapChain.Present(0, PresentFlags.None);
    });
    #endregion
  10. And finally, after the render loop we have our code to clean up the Direct3D COM references.

    #region Direct3D Cleanup
    // Release the device and any other resources created
    renderTargetView.Dispose();
    backBuffer.Dispose();
    device.Dispose();
    swapChain.Dispose();
    #endregion
  11. Start debugging the project (F5). If all is well, the application will run and show a window like the following screenshot. Nothing very exciting yet but we now have a working device and swap chain.

    Output from the empty project

How it works…

We've created a standard Windows Forms Application to simplify the example so that the project can be built on Windows 7, Windows 8, and Windows 8.1.

Adding the SharpDX.dll reference to your project provides access to all the common enumerations and structures that have been generated in SharpDX from the Direct3D SDK header files, along with a number of base classes and helpers such as a matrix implementation and the RenderLoop we have used. Adding the SharpDX.DXGI.dll reference provides access to the DXGI API (where we get our SwapChain from), and finally SharpDX.Direct3D11.dll provides us with access to the Direct3D 11 types.

The using directives added are fairly self-explanatory except perhaps the SharpDX.Windows namespace. This contains the implementation for RenderLoop and also a System.Windows.Form descendant that provides some helpful events for Direct3D applications (for example, when to pause/resume rendering).

When adding the using directives, there are sometimes conflicts in type names between namespaces. In this instance there is a definition for the Device class in the namespaces SharpDX.DXGI and SharpDX.Direct3D11. Rather than having to always use fully qualified type names, we can instead explicitly state which type should be used with a device using an alias directive as we have done with:

using Device = SharpDX.Direct3D11.Device;

Our Direct3D recipes will typically be split into three stages:

  • Initialization: This is where we will create the Direct3D device and resources

  • Render loop: This is where we will execute our rendering commands and logic

  • Finalization: This is where we will cleanup and free any resources

The previous code listing has each of the key lines of code highlighted so that you can easily follow along.

Initialization

First is the creation of a window so that we have a valid handle to provide while creating the SwapChain object. We then declare the device and swapChain variables that will store the output of our call to the static method Device.CreateDeviceAndSwapChain.

The creation of the device and swap chain takes place next. This is the first highlighted line in the code listing.

Here we are telling the API to create a Direct3D 11 device using the hardware driver, with no specific flags (the native enumeration for DeviceCreationFlags is D3D11_CREATE_DEVICE_FLAG) and to use the feature levels available between 11.1 and 10.0. Because we have not used the Device.CreateDeviceAndSwapChain override that accepts a SharpDX.DXGI.Adapter object instance, the device will be constructed using the first adapter found.

This is a common theme with the SharpDX constructors and method overrides, often implementing default behavior or excluding invalid combinations of parameters to simplify their usage, while still providing the option of more detailed control that is necessary with such a complex API.

SwapChainDescription (natively DXGI_SWAP_CHAIN_DESC) is describing a back buffer that is the same size as the window with a fullscreen refresh rate of 60 Hz. We have specified a format of SharpDX.DXGI.Format.R8G8B8A8_UNorm, meaning each pixel will be made up of 32-bits consisting of four 8-bit unsigned normalized values (for example, values between 0.0-1.0 represent the range 0-255) representing Red, Green, Blue, and Alpha respectively. UNorm refers to the fact that each of the values stored are normalized to 8-bit values between 0.0 and 1.0, for example, a red component stored in an unsigned byte of 255 is 1 and 127 becomes 0.5. A texture format ending in _UInt on the other hand is storing unsigned integer values, and _Float is using floating point values. Formats ending in _SRgb store gamma-corrected values, the hardware will linearize these values when reading and convert back to the sRGB format when writing out pixels.

The back buffer can only be created using a limited number of the available resource formats. The feature level also impacts the formats that can be used. Supported back buffer formats for feature level >= 11.0 are:

SharpDX.DXGI.Format.R8G8B8A8_UNorm

SharpDX.DXGI.Format.R8G8B8A8_UNorm_SRgb

SharpDX.DXGI.Format.B8G8R8A8_UNorm

SharpDX.DXGI.Format.B8G8R8A8_UNorm_SRgb

SharpDX.DXGI.Format.R16G16B16A16_Float

SharpDX.DXGI.Format.R10G10B10A2_UNorm

SharpDX.DXGI.Format.R10G10B10_Xr_Bias_A2_UNorm

We do not want to implement any multisampling of pixels at this time, so we have provided the default sampler mode for no anti-aliasing, that is, one sample and a quality of zero: new SampleDescription(1, 0).

The buffer usage flag is set to indicate that the buffer will be used as a back buffer and as a render-target output resource. The bitwise OR operator can be applied to all flags in Direct3D.

The number of back buffers for the swap chain is set to one and there are no flags that we need to add to modify the swap chain behavior.

With IsWindowed = true, we have indicated that the output will be windowed to begin with and we have passed the handle of the form we created earlier for the output window.

The swap effect used is SwapEffect.Discard, which will result in the back buffer contents being discarded after each swapChain.Present.

Note

Windows Store apps must use a swap effect of SwapEffect.FlipSequential, which in turn limits the valid resource formats for the back buffer to one of the following:

SharpDX.DXGI.Format.R8G8B8A8_UNorm

SharpDX.DXGI.Format.B8G8R8A8_UNorm

SharpDX.DXGI.Format.R16G16B16A16_Float

With the device and swap chain initialized, we now retrieve a reference to the back buffer so that we can create RenderTargetView. You can see here that we are not creating any new objects. We are simply querying the existing objects for a reference to the applicable Direct3D interfaces. We do still have to dispose of these correctly as the underlying COM reference counters will have been incremented.

Render loop

The next highlighted piece of code is the SharpDX.Windows.RenderLoop.Run helper function. This takes our form and delegate or Action as input, with delegate executed within a loop. The loop takes care of all application messages, and will listen for any application close events and exit the loop automatically, for example, if the form is closed. The render loop blocks the thread so that any code located after the call to RenderLoop.Run will not be executed until the loop has exited.

Now we execute our first rendering command which is to clear renderTargetView with a light blue color. This line is retrieving the immediate device context from the device and then executing the ClearRenderTargetView command. As this is not a deferred context the command is executed immediately.

Finally we tell the swap chain to present the back buffer (our renderTargetView that we just set to light blue) to the front buffer.

Finalization

The finalization stage is quite straight forward. After the RenderLoop exits, we clean up any resources that we have created and dispose of the device and swap chain.

All SharpDX classes that represent Direct3D objects implement the IDisposable interface and should be disposed off to release unmanaged resources.

There's more…

To make the example a little more interesting, try using a Linear interpolation (LERP) of the color that is being passed to the ClearRenderTargetView command. For example, the following code will interpolate the color between light and dark blue over 2 seconds:

var lerpColor = SharpDX.Color.Lerp(SharpDX.Color.LightBlue, 
                    SharpDX.Color.DarkBlue, 
                    (float)(totalSeconds / 2.0 % 1.0));
device.ImmediateContext.ClearRenderTargetView(
    renderTargetView,
    lerpColor);

You will have noticed that there are a number of other SharpDX assemblies available within the SharpDX binaries directory.

The SharpDX.Direct2D1.dll assembly provides you with the Direct2D API. SharpDX.D3DCompiler.dll provides runtime shader compilation, which we will be using to compile our shaders in later chapters. SharpDX.XAudio2.dll exposes the XAudio2 API for mixing voices and SharpDX.RawInput.dll provides access to the raw data sent from user input devices, such as the keyboard, mouse, and gamepads or joysticks. The Microsoft Media Foundation, for dealing with audio/video playback, is wrapped by the SharpDX.MediaFoundation.dll assembly.

Finally, the SharpDX.Toolkit.dll assemblies provide a high-level game API for Direct3D 11 much like XNA 4.0 does for Direct3D 9. These assemblies hide away a lot of the low-level Direct3D interaction and provide a number of compilation tools and convenience functions to streamline including shaders and other game content in your project. The framework is worth taking a look at for high-level operations, but as we will tend to be working with the low-level API, it is generally not suitable for our purposes here.

The SharpDX package provides binaries for various platforms. We have used the DirectX 11.1 .NET 4.0 or the DirectX 11.2 .NET 4.0 build here and will use the WinRT build in Chapter 11, Integrating Direct3D with XAML and Windows 8.1. SharpDX also provides assemblies and classes for Direct3D 11, Direct3D 10, and Direct3D 9.

See also

  • We will see how to gain access to the Direct3D 11.1/11.2 device and swap chain in the next recipe, Initializing a Direct3D 11.1/11.2 device and swap chain.

  • In Chapter 2, Rendering with Direct3D, we will cover more detail about rendering, and focus on resource creation, the rendering loop, and simple shaders.

  • Chapter 11, Integrating Direct3D with XAML and Windows 8.1, shows how to build a Windows Store app for Windows 8.1.

  • The Microsoft Developer Network (MSDN) provides a great deal of useful information. The Direct3D launch page can be found at http://msdn.microsoft.com/en-us/library/windows/desktop/hh309466(v=vs.85).aspx.

 

Initializing a Direct3D 11.1/11.2 device and swap chain


We now know how to create our device and swap chain, however, we do not yet have access to some of the features available in Direct3D 11.1 or 11.2 as we are only creating Direct3D 11 references.

In this recipe we will modify the previous example so that we are instead creating SharpDX.Direct3D11.Device1 and SharpDX.DXGI.SwapChain1 (natively these are ID3D11Device1 and IDXGISwapChain1, respectively) to access Direct3D 11.1 features, and SharpDX.Direct3D11.Device2, and SharpDX.DXGI.SwapChain2 (natively these are ID3D11Device2 and IDXGISwapChain2, respectively) to access the features of Direct3D 11.2.

Device1 allows, among others, the creation of blend states that utilize logical operations and access to DeviceContext1 to access larger constant buffers in shaders than would normally be possible.

SharpDX.DXGI.SwapChain1 includes support for stereoscopic 3D display and supports WinRT and Windows Phone 8 development.

Note

The Direct3D 11.2 API is only available on Windows 8.1.

Getting ready

First we will create a new Windows Form Application project named Ch01_02Direct3D11_1 in our D3DRendering.sln solution.

Now add the SharpDX references as outlined in the previous recipe, choosing the appropriate version – Building a Direct3D 11 application with C# and SharpDX.

Set the new project as the startup project by right-clicking on the project in the solution explorer and click on Set as StartUp Project.

Note

For Windows 7/Windows Server 2008 R2 users, this recipe requires that you have installed the platform update for Windows 7 Service Pack 1/Windows Server 2008 R2 SP1.

It is not possible to use the Direct3D 11.2 API with Windows 7, as this version is available to Windows 8.1 only.

How to do it…

We'll begin by creating the Direct3D 11 device as done in the previous recipe and then query the object for an implementation of the Direct3D 11.1 Device1 COM interface.

  1. Open Program.cs and add the using directives from the previous recipe along with one additional alias:

    using SharpDX;
    using SharpDX.Windows;
    using SharpDX.DXGI;
    using SharpDX.Direct3D11;
    // Resolve name conflicts by explicitly stating the class to use:
    using Device = SharpDX.Direct3D11.Device;
    using Device1 = SharpDX.Direct3D11.Device1;
    
  2. Now copy the contents of the Main() method from the previous recipe.

  3. Build the project (F6) just to be sure everything is setup correctly before continuing.

  4. Within the Main() method, replace the existing device initialization with the following code:

    // Create the device and swapchain
    Device1 device;
    SwapChain1 swapChain;
    
    // First create a regular D3D11 device
    using (var device11 = new Device(
            SharpDX.Direct3D.DriverType.Hardware, 
            DeviceCreationFlags.None,
            new [] {
                SharpDX.Direct3D.FeatureLevel.Level_11_1,
                SharpDX.Direct3D.FeatureLevel.Level_11_0,
            }))
    {
      // Query device for the Device1 interface (ID3D11Device1)
      device = device11.QueryInterfaceOrNull<Device1>();
      if (device == null)
          throw new NotSupportedException( 
              "SharpDX.Direct3D11.Device1 is not supported");
    }

    Note

    We are explicitly excluding feature levels below 11_0 as we will be using SM5 and other Direct3D 11 features.

    Retrieving the Direct3D 11.2 interfaces is performed in the exact same way except with SharpDX.Direct3D11.Device2.

  5. With the device created, we now need to initialize our swap chain as shown in the following code:

    // Rather than create a new DXGI Factory we reuse the
    // one that has been used internally to create the device
    using (var dxgi = device.QueryInterface<SharpDX.DXGI.Device2>())
    using (var adapter = dxgi.Adapter)
    using (var factory = adapter.GetParent<Factory2>())
    {
        var desc1 = new SwapChainDescription1()
        {
            Width = form.ClientSize.Width,
            Height = form.ClientSize.Height,
            Format = Format.R8G8B8A8_UNorm,
            Stereo = false,
            SampleDescription = new SampleDescription(1, 0),
            Usage = Usage.BackBuffer | Usage.RenderTargetOutput,
            BufferCount = 1,
            Scaling = Scaling.Stretch,
            SwapEffect = SwapEffect.Discard,
        };
    
    
        swapChain = new SwapChain1(factory,
            device,
            form.Handle,
            ref desc1,
            new SwapChainFullScreenDescription()
            {
                RefreshRate = new Rational(60, 1),
                Scaling = DisplayModeScaling.Centered,
                Windowed = true
            },
            // Restrict output to specific Output (monitor)
            null);
    }

    Note

    To retrieve the Direct3D 11.2 swap chain, create the swap chain as done here and then use a call to swapChain.QueryInterfaceOrNull<SwapChain2>();

  6. Finally we will change the swapChain.Present call from within the render loop of the previous recipe to:

    // Present the frame
    swapChain.Present(0, PresentFlags.None, new 
        PresentParameters());
    
  7. Run the project (F5). The result should be identical to the previous recipe.

How it works…

Our first change to the previous code is the addition of a new directive using an alias directive for SharpDX.Direct3D11.Device1. We keep the SharpDX.Direct3D11.Device alias because we first create a regular device and then query it for the 11.1 implementation.

Within the Direct3D Initialization region and after the window is created, we have changed the declaration of the device and swapChain variables to be of type Device1 and SwapChain1. We then create Device with the same parameters as before except using a constructor rather than the previous Device.CreateWithSwapChain method. This is done within a using statement so that the reference to the first device is automatically disposed. Within the using block we query the device for a reference to the Device1 class. If the implementation of Device1 was unavailable in the Direct3D API, the return value from device11.QueryInterfaceOrNull<Device1> would be null while using the regular QueryInterface<T> method would result in a SharpDX.SharpDXException being thrown.

Note

All SharpDX classes that wrap a native COM object support a number of variations of the QueryInterface method to query the underlying IUnknown interface.

To create the swap chain, we need to first get a reference to a SharpDX.DXGI.Factory2 instance. Rather than creating a new factory, we will use the one that was initialized internally to create our device. All device instances also implement the interface for SharpDX.DXGI.Device, which gives us access to the Adapter property. As this is provided by the DXGI API we can work our way back from the device to a SharpDX.DXGI.Factory2 instance via the GetParent method.

The equivalent unmanaged example of this section would look something like:

// pd3dDevice creation omitted
IDXGIDevice2* pDXGIDevice;
hr = pd3dDevice->QueryInterface(__uuidof(IDXGIDevice2), 
    &pDXGIDevice);
IDXGIAdapter* pDXGIAdapter;
hr = pDXGIDevice->GetParent(__uuidof(IDXGIAdapter), 
    &pDXGIAdapter);
IDXGIFactory2* pDXGIFactory;
pDXGIAdapter->GetParent(__uuidof(IDXGIFactory2), &pDXGIFactory);

Describing the swap chain for Direct3D 11.1 is slightly different as it separates the description into two structures. The first structure, SwapChainDescription1, describes the buffer size, format, size, usage, and so on like the original but introduces a Stereo and Scaling option and excludes the fullscreen properties. The second structure, SwapChainFullScreenDescription, describes the fullscreen behavior of the swap chain also with a Scaling option.

As this is a desktop application, we use the SwapChain1 constructor that accepts the window handle to create a swap chain for it. We also pass in the swap chain description structures.

Note

For Windows Store apps, we would instead use the appropriate constructor that accepts a Windows.UI.Core.CoreWindow instance. In the case of Windows.UI.Xaml.Controls.SwapChainPanel, no window object is provided and the created swap chain is assigned to the native panel. Details on this are provided in Chapter 11, Integrating Direct3D with XAML and Windows 8.1.

The last parameter of the factory's swap chain creation method allows the application to restrict the display of information to a particular display device. In this case we are not restricting the output, so we are passing null.

Finally we present the back buffer using the recommended Present method override for DXGI 1.2 (IDXGISwapChain1.Present1). The additional PresentParameters parameter allows an application to optimize presentation by specifying scrolling and dirty rectangles, which reduces memory bandwidth and power consumption. In this case we just pass through an empty instance.

There's more…

There are a number of different ways to initialize your Direct3D device and swap chain. For example, if you are enumerating the available adapters and allowing a user to select, which shall be used by the device constructor instead of defaulting to the first, you will already have created a DXGI factory object and the previous code would look a little different.

The output restriction configuration of the swap chain is an interesting concept and easy to demonstrate if you have more than one screen. With the previous example in place:

  1. Change null in the last parameter passed to the new SwapChain1(...) constructor to adapter.Outputs[0].

  2. Change the swap chain present line to:

    swapChain.Present(0, PresentFlags.RestrictToOutput, new PresentParameters());

If you then drag the window so that it sits between your two displays, the result will look something like the following screenshot. Any portion that sits outside of the designated output will not be rendered and appear black.

Result of restricting output to the first screen

See also

 

Debugging your Direct3D application


Debugging the Direct3D pipeline can be a difficult task at times. There are so many elements that are impacting upon the result that pinpointing the cause of an issue can take some work and ingenuity.

This recipe will show you how to get your project ready for debugging, set up object tracking, and show you how to start the Visual Studio 2012 Graphics Debugger in managed applications.

First it is worth taking a look at a number of areas of Direct3D that require different techniques for debugging:

  • Debugging Direct3D errors: Direct3D errors such as the parameter being incorrect or invalid can be diagnosed with the help of enabling the Direct3D debug layer by passing the DeviceCreationFlags.Debug flag during device creation. When the debug layer is enabled, the Direct3D API will send additional details to the debug output window about any errors that occur. There are four classes of messages, the first two CORRUPTION and ERROR are problems that require addressing, while WARNING and INFO messages may or may not require programmer intervention.

  • Tracking resources: Direct3D resource leaks are another area that can take some tracking down, especially in complicated scenes. SharpDX allows you to enable object tracking that you can query at any time to retrieve a list of Direct3D objects that have not been released along with their creation stack trace. On application exit, a complete list of any unreleased objects is printed to the debug output. Enabling the debug layer and the object tracking takes a toll on performance and should only be enabled as needed rather than always enabled during development.

  • Debugging a pixel: Finally there is per-pixel output and shader debugging. Traditionally the DirectX SDK PIX tool has been used for recording Direct3D API calls, now with the Windows 8 SDK and Visual Studio 2012 you can use the Graphics Debugger. When active, this debugger allows you to capture frames that can then be analyzed. You can determine what has impacted an individual pixel, including support for stepping through shaders.

Getting ready

Before we can debug our Direct3D application, we need to prepare the project settings with the following steps:

  1. Create a new Windows Form Application named Ch01_03Debugging in our D3DRendering.sln solution. Set this new project as the startup project.

  2. To support the Visual Studio 2012 Graphics Debugger and to allow native debug messages from Direct3D to appear in the debugger output window, we must first enable native code debugging for the project. To do this, right-click on the project in the solution explorer and click on Properties, now click on the Debug settings and check Enable native code debugging.

    Enabling native code debugging

    Note

    Mixed mode debugging for x64 is only supported starting with .NET Framework 4.

How to do it…

First we will be adding some debug information to our code and then use the Visual Studio Graphics Debugger to capture a frame. We'll then continue to enable the Direct3D 11 debug layer and SharpDX object tracking.

Starting the Visual Studio Graphics Debugger:

  1. Implement all the steps from the first recipe, Building a Direct3D 11 application with C# and SharpDX.

  2. Build the project (F6) to be sure everything is working correctly.

  3. Just before the render loop region, we will add the following:

    // Setup object debug names
    device.DebugName = "The Device";
    swapChain.DebugName = "The SwapChain";
    backBuffer.DebugName = "The Backbuffer";
    renderTargetView.DebugName = "The RenderTargetView";
  4. Let's now run the Visual Studio 2012 graphics debugger by navigating to DEBUG | Graphics | Start diagnostics (Alt+F5).

    Tip

    If the option is unavailable or does not do anything, be sure to check that you have enabled native debugging and that the selected .NET Framework version is 4.0 or later. The project must also be using a debug configuration not release.

  5. We can ignore the warning that says that there are no native symbols in the symbol file. This is because we are trying to debug a managed application. Click on Yes to continue.

  6. If all is well, we should see some statistics in the top left of our application as shown in the following screenshot:

    Graphics debugger text overlay in top left

  7. Press the Prt Scr key and you should now have a frame captured in Visual Studio. Select the frame, and then click anywhere within the preview of the frame. This will now select a single pixel in Graphics Pixel History as shown in the following screenshot:

    The graphics debugger windows with the pixel history and object table highlighted

  8. Stop the debugger.

Enabling the debug layer and object tracking:

Now that we are able to run the debugger, let's turn on the Direct3D debug layer and enable object tracking with the following steps:

  1. Continuing from where we were, add the following to Program.cs at the start of the Main() function:

    // Enable object tracking
    SharpDX.Configuration.EnableObjectTracking = true;
  2. Within the Direct3D Initialization region, change the CreateWithSwapChain call to pass in the debug flag:

    Device.CreateWithSwapChain(
        SharpDX.Direct3D.DriverType.Hardware,
        // Enable Device debug layer
        DeviceCreationFlags.Debug,
        new SwapChainDescription()
        {
  3. Next, we will replace the existing swapChain.Present with the following:

    // Output the current active Direct3D objects
    System.Diagnostics.Debug.Write(
      SharpDX.Diagnostics.ObjectTracker.ReportActiveObjects());
    
    // This is a deliberate invalid call to Present
    swapChain.Present(0, PresentFlags.RestrictToOutput);
  4. Debug the project (F5) and there should be an exception thrown. The debug output should contain something like this for the ReportActiveObjects call:

    [0]: Active COM Object: [0x11BFB00] Class: [SharpDX.DXGI.SwapChain] Time [05/17/2013 16:32:33] Stack:
    	c:\Projects\D3DRendering\Ch01_03Debugging\Program.cs(60,13) : Void Main()
    
    [1]: Active COM Object: [0x11A9C1C] Class: [SharpDX.Direct3D11.Device] Time [05/17/2013 16:32:33] Stack:
    	c:\Projects\D3DRendering\Ch01_03Debugging\Program.cs(60,13) : Void Main()
    
    [2]: Active COM Object: [0x11ABE48] Class: [SharpDX.Direct3D11.DeviceContext] Time [05/17/2013 16:32:33] Stack:
    	c:\Projects\D3DRendering\Ch01_03Debugging\Program.cs(60,13) : Void Main()
    
    [3]: Active COM Object: [0x11C0034] Class: [SharpDX.Direct3D11.Texture2D] Time [05/17/2013 16:32:33] Stack:
    	c:\Projects\D3DRendering\Ch01_03Debugging\Program.cs(85,13) : Void Main()
    
    [4]: Active COM Object: [0x11E0A74] Class: [SharpDX.Direct3D11.RenderTargetView] Time [05/17/2013 16:32:33] Stack:
    	c:\Projects\D3DRendering\Ch01_03Debugging\Program.cs(86,13) : Void Main()
    
    Count per Type:
    Device : 1
    DeviceContext : 1
    RenderTargetView : 1
    SwapChain : 1
    Texture2D : 1
  5. The incorrect call to Present should have resulted in the following being written to the debug output:

    DXGI ERROR: IDXGISwapChain::Present: Present is being called with DXGI_PRESENT_RESTRICT_TO_OUTPUT, which is only valid if the SwapChain was created with a valid pRestrictToOutput. [ MISCELLANEOUS ERROR #120: ]

How it works…

We begin with the code from the first recipe that was rendering a pleasant light blue background for our window.

The debug names that we have added are arbitrary values for you to use to distinguish between different objects of the same type. You may have noticed that these appeared within the Graphics Object Table when a frame has been captured (marked with a red square in the previous screenshot). This will help when you are trying to debug with lots of objects of the same type. From here, it is possible to inspect each object by double clicking, to view the contents of textures and buffers, or the properties of a device or swap chain.

Once we have captured the frame and selected a pixel, the Graphics Pixel History window (circled in red in the previous screenshot) shows the initial color, the color after the call to ClearRenderTarget and the final color of the selected pixel. If there were other operations that took place on the pixel (including shaders), this is where we would be able to delve deeper.

The second part of this recipe introduces Direct3D error debugging and object tracking.

First we enabled object tracking on the first line of Main() to demonstrate how SharpDX will keep track of objects for us.

We create the device as before, but pass through DeviceCreationFlags.Debug instead of DeviceCreationFlags.None. This enables the Direct3D debug layer for this device and we will receive additional messages for any errors after this point.

Next we generate a report of all active Direct3D COM objects. This is the same report that is generated if we were to forget to dispose of any resources before application exit. The report includes the stack trace, and double clicking on the line in the stack trace will take you to the appropriate line in the source code editor.

Finally we have introduced a deliberate error in the call to Present. The message quite clearly indicates that there is a problem with our use of the PresentFlags.RestrictToOutput flag. Either our initialization of the swap chain is incorrect or the call to Present needs changing. In this case we have not configured the swap chain with an output to be restricted to.

There's more…

The graphics debugger has a number of useful debug windows that you can access while you have the recorded graphics experiment open. These are accessible by navigating to the DEBUG/Graphics menu and include Events List (shown on the bottom left of the earlier screenshot) and Pipeline Stages in addition to the ones we have already discussed.

Note

Because the graphics debugger has been initially designed for unmanaged code, the Graphics Event Call Stack window does not resolve the managed source code line numbers correctly. This may change with a future update to Visual Studio.

The SharpDX.Diagnostics.ObjectTracker static class has a number of additional methods that are useful at runtime, such as finding an object reference by its native IntPtr or perhaps iterating the list to check the number of active DeviceContext objects.

It is also possible to debug the HLSL shader code by stepping through the logic based on the selected pixel.

See also

NVIDIA, AMD, and Intel all provide development tools specific to their hardware that can assist with debugging and can be found on the respective websites as follows:

About the Author

  • Justin Stenning

    Justin Stenning , a software enthusiast since DOS was king, has been working as a software engineer since he was 20. He has been the technical lead on a range of projects, from enterprise content management and software integrations to mobile apps, mapping, and biosecurity management systems. Justin has been involved in a number of open source projects, including capturing images from fullscreen Direct3D games and displaying in-game overlays, and enjoys giving a portion of his spare time to the open source community. Justin completed his Bachelor of Information Technology at Central Queensland University, Rockhampton. When not coding or gaming, he thinks about coding or gaming, or rides his motorbike. Justin lives with his awesome wife, and his cheeky and quirky children in Central Victoria, Australia.

    Browse publications by this author

Latest Reviews

(3 reviews total)
Suit for many devices. Good content.
Good
Gutes Buch, allerdings sind die Codebeispiele mit SharpDX leicht veraltet und man muss selber nachbessern - was aber dem Lerneffekt zugute kommt.
Book Title
Access this book, plus 8,000 other titles for FREE
Access now