Jump to content
  • Advertisement
Sign in to follow this  
Tril

Flip the backbuffer to the frontbuffer faster than the render loop

This topic is 3858 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I'm using DirectX 9 (August 2007 DirectX SDK) on Windows XP. I don't know if what I'm trying to do is possible with DirectX and if it is, I don't know how to do it properly. I'm using an NVIDIA 8600 GT video card. I'm working on frame-sequential stereoscopy with shutter glasses. I'm running my crt monitor at a refresh rate of 120 Hz. I need to alternately display the left and right eyes frames at 120 fps. Two situations can happen : - I render faster than 120 fps but I want to display at 120 fps. - I render slower than 120 fps but I want to display at 120 fps. The first situation is easy to solve. I just need to activate vsync. I use what's built-in DirectX (D3DPRESENT_INTERVAL_DEFAULT or D3DPRESENT_INTERVAL_ONE) or I do it myself by using IDirect3DDevice9::GetRasterStatus with a timer, etc. I'm having problems with the second situation. Let's say I have a render loop that runs at 30 fps but I want to flip the backbuffer to the frontbuffer at 120 fps. Here's some code to show what I'm trying to do.
LPDIRECT3DDEVICE9		pD3DDevice;
IDirect3DSurface9 *		m_pRenderSurfRender;
IDirect3DSurface9 *		m_pRenderSurfLeft;
IDirect3DSurface9 *		m_pRenderSurfRight;
IDirect3DSurface9 *		m_pRenderSurfBackBuffer;

// Function with my render loop that runs at 30 fps
renderFunc()
{
	bool Left = true;

	HRESULT hr=f_pD3DDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &m_pRenderSurfBackBuffer);
	if(SUCCEEDED(hr))
	{
		D3DSURFACE_DESC desc;
		hr=m_pRenderSurfBackBuffer->GetDesc(&desc);
		if(SUCCEEDED(hr))
		{	
			hr=pD3DDevice->CreateRenderTarget(desc.Width, desc.Height, desc.Format, desc.MultiSampleType, desc.MultiSampleQuality, FALSE, &m_pRenderSurfRender, NULL);
			hr=pD3DDevice->CreateRenderTarget(desc.Width, desc.Height, desc.Format, desc.MultiSampleType, desc.MultiSampleQuality, FALSE, &m_pRenderSurfLeft, NULL);
			hr=pD3DDevice->CreateRenderTarget(desc.Width, desc.Height, desc.Format, desc.MultiSampleType, desc.MultiSampleQuality, FALSE, &m_pRenderSurfRight, NULL);
		}
	}

	while (1)
	{
		// delay of 33.3333 ms (to simulate 30 fps)
	
		pD3DDevice->SetRenderTarget(0, m_pRenderSurfRender);
		pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0,0,200), 1.0f, 0); // Clear to blue
	
		if( SUCCEEDED( pD3DDevice->BeginScene() ) )
		{
			// Do the rendering
			pD3DDevice-EndScene();
		}

		if (Left)
		{
			Left = false;
			pD3DDevice->StretchRect(m_pRenderSurfRender, NULL, m_pRenderSurfLeft, NULL, D3DTEXF_NONE);
		}
		else
		{
			Left = true;
			pD3DDevice->StretchRect(m_pRenderSurfRender, NULL, m_pRenderSurfRight, NULL, D3DTEXF_NONE);
		}
	}
}


// Display function in another thread than the render loop to flip the backbuffer to the frontbuffer at 120 fps
displayFunc()
{	
	bool displayLeft = true;

	while (1)
	{
		// delay of 8.3333 ms here (to simulate 120 fps)
		
		if (displayLeft) // Copy left eye frame to backbuffer
		{
			displayLeft = false;
			pD3DDevice->StretchRect(m_pRenderSurfLeft, NULL, m_pRenderSurfBackBuffer, NULL, D3DTEXF_NONE);
		}
		else // Copy right eye frame to backbuffer
		{
			displayLeft = true;
			pD3DDevice->StretchRect(m_pRenderSurfRight, NULL, m_pRenderSurfBackBuffer, NULL, D3DTEXF_NONE);
		}
	
		hrPresent=pD3DDevice->Present(pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion);
	}
}
Basically, I thought that I could render to a surface that's not the backbuffer and copy that surface to another surface (left or righ eye surface). Then in a function in another thread, I copy the left or right eye surface to the backbuffer and call Present. I see some problems with this method : - Present is meant to be called outside of BeginScene/EndScene pairs. I'm not always calling it outside of BeginScene/EndScene pairs because I want to flip the backbuffer and frontbuffer faster than I render. - The code can call StretchRect on the same surface at the same time from two threads (as dest parameter in one thread and as source parameter in the other). Do I need to prevent this? Can I simply use critical sections around every call to StretchRect? - I don't know if there are any issues I should be aware of when calling DirectX functions from different threads. Even thought I thought it would not work, I tried it anyway. The drawing randomly locks up after a few seconds of running my test app. When that happens if I still try to call Present in the other thread, Present returns D3DERR_DRIVERINTERNALERROR a few times and then it returns D3DERR_INVALIDCALL afterward. I'm guessing that happens because Present is not made to be called outside of BeginScene/EndScene pairs or because of something else. I'll try to run the debug runtime to get more precise error messages. On MSDN library, in IDirect3DDevice9::Present, it says the following :
Quote:
IDirect3DDevice9::Present will fail, returning D3DERR_INVALIDCALL, if called between BeginScene and EndScene pairs unless the render target is not the current render target (such as the back buffer you get from creating an additional swap chain). This is a new behavior for Direct3D 9.
Because of the use of the word "unless", I'm left to believe that DirectX can support calling Present outside of BeginScene/EndScene pairs without returning errors. I'm new to DirectX so I thought I would ask more experienced users for advice. I would love to get this working. NVIDIA Stereo drivers support flipping the backbuffer to the frontbuffer at 120 fps on NVIDIA series 5, 6 and 7 video cards. I don't know if they did it with DirectX alone or if they used some tricks only they can do because they designed the video card.

Share this post


Link to post
Share on other sites
Advertisement
I am not sure I understand really what you are trying to accomplish, but seems to me quite impossible to present backbuffer before it is ready to be presented. You can't use somethign you don't have, otherwise we would all have 1000 fps in our games :-)

I don't know how to draw to front buffer directly (in opengl you don't have to use double buffering), probably there is some method to render directly in directx as well. But then as known you will get tearing.

Instead you could render to textures in forehand and cash those and then just play those for a user. But that would be only playback in real time, not really real time app, and also depending on how big lag you have, you could eventually get out of prerendered images and be back to square 1.

If you are rendering "slower" then 120 fps (or any other desired fps), then you have met standard problem in realtime graphics industry: GETTING MOOOORE FPPSSSS!

Entire realtime graphics are about that, so just start to optimize your engine, shaders, art ... :)

As a hack maybe you could "replay" frame or two, or maybe few. Choosing few different frames to play twice might get unnoticed (don't choose frames too close to each other). But if you are legging behind too many frames it will got noticed, but I believe if you display every frame twice, you will be rendering at 60 fps, and displaying at 120, but then I am not sure you can achieve that so easy, since you probably have to render to texture and then copy texture to framebuffer.

Another option is to lower your fps demand. Why can't 60 or 80 do?

I might missunderstand what you ment, so forgive me if answer is inappropriate or trivial :-).

Quote:
The code can call StretchRect on the same surface at the same time from two threads (as dest parameter in one thread and as source parameter in the other). Do I need to prevent this? Can I simply use critical sections around every call to StretchRect?
This seems like a standard race condition and you should probably tackle it so. If "dest" thread didn't finnished, what will source see? I don't know if dx internally do locking/unlocking, but when looking in older tutorials and lierature, I see that one shoud lock surfaces before use and unlock after. Try and see what happends if you synchronize.

Share this post


Link to post
Share on other sites
What happens is that I want to display at 120 fps (my CRT monitor refresh rate) while I don't know the rendering fps. The rendering fps is usually slower than the displaying fps.

The way I tried to do it before was not a good idea. It was sure to fail but I did not know any better way so I tried anyway.

I render a left and right eye view alternatively. I also display a left eye and right eye view alternatively. If a new frame is not ready, I want to display a previously rendered frame.

I did not know how to do that before a few days ago. I found a way of doing it in the Direct3D9Ex in the Windows SDK. It renders in two differents threads at the same time. It shows how to use a shared texture to pass the rendered surface from one thread to the other and how to set the priority to keep one rendering thread fast while keeping the other slower.

It uses Direct3D9Ex so it only works on Vista.

In the foreground thread that does the rendering, it does the following :
    // set the realtime GPU thread priority
g_pDevRealTime->SetGPUThreadPriority( 7 );
SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_HIGHEST );

and in the background thread it does :
	// Set the GPU thread priority
pDevBackground->SetGPUThreadPriority( -7 );
SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_LOWEST );

That's the only sample I've found that shows how SetGPUThreadPriority might be used.

This sample is used to show how to keep the mouse fluid while the background updates slowly but that's not what I want to do so I make some changes to it.

In my case, I remove the displaying of the mouse. I make a second shared texture. I alternatively render two differents views in the background thread and copy them to their respective shared texture. In the foreground, I alternatively copy the two shared textures to the backbuffer and display them.

This way I successfully display alternatively left and right eye frames at the monitor refresh rate no matter the background thread rendering fps.

Now, I need to try this in a game environment to see if it works well and if the method does not cause any significant performance penalty.

Share this post


Link to post
Share on other sites
Quote:
I alternatively render two differents views in the background thread and copy them to their respective shared texture. In the foreground, I alternatively copy the two shared textures to the backbuffer and display them.
Sounds like you got it to work.

Quote:
What happens is that I want to display at 120 fps (my CRT monitor refresh rate) while I don't know the rendering fps. The rendering fps is usually slower than the displaying fps.
Yes of course. And you can only display as many frames as you have rendered, unless you do something like repeating a frame.

If you go back to your very first post and part of it:
Quote:

- I render slower than 120 fps but I want to display at 120 fps.


You can't display more then you render. If you render say 100 frames per second, how would you then display 120? Where those 20 comes from? You have to either cheat and display some frames more then once or you have to prerender some cache and then do playback or you have to increase your frame per second rate :-). I don't think it has to do a lot with threading or how many cameras you render with (I mean same problem would occur with some very complex scene if you had only 1 camera and 1 thread).

But since you got it to work, it means that your game can keep up with 240 fps. Hope you can keep those :-).



[Edited by - joe_bubamara on May 26, 2008 5:23:44 PM]

Share this post


Link to post
Share on other sites
It has to do with how the shutter glasses work. They turn dark one eye after the other. They do that at the refresh rate of the monitor. They use some electronics to detect the VSYNC signal on one of the pins of the VGA connector of a CRT monitor.

In Direct3D, if you call Present and vertical sync is activated, the back buffer is flipped to the front buffer at the next VSYNC pulse. If you don't call Present between two VSYNC pulses, the video card will just display again the last frame it received when Present was last called.

This causes a problem with shutter glasses. Since they are synced to the monitor refresh rate, if you render the same frame twice, the two eyes get flipped. Your left eye will see the right eye frame, and your right eye will see the left eye frame.

It's important to perfectly keep alternating between left and right frames at the necessary fast pace. You have to call Present to flip them and if you don't do it fast enough (and you can't if rendering takes too long), you show the same frame twice in a row.

The reason why I talk about 120 Hz is because when the shutter glasses flash on and off at that rate, you don't notice that they flicker. It's a too high frequency for the eye to notice. However, at 60 Hz, you clearly see the glasses flash on and off and it's not pleasant.

Share this post


Link to post
Share on other sites
ok. I knew about general idea of how stereoscopic rendering works, but I didn't knew of this one:

Quote:
Since they are synced to the monitor refresh rate, if you render the same frame twice, the two eyes get flipped. Your left eye will see the right eye frame, and your right eye will see the left eye frame.
:-). Cool to know.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!