Skip to main content
GameDev.net gamedev.net
🔒 Locked

Multiple Devices vs Multiple Swap Chains

Started by Simplicity Jan 14, 2006 at 4:08 AM 16 replies 24.2k views
Original Post
Simplicity
Simplicity
My intension is to have rendering to different windows, each can have their own different dimension, viewports, colorformat, etc. It seems like if I create multiple swap chains on a single device, they must have the same dimension and colorformat. So is multiple devices the only way to go?
jollyjeffers
jollyjeffers
That sounds about right to me... Swap chains have a number of limitations to them (as you've seen!) so if you can't live within them you'll have to use multiple devices.

In your situation, the really useful part about multiple swap chains is that you can share resources/rendering. With multiple devices you have to have multiple resources - no sharing of textures/geometry etc..

hth
Jack
<hr align="left" width="25%" />
Jack Hoxley <small>[</small><small> Forum FAQ | Revised FAQ |
Namethatnobodyelsetook
Namethatnobodyelsetook
Swap chains have their own parameters, with their own size, color format, and multisample settings. Their biggest limitation is that they're still tied to the adapter your device was created for. You can look into supporting multi-head, which may get you swapchains on one device able to span two monitors driven by the same graphics card.
Simplicity
Simplicity
If you create 2 windows, one with the dimension 800x600 and another with a dimension 400x400 and create 2 swap chains with the corresponding parameters. Setting the viewport for the first window/swap chain to 800x600 will render to the entire client area. Setting the viewport for the second window/swap chain to 400x400, you would expect it to render to the entire client area, but in fact it doesn't. I will be scaled according to the first swap chain's dimension and will only render partially and "screwed up".

If this is the case, why would you want to create multiple swap chains anyway? If we wish to have multiple views on different window, we could always override the hWnd parameter in the Present function right?

What is the equivalent of creating multiple render contexts for OpenGL and switching context? Is it like creating multiple devices / swap chain, or total a different concept?
Namethatnobodyelsetook
Namethatnobodyelsetook
I've had no problems with various sized swap chains. Here's the general flow.

Init:
Create device 800x600 w/ autodepthstencil
Restore()

On Lost Device: (ie: before reset or exit)
release depthstencil for swapchain
release backbuffer for swapchain
Release swapchain
Release device depthstencil
Release device backbuffer

On Restore: (ie: after reset or init)
Get backbuffer surface
Get DepthStencil surface
Create swap chain 400x400
Get swapchain back buffer
Create depth stencil surface 400x400 for use with swap chain

Render:
BeginScene
SetRenderTarget(device's backbuffer)
SetDepthStencil(device's depthstencil)
SetViewport(0,0,800x600)
Clear Target/Depth/Stencil
Draw
EndScene
Device present

BeginScene
SetRenderTarget(swapchain back buffer)
SetDepthStencil(depthstencil we made for swapchain)
SetViewport(0,0,400,400)
Clear Target/Depth/Stencil
Draw
EndScene
Swap chain present

The advantage of swapchains over overriding the hwnd in Present is precisely to get a different sized backbuffer and/or AA settings to avoid the scaling problems you seem to be having.
Simplicity
Simplicity
Check it out!

This first image is using the method you described:
Primary swap chain: 800x600
Secondary swap chain: 400x400

SetViewport for the first one: (0, 0, 800, 600)

SetViewport for the second one: (0, 0, 400, 400 )



While doing this:

Setviewport for the second one: (0, 0, 800, 600)



So the method I used to "kind of" solve the problem is to allow the user to specify the viewport size by percentage. Then, when setting the viewport I just multiply it by the primary swap chain size.
jollyjeffers
jollyjeffers
Those two images you've posted are on some sort of password-protected server. They kept popping up login dialogs for me whenever I viewed this thread [smile]

I've just crossed them out now until you can fix the permissions or find a different host.

Cheers,
Jack
<hr align="left" width="25%" />
Jack Hoxley <small>[</small><small> Forum FAQ | Revised FAQ |
Simplicity
Simplicity
Is the problem with the images fixed now?
jollyjeffers
jollyjeffers
Quote:
Original post by Simplicity
Is the problem with the images fixed now?

Yup, I can see them now. Thanks for sorting that one out [smile]

Jack
<hr align="left" width="25%" />
Jack Hoxley <small>[</small><small> Forum FAQ | Revised FAQ |
Namethatnobodyelsetook
Namethatnobodyelsetook
#include "windows.h"#include "d3d9.h"#include "d3dx9.h"#ifdef _DEBUG#pragma comment(lib, "d3d9.lib")#pragma comment(lib, "d3dx9d.lib")#else#pragma comment(lib, "d3d9.lib")#pragma comment(lib, "d3dx9.lib")#endifHINSTANCE g_3DhInst;ATOM g_3DWinClass;HWND g_3DWindow=0;HWND g_3DFocusWindow=0;DWORD g_3DWindowStyleWin;DWORD g_3DWindowStyleFullScreen;DWORD g_3DWindowStyleExWin;DWORD g_3DWindowStyleExFullScreen;D3DPRESENT_PARAMETERS g_d3dpp;LPDIRECT3D9 g_pD3D=0;LPDIRECT3DDEVICE9 g_pDev=0;bool g_bResetDevice = false;char g_strWinClass[] = "QuickDX9";char g_strWinTitle[] = "TestApp";LRESULT WINAPI TestWinProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam){	if (msg==WM_CLOSE)	{		PostQuitMessage(0);	}	else if (msg==WM_SIZE)	{		g_bResetDevice = true;	}	else if (msg==WM_KEYDOWN && wParam == VK_ESCAPE)	{		PostQuitMessage(0);	}	return DefWindowProc(hWnd, msg, wParam, lParam);}bool RegisterWin(){	WNDCLASSEX wndclass;	memset(&wndclass, 0, sizeof(wndclass));	wndclass.cbSize = sizeof(WNDCLASSEX);	wndclass.style = CS_OWNDC;	wndclass.lpfnWndProc = TestWinProc;	wndclass.hInstance = g_3DhInst;	wndclass.lpszClassName = g_strWinClass;	g_3DWinClass = RegisterClassEx(&wndclass);	return g_3DWinClass != 0;}bool CreateWin(){	RECT rc;	g_3DWindowStyleFullScreen = g_3DWindowStyleWin = WS_OVERLAPPEDWINDOW | WS_VISIBLE;	g_3DWindowStyleExFullScreen = g_3DWindowStyleExWin = WS_EX_OVERLAPPEDWINDOW;	rc.left = 0;	rc.top = 0;	rc.right = 800;	rc.bottom = 600;	AdjustWindowRectEx(&rc, g_3DWindowStyleWin, false, g_3DWindowStyleExWin);	g_3DFocusWindow = CreateWindowEx(g_3DWindowStyleExWin, g_strWinClass, g_strWinTitle, g_3DWindowStyleWin, 0, 0, rc.right - rc.left, rc.bottom - rc.top, 0, 0, g_3DhInst, 0);	g_3DWindow = g_3DFocusWindow;	return g_3DWindow != 0;}bool MsgPump(){	MSG msg;	static bool exit = false;	if (exit)		return false;	while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))	{		if (msg.message == WM_QUIT)		{			exit = true;			return false;		}		TranslateMessage(&msg);		DispatchMessage(&msg);	}	return true;}void Cleanup(){	if (g_pDev)		g_pDev->Release();	if (g_pD3D)		g_pD3D->Release();	if (g_3DWindow)		DestroyWindow(g_3DWindow);}bool CreateD3D(){	g_pD3D = Direct3DCreate9(D3D_SDK_VERSION);	return g_pD3D != 0;}bool CreateDevice(){	RECT rc;	GetClientRect(g_3DWindow, &rc);	memset(&g_d3dpp, 0, sizeof(g_d3dpp));	g_d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;	g_d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;	g_d3dpp.EnableAutoDepthStencil = true;	g_d3dpp.AutoDepthStencilFormat = D3DFMT_D24X8;	g_d3dpp.hDeviceWindow = g_3DWindow;	g_d3dpp.BackBufferWidth = rc.right - rc.left;	g_d3dpp.BackBufferHeight = rc.bottom - rc.top;	g_d3dpp.BackBufferCount = 1;	g_d3dpp.Windowed = true;	g_d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE ;	if (FAILED(g_pD3D->CreateDevice(0, D3DDEVTYPE_HAL, g_3DFocusWindow, D3DCREATE_HARDWARE_VERTEXPROCESSING, &g_d3dpp, &g_pDev)))		return false;	return true;}void TestPreReset();bool TestPostReset();bool ResetDevice(){	TestPreReset();	if (FAILED(g_pDev->Reset(&g_d3dpp)))		return false;	return TestPostReset();}bool TestDevice(){	HRESULT hr = g_pDev->TestCooperativeLevel();	if (hr == D3DERR_DEVICENOTRESET || (hr == S_OK && g_bResetDevice))	{		g_bResetDevice = false;		if (!ResetDevice())			return false;	}	else if (hr == D3DERR_DEVICELOST)		return false;	return true;}HWND g_swapwnd = 0;LPDIRECT3DSWAPCHAIN9 g_pSwap = 0;D3DPRESENT_PARAMETERS g_scparam;LPDIRECT3DSURFACE9 g_pSwapDepthSurf = 0;LPDIRECT3DSURFACE9 g_pSwapRenderSurf = 0;LPDIRECT3DSURFACE9 g_pDevDepthSurf = 0;LPDIRECT3DSURFACE9 g_pDevRenderSurf = 0;bool TestSetup(){	RECT rc;	rc.left = 0;	rc.top = 0;	rc.right = 400;	rc.bottom = 400;	AdjustWindowRectEx(&rc, g_3DWindowStyleWin, false, g_3DWindowStyleExWin);	g_swapwnd = CreateWindowEx(g_3DWindowStyleExWin, g_strWinClass, "ChainWnd", g_3DWindowStyleWin, 800, 0, rc.right - rc.left, rc.bottom - rc.top, 0, 0, g_3DhInst, 0);	return TestPostReset();}void TestCleanup(){	TestPreReset();	if (g_swapwnd)		DestroyWindow(g_swapwnd);}void TestPreReset(){	if (g_pSwapDepthSurf)		g_pSwapDepthSurf->Release();	g_pSwapDepthSurf = 0;	if (g_pSwapRenderSurf)		g_pSwapRenderSurf->Release();	g_pSwapRenderSurf = 0;	if (g_pSwap)		g_pSwap->Release();	g_pSwap = 0;	if (g_pDevRenderSurf)		g_pDevRenderSurf->Release();	g_pDevRenderSurf = 0;	if (g_pDevDepthSurf)		g_pDevDepthSurf->Release();	g_pDevDepthSurf = 0;}bool TestPostReset(){	memset(&g_scparam, 0, sizeof(g_scparam));	g_scparam.BackBufferCount = 1;	g_scparam.BackBufferFormat = D3DFMT_X8R8G8B8;	g_scparam.BackBufferHeight = 400;	g_scparam.BackBufferWidth = 400;	g_scparam.hDeviceWindow = g_swapwnd;	g_scparam.SwapEffect = D3DSWAPEFFECT_DISCARD;	g_scparam.Windowed = true;	g_scparam.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;	g_pDev->CreateAdditionalSwapChain(&g_scparam, &g_pSwap);	g_pSwap->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &g_pSwapRenderSurf);	g_pDev->CreateDepthStencilSurface(400,400,D3DFMT_D24X8, D3DMULTISAMPLE_NONE, 0, false, &g_pSwapDepthSurf, 0);	g_pDev->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &g_pDevRenderSurf);	g_pDev->GetDepthStencilSurface(&g_pDevDepthSurf);	return true;}class TestVert{public:	float x,y,z;	DWORD color;};TestVert g_aoVerts[4];D3DXMATRIX w,v,p;void TestUpdate(){	g_aoVerts[0].x = -1; 	g_aoVerts[0].y = -1; 	g_aoVerts[0].z =  0;	g_aoVerts[0].color = 0xFFFF0000;	g_aoVerts[1].x = -1; 	g_aoVerts[1].y =  1; 	g_aoVerts[1].z =  0;	g_aoVerts[1].color = 0xFFFFFF00;	g_aoVerts[2].x =  1; 	g_aoVerts[2].y = -1; 	g_aoVerts[2].z =  0;	g_aoVerts[2].color = 0xFFFF00FF;	g_aoVerts[3].x =  1; 	g_aoVerts[3].y =  1; 	g_aoVerts[3].z =  0;	g_aoVerts[3].color = 0xFFFFFFFF;	POINT pt;	GetCursorPos(&pt);	D3DXMatrixRotationYawPitchRoll(&w, (pt.x - 800.0f) / 800 * D3DX_PI / 2, (pt.y - 600.0f) / 600 * D3DX_PI / 2, 0);	w._43 = 2;	D3DXMatrixPerspectiveFovLH(&p, D3DX_PI/4, 1.0, 0.1f, 500.0f);	g_pDev->SetTransform(D3DTS_WORLD, &w);	g_pDev->SetTransform(D3DTS_PROJECTION, &p);	D3DXMatrixIdentity(&w);	g_pDev->SetTransform(D3DTS_VIEW, &w);}void TestRender(){	g_pDev->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);	g_pDev->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_DIFFUSE);	g_pDev->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);	g_pDev->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE);	g_pDev->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE);	g_pDev->SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE);	g_pDev->SetRenderState(D3DRS_LIGHTING, false);	g_pDev->SetFVF(D3DFVF_XYZ|D3DFVF_DIFFUSE);	g_pDev->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);	g_pDev->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, g_aoVerts, sizeof(TestVert));}int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR cmdLine, int cmdShow){	g_3DhInst = hInst;	do	{		if (!RegisterWin())			break;		if (!CreateWin())			break;		if (!CreateD3D())			break;		if (!CreateDevice())			break;		if (!TestSetup())			break;		while (MsgPump())		{			if (TestDevice())			{				D3DVIEWPORT9 vp;				vp.X = 0;				vp.Y = 0;				vp.Width = 800;				vp.Height = 600;				vp.MinZ = 0;				vp.MaxZ = 1;				TestUpdate();				g_pDev->SetRenderTarget(0, g_pDevRenderSurf);				g_pDev->SetDepthStencilSurface(g_pDevDepthSurf);				g_pDev->SetViewport(&vp);				g_pDev->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x456789, 1.0f, 0);				g_pDev->BeginScene();				TestRender();				g_pDev->EndScene();				g_pDev->Present(0,0,0,0);				g_pDev->SetRenderTarget(0, g_pSwapRenderSurf);				g_pDev->SetDepthStencilSurface(g_pSwapDepthSurf);				vp.Width = 400;				vp.Height = 400;				g_pDev->SetViewport(&vp);				g_pDev->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x654321, 1.0f, 0);				g_pDev->BeginScene();				TestRender();				g_pDev->EndScene();				g_pSwap->Present(0, 0, 0, 0, 0);			}		};	} while (0);	TestCleanup();	Cleanup();	return 0;}

This code works just fine for me. Compare carefully to yours.
jollyjeffers
jollyjeffers
Nice one ntnet [smile]

I'll stick this to the top of the forum for a little bit as a useful resource. This is definitely not the first time this sort of question has been asked, and I doubt it'll be the last time - when I get around to refreshing the FAQ I'll see about getting this included..

Cheers,
Jack
<hr align="left" width="25%" />
Jack Hoxley <small>[</small><small> Forum FAQ | Revised FAQ |
LarsMiddendorf
LarsMiddendorf
Is there a problem when doing the following for a MDI application.

1)Create one backbuffer with the size of the MDI parent window.
Use D3DSWAPEFFECT_COPY.

Then foreach MDI child
2)Before rendering to a child window, change viewport.
3)Override Window Handle and Rectangle at Device.Present
Namethatnobodyelsetook
Namethatnobodyelsetook
It should work, but it assumes a few things.

1) The MDI childs don't overlap. It'll work for splitters.
2) The app uses most of the space. If you have a few windows with lots of MDI background space you've got lots of allocated backbuffer space for nothing.
2.5) Then again, if you always draw and present each child, your backbuffer can be as small as the largest child window.
3) It assumes you want MDI. Microsoft has long since stopped recommending MDI. Sometimes a nice popup that can leave the parent is much better.
4) Slow resizing. The only way to reset the main swapchain is to reset the device, which takes a while. A swapchain can be destroyed and recreated in response to a size command really quickly. My sample above didn't take advantage of that, but it's a nice bonus of swapchains.
LarsMiddendorf
LarsMiddendorf
Can I delete the default swapchain? For example what should be done if the window with the default swap chain is closed and the other window with the additional swap chain remains open?
kovacsp
kovacsp
Hi,

my soulution for the window deleting question is:
I create my device with 1x1 size, and use the default swap chain for nothing at all. Then, for all my windows, I create a separate swap chain and depth buffer. They can have arbitrary sizes, AA settings, etc (at least, on nvidia cards, as Ati drivers still have some problem with AA on swap chain no 2, no. 3, etc.., but they promised to correct it soon)

kp
------------------------------------------------------------Neo, the Matrix should be 16-byte aligned for better performance!
LarsMiddendorf
LarsMiddendorf
Aha, thanks. That could be a good solution to remove the non-orthogonality between the default and additional swap chains.
I hope this doesn't degrade performace. On NVidia cards there is/was sometimes only early-Z on the first FrameBuffer Object (OpenGL.)
kovacsp
kovacsp
Hi,

I didn't notice any difference in speed. And yes, it's pretyy comfortable to handle all my windows the same way :)

kp
------------------------------------------------------------Neo, the Matrix should be 16-byte aligned for better performance!
slack
slack
Quote:
Original post by kovacsp
Hi,

my soulution for the window deleting question is:
I create my device with 1x1 size, and use the default swap chain for nothing at all. Then, for all my windows, I create a separate swap chain and depth buffer. They can have arbitrary sizes, AA settings, etc (at least, on nvidia cards, as Ati drivers still have some problem with AA on swap chain no 2, no. 3, etc.., but they promised to correct it soon)

kp


For awhile, I created the device using one of my render view controls and the others as additional swap chains. If the first view was destroyed, the next control would be initialized to be the default swap chain. Then, I did exactly kovacs approach with the 1x1 unused swap chain and I've had no problems since then.

As a side note since this topic is comparing multiple swap chains versus device, I have had performance issues dealing with a single device and dual monitors. I use a single device object and have made no changes to accomodate a second monitor, but D3D or the device drivers seem to handle things behind the scenes. For optimal performance with multiple monitors, it looks like multiple device objects are needed if you have the resources to duplicate your textures/vertex buffers/etc.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.