From 72b25b99bc35550d546dc4ae59552eb46dc2b485 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Mon, 29 Jan 2024 21:47:02 +1100 Subject: [PATCH] [host] add new D12 capture interface Note, this capture interface is not yet feature complete but does seem to be stable. --- host/include/interface/capture.h | 22 +- host/platform/Windows/CMakeLists.txt | 3 + host/platform/Windows/capture/CMakeLists.txt | 10 +- .../Windows/capture/D12/CMakeLists.txt | 23 + host/platform/Windows/capture/D12/backend.h | 59 ++ .../platform/Windows/capture/D12/backend/dd.c | 712 +++++++++++++++++ host/platform/Windows/capture/D12/d12.c | 743 ++++++++++++++++++ .../Windows/capture/DXGI/CMakeLists.txt | 3 - .../Windows/capture/DXGI/src/com_ref.c | 153 ---- .../Windows/capture/DXGI/src/com_ref.h | 99 --- .../platform/Windows/capture/DXGI/src/d3d11.c | 11 + .../platform/Windows/capture/DXGI/src/d3d12.c | 20 +- host/platform/Windows/capture/DXGI/src/dxgi.c | 82 +- .../Windows/capture/DXGI/src/pp/downsample.c | 10 +- .../Windows/capture/DXGI/src/pp/rgb24.c | 10 +- .../capture/DXGI/src/pp/sdrwhitelevel.c | 22 +- .../Windows/capture/NVFBC/src/nvfbc.c | 21 +- host/platform/Windows/include/com_ref.h | 98 +++ host/platform/Windows/src/com_ref.c | 108 +++ host/src/app.c | 62 +- 20 files changed, 1919 insertions(+), 352 deletions(-) create mode 100644 host/platform/Windows/capture/D12/CMakeLists.txt create mode 100644 host/platform/Windows/capture/D12/backend.h create mode 100644 host/platform/Windows/capture/D12/backend/dd.c create mode 100644 host/platform/Windows/capture/D12/d12.c delete mode 100644 host/platform/Windows/capture/DXGI/src/com_ref.c delete mode 100644 host/platform/Windows/capture/DXGI/src/com_ref.h create mode 100644 host/platform/Windows/include/com_ref.h create mode 100644 host/platform/Windows/src/com_ref.c diff --git a/host/include/interface/capture.h b/host/include/interface/capture.h index d3c8c8b6..c94f0cd8 100644 --- a/host/include/interface/capture.h +++ b/host/include/interface/capture.h @@ -106,7 +106,7 @@ typedef struct CapturePointer CapturePointer; typedef bool (*CaptureGetPointerBuffer )(void ** data, uint32_t * size); -typedef void (*CapturePostPointerBuffer)(CapturePointer pointer); +typedef void (*CapturePostPointerBuffer)(const CapturePointer * pointer); typedef struct CaptureInterface { @@ -116,19 +116,27 @@ typedef struct CaptureInterface void (*initOptions )(void); bool(*create)( - void * ivshmemBase, CaptureGetPointerBuffer getPointerBufferFn, - CapturePostPointerBuffer postPointerBufferFn + CapturePostPointerBuffer postPointerBufferFn, + unsigned frameBuffers ); - bool (*init )(unsigned * alignSize); + bool (*init )(void * ivshmemBase, unsigned * alignSize); bool (*start )(void); void (*stop )(void); bool (*deinit )(void); void (*free )(void); - CaptureResult (*capture )(unsigned frameBufferIndex, FrameBuffer * frame); - CaptureResult (*waitFrame )(CaptureFrame * frame, const size_t maxFrameSize); - CaptureResult (*getFrame )(FrameBuffer * frame, int frameIndex); + CaptureResult (*capture )( + unsigned frameBufferIndex, + FrameBuffer * frame); + CaptureResult (*waitFrame )( + unsigned frameBufferIndex, + CaptureFrame * frame, + const size_t maxFrameSize); + CaptureResult (*getFrame )( + unsigned frameBufferIndex, + FrameBuffer * frame, + const size_t maxFrameSize); } CaptureInterface; diff --git a/host/platform/Windows/CMakeLists.txt b/host/platform/Windows/CMakeLists.txt index 5beedfa5..30989a00 100644 --- a/host/platform/Windows/CMakeLists.txt +++ b/host/platform/Windows/CMakeLists.txt @@ -10,11 +10,14 @@ add_library(platform_Windows STATIC src/service.c src/mousehook.c src/force_compose.c + src/com_ref.c ) # allow use of functions for Windows 7 or later add_compile_definitions(WINVER=0x0601 _WIN32_WINNT=0x0601) +add_definitions("-DCOBJMACROS -DINITGUID -DWIDL_C_INLINE_WRAPPERS") + add_subdirectory("capture") target_link_libraries(platform_Windows diff --git a/host/platform/Windows/capture/CMakeLists.txt b/host/platform/Windows/capture/CMakeLists.txt index 535802d4..4d679fcb 100644 --- a/host/platform/Windows/capture/CMakeLists.txt +++ b/host/platform/Windows/capture/CMakeLists.txt @@ -3,8 +3,9 @@ project(capture LANGUAGES C) include(PreCapture) -option(USE_NVFBC "Enable NVFBC Support" OFF) +option(USE_D12 "Enable DirectX12 Support" ON) option(USE_DXGI "Enable DXGI Support" ON) +option(USE_NVFBC "Enable NVFBC Support" OFF) if(NOT DEFINED NVFBC_SDK) set(NVFBC_SDK "C:/Program Files (x86)/NVIDIA Corporation/NVIDIA Capture SDK") @@ -17,6 +18,10 @@ if(NOT EXISTS "${nvfbc_sdk}/inc" OR NOT IS_DIRECTORY "${nvfbc_sdk}/inc") set(USE_NVFBC OFF) endif() +if(USE_D12) + add_capture("D12") +endif() + if(USE_DXGI) add_capture("DXGI") endif() @@ -25,7 +30,8 @@ if(USE_NVFBC) add_capture("NVFBC") endif() -add_feature_info(USE_DXGI USE_DXGI "DXGI Desktop Duplication capture backend.") +add_feature_info(USE_D12 USE_D12 "DirectX12 capture backend.") +add_feature_info(USE_DXGI USE_DXGI "DXGI Desktop Duplication capture backend.") add_feature_info(USE_NVFBC USE_NVFBC "NVFBC capture backend.") include("PostCapture") diff --git a/host/platform/Windows/capture/D12/CMakeLists.txt b/host/platform/Windows/capture/D12/CMakeLists.txt new file mode 100644 index 00000000..7af46736 --- /dev/null +++ b/host/platform/Windows/capture/D12/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.5) +project(capture_D12 LANGUAGES C) + +add_library(capture_D12 STATIC + d12.c + backend/dd.c +) + +add_definitions("-DCOBJMACROS -DINITGUID -DWIDL_C_INLINE_WRAPPERS") + +target_link_libraries(capture_D12 + lg_common + d3d11 + dxgi + dwmapi + d3dcompiler +) + +target_include_directories(capture_D12 + PRIVATE + . + "${PROJECT_TOP}/vendor/directx" +) diff --git a/host/platform/Windows/capture/D12/backend.h b/host/platform/Windows/capture/D12/backend.h new file mode 100644 index 00000000..28ed804b --- /dev/null +++ b/host/platform/Windows/capture/D12/backend.h @@ -0,0 +1,59 @@ +/** + * Looking Glass + * Copyright © 2017-2024 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program 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 General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _H_D12_BACKEND_ +#define _H_D12_BACKEND_ + +#include +#include +#include "interface/capture.h" + +typedef struct D12Backend +{ + // friendly name + const char * name; + + // internal name + const char * codeName; + + // creation/init/free + bool (*create)(unsigned frameBuffers); + bool (*init)( + bool debug, + ID3D12Device3 * device, + IDXGIAdapter1 * adapter, + IDXGIOutput * output); + bool (*deinit)(void); + void (*free)(void); + + // capture callbacks + CaptureResult (*capture)(unsigned frameBufferIndex); + CaptureResult (*sync )(ID3D12CommandQueue * commandQueue); + ID3D12Resource * (*fetch )(unsigned frameBufferIndex); +} +D12Backend; + +// apis for the backend +void d12_updatePointer( + CapturePointer * pointer, void * shape, size_t shapeSize); + +extern D12Backend D12Backend_DD; + +#endif diff --git a/host/platform/Windows/capture/D12/backend/dd.c b/host/platform/Windows/capture/D12/backend/dd.c new file mode 100644 index 00000000..672588de --- /dev/null +++ b/host/platform/Windows/capture/D12/backend/dd.c @@ -0,0 +1,712 @@ +#include "backend.h" + +#include "com_ref.h" +#include "common/debug.h" +#include "common/windebug.h" +#include "common/array.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#define CACHE_SIZE 10 + +typedef struct DDCacheInfo +{ + D3D11_TEXTURE2D_DESC format; + ID3D11Texture2D ** srcTex; + ID3D12Resource ** d12Res; + ID3D11Fence ** fence; + ID3D12Fence ** d12Fence; + UINT64 fenceValue; + bool ready; +} +DDCacheInfo; + +struct DDInstance +{ + ComScope * comScope; + + HDESK desktop; + + ID3D12Device3 ** d12device; + ID3D11Device5 ** device; + ID3D11DeviceContext4 ** context; + IDXGIOutputDuplication ** dup; + bool release; + + DDCacheInfo cache[CACHE_SIZE]; + DDCacheInfo * current; + + bool lastPosValid; + DXGI_OUTDUPL_POINTER_POSITION lastPos; + + void * shapeBuffer; + unsigned shapeBufferSize; +}; + +struct DDInstance * this = NULL; + +#define comRef_toGlobal(dst, src) \ + _comRef_toGlobal(this->comScope, dst, src) + +static void d12_dd_openDesktop(void); +static bool d12_dd_handleFrameUpdate(IDXGIResource * res); + +static void d12_dd_handlePointerMovement(DXGI_OUTDUPL_POINTER_POSITION * pos, + CapturePointer * pointer, bool * changed); +static void d12_dd_handlePointerShape( + CapturePointer * pointer, size_t size, bool * changed); + +static bool d12_dd_getCache(ID3D11Texture2D * srcTex, DDCacheInfo ** result); +static bool d12_dd_convertResource(ID3D11Texture2D * srcTex, + DDCacheInfo * cache); + +static bool d12_dd_create(unsigned frameBuffers) +{ + this = calloc(1, sizeof(*this)); + if (!this) + { + DEBUG_ERROR("out of memory"); + return false; + } + + return true; +} + +static bool d12_dd_init( + bool debug, + ID3D12Device3 * device, + IDXGIAdapter1 * adapter, + IDXGIOutput * output) +{ + bool result = false; + HRESULT hr; + + comRef_initGlobalScope(10 + CACHE_SIZE * 2, this->comScope); + comRef_scopePush(10); + + // try to open the desktop so we can capture the secure desktop + d12_dd_openDesktop(); + + comRef_defineLocal(IDXGIAdapter, _adapter); + hr = IDXGIAdapter1_QueryInterface( + adapter, &IID_IDXGIAdapter, (void **)_adapter); + if (FAILED(hr)) + { + DEBUG_ERROR("Failed to get the IDXGIAdapter interface"); + goto exit; + } + + static const D3D_FEATURE_LEVEL featureLevels[] = + { + D3D_FEATURE_LEVEL_12_1, + D3D_FEATURE_LEVEL_12_0, + 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, + D3D_FEATURE_LEVEL_9_2, + D3D_FEATURE_LEVEL_9_1 + }; + D3D_FEATURE_LEVEL featureLevel; + + // create a DirectX11 context + comRef_defineLocal(ID3D11Device , d11device); + comRef_defineLocal(ID3D11DeviceContext, d11context); + hr = D3D11CreateDevice( + *_adapter, + D3D_DRIVER_TYPE_UNKNOWN, + NULL, + D3D11_CREATE_DEVICE_VIDEO_SUPPORT | + (debug ? D3D11_CREATE_DEVICE_DEBUG : 0), + featureLevels, + ARRAY_LENGTH(featureLevels), + D3D11_SDK_VERSION, + d11device, + &featureLevel, + d11context); + + if (FAILED(hr)) + { + DEBUG_WINERROR("Failed to create the D3D11Device", hr); + goto exit; + } + + // get the updated interfaces + comRef_defineLocal(ID3D11DeviceContext4, d11context4); + hr = ID3D11DeviceContext_QueryInterface( + *d11context, &IID_ID3D11DeviceContext4, (void **)d11context4); + if (FAILED(hr)) + { + DEBUG_WINERROR("Failed to get the ID3D11Context4 interface", hr); + goto exit; + } + + comRef_defineLocal(ID3D11Device5, d11device5); + hr = ID3D11Device_QueryInterface( + *d11device, &IID_ID3D11Device5, (void **)d11device5); + if (FAILED(hr)) + { + DEBUG_WINERROR("Failed to get the ID3D11Device5 interface", hr); + goto exit; + } + + // try to reduce the latency + comRef_defineLocal(IDXGIDevice1, dxgi1); + hr = ID3D11Device_QueryInterface( + *d11device, &IID_IDXGIDevice1, (void **)dxgi1); + if (FAILED(hr)) + { + DEBUG_WINERROR("failed to query the DXGI interface from the device", hr); + goto exit; + } + IDXGIDevice1_SetMaximumFrameLatency(*dxgi1, 1); + + // duplicate the output + comRef_defineLocal(IDXGIOutput5 , output5); + comRef_defineLocal(IDXGIOutputDuplication, dup ); + hr = IDXGIOutput_QueryInterface(output, &IID_IDXGIOutput5, (void **)output5); + if (FAILED(hr)) + { + DEBUG_WARN("IDXGIOutput5 is not available, " + "please update windows for improved performance!"); + DEBUG_WARN("Falling back to IDXGIOutput1"); + + comRef_defineLocal(IDXGIOutput1, output1); + hr = IDXGIOutput_QueryInterface(output, &IID_IDXGIOutput1, (void **)output1); + if (FAILED(hr)) + { + DEBUG_ERROR("Failed to query IDXGIOutput1 from the output"); + goto exit; + } + + // we try this twice in case we still get an error on re-initialization + for (int i = 0; i < 2; ++i) + { + hr = IDXGIOutput1_DuplicateOutput(*output1, *(IUnknown **)d11device, dup); + if (SUCCEEDED(hr)) + break; + Sleep(200); + } + } + else + { + static const DXGI_FORMAT supportedFormats[] = + { + DXGI_FORMAT_B8G8R8A8_UNORM, + DXGI_FORMAT_R8G8B8A8_UNORM, + DXGI_FORMAT_R16G16B16A16_FLOAT + }; + + // we try this twice in case we still get an error on re-initialization + for (int i = 0; i < 2; ++i) + { + hr = IDXGIOutput5_DuplicateOutput1( + *output5, + *(IUnknown **)d11device, + 0, + ARRAY_LENGTH(supportedFormats), + supportedFormats, + dup); + + if (SUCCEEDED(hr)) + break; + + // if access is denied we just keep trying until it isn't + if (hr == E_ACCESSDENIED) + --i; + + Sleep(200); + } + } + + if (FAILED(hr)) + { + DEBUG_WINERROR("DuplicateOutput Failed", hr); + goto exit; + } + + ID3D12Device3_AddRef(device); + comRef_toGlobal(this->d12device, &device ); + comRef_toGlobal(this->device , d11device5 ); + comRef_toGlobal(this->context , d11context4); + comRef_toGlobal(this->dup , dup ); + result = true; + +exit: + comRef_scopePop(); + if (!result) + comRef_freeScope(&this->comScope); + + return result; +} + +static bool d12_dd_deinit(void) +{ + if (this->release) + { + IDXGIOutputDuplication_ReleaseFrame(*this->dup); + this->release = false; + } + + if (this->desktop) + { + CloseDesktop(this->desktop); + this->desktop = NULL; + } + + comRef_freeScope(&this->comScope); + memset(this, 0, sizeof(*this)); + return true; +} + +static void d12_dd_free(void) +{ + free(this->shapeBuffer); + free(this); + this = NULL; +} + +static CaptureResult d12_dd_hResultToCaptureResult(const HRESULT status) +{ + switch(status) + { + case S_OK: + return CAPTURE_RESULT_OK; + + case DXGI_ERROR_WAIT_TIMEOUT: + return CAPTURE_RESULT_TIMEOUT; + + case WAIT_ABANDONED: + case DXGI_ERROR_ACCESS_LOST: + return CAPTURE_RESULT_REINIT; + + default: + return CAPTURE_RESULT_ERROR; + } +} + +static CaptureResult d12_dd_capture(unsigned frameBufferIndex) +{ + HRESULT hr; + CaptureResult result = CAPTURE_RESULT_ERROR; + comRef_scopePush(10); + + DXGI_OUTDUPL_FRAME_INFO frameInfo = {0}; + comRef_defineLocal(IDXGIResource, res); + +retry: + if (this->release) + { + IDXGIOutputDuplication_ReleaseFrame(*this->dup); + this->release = false; + } + + hr = IDXGIOutputDuplication_AcquireNextFrame( + *this->dup, 1000, &frameInfo, res); + + result = d12_dd_hResultToCaptureResult(hr); + if (result != CAPTURE_RESULT_OK) + { + if (result == CAPTURE_RESULT_ERROR) + DEBUG_WINERROR("AcquireNextFrame failed", hr); + + if (hr == DXGI_ERROR_ACCESS_LOST) + { + hr = ID3D11Device5_GetDeviceRemovedReason(*this->device); + if (FAILED(hr)) + { + DEBUG_WINERROR("Device Removed", hr); + result = CAPTURE_RESULT_ERROR; + } + } + + goto exit; + } + + this->release = true; + + // if we have a new frame + if (frameInfo.LastPresentTime.QuadPart != 0) + if (!d12_dd_handleFrameUpdate(*res)) + { + result = CAPTURE_RESULT_ERROR; + goto exit; + } + + bool postPointer = false; + bool postShape = false; + CapturePointer pointer = {0}; + + // if the pointer has moved + if (frameInfo.LastMouseUpdateTime.QuadPart != 0) + d12_dd_handlePointerMovement( + &frameInfo.PointerPosition, &pointer, &postPointer); + + // if the pointer shape has changed + if (frameInfo.PointerShapeBufferSize > 0) + d12_dd_handlePointerShape( + &pointer, frameInfo.PointerShapeBufferSize, &postShape); + + if (postPointer) + d12_updatePointer(&pointer, this->shapeBuffer, this->shapeBufferSize); + + // if this was not a frame update, go back and try again + if (frameInfo.LastPresentTime.QuadPart == 0) + goto retry; + +exit: + comRef_scopePop(); + return result; +} + +static CaptureResult d12_dd_sync(ID3D12CommandQueue * commandQueue) +{ + if (!this->current) + return CAPTURE_RESULT_TIMEOUT; + + DDCacheInfo * cache = this->current; + if (ID3D11Fence_GetCompletedValue(*cache->fence) < cache->fenceValue) + ID3D12CommandQueue_Wait(commandQueue, *cache->d12Fence, cache->fenceValue); + + return CAPTURE_RESULT_OK; +} + +static ID3D12Resource * d12_dd_fetch(unsigned frameBufferIndex) +{ + if (!this->current) + return NULL; + + ID3D12Resource_AddRef(*this->current->d12Res); + return *this->current->d12Res; +} + +static void d12_dd_openDesktop(void) +{ + this->desktop = OpenInputDesktop(0, FALSE, GENERIC_READ); + if (!this->desktop) + DEBUG_WINERROR("Failed to open the desktop", GetLastError()); + else + { + if (!SetThreadDesktop(this->desktop)) + { + DEBUG_WINERROR("Failed to set the thread desktop", GetLastError()); + CloseDesktop(this->desktop); + this->desktop = NULL; + } + } + + if (!this->desktop) + { + DEBUG_INFO("The above error(s) will prevent LG from being able to capture " + "the secure desktop (UAC dialogs)"); + DEBUG_INFO("This is not a failure, please do not report this as an issue."); + DEBUG_INFO("To fix this, install and run the Looking Glass host as a " + "service."); + DEBUG_INFO("looking-glass-host.exe InstallService"); + } +} + +static bool d12_dd_handleFrameUpdate(IDXGIResource * res) +{ + bool result = false; + comRef_scopePush(1); + + comRef_defineLocal(ID3D11Texture2D, srcTex); + HRESULT hr = IDXGIResource_QueryInterface( + res, &IID_ID3D11Texture2D, (void **)srcTex); + if (FAILED(hr)) + { + DEBUG_WINERROR("Failed to obtain the D3D11Texture2D interface", hr); + goto exit; + } + + if (!d12_dd_getCache(*srcTex, &this->current)) + goto exit; + + /** + * Even though we have not performed any copy/draw operations we still need to + * use a fence. Because we share this texture with DirectX12 it is able to + * read from it before the desktop duplication API has finished updating it.*/ + ++this->current->fenceValue; + ID3D11DeviceContext4_Signal( + *this->context, *this->current->fence, this->current->fenceValue); + + result = true; + +exit: + comRef_scopePop(); + return result; +} + +static void d12_dd_handlePointerMovement(DXGI_OUTDUPL_POINTER_POSITION * pos, + CapturePointer * pointer, bool * changed) +{ + bool setPos = false; + + // if the last position is valid, check against it for changes + if (this->lastPosValid) + { + // update the position only if the pointer is visible and it has moved + if (pos->Visible && ( + pos->Position.x != this->lastPos.Position.x || + pos->Position.y != this->lastPos.Position.y)) + setPos = true; + + // if the visibillity has changed + if (pos->Visible != this->lastPos.Visible) + *changed = true; + } + else + { + // update the position only if the pointer is visible + setPos = pos->Visible; + + // this is the first update, we need to send it + *changed = true; + } + + pointer->visible = pos->Visible; + if (setPos) + { + pointer->positionUpdate = true; + pointer->x = pos->Position.x; + pointer->y = pos->Position.y; + + *changed = true; + } + + memcpy(&this->lastPos, pos, sizeof(*pos)); + this->lastPosValid = true; +} + +static void d12_dd_handlePointerShape( + CapturePointer * pointer, size_t size, bool * changed) +{ + HRESULT hr; + DXGI_OUTDUPL_POINTER_SHAPE_INFO info; + +retry: + if (this->shapeBufferSize < size) + { + free(this->shapeBuffer); + this->shapeBuffer = malloc(size); + if (!this->shapeBuffer) + { + DEBUG_ERROR("out of memory"); + this->shapeBufferSize = 0; + return; + } + this->shapeBufferSize = size; + } + + UINT s; + hr = IDXGIOutputDuplication_GetFramePointerShape( + *this->dup, + this->shapeBufferSize, + this->shapeBuffer, + &s, + &info); + + if (FAILED(hr)) + { + if (hr == DXGI_ERROR_MORE_DATA) + { + size = s; + goto retry; + } + DEBUG_WINERROR("Failed to get the pointer shape", hr); + return; + } + + switch(info.Type) + { + case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR: + pointer->format = CAPTURE_FMT_COLOR; + break; + + case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: + pointer->format = CAPTURE_FMT_MASKED; + break; + + case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME: + pointer->format = CAPTURE_FMT_MONO; + break; + + default: + DEBUG_ERROR("Unsupporter cursor format"); + return; + } + + pointer->shapeUpdate = true; + pointer->width = info.Width; + pointer->height = info.Height; + pointer->pitch = info.Pitch; + pointer->hx = info.HotSpot.x; + pointer->hy = info.HotSpot.y; + + *changed = true; +} + +static bool d12_dd_getCache(ID3D11Texture2D * srcTex, DDCacheInfo ** result) +{ + *result = NULL; + D3D11_TEXTURE2D_DESC srcDesc; + ID3D11Texture2D_GetDesc(srcTex, &srcDesc); + + unsigned freeSlot = CACHE_SIZE; + for(unsigned i = 0; i < CACHE_SIZE; ++i) + { + DDCacheInfo * cache = &this->cache[i]; + if (!cache->ready) + { + freeSlot = min(freeSlot, i); + continue; + } + + // check for a resource match + if (*cache->srcTex != srcTex) + continue; + + // check if the match is not valid + if (cache->format.Width != srcDesc.Width || + cache->format.Height != srcDesc.Height || + cache->format.Format != srcDesc.Format) + { + // break out and allow this entry to be rebuilt + cache->ready = false; + freeSlot = i; + break; + } + + // found, so return it + *result = cache; + return true; + } + + // cache is full + if (freeSlot == CACHE_SIZE) + return false; + + // convert the resource + if (!d12_dd_convertResource(srcTex, &this->cache[freeSlot])) + return false; + + // return the new cache entry + *result = &this->cache[freeSlot]; + return true; +} + +static bool d12_dd_convertResource(ID3D11Texture2D * srcTex, DDCacheInfo * cache) +{ + bool result = false; + HRESULT hr; + comRef_scopePush(10); + + D3D11_TEXTURE2D_DESC srcDesc; + ID3D11Texture2D_GetDesc(srcTex, &srcDesc); + + // get the DXGI resource interface so we can create the shared handle + comRef_defineLocal(IDXGIResource1, dxgiRes); + hr = ID3D11Texture2D_QueryInterface( + srcTex, &IID_IDXGIResource1, (void **)dxgiRes); + if (FAILED(hr)) + { + DEBUG_WINERROR("Failed to obtain the shared ID3D11Resource1 interface", hr); + goto exit; + } + + // create the shared handle + HANDLE sharedHandle; + hr = IDXGIResource1_CreateSharedHandle( + *dxgiRes, NULL, DXGI_SHARED_RESOURCE_READ, NULL, &sharedHandle); + if (FAILED(hr)) + { + DEBUG_WINERROR("Failed to create the shared handle", hr); + goto exit; + } + + // open the resource as a DirectX12 resource + comRef_defineLocal(ID3D12Resource, dst); + hr = ID3D12Device3_OpenSharedHandle( + *this->d12device, sharedHandle, &IID_ID3D12Resource, (void **)dst); + if (FAILED(hr)) + { + DEBUG_WINERROR("Failed to open the D3D12Resource from the handle", hr); + CloseHandle(sharedHandle); + goto exit; + } + + // close the shared handle + CloseHandle(sharedHandle); + + // create the sync fence + comRef_defineLocal(ID3D11Fence, fence); + hr = ID3D11Device5_CreateFence( + *this->device, 0, D3D11_FENCE_FLAG_SHARED, &IID_ID3D11Fence, (void **)fence); + if (FAILED(hr)) + { + DEBUG_WINERROR("Failed to create the fence", hr); + goto exit; + } + + // create the fence shared handle + hr = ID3D11Fence_CreateSharedHandle( + *fence, NULL, GENERIC_ALL, NULL, &sharedHandle); + if (FAILED(hr)) + { + DEBUG_WINERROR("Failed to create the fence shared handle", hr); + goto exit; + } + + // open the fence as a DirectX12 fence + comRef_defineLocal(ID3D12Fence, d12Fence); + hr = ID3D12Device3_OpenSharedHandle( + *this->d12device, sharedHandle, &IID_ID3D12Fence, (void **)d12Fence); + if (FAILED(hr)) + { + DEBUG_WINERROR("Failed to open the D3D12Fence from the handle", hr); + CloseHandle(sharedHandle); + goto exit; + } + + // close the shared handle + CloseHandle(sharedHandle); + + // store the details + ID3D11Texture2D_AddRef(srcTex); + comRef_toGlobal(cache->srcTex , &srcTex ); + comRef_toGlobal(cache->d12Res , dst ); + comRef_toGlobal(cache->fence , fence ); + comRef_toGlobal(cache->d12Fence, d12Fence); + memcpy(&cache->format, &srcDesc, sizeof(srcDesc)); + cache->fenceValue = 0; + cache->ready = true; + + result = true; +exit: + comRef_scopePop(); + return result; +} + +D12Backend D12Backend_DD = +{ + .name = "Desktop Duplication", + .codeName = "DD", + + .create = d12_dd_create, + .init = d12_dd_init, + .deinit = d12_dd_deinit, + .free = d12_dd_free, + .capture = d12_dd_capture, + .sync = d12_dd_sync, + .fetch = d12_dd_fetch +}; diff --git a/host/platform/Windows/capture/D12/d12.c b/host/platform/Windows/capture/D12/d12.c new file mode 100644 index 00000000..0a91bbff --- /dev/null +++ b/host/platform/Windows/capture/D12/d12.c @@ -0,0 +1,743 @@ +#include "interface/capture.h" + +#include "common/array.h" +#include "common/debug.h" +#include "common/windebug.h" +#include "com_ref.h" + +#include "backend.h" + +#include +#include +#include + +// definitions + +typedef HRESULT (*D3D12CreateDevice_t)( + IUnknown *pAdapter, + D3D_FEATURE_LEVEL MinimumFeatureLevel, + REFIID riid, + void **ppDevice +); + +typedef HRESULT (*D3D12GetDebugInterface_t)( + REFIID riid, + void **ppvDebug +); + +typedef struct D12CommandGroup +{ + ID3D12CommandAllocator ** allocator; + ID3D12GraphicsCommandList ** gfxList; + ID3D12CommandList ** cmdList; + ID3D12Fence ** fence; + HANDLE event; + UINT64 fenceValue; +} +D12CommandGroup; + +struct D12Interface +{ + ComScope * comScope; + + HMODULE d3d12; + D3D12CreateDevice_t D3D12CreateDevice; + D3D12GetDebugInterface_t D3D12GetDebugInterface; + + IDXGIFactory2 ** factory; + ID3D12Device3 ** device; + + ID3D12CommandQueue ** commandQueue; + D12CommandGroup copyCommand; + + void * ivshmemBase; + ID3D12Heap ** ivshmemHeap; + + CaptureGetPointerBuffer getPointerBufferFn; + CapturePostPointerBuffer postPointerBufferFn; + + D12Backend * backend; + + // capture format tracking + D3D12_RESOURCE_DESC lastFormat; + unsigned formatVer; + + // options + bool debug; + + // must be last + struct + { + // the size of the frame buffer + unsigned size; + // the frame buffer it itself + FrameBuffer * frameBuffer; + // the resource backed by the framebuffer + ID3D12Resource ** resource; + } + frameBuffers[0]; +}; + +// defines + +#define comRef_toGlobal(dst, src) \ + _comRef_toGlobal(this->comScope, dst, src) + +// locals + +static struct D12Interface * this = NULL; + +// forwards + +static bool d12_enumerateDevices( + IDXGIFactory2 ** factory, + IDXGIAdapter1 ** adapter, + IDXGIOutput ** output); + +static bool d12_createCommandGroup( + ID3D12Device3 * device, + D3D12_COMMAND_LIST_TYPE type, + D12CommandGroup * dst, + LPCWSTR name); + +static void d12_freeCommandGroup( + D12CommandGroup * grp); + +static bool d12_executeCommandGroup( + D12CommandGroup * grp); + +static ID3D12Resource * d12_frameBufferToResource( + unsigned frameBufferIndex, + FrameBuffer * frameBuffer, + unsigned size); + +// implementation + +static const char * d12_getName(void) +{ + return "D12"; +} + +static void d12_initOptions(void) +{ +} + +static bool d12_create( + CaptureGetPointerBuffer getPointerBufferFn, + CapturePostPointerBuffer postPointerBufferFn, + unsigned frameBuffers) +{ + this = calloc(1, offsetof(struct D12Interface, frameBuffers) + + sizeof(this->frameBuffers[0]) * frameBuffers); + if (!this) + { + DEBUG_ERROR("failed to allocate D12Interface struct"); + return false; + } + + this->debug = false; + this->d3d12 = LoadLibrary("d3d12.dll"); + if (!this->d3d12) + { + DEBUG_ERROR("failed to load d3d12.dll"); + free(this); + return false; + } + + this->D3D12CreateDevice = (D3D12CreateDevice_t) + GetProcAddress(this->d3d12, "D3D12CreateDevice"); + + this->D3D12GetDebugInterface = (D3D12GetDebugInterface_t) + GetProcAddress(this->d3d12, "D3D12GetDebugInterface"); + + this->getPointerBufferFn = getPointerBufferFn; + this->postPointerBufferFn = postPointerBufferFn; + + this->backend = &D12Backend_DD; + if (!this->backend->create(frameBuffers)) + { + DEBUG_ERROR("backend \"%s\" failed to create", this->backend->codeName); + CloseHandle(this->d3d12); + free(this); + return false; + } + + return true; +} + +static bool d12_init(void * ivshmemBase, unsigned * alignSize) +{ + bool result = false; + comRef_initGlobalScope(100, this->comScope); + comRef_scopePush(10); + + // create a DXGI factory + comRef_defineLocal(IDXGIFactory2, factory); + HRESULT hr = CreateDXGIFactory2( + this->debug ? DXGI_CREATE_FACTORY_DEBUG : 0, + &IID_IDXGIFactory2, + (void **)factory); + + if (FAILED(hr)) + { + DEBUG_WINERROR("Failed to create the DXGI factory", hr); + goto exit; + } + + // find the adapter and output we want to use + comRef_defineLocal(IDXGIAdapter1, adapter); + comRef_defineLocal(IDXGIOutput , output ); + if (!d12_enumerateDevices(factory, adapter, output)) + goto exit; + + if (this->debug) + { + comRef_defineLocal(ID3D12Debug1, debug); + hr = this->D3D12GetDebugInterface(&IID_ID3D12Debug1, (void **)debug); + if (FAILED(hr)) + { + DEBUG_WINERROR("D3D12GetDebugInterface", hr); + goto exit; + } + + ID3D12Debug1_EnableDebugLayer(*debug); + ID3D12Debug1_SetEnableGPUBasedValidation(*debug, TRUE); + ID3D12Debug1_SetEnableSynchronizedCommandQueueValidation(*debug, TRUE); + } + + // create the D3D12 device + comRef_defineLocal(ID3D12Device3, device); + hr = this->D3D12CreateDevice( + (IUnknown *)*adapter, + D3D_FEATURE_LEVEL_12_0, + &IID_ID3D12Device3, + (void **)device); + + if (FAILED(hr)) + { + DEBUG_WINERROR("Failed to create the DirectX12 device", hr); + goto exit; + } + + /* make this static as we downgrade the priority on failure and we want to + remember it */ + static D3D12_COMMAND_QUEUE_DESC queueDesc = + { + .Type = D3D12_COMMAND_LIST_TYPE_COPY, + .Priority = D3D12_COMMAND_QUEUE_PRIORITY_GLOBAL_REALTIME, + .Flags = D3D12_COMMAND_QUEUE_FLAG_NONE, + }; + + comRef_defineLocal(ID3D12CommandQueue, commandQueue); +retryCreateCommandQueue: + hr = ID3D12Device3_CreateCommandQueue( + *device, &queueDesc, &IID_ID3D12CommandQueue, (void **)commandQueue); + if (FAILED(hr)) + { + if (queueDesc.Priority == D3D12_COMMAND_QUEUE_PRIORITY_GLOBAL_REALTIME) + { + DEBUG_WARN("Failed to create queue with real time priority"); + queueDesc.Priority = D3D12_COMMAND_QUEUE_PRIORITY_HIGH; + goto retryCreateCommandQueue; + } + + DEBUG_WINERROR("Failed to create ID3D12CommandQueue", hr); + goto exit; + } + ID3D12CommandQueue_SetName(*commandQueue, L"Command Queue"); + + if (!d12_createCommandGroup( + *device, D3D12_COMMAND_LIST_TYPE_COPY, &this->copyCommand, L"Copy")) + goto exit; + + // Create the IVSHMEM heap + this->ivshmemBase = ivshmemBase; + comRef_defineLocal(ID3D12Heap, ivshmemHeap); + hr = ID3D12Device3_OpenExistingHeapFromAddress( + *device, ivshmemBase, &IID_ID3D12Heap, (void **)ivshmemHeap); + if (FAILED(hr)) + { + DEBUG_WINERROR("Failed to open the framebuffer as a D3D12Heap", hr); + goto exit; + } + + // Adjust the alignSize based on the required heap alignment + D3D12_HEAP_DESC heapDesc = ID3D12Heap_GetDesc(*ivshmemHeap); + *alignSize = heapDesc.Alignment; + + // initialize the backend + if (!this->backend->init(this->debug, *device, *adapter, *output)) + goto exit; + + comRef_toGlobal(this->factory , factory ); + comRef_toGlobal(this->device , device ); + comRef_toGlobal(this->commandQueue, commandQueue); + comRef_toGlobal(this->ivshmemHeap , ivshmemHeap ); + + result = true; + +exit: + comRef_scopePop(); + if (!result) + comRef_freeScope(&this->comScope); + + return result; +} + +static void d12_stop(void) +{ +} + +static bool d12_deinit(void) +{ + bool result = true; + if (!this->backend->deinit()) + result = false; + + d12_freeCommandGroup(&this->copyCommand); + comRef_freeScope(&this->comScope); + return result; +} + +static void d12_free(void) +{ + this->backend->free(); + FreeLibrary(this->d3d12); + free(this); + this = NULL; +} + +static CaptureResult d12_capture( + unsigned frameBufferIndex, FrameBuffer * frameBuffer) +{ + return this->backend->capture(frameBufferIndex); +} + +static CaptureResult d12_waitFrame(unsigned frameBufferIndex, + CaptureFrame * frame, const size_t maxFrameSize) +{ + CaptureResult result = CAPTURE_RESULT_ERROR; + comRef_scopePush(1); + + comRef_defineLocal(ID3D12Resource, src); + *src = this->backend->fetch(frameBufferIndex); + if (!*src) + { + DEBUG_ERROR("D12 backend failed to produce an expected frame: %u", + frameBufferIndex); + result = CAPTURE_RESULT_ERROR; + goto exit; + } + + D3D12_RESOURCE_DESC desc = ID3D12Resource_GetDesc(*src); + if (desc.Width != this->lastFormat.Width || + desc.Height != this->lastFormat.Height || + desc.Format != this->lastFormat.Format) + { + ++this->formatVer; + memcpy(&this->lastFormat, &desc, sizeof(desc)); + } + + const unsigned int maxRows = maxFrameSize / (desc.Width * 4); + + frame->formatVer = this->formatVer; + frame->screenWidth = desc.Width; + frame->screenHeight = desc.Height; + frame->dataWidth = desc.Width; + frame->dataHeight = min(maxRows, desc.Height); + frame->frameWidth = desc.Width; + frame->frameHeight = desc.Height; + frame->truncated = maxRows < desc.Height; + frame->pitch = desc.Width * 4; + frame->stride = desc.Width; + frame->format = CAPTURE_FMT_BGRA; + frame->hdr = false; + frame->hdrPQ = false; + frame->rotation = CAPTURE_ROT_0; + frame->damageRectsCount = 0; + + result = CAPTURE_RESULT_OK; + +exit: + comRef_scopePop(); + return result; +} + +static CaptureResult d12_getFrame(unsigned frameBufferIndex, + FrameBuffer * frameBuffer, const size_t maxFrameSize) +{ + CaptureResult result = CAPTURE_RESULT_ERROR; + comRef_scopePush(2); + + comRef_defineLocal(ID3D12Resource, src); + *src = this->backend->fetch(frameBufferIndex); + if (!*src) + { + DEBUG_ERROR("D12 backend failed to produce an expected frame: %u", + frameBufferIndex); + goto exit; + } + + comRef_defineLocal(ID3D12Resource, dst) + *dst = d12_frameBufferToResource(frameBufferIndex, frameBuffer, maxFrameSize); + if (!*dst) + goto exit; + + // copy into the framebuffer resource + D3D12_RESOURCE_DESC desc = ID3D12Resource_GetDesc(*src); + D3D12_TEXTURE_COPY_LOCATION srcLoc = + { + .pResource = *src, + .Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX, + .SubresourceIndex = 0 + }; + + D3D12_TEXTURE_COPY_LOCATION dstLoc = + { + .pResource = *dst, + .Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT, + .PlacedFootprint = + { + .Offset = 0, + .Footprint = + { + .Format = desc.Format, + .Width = desc.Width, + .Height = desc.Height, + .Depth = 1, + .RowPitch = desc.Width * 4 + } + } + }; + + ID3D12GraphicsCommandList_CopyTextureRegion( + *this->copyCommand.gfxList, &dstLoc, 0, 0, 0, &srcLoc, NULL); + + // allow the backend to insert a fence into the command queue if it needs it + result = this->backend->sync(*this->commandQueue); + if (result != CAPTURE_RESULT_OK) + goto exit; + + d12_executeCommandGroup(&this->copyCommand); + + framebuffer_set_write_ptr(frameBuffer, desc.Height * desc.Width * 4); + result = CAPTURE_RESULT_OK; + +exit: + comRef_scopePop(); + return result; +} + +static bool d12_enumerateDevices( + IDXGIFactory2 ** factory, + IDXGIAdapter1 ** adapter, + IDXGIOutput ** output) +{ + DXGI_ADAPTER_DESC1 adapterDesc; + DXGI_OUTPUT_DESC outputDesc; + + for( + int i = 0; + IDXGIFactory2_EnumAdapters1(*factory, i, adapter) + != DXGI_ERROR_NOT_FOUND; + ++i, comRef_release(adapter)) + { + HRESULT hr = IDXGIAdapter1_GetDesc1(*adapter, &adapterDesc); + if (FAILED(hr)) + { + DEBUG_WINERROR("Failed to get the device description", hr); + comRef_release(adapter); + return false; + } + + // check for devices without D3D support + static const UINT blacklist[][2] = + { + //VID , PID + {0x1414, 0x008c}, // Microsoft Basic Render Driver + {0x1b36, 0x000d}, // QXL + {0x1234, 0x1111} // QEMU Standard VGA + }; + + bool skip = false; + for(int n = 0; n < ARRAY_LENGTH(blacklist); ++n) + { + if (adapterDesc.VendorId == blacklist[n][0] && + adapterDesc.DeviceId == blacklist[n][1]) + { + DEBUG_INFO("Not using unsupported adapter: %ls", + adapterDesc.Description); + skip = true; + break; + } + } + if (skip) + continue; + + // FIXME: Allow specifying the specific adapter + + for( + int n = 0; + IDXGIAdapter1_EnumOutputs(*adapter, n, output) != DXGI_ERROR_NOT_FOUND; + ++n, comRef_release(output)) + { + IDXGIOutput_GetDesc(*output, &outputDesc); + // FIXME: Allow specifying the specific output + + if (outputDesc.AttachedToDesktop) + break; + } + + if (*output) + break; + } + + if (!*output) + { + DEBUG_ERROR("Failed to locate a valid output device"); + return false; + } + + DEBUG_INFO("Device Name : %ls" , outputDesc.DeviceName); + DEBUG_INFO("Device Description: %ls" , adapterDesc.Description); + DEBUG_INFO("Device Vendor ID : 0x%x" , adapterDesc.VendorId); + DEBUG_INFO("Device Device ID : 0x%x" , adapterDesc.DeviceId); + DEBUG_INFO("Device Video Mem : %u MiB" , + (unsigned)(adapterDesc.DedicatedVideoMemory / 1048576)); + DEBUG_INFO("Device Sys Mem : %u MiB" , + (unsigned)(adapterDesc.DedicatedSystemMemory / 1048576)); + DEBUG_INFO("Shared Sys Mem : %u MiB" , + (unsigned)(adapterDesc.SharedSystemMemory / 1048576)); + + return true; +} + +static bool d12_createCommandGroup( + ID3D12Device3 * device, + D3D12_COMMAND_LIST_TYPE type, + D12CommandGroup * dst, + LPCWSTR name) +{ + bool result = false; + HRESULT hr; + comRef_scopePush(10); + + comRef_defineLocal(ID3D12CommandAllocator, allocator); + hr = ID3D12Device3_CreateCommandAllocator( + device, + type, + &IID_ID3D12CommandAllocator, + (void **)allocator); + if (FAILED(hr)) + { + DEBUG_ERROR("Failed to create the ID3D12CommandAllocator"); + goto exit; + } + ID3D12CommandAllocator_SetName(*allocator, name); + + comRef_defineLocal(ID3D12GraphicsCommandList, gfxList); + hr = ID3D12Device3_CreateCommandList( + device, + 0, + type, + *allocator, + NULL, + &IID_ID3D12GraphicsCommandList, + (void **)gfxList); + if (FAILED(hr)) + { + DEBUG_WINERROR("Failed to create ID3D12GraphicsCommandList", hr); + goto exit; + } + ID3D12GraphicsCommandList_SetName(*gfxList, name); + + comRef_defineLocal(ID3D12CommandList, cmdList); + hr = ID3D12GraphicsCommandList_QueryInterface( + *gfxList, &IID_ID3D12CommandList, (void **)cmdList); + if (FAILED(hr)) + { + DEBUG_WINERROR("Failed to query the ID3D12CommandList interface", hr); + goto exit; + } + + comRef_defineLocal(ID3D12Fence, fence); + hr = ID3D12Device3_CreateFence( + device, 0, D3D12_FENCE_FLAG_NONE, &IID_ID3D12Fence, (void **)fence); + if (FAILED(hr)) + { + DEBUG_WINERROR("Failed to create ID3D12Fence", hr); + goto exit; + } + + // Create the completion event for the fence + HANDLE event = CreateEvent(NULL, FALSE, FALSE, NULL); + if (!event) + { + DEBUG_WINERROR("Failed to create the completion event", GetLastError()); + goto exit; + } + + comRef_toGlobal(dst->allocator, allocator); + comRef_toGlobal(dst->gfxList , gfxList ); + comRef_toGlobal(dst->cmdList , cmdList ); + comRef_toGlobal(dst->fence , fence ); + dst->event = event; + dst->fenceValue = 0; + + result = true; + +exit: + comRef_scopePop(); + return result; +} + +static void d12_freeCommandGroup( + D12CommandGroup * grp) +{ + // com objet release is handled by comRef, but the handle is not + if (grp->event) + { + CloseHandle(grp->event); + grp->event = NULL; + } +} + +static bool d12_executeCommandGroup( + D12CommandGroup * grp) +{ + HRESULT hr = ID3D12GraphicsCommandList_Close(*grp->gfxList); + if (FAILED(hr)) + { + DEBUG_WINERROR("Failed to close the command list", hr); + return false; + } + + ID3D12CommandQueue_ExecuteCommandLists( + *this->commandQueue, 1, grp->cmdList); + + hr = ID3D12CommandQueue_Signal( + *this->commandQueue, *grp->fence, ++grp->fenceValue); + if (FAILED(hr)) + { + DEBUG_WINERROR("Failed to set the fence signal", hr); + return false; + } + + if (ID3D12Fence_GetCompletedValue(*grp->fence) < grp->fenceValue) + { + ID3D12Fence_SetEventOnCompletion(*grp->fence, grp->fenceValue, grp->event); + WaitForSingleObject(grp->event, INFINITE); + } + + hr = ID3D12CommandAllocator_Reset(*grp->allocator); + if (FAILED(hr)) + { + DEBUG_WINERROR("Failed to reset the command allocator", hr); + return false; + } + + hr = ID3D12GraphicsCommandList_Reset(*grp->gfxList, *grp->allocator, NULL); + if (FAILED(hr)) + { + DEBUG_WINERROR("Failed to reset the graphics command list", hr); + return false; + } + + return true; +} + +static ID3D12Resource * d12_frameBufferToResource(unsigned frameBufferIndex, + FrameBuffer * frameBuffer, unsigned size) +{ + ID3D12Resource * result = NULL; + comRef_scopePush(10); + + typeof(this->frameBuffers[0]) * fb = &this->frameBuffers[frameBufferIndex]; + + // nothing to do if the resource is already setup and is big enough + if (fb->resource && fb->frameBuffer == frameBuffer && fb->size >= size) + { + result = *fb->resource; + ID3D12Resource_AddRef(result); + goto exit; + } + + fb->size = size; + fb->frameBuffer = frameBuffer; + + D3D12_RESOURCE_DESC desc = + { + .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER, + .Alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT, + .Width = size, + .Height = 1, + .DepthOrArraySize = 1, + .MipLevels = 1, + .Format = DXGI_FORMAT_UNKNOWN, + .SampleDesc.Count = 1, + .SampleDesc.Quality = 0, + .Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR, + .Flags = D3D12_RESOURCE_FLAG_ALLOW_CROSS_ADAPTER + }; + + comRef_defineLocal(ID3D12Resource, resource); + HRESULT hr = ID3D12Device3_CreatePlacedResource( + *this->device, + *this->ivshmemHeap, + (uintptr_t)framebuffer_get_data(frameBuffer) - (uintptr_t)this->ivshmemBase, + &desc, + D3D12_RESOURCE_STATE_COPY_DEST, + NULL, + &IID_ID3D12Resource, + (void **)resource); + + if (FAILED(hr)) + { + DEBUG_WINERROR("Failed to create the FrameBuffer ID3D12Resource", hr); + goto exit; + } + + // cache the resource + comRef_toGlobal(fb->resource, resource); + result = *fb->resource; + ID3D12Resource_AddRef(result); + +exit: + comRef_scopePop(); + return result; +} + +void d12_updatePointer(CapturePointer * pointer, void * shape, size_t shapeSize) +{ + if (pointer->shapeUpdate) + { + void * dst; + UINT dstSize; + if (!this->getPointerBufferFn(&dst, &dstSize)) + { + DEBUG_ERROR("Failed to obtain a buffer for the pointer shape"); + pointer->shapeUpdate = false; + } + + size_t copySize = min(dstSize, shapeSize); + memcpy(dst, shape, copySize); + } + + this->postPointerBufferFn(pointer); +} + +struct CaptureInterface Capture_D12 = +{ + .shortName = "D12", + .asyncCapture = false, + .getName = d12_getName, + .initOptions = d12_initOptions, + .create = d12_create, + .init = d12_init, + .stop = d12_stop, + .deinit = d12_deinit, + .free = d12_free, + .capture = d12_capture, + .waitFrame = d12_waitFrame, + .getFrame = d12_getFrame +}; diff --git a/host/platform/Windows/capture/DXGI/CMakeLists.txt b/host/platform/Windows/capture/DXGI/CMakeLists.txt index d0f9e558..b9f048ab 100644 --- a/host/platform/Windows/capture/DXGI/CMakeLists.txt +++ b/host/platform/Windows/capture/DXGI/CMakeLists.txt @@ -7,15 +7,12 @@ add_library(capture_DXGI STATIC src/d3d12.c src/ods_capture.c src/util.c - src/com_ref.c src/pp/downsample.c src/pp/sdrwhitelevel.c src/pp/rgb24.c ) -add_definitions("-DCOBJMACROS -DINITGUID -DWIDL_C_INLINE_WRAPPERS") - target_link_libraries(capture_DXGI lg_common d3d11 diff --git a/host/platform/Windows/capture/DXGI/src/com_ref.c b/host/platform/Windows/capture/DXGI/src/com_ref.c deleted file mode 100644 index 3aa1c010..00000000 --- a/host/platform/Windows/capture/DXGI/src/com_ref.c +++ /dev/null @@ -1,153 +0,0 @@ -/** - * Looking Glass - * Copyright © 2017-2023 The Looking Glass Authors - * https://looking-glass.io - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation; either version 2 of the License, or (at your option) - * any later version. - * - * This program 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 General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., 59 - * Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#define COMREF_INTERNAL -#include "com_ref.h" - -#include "common/debug.h" -#include "common/vector.h" - -typedef struct -{ - int scope; - IUnknown * value; - IUnknown *** ref; -} -COMRef; - -static bool comInit = false; -static int comScope = -1; -static Vector comObjectsLocal = {0}; -static Vector comObjectsGlobal = {0}; - -bool comRef_init(unsigned globals, unsigned locals) -{ - if (comInit) - return true; - - if (!vector_create(&comObjectsGlobal, sizeof(COMRef), globals)) - return false; - - if (!vector_create(&comObjectsLocal, sizeof(COMRef), locals)) - { - vector_destroy(&comObjectsGlobal); - return false; - } - - comInit = true; - return true; -} - -void comRef_free(void) -{ - if (!comInit) - return; - - COMRef * ref; - - if (comScope > -1) - { - DEBUG_WARN("There is %d unmatched `comRef_scopePush` calls", comScope+1); - vector_forEachRef(ref, &comObjectsLocal) - if (ref->value) - IUnknown_Release(ref->value); - } - - vector_forEachRef(ref, &comObjectsGlobal) - { - if (ref->ref) - *ref->ref = NULL; - - if (ref->value) - IUnknown_Release(ref->value); - } - - comScope = -1; - vector_destroy(&comObjectsLocal); - vector_destroy(&comObjectsGlobal); - comInit = false; -} - -static IUnknown ** comRef_new(Vector * vector, IUnknown *** dst) -{ - DEBUG_ASSERT(comInit && "comRef has not been initialized"); - - // we must not allow the vector to grow as if the realloc moves to a new - // address it will invalidate any external pointers to members in it - DEBUG_ASSERT(vector_size(vector) < vector_capacity(vector) && - "comRef vector too small!"); - - COMRef * ref = (COMRef *)vector_push(vector, NULL); - if (!ref) - { - DEBUG_ERROR("Failed to allocate ram for com object"); - return NULL; - } - - ref->scope = comScope; - ref->ref = dst; - ref->value = NULL; - - if (dst) - *dst = &ref->value; - - return &ref->value; -} - -IUnknown ** comRef_newGlobal(IUnknown *** dst) -{ - return comRef_new(&comObjectsGlobal, dst); -} - -IUnknown ** comRef_newLocal(IUnknown *** dst) -{ - IUnknown ** ret = comRef_new(&comObjectsLocal, NULL); - *dst = ret; - return ret; -} - -void comRef_scopePush(void) -{ - DEBUG_ASSERT(comInit && "comRef has not been initialized"); - ++comScope; -} - -void comRef_scopePop(void) -{ - DEBUG_ASSERT(comInit && "comRef has not been initialized"); - DEBUG_ASSERT(comScope >= 0); - - COMRef * ref; - while(vector_size(&comObjectsLocal) > 0) - { - ref = (COMRef *)vector_ptrTo(&comObjectsLocal, - vector_size(&comObjectsLocal) - 1); - - if (ref->scope < comScope) - break; - - if (ref->value) - IUnknown_Release(ref->value); - - vector_pop(&comObjectsLocal); - } - - --comScope; -} diff --git a/host/platform/Windows/capture/DXGI/src/com_ref.h b/host/platform/Windows/capture/DXGI/src/com_ref.h deleted file mode 100644 index 99ff7fb1..00000000 --- a/host/platform/Windows/capture/DXGI/src/com_ref.h +++ /dev/null @@ -1,99 +0,0 @@ -/** - * Looking Glass - * Copyright © 2017-2023 The Looking Glass Authors - * https://looking-glass.io - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation; either version 2 of the License, or (at your option) - * any later version. - * - * This program 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 General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., 59 - * Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include -#include - -/** - * These functions are to assist in tracking and relasing COM objects - */ - -/** - * Initialize the com object tracking - */ -bool comRef_init(unsigned globals, unsigned locals); - -/** - * Release globals and deinitialize the com object tracking - */ -void comRef_free(void); - -/** - * Create a new global COM reference - */ -IUnknown ** comRef_newGlobal(IUnknown *** dst); - -/** - * Create a new locally scoped COM reference - */ -IUnknown ** comRef_newLocal(IUnknown *** dst); - -/** - * Define and create a new locally scoped COM reference - */ -#define comRef_defineLocal(type, name) \ - type ** name; \ - comRef_newLocal(&name); - -/** - * Release a COM reference immediately - * This is just a helper, the ref is still tracked if used again - */ -inline static ULONG comRef_release(IUnknown ** ref) -{ - if (!ref) - return 0; - - ULONG count = 0; - if (*ref) - count = IUnknown_Release(*ref); - *ref = NULL; - return count; -} - -/** - * Create a new local scope - */ -void comRef_scopePush(void); - -/** - * Exit from a local scope and release all locals - */ -void comRef_scopePop (void); - -/** - * Macros to prevent needing to typecast calls to these methods - */ -#ifndef COMREF_INTERNAL - #define comRef_newGlobal(dst) comRef_newGlobal((IUnknown ***)(dst)) - #define comRef_newLocal(dst) comRef_newLocal((IUnknown ***)(dst)) - #define comRef_release(ref) comRef_release((IUnknown **)(ref)) -#endif - -/** - * Convert a local to a global - */ -#define comRef_toGlobal(dst, src) \ -{ \ - IUnknown ** global = comRef_newGlobal(&(dst)); \ - DEBUG_ASSERT(global && "comRef_newGlobal failed\n"); \ - *global = (IUnknown*)*(src); \ - *(src) = NULL; \ -} diff --git a/host/platform/Windows/capture/DXGI/src/d3d11.c b/host/platform/Windows/capture/DXGI/src/d3d11.c index 86b9d722..467626f3 100644 --- a/host/platform/Windows/capture/DXGI/src/d3d11.c +++ b/host/platform/Windows/capture/DXGI/src/d3d11.c @@ -29,6 +29,8 @@ struct D3D11Backend { + ComScope * comScope; + RunningAvg avgMapTime; uint64_t usleepMapTime; @@ -43,6 +45,9 @@ struct D3D11Backend static struct D3D11Backend * this = NULL; +#define comRef_toGlobal(dst, src) \ + _comRef_toGlobal(this->comScope, dst, src) + static bool d3d11_create( void * ivshmemBase, unsigned * alignSize, @@ -62,6 +67,8 @@ static bool d3d11_create( this->avgMapTime = runningavg_new(10); this->textures = textures; + + comRef_initGlobalScope(10, this->comScope); return true; } @@ -72,6 +79,7 @@ static bool d3d11_configure( unsigned bpp, unsigned * pitch) { + comRef_scopePush(10); HRESULT status; D3D11_TEXTURE2D_DESC texTexDesc = @@ -116,9 +124,11 @@ static bool d3d11_configure( *(ID3D11Resource **)this->texture[0].tex, 0); *pitch = mapping.RowPitch; + comRef_scopePop(); return true; fail: + comRef_scopePop(); return false; } @@ -128,6 +138,7 @@ static void d3d11_free(void) return; runningavg_free(&this->avgMapTime); + comRef_freeScope(&this->comScope); free(this); this = NULL; } diff --git a/host/platform/Windows/capture/DXGI/src/d3d12.c b/host/platform/Windows/capture/DXGI/src/d3d12.c index 1019fb86..3e1479fa 100644 --- a/host/platform/Windows/capture/DXGI/src/d3d12.c +++ b/host/platform/Windows/capture/DXGI/src/d3d12.c @@ -51,6 +51,8 @@ struct SharedCache struct D3D12Backend { + ComScope * comScope; + HMODULE d3d12; unsigned width, height, pitch; DXGI_FORMAT format; @@ -78,6 +80,9 @@ struct D3D12Backend static struct D3D12Backend * this = NULL; +#define comRef_toGlobal(dst, src) \ + _comRef_toGlobal(this->comScope, dst, src) + typedef HRESULT (*D3D12CreateDevice_t)( IUnknown *pAdapter, D3D_FEATURE_LEVEL MinimumFeatureLevel, @@ -99,7 +104,6 @@ static bool d3d12_create( unsigned textures) { DEBUG_ASSERT(!this); - comRef_scopePush(); bool result = false; HRESULT status; @@ -113,6 +117,9 @@ static bool d3d12_create( goto exit; } + comRef_initGlobalScope(10, this->comScope); + comRef_scopePush(10); + this->d3d12 = LoadLibrary("d3d12.dll"); if (!this->d3d12) goto exit; @@ -252,7 +259,7 @@ static bool d3d12_configure( { bool result = false; HRESULT status; - comRef_scopePush(); + comRef_scopePush(10); this->width = width; this->height = height; @@ -350,6 +357,7 @@ static void d3d12_free(void) if (this->d3d12) FreeLibrary(this->d3d12); + comRef_freeScope(&this->comScope); free(this); this = NULL; } @@ -360,7 +368,7 @@ static bool d3d12_preCopy( unsigned frameBufferIndex, FrameBuffer * frameBuffer) { - comRef_scopePush(); + comRef_scopePush(10); bool result = false; // we need to flush the DX11 context explicity or we get tons of lag @@ -478,9 +486,7 @@ done: nsleep((uint64_t)(this->copySleep * 1000000)); exit: - if (!result) - comRef_scopePop(); - + comRef_scopePop(); return result; } @@ -616,8 +622,6 @@ static bool d3d12_postCopy(ID3D11Texture2D * src, unsigned textureIndex) result = true; exit: - //push is in preCopy - comRef_scopePop(); return result; } diff --git a/host/platform/Windows/capture/DXGI/src/dxgi.c b/host/platform/Windows/capture/DXGI/src/dxgi.c index 29ac0b11..09bc7452 100644 --- a/host/platform/Windows/capture/DXGI/src/dxgi.c +++ b/host/platform/Windows/capture/DXGI/src/dxgi.c @@ -106,6 +106,8 @@ FrameDamage; struct DXGIInterface { + ComScope * comScope; + bool initialized; LARGE_INTEGER perfFreq; LARGE_INTEGER frameTime; @@ -125,7 +127,6 @@ struct DXGIInterface D3D_FEATURE_LEVEL featureLevel; IDXGIOutputDuplication ** dup; int maxTextures; - void * ivshmemBase; Texture * texture; int texRIndex; int texWIndex; @@ -140,6 +141,7 @@ struct DXGIInterface CaptureGetPointerBuffer getPointerBufferFn; CapturePostPointerBuffer postPointerBufferFn; + unsigned frameBuffers; LGEvent * frameEvent; unsigned int formatVer; @@ -160,6 +162,7 @@ struct DXGIInterface }; // locals + static struct DXGIInterface * this = NULL; extern struct DXGICopyBackend copyBackendD3D11; @@ -169,6 +172,11 @@ static struct DXGICopyBackend * backends[] = { ©BackendD3D11, }; +// defines + +#define comRef_toGlobal(dst, src) \ + _comRef_toGlobal(this->comScope, dst, src) + // forwards static bool dxgi_deinit(void); @@ -277,9 +285,9 @@ static void dxgi_initOptions(void) } static bool dxgi_create( - void * ivshmemBase, CaptureGetPointerBuffer getPointerBufferFn, - CapturePostPointerBuffer postPointerBufferFn) + CapturePostPointerBuffer postPointerBufferFn, + unsigned frameBuffers) { DEBUG_ASSERT(!this); this = calloc(1, sizeof(*this)); @@ -306,16 +314,18 @@ static bool dxgi_create( this->allowRGB24 = option_get_bool("dxgi", "allowRGB24"); this->dwmFlush = option_get_bool("dxgi", "dwmFlush"); this->disableDamage = option_get_bool("dxgi", "disableDamage"); - this->ivshmemBase = ivshmemBase; this->texture = calloc(this->maxTextures, sizeof(*this->texture)); this->getPointerBufferFn = getPointerBufferFn; this->postPointerBufferFn = postPointerBufferFn; + this->frameBuffers = frameBuffers; return true; } static bool initVertexShader(void) { + comRef_scopePush(10); + static const char * vshaderSrc = "void main(\n" " in uint vertexID : SV_VERTEXID,\n" @@ -362,23 +372,16 @@ static bool initVertexShader(void) } comRef_toGlobal(this->vshader, vshader); + comRef_scopePop(); return true; } -static bool dxgi_init(unsigned * alignSize) +static bool dxgi_init(void * ivshmemBase, unsigned * alignSize) { DEBUG_ASSERT(this); - if (!comRef_init( - 20 + this->maxTextures * 16, //max total globals - 20 //max total locals - )) - { - DEBUG_ERROR("failed to intialize the comRef tracking"); - return false; - } - - comRef_scopePush(); + comRef_initGlobalScope(20 + this->maxTextures * 16, this->comScope); + comRef_scopePush(20); this->desktop = OpenInputDesktop(0, FALSE, GENERIC_READ); if (!this->desktop) @@ -411,9 +414,10 @@ static bool dxgi_init(unsigned * alignSize) lgResetEvent(this->frameEvent); + comRef_defineLocal(IDXGIFactory1, factory); status = CreateDXGIFactory2(this->debug ? DXGI_CREATE_FACTORY_DEBUG : 0, &IID_IDXGIFactory1, - (void **)comRef_newGlobal(&this->factory)); + (void **)factory); if (FAILED(status)) { @@ -429,7 +433,7 @@ static bool dxgi_init(unsigned * alignSize) for ( int i = 0; - IDXGIFactory1_EnumAdapters1(*this->factory, i, adapter) + IDXGIFactory1_EnumAdapters1(*factory, i, adapter) != DXGI_ERROR_NOT_FOUND; ++i, comRef_release(adapter)) { @@ -509,6 +513,7 @@ static bool dxgi_init(unsigned * alignSize) goto fail; } + comRef_toGlobal(this->factory, factory); comRef_toGlobal(this->adapter, adapter); comRef_toGlobal(this->output , output ); @@ -569,6 +574,8 @@ static bool dxgi_init(unsigned * alignSize) goto fail; } + comRef_defineLocal(ID3D11Device , device ); + comRef_defineLocal(ID3D11DeviceContext, deviceContext); status = D3D11CreateDevice( *tmp, D3D_DRIVER_TYPE_UNKNOWN, @@ -577,11 +584,9 @@ static bool dxgi_init(unsigned * alignSize) (this->debug ? D3D11_CREATE_DEVICE_DEBUG : 0), featureLevels, featureLevelCount, D3D11_SDK_VERSION, - (ID3D11Device **)comRef_newGlobal(&this->device), + device, &this->featureLevel, - (ID3D11DeviceContext **)comRef_newGlobal(&this->deviceContext)); - - LG_LOCK_INIT(this->deviceContextLock); + deviceContext); if (FAILED(status)) { @@ -589,6 +594,10 @@ static bool dxgi_init(unsigned * alignSize) goto fail; } + comRef_toGlobal(this->device , device ); + comRef_toGlobal(this->deviceContext, deviceContext); + LG_LOCK_INIT(this->deviceContextLock); + switch(outputDesc.Rotation) { case DXGI_MODE_ROTATION_ROTATE90: @@ -648,6 +657,8 @@ static bool dxgi_init(unsigned * alignSize) IDXGIDevice1_SetMaximumFrameLatency(*dxgi, 1); } + comRef_defineLocal(IDXGIOutputDuplication, dup); + comRef_defineLocal(IDXGIOutput5, output5); status = IDXGIOutput_QueryInterface( *this->output, &IID_IDXGIOutput5, (void **)output5); @@ -671,8 +682,7 @@ static bool dxgi_init(unsigned * alignSize) for (int i = 0; i < 2; ++i) { status = IDXGIOutput1_DuplicateOutput( - *output1, (IUnknown *)*this->device, - (IDXGIOutputDuplication **)comRef_newGlobal(&this->dup)); + *output1, *(IUnknown **)this->device, dup); if (SUCCEEDED(status)) break; @@ -703,7 +713,7 @@ static bool dxgi_init(unsigned * alignSize) 0, ARRAY_LENGTH(supportedFormats), supportedFormats, - (IDXGIOutputDuplication **)comRef_newGlobal(&this->dup)); + dup); if (SUCCEEDED(status)) break; @@ -721,6 +731,8 @@ static bool dxgi_init(unsigned * alignSize) goto fail; } + comRef_toGlobal(this->dup, dup); + comRef_defineLocal(IDXGIOutput6, output6); status = IDXGIOutput_QueryInterface( *this->output, &IID_IDXGIOutput6, (void **)output6); @@ -806,9 +818,9 @@ static bool dxgi_init(unsigned * alignSize) if (!strcasecmp(copyBackend, backends[i]->code)) { if (!backends[i]->create( - this->ivshmemBase, + ivshmemBase, alignSize, - LGMP_Q_FRAME_LEN, + this->frameBuffers, this->maxTextures)) { DEBUG_ERROR("Failed to initialize selected capture backend: %s", backends[i]->name); @@ -914,7 +926,7 @@ static bool dxgi_deinit(void) dxgi_releaseFrame(); // this MUST run before backend->free() & ppFreeAll. - comRef_free(); + comRef_freeScope(&this->comScope); ppFreeAll(); if (this->backend) @@ -1066,7 +1078,7 @@ static CaptureResult dxgi_capture(unsigned frameBufferIndex, { DEBUG_ASSERT(this); DEBUG_ASSERT(this->initialized); - comRef_scopePush(); + comRef_scopePush(10); Texture * tex = NULL; CaptureResult result; @@ -1386,7 +1398,7 @@ static CaptureResult dxgi_capture(unsigned frameBufferIndex, if (postPointer) { pointer.visible = this->lastPointerVisible; - this->postPointerBufferFn(pointer); + this->postPointerBufferFn(&pointer); } result = CAPTURE_RESULT_OK; @@ -1395,7 +1407,8 @@ exit: return result; } -static CaptureResult dxgi_waitFrame(CaptureFrame * frame, const size_t maxFrameSize) +static CaptureResult dxgi_waitFrame(unsigned frameBufferIndex, + CaptureFrame * frame, const size_t maxFrameSize) { DEBUG_ASSERT(this); DEBUG_ASSERT(this->initialized); @@ -1444,17 +1457,18 @@ static CaptureResult dxgi_waitFrame(CaptureFrame * frame, const size_t maxFrameS return CAPTURE_RESULT_OK; } -static CaptureResult dxgi_getFrame(FrameBuffer * frame, int frameIndex) +static CaptureResult dxgi_getFrame(unsigned frameBufferIndex, + FrameBuffer * frame, const size_t maxFrameSize) { DEBUG_ASSERT(this); DEBUG_ASSERT(this->initialized); Texture * tex = &this->texture[this->texRIndex]; - FrameDamage * damage = &this->frameDamage[frameIndex]; + FrameDamage * damage = &this->frameDamage[frameBufferIndex]; if (this->backend->writeFrame) { - CaptureResult result = this->backend->writeFrame(frameIndex, frame); + CaptureResult result = this->backend->writeFrame(frameBufferIndex, frame); if (result != CAPTURE_RESULT_OK) return result; } @@ -1500,7 +1514,7 @@ static CaptureResult dxgi_getFrame(FrameBuffer * frame, int frameIndex) for (int i = 0; i < LGMP_Q_FRAME_LEN; ++i) { struct FrameDamage * damage = this->frameDamage + i; - if (i == frameIndex) + if (i == frameBufferIndex) damage->count = 0; else if (tex->damageRectsCount > 0 && damage->count >= 0 && damage->count + tex->damageRectsCount <= KVMFR_MAX_DAMAGE_RECTS) diff --git a/host/platform/Windows/capture/DXGI/src/pp/downsample.c b/host/platform/Windows/capture/DXGI/src/pp/downsample.c index 739954c7..2403c036 100644 --- a/host/platform/Windows/capture/DXGI/src/pp/downsample.c +++ b/host/platform/Windows/capture/DXGI/src/pp/downsample.c @@ -31,6 +31,8 @@ typedef struct Downsample { + ComScope * comScope; + ID3D11Device ** device; ID3D11DeviceContext ** context; bool shareable; @@ -43,6 +45,9 @@ typedef struct Downsample Downsample; static Downsample this = {0}; +#define comRef_toGlobal(dst, src) \ + _comRef_toGlobal(this.comScope, dst, src) + typedef struct { ID3D11Texture2D ** tex; @@ -73,11 +78,14 @@ static bool downsample_setup( this.device = device; this.context = context; this.shareable = shareable; + + comRef_initGlobalScope(10, this.comScope); return true; } static void downsample_finish(void) { + comRef_freeScope(&this.comScope); memset(&this, 0, sizeof(this)); } @@ -96,7 +104,7 @@ static bool downsample_configure(void * opaque, return true; HRESULT status; - comRef_scopePush(); + comRef_scopePush(10); if (!this.pshader) { diff --git a/host/platform/Windows/capture/DXGI/src/pp/rgb24.c b/host/platform/Windows/capture/DXGI/src/pp/rgb24.c index 700b767d..13abc541 100644 --- a/host/platform/Windows/capture/DXGI/src/pp/rgb24.c +++ b/host/platform/Windows/capture/DXGI/src/pp/rgb24.c @@ -29,6 +29,8 @@ typedef struct RGB24 { + ComScope * comScope; + ID3D11Device ** device; ID3D11DeviceContext ** context; bool shareable; @@ -40,6 +42,9 @@ typedef struct RGB24 RGB24; static RGB24 this = {0}; +#define comRef_toGlobal(dst, src) \ + _comRef_toGlobal(this.comScope, dst, src) + typedef struct { ID3D11Texture2D ** tex; @@ -55,6 +60,7 @@ static bool rgb24_setup( bool shareable ) { + comRef_initGlobalScope(10, this.comScope); this.device = device; this.context = context; this.shareable = shareable; @@ -63,6 +69,7 @@ static bool rgb24_setup( static void rgb24_finish(void) { + comRef_freeScope(&this.comScope); memset(&this, 0, sizeof(this)); } @@ -74,7 +81,8 @@ static bool rgb24_configure(void * opaque, RGB24Inst * inst = (RGB24Inst *)opaque; HRESULT status; - comRef_scopePush(); + + comRef_scopePush(10); if (!this.pshader) { diff --git a/host/platform/Windows/capture/DXGI/src/pp/sdrwhitelevel.c b/host/platform/Windows/capture/DXGI/src/pp/sdrwhitelevel.c index 5bb3c2fd..b959f628 100644 --- a/host/platform/Windows/capture/DXGI/src/pp/sdrwhitelevel.c +++ b/host/platform/Windows/capture/DXGI/src/pp/sdrwhitelevel.c @@ -29,6 +29,8 @@ typedef struct SDRWhiteLevel { + ComScope * comScope; + ID3D11Device ** device; ID3D11DeviceContext ** context; @@ -43,6 +45,9 @@ typedef struct SDRWhiteLevel SDRWhiteLevel; static SDRWhiteLevel this = {0}; +#define comRef_toGlobal(dst, src) \ + _comRef_toGlobal(this.comScope, dst, src) + typedef struct { ID3D11Texture2D ** tex; @@ -66,13 +71,15 @@ static bool sdrWhiteLevel_setup( ) { bool result = false; - comRef_scopePush(); HRESULT status; this.device = device; this.context = context; this.shareable = shareable; + comRef_initGlobalScope(10, this.comScope); + comRef_scopePush(10); + comRef_defineLocal(IDXGIOutput6, output6); status = IDXGIOutput_QueryInterface( *output, &IID_IDXGIOutput6, (void **)output6); @@ -139,9 +146,8 @@ static bool sdrWhiteLevel_setup( .MaxLOD = D3D11_FLOAT32_MAX }; - status = ID3D11Device_CreateSamplerState( - *this.device, &samplerDesc, - (ID3D11SamplerState **)comRef_newGlobal(&this.sampler)); + comRef_defineLocal(ID3D11SamplerState, sampler); + status = ID3D11Device_CreateSamplerState(*this.device, &samplerDesc, sampler); if (FAILED(status)) { @@ -156,9 +162,10 @@ static bool sdrWhiteLevel_setup( .BindFlags = D3D11_BIND_CONSTANT_BUFFER, }; + comRef_defineLocal(ID3D11Buffer, buffer); status = ID3D11Device_CreateBuffer( *this.device, &bufferDesc, NULL, - (ID3D11Buffer **)comRef_newGlobal(&this.buffer)); + buffer); if (FAILED(status)) { @@ -169,6 +176,8 @@ static bool sdrWhiteLevel_setup( updateConsts(); DEBUG_INFO("SDR White Level : %f" , this.sdrWhiteLevel); + comRef_toGlobal(this.sampler, sampler); + comRef_toGlobal(this.buffer , buffer ); result = true; exit: @@ -178,6 +187,7 @@ exit: static void sdrWhiteLevel_finish(void) { + comRef_freeScope(&this.comScope); memset(&this, 0, sizeof(this)); } @@ -225,7 +235,7 @@ static bool sdrWhiteLevel_configure(void * opaque, if (inst->tex) return true; - comRef_scopePush(); + comRef_scopePush(10); // create the output texture D3D11_TEXTURE2D_DESC texDesc = diff --git a/host/platform/Windows/capture/NVFBC/src/nvfbc.c b/host/platform/Windows/capture/NVFBC/src/nvfbc.c index d94be0bf..ba04b06a 100644 --- a/host/platform/Windows/capture/NVFBC/src/nvfbc.c +++ b/host/platform/Windows/capture/NVFBC/src/nvfbc.c @@ -128,7 +128,7 @@ static void on_mouseMove(int x, int y) .y = y - this->mouseHotY }; - this->postPointerBufferFn(pointer); + this->postPointerBufferFn(&pointer); } static const char * nvfbc_getName(void) @@ -185,9 +185,9 @@ static void nvfbc_initOptions(void) } static bool nvfbc_create( - void * ivshmemBase, CaptureGetPointerBuffer getPointerBufferFn, - CapturePostPointerBuffer postPointerBufferFn) + CapturePostPointerBuffer postPointerBufferFn, + unsigned frameBuffers) { if (!NvFBCInit()) return false; @@ -221,7 +221,7 @@ static void updateScale(void) this->targetHeight = this->height; } -static bool nvfbc_init(unsigned * alignSize) +static bool nvfbc_init(void * ivshmemBase, unsigned * alignSize) { int adapterIndex = option_get_int("nvfbc", "adapterIndex"); @@ -650,8 +650,8 @@ done: frame->damageRectsCount = rectId; } -static CaptureResult nvfbc_waitFrame(CaptureFrame * frame, - const size_t maxFrameSize) +static CaptureResult nvfbc_waitFrame(unsigned frameBufferIndex, + CaptureFrame * frame, const size_t maxFrameSize) { if (unlikely(this->stop)) return CAPTURE_RESULT_REINIT; @@ -711,12 +711,13 @@ static CaptureResult nvfbc_waitFrame(CaptureFrame * frame, return CAPTURE_RESULT_OK; } -static CaptureResult nvfbc_getFrame(FrameBuffer * frame, int frameIndex) +static CaptureResult nvfbc_getFrame(unsigned frameBufferIndex, + FrameBuffer * frame, const size_t maxFrameSize) { const unsigned int h = DIFF_MAP_DIM(this->grabHeight, this->diffShift); const unsigned int w = DIFF_MAP_DIM(this->grabWidth, this->diffShift); uint8_t * frameData = framebuffer_get_data(frame); - struct FrameInfo * info = this->frameInfo + frameIndex; + struct FrameInfo * info = this->frameInfo + frameBufferIndex; if (info->width == this->grabWidth && info->height == this->grabHeight) { @@ -791,7 +792,7 @@ static CaptureResult nvfbc_getFrame(FrameBuffer * frame, int frameIndex) for (int i = 0; i < LGMP_Q_FRAME_LEN; ++i) { - if (i == frameIndex) + if (i == frameBufferIndex) { this->frameInfo[i].width = this->grabWidth; this->frameInfo[i].height = this->grabHeight; @@ -855,7 +856,7 @@ static int pointerThread(void * unused) pointer.x = this->mouseX - pointer.hx; pointer.y = this->mouseY - pointer.hy; - this->postPointerBufferFn(pointer); + this->postPointerBufferFn(&pointer); } return 0; diff --git a/host/platform/Windows/include/com_ref.h b/host/platform/Windows/include/com_ref.h new file mode 100644 index 00000000..8b92ab26 --- /dev/null +++ b/host/platform/Windows/include/com_ref.h @@ -0,0 +1,98 @@ +/** + * Looking Glass + * Copyright © 2017-2023 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program 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 General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include + +#include "common/locking.h" + +/** + * These functions are to assist in tracking and releasing COM objects + */ + +typedef struct ComScope ComScope; + +struct ComScope +{ + bool threadSafe; + LG_Lock lock; + + unsigned size; + unsigned used; + struct + { + IUnknown *** ptr; + IUnknown * ref; + } + * refs; + + void (*free)(void * ptr); +}; + +void comRef_initScope(unsigned size, ComScope ** instance, + void *(allocFn)(size_t size), void (freeFn)(void * ptr), bool threadSafe); + +void comRef_freeScope(ComScope ** instance); + +IUnknown ** comRef_new(ComScope * scope, IUnknown *** dst); + +#define comRef_initGlobalScope(size, scope) \ + comRef_initScope((size), &(scope), malloc, free, true) + +#define comRef_freeGlobalScope(scope) \ + comRef_freeScope(&(scope)) + +#define comRef_scopePush(size) \ + ComScope * _comRef_localScope = alloca(sizeof(*_comRef_localScope) + \ + sizeof(*(_comRef_localScope->refs)) * size); \ + comRef_initScope(size, &_comRef_localScope, NULL, NULL, false); + +#define comRef_scopePop() \ + comRef_freeScope(&_comRef_localScope) + +#define comRef_defineLocal(type, name) \ + type ** name = NULL; \ + comRef_new(_comRef_localScope, (IUnknown ***)&(name)); + +#define _comRef_toGlobal(globalScope, dst, src) \ +{ \ + IUnknown ** global = comRef_new((globalScope), (IUnknown ***)&(dst)); \ + *global = (IUnknown *)*(src); \ + *(src) = NULL; \ +} + +/** + * Release a COM reference immediately + * This is just a helper, the ref is still tracked if used again + */ +inline static ULONG comRef_release(IUnknown ** ref) +{ + if (!ref) + return 0; + + ULONG count = 0; + if (*ref) + count = IUnknown_Release(*ref); + *ref = NULL; + return count; +} + +#define comRef_release(ref) comRef_release((IUnknown **)(ref)) diff --git a/host/platform/Windows/src/com_ref.c b/host/platform/Windows/src/com_ref.c new file mode 100644 index 00000000..ab4e8663 --- /dev/null +++ b/host/platform/Windows/src/com_ref.c @@ -0,0 +1,108 @@ +/** + * Looking Glass + * Copyright © 2017-2023 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program 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 General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "com_ref.h" + +#include "common/debug.h" + +void comRef_initScope(unsigned size, ComScope ** instance, + void *(allocFn)(size_t size), void (freeFn)(void * ptr), bool threadSafe) +{ + ComScope * scope = *instance; + + const size_t ttlSize = sizeof(*scope) + sizeof(*(scope->refs)) * size; + if (allocFn) + scope = allocFn(ttlSize); + DEBUG_ASSERT(scope && "No memory for scope"); + + memset(scope, 0, ttlSize); + scope->threadSafe = threadSafe; + if (threadSafe) + LG_LOCK_INIT(scope->lock); + + scope->size = size; + scope->refs = (typeof(scope->refs))(scope+1); + scope->free = freeFn; + + *instance = scope; +} + +void comRef_freeScope(ComScope ** instance) +{ + if (!*instance) + return; + + ComScope * scope = *instance; + for(unsigned i = 0; i < scope->used; ++i) + { + typeof(scope->refs) ref = &scope->refs[i]; + if (ref->ref) + { + IUnknown_Release(ref->ref); + ref->ref = NULL; + } + + *ref->ptr = NULL; + ref->ptr = NULL; + } + + if (scope->threadSafe) + LG_LOCK_FREE(scope->lock); + + if (scope->free) + scope->free(scope); + + *instance = NULL; +} + +IUnknown ** comRef_new(ComScope * scope, IUnknown *** dst) +{ + /* check if the value it points to is already in our memory range and if it + * does, then reuse it */ + if ((uintptr_t)*dst >= (uintptr_t)(scope->refs) && + (uintptr_t)*dst < (uintptr_t)(scope->refs + scope->used)) + { + // if it already holds a value, release it before we overwrite + if (**dst) + { + IUnknown_Release(**dst); + **dst = NULL; + } + + // return the existing member + return *dst; + } + + /* If you hit this, you need to enlarge the scope size, or check if you have a + * resource leak. */ + DEBUG_ASSERT(scope->used < scope->size && "ComRef Scope Full"); + + if (scope->threadSafe) + LG_LOCK(scope->lock); + + scope->refs[scope->used].ptr = dst; + *dst = &scope->refs[scope->used].ref; + ++scope->used; + + if (scope->threadSafe) + LG_UNLOCK(scope->lock); + + return *dst; +} diff --git a/host/src/app.c b/host/src/app.c index 6dc17afc..b82b31b0 100644 --- a/host/src/app.c +++ b/host/src/app.c @@ -78,6 +78,7 @@ struct app int exitcode; PLGMPHost lgmp; + void *ivshmemBase; PLGMPHostQueue pointerQueue; PLGMPMemory pointerMemory[LGMP_Q_POINTER_LEN]; @@ -96,7 +97,8 @@ struct app KVMFRFrame * frame [LGMP_Q_FRAME_LEN]; FrameBuffer * frameBuffer[LGMP_Q_FRAME_LEN]; - unsigned int frameIndex; + unsigned int captureIndex; + unsigned int readIndex; bool frameValid; uint32_t frameSerial; @@ -181,7 +183,7 @@ static bool lgmpTimer(void * opaque) return true; } -static bool sendFrame(void) +static bool sendFrame(CaptureResult result) { CaptureFrame frame = { 0 }; bool repeatFrame = false; @@ -197,7 +199,11 @@ static bool sendFrame(void) if (app.state != APP_STATE_RUNNING) return false; - switch(app.iface->waitFrame(&frame, app.maxFrameSize)) + // only wait if the result from the capture was OK + if (result == CAPTURE_RESULT_OK) + result = app.iface->waitFrame(app.captureIndex, &frame, app.maxFrameSize); + + switch(result) { case CAPTURE_RESULT_OK: // reading the new subs count zeros it @@ -236,17 +242,12 @@ static bool sendFrame(void) if (repeatFrame) { if ((status = lgmpHostQueuePost(app.frameQueue, 0, - app.frameMemory[app.frameIndex])) != LGMP_OK) + app.frameMemory[app.readIndex])) != LGMP_OK) DEBUG_ERROR("%s", lgmpStatusString(status)); return true; } - // we increment the index first so that if we need to repeat a frame - // the index still points to the latest valid frame - if (++app.frameIndex == LGMP_Q_FRAME_LEN) - app.frameIndex = 0; - - KVMFRFrame * fi = app.frame[app.frameIndex]; + KVMFRFrame * fi = app.frame[app.captureIndex]; KVMFRFrameFlags flags = (frame.hdr ? FRAME_FLAG_HDR : 0) | (frame.hdrPQ ? FRAME_FLAG_HDR_PQ : 0); @@ -322,17 +323,24 @@ static bool sendFrame(void) app.frameValid = true; - framebuffer_prepare(app.frameBuffer[app.frameIndex]); + framebuffer_prepare(app.frameBuffer[app.captureIndex]); /* we post and then get the frame, this is intentional! */ if ((status = lgmpHostQueuePost(app.frameQueue, 0, - app.frameMemory[app.frameIndex])) != LGMP_OK) + app.frameMemory[app.captureIndex])) != LGMP_OK) { DEBUG_ERROR("%s", lgmpStatusString(status)); return true; } - app.iface->getFrame(app.frameBuffer[app.frameIndex], app.frameIndex); + app.iface->getFrame( + app.captureIndex, + app.frameBuffer[app.captureIndex], + app.maxFrameSize); + + app.readIndex = app.captureIndex; + if (++app.captureIndex == LGMP_Q_FRAME_LEN) + app.captureIndex = 0; return true; } @@ -342,7 +350,7 @@ static int frameThread(void * opaque) while(app.state == APP_STATE_RUNNING) { - if (!sendFrame()) + if (!sendFrame(CAPTURE_RESULT_OK)) break; } DEBUG_INFO("Frame thread stopped"); @@ -392,7 +400,7 @@ static bool captureStart(void) { if (app.state == APP_STATE_IDLE) { - if (!app.iface->init(&app.alignSize)) + if (!app.iface->init(app.ivshmemBase, &app.alignSize)) { DEBUG_ERROR("Failed to initialize the capture device"); return false; @@ -419,6 +427,7 @@ static bool captureStop(void) return false; } + app.frameValid = false; return true; } @@ -527,17 +536,17 @@ static void sendPointer(bool newClient) postPointer(flags, mem); } -void capturePostPointerBuffer(CapturePointer pointer) +void capturePostPointerBuffer(const CapturePointer * pointer) { LG_LOCK(app.pointerLock); int x = app.pointerInfo.x; int y = app.pointerInfo.y; - memcpy(&app.pointerInfo, &pointer, sizeof(CapturePointer)); + memcpy(&app.pointerInfo, pointer, sizeof(CapturePointer)); /* if there was not a position update, restore the x & y */ - if (!pointer.positionUpdate) + if (!pointer->positionUpdate) { app.pointerInfo.x = x; app.pointerInfo.y = y; @@ -829,6 +838,7 @@ int app_main(int argc, char * argv[]) DEBUG_ERROR("Failed to open the IVSHMEM device"); return LG_HOST_EXIT_FATAL; } + app.ivshmemBase = shmDev.mem; int exitcode = 0; DEBUG_INFO("IVSHMEM Size : %u MiB", shmDev.size / 1048576); @@ -856,15 +866,15 @@ int app_main(int argc, char * argv[]) DEBUG_INFO("Trying : %s", iface->getName()); if (!iface->create( - shmDev.mem, captureGetPointerBuffer, - capturePostPointerBuffer)) + capturePostPointerBuffer, + ARRAY_LENGTH(app.frameBuffer))) { iface = NULL; continue; } - if (iface->init(&app.alignSize)) + if (iface->init(app.ivshmemBase, &app.alignSize)) break; iface->free(); @@ -965,12 +975,8 @@ int app_main(int argc, char * argv[]) const uint64_t captureStart = microtime(); - unsigned nextIndex = app.frameIndex + 1; - if (nextIndex == LGMP_Q_FRAME_LEN) - nextIndex = 0; - const CaptureResult result = app.iface->capture( - nextIndex, app.frameBuffer[nextIndex]); + app.captureIndex, app.frameBuffer[app.captureIndex]); if (likely(result == CAPTURE_RESULT_OK)) previousFrameTime = captureStart; @@ -982,7 +988,7 @@ int app_main(int argc, char * argv[]) { LGMP_STATUS status; if ((status = lgmpHostQueuePost(app.frameQueue, 0, - app.frameMemory[app.frameIndex])) != LGMP_OK) + app.frameMemory[app.readIndex])) != LGMP_OK) DEBUG_ERROR("%s", lgmpStatusString(status)); } } @@ -1005,7 +1011,7 @@ int app_main(int argc, char * argv[]) } if (!app.iface->asyncCapture) - sendFrame(); + sendFrame(result); } if (app.state != APP_STATE_SHUTDOWN)