Multiple SwapChain

SwapChain is a special render buffer, which is used as a final rendering target to output to display. Each time a frame has been completed in Buffer1, it becomes Buffer2 to be displayed on the output. The previous Buffer2 becomes Buffer1 to create the next frame. This constant change between back and front buffer is called swapping or flipping.


SwapChain generation
You can find the SwapChain Class in DXGI. The buffer generation is simple:

'SwapChain
'
Private _SwapChain As DXGI.SwapChain
...
 _SwapChain = New DXGI.SwapChain(Factory, Device, _SwapChainDescription)
...

Default Swapchain Description
This function creates a default description for the SwapChain buffer. This should be easy to create a SwapChain on each device.

'Default SwapChain Description
'
Public Shared Function GetDefaultSwapChainDescrition(C As System.Windows.Forms.Control) As DXGI.SwapChainDescription

 Dim SwapChainDesc As New DXGI.SwapChainDescription
 Dim ModeDesc As New DXGI.ModeDescription
 Dim SampleDesc As New DXGI.SampleDescription

 With ModeDesc
  .Format = DXGI.Format.R8G8B8A8_UNorm
  .RefreshRate = New Rational(60, 1)
  .Scaling = DXGI.DisplayModeScaling.Unspecified
  .ScanlineOrdering = DXGI.DisplayModeScanlineOrdering.Unspecified
  .Width = C.Height
  .Height = C.Width
 End With

 With SampleDesc
  .Count = 1
  .Quality = 0
 End With

 With SwapChainDesc
  .ModeDescription = ModeDesc
  .SampleDescription = SampleDesc
  .BufferCount = 1
  .Flags = DXGI.SwapChainFlags.None
  .IsWindowed = True
  .OutputHandle = C.Handle
  .SwapEffect = DXGI.SwapEffect.Discard
  .Usage = DXGI.Usage.RenderTargetOutput
 End With

 Return SwapChainDesc
End Function

Multiple SwapChain
If you want to render on more then one Control, you need to create several SwapChain buffers. It should be noted that all SwapChains are using the same Factory and Device.


Multiple Swapchain Rendering

Advertisements

Constant Buffer

To exchange data between CPU and GPU,  you have to generate two buffers. One on CPU and one on GPU.

To exchange Data between CPU/GPU in SlimDX you have to:

  • create a structure of data that corresponds to the structure in shader
  • create a DirectX buffer, at least in the byte size of the structure. The byte size of the buffer must be dividable by 16.
  • write the structure into a DataStream
  • Update the DirectX buffer with the DataStream

In the example slot 3 is used for constant buffer.

CPU

'#Pixel Shader Variablen
Public Structure BufferStructurePS
  Public Color As Vector4
  Public GlowColor As Vector4
  Public EmessivePower As Single
  Public SpecularPower As Single
  Public SpecularIntensity As Single
End Structure

GPU

//BUFFER
cbuffer CONSTANT_BUFFER : register(b3)
{
 float4 DiffuseColor;
 float4 GlowColor;
 float  EmessivePower;
 float  SpecularPower;
 float  SpecularIntensity;
};

Classe
I put all the things in on class, doins all the work for us.

Imports SlimDX
Imports SlimDX.Direct3D11
Imports System.Runtime.InteropServices

Public Class ConstantBuffer

Public ReadOnly Property ByteData As Byte()
    Get
        Return _RawData
    End Get
End Property

Public ReadOnly Property RawSize As Integer
    Get
        Return _RawSize
    End Get
End Property

Public ReadOnly Property Data As Buffer
    Get
        Return _CBuffer
    End Get
End Property

Private _CBuffer As Buffer
Private _RawSize As Integer
Private _RawSize16 As Integer
Private _Context As DeviceContext
Private _RawData() As Byte

Public Sub New(RawSize As Integer, DX11Device As DX11Device)
  'If the bind flag is D3D11_BIND_CONSTANT_BUFFER,
  'you must set the ByteWidth value in multiples of 16

  ReDim _RawData(RawSize - 1)
  _RawSize = RawSize
  _RawSize16 = CInt(Math.Ceiling(RawSize / 16) * 16)

  _Context = DX11Device.Device.ImmediateContext
  _CBuffer = CreateBuffer(_RawSize16, DX11Device.Device)
  AddHandler DX11Device.Disposing, AddressOf Dispose
End Sub

Private Function CreateBuffer(RawSize16 As Integer, Device As Device) As Buffer
 '#Create Buffer
  Dim BufferDesc As New BufferDescription
  With BufferDesc
   .Usage = ResourceUsage.Default
   .SizeInBytes = RawSize16
   .BindFlags = BindFlags.ConstantBuffer
  End With
  Return New Buffer(Device, BufferDesc)
End Function

Public Sub Update(CBufferStructure As Object)
 '#Convert Structure to DataStream
  Dim buffer As IntPtr = Marshal.AllocHGlobal(_RawSize)
  Marshal.StructureToPtr(CBufferStructure, buffer, False)
  Marshal.Copy(buffer, _RawData, 0, RawSize)
  Marshal.FreeHGlobal(buffer)

  Using DS As New DataStream(_RawSize16, True, True)
   DS.WriteRange(_RawData, 0, _RawSize)
   DS.Position = 0

   '#Update Buffer
    With _Context
     .UpdateSubresource(New DataBox(0, 0, DS), _CBuffer, 0)
    End With
  End Using
End Sub

Private Sub Dispose()
  '#Dispose Buffer
  If Not _CBuffer Is Nothing Then _CBuffer.Dispose()
End Sub

End Class

Form and RenderLoop

All controls are automatically redrawn in Net. For a RenderLoop, of course, this is not desirable. The graphic engine controls the drawing procces.

You have to create your own control and RenderLoop.  Here’s how to do this.


Inizialize Class

'
_RenderForm = New Renderform(AddressOf GameLoop)
_RenderForm.Run()

Private Sub GameLoop()
...
End sub
'

Renderform Class


Imports System.Runtime.InteropServices
Imports System.Windows.Forms

Public Class Renderform : Inherits Form

 <StructLayout(LayoutKind.Sequential)> _
 Public Structure Message
     Public hWnd As IntPtr
     Public msg As UInteger
     Public wParam As IntPtr
     Public lParam As IntPtr
     Public time As UInteger
     Public p As System.Drawing.Point
 End Structure

 Delegate Sub MsgLoop()
 Private _Loop As MsgLoop

 <DllImport("User32.dll", CharSet:=CharSet.Auto)> _
 Public Shared Function PeekMessage(ByRef msg As Message, hWnd As IntPtr, messageFilterMin As UInteger, messageFilterMax As UInteger, flags As UInteger) As Boolean
 End Function

Public ReadOnly Property AppStillIdle() As Boolean
  Get
       Dim msg As Message
       Return Not PeekMessage(msg, IntPtr.Zero, 0, 0, 0)
   End Get
End Property

Public Sub New(RendeLoop As MsgLoop)
   Me.Size = New Drawing.Size(New Drawing.Point(640, 480))
   Me.StartPosition = FormStartPosition.CenterScreen
   _Loop = RendeLoop
End Sub

Public Sub Run()
   Me.SetStyle(ControlStyles.UserPaint, True)
   Me.SetStyle(ControlStyles.AllPaintingInWmPaint, True)
   Me.SetStyle(ControlStyles.CacheText, True)
   AddHandler Application.Idle, AddressOf Application_Idle
   Application.Run(Me)
   Cursor.Hide()
End Sub

Private Sub Application_Idle(s As Object, e As EventArgs)
   While (AppStillIdle)
       _Loop()
   End While
End Sub

End Class

View Frustum Culling

Man stelle sich vor, die Kamera steht mittig auf einem Terrain, umringt von tausenden Objekten. Ohne Optimierung würden jedesmal alle Vertexdaten zur Verarbeitung an die GPU geschickt.

Mit Frustum Culling werden nur noch Objekte verarbeitet, die sich auch wirklich im Sichtfeld der Kamera befinden. Alles was ausserhalb des Kamera-Frustums liegt fällt raus.

Aus der frame-aktuellen ViewProjektionMatrix (View x Projection) lassen sich alle sechs Planes (Near, Far, Left, Right, Top, Bottom) extrahieren. Anhand der Plane-Normalen lässt sich über ein Dot-Produkt ermitteln ob sich ein Punkt vor oder hinter der Plane befindet. Liegt der Testpunkt vor allen Planes, befindet er sich innerhalb des Frustums. Damit die Berechnung des Dot-Produkts richtig funktioniert, ist es wichtig die Planes zu normalisieren.


View Frustum Planes

'Build the view frustum planes
'
Public Sub Update(ViewProjection As Matrix)
  With ViewProjection
    '#Far Plane
    _Plane(0) = New Plane(.M14 - .M13, .M24 - .M23, .M34 - .M33, .M44 - .M43)
    '#Near Plane
    _Plane(1) = New Plane(.M13, .M23, .M33, .M43)
    '#Left Plane
    _Plane(2) = New Plane(.M14 + .M11, .M24 + .M21, .M34 + .M31, .M44 + .M41)
    '#Right Plane
    _Plane(3) = New Plane(.M14 - .M11, .M24 - .M21, .M34 - .M31, .M44 - .M41)
    '#Top Plane
    _Plane(4) = New Plane(.M14 - .M12, .M24 - .M22, .M34 - .M32, .M44 - .M42)
    '#Bottom Plane
    _Plane(5) = New Plane(.M14 + .M12, .M24 + .M22, .M34 + .M32, .M44 + .M42)
  End With
  
  '#Normalize Planes
  For i = 0 To 5
    _Plane(i) = Plane.Normalize(_Plane(i))
  Next
End Sub

'Test if Point is in Frustum
'
Public Function PointInFrustum(ByVal Point As Vector3, Optional Radius As Single = 0) As Boolean
  Dim vec4 As New Vector4(Point.X, Point.Y, Point.Z, 1)
     For Each p In _Plane
       If Plane.Dot(p, vec4) < -Radius Then Return False
     Next
  Return True
End Function

Bounding Test
Eine BoundingBox besteht aus 8 Punkten. Jeder einzelne Punkt wird gegen alle Planes getestet. Bei einem positiven Ergebnis wird die Schleife frühzeitig verlassen. Eine BoundingSphere ist wesentlich günstiger. Beim Test wird nur der Center benötigt um das Ergebnis gegen den Radius zu testen.

'BoundingBox Test
'
Public Function BoundingBoxInFrustum(Bounding As BoundingBox) As Boolean
  For Each v In Bounding.GetCorners
    If PointInFrustum(v) Then Return True
  Next
  Return False
End Function
'

'BoundingSphere Test
'
Public Function BoundingSphereInFrustum(Bounding As BoundingSphere) As Boolean
  Return PointInFrustum(Bounding.Center, Bounding.Radius)
End Function

Camera Bounding
Alle Objekte hinter der NearPlane werden durch das Culling verworfen. Dies kann in manchen Situationen jedoch unerwünscht sein. Um Objekte für Schatten oder Höhenberechnung dennoch zu erfassen, wird ein Bounding um die Kameraposition erstellt. Damit lässt sich auf einfache Weise ein Sphere-Sphere-Test realisieren der einen bestimmten Radius abdeckt.

'Sphere Intersection
'
Return BoundingSphere.Intersects(Camera.Bounding, Obj.Bounding)
'

Resize and Fullscreen (DXGI)

Verändert man die Dimension des Controls auf dem gerendert wird, stimmen die Dimension der Buffer nicht mehr mit der Dimension des Controls überein und es kommt zu Verzerrungen. Betroffen sind alle Rendertargets, der Viewport und die SwapChain. Es ist sinnvoll allen Rendertargets eine Resize-Methode zu implementieren, um sich selbst an die neuen Pixelabmessungen anzupassen.


SwapChain Resize
Alle Objekte die aus der SwapChain erzeugt wurden, müssen vor dem Resize disposed werden.

'Dispose RenderTargetView
'
If Not _BackBufferRenderTargetView Is Nothing Then _BackBufferRenderTargetView.Dispose()
'

Ein Resize der SwapChain mit DXGI ist recht einfach und benötigt lediglich eine Zeile Code.

'Resize SwapChainBuffers
'
_SwapChain.ResizeBuffers(0, 0, 0, DXGI.Format.Unknown, DXGI.SwapChainFlags.None)
'

Nach dem Resize der SwapChainBuffer, erzeugen wir einen neuen RenderTargetView um wieder in den BackBuffer rendern zu können.

'New RenderTargetView
'
Using Resource = Direct3D11.Resource.FromSwapChain(Of Texture2D)(_SwapChain, 0)
  _BackBufferRenderTargetView = New RenderTargetView(_DX11Device.Device, Resource)
End Using
'


SwapChain FullScreen Switching

Das Switching der SwapChain in den FullScreen, wurde über die Tastenkombination Alt+Enter direkt in Windows implementiert. Unter Winforms scheint es damit aber Probleme zu geben. Die Entwickler von SlimDX empfehlen eine eingene Implementierung über Alt+Enter oder einer anderen Tastenkombination. Bei der Erstellung der SwapChain lässt sich die Windows-Implementierung deaktivieren. Um Probleme zu vermeiden, sollte die Anwendung vor dem Beenden wieder in den Fenstermodus geschaltet werden.

'Disable Alt+Enter
'
_DX11Device.Factory.SetWindowAssociation(_SwapChainDescription.OutputHandle, DXGI.WindowAssociationFlags.IgnoreAltEnter)
_SwapChain = New DXGI.SwapChain(_DX11Device.Factory, _DX11Device.Device, _SwapChainDescription)
'

Die einfachste Variante mit einer gültigen Auflösung in den Fullscreen zu wechseln, ist die aktuelle Desktopauflösung zu nutzen. Generell wird wohl empfohlen den gewünschten Resize immer im WindowMode durchzuführen, bevor in den FullScreen gewechselt wird.

Public Sub SwitchFullScreen()
  '#Get Render-Control
   Dim c As System.Windows.Forms.Control = System.Windows.Forms.Control.FromHandle(_SwapChainDescription.OutputHandle)

  If _SwapChain.IsFullScreen Then
      '#Switch to WindowMode
      _SwapChain.IsFullScreen = False
      '#Set WindowModeSize
      c.Size = New Drawing.Size(_ResX, _ResY)
  Else
      '#Save WindowModeSize
      _ResX = c.Width
      _ResY = c.Height
      '#Set Desktop ScreenSize
      c.Size = New Drawing.Size(System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width, System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height)
      '#Go FullScreen
       _SwapChain.IsFullScreen = True
      End If
End Sub

DirectX11 Device Creation (Win10)

The Device.CreateWithSwapChain function is not working with Windows10. You have to generate Device und Swapchain by yourself.


Declare variables

Private _Adapter As Adapter
Private _Factory As Factory
Private _Device As SlimDX.Direct3D11.Device

 


Generate Device

'
'#Factory
_Factory = New Factory
'#Set FeatureLevel
Dim Fl() As SlimDX.Direct3D11.FeatureLevel = {SlimDX.Direct3D11.FeatureLevel.Level_10_1, SlimDX.Direct3D11.FeatureLevel.Level_11_0}
'#Get Adaptercount
Dim AdpCount As Integer = _Factory.GetAdapterCount

'#Create Device
If AdpCount &amp;gt; 0 Then
  _Adapter = _Factory.GetAdapter(0)
  _Device = New SlimDX.Direct3D11.Device(_Adapter, SlimDX.Direct3D11.DeviceCreationFlags.Debug, Fl)
End If
'

 


Dispose variables

'
If Not _Adapter Is Nothing Then _Adapter.Dispose()
If Not _Factory Is Nothing Then _Factory.Dispose()
If Not _Device Is Nothing Then _Device.Dispose()
'

All SlimDX object musst be disposed by yourself. Net does not do that for us in that case!