mirror of
https://github.com/gnif/LookingGlass.git
synced 2024-11-12 20:27:53 +00:00
[host] add new D12 capture interface
Note, this capture interface is not yet feature complete but does seem to be stable.
This commit is contained in:
parent
e376e6fb53
commit
72b25b99bc
20 changed files with 1919 additions and 352 deletions
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
23
host/platform/Windows/capture/D12/CMakeLists.txt
Normal file
23
host/platform/Windows/capture/D12/CMakeLists.txt
Normal file
|
@ -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"
|
||||
)
|
59
host/platform/Windows/capture/D12/backend.h
Normal file
59
host/platform/Windows/capture/D12/backend.h
Normal file
|
@ -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 <stdbool.h>
|
||||
#include <d3d12.h>
|
||||
#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
|
712
host/platform/Windows/capture/D12/backend/dd.c
Normal file
712
host/platform/Windows/capture/D12/backend/dd.c
Normal file
|
@ -0,0 +1,712 @@
|
|||
#include "backend.h"
|
||||
|
||||
#include "com_ref.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/windebug.h"
|
||||
#include "common/array.h"
|
||||
|
||||
#include <d3d11.h>
|
||||
#include <d3d11_4.h>
|
||||
#include <d3d11on12.h>
|
||||
|
||||
#include <dxgi.h>
|
||||
#include <dxgi1_2.h>
|
||||
#include <dxgi1_3.h>
|
||||
#include <dxgi1_5.h>
|
||||
#include <dxgi1_6.h>
|
||||
|
||||
#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
|
||||
};
|
743
host/platform/Windows/capture/D12/d12.c
Normal file
743
host/platform/Windows/capture/D12/d12.c
Normal file
|
@ -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 <dxgi.h>
|
||||
#include <dxgi1_3.h>
|
||||
#include <d3dcommon.h>
|
||||
|
||||
// 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
|
||||
};
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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 <stdbool.h>
|
||||
#include <windows.h>
|
||||
|
||||
/**
|
||||
* 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; \
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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;
|
||||
|
|
98
host/platform/Windows/include/com_ref.h
Normal file
98
host/platform/Windows/include/com_ref.h
Normal file
|
@ -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 <stdbool.h>
|
||||
#include <windows.h>
|
||||
#include <malloc.h>
|
||||
|
||||
#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))
|
108
host/platform/Windows/src/com_ref.c
Normal file
108
host/platform/Windows/src/com_ref.c
Normal file
|
@ -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;
|
||||
}
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue