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?