From 03622f61b0ffb179edff3942e4d7b9f98f73074b Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Fri, 29 Dec 2017 20:53:52 +1100 Subject: [PATCH] [host] Added experimental H264 compression to DXGI (disabled by default) This is not yet working, the client is yet to be updated to support decompressing this stream. --- common/KVMFR.h | 2 +- common/debug.h | 5 + host/Capture/DXGI.cpp | 638 +++++++++++++++++++++++++++----- host/Capture/DXGI.h | 74 +++- host/MultiMemcpy.cpp | 24 +- host/MultiMemcpy.h | 21 +- host/Util.h | 21 ++ host/looking-glass-host.vcxproj | 16 +- host/main.cpp | 1 + 9 files changed, 687 insertions(+), 115 deletions(-) diff --git a/common/KVMFR.h b/common/KVMFR.h index 0dc9ab25..f7236bc3 100644 --- a/common/KVMFR.h +++ b/common/KVMFR.h @@ -27,7 +27,7 @@ typedef enum FrameType { FRAME_TYPE_INVALID , FRAME_TYPE_ARGB , // ABGR interleaved: A,R,G,B 32bpp - FRAME_TYPE_RGB , // RGB interleaved : R,G,B 24bpp + FRAME_TYPE_H264 , // H264 compressed FRAME_TYPE_MAX , // sentinel value } FrameType; diff --git a/common/debug.h b/common/debug.h index 508c8c1a..1b187dea 100644 --- a/common/debug.h +++ b/common/debug.h @@ -59,3 +59,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA #else #define DEBUG_PROTO(fmt, ...) do {} while(0) #endif + +#ifdef _WIN32 + #include "Util.h" + #define DEBUG_WINERROR(x, y) Util::DebugWinError(STRIPPATH(__FILE__), __LINE__, __FUNCTION__, x, y) +#endif diff --git a/host/Capture/DXGI.cpp b/host/Capture/DXGI.cpp index a4c6917d..885e609b 100644 --- a/host/Capture/DXGI.cpp +++ b/host/Capture/DXGI.cpp @@ -23,7 +23,24 @@ using namespace Capture; #include "common/debug.h" #include "TraceUtil.h" +#include +#include +#include +#include +#include +#include + +template void SafeRelease(T **ppT) +{ + if (*ppT) + { + (*ppT)->Release(); + *ppT = NULL; + } +} + DXGI::DXGI() : + m_cRef(1), m_options(NULL), m_initialized(false), m_dxgiFactory(), @@ -33,7 +50,7 @@ DXGI::DXGI() : m_texture(), m_pointer(NULL) { - + MFStartup(MF_VERSION); } DXGI::~DXGI() @@ -66,15 +83,15 @@ bool DXGI::Initialize(CaptureOptions * options) output->GetDesc(&outputDesc); if (!outputDesc.AttachedToDesktop) { - output.Release(); + SafeRelease(&output); continue; } m_output = output; if (!m_output) { - output.Release(); - adapter.Release(); + SafeRelease(&output); + SafeRelease(&adapter); DEBUG_ERROR("Failed to get IDXGIOutput1"); DeInitialize(); return false; @@ -83,7 +100,7 @@ bool DXGI::Initialize(CaptureOptions * options) m_width = outputDesc.DesktopCoordinates.right - outputDesc.DesktopCoordinates.left; m_height = outputDesc.DesktopCoordinates.bottom - outputDesc.DesktopCoordinates.top; - output.Release(); + SafeRelease(&output); done = true; break; } @@ -91,7 +108,7 @@ bool DXGI::Initialize(CaptureOptions * options) if (done) break; - adapter.Release(); + SafeRelease(&adapter); } if (!done) @@ -102,6 +119,8 @@ bool DXGI::Initialize(CaptureOptions * options) } static const D3D_FEATURE_LEVEL featureLevels[] = { + D3D_FEATURE_LEVEL_11_1, + D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_3, @@ -119,29 +138,54 @@ bool DXGI::Initialize(CaptureOptions * options) adapter, D3D_DRIVER_TYPE_UNKNOWN, NULL, - CREATE_FLAGS, + CREATE_FLAGS | D3D11_CREATE_DEVICE_VIDEO_SUPPORT, featureLevels, ARRAYSIZE(featureLevels), D3D11_SDK_VERSION, &m_device, &m_featureLevel, &m_deviceContext ); - -#undef CREATE_FLAGS - - adapter.Release(); + SafeRelease(&adapter); + #undef CREATE_FLAGS if (FAILED(status)) { - DEBUG_ERROR("Failed to create D3D11 device"); + DEBUG_WINERROR("Failed to create D3D11 device", status); DeInitialize(); return false; } - IDXGIDevicePtr dxgi; - if (FAILED(m_device->QueryInterface(__uuidof(IDXGIDevice), (void **)&dxgi))) + bool h264 = false; + for(CaptureOptions::const_iterator it = m_options->cbegin(); it != m_options->cend(); ++it) { - DEBUG_ERROR("Failed to obtain the IDXGIDevice interface from the D3D11 device"); + if (_stricmp(*it, "h264") == 0) h264 = true; + } + + if (h264) + { + DEBUG_WARN("Enabling experimental H.264 compression"); + m_frameType = FRAME_TYPE_H264; + if (!InitH264Capture()) + { + DeInitialize(); + return false; + } + } + else + { + m_frameType = FRAME_TYPE_ARGB; + if (!InitRawCapture()) + { + DeInitialize(); + return false; + } + } + + IDXGIDevicePtr dxgi; + status = m_device->QueryInterface(__uuidof(IDXGIDevice), (void **)&dxgi); + if (FAILED(status)) + { + DEBUG_WINERROR("Failed to obtain the IDXGIDevice interface from the D3D11 device", status); DeInitialize(); return false; } @@ -160,11 +204,17 @@ bool DXGI::Initialize(CaptureOptions * options) if (FAILED(status)) { - DEBUG_ERROR("DuplicateOutput Failed: %08x", (int)status); + DEBUG_WINERROR("DuplicateOutput Failed", status); DeInitialize(); return false; } + m_initialized = true; + return true; +} + +bool DXGI::InitRawCapture() +{ D3D11_TEXTURE2D_DESC texDesc; ZeroMemory(&texDesc, sizeof(texDesc)); texDesc.Width = m_width; @@ -179,15 +229,180 @@ bool DXGI::Initialize(CaptureOptions * options) texDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; texDesc.MiscFlags = 0; - status = m_device->CreateTexture2D(&texDesc, NULL, &m_texture); + HRESULT status = m_device->CreateTexture2D(&texDesc, NULL, &m_texture); if (FAILED(status)) { - DEBUG_ERROR("Failed to create texture: %08x", (int)status); - DeInitialize(); + DEBUG_WINERROR("Failed to create texture", status); return false; } - - m_initialized = true; + + return true; +} + +bool DXGI::InitH264Capture() +{ + HRESULT status; + + MFT_REGISTER_TYPE_INFO typeInfo; + IMFActivate **activationPointers; + UINT32 activationPointerCount; + + ID3D10MultithreadPtr mt(m_device); + mt->SetMultithreadProtected(TRUE); + + m_encodeEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + InitializeCriticalSection(&m_encodeCS); + + typeInfo.guidMajorType = MFMediaType_Video; + typeInfo.guidSubtype = MFVideoFormat_H264; + + status = MFTEnumEx( + MFT_CATEGORY_VIDEO_ENCODER, + MFT_ENUM_FLAG_HARDWARE, + NULL, + &typeInfo, + &activationPointers, + &activationPointerCount + ); + if (FAILED(status)) + { + DEBUG_WINERROR("Failed to enumerate encoder MFTs", status); + return false; + } + + if (activationPointerCount == 0) + { + DEBUG_WINERROR("Hardware H264 MFT not available", status); + return false; + } + + { + UINT32 nameLen = 0; + activationPointers[0]->GetStringLength(MFT_FRIENDLY_NAME_Attribute, &nameLen); + wchar_t * name = new wchar_t[nameLen+1]; + activationPointers[0]->GetString(MFT_FRIENDLY_NAME_Attribute, name, nameLen + 1, NULL); + DEBUG_INFO("Using Encoder: %S", name); + delete[] name; + } + + status = activationPointers[0]->ActivateObject(IID_PPV_ARGS(&m_mfTransform)); + if (FAILED(status)) + { + CoTaskMemFree(activationPointers); + DEBUG_WINERROR("Failed to create H264 encoder MFT", status); + return false; + } + CoTaskMemFree(activationPointers); + + IMFAttributesPtr attribs; + m_mfTransform->GetAttributes(&attribs); + attribs->SetUINT32 (MF_READWRITE_ENABLE_HARDWARE_TRANSFORMS , TRUE); + attribs->SetUINT32 (MF_SOURCE_READER_ENABLE_VIDEO_PROCESSING , TRUE); + attribs->SetUINT32 (MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING, TRUE); + attribs->SetUINT32 (MF_LOW_LATENCY , TRUE); + + UINT32 d3d11Aware = 0; + UINT32 async = 0; + attribs->GetUINT32(MF_TRANSFORM_ASYNC, &async); + attribs->GetUINT32(MF_SA_D3D11_AWARE, &d3d11Aware); + if (async) + attribs->SetUINT32(MF_TRANSFORM_ASYNC_UNLOCK, TRUE); + SafeRelease(&attribs); + + status = m_mfTransform.QueryInterface(IID_PPV_ARGS(&m_mediaEventGen)); + if (FAILED(status)) + { + DEBUG_WINERROR("Failed to obtain th emedia event generator interface", status); + return false; + } + + status = m_mediaEventGen->BeginGetEvent(this, NULL); + if (FAILED(status)) + { + DEBUG_WINERROR("Failed to set the begin get event", status); + return false; + } + + if (d3d11Aware) + { + MFCreateDXGIDeviceManager(&m_resetToken, &m_mfDeviceManager); + status = m_mfDeviceManager->ResetDevice(m_device, m_resetToken); + if (FAILED(status)) + { + DEBUG_WINERROR("Failed to call reset device", status); + return false; + } + + status = m_mfTransform->ProcessMessage(MFT_MESSAGE_SET_D3D_MANAGER, ULONG_PTR(m_mfDeviceManager.GetInterfacePtr())); + if (FAILED(status)) + { + DEBUG_WINERROR("Failed to set the D3D manager", status); + return false; + } + } + + IMFMediaTypePtr outType; + MFCreateMediaType(&outType); + + outType->SetGUID (MF_MT_MAJOR_TYPE , MFMediaType_Video); + outType->SetGUID (MF_MT_SUBTYPE , MFVideoFormat_H264); + outType->SetUINT32(MF_MT_AVG_BITRATE , 240000); + outType->SetUINT32(MF_MT_INTERLACE_MODE , MFVideoInterlace_Progressive); + outType->SetUINT32(MF_MT_MPEG2_PROFILE , eAVEncH264VProfile_444); + outType->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE); + + MFSetAttributeSize (outType, MF_MT_FRAME_SIZE , m_width, m_height); + MFSetAttributeRatio(outType, MF_MT_FRAME_RATE , 30, 1); + MFSetAttributeRatio(outType, MF_MT_PIXEL_ASPECT_RATIO, 1, 1); + + status = m_mfTransform->SetOutputType(0, outType, 0); + if (FAILED(status)) + { + DEBUG_WINERROR("Failed to set the output media type on the H264 encoder MFT", status); + return false; + } + + IMFMediaTypePtr inType; + MFCreateMediaType(&inType); + + inType->SetGUID (MF_MT_MAJOR_TYPE , MFMediaType_Video ); + inType->SetGUID (MF_MT_SUBTYPE , MFVideoFormat_NV12); + inType->SetUINT32(MF_MT_INTERLACE_MODE , MFVideoInterlace_Progressive); + inType->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE); + + MFSetAttributeSize (inType, MF_MT_FRAME_SIZE , m_width, m_height); + MFSetAttributeRatio(inType, MF_MT_FRAME_RATE , 30, 1); + MFSetAttributeRatio(inType, MF_MT_PIXEL_ASPECT_RATIO, 1 , 1); + + status = m_mfTransform->SetInputType(0, inType, 0); + if (FAILED(status)) + { + DEBUG_WINERROR("Failed to set the input media type on the H264 encoder MFT", status); + return false; + } + + m_mfTransform->ProcessMessage(MFT_MESSAGE_COMMAND_FLUSH , NULL); + m_mfTransform->ProcessMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, NULL); + m_mfTransform->ProcessMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, NULL); + +#if 0 + status = MFTRegisterLocalByCLSID( + __uuidof(CColorConvertDMO), + MFT_CATEGORY_VIDEO_PROCESSOR, + L"", + MFT_ENUM_FLAG_SYNCMFT, + 0, + NULL, + 0, + NULL + ); + if (FAILED(status)) + { + DEBUG_ERROR("Failed to register color converter DSP"); + return false; + } +#endif + return true; } @@ -212,23 +427,22 @@ void DXGI::DeInitialize() m_surfaceMapped = false; } - if (m_texture) - m_texture.Release(); + SafeRelease(&m_texture); + SafeRelease(&m_dup); + SafeRelease(&m_output); + SafeRelease(&m_deviceContext); + SafeRelease(&m_device); + SafeRelease(&m_dxgiFactory); + SafeRelease(&m_mfTransform); + SafeRelease(&m_mfDeviceManager); + SafeRelease(&m_mediaEventGen); - if (m_dup) - m_dup.Release(); - - if (m_output) - m_output.Release(); - - if (m_deviceContext) - m_deviceContext.Release(); - - if (m_device) - m_device.Release(); - - if (m_dxgiFactory) - m_dxgiFactory.Release(); + if (m_encodeEvent) + { + CloseHandle(m_encodeEvent); + m_encodeEvent = NULL; + DeleteCriticalSection(&m_encodeCS); + } m_initialized = false; } @@ -237,8 +451,7 @@ FrameType DXGI::GetFrameType() { if (!m_initialized) return FRAME_TYPE_INVALID; - - return FRAME_TYPE_ARGB; + return m_frameType; } size_t DXGI::GetMaxFrameSize() @@ -249,6 +462,89 @@ size_t DXGI::GetMaxFrameSize() return (m_width * m_height * 4); } +STDMETHODIMP Capture::DXGI::Invoke(IMFAsyncResult * pAsyncResult) +{ + HRESULT status, evtStatus; + MediaEventType meType = MEUnknown; + IMFMediaEvent *pEvent = NULL; + + status = m_mediaEventGen->EndGetEvent(pAsyncResult, &pEvent); + if (FAILED(status)) + { + DEBUG_WINERROR("EndGetEvent", status); + return status; + } + + status = pEvent->GetStatus(&evtStatus); + if (FAILED(status)) + { + SafeRelease(&pEvent); + DEBUG_WINERROR("GetStatus", status); + return status; + } + + if (FAILED(evtStatus)) + { + SafeRelease(&pEvent); + DEBUG_WINERROR("evtStatus", evtStatus); + return evtStatus; + } + + status = pEvent->GetType(&meType); + if (FAILED(status)) + { + SafeRelease(&pEvent); + DEBUG_WINERROR("GetType", status); + return status; + } + SafeRelease(&pEvent); + + switch (meType) + { + case METransformNeedInput: + EnterCriticalSection(&m_encodeCS); + m_encodeNeedsData = true; + SetEvent(m_encodeEvent); + LeaveCriticalSection(&m_encodeCS); + break; + + case METransformHaveOutput: + EnterCriticalSection(&m_encodeCS); + m_encodeHasData = true; + SetEvent(m_encodeEvent); + LeaveCriticalSection(&m_encodeCS); + break; + + case METransformDrainComplete: + { + status = m_mfTransform->ProcessMessage(MFT_MESSAGE_COMMAND_FLUSH, 0); + if (FAILED(status)) + { + DEBUG_WINERROR("MFT_MESSAGE_COMMAND_FLUSH", status); + return status; + } + break; + } + + case MEError: + DEBUG_INFO("err"); + break; + + default: + DEBUG_INFO("unk"); + break; + } + + status = m_mediaEventGen->BeginGetEvent(this, NULL); + if (FAILED(status)) + { + DEBUG_WINERROR("BeginGetEvent", status); + return status; + } + + return status; +} + void DXGI::WaitForDesktop() { HDESK desktop; @@ -263,7 +559,7 @@ void DXGI::WaitForDesktop() CloseDesktop(desktop); } -GrabStatus DXGI::GrabFrame(FrameInfo & frame) +GrabStatus Capture::DXGI::GrabFrameTexture(FrameInfo & frame, ID3D11Texture2DPtr & texture) { if (!m_initialized) return GRAB_STATUS_ERROR; @@ -273,9 +569,9 @@ GrabStatus DXGI::GrabFrame(FrameInfo & frame) HRESULT status; bool cursorUpdate = false; - for(int i = 0; i < 2; ++i) + for (int i = 0; i < 2; ++i) { - while(true) + while (true) { if (m_releaseFrame) { @@ -284,16 +580,16 @@ GrabStatus DXGI::GrabFrame(FrameInfo & frame) switch (status) { - case S_OK: - break; + case S_OK: + break; - case DXGI_ERROR_INVALID_CALL: - DEBUG_ERROR("Frame was already released"); - return GRAB_STATUS_ERROR; + case DXGI_ERROR_INVALID_CALL: + DEBUG_ERROR("Frame was already released"); + return GRAB_STATUS_ERROR; - case DXGI_ERROR_ACCESS_LOST: - WaitForDesktop(); - return GRAB_STATUS_REINIT; + case DXGI_ERROR_ACCESS_LOST: + WaitForDesktop(); + return GRAB_STATUS_REINIT; } } @@ -305,9 +601,9 @@ GrabStatus DXGI::GrabFrame(FrameInfo & frame) m_memcpy.Wake(); // send the last frame again if we timeout to prevent the client stalling on restart - frame.width = m_width; + frame.width = m_width; frame.height = m_height; - frame.pitch = m_mapping.RowPitch; + frame.pitch = m_mapping.RowPitch; frame.stride = m_mapping.RowPitch / 4; unsigned int size = m_height * m_mapping.RowPitch; @@ -326,18 +622,18 @@ GrabStatus DXGI::GrabFrame(FrameInfo & frame) if ( m_lastMousePos.x != frameInfo.PointerPosition.Position.x || m_lastMousePos.y != frameInfo.PointerPosition.Position.y - ) { - cursorUpdate = true; - frame.cursor.hasPos = true; - frame.cursor.x = frameInfo.PointerPosition.Position.x; - frame.cursor.y = frameInfo.PointerPosition.Position.y; - m_lastMousePos.x = frameInfo.PointerPosition.Position.x; - m_lastMousePos.y = frameInfo.PointerPosition.Position.y; + ) { + cursorUpdate = true; + frame.cursor.hasPos = true; + frame.cursor.x = frameInfo.PointerPosition.Position.x; + frame.cursor.y = frameInfo.PointerPosition.Position.y; + m_lastMousePos.x = frameInfo.PointerPosition.Position.x; + m_lastMousePos.y = frameInfo.PointerPosition.Position.y; } if (m_lastMouseVis != frameInfo.PointerPosition.Visible) { - cursorUpdate = true; + cursorUpdate = true; m_lastMouseVis = frameInfo.PointerPosition.Visible; } @@ -352,7 +648,7 @@ GrabStatus DXGI::GrabFrame(FrameInfo & frame) { if (m_pointer) delete[] m_pointer; - m_pointer = new BYTE[frameInfo.PointerShapeBufferSize]; + m_pointer = new BYTE[frameInfo.PointerShapeBufferSize]; m_pointerBufSize = frameInfo.PointerShapeBufferSize; } @@ -360,32 +656,32 @@ GrabStatus DXGI::GrabFrame(FrameInfo & frame) status = m_dup->GetFramePointerShape(m_pointerBufSize, m_pointer, &m_pointerSize, &shapeInfo); if (!SUCCEEDED(status)) { - DEBUG_ERROR("Failed to get the new pointer shape: %08x", (int)status); + DEBUG_WINERROR("Failed to get the new pointer shape", status); return GRAB_STATUS_ERROR; } switch (shapeInfo.Type) { - case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR : frame.cursor.type = CURSOR_TYPE_COLOR; break; - case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: frame.cursor.type = CURSOR_TYPE_MASKED_COLOR; break; - case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME : frame.cursor.type = CURSOR_TYPE_MONOCHROME; break; - default: - DEBUG_ERROR("Invalid cursor type"); - return GRAB_STATUS_ERROR; + case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR: frame.cursor.type = CURSOR_TYPE_COLOR; break; + case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: frame.cursor.type = CURSOR_TYPE_MASKED_COLOR; break; + case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME: frame.cursor.type = CURSOR_TYPE_MONOCHROME; break; + default: + DEBUG_ERROR("Invalid cursor type"); + return GRAB_STATUS_ERROR; } frame.cursor.hasShape = true; - frame.cursor.shape = m_pointer; - frame.cursor.w = shapeInfo.Width; - frame.cursor.h = shapeInfo.Height; - frame.cursor.pitch = shapeInfo.Pitch; + frame.cursor.shape = m_pointer; + frame.cursor.w = shapeInfo.Width; + frame.cursor.h = shapeInfo.Height; + frame.cursor.pitch = shapeInfo.Pitch; frame.cursor.dataSize = m_pointerSize; } if (frameInfo.LastPresentTime.QuadPart != 0) break; - res.Release(); + SafeRelease(&res); if (cursorUpdate) return GRAB_STATUS_CURSOR; @@ -393,39 +689,51 @@ GrabStatus DXGI::GrabFrame(FrameInfo & frame) if (SUCCEEDED(status)) break; - - switch (status) - { - // desktop switch, mode change, switch DWM on or off or Secure Desktop - case DXGI_ERROR_ACCESS_LOST: - case WAIT_ABANDONED: - WaitForDesktop(); - return GRAB_STATUS_REINIT; - default: - // unknown failure - DEBUG_INFO("AcquireNextFrame failed: %08x", (int)status); - return GRAB_STATUS_ERROR; + switch (status) + { + // desktop switch, mode change, switch DWM on or off or Secure Desktop + case DXGI_ERROR_ACCESS_LOST: + case WAIT_ABANDONED: + WaitForDesktop(); + return GRAB_STATUS_REINIT; + + default: + // unknown failure + DEBUG_WINERROR("AcquireNextFrame failed", status); + return GRAB_STATUS_ERROR; } } // retry count exceeded if (FAILED(status)) { - DEBUG_ERROR("Failed to acquire next frame"); + DEBUG_WINERROR("Failed to acquire next frame", status); return GRAB_STATUS_ERROR; } + + res.QueryInterface(IID_PPV_ARGS(&texture)); + SafeRelease(&res); - ID3D11Texture2DPtr src(res); - res.Release(); - if (!src) + if (!texture) { DEBUG_ERROR("Failed to get src ID3D11Texture2D"); return GRAB_STATUS_ERROR; } + return GRAB_STATUS_OK; +} + +GrabStatus Capture::DXGI::GrabFrameRaw(FrameInfo & frame) +{ + GrabStatus result; + ID3D11Texture2DPtr src; + result = GrabFrameTexture(frame, src); + if (result != GRAB_STATUS_OK) + return result; + m_deviceContext->CopyResource(m_texture, src); - src.Release(); + SafeRelease(&src); if (m_surfaceMapped) { @@ -433,10 +741,11 @@ GrabStatus DXGI::GrabFrame(FrameInfo & frame) m_surfaceMapped = false; } + HRESULT status; status = m_deviceContext->Map(m_texture, 0, D3D11_MAP_READ, 0, &m_mapping); if (FAILED(status)) { - DEBUG_ERROR("Failed to map the texture: %08x", (int)status); + DEBUG_WINERROR("Failed to map the texture", status); DeInitialize(); return GRAB_STATUS_ERROR; } @@ -446,14 +755,153 @@ GrabStatus DXGI::GrabFrame(FrameInfo & frame) // wake up the copy threads m_memcpy.Wake(); - frame.width = m_width; - frame.height = m_height; - frame.pitch = m_mapping.RowPitch; - frame.stride = m_mapping.RowPitch >> 2; + frame.pitch = m_mapping.RowPitch; + frame.stride = m_mapping.RowPitch >> 2; const unsigned int size = m_height * m_mapping.RowPitch; m_memcpy.Copy(frame.buffer, m_mapping.pData, size < frame.bufferSize ? size : frame.bufferSize); TRACE_END; return GRAB_STATUS_OK; +} + +GrabStatus Capture::DXGI::GrabFrameH264(FrameInfo & frame) +{ + while(true) + { + // only reset the event if there isn't work pending + EnterCriticalSection(&m_encodeCS); + if (!m_encodeHasData && !m_encodeNeedsData) + ResetEvent(m_encodeEvent); + LeaveCriticalSection(&m_encodeCS); + + switch (WaitForSingleObject(m_encodeEvent, 1000)) + { + case WAIT_FAILED: + DEBUG_WINERROR("Wait for encode event failed", GetLastError()); + return GRAB_STATUS_ERROR; + + case WAIT_ABANDONED: + DEBUG_ERROR("Wait abandoned"); + return GRAB_STATUS_ERROR; + + case WAIT_TIMEOUT: + continue; + + case WAIT_OBJECT_0: + break; + } + + EnterCriticalSection(&m_encodeCS); + + HRESULT status; + if (m_encodeNeedsData) + { + LeaveCriticalSection(&m_encodeCS); + GrabStatus result; + ID3D11Texture2DPtr src; + result = GrabFrameTexture(frame, src); + if (result != GRAB_STATUS_OK) + { + return result; + } + + // cursor data may be returned, only turn off the flag if we have a frame + EnterCriticalSection(&m_encodeCS); + m_encodeNeedsData = false; + LeaveCriticalSection(&m_encodeCS); + + IMFMediaBufferPtr buffer; + status = MFCreateDXGISurfaceBuffer(__uuidof(ID3D11Texture2D), src, 0, FALSE, &buffer); + if (FAILED(status)) + { + DEBUG_WINERROR("Failed to create DXGI surface buffer from texture", status); + return GRAB_STATUS_ERROR; + } + + IMF2DBufferPtr imfBuffer(buffer); + DWORD length; + imfBuffer->GetContiguousLength(&length); + buffer->SetCurrentLength(length); + SafeRelease(&imfBuffer); + + IMFSamplePtr sample; + MFCreateSample(&sample); + sample->AddBuffer(buffer); + + status = m_mfTransform->ProcessInput(0, sample, 0); + if (FAILED(status)) + { + DEBUG_WINERROR("Failed to process the input", status); + return GRAB_STATUS_ERROR; + } + + SafeRelease(&src ); + SafeRelease(&sample); + SafeRelease(&buffer); + + EnterCriticalSection(&m_encodeCS); + } + + if (m_encodeHasData) + { + m_encodeHasData = false; + LeaveCriticalSection(&m_encodeCS); + + // wake up the copy threads + TRACE_START("copy"); + m_memcpy.Wake(); + + MFT_OUTPUT_STREAM_INFO streamInfo; + status = m_mfTransform->GetOutputStreamInfo(0, &streamInfo); + if (FAILED(status)) + { + DEBUG_WINERROR("GetOutputStreamInfo", status); + return GRAB_STATUS_ERROR; + } + + DWORD outStatus; + MFT_OUTPUT_DATA_BUFFER outDataBuffer; + outDataBuffer.dwStreamID = 0; + outDataBuffer.dwStatus = 0; + outDataBuffer.pEvents = NULL; + outDataBuffer.pSample = NULL; + + status = m_mfTransform->ProcessOutput(0, 1, &outDataBuffer, &outStatus); + if (FAILED(status)) + { + DEBUG_WINERROR("ProcessOutput", status); + return GRAB_STATUS_ERROR; + } + + IMFMediaBufferPtr buffer; + MFCreateAlignedMemoryBuffer((DWORD)frame.bufferSize, MF_128_BYTE_ALIGNMENT, &buffer); + outDataBuffer.pSample->CopyToBuffer(buffer); + SafeRelease(&outDataBuffer.pEvents); + SafeRelease(&outDataBuffer.pSample); + + BYTE *pixels; + DWORD maxLen, curLen; + buffer->Lock(&pixels, &maxLen, &curLen); + m_memcpy.Copy(frame.buffer, pixels, curLen); + buffer->Unlock(); + + SafeRelease(&buffer); + TRACE_END; + return GRAB_STATUS_OK; + } + + LeaveCriticalSection(&m_encodeCS); + } +} + +GrabStatus DXGI::GrabFrame(FrameInfo & frame) +{ + frame.width = m_width; + frame.height = m_height; + + if (m_frameType == FRAME_TYPE_H264) + return GrabFrameH264(frame); + else + return GrabFrameRaw(frame); } \ No newline at end of file diff --git a/host/Capture/DXGI.h b/host/Capture/DXGI.h index ec6078ed..cbf192de 100644 --- a/host/Capture/DXGI.h +++ b/host/Capture/DXGI.h @@ -24,14 +24,17 @@ Place, Suite 330, Boston, MA 02111-1307 USA #define W32_LEAN_AND_MEAN #include +#include #include #include +#include #include #include _COM_SMARTPTR_TYPEDEF(IDXGIFactory1 , __uuidof(IDXGIFactory1 )); _COM_SMARTPTR_TYPEDEF(ID3D11Device , __uuidof(ID3D11Device )); _COM_SMARTPTR_TYPEDEF(ID3D11DeviceContext , __uuidof(ID3D11DeviceContext )); +_COM_SMARTPTR_TYPEDEF(ID3D10Multithread , __uuidof(ID3D10Multithread )); _COM_SMARTPTR_TYPEDEF(IDXGIDevice , __uuidof(IDXGIDevice )); _COM_SMARTPTR_TYPEDEF(IDXGIOutput1 , __uuidof(IDXGIOutput1 )); _COM_SMARTPTR_TYPEDEF(IDXGIOutput , __uuidof(IDXGIOutput )); @@ -40,17 +43,28 @@ _COM_SMARTPTR_TYPEDEF(IDXGIOutputDuplication, __uuidof(IDXGIOutputDuplication)); _COM_SMARTPTR_TYPEDEF(ID3D11Texture2D , __uuidof(ID3D11Texture2D )); _COM_SMARTPTR_TYPEDEF(IDXGIResource , __uuidof(IDXGIResource )); +_COM_SMARTPTR_TYPEDEF(IMFActivate , __uuidof(IMFActivate )); +_COM_SMARTPTR_TYPEDEF(IMFAttributes , __uuidof(IMFAttributes )); +_COM_SMARTPTR_TYPEDEF(IMFDXGIDeviceManager , __uuidof(IMFDXGIDeviceManager )); +_COM_SMARTPTR_TYPEDEF(IMFTransform , __uuidof(IMFTransform )); +_COM_SMARTPTR_TYPEDEF(IMFMediaEventGenerator, __uuidof(IMFMediaEventGenerator)); +_COM_SMARTPTR_TYPEDEF(IMFMediaType , __uuidof(IMFMediaType )); +_COM_SMARTPTR_TYPEDEF(IMFSample , __uuidof(IMFSample )); +_COM_SMARTPTR_TYPEDEF(IMFMediaBuffer , __uuidof(IMFMediaBuffer )); +_COM_SMARTPTR_TYPEDEF(IMF2DBuffer , __uuidof(IMF2DBuffer )); + namespace Capture { - class DXGI : public ICapture + class DXGI : public ICapture, public IMFAsyncCallback { public: DXGI(); - ~DXGI(); + virtual ~DXGI(); const char * GetName() { return "DXGI"; } bool Initialize(CaptureOptions * options); + void DeInitialize(); bool ReInitialize() { @@ -67,14 +81,54 @@ namespace Capture size_t GetMaxFrameSize(); enum GrabStatus GrabFrame(struct FrameInfo & frame); + /* + Junk needed for the horrid IMFAsyncCallback interface + */ + STDMETHODIMP QueryInterface(REFIID riid, void ** ppv) + { + static const QITAB qit[] = + { + QITABENT(DXGI, IMFAsyncCallback), + { NULL } + }; + + return QISearch(this, qit, riid, ppv); + } + + STDMETHODIMP_(ULONG) AddRef() + { + return InterlockedIncrement(&m_cRef); + } + + STDMETHODIMP_(ULONG) Release() + { + long cRef = InterlockedDecrement(&m_cRef); + if (!cRef) + delete this; + return cRef; + } + + STDMETHODIMP GetParameters(DWORD *pdwFlags, DWORD *pdwQueue) { return E_NOTIMPL; } + STDMETHODIMP Invoke(IMFAsyncResult *pAsyncResult); + private: + bool InitRawCapture(); + bool InitH264Capture(); + + GrabStatus DXGI::GrabFrameTexture(FrameInfo & frame, ID3D11Texture2DPtr & texture); + GrabStatus DXGI::GrabFrameRaw (FrameInfo & frame); + GrabStatus DXGI::GrabFrameH264 (FrameInfo & frame); + void WaitForDesktop(); + + long m_cRef; CaptureOptions * m_options; - bool m_initialized; - unsigned int m_width; - unsigned int m_height; + bool m_initialized; + unsigned int m_width; + unsigned int m_height; + enum FrameType m_frameType; MultiMemcpy m_memcpy; IDXGIFactory1Ptr m_dxgiFactory; @@ -87,6 +141,16 @@ namespace Capture ID3D11Texture2DPtr m_texture; D3D11_MAPPED_SUBRESOURCE m_mapping; bool m_surfaceMapped; + + HANDLE m_encodeEvent; + bool m_encodeNeedsData, m_encodeHasData; + CRITICAL_SECTION m_encodeCS; + + UINT m_resetToken; + IMFDXGIDeviceManagerPtr m_mfDeviceManager; + IMFTransformPtr m_mfTransform; + IMFMediaEventGeneratorPtr m_mediaEventGen; + BYTE * m_pointer; UINT m_pointerBufSize; UINT m_pointerSize; diff --git a/host/MultiMemcpy.cpp b/host/MultiMemcpy.cpp index 5956af09..45f2c615 100644 --- a/host/MultiMemcpy.cpp +++ b/host/MultiMemcpy.cpp @@ -18,7 +18,6 @@ Place, Suite 330, Boston, MA 02111-1307 USA */ #include "MultiMemcpy.h" -#include "Util.h" #include "common/memcpySSE.h" MultiMemcpy::MultiMemcpy() @@ -27,6 +26,7 @@ MultiMemcpy::MultiMemcpy() { m_workers[i].id = (1 << i); m_workers[i].running = &m_running; + m_workers[i].abort = false; m_workers[i].start = CreateSemaphore(NULL, 0, 1, NULL); m_workers[i].thread = CreateThread(0, 0, WorkerFunction, &m_workers[i], 0, NULL); @@ -44,15 +44,23 @@ MultiMemcpy::~MultiMemcpy() void MultiMemcpy::Copy(void * dst, void * src, size_t size) { - if (!m_awake) - Wake(); + const size_t block = (size / MULTIMEMCPY_THREADS) & ~0x7F; + if (block == 0) + { + Abort(); + memcpySSE(dst, src, size); + return; + } - const size_t block = size / MULTIMEMCPY_THREADS; + Wake(); for (int i = 0; i < MULTIMEMCPY_THREADS; ++i) { m_workers[i].dst = (uint8_t *)dst + i * block; m_workers[i].src = (uint8_t *)src + i * block; - m_workers[i].size = (i + 1) * block - i * block; + if (i == MULTIMEMCPY_THREADS - 1) + m_workers[i].size = size - (block * i); + else + m_workers[i].size = block; } INTERLOCKED_OR8(&m_running, (1 << MULTIMEMCPY_THREADS) - 1); @@ -69,6 +77,12 @@ DWORD WINAPI MultiMemcpy::WorkerFunction(LPVOID param) { WaitForSingleObject(w->start, INFINITE); while(!(*w->running & w->id)) {} + if (w->abort) + { + w->abort = false; + INTERLOCKED_AND8(w->running, ~w->id); + continue; + } memcpySSE(w->dst, w->src, w->size); INTERLOCKED_AND8(w->running, ~w->id); diff --git a/host/MultiMemcpy.h b/host/MultiMemcpy.h index 6fcc133c..188a17c8 100644 --- a/host/MultiMemcpy.h +++ b/host/MultiMemcpy.h @@ -22,6 +22,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA #include #include +#include "Util.h" + #pragma once class MultiMemcpy { @@ -41,12 +43,29 @@ public: m_awake = true; } + // abort a pre-empted copy + inline void Abort() + { + if (!m_awake) + return; + + for (int i = 0; i < MULTIMEMCPY_THREADS; ++i) + m_workers[i].abort = true; + + INTERLOCKED_OR8(&m_running, (1 << MULTIMEMCPY_THREADS) - 1); + while (m_running) {} + + m_awake = false; + } + + void Copy(void * dst, void * src, size_t size); private: struct Worker { - unsigned int id; + unsigned int id; volatile char *running; + bool abort; HANDLE start; HANDLE thread; diff --git a/host/Util.h b/host/Util.h index 7d2632c2..d8f605f7 100644 --- a/host/Util.h +++ b/host/Util.h @@ -40,6 +40,27 @@ Place, Suite 330, Boston, MA 02111-1307 USA class Util { public: + static void DebugWinError(const char * file, const unsigned int line, const char * function, const char * desc, HRESULT status) + { + char *buffer; + FormatMessageA( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, + NULL, + status, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (char*)&buffer, + 1024, + NULL + ); + + for(size_t i = strlen(buffer) - 1; i > 0; --i) + if (buffer[i] == '\n' || buffer[i] == '\r') + buffer[i] = 0; + + fprintf(stderr, "[E] %20s:%-4u | %-30s | %s: 0x%08x (%s)\n", file, line, function, desc, status, buffer); + LocalFree(buffer); + } + static std::string GetSystemRoot() { std::string defaultPath; diff --git a/host/looking-glass-host.vcxproj b/host/looking-glass-host.vcxproj index b11541e8..c90635e5 100644 --- a/host/looking-glass-host.vcxproj +++ b/host/looking-glass-host.vcxproj @@ -165,7 +165,7 @@ Windows true - kernel32.lib;shlwapi.lib;dxgi.lib;d3d11.lib;setupapi.lib;uuid.lib;%(AdditionalDependencies) + kernel32.lib;shlwapi.lib;dxgi.lib;d3d11.lib;setupapi.lib;uuid.lib;wmcodecdspuuid.lib;mfplat.lib;mfuuid.lib;evr.lib;%(AdditionalDependencies) %(AdditionalLibraryDirectories) @@ -185,7 +185,7 @@ Windows true - kernel32.lib;shlwapi.lib;dxgi.lib;d3d11.lib;setupapi.lib;uuid.lib;%(AdditionalDependencies) + kernel32.lib;shlwapi.lib;dxgi.lib;d3d11.lib;setupapi.lib;uuid.lib;wmcodecdspuuid.lib;mfplat.lib;mfuuid.lib;evr.lib;%(AdditionalDependencies) %(AdditionalLibraryDirectories) @@ -205,7 +205,7 @@ Windows true - kernel32.lib;shlwapi.lib;dxgi.lib;d3d11.lib;setupapi.lib;uuid.lib;%(AdditionalDependencies) + kernel32.lib;shlwapi.lib;dxgi.lib;d3d11.lib;setupapi.lib;uuid.lib;wmcodecdspuuid.lib;mfplat.lib;mfuuid.lib;evr.lib;%(AdditionalDependencies) %(AdditionalLibraryDirectories) @@ -225,7 +225,7 @@ Windows true - kernel32.lib;shlwapi.lib;dxgi.lib;d3d11.lib;setupapi.lib;uuid.lib;%(AdditionalDependencies) + kernel32.lib;shlwapi.lib;dxgi.lib;d3d11.lib;setupapi.lib;uuid.lib;wmcodecdspuuid.lib;mfplat.lib;mfuuid.lib;evr.lib;%(AdditionalDependencies) %(AdditionalLibraryDirectories) @@ -249,7 +249,7 @@ true true true - kernel32.lib;shlwapi.lib;dxgi.lib;d3d11.lib;setupapi.lib;uuid.lib;%(AdditionalDependencies) + kernel32.lib;shlwapi.lib;dxgi.lib;d3d11.lib;setupapi.lib;uuid.lib;wmcodecdspuuid.lib;mfplat.lib;mfuuid.lib;evr.lib;%(AdditionalDependencies) %(AdditionalLibraryDirectories) @@ -273,7 +273,7 @@ true true true - kernel32.lib;shlwapi.lib;dxgi.lib;d3d11.lib;setupapi.lib;uuid.lib;%(AdditionalDependencies) + kernel32.lib;shlwapi.lib;dxgi.lib;d3d11.lib;setupapi.lib;uuid.lib;wmcodecdspuuid.lib;mfplat.lib;mfuuid.lib;evr.lib;%(AdditionalDependencies) %(AdditionalLibraryDirectories) @@ -297,7 +297,7 @@ true true true - kernel32.lib;shlwapi.lib;dxgi.lib;d3d11.lib;setupapi.lib;uuid.lib;%(AdditionalDependencies) + kernel32.lib;shlwapi.lib;dxgi.lib;d3d11.lib;setupapi.lib;uuid.lib;wmcodecdspuuid.lib;mfplat.lib;mfuuid.lib;evr.lib;%(AdditionalDependencies) %(AdditionalLibraryDirectories) @@ -321,7 +321,7 @@ true true true - kernel32.lib;shlwapi.lib;dxgi.lib;d3d11.lib;setupapi.lib;uuid.lib;%(AdditionalDependencies) + kernel32.lib;shlwapi.lib;dxgi.lib;d3d11.lib;setupapi.lib;uuid.lib;wmcodecdspuuid.lib;mfplat.lib;mfuuid.lib;evr.lib;%(AdditionalDependencies) %(AdditionalLibraryDirectories) diff --git a/host/main.cpp b/host/main.cpp index 440afa96..a1f3ef5d 100644 --- a/host/main.cpp +++ b/host/main.cpp @@ -52,6 +52,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdParam { CrashHandler::Initialize(); TraceUtil::Initialize(); + CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS);