/***************************************************************************** * DXGICapture.cpp * * Copyright (C) 2020 Gokhan Erdogdu * * DXGICapture is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * DXGICapture is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * ******************************************************************************/ #include "DXGICapture.h" #include "DXGICaptureHelper.h" #include #pragma comment(lib, "D3D11.lib") #pragma comment(lib, "d2d1.lib") #pragma comment(lib, "windowscodecs.lib") #pragma comment(lib, "shcore.lib") // SetProcessDpiAwareness // Driver types supported const D3D_DRIVER_TYPE g_DriverTypes[] = { D3D_DRIVER_TYPE_HARDWARE, D3D_DRIVER_TYPE_WARP, D3D_DRIVER_TYPE_REFERENCE, }; const UINT g_NumDriverTypes = ARRAYSIZE(g_DriverTypes); // Feature levels supported const D3D_FEATURE_LEVEL g_FeatureLevels[] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_1 }; const UINT g_NumFeatureLevels = ARRAYSIZE(g_FeatureLevels); #define AUTOLOCK() ATL::CComCritSecLock auto_lock((ATL::CComAutoCriticalSection&)(m_csLock)) // // class CDXGICapture // CDXGICapture::CDXGICapture() : m_csLock() , m_bInitialized(FALSE) , m_lD3DFeatureLevel(D3D_FEATURE_LEVEL_INVALID) { RtlZeroMemory(&m_rendererInfo, sizeof(m_rendererInfo)); RtlZeroMemory(&m_mouseInfo, sizeof(m_mouseInfo)); RtlZeroMemory(&m_tempMouseBuffer, sizeof(m_tempMouseBuffer)); RtlZeroMemory(&m_desktopOutputDesc, sizeof(m_desktopOutputDesc)); } CDXGICapture::~CDXGICapture() { this->Terminate(); } HRESULT CDXGICapture::loadMonitorInfos(ID3D11Device *pDevice) { CHECK_POINTER(pDevice); HRESULT hr = S_OK; CComPtr ipDevice(pDevice); // Get DXGI device CComPtr ipDxgiDevice; hr = ipDevice->QueryInterface(IID_PPV_ARGS(&ipDxgiDevice)); if (FAILED(hr)) { return hr; } // Get DXGI adapter CComPtr ipDxgiAdapter; hr = ipDxgiDevice->GetParent(IID_PPV_ARGS(&ipDxgiAdapter)); if (FAILED(hr)) { return hr; } ipDxgiDevice = nullptr; CComPtr ipDxgiOutput; for (UINT i = 0; SUCCEEDED(hr); ++i) { ipDxgiOutput = nullptr; hr = ipDxgiAdapter->EnumOutputs(i, &ipDxgiOutput); if ((nullptr != ipDxgiOutput) && (hr != DXGI_ERROR_NOT_FOUND)) { DXGI_OUTPUT_DESC DesktopDesc; hr = ipDxgiOutput->GetDesc(&DesktopDesc); if (FAILED(hr)) { continue; } tagDublicatorMonitorInfo *pInfo; pInfo = new (std::nothrow) tagDublicatorMonitorInfo; if (nullptr == pInfo) { return E_OUTOFMEMORY; } hr = DXGICaptureHelper::ConvertDxgiOutputToMonitorInfo(&DesktopDesc, i, pInfo); if (FAILED(hr)) { delete pInfo; continue; } m_monitorInfos.push_back(pInfo); } } ipDxgiOutput = nullptr; ipDxgiAdapter = nullptr; return S_OK; } void CDXGICapture::freeMonitorInfos() { size_t nCount = m_monitorInfos.size(); if (nCount == 0) { return; } DublicatorMonitorInfoVec::iterator it = m_monitorInfos.begin(); DublicatorMonitorInfoVec::iterator end = m_monitorInfos.end(); for (size_t i = 0; (i < nCount) && (it != end); i++, it++) { tagDublicatorMonitorInfo *pInfo = *it; if (nullptr != pInfo) { delete pInfo; } } m_monitorInfos.clear(); } HRESULT CDXGICapture::createDeviceResource( const tagScreenCaptureFilterConfig *pConfig, const tagDublicatorMonitorInfo *pSelectedMonitorInfo ) { HRESULT hr = S_OK; CComPtr ipDxgiOutputDuplication; CComPtr ipCopyTexture2D; CComPtr ipD2D1Device; CComPtr ipD2D1DeviceContext; CComPtr ipD2D1Factory; CComPtr ipWICImageFactory; CComPtr ipWICOutputBitmap; CComPtr ipD2D1RenderTarget; DXGI_OUTPUT_DESC dgixOutputDesc; tagRendererInfo rendererInfo; RtlZeroMemory(&dgixOutputDesc, sizeof(dgixOutputDesc)); RtlZeroMemory(&rendererInfo, sizeof(rendererInfo)); // copy configuration to renderer info rendererInfo.MonitorIdx = pConfig->MonitorIdx; rendererInfo.ShowCursor = pConfig->ShowCursor; rendererInfo.RotationMode = pConfig->RotationMode; rendererInfo.SizeMode = pConfig->SizeMode; rendererInfo.OutputSize = pConfig->OutputSize; // default rendererInfo.ScaleX = 1.0f; rendererInfo.ScaleY = 1.0f; do { // Get DXGI factory CComPtr ipDxgiDevice; hr = m_ipD3D11Device->QueryInterface(IID_PPV_ARGS(&ipDxgiDevice)); CHECK_HR_BREAK(hr); CComPtr ipDxgiAdapter; hr = ipDxgiDevice->GetParent(IID_PPV_ARGS(&ipDxgiAdapter)); CHECK_HR_BREAK(hr); // Get output CComPtr ipDxgiOutput; hr = ipDxgiAdapter->EnumOutputs(rendererInfo.MonitorIdx, &ipDxgiOutput); CHECK_HR_BREAK(hr); // Get output description hr = ipDxgiOutput->GetDesc(&dgixOutputDesc); CHECK_HR_BREAK(hr); tagDublicatorMonitorInfo curMonInfo; hr = DXGICaptureHelper::ConvertDxgiOutputToMonitorInfo(&dgixOutputDesc, rendererInfo.MonitorIdx, &curMonInfo); CHECK_HR_BREAK(hr); if (!DXGICaptureHelper::IsEqualMonitorInfo(pSelectedMonitorInfo, &curMonInfo)) { hr = E_INVALIDARG; // Monitor settings have changed ??? break; } // QI for Output 1 CComPtr ipDxgiOutput1; hr = ipDxgiOutput->QueryInterface(IID_PPV_ARGS(&ipDxgiOutput1)); CHECK_HR_BREAK(hr); // Create desktop duplication hr = ipDxgiOutput1->DuplicateOutput(m_ipD3D11Device, &ipDxgiOutputDuplication); CHECK_HR_BREAK(hr); DXGI_OUTDUPL_DESC dxgiOutputDuplDesc; ipDxgiOutputDuplication->GetDesc(&dxgiOutputDuplDesc); hr = DXGICaptureHelper::CalculateRendererInfo(&dxgiOutputDuplDesc, &rendererInfo); CHECK_HR_BREAK(hr); // Create CPU access texture D3D11_TEXTURE2D_DESC desc; desc.Width = rendererInfo.SrcBounds.Width; desc.Height = rendererInfo.SrcBounds.Height; desc.Format = rendererInfo.SrcFormat; desc.ArraySize = 1; desc.BindFlags = 0; desc.MiscFlags = 0; desc.SampleDesc.Count = 1; desc.SampleDesc.Quality = 0; desc.MipLevels = 1; desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE; desc.Usage = D3D11_USAGE_STAGING; hr = m_ipD3D11Device->CreateTexture2D(&desc, NULL, &ipCopyTexture2D); CHECK_HR_BREAK(hr); if (nullptr == ipCopyTexture2D) { hr = E_OUTOFMEMORY; break; } #pragma region // Create D2D1 device UINT uiFlags = m_ipD3D11Device->GetCreationFlags(); D2D1_CREATION_PROPERTIES d2d1Props = D2D1::CreationProperties ( (uiFlags & D3D11_CREATE_DEVICE_SINGLETHREADED) ? D2D1_THREADING_MODE_SINGLE_THREADED : D2D1_THREADING_MODE_MULTI_THREADED, D2D1_DEBUG_LEVEL_NONE, (uiFlags & D3D11_CREATE_DEVICE_SINGLETHREADED) ? D2D1_DEVICE_CONTEXT_OPTIONS_NONE : D2D1_DEVICE_CONTEXT_OPTIONS_ENABLE_MULTITHREADED_OPTIMIZATIONS ); hr = D2D1CreateDevice(ipDxgiDevice, d2d1Props, &ipD2D1Device); CHECK_HR_BREAK(hr); // Get D2D1 factory ipD2D1Device->GetFactory(&ipD2D1Factory); if (nullptr == ipD2D1Factory) { hr = D2DERR_INVALID_CALL; break; } //create WIC factory hr = CoCreateInstance( CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_IWICImagingFactory, reinterpret_cast(&ipWICImageFactory) ); CHECK_HR_BREAK(hr); // create D2D1 target bitmap for render hr = ipWICImageFactory->CreateBitmap( (UINT)rendererInfo.OutputSize.Width, (UINT)rendererInfo.OutputSize.Height, GUID_WICPixelFormat32bppPBGRA, WICBitmapCacheOnDemand, &ipWICOutputBitmap); CHECK_HR_BREAK(hr); if (nullptr == ipWICOutputBitmap) { hr = E_OUTOFMEMORY; break; } // create a D2D1 render target (for D2D1 drawing) D2D1_RENDER_TARGET_PROPERTIES d2d1RenderTargetProp = D2D1::RenderTargetProperties ( D2D1_RENDER_TARGET_TYPE_DEFAULT, D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED), 0.0f, // default dpi 0.0f, // default dpi D2D1_RENDER_TARGET_USAGE_GDI_COMPATIBLE ); hr = ipD2D1Factory->CreateWicBitmapRenderTarget( ipWICOutputBitmap, d2d1RenderTargetProp, &ipD2D1RenderTarget ); CHECK_HR_BREAK(hr); #pragma endregion } while (false); if (SUCCEEDED(hr)) { // copy output parameters memcpy_s((void*)&m_rendererInfo, sizeof(m_rendererInfo), (const void*)&rendererInfo, sizeof(m_rendererInfo)); // set parameters m_desktopOutputDesc = dgixOutputDesc; m_ipDxgiOutputDuplication = ipDxgiOutputDuplication; m_ipCopyTexture2D = ipCopyTexture2D; m_ipD2D1Device = ipD2D1Device; m_ipD2D1Factory = ipD2D1Factory; m_ipWICImageFactory = ipWICImageFactory; m_ipWICOutputBitmap = ipWICOutputBitmap; m_ipD2D1RenderTarget = ipD2D1RenderTarget; } return S_OK; } void CDXGICapture::terminateDeviceResource() { m_ipDxgiOutputDuplication = nullptr; m_ipCopyTexture2D = nullptr; m_ipD2D1Device = nullptr; m_ipD2D1Factory = nullptr; m_ipWICImageFactory = nullptr; m_ipWICOutputBitmap = nullptr; m_ipD2D1RenderTarget = nullptr; // clear config parameters RtlZeroMemory(&m_rendererInfo, sizeof(m_rendererInfo)); // clear mouse information parameters if (m_mouseInfo.PtrShapeBuffer != nullptr) { delete[] m_mouseInfo.PtrShapeBuffer; m_mouseInfo.PtrShapeBuffer = nullptr; } RtlZeroMemory(&m_mouseInfo, sizeof(m_mouseInfo)); // clear temp temp buffer if (m_tempMouseBuffer.Buffer != nullptr) { delete[] m_tempMouseBuffer.Buffer; m_tempMouseBuffer.Buffer = nullptr; } RtlZeroMemory(&m_tempMouseBuffer, sizeof(m_tempMouseBuffer)); // clear desktop output desc RtlZeroMemory(&m_desktopOutputDesc, sizeof(m_desktopOutputDesc)); } HRESULT CDXGICapture::Initialize() { AUTOLOCK(); if (m_bInitialized) { return HRESULT_FROM_WIN32(ERROR_ALREADY_INITIALIZED); // already initialized } HRESULT hr = S_OK; D3D_FEATURE_LEVEL lFeatureLevel; CComPtr ipDevice; CComPtr ipDeviceContext; // required for monitor dpi problem (???) SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); // Create device for (UINT i = 0; i < g_NumDriverTypes; ++i) { hr = D3D11CreateDevice( nullptr, g_DriverTypes[i], nullptr, /* D3D11_CREATE_DEVICE_BGRA_SUPPORT * This flag adds support for surfaces with a different * color channel ordering than the API default. * You need it for compatibility with Direct2D. */ D3D11_CREATE_DEVICE_BGRA_SUPPORT, g_FeatureLevels, g_NumFeatureLevels, D3D11_SDK_VERSION, &ipDevice, &lFeatureLevel, &ipDeviceContext); if (SUCCEEDED(hr)) { // Device creation success, no need to loop anymore break; } ipDevice = nullptr; ipDeviceContext = nullptr; } if (FAILED(hr)) { return hr; } if (nullptr == ipDevice) { return E_UNEXPECTED; } // load all monitor informations hr = loadMonitorInfos(ipDevice); if (FAILED(hr)) { return hr; } // set common fields m_lD3DFeatureLevel = lFeatureLevel; m_ipD3D11Device = ipDevice; m_ipD3D11DeviceContext = ipDeviceContext; m_bInitialized = TRUE; return S_OK; } HRESULT CDXGICapture::Terminate() { AUTOLOCK(); if (!m_bInitialized) { return S_FALSE; // already terminated } this->terminateDeviceResource(); m_ipD3D11Device = nullptr; m_ipD3D11DeviceContext = nullptr; m_lD3DFeatureLevel = D3D_FEATURE_LEVEL_INVALID; freeMonitorInfos(); m_bInitialized = FALSE; return S_OK; } HRESULT CDXGICapture::SetConfig(const tagScreenCaptureFilterConfig *pConfig) { AUTOLOCK(); if (!m_bInitialized) { return D2DERR_NOT_INITIALIZED; } if (nullptr == pConfig) { return E_INVALIDARG; } // terminate old resources this->terminateDeviceResource(); HRESULT hr = S_OK; const tagDublicatorMonitorInfo *pSelectedMonitorInfo = nullptr; pSelectedMonitorInfo = this->FindDublicatorMonitorInfo(pConfig->MonitorIdx); if (nullptr == pSelectedMonitorInfo) { return E_INVALIDARG; } hr = this->createDeviceResource(pConfig, pSelectedMonitorInfo); if (FAILED(hr)) { return hr; } return hr; } HRESULT CDXGICapture::SetConfig(const tagScreenCaptureFilterConfig &config) { return this->SetConfig(&config); } BOOL CDXGICapture::IsInitialized() const { AUTOLOCK(); return m_bInitialized; } D3D_FEATURE_LEVEL CDXGICapture::GetD3DFeatureLevel() const { AUTOLOCK(); return m_lD3DFeatureLevel; } int CDXGICapture::GetDublicatorMonitorInfoCount() const { AUTOLOCK(); return (int)m_monitorInfos.size(); } const tagDublicatorMonitorInfo* CDXGICapture::GetDublicatorMonitorInfo(int index) const { AUTOLOCK(); size_t nCount = m_monitorInfos.size(); if ((index < 0) || (index >= (int)nCount)) { return nullptr; } return m_monitorInfos[index]; } // GetDublicatorMonitorInfo const tagDublicatorMonitorInfo* CDXGICapture::FindDublicatorMonitorInfo(int monitorIdx) const { AUTOLOCK(); size_t nCount = m_monitorInfos.size(); if (nCount == 0) { return nullptr; } DublicatorMonitorInfoVec::const_iterator it = m_monitorInfos.begin(); DublicatorMonitorInfoVec::const_iterator end = m_monitorInfos.end(); for (size_t i = 0; (i < nCount) && (it != end); i++, it++) { tagDublicatorMonitorInfo *pInfo = *it; if (monitorIdx == pInfo->Idx) { return pInfo; } } return nullptr; } // FindDublicatorMonitorInfo // // CaptureToFile // HRESULT CDXGICapture::CaptureToFile(_In_ LPCWSTR lpcwOutputFileName, _Out_opt_ BOOL *pRetIsTimeout /*= NULL*/, _Out_opt_ UINT *pRetRenderDuration /*= NULL*/) { AUTOLOCK(); if (nullptr != pRetIsTimeout) { *pRetIsTimeout = FALSE; } if (nullptr != pRetRenderDuration) { *pRetRenderDuration = 0xFFFFFFFF; } if (!m_bInitialized) { return D2DERR_NOT_INITIALIZED; } CHECK_POINTER_EX(m_ipDxgiOutputDuplication, E_INVALIDARG); CHECK_POINTER_EX(lpcwOutputFileName, E_INVALIDARG); HRESULT hr = S_OK; hr = DXGICaptureHelper::IsRendererInfoValid(&m_rendererInfo); if (FAILED(hr)) { return hr; } // is valid? hr = DXGICaptureHelper::GetContainerFormatByFileName(lpcwOutputFileName); if (FAILED(hr)) { return hr; } DXGI_OUTDUPL_FRAME_INFO FrameInfo; CComPtr ipDesktopResource; CComPtr ipAcquiredDesktopImage; CComPtr ipD2D1SourceBitmap; std::chrono::system_clock::time_point startTick; if (nullptr != pRetRenderDuration) { startTick = std::chrono::system_clock::now(); } // Get new frame hr = m_ipDxgiOutputDuplication->AcquireNextFrame(1000, &FrameInfo, &ipDesktopResource); if (hr == DXGI_ERROR_WAIT_TIMEOUT) { if (nullptr != pRetIsTimeout) { *pRetIsTimeout = TRUE; } return S_FALSE; } else if (FAILED(hr)) { return hr; } // QI for ID3D11Texture2D hr = ipDesktopResource->QueryInterface(IID_PPV_ARGS(&ipAcquiredDesktopImage)); ipDesktopResource = nullptr; CHECK_HR_RETURN(hr); if (nullptr == ipAcquiredDesktopImage) { // release frame m_ipDxgiOutputDuplication->ReleaseFrame(); return E_OUTOFMEMORY; } // Copy needed full part of desktop image m_ipD3D11DeviceContext->CopyResource(m_ipCopyTexture2D, ipAcquiredDesktopImage); if (m_rendererInfo.ShowCursor) { hr = DXGICaptureHelper::GetMouse(m_ipDxgiOutputDuplication, &m_mouseInfo, &FrameInfo, (UINT)m_rendererInfo.MonitorIdx, m_desktopOutputDesc.DesktopCoordinates.left, m_desktopOutputDesc.DesktopCoordinates.top); if (SUCCEEDED(hr) && m_mouseInfo.Visible) { hr = DXGICaptureHelper::DrawMouse(&m_mouseInfo, &m_desktopOutputDesc, &m_tempMouseBuffer, m_ipCopyTexture2D); } if (FAILED(hr)) { // release frame m_ipDxgiOutputDuplication->ReleaseFrame(); return hr; } } // release frame hr = m_ipDxgiOutputDuplication->ReleaseFrame(); CHECK_HR_RETURN(hr); // create D2D1 source bitmap hr = DXGICaptureHelper::CreateBitmap(m_ipD2D1RenderTarget, m_ipCopyTexture2D, &ipD2D1SourceBitmap); CHECK_HR_RETURN(hr); D2D1_RECT_F rcSource = D2D1::RectF( (FLOAT)m_rendererInfo.SrcBounds.X, (FLOAT)m_rendererInfo.SrcBounds.Y, (FLOAT)(m_rendererInfo.SrcBounds.X + m_rendererInfo.SrcBounds.Width), (FLOAT)(m_rendererInfo.SrcBounds.Y + m_rendererInfo.SrcBounds.Height)); D2D1_RECT_F rcTarget = D2D1::RectF( (FLOAT)m_rendererInfo.DstBounds.X, (FLOAT)m_rendererInfo.DstBounds.Y, (FLOAT)(m_rendererInfo.DstBounds.X + m_rendererInfo.DstBounds.Width), (FLOAT)(m_rendererInfo.DstBounds.Y + m_rendererInfo.DstBounds.Height)); D2D1_POINT_2F ptTransformCenter = D2D1::Point2F(m_rendererInfo.OutputSize.Width / 2.0f, m_rendererInfo.OutputSize.Height / 2.0f); // Apply the rotation transform to the render target. D2D1::Matrix3x2F rotate = D2D1::Matrix3x2F::Rotation( m_rendererInfo.RotationDegrees, ptTransformCenter ); D2D1::Matrix3x2F scale = D2D1::Matrix3x2F::Scale( D2D1::SizeF(m_rendererInfo.ScaleX, m_rendererInfo.ScaleY), ptTransformCenter ); // Priority: first rotate, after scale... m_ipD2D1RenderTarget->SetTransform(rotate * scale); m_ipD2D1RenderTarget->BeginDraw(); // clear background color m_ipD2D1RenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::Black, 1.0f)); m_ipD2D1RenderTarget->DrawBitmap(ipD2D1SourceBitmap, rcTarget, 1.0f, D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, rcSource); // Reset transform //m_ipD2D1RenderTarget->SetTransform(D2D1::Matrix3x2F::Identity()); // Logo draw sample //m_ipD2D1RenderTarget->DrawBitmap(ipBmpLogo, D2D1::RectF(0, 0, 2 * 200, 2 * 46)); hr = m_ipD2D1RenderTarget->EndDraw(); if (FAILED(hr)) { return hr; } // calculate render time without save if (nullptr != pRetRenderDuration) { *pRetRenderDuration = (UINT)((std::chrono::system_clock::now() - startTick).count() / 10000); } hr = DXGICaptureHelper::SaveImageToFile(m_ipWICImageFactory, m_ipWICOutputBitmap, lpcwOutputFileName); if (FAILED(hr)) { return hr; } return S_OK; } // CaptureToFile #undef AUTOLOCK