[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:
Geoffrey McRae 2024-01-29 21:47:02 +11:00
parent e376e6fb53
commit 72b25b99bc
20 changed files with 1919 additions and 352 deletions

View File

@ -106,7 +106,7 @@ typedef struct CapturePointer
CapturePointer; CapturePointer;
typedef bool (*CaptureGetPointerBuffer )(void ** data, uint32_t * size); typedef bool (*CaptureGetPointerBuffer )(void ** data, uint32_t * size);
typedef void (*CapturePostPointerBuffer)(CapturePointer pointer); typedef void (*CapturePostPointerBuffer)(const CapturePointer * pointer);
typedef struct CaptureInterface typedef struct CaptureInterface
{ {
@ -116,19 +116,27 @@ typedef struct CaptureInterface
void (*initOptions )(void); void (*initOptions )(void);
bool(*create)( bool(*create)(
void * ivshmemBase,
CaptureGetPointerBuffer getPointerBufferFn, CaptureGetPointerBuffer getPointerBufferFn,
CapturePostPointerBuffer postPointerBufferFn CapturePostPointerBuffer postPointerBufferFn,
unsigned frameBuffers
); );
bool (*init )(unsigned * alignSize); bool (*init )(void * ivshmemBase, unsigned * alignSize);
bool (*start )(void); bool (*start )(void);
void (*stop )(void); void (*stop )(void);
bool (*deinit )(void); bool (*deinit )(void);
void (*free )(void); void (*free )(void);
CaptureResult (*capture )(unsigned frameBufferIndex, FrameBuffer * frame); CaptureResult (*capture )(
CaptureResult (*waitFrame )(CaptureFrame * frame, const size_t maxFrameSize); unsigned frameBufferIndex,
CaptureResult (*getFrame )(FrameBuffer * frame, int frameIndex); FrameBuffer * frame);
CaptureResult (*waitFrame )(
unsigned frameBufferIndex,
CaptureFrame * frame,
const size_t maxFrameSize);
CaptureResult (*getFrame )(
unsigned frameBufferIndex,
FrameBuffer * frame,
const size_t maxFrameSize);
} }
CaptureInterface; CaptureInterface;

View File

@ -10,11 +10,14 @@ add_library(platform_Windows STATIC
src/service.c src/service.c
src/mousehook.c src/mousehook.c
src/force_compose.c src/force_compose.c
src/com_ref.c
) )
# allow use of functions for Windows 7 or later # allow use of functions for Windows 7 or later
add_compile_definitions(WINVER=0x0601 _WIN32_WINNT=0x0601) add_compile_definitions(WINVER=0x0601 _WIN32_WINNT=0x0601)
add_definitions("-DCOBJMACROS -DINITGUID -DWIDL_C_INLINE_WRAPPERS")
add_subdirectory("capture") add_subdirectory("capture")
target_link_libraries(platform_Windows target_link_libraries(platform_Windows

View File

@ -3,8 +3,9 @@ project(capture LANGUAGES C)
include(PreCapture) 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_DXGI "Enable DXGI Support" ON)
option(USE_NVFBC "Enable NVFBC Support" OFF)
if(NOT DEFINED NVFBC_SDK) if(NOT DEFINED NVFBC_SDK)
set(NVFBC_SDK "C:/Program Files (x86)/NVIDIA Corporation/NVIDIA Capture 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) set(USE_NVFBC OFF)
endif() endif()
if(USE_D12)
add_capture("D12")
endif()
if(USE_DXGI) if(USE_DXGI)
add_capture("DXGI") add_capture("DXGI")
endif() endif()
@ -25,7 +30,8 @@ if(USE_NVFBC)
add_capture("NVFBC") add_capture("NVFBC")
endif() 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.") add_feature_info(USE_NVFBC USE_NVFBC "NVFBC capture backend.")
include("PostCapture") include("PostCapture")

View 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"
)

View 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

View 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
};

View 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
};

View File

@ -7,15 +7,12 @@ add_library(capture_DXGI STATIC
src/d3d12.c src/d3d12.c
src/ods_capture.c src/ods_capture.c
src/util.c src/util.c
src/com_ref.c
src/pp/downsample.c src/pp/downsample.c
src/pp/sdrwhitelevel.c src/pp/sdrwhitelevel.c
src/pp/rgb24.c src/pp/rgb24.c
) )
add_definitions("-DCOBJMACROS -DINITGUID -DWIDL_C_INLINE_WRAPPERS")
target_link_libraries(capture_DXGI target_link_libraries(capture_DXGI
lg_common lg_common
d3d11 d3d11

View File

@ -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;
}

View File

@ -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; \
}

View File

@ -29,6 +29,8 @@
struct D3D11Backend struct D3D11Backend
{ {
ComScope * comScope;
RunningAvg avgMapTime; RunningAvg avgMapTime;
uint64_t usleepMapTime; uint64_t usleepMapTime;
@ -43,6 +45,9 @@ struct D3D11Backend
static struct D3D11Backend * this = NULL; static struct D3D11Backend * this = NULL;
#define comRef_toGlobal(dst, src) \
_comRef_toGlobal(this->comScope, dst, src)
static bool d3d11_create( static bool d3d11_create(
void * ivshmemBase, void * ivshmemBase,
unsigned * alignSize, unsigned * alignSize,
@ -62,6 +67,8 @@ static bool d3d11_create(
this->avgMapTime = runningavg_new(10); this->avgMapTime = runningavg_new(10);
this->textures = textures; this->textures = textures;
comRef_initGlobalScope(10, this->comScope);
return true; return true;
} }
@ -72,6 +79,7 @@ static bool d3d11_configure(
unsigned bpp, unsigned bpp,
unsigned * pitch) unsigned * pitch)
{ {
comRef_scopePush(10);
HRESULT status; HRESULT status;
D3D11_TEXTURE2D_DESC texTexDesc = D3D11_TEXTURE2D_DESC texTexDesc =
@ -116,9 +124,11 @@ static bool d3d11_configure(
*(ID3D11Resource **)this->texture[0].tex, 0); *(ID3D11Resource **)this->texture[0].tex, 0);
*pitch = mapping.RowPitch; *pitch = mapping.RowPitch;
comRef_scopePop();
return true; return true;
fail: fail:
comRef_scopePop();
return false; return false;
} }
@ -128,6 +138,7 @@ static void d3d11_free(void)
return; return;
runningavg_free(&this->avgMapTime); runningavg_free(&this->avgMapTime);
comRef_freeScope(&this->comScope);
free(this); free(this);
this = NULL; this = NULL;
} }

View File

@ -51,6 +51,8 @@ struct SharedCache
struct D3D12Backend struct D3D12Backend
{ {
ComScope * comScope;
HMODULE d3d12; HMODULE d3d12;
unsigned width, height, pitch; unsigned width, height, pitch;
DXGI_FORMAT format; DXGI_FORMAT format;
@ -78,6 +80,9 @@ struct D3D12Backend
static struct D3D12Backend * this = NULL; static struct D3D12Backend * this = NULL;
#define comRef_toGlobal(dst, src) \
_comRef_toGlobal(this->comScope, dst, src)
typedef HRESULT (*D3D12CreateDevice_t)( typedef HRESULT (*D3D12CreateDevice_t)(
IUnknown *pAdapter, IUnknown *pAdapter,
D3D_FEATURE_LEVEL MinimumFeatureLevel, D3D_FEATURE_LEVEL MinimumFeatureLevel,
@ -99,7 +104,6 @@ static bool d3d12_create(
unsigned textures) unsigned textures)
{ {
DEBUG_ASSERT(!this); DEBUG_ASSERT(!this);
comRef_scopePush();
bool result = false; bool result = false;
HRESULT status; HRESULT status;
@ -113,6 +117,9 @@ static bool d3d12_create(
goto exit; goto exit;
} }
comRef_initGlobalScope(10, this->comScope);
comRef_scopePush(10);
this->d3d12 = LoadLibrary("d3d12.dll"); this->d3d12 = LoadLibrary("d3d12.dll");
if (!this->d3d12) if (!this->d3d12)
goto exit; goto exit;
@ -252,7 +259,7 @@ static bool d3d12_configure(
{ {
bool result = false; bool result = false;
HRESULT status; HRESULT status;
comRef_scopePush(); comRef_scopePush(10);
this->width = width; this->width = width;
this->height = height; this->height = height;
@ -350,6 +357,7 @@ static void d3d12_free(void)
if (this->d3d12) if (this->d3d12)
FreeLibrary(this->d3d12); FreeLibrary(this->d3d12);
comRef_freeScope(&this->comScope);
free(this); free(this);
this = NULL; this = NULL;
} }
@ -360,7 +368,7 @@ static bool d3d12_preCopy(
unsigned frameBufferIndex, unsigned frameBufferIndex,
FrameBuffer * frameBuffer) FrameBuffer * frameBuffer)
{ {
comRef_scopePush(); comRef_scopePush(10);
bool result = false; bool result = false;
// we need to flush the DX11 context explicity or we get tons of lag // 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)); nsleep((uint64_t)(this->copySleep * 1000000));
exit: exit:
if (!result) comRef_scopePop();
comRef_scopePop();
return result; return result;
} }
@ -616,8 +622,6 @@ static bool d3d12_postCopy(ID3D11Texture2D * src, unsigned textureIndex)
result = true; result = true;
exit: exit:
//push is in preCopy
comRef_scopePop();
return result; return result;
} }

View File

@ -106,6 +106,8 @@ FrameDamage;
struct DXGIInterface struct DXGIInterface
{ {
ComScope * comScope;
bool initialized; bool initialized;
LARGE_INTEGER perfFreq; LARGE_INTEGER perfFreq;
LARGE_INTEGER frameTime; LARGE_INTEGER frameTime;
@ -125,7 +127,6 @@ struct DXGIInterface
D3D_FEATURE_LEVEL featureLevel; D3D_FEATURE_LEVEL featureLevel;
IDXGIOutputDuplication ** dup; IDXGIOutputDuplication ** dup;
int maxTextures; int maxTextures;
void * ivshmemBase;
Texture * texture; Texture * texture;
int texRIndex; int texRIndex;
int texWIndex; int texWIndex;
@ -140,6 +141,7 @@ struct DXGIInterface
CaptureGetPointerBuffer getPointerBufferFn; CaptureGetPointerBuffer getPointerBufferFn;
CapturePostPointerBuffer postPointerBufferFn; CapturePostPointerBuffer postPointerBufferFn;
unsigned frameBuffers;
LGEvent * frameEvent; LGEvent * frameEvent;
unsigned int formatVer; unsigned int formatVer;
@ -160,6 +162,7 @@ struct DXGIInterface
}; };
// locals // locals
static struct DXGIInterface * this = NULL; static struct DXGIInterface * this = NULL;
extern struct DXGICopyBackend copyBackendD3D11; extern struct DXGICopyBackend copyBackendD3D11;
@ -169,6 +172,11 @@ static struct DXGICopyBackend * backends[] = {
&copyBackendD3D11, &copyBackendD3D11,
}; };
// defines
#define comRef_toGlobal(dst, src) \
_comRef_toGlobal(this->comScope, dst, src)
// forwards // forwards
static bool dxgi_deinit(void); static bool dxgi_deinit(void);
@ -277,9 +285,9 @@ static void dxgi_initOptions(void)
} }
static bool dxgi_create( static bool dxgi_create(
void * ivshmemBase,
CaptureGetPointerBuffer getPointerBufferFn, CaptureGetPointerBuffer getPointerBufferFn,
CapturePostPointerBuffer postPointerBufferFn) CapturePostPointerBuffer postPointerBufferFn,
unsigned frameBuffers)
{ {
DEBUG_ASSERT(!this); DEBUG_ASSERT(!this);
this = calloc(1, sizeof(*this)); this = calloc(1, sizeof(*this));
@ -306,16 +314,18 @@ static bool dxgi_create(
this->allowRGB24 = option_get_bool("dxgi", "allowRGB24"); this->allowRGB24 = option_get_bool("dxgi", "allowRGB24");
this->dwmFlush = option_get_bool("dxgi", "dwmFlush"); this->dwmFlush = option_get_bool("dxgi", "dwmFlush");
this->disableDamage = option_get_bool("dxgi", "disableDamage"); this->disableDamage = option_get_bool("dxgi", "disableDamage");
this->ivshmemBase = ivshmemBase;
this->texture = calloc(this->maxTextures, sizeof(*this->texture)); this->texture = calloc(this->maxTextures, sizeof(*this->texture));
this->getPointerBufferFn = getPointerBufferFn; this->getPointerBufferFn = getPointerBufferFn;
this->postPointerBufferFn = postPointerBufferFn; this->postPointerBufferFn = postPointerBufferFn;
this->frameBuffers = frameBuffers;
return true; return true;
} }
static bool initVertexShader(void) static bool initVertexShader(void)
{ {
comRef_scopePush(10);
static const char * vshaderSrc = static const char * vshaderSrc =
"void main(\n" "void main(\n"
" in uint vertexID : SV_VERTEXID,\n" " in uint vertexID : SV_VERTEXID,\n"
@ -362,23 +372,16 @@ static bool initVertexShader(void)
} }
comRef_toGlobal(this->vshader, vshader); comRef_toGlobal(this->vshader, vshader);
comRef_scopePop();
return true; return true;
} }
static bool dxgi_init(unsigned * alignSize) static bool dxgi_init(void * ivshmemBase, unsigned * alignSize)
{ {
DEBUG_ASSERT(this); DEBUG_ASSERT(this);
if (!comRef_init( comRef_initGlobalScope(20 + this->maxTextures * 16, this->comScope);
20 + this->maxTextures * 16, //max total globals comRef_scopePush(20);
20 //max total locals
))
{
DEBUG_ERROR("failed to intialize the comRef tracking");
return false;
}
comRef_scopePush();
this->desktop = OpenInputDesktop(0, FALSE, GENERIC_READ); this->desktop = OpenInputDesktop(0, FALSE, GENERIC_READ);
if (!this->desktop) if (!this->desktop)
@ -411,9 +414,10 @@ static bool dxgi_init(unsigned * alignSize)
lgResetEvent(this->frameEvent); lgResetEvent(this->frameEvent);
comRef_defineLocal(IDXGIFactory1, factory);
status = CreateDXGIFactory2(this->debug ? DXGI_CREATE_FACTORY_DEBUG : 0, status = CreateDXGIFactory2(this->debug ? DXGI_CREATE_FACTORY_DEBUG : 0,
&IID_IDXGIFactory1, &IID_IDXGIFactory1,
(void **)comRef_newGlobal(&this->factory)); (void **)factory);
if (FAILED(status)) if (FAILED(status))
{ {
@ -429,7 +433,7 @@ static bool dxgi_init(unsigned * alignSize)
for ( for (
int i = 0; int i = 0;
IDXGIFactory1_EnumAdapters1(*this->factory, i, adapter) IDXGIFactory1_EnumAdapters1(*factory, i, adapter)
!= DXGI_ERROR_NOT_FOUND; != DXGI_ERROR_NOT_FOUND;
++i, comRef_release(adapter)) ++i, comRef_release(adapter))
{ {
@ -509,6 +513,7 @@ static bool dxgi_init(unsigned * alignSize)
goto fail; goto fail;
} }
comRef_toGlobal(this->factory, factory);
comRef_toGlobal(this->adapter, adapter); comRef_toGlobal(this->adapter, adapter);
comRef_toGlobal(this->output , output ); comRef_toGlobal(this->output , output );
@ -569,6 +574,8 @@ static bool dxgi_init(unsigned * alignSize)
goto fail; goto fail;
} }
comRef_defineLocal(ID3D11Device , device );
comRef_defineLocal(ID3D11DeviceContext, deviceContext);
status = D3D11CreateDevice( status = D3D11CreateDevice(
*tmp, *tmp,
D3D_DRIVER_TYPE_UNKNOWN, D3D_DRIVER_TYPE_UNKNOWN,
@ -577,11 +584,9 @@ static bool dxgi_init(unsigned * alignSize)
(this->debug ? D3D11_CREATE_DEVICE_DEBUG : 0), (this->debug ? D3D11_CREATE_DEVICE_DEBUG : 0),
featureLevels, featureLevelCount, featureLevels, featureLevelCount,
D3D11_SDK_VERSION, D3D11_SDK_VERSION,
(ID3D11Device **)comRef_newGlobal(&this->device), device,
&this->featureLevel, &this->featureLevel,
(ID3D11DeviceContext **)comRef_newGlobal(&this->deviceContext)); deviceContext);
LG_LOCK_INIT(this->deviceContextLock);
if (FAILED(status)) if (FAILED(status))
{ {
@ -589,6 +594,10 @@ static bool dxgi_init(unsigned * alignSize)
goto fail; goto fail;
} }
comRef_toGlobal(this->device , device );
comRef_toGlobal(this->deviceContext, deviceContext);
LG_LOCK_INIT(this->deviceContextLock);
switch(outputDesc.Rotation) switch(outputDesc.Rotation)
{ {
case DXGI_MODE_ROTATION_ROTATE90: case DXGI_MODE_ROTATION_ROTATE90:
@ -648,6 +657,8 @@ static bool dxgi_init(unsigned * alignSize)
IDXGIDevice1_SetMaximumFrameLatency(*dxgi, 1); IDXGIDevice1_SetMaximumFrameLatency(*dxgi, 1);
} }
comRef_defineLocal(IDXGIOutputDuplication, dup);
comRef_defineLocal(IDXGIOutput5, output5); comRef_defineLocal(IDXGIOutput5, output5);
status = IDXGIOutput_QueryInterface( status = IDXGIOutput_QueryInterface(
*this->output, &IID_IDXGIOutput5, (void **)output5); *this->output, &IID_IDXGIOutput5, (void **)output5);
@ -671,8 +682,7 @@ static bool dxgi_init(unsigned * alignSize)
for (int i = 0; i < 2; ++i) for (int i = 0; i < 2; ++i)
{ {
status = IDXGIOutput1_DuplicateOutput( status = IDXGIOutput1_DuplicateOutput(
*output1, (IUnknown *)*this->device, *output1, *(IUnknown **)this->device, dup);
(IDXGIOutputDuplication **)comRef_newGlobal(&this->dup));
if (SUCCEEDED(status)) if (SUCCEEDED(status))
break; break;
@ -703,7 +713,7 @@ static bool dxgi_init(unsigned * alignSize)
0, 0,
ARRAY_LENGTH(supportedFormats), ARRAY_LENGTH(supportedFormats),
supportedFormats, supportedFormats,
(IDXGIOutputDuplication **)comRef_newGlobal(&this->dup)); dup);
if (SUCCEEDED(status)) if (SUCCEEDED(status))
break; break;
@ -721,6 +731,8 @@ static bool dxgi_init(unsigned * alignSize)
goto fail; goto fail;
} }
comRef_toGlobal(this->dup, dup);
comRef_defineLocal(IDXGIOutput6, output6); comRef_defineLocal(IDXGIOutput6, output6);
status = IDXGIOutput_QueryInterface( status = IDXGIOutput_QueryInterface(
*this->output, &IID_IDXGIOutput6, (void **)output6); *this->output, &IID_IDXGIOutput6, (void **)output6);
@ -806,9 +818,9 @@ static bool dxgi_init(unsigned * alignSize)
if (!strcasecmp(copyBackend, backends[i]->code)) if (!strcasecmp(copyBackend, backends[i]->code))
{ {
if (!backends[i]->create( if (!backends[i]->create(
this->ivshmemBase, ivshmemBase,
alignSize, alignSize,
LGMP_Q_FRAME_LEN, this->frameBuffers,
this->maxTextures)) this->maxTextures))
{ {
DEBUG_ERROR("Failed to initialize selected capture backend: %s", backends[i]->name); DEBUG_ERROR("Failed to initialize selected capture backend: %s", backends[i]->name);
@ -914,7 +926,7 @@ static bool dxgi_deinit(void)
dxgi_releaseFrame(); dxgi_releaseFrame();
// this MUST run before backend->free() & ppFreeAll. // this MUST run before backend->free() & ppFreeAll.
comRef_free(); comRef_freeScope(&this->comScope);
ppFreeAll(); ppFreeAll();
if (this->backend) if (this->backend)
@ -1066,7 +1078,7 @@ static CaptureResult dxgi_capture(unsigned frameBufferIndex,
{ {
DEBUG_ASSERT(this); DEBUG_ASSERT(this);
DEBUG_ASSERT(this->initialized); DEBUG_ASSERT(this->initialized);
comRef_scopePush(); comRef_scopePush(10);
Texture * tex = NULL; Texture * tex = NULL;
CaptureResult result; CaptureResult result;
@ -1386,7 +1398,7 @@ static CaptureResult dxgi_capture(unsigned frameBufferIndex,
if (postPointer) if (postPointer)
{ {
pointer.visible = this->lastPointerVisible; pointer.visible = this->lastPointerVisible;
this->postPointerBufferFn(pointer); this->postPointerBufferFn(&pointer);
} }
result = CAPTURE_RESULT_OK; result = CAPTURE_RESULT_OK;
@ -1395,7 +1407,8 @@ exit:
return result; 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);
DEBUG_ASSERT(this->initialized); DEBUG_ASSERT(this->initialized);
@ -1444,17 +1457,18 @@ static CaptureResult dxgi_waitFrame(CaptureFrame * frame, const size_t maxFrameS
return CAPTURE_RESULT_OK; 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);
DEBUG_ASSERT(this->initialized); DEBUG_ASSERT(this->initialized);
Texture * tex = &this->texture[this->texRIndex]; Texture * tex = &this->texture[this->texRIndex];
FrameDamage * damage = &this->frameDamage[frameIndex]; FrameDamage * damage = &this->frameDamage[frameBufferIndex];
if (this->backend->writeFrame) if (this->backend->writeFrame)
{ {
CaptureResult result = this->backend->writeFrame(frameIndex, frame); CaptureResult result = this->backend->writeFrame(frameBufferIndex, frame);
if (result != CAPTURE_RESULT_OK) if (result != CAPTURE_RESULT_OK)
return result; return result;
} }
@ -1500,7 +1514,7 @@ static CaptureResult dxgi_getFrame(FrameBuffer * frame, int frameIndex)
for (int i = 0; i < LGMP_Q_FRAME_LEN; ++i) for (int i = 0; i < LGMP_Q_FRAME_LEN; ++i)
{ {
struct FrameDamage * damage = this->frameDamage + i; struct FrameDamage * damage = this->frameDamage + i;
if (i == frameIndex) if (i == frameBufferIndex)
damage->count = 0; damage->count = 0;
else if (tex->damageRectsCount > 0 && damage->count >= 0 && else if (tex->damageRectsCount > 0 && damage->count >= 0 &&
damage->count + tex->damageRectsCount <= KVMFR_MAX_DAMAGE_RECTS) damage->count + tex->damageRectsCount <= KVMFR_MAX_DAMAGE_RECTS)

View File

@ -31,6 +31,8 @@
typedef struct Downsample typedef struct Downsample
{ {
ComScope * comScope;
ID3D11Device ** device; ID3D11Device ** device;
ID3D11DeviceContext ** context; ID3D11DeviceContext ** context;
bool shareable; bool shareable;
@ -43,6 +45,9 @@ typedef struct Downsample
Downsample; Downsample;
static Downsample this = {0}; static Downsample this = {0};
#define comRef_toGlobal(dst, src) \
_comRef_toGlobal(this.comScope, dst, src)
typedef struct typedef struct
{ {
ID3D11Texture2D ** tex; ID3D11Texture2D ** tex;
@ -73,11 +78,14 @@ static bool downsample_setup(
this.device = device; this.device = device;
this.context = context; this.context = context;
this.shareable = shareable; this.shareable = shareable;
comRef_initGlobalScope(10, this.comScope);
return true; return true;
} }
static void downsample_finish(void) static void downsample_finish(void)
{ {
comRef_freeScope(&this.comScope);
memset(&this, 0, sizeof(this)); memset(&this, 0, sizeof(this));
} }
@ -96,7 +104,7 @@ static bool downsample_configure(void * opaque,
return true; return true;
HRESULT status; HRESULT status;
comRef_scopePush(); comRef_scopePush(10);
if (!this.pshader) if (!this.pshader)
{ {

View File

@ -29,6 +29,8 @@
typedef struct RGB24 typedef struct RGB24
{ {
ComScope * comScope;
ID3D11Device ** device; ID3D11Device ** device;
ID3D11DeviceContext ** context; ID3D11DeviceContext ** context;
bool shareable; bool shareable;
@ -40,6 +42,9 @@ typedef struct RGB24
RGB24; RGB24;
static RGB24 this = {0}; static RGB24 this = {0};
#define comRef_toGlobal(dst, src) \
_comRef_toGlobal(this.comScope, dst, src)
typedef struct typedef struct
{ {
ID3D11Texture2D ** tex; ID3D11Texture2D ** tex;
@ -55,6 +60,7 @@ static bool rgb24_setup(
bool shareable bool shareable
) )
{ {
comRef_initGlobalScope(10, this.comScope);
this.device = device; this.device = device;
this.context = context; this.context = context;
this.shareable = shareable; this.shareable = shareable;
@ -63,6 +69,7 @@ static bool rgb24_setup(
static void rgb24_finish(void) static void rgb24_finish(void)
{ {
comRef_freeScope(&this.comScope);
memset(&this, 0, sizeof(this)); memset(&this, 0, sizeof(this));
} }
@ -74,7 +81,8 @@ static bool rgb24_configure(void * opaque,
RGB24Inst * inst = (RGB24Inst *)opaque; RGB24Inst * inst = (RGB24Inst *)opaque;
HRESULT status; HRESULT status;
comRef_scopePush();
comRef_scopePush(10);
if (!this.pshader) if (!this.pshader)
{ {

View File

@ -29,6 +29,8 @@
typedef struct SDRWhiteLevel typedef struct SDRWhiteLevel
{ {
ComScope * comScope;
ID3D11Device ** device; ID3D11Device ** device;
ID3D11DeviceContext ** context; ID3D11DeviceContext ** context;
@ -43,6 +45,9 @@ typedef struct SDRWhiteLevel
SDRWhiteLevel; SDRWhiteLevel;
static SDRWhiteLevel this = {0}; static SDRWhiteLevel this = {0};
#define comRef_toGlobal(dst, src) \
_comRef_toGlobal(this.comScope, dst, src)
typedef struct typedef struct
{ {
ID3D11Texture2D ** tex; ID3D11Texture2D ** tex;
@ -66,13 +71,15 @@ static bool sdrWhiteLevel_setup(
) )
{ {
bool result = false; bool result = false;
comRef_scopePush();
HRESULT status; HRESULT status;
this.device = device; this.device = device;
this.context = context; this.context = context;
this.shareable = shareable; this.shareable = shareable;
comRef_initGlobalScope(10, this.comScope);
comRef_scopePush(10);
comRef_defineLocal(IDXGIOutput6, output6); comRef_defineLocal(IDXGIOutput6, output6);
status = IDXGIOutput_QueryInterface( status = IDXGIOutput_QueryInterface(
*output, &IID_IDXGIOutput6, (void **)output6); *output, &IID_IDXGIOutput6, (void **)output6);
@ -139,9 +146,8 @@ static bool sdrWhiteLevel_setup(
.MaxLOD = D3D11_FLOAT32_MAX .MaxLOD = D3D11_FLOAT32_MAX
}; };
status = ID3D11Device_CreateSamplerState( comRef_defineLocal(ID3D11SamplerState, sampler);
*this.device, &samplerDesc, status = ID3D11Device_CreateSamplerState(*this.device, &samplerDesc, sampler);
(ID3D11SamplerState **)comRef_newGlobal(&this.sampler));
if (FAILED(status)) if (FAILED(status))
{ {
@ -156,9 +162,10 @@ static bool sdrWhiteLevel_setup(
.BindFlags = D3D11_BIND_CONSTANT_BUFFER, .BindFlags = D3D11_BIND_CONSTANT_BUFFER,
}; };
comRef_defineLocal(ID3D11Buffer, buffer);
status = ID3D11Device_CreateBuffer( status = ID3D11Device_CreateBuffer(
*this.device, &bufferDesc, NULL, *this.device, &bufferDesc, NULL,
(ID3D11Buffer **)comRef_newGlobal(&this.buffer)); buffer);
if (FAILED(status)) if (FAILED(status))
{ {
@ -169,6 +176,8 @@ static bool sdrWhiteLevel_setup(
updateConsts(); updateConsts();
DEBUG_INFO("SDR White Level : %f" , this.sdrWhiteLevel); DEBUG_INFO("SDR White Level : %f" , this.sdrWhiteLevel);
comRef_toGlobal(this.sampler, sampler);
comRef_toGlobal(this.buffer , buffer );
result = true; result = true;
exit: exit:
@ -178,6 +187,7 @@ exit:
static void sdrWhiteLevel_finish(void) static void sdrWhiteLevel_finish(void)
{ {
comRef_freeScope(&this.comScope);
memset(&this, 0, sizeof(this)); memset(&this, 0, sizeof(this));
} }
@ -225,7 +235,7 @@ static bool sdrWhiteLevel_configure(void * opaque,
if (inst->tex) if (inst->tex)
return true; return true;
comRef_scopePush(); comRef_scopePush(10);
// create the output texture // create the output texture
D3D11_TEXTURE2D_DESC texDesc = D3D11_TEXTURE2D_DESC texDesc =

View File

@ -128,7 +128,7 @@ static void on_mouseMove(int x, int y)
.y = y - this->mouseHotY .y = y - this->mouseHotY
}; };
this->postPointerBufferFn(pointer); this->postPointerBufferFn(&pointer);
} }
static const char * nvfbc_getName(void) static const char * nvfbc_getName(void)
@ -185,9 +185,9 @@ static void nvfbc_initOptions(void)
} }
static bool nvfbc_create( static bool nvfbc_create(
void * ivshmemBase,
CaptureGetPointerBuffer getPointerBufferFn, CaptureGetPointerBuffer getPointerBufferFn,
CapturePostPointerBuffer postPointerBufferFn) CapturePostPointerBuffer postPointerBufferFn,
unsigned frameBuffers)
{ {
if (!NvFBCInit()) if (!NvFBCInit())
return false; return false;
@ -221,7 +221,7 @@ static void updateScale(void)
this->targetHeight = this->height; 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"); int adapterIndex = option_get_int("nvfbc", "adapterIndex");
@ -650,8 +650,8 @@ done:
frame->damageRectsCount = rectId; frame->damageRectsCount = rectId;
} }
static CaptureResult nvfbc_waitFrame(CaptureFrame * frame, static CaptureResult nvfbc_waitFrame(unsigned frameBufferIndex,
const size_t maxFrameSize) CaptureFrame * frame, const size_t maxFrameSize)
{ {
if (unlikely(this->stop)) if (unlikely(this->stop))
return CAPTURE_RESULT_REINIT; return CAPTURE_RESULT_REINIT;
@ -711,12 +711,13 @@ static CaptureResult nvfbc_waitFrame(CaptureFrame * frame,
return CAPTURE_RESULT_OK; 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 h = DIFF_MAP_DIM(this->grabHeight, this->diffShift);
const unsigned int w = DIFF_MAP_DIM(this->grabWidth, this->diffShift); const unsigned int w = DIFF_MAP_DIM(this->grabWidth, this->diffShift);
uint8_t * frameData = framebuffer_get_data(frame); 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) 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) 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].width = this->grabWidth;
this->frameInfo[i].height = this->grabHeight; this->frameInfo[i].height = this->grabHeight;
@ -855,7 +856,7 @@ static int pointerThread(void * unused)
pointer.x = this->mouseX - pointer.hx; pointer.x = this->mouseX - pointer.hx;
pointer.y = this->mouseY - pointer.hy; pointer.y = this->mouseY - pointer.hy;
this->postPointerBufferFn(pointer); this->postPointerBufferFn(&pointer);
} }
return 0; return 0;

View 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))

View 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;
}

View File

@ -78,6 +78,7 @@ struct app
int exitcode; int exitcode;
PLGMPHost lgmp; PLGMPHost lgmp;
void *ivshmemBase;
PLGMPHostQueue pointerQueue; PLGMPHostQueue pointerQueue;
PLGMPMemory pointerMemory[LGMP_Q_POINTER_LEN]; PLGMPMemory pointerMemory[LGMP_Q_POINTER_LEN];
@ -96,7 +97,8 @@ struct app
KVMFRFrame * frame [LGMP_Q_FRAME_LEN]; KVMFRFrame * frame [LGMP_Q_FRAME_LEN];
FrameBuffer * frameBuffer[LGMP_Q_FRAME_LEN]; FrameBuffer * frameBuffer[LGMP_Q_FRAME_LEN];
unsigned int frameIndex; unsigned int captureIndex;
unsigned int readIndex;
bool frameValid; bool frameValid;
uint32_t frameSerial; uint32_t frameSerial;
@ -181,7 +183,7 @@ static bool lgmpTimer(void * opaque)
return true; return true;
} }
static bool sendFrame(void) static bool sendFrame(CaptureResult result)
{ {
CaptureFrame frame = { 0 }; CaptureFrame frame = { 0 };
bool repeatFrame = false; bool repeatFrame = false;
@ -197,7 +199,11 @@ static bool sendFrame(void)
if (app.state != APP_STATE_RUNNING) if (app.state != APP_STATE_RUNNING)
return false; 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: case CAPTURE_RESULT_OK:
// reading the new subs count zeros it // reading the new subs count zeros it
@ -236,17 +242,12 @@ static bool sendFrame(void)
if (repeatFrame) if (repeatFrame)
{ {
if ((status = lgmpHostQueuePost(app.frameQueue, 0, if ((status = lgmpHostQueuePost(app.frameQueue, 0,
app.frameMemory[app.frameIndex])) != LGMP_OK) app.frameMemory[app.readIndex])) != LGMP_OK)
DEBUG_ERROR("%s", lgmpStatusString(status)); DEBUG_ERROR("%s", lgmpStatusString(status));
return true; return true;
} }
// we increment the index first so that if we need to repeat a frame KVMFRFrame * fi = app.frame[app.captureIndex];
// 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];
KVMFRFrameFlags flags = KVMFRFrameFlags flags =
(frame.hdr ? FRAME_FLAG_HDR : 0) | (frame.hdr ? FRAME_FLAG_HDR : 0) |
(frame.hdrPQ ? FRAME_FLAG_HDR_PQ : 0); (frame.hdrPQ ? FRAME_FLAG_HDR_PQ : 0);
@ -322,17 +323,24 @@ static bool sendFrame(void)
app.frameValid = true; 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! */ /* we post and then get the frame, this is intentional! */
if ((status = lgmpHostQueuePost(app.frameQueue, 0, if ((status = lgmpHostQueuePost(app.frameQueue, 0,
app.frameMemory[app.frameIndex])) != LGMP_OK) app.frameMemory[app.captureIndex])) != LGMP_OK)
{ {
DEBUG_ERROR("%s", lgmpStatusString(status)); DEBUG_ERROR("%s", lgmpStatusString(status));
return true; 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; return true;
} }
@ -342,7 +350,7 @@ static int frameThread(void * opaque)
while(app.state == APP_STATE_RUNNING) while(app.state == APP_STATE_RUNNING)
{ {
if (!sendFrame()) if (!sendFrame(CAPTURE_RESULT_OK))
break; break;
} }
DEBUG_INFO("Frame thread stopped"); DEBUG_INFO("Frame thread stopped");
@ -392,7 +400,7 @@ static bool captureStart(void)
{ {
if (app.state == APP_STATE_IDLE) 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"); DEBUG_ERROR("Failed to initialize the capture device");
return false; return false;
@ -419,6 +427,7 @@ static bool captureStop(void)
return false; return false;
} }
app.frameValid = false;
return true; return true;
} }
@ -527,17 +536,17 @@ static void sendPointer(bool newClient)
postPointer(flags, mem); postPointer(flags, mem);
} }
void capturePostPointerBuffer(CapturePointer pointer) void capturePostPointerBuffer(const CapturePointer * pointer)
{ {
LG_LOCK(app.pointerLock); LG_LOCK(app.pointerLock);
int x = app.pointerInfo.x; int x = app.pointerInfo.x;
int y = app.pointerInfo.y; 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 there was not a position update, restore the x & y */
if (!pointer.positionUpdate) if (!pointer->positionUpdate)
{ {
app.pointerInfo.x = x; app.pointerInfo.x = x;
app.pointerInfo.y = y; app.pointerInfo.y = y;
@ -829,6 +838,7 @@ int app_main(int argc, char * argv[])
DEBUG_ERROR("Failed to open the IVSHMEM device"); DEBUG_ERROR("Failed to open the IVSHMEM device");
return LG_HOST_EXIT_FATAL; return LG_HOST_EXIT_FATAL;
} }
app.ivshmemBase = shmDev.mem;
int exitcode = 0; int exitcode = 0;
DEBUG_INFO("IVSHMEM Size : %u MiB", shmDev.size / 1048576); 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()); DEBUG_INFO("Trying : %s", iface->getName());
if (!iface->create( if (!iface->create(
shmDev.mem,
captureGetPointerBuffer, captureGetPointerBuffer,
capturePostPointerBuffer)) capturePostPointerBuffer,
ARRAY_LENGTH(app.frameBuffer)))
{ {
iface = NULL; iface = NULL;
continue; continue;
} }
if (iface->init(&app.alignSize)) if (iface->init(app.ivshmemBase, &app.alignSize))
break; break;
iface->free(); iface->free();
@ -965,12 +975,8 @@ int app_main(int argc, char * argv[])
const uint64_t captureStart = microtime(); const uint64_t captureStart = microtime();
unsigned nextIndex = app.frameIndex + 1;
if (nextIndex == LGMP_Q_FRAME_LEN)
nextIndex = 0;
const CaptureResult result = app.iface->capture( const CaptureResult result = app.iface->capture(
nextIndex, app.frameBuffer[nextIndex]); app.captureIndex, app.frameBuffer[app.captureIndex]);
if (likely(result == CAPTURE_RESULT_OK)) if (likely(result == CAPTURE_RESULT_OK))
previousFrameTime = captureStart; previousFrameTime = captureStart;
@ -982,7 +988,7 @@ int app_main(int argc, char * argv[])
{ {
LGMP_STATUS status; LGMP_STATUS status;
if ((status = lgmpHostQueuePost(app.frameQueue, 0, if ((status = lgmpHostQueuePost(app.frameQueue, 0,
app.frameMemory[app.frameIndex])) != LGMP_OK) app.frameMemory[app.readIndex])) != LGMP_OK)
DEBUG_ERROR("%s", lgmpStatusString(status)); DEBUG_ERROR("%s", lgmpStatusString(status));
} }
} }
@ -1005,7 +1011,7 @@ int app_main(int argc, char * argv[])
} }
if (!app.iface->asyncCapture) if (!app.iface->asyncCapture)
sendFrame(); sendFrame(result);
} }
if (app.state != APP_STATE_SHUTDOWN) if (app.state != APP_STATE_SHUTDOWN)