/** * Captures the frame in BGRA format from the backbuffer. This method works but is really slow because it locks the backbuffer * during the copy and prevents any furthur rendering until the copy is complete. It also locks the backbuffer surface * immediately after requesting the render target data which is bad because it doesn't give the GPU time to prepare the data * for locking asynchronously. */ bool CaptureFrame_Slow(int captureWidth, int captureHeight, unsigned char*& outBgraFrame) { bool grabbed = false; // Grab the back buffer IDirect3DSurface9* pBackBuffer = nullptr; gGraphicsDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &pBackBuffer); // Backbuffer not available if (pBackBuffer == nullptr) { return false; } // Get backbuffer info D3DSURFACE_DESC srcDesc; pBackBuffer->GetDesc(&srcDesc); // Create/recreate the target textures and surfaces if needed if (gResizeTexture == nullptr || captureHeight != gBroadcastHeight || captureWidth != gBroadcastWidth) { SAFE_RELEASE(gCaptureSurface); SAFE_RELEASE(gResizeSurface); SAFE_RELEASE(gResizeTexture); gBroadcastHeight = captureHeight; gBroadcastWidth = captureWidth; // Allocate a texture for the stretch and copy if ( FAILED(gGraphicsDevice->CreateTexture(captureWidth, captureHeight, 1, D3DUSAGE_RENDERTARGET, srcDesc.Format, D3DPOOL_DEFAULT, &gResizeTexture, nullptr)) ) { ReportError("Error allocating resize texture"); return false; } if ( FAILED(gResizeTexture->GetSurfaceLevel(0, &gResizeSurface)) ) { ReportError("Error retrieving surface"); return false; } // Allocate a surface for capturing the final buffer if ( FAILED(gGraphicsDevice->CreateOffscreenPlainSurface(captureWidth, captureHeight, srcDesc.Format, D3DPOOL_SYSTEMMEM, &gCaptureSurface, nullptr)) ) { ReportError("Error allocating capture surface"); return false; } // Clear the surface to black if ( FAILED(gGraphicsDevice->ColorFill(gResizeSurface, nullptr, D3DCOLOR_ARGB(0, 0, 0, 0))) ) { ReportError("Error filling with black"); return false; } } // Stretch and copy the image to the correct area of the destination (black-bordering if necessary) float captureAspect = (float)captureHeight / (float)captureWidth; float srcAspect = (float)srcDesc.Height / (float)srcDesc.Width; RECT destRect; // Determine the destination rectangle if (captureAspect >= srcAspect) { float scale = (float)captureWidth / (float)srcDesc.Width; destRect.left = 0; destRect.right = captureWidth-1; destRect.top = (LONG)( ((float)captureHeight - (float)srcDesc.Height*scale) / 2 ); destRect.bottom = (LONG)( ((float)captureHeight + (float)srcDesc.Height*scale) / 2 ); } else { float scale = (float)captureHeight / (float)srcDesc.Height; destRect.top = 0; destRect.bottom = captureHeight-1; destRect.left = (LONG)( ((float)captureWidth - (float)srcDesc.Width*scale) / 2 ); destRect.right = (LONG)( ((float)captureWidth + (float)srcDesc.Width*scale) / 2 ); } // Do the stretch and copy if ( FAILED(gGraphicsDevice->StretchRect(pBackBuffer, nullptr, gResizeSurface, &destRect, D3DTEXF_LINEAR)) ) { ReportError("Error in StretchRect"); return false; } // Capture the results of the stretch and copy gGraphicsDevice->GetRenderTargetData(gResizeSurface, gCaptureSurface); // This is expensive since LockRect will block until GetRenderTargetData finishes D3DLOCKED_RECT rect; if ( FAILED(gCaptureSurface->LockRect(&rect, 0, D3DLOCK_READONLY)) ) { ReportError("Error locking rectangle"); gCaptureSurface->UnlockRect(); return false; } const int kPixelSize = 4; const int bgraWidthBytes = captureWidth * kPixelSize; const int bgraFrameBytes = captureWidth * captureHeight * kPixelSize; // Grab the free buffer from the streaming pool outBgraFrame = GetNextFreeBuffer(); // Copy the buffer if (outBgraFrame != nullptr) { for (int y = 0; y < captureHeight; ++y) { memcpy( outBgraFrame + y * bgraWidthBytes, (char*)rect.pBits + y * bgraWidthBytes, bgraWidthBytes ); } grabbed = true; } gCaptureSurface->UnlockRect(); pBackBuffer->Release(); return grabbed; }
/** * Captures the frame in RGBA format from the backbuffer. This method works but is really slow because it locks the backbuffer * during the copy and prevents any furthur rendering until the copy is complete. It also locks the backbuffer surface * immediately after requesting the render target data which is bad because it doesn't give the GPU time to prepare the data * for locking asynchronously. */ bool CaptureFrame_Slow(int captureWidth, int captureHeight, unsigned char*& outRgbaFrame) { // Create/recreate the target textures and surfaces if needed if (gResizeFBO == 0 || captureHeight != gBroadcastHeight || captureWidth != gBroadcastWidth) { // recreate resize FBO CreateResizeFBO(captureWidth, captureHeight); // recreate capture PBO CreateCapturePBO(captureWidth, captureHeight); gBroadcastHeight = captureHeight; gBroadcastWidth = captureWidth; } // strect to resize FBO RenderToResizeFBO(captureWidth, captureHeight); // read resize FBO to capture PBO { glBindFramebuffer(GL_FRAMEBUFFER, gResizeFBO); glBindBuffer(GL_PIXEL_PACK_BUFFER, gCapturePBO); glReadPixels(0, 0, captureWidth, captureHeight, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); glBindFramebuffer(GL_FRAMEBUFFER, 0); } // lock PBO for reading void* buffer = glMapBuffer(GL_PIXEL_PACK_BUFFER , GL_READ_ONLY); if ( buffer == nullptr ) { ReportError("Error locking PBO"); return false; } const int kPixelSize = 4; const int rgbaWidthBytes = captureWidth * kPixelSize; const int bgraFrameBytes = captureWidth * captureHeight * kPixelSize; // Grab the free buffer from the streaming pool outRgbaFrame = GetNextFreeBuffer(); if ( outRgbaFrame != nullptr ) { memcpy(outRgbaFrame, buffer, bgraFrameBytes); } GLboolean success; success = glUnmapBuffer(GL_PIXEL_PACK_BUFFER); if ( !success ) { ReportError("Error unlocking PBO"); glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); return false; } glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); return true; }