This is going to be a very brief tutorial; the idea for it came about from a comment on my very first tutorial about using multiple viewports. I assumed that using multiple viewports would be a simple matter of just calling a SetViewport method just like in DX9, but it isn’t. I tried finding some info online but there is absolutely nothing available so I had to figure it out on my own. There are two methods to get multiple viewports working. The first requires a state change when selecting the viewports but I don’t think that the cost of that is too prohibitive since you would probably only swap viewports once per viewport during the scene rendering. The second method involves using a geometry shader to specify which viewport to use during the clipping/screen mapping stage in the pipeline.
What is a viewport
Well lets first discuss what a viewport actually is, if you Google a bit you’ll find almost no information regarding viewports or what they actually are (and there is absolutely no info in the DX documentation). A viewport is a rectangle that defines the area of the frame buffer that you are rendering to. Viewports do have depth values which affect the projected z range of any primitives in the viewport but this is only used in very advanced cases so you should always set the near depth to 0 and the far depth to 1. If we imagine a car game in which we have a rear view mirror, a simple method to draw the rear view mirror contents is to set the viewport to the mirror area, rotate the camera to face backwards and render the scene. Another common use in games is when you see another player’s viewpoint within your HUD (Ghost recon does this quite often), once again to render this all that is require is to set the viewport to the area of your final image you want to render to, then you render the scene from the other players viewpoint.
Setting up multiple viewports
The first step in using multiple viewports is to define the areas of the viewports. This is done by filling out a D3D10_VIEWPORT struct. In our case since we have two viewport we need an array of structs ( D3D10_VIEWPORT viewports[2] ):
//define viewports viewports[0].Width = width/2; viewports[0].Height = height; viewports[0].MinDepth = 0.0f; viewports[0].MaxDepth = 1.0f; viewports[0].TopLeftX = 0; viewports[0].TopLeftY = 0; viewports[1].Width = width/2; viewports[1].Height = height; viewports[1].MinDepth = 0.0f; viewports[1].MaxDepth = 1.0f; viewports[1].TopLeftX = width/2 - 5; viewports[1].TopLeftY = 0;
We’ve split our render target (frame buffer) into two viewports. If we set our viewport as normal using the RSSetViewports method and render our scene the scene will only be drawn to the left half of the window. So how do we make use of multiple viewports?
Setting the Active Viewport – Method 1
The first method to set the active viewport is to call the RSSetViewports Method with the viewport you wish to use prior to any rendering, once you call this method all future rendering will be done in that viewport. So if I wanted to render an object to each viewport the code would look something like this:
//SET VIEWPORT - METHOD 1 - Simple State Change //============================================================================= pBasicTechnique1->GetDesc( &techDesc ); //set viewport to viewports[0] pD3DDevice->RSSetViewports(1, &viewports[0]); for( UINT p = 0; p < techDesc.Passes; ++p ) { //apply technique pBasicTechnique1->GetPassByIndex( p )->Apply( 0 ); //draw pD3DDevice->Draw( 3, 0 ); } //set viewport to viewports[0] pD3DDevice->RSSetViewports(1, &viewports[1]); for( UINT p = 0; p < techDesc.Passes; ++p ) { //apply technique pBasicTechnique1->GetPassByIndex( p )->Apply( 0 ); //draw pD3DDevice->Draw( 3, 0 ); }
This method works but requires a state change to achieve this.
Setting the Active Viewport – Method 2
The second method is a little more involved. The first step is to set all the viewports you wish to use in your initialization method (you only need to do this once):
//set all required viewports pD3DDevice->RSSetViewports(2, viewports);
The second step involves creating a new scalar HLSL variable: activeViewport and writing a geometry shader that makes use of it. You all need to create a new output struct for the geometry shader that contains a variable with the SV_ViewportArrayIndex semantic. This semantic specifies which viewport the outputted primitive (triangle) should be sent to. The code below is for a simple geometry shader that receives a triangle and adds the viewport index necessary, then sends it out again.
uint activeViewport; struct GS_OUTPUT { float4 Pos : SV_POSITION; float4 Color : COLOR0; uint viewport : SV_ViewportArrayIndex; }; //-------------------------------------------------------------------------------------- // Geometry Shader //-------------------------------------------------------------------------------------- [maxvertexcount(3)] void GS( triangle PS_INPUT In[3], inout TriangleStream<GS_OUTPUT> TriStream ) { GS_OUTPUT output; output.viewport = activeViewport; for( int v = 0; v < 3; v++ ) { output.Pos = In[v].Pos; output.Color = In[v].Color; TriStream.Append( output ); } } //dont forget to include geometry shader in the technique technique10 RenderMethod2 { pass P0 { SetVertexShader( CompileShader( vs_4_0, VS() ) ); SetGeometryShader( CompileShader( gs_4_0, GS() ) ); SetPixelShader( CompileShader( ps_4_0, PS() ) ); } }
The rendering code will now change as well (remember to create a new ID3D10EffectScalarVariable* pActiveViewport and bind it to the new HLSL variable), the new rendering code is as follows, now this method should be more efficient than the first assuming you using constant buffers in your HLSL code, constant buffers are something I’d like to cover in a later tutorial). Anyways heres the new rendering code:
//SET VIEWPORT - METHOD 2 - Using a GS //============================================================================== pBasicTechnique2->GetDesc( &techDesc ); //set viewport to viewports[0] pActiveViewport->SetInt(0); for( UINT p = 0; p < techDesc.Passes; ++p ) { //apply technique pBasicTechnique2->GetPassByIndex( p )->Apply( 0 ); //draw pD3DDevice->Draw( 3, 0 ); } //set viewport to viewports[1] pActiveViewport->SetInt(1); for( UINT p = 0; p < techDesc.Passes; ++p ) { //apply technique pBasicTechnique2->GetPassByIndex( p )->Apply( 0 ); //draw pD3DDevice->Draw( 3, 0 ); }
And there we have it, the first DirectX10 viewport tutorial on the internet 😉
Tutorial 7 Source Code (VS2010 Solution):GitHub
Hey, you might want to handle freeing resources better.
C++ doesn’t have automatic memory handling, and D3D10 doesn’t hold references to the interfaces. So when you create a ‘new D3DXVECTOR’ it isn’t being freed. D3DXVECTOR class certainly doesn’t free the memory passed into it. It is expecting the caller to free the memory.
Ditto for ID3D10 Index Buffer, Vertex Buffer, etc.
It’s a bit sloppy for a demo, even though yes the memory does get freed when the process destroyed. Otherwise, it is a fine tutorial.
But question: Why render the scene in two passes? You can use the geometry shader to render both triangles – just loop through the viewpoint indices:
for (int viewportIndex = 0; viewportIndex < viewportCount; viewportIndex++)
{
output.viewport = viewportIndex;
for( int v = 0; v < 3; v++ )
{
output.Pos = In[v].Pos;
output.Color = In[v].Color;
TriStream.Append( output );
}
}
Just change maxvertexcount to like, maxViewports * 3 (e.g. 16*3 = 48)
Hi Matthew,
You are right about the resource handling but you also have to understand that it took me around 20minutes to write up the code for the tutorial, and that its not meant as a C++ tutorial but rather as a viewport tutorial. I will clean up the code tho first chance I get tho, since now its gonna be bother me that you mentioned it.
The rendering is done like that since you will obviously render more than just a triangle to the viewport. And there is no need to waste extra bandwidth per vertex adding in a viewport flag. If you use an HLSL cbuffer for the viewport then changing the viewport for rendered primitives is almost free.
for example in a racing game:
Using method 2 would it be faster to…
set viewport 1
set camera view to driver’s view
render scenery
set viewport 2
rotate camera 180 degrees
render scenery
set viewport 1
set camera view to driver’s view
render track
set viewport 2
rotate camera 180 degrees
render track
… presuming that the raster state will be changing and the technique will likely be different for each object type?
I’ve updated most of my tutorials to have better resource management, i could only spend around half an hour on it today so it might not be perfect, but its a start right? 🙂
The tutorials are meant as a starter to DX, not as a programming guideline or a framework.
Men du kan ju släcka alla dina lampor med ett snäpp med fingrarna, eller i alla fall ett tryck. Vet inte vad det heter men tex Kjell o Co och ClasOlson har sÃ¥dana här: man fjärrstyr sin belysning med en fjorrrkont¤Ãll. Kolla det!
Looks like you had such a fun NYE! Love all the pillsbury recipes…The bake-off rules are going to be posted soon, I really want to try to get in this year I was a finalist in 2008 and it was freakin amazing!
hurricanes and their destruction, wrath are always in the back of someone’s mind living near water and its unpredictable surfaces. Well written, Raven. Have a nice night.
but a basic level of programming discipline is important in any case
I totally agree. Being rushed is a bit of a lame excuse for not having proper clean up code but like I said please try to understand that I wrote most of the tutorials while juggling 2 full time jobs. That being said I have gone back and updated the tuts to include resource deallocation code.
If you actually take a look at the code, I think you’ll notice that it’s quite neat and well structured. Its a lot better than most of the free tutorial code samples out on the web.
Thank you for writing this tutorial Bobby. It helped me understand how viewports work. And the really good thing is that you wrote two ways to use viewports.
Gosh, the guy takes the time to share some knowledge and gets cut down for memory management! That’s gratitude for you! I for one am thankful that you took the time to share these tutorials – for free I might add – and don’t mind about shortcuts. After all, I’m not paying you to produce this material and you’ve demonstrated the core concept well. Cheers!