CPU blocking when doing GetRenderTargetData

Started by
1 comment, last by Temaran 10 years, 8 months ago

Hello everyone!

I've been battling with a problem the last week in regards to getting a copy of the back buffer of my game to system memory to do some very selective image processing. I've tried a few different approaches, among them everything here:

http://www.codeproject.com/Articles/5051/Various-methods-for-capturing-the-screen (Too slow)

I've also tried a few more direct approaches, among them using D3DXLoadSurfaceFromSurface and of course GetRenderTargetData. I do the capture every two seconds or so, and everytime the relevant code triggers, the game jolts for a split second. My latest working theory was that this is due to the CPU blocking while waiting for the graphics hardware to finish rendering to the surfaces involved. But after making some additional tests that seem to disprove that, I'm seriously stumped on what could be the cause.

Is the simple answer that it just cannot be done? But in that case, how does for example FRAPS capture the back buffer to system memory without stuttering? It doesn't seem like they are using a video driver according to these:

http://www.ring3circus.com/blog/2007/11/22/case-study-fraps/

https://frapsforum.com/threads/how-does-fraps-work.1405/

Even if they are manipulating it on the gfx card side more before actually getting it over to CPU land, they still have to stream it over to system memory some time.

Anyways, back to the main problem; my latest test tries to attack the problem of the graphics hardware potentially being busy by first doing a stretch rect operation to downsample the backbuffer before streaming to system memory in EndScene, and then waiting a couple of frames (to make sure that the stretching is complete), to finally do the GetRenderTargetData just before BeginScene those few frames later. I've tried a few other permutations such as trying to do the GRTD in endscene and present as well, but to no avail.

Here is my current code:


private int _waitedFrames = -1;


public void ExecuteD3D9(Device device, IGraphicsRequestReplies replyInterface, IReporter reporter, DateTime now)
        {
            if (_bbCopyRenderTarget == null)
            {
                using (var sc = device.GetSwapChain(0))
                    _bbCopyRenderTarget = Surface.CreateRenderTarget(device, _data.DownSampleSize.Width, _data.DownSampleSize.Height, sc.PresentParameters.BackBufferFormat, MultisampleType.None, 0, false);
            }


            if (_cpuSideOffscreenSurface == null)
            {
                using (var sc = device.GetSwapChain(0))
                    _cpuSideOffscreenSurface = Surface.CreateOffscreenPlain(device, _data.DownSampleSize.Width, _data.DownSampleSize.Height, sc.PresentParameters.BackBufferFormat, Pool.SystemMemory);
            }
            
            if (ShouldWeCheckThisFrame(now) && !_isProcessing && _waitedFrames == -1)
            {
                using (var bb = device.GetBackBuffer(0, 0))
                {
                        if (_data.DownSampleSize.Width > 0 && _data.DownSampleSize.Height > 0)
                            device.StretchRectangle(bb,
                                                    new Rectangle(0, 0, bb.Description.Width, bb.Description.Height),
                                                    _bbCopyRenderTarget, new Rectangle(new Point(), _data.DownSampleSize),
                                                    TextureFilter.None);


                        _waitedFrames = 0;
                        reporter.DebugMessage("STRETCH");
                }
            }


            if (_reportString != null && !_isProcessing)
            {
                _cachedString = _reportString;
                _reportString = null;
            }


            if (_data.DisplayDebugData)
                reporter.DisplayOnScreen(_cachedString);
        }


        public void ExecuteD3D9BeginScene(Device device, IGraphicsRequestReplies replyInterface, IReporter reporter, DateTime now)
        {
            if (_waitedFrames != -1)
            {
                _waitedFrames++;
                if (_waitedFrames >= 2)
                {
                    device.GetRenderTargetData(_bbCopyRenderTarget, _cpuSideOffscreenSurface);
                    reporter.DebugMessage("GET!!!");
                    _waitedFrames = -1;
                }
            }
        }

Even though there seems to be no way for the hardware to still be busy (The operation that causes the spike; GetRenderTargetData is performed two frames after the stretch, and is executed before BeginScene, so no new rendering operations are issued yet), I still get spikes on each GRTD.

Does anyone know why this could be?

Advertisement

You don't need to wait to call GetRenderTargetData, you need to wait before calling Lock on the surface that you copied the data to. The Lock is where the driver will sync to make sure the GPU is done writing, so you want to wait as long as possible before calling it.

Thank you for the reply!

However, in my test code which produces the stuttering effect, I never actually lock the SYSMEM surface. I thought the first step should be getting GetRenderTargetData to work by itself before adding more logic. If I comment that single line out, the stutter disappears, comment it back in again, and it reappears. So it is definitely GRTD that is the cause of the problem. Could it be then that it's simply the method call that is too slow?

But then again, if that were the case, is there anything to be done? And it makes me think about how FRAPS does it. Since they are able to, it seems like it should be doable without the stutter in some way.

This topic is closed to new replies.

Advertisement