Skip to content

Commit

Permalink
Async readback from GPU
Browse files Browse the repository at this point in the history
  • Loading branch information
leadedge committed Oct 2, 2020
1 parent 36e52df commit ca231db
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 49 deletions.
135 changes: 95 additions & 40 deletions SpoutDX/source/SpoutDX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
// 24.09.20 - Move try/catch to OpenDX11shareHandle in SpoutDirectX class
// 25.09.20 - Clean up ReceiveSenderData to allow for failure of OpenDX11shareHandle
// Compare share handle to detect a new sender instead of the name
// 02.10.20 - Async readback from GPU using two staging textures
//
// ====================================================================================
/*
Expand Down Expand Up @@ -103,7 +104,10 @@ spoutDX::spoutDX()
// Initialize variables
m_pd3dDevice = nullptr;
m_pImmediateContext = nullptr;
m_pStagingTexture = nullptr;
m_pStaging[0] = nullptr;
m_pStaging[1] = nullptr;
m_Index = 0;
m_NextIndex = 0;
m_pSharedTexture = nullptr;
m_dxShareHandle = nullptr;
m_SenderNameSetup[0] = 0;
Expand Down Expand Up @@ -167,11 +171,17 @@ void spoutDX::CleanupDX11()
{
SpoutLogNotice("spoutDX::CleanupDX11()");

if (m_pStagingTexture)
m_pStagingTexture->Release();

if (m_pSharedTexture)
m_pSharedTexture->Release();
m_pStagingTexture = nullptr;

if (m_pStaging[0]) m_pStaging[0]->Release();
if (m_pStaging[1]) m_pStaging[1]->Release();
m_pStaging[0] = nullptr;
m_pStaging[1] = nullptr;
m_Index = 0;
m_NextIndex = 0;

m_pSharedTexture = nullptr;
m_dxShareHandle = nullptr;

Expand Down Expand Up @@ -218,15 +228,19 @@ void spoutDX::ReleaseSender()
if (m_pSharedTexture)
m_pSharedTexture->Release();

if (m_pStagingTexture)
m_pStagingTexture->Release();
if (m_pStaging[0]) m_pStaging[0]->Release();
if (m_pStaging[1]) m_pStaging[1]->Release();

if (m_bSpoutInitialized)
spoutsender.ReleaseSenderName(m_SenderName);

m_pStagingTexture = nullptr;
m_pStaging[0] = nullptr;
m_pStaging[1] = nullptr;
m_Index = 0;
m_NextIndex = 0;
m_pSharedTexture = nullptr;
m_dxShareHandle = nullptr;

m_Width = 0;
m_Height = 0;
m_SenderName[0] = 0;
Expand Down Expand Up @@ -518,8 +532,7 @@ bool spoutDX::ReceiveTexture(ID3D11Texture2D** ppTexture)

}


// Receive an rgba image from a sender via DX11 staging texture
// Receive an rgba image from a sender via DX11 staging textures
// A new shared texture pointer (m_pSharedTexture) is retrieved if the sender changed
bool spoutDX::ReceiveImage(unsigned char * pixels, bool bInvert)
{
Expand All @@ -534,14 +547,14 @@ bool spoutDX::ReceiveImage(unsigned char * pixels, bool bInvert)
// A new sender has been found or the one connected has changed.
// The staging texture must be the same size and format as the sender
// Create a new staging texture if it is a different size
CheckStagingTexture(m_Width, m_Height, m_dwFormat);
CheckStagingTextures(m_Width, m_Height, m_dwFormat);
// The application detects the change with IsUpdated().
return true;
}

// The pixel buffer is created after the first update
// So check here instead of at the beginning
if (!m_pStagingTexture || !pixels)
if (!pixels)
return false;

//
Expand All @@ -551,10 +564,8 @@ bool spoutDX::ReceiveImage(unsigned char * pixels, bool bInvert)
if (frame.CheckTextureAccess(m_pSharedTexture)) {
// Check if the sender has produced a new frame.
if (frame.GetNewFrame()) {
// Copy from the sender's shared texture to the staging texture.
m_pImmediateContext->CopyResource(m_pStagingTexture, m_pSharedTexture);
// Map the staging texture and retrieve the pixels
ReadRGBApixels(m_pStagingTexture, pixels, m_Width, m_Height, bInvert);
// Read from the sender GPU texture to CPU pixels via staging textures
ReadRGBAimage(pixels, m_Width, m_Height, bInvert);
}
// Allow access to the shared texture
frame.AllowTextureAccess(m_pSharedTexture);
Expand All @@ -574,14 +585,10 @@ bool spoutDX::ReceiveImage(unsigned char * pixels, bool bInvert)

}

// Receive from a sender via DX11 staging texture to an rgb buffer of variable size

// Receive from a sender via DX11 staging textures to an rgb buffer of variable size
bool spoutDX::ReceiveRGBimage(unsigned char * pData, unsigned int width, unsigned int height, bool bInvert)
{
// TODO - check if source is null initially
// Quit if no receiving pixels
if (!pData)
return false;

// Make sure DirectX is initialized
if (!OpenDirectX11())
return false;
Expand All @@ -591,14 +598,16 @@ bool spoutDX::ReceiveRGBimage(unsigned char * pData, unsigned int width, unsigne
// The sender name, width, height, format, shared texture handle and pointer have been retrieved.
if (m_bUpdated) {
// A new sender has been found or the one connected has changed.
// The staging texture must be the same size and format as the sender
// Create a new staging texture if it is a different size
CheckStagingTexture(m_Width, m_Height, m_dwFormat);
// The staging textures must be the same size and format as the sender
// Create a new staging textures if a different size
CheckStagingTextures(m_Width, m_Height, m_dwFormat);
// The application detects the change with IsUpdated().
return true;
}

if (!m_pStagingTexture)
// The pixel buffer is created after the first update
// So check here instead of at the beginning
if (!pData)
return false;

//
Expand All @@ -609,10 +618,8 @@ bool spoutDX::ReceiveRGBimage(unsigned char * pData, unsigned int width, unsigne
if (frame.CheckTextureAccess(m_pSharedTexture)) {
// Check if the sender has produced a new frame.
if (frame.GetNewFrame()) {
// Copy from the sender's shared texture to the staging texture.
m_pImmediateContext->CopyResource(m_pStagingTexture, m_pSharedTexture);
// Map the staging texture and retrieve the pixels
ReadRGBpixels(m_pStagingTexture, pData, width, height, bInvert);
// Read from the sender rgba GPU texture to CPU rgb pixels via staging textures
ReadRGBimage(pData, width, height, bInvert);
}
}

Expand All @@ -633,6 +640,41 @@ bool spoutDX::ReceiveRGBimage(unsigned char * pData, unsigned int width, unsigne

}

// Asynchronous Read-back using two staging textures
// One texture - approx 7 - 12 msec at 1920x1080
// Two textures - approx 2.5 - 3.5 msec at 1920x1080
bool spoutDX::ReadRGBimage(unsigned char* pixels, unsigned int width, unsigned int height, bool bInvert)
{
if (!m_pStaging[0] || !m_pStaging[1])
return false;

m_Index = (m_Index + 1) % 2;
m_NextIndex = (m_Index + 1) % 2;
// Copy from the sender's shared texture to the first staging texture
m_pImmediateContext->CopyResource(m_pStaging[m_Index], m_pSharedTexture);
// Map and read from the second while the first is occupied
ReadRGBpixels(m_pStaging[m_NextIndex], pixels, width, height, bInvert);

return true;

}

bool spoutDX::ReadRGBAimage(unsigned char* pixels, unsigned int width, unsigned int height, bool bInvert)
{
if (!m_pStaging[0] || !m_pStaging[1])
return false;

m_Index = (m_Index + 1) % 2;
m_NextIndex = (m_Index + 1) % 2;
// Copy from the sender's shared texture to the first staging texture
m_pImmediateContext->CopyResource(m_pStaging[m_Index], m_pSharedTexture);
// Map and read from the second while the first is occupied
ReadRGBApixels(m_pStaging[m_NextIndex], pixels, width, height, bInvert);

return true;

}

// Pop up SpoutPanel to allow the user to select a sender
void spoutDX::SelectSender()
{
Expand Down Expand Up @@ -981,7 +1023,7 @@ bool spoutDX::ReadRGBApixels(ID3D11Texture2D* pStagingTexture,
if (SUCCEEDED(hr)) {
// Copy the staging texture pixels to the user buffer
spoutcopy.rgba2rgba(mappedSubResource.pData, pixels, width, height, mappedSubResource.RowPitch, bInvert);
m_pImmediateContext->Unmap(m_pStagingTexture, 0);
m_pImmediateContext->Unmap(pStagingTexture, 0);
return true;
} // endif DX11 map OK

Expand All @@ -1003,12 +1045,16 @@ bool spoutDX::ReadRGBpixels(ID3D11Texture2D* pStagingTexture,
// Map the resource so we can access the pixels
D3D11_MAPPED_SUBRESOURCE mappedSubResource;
// Make sure all commands are done before mapping the staging texture
// Between 1.5 and 7 msec
m_pImmediateContext->Flush();
// Between 1.5 and 7 msec
// Map waits for GPU access
HRESULT hr = m_pImmediateContext->Map(pStagingTexture, 0, D3D11_MAP_READ, 0, &mappedSubResource);
if (SUCCEEDED(hr)) {

// Copy the staging texture pixels to the user buffer
if (m_dwFormat == 28) {
// If the texture format is RGBA it has to be converted to BGR for the staging texture copy
// If the texture format is RGBA it has to be converted to BGR by the staging texture copy
if (width != m_Width || height != m_Height) {
spoutcopy.rgba2bgrResample(mappedSubResource.pData, pixels, m_Width, m_Height, mappedSubResource.RowPitch, width, height, bInvert);
}
Expand All @@ -1021,10 +1067,13 @@ bool spoutDX::ReadRGBpixels(ID3D11Texture2D* pStagingTexture,
spoutcopy.rgba2rgbResample(mappedSubResource.pData, pixels, m_Width, m_Height, mappedSubResource.RowPitch, width, height, bInvert);
}
else {
// Approx 5 msec at 1920x1080
spoutcopy.rgba2rgb(mappedSubResource.pData, pixels, m_Width, m_Height, mappedSubResource.RowPitch, bInvert);
}
}
m_pImmediateContext->Unmap(m_pStagingTexture, 0);

// 0.03 msec
m_pImmediateContext->Unmap(pStagingTexture, 0);

return true;
} // endif DX11 map OK
Expand All @@ -1034,35 +1083,41 @@ bool spoutDX::ReadRGBpixels(ID3D11Texture2D* pStagingTexture,
} // end ReadRGBpixels


// Create a new class staging texture if it has changed size or does not exist yet
bool spoutDX::CheckStagingTexture(unsigned int width, unsigned int height, DWORD dwFormat)
// Create new class staging textures if changed size or do not exist yet
bool spoutDX::CheckStagingTextures(unsigned int width, unsigned int height, DWORD dwFormat)
{
if (!m_pd3dDevice)
return false;

D3D11_TEXTURE2D_DESC desc = { 0 };

if (m_pStagingTexture) {
if (m_pStaging[0] && m_pStaging[1]) {
// Get the size to test for change
m_pStagingTexture->GetDesc(&desc);
m_pStaging[0]->GetDesc(&desc);
if (desc.Width != width || desc.Height != height || desc.Format != (DXGI_FORMAT)dwFormat) {
// Staging texture must not be mapped
m_pStagingTexture->Release();
m_pStagingTexture = nullptr;
// Staging textures must not be mapped before release
m_pStaging[0]->Release();
m_pStaging[1]->Release();
m_pStaging[0] = nullptr;
m_pStaging[1] = nullptr;
m_Index = 0;
m_NextIndex = 0;
}
else {
return true;
}
}
else {
if (CreateDX11StagingTexture(width, height, (DXGI_FORMAT)dwFormat, &m_pStagingTexture)) {
if (CreateDX11StagingTexture(width, height, (DXGI_FORMAT)dwFormat, &m_pStaging[0])
&& CreateDX11StagingTexture(width, height, (DXGI_FORMAT)dwFormat, &m_pStaging[1])) {
return true;
}
}

return false;
}


// Create a DirectX 11 staging texture for read and write
bool spoutDX::CreateDX11StagingTexture(unsigned int width, unsigned int height, DXGI_FORMAT format, ID3D11Texture2D** pStagingTexture)
{
Expand Down
26 changes: 17 additions & 9 deletions SpoutDX/source/SpoutDX.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@
#define __spoutDX__

// Change the path as required
#include "SpoutCommon.h" // for dll build and utilities
#include "SpoutSenderNames.h" // for sender creation and update
#include "SpoutDirectX.h" // for creating DX11 textures
#include "SpoutFrameCount.h" // for mutex lock and new frame signal
#include "SpoutCopy.h" // for pixel copy
#include "SpoutUtils.h" // Registry utiities
#include "..\..\SpoutSDK\SpoutCommon.h" // for dll build and utilities
#include "..\..\SpoutSDK\SpoutSenderNames.h" // for sender creation and update
#include "..\..\SpoutSDK\SpoutDirectX.h" // for creating DX11 textures
#include "..\..\SpoutSDK\SpoutFrameCount.h" // for mutex lock and new frame signal
#include "..\..\SpoutSDK\SpoutCopy.h" // for pixel copy
#include "..\..\SpoutSDK\SpoutUtils.h" // Registry utiities

#include <direct.h> // for _getcwd
#include <TlHelp32.h> // for PROCESSENTRY32
Expand Down Expand Up @@ -162,7 +162,10 @@ protected :
ID3D11Device* m_pd3dDevice;
ID3D11DeviceContext* m_pImmediateContext;
ID3D11Texture2D* m_pSharedTexture;
ID3D11Texture2D* m_pStagingTexture;
ID3D11Texture2D* m_pStaging[2];
int m_Index;
int m_NextIndex;

HANDLE m_dxShareHandle;
DWORD m_dwFormat;
SharedTextureInfo m_SenderInfo;
Expand All @@ -181,10 +184,15 @@ protected :

bool ReceiveSenderData();
void CreateReceiver(const char * sendername, unsigned int width, unsigned int height, DWORD dwFormat);

// Read pixels via staging texture
bool ReadRGBApixels(ID3D11Texture2D* pStagingTexture, unsigned char* pixels, unsigned int width, unsigned int height, bool bInvert);
bool ReadRGBpixels (ID3D11Texture2D* pStagingTexture, unsigned char* pixels, unsigned int width, unsigned int height, bool bInvert);
bool CheckStagingTexture(unsigned int width, unsigned int height, DWORD dwFormat = DXGI_FORMAT_B8G8R8A8_UNORM);
bool ReadRGBpixels(ID3D11Texture2D* pStagingTexture, unsigned char* pixels, unsigned int width, unsigned int height, bool bInvert);
bool ReadRGBAimage(unsigned char* pixels, unsigned int width, unsigned int height, bool bInvert);
bool ReadRGBimage(unsigned char* pixels, unsigned int width, unsigned int height, bool bInvert);
bool CheckStagingTextures(unsigned int width, unsigned int height, DWORD dwFormat = DXGI_FORMAT_B8G8R8A8_UNORM);
bool CreateDX11StagingTexture(unsigned int width, unsigned int height, DXGI_FORMAT format, ID3D11Texture2D** pStagingTexture);

void SelectSenderPanel();
bool CheckSpoutPanel(char *sendername, int maxchars = 256);

Expand Down

0 comments on commit ca231db

Please sign in to comment.