/** * 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 */ #include "d12.h" #include "interface/capture.h" #include "common/array.h" #include "common/debug.h" #include "common/windebug.h" #include "common/option.h" #include "common/rects.h" #include "common/vector.h" #include "common/display.h" #include "com_ref.h" #include "backend.h" #include "effects.h" #include "command_group.h" #include #include #include #include // definitions struct D12Interface { HMODULE d3d12; IDXGIFactory2 ** factory; ID3D12Device3 ** device; DISPLAYCONFIG_PATH_INFO displayPathInfo; ID3D12CommandQueue ** copyQueue; ID3D12CommandQueue ** computeQueue; D12CommandGroup copyCommand; D12CommandGroup computeCommand; void * ivshmemBase; ID3D12Heap ** ivshmemHeap; CaptureGetPointerBuffer getPointerBufferFn; CapturePostPointerBuffer postPointerBufferFn; D12Backend * backend; Vector effects; bool effectsActive; // capture format tracking D12FrameFormat captureFormat; unsigned formatVer; unsigned pitch; // output format tracking D12FrameFormat dstFormat; // prior frame dirty rects RECT dirtyRects[D12_MAX_DIRTY_RECTS]; unsigned nbDirtyRects; // options bool debug; bool trackDamage; unsigned frameBufferCount; // 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]; }; // gloabls struct DX12 DX12 = {0}; ComScope * d12_comScope = NULL; // defines // locals static struct D12Interface * this = NULL; // forwards static bool d12_enumerateDevices( IDXGIFactory2 ** factory, IDXGIAdapter1 ** adapter, IDXGIOutput ** output); static bool d12_heapTest(ID3D12Device3 * device, ID3D12Heap * heap); static ID3D12Resource * d12_frameBufferToResource( unsigned frameBufferIndex, FrameBuffer * frameBuffer, unsigned size); // implementation static const char * d12_getName(void) { return "D12"; } static void d12_initOptions(void) { struct Option options[] = { { .module = "d12", .name = "adapter", .description = "The name of the adapter to capture", .type = OPTION_TYPE_STRING, .value.x_string = NULL }, { .module = "d12", .name = "output", .description = "The name of the adapter's output to capture", .type = OPTION_TYPE_STRING, .value.x_string = NULL }, { .module = "d12", .name = "trackDamage", .description = "Perform damage-aware copies (saves bandwidth)", .type = OPTION_TYPE_BOOL, .value.x_bool = true }, { .module = "d12", .name = "debug", .description = "Enable DirectX12 debugging and validation (SLOW!)", .type = OPTION_TYPE_BOOL, .value.x_bool = false }, {0} }; option_register(options); for(const D12Effect ** effect = D12Effects; *effect; ++effect) d12_effectInitOptions(*effect); } 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 = option_get_bool("d12", "debug" ); this->trackDamage = option_get_bool("d12", "trackDamage" ); DEBUG_INFO( "debug:%d trackDamage:%d", this->debug, this->trackDamage); this->d3d12 = LoadLibrary("d3d12.dll"); if (!this->d3d12) { DEBUG_ERROR("failed to load d3d12.dll"); free(this); return false; } DX12.D3D12CreateDevice = (typeof(DX12.D3D12CreateDevice)) GetProcAddress(this->d3d12, "D3D12CreateDevice"); DX12.D3D12GetDebugInterface = (typeof(DX12.D3D12GetDebugInterface)) GetProcAddress(this->d3d12, "D3D12GetDebugInterface"); DX12.D3D12SerializeVersionedRootSignature = (typeof(DX12.D3D12SerializeVersionedRootSignature)) GetProcAddress(this->d3d12, "D3D12SerializeVersionedRootSignature"); this->getPointerBufferFn = getPointerBufferFn; this->postPointerBufferFn = postPointerBufferFn; if (!d12_backendCreate(&D12Backend_DD, &this->backend, frameBuffers)) { DEBUG_ERROR("backend \"%s\" failed to create", this->backend->codeName); CloseHandle(this->d3d12); free(this); return false; } this->frameBufferCount = frameBuffers; return true; } static bool d12_init(void * ivshmemBase, unsigned * alignSize) { bool result = false; comRef_initGlobalScope(100, d12_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 = DX12.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); } // get the display path info comRef_defineLocal(IDXGIOutput6, output6); hr = IDXGIOutput_QueryInterface(*output, &IID_IDXGIOutput6, (void **)output6); if (FAILED(hr)) { DEBUG_WINERROR("Failed to obtain the IDXGIOutput6 interface", hr); goto exit; } DXGI_OUTPUT_DESC1 desc1; IDXGIOutput6_GetDesc1(*output6, &desc1); if (!display_getPathInfo(desc1.Monitor, &this->displayPathInfo)) { DEBUG_ERROR("Failed to get the display path info"); goto exit; } // create the D3D12 device comRef_defineLocal(ID3D12Device3, device); hr = DX12.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; } D3D12_COMMAND_QUEUE_DESC copyQueueDesc = { .Type = D3D12_COMMAND_LIST_TYPE_COPY, .Priority = D3D12_COMMAND_QUEUE_PRIORITY_HIGH, .Flags = D3D12_COMMAND_QUEUE_FLAG_NONE }; comRef_defineLocal(ID3D12CommandQueue, copyQueue); hr = ID3D12Device3_CreateCommandQueue( *device, ©QueueDesc, &IID_ID3D12CommandQueue, (void **)copyQueue); if (FAILED(hr)) { DEBUG_WINERROR("Failed to create ID3D12CommandQueue (copy)", hr); goto exit; } ID3D12CommandQueue_SetName(*copyQueue, L"Copy"); // create the compute queue D3D12_COMMAND_QUEUE_DESC computeQueueDesc = { .Type = D3D12_COMMAND_LIST_TYPE_COMPUTE, .Priority = D3D12_COMMAND_QUEUE_PRIORITY_HIGH, .Flags = D3D12_COMMAND_QUEUE_FLAG_NONE }; comRef_defineLocal(ID3D12CommandQueue, computeQueue); hr = ID3D12Device3_CreateCommandQueue( *device, &computeQueueDesc, &IID_ID3D12CommandQueue, (void **)computeQueue); if (FAILED(hr)) { DEBUG_WINERROR("Failed to create the ID3D12CommandQueue (compute)", hr); goto exit; } ID3D12CommandQueue_SetName(*computeQueue, L"Compute"); if (!d12_commandGroupCreate( *device, D3D12_COMMAND_LIST_TYPE_COPY, &this->copyCommand, L"Copy")) goto exit; if (!d12_commandGroupCreate( *device, D3D12_COMMAND_LIST_TYPE_COMPUTE, &this->computeCommand, L"Compute")) 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; /* Ensure we can create resources in the ivshmem heap * NOTE: It is safe to do this as the application has not yet setup the KVMFR * headers, so we can just attempt to create a resource at the start of the * heap without corrupting anything */ if (!d12_heapTest(*device, *ivshmemHeap)) { DEBUG_ERROR( "Unable to create resources in the IVSHMEM heap, is REBAR working?"); goto exit; } // initialize the backend if (!d12_backendInit(this->backend, this->debug, *device, *adapter, *output, this->trackDamage)) goto exit; // create the vector of effects vector_create(&this->effects, sizeof(D12Effect *), 0); // create all the effects for(const D12Effect ** effect = D12Effects; *effect; ++effect) { D12Effect * instance; switch(d12_effectCreate(*effect, &instance, *device, &this->displayPathInfo)) { case D12_EFFECT_STATUS_OK: DEBUG_INFO("D12 Created Effect: %s", (*effect)->name); vector_push(&this->effects, &instance); break; case D12_EFFECT_STATUS_BYPASS: continue; case D12_EFFECT_STATUS_ERROR: DEBUG_ERROR("Failed to create effect: %s", (*effect)->name); goto exit; } } comRef_toGlobal(this->factory , factory ); comRef_toGlobal(this->device , device ); comRef_toGlobal(this->copyQueue , copyQueue ); comRef_toGlobal(this->computeQueue, computeQueue ); comRef_toGlobal(this->ivshmemHeap , ivshmemHeap ); result = true; exit: comRef_scopePop(); if (!result) { D12Effect * effect; vector_forEach(effect, &this->effects) d12_effectFree(&effect); vector_destroy(&this->effects); comRef_freeScope(&d12_comScope); } return result; } static void d12_stop(void) { } static bool d12_deinit(void) { bool result = true; D12Effect * effect; vector_forEach(effect, &this->effects) d12_effectFree(&effect); vector_destroy(&this->effects); if (!d12_backendDeinit(this->backend)) result = false; d12_commandGroupFree(&this->copyCommand ); d12_commandGroupFree(&this->computeCommand); IDXGIFactory2 * factory = *this->factory; IDXGIFactory2_AddRef(factory); comRef_freeScope(&d12_comScope); if (IDXGIFactory2_Release(factory) != 0) DEBUG_WARN("MEMORY LEAK"); // zero the framebuffers memset(this->frameBuffers, 0, sizeof(*this->frameBuffers) * this->frameBufferCount); /* zero the formats so we properly reinit otherwise we wont detect the format change and setup the effect chain */ memset(&this->captureFormat, 0, sizeof(this->captureFormat)); memset(&this->dstFormat , 0, sizeof(this->dstFormat )); /* dirty rect history is no longer valid */ this->nbDirtyRects = 0; return result; } static void d12_free(void) { d12_backendFree(&this->backend); FreeLibrary(this->d3d12); free(this); this = NULL; } static CaptureResult d12_capture( unsigned frameBufferIndex, FrameBuffer * frameBuffer) { return d12_backendCapture(this->backend, frameBufferIndex); } static CaptureResult d12_waitFrame(unsigned frameBufferIndex, CaptureFrame * frame, const size_t maxFrameSize) { CaptureResult result = CAPTURE_RESULT_ERROR; comRef_scopePush(1); D12FrameDesc desc; comRef_defineLocal(ID3D12Resource, src); *src = d12_backendFetch(this->backend, frameBufferIndex, &desc); if (!*src) { DEBUG_ERROR("D12 backend failed to produce an expected frame: %u", frameBufferIndex); goto exit; } D12FrameFormat srcFormat = { .desc = ID3D12Resource_GetDesc(*src), .colorSpace = desc.colorSpace, .width = srcFormat.desc.Width, .height = srcFormat.desc.Height }; switch(srcFormat.desc.Format) { case DXGI_FORMAT_B8G8R8A8_UNORM: srcFormat.format = CAPTURE_FMT_BGRA; break; case DXGI_FORMAT_R8G8B8A8_UNORM: srcFormat.format = CAPTURE_FMT_RGBA; break; case DXGI_FORMAT_R10G10B10A2_UNORM: srcFormat.format = CAPTURE_FMT_RGBA10; break; case DXGI_FORMAT_R16G16B16A16_FLOAT: srcFormat.format = CAPTURE_FMT_RGBA16F; break; default: DEBUG_ERROR("Unsupported source format"); goto exit; } // if the input format changed, reconfigure the effects if (srcFormat.desc.Width == 0 || srcFormat.desc.Width != this->captureFormat.desc.Width || srcFormat.desc.Height != this->captureFormat.desc.Height || srcFormat.desc.Format != this->captureFormat.desc.Format || srcFormat.colorSpace != this->captureFormat.colorSpace) { D12FrameFormat dstFormat = this->dstFormat; this->captureFormat = srcFormat; this->effectsActive = false; D12Effect * effect; D12FrameFormat curFormat = srcFormat; vector_forEach(effect, &this->effects) { dstFormat = curFormat; switch(d12_effectSetFormat(effect, *this->device, &curFormat, &dstFormat)) { case D12_EFFECT_STATUS_OK: this->effectsActive = true; curFormat = dstFormat; effect->enabled = true; DEBUG_INFO("D12 Effect Active: %s", effect->name); break; case D12_EFFECT_STATUS_ERROR: DEBUG_ERROR("Failed to set the effect input format"); goto exit; case D12_EFFECT_STATUS_BYPASS: effect->enabled = false; break; } } // if the output format changed if (dstFormat.desc.Width != this->dstFormat.desc.Width || dstFormat.desc.Height != this->dstFormat.desc.Height || dstFormat.desc.Format != this->dstFormat.desc.Format || dstFormat.colorSpace != this->dstFormat.colorSpace || dstFormat.width != this->dstFormat.width || dstFormat.height != this->dstFormat.height || dstFormat.format != this->dstFormat.format) { ++this->formatVer; this->dstFormat = dstFormat; } } D3D12_PLACED_SUBRESOURCE_FOOTPRINT layout; ID3D12Device3_GetCopyableFootprints(*this->device, &this->dstFormat.desc, 0 , // FirstSubresource 1 , // NumSubresources 0 , // BaseOffset, &layout , // pLayouts NULL , // pNumRows, NULL , // pRowSizeInBytes, NULL); // pTotalBytes this->pitch = layout.Footprint.RowPitch; const unsigned maxRows = maxFrameSize / layout.Footprint.RowPitch; const unsigned bpp = this->dstFormat.format == CAPTURE_FMT_RGBA16F ? 8 : 4; frame->formatVer = this->formatVer; frame->screenWidth = srcFormat.width; frame->screenHeight = srcFormat.height; frame->dataWidth = this->dstFormat.desc.Width; frame->dataHeight = min(maxRows, this->dstFormat.desc.Height); frame->frameWidth = this->dstFormat.width; frame->frameHeight = this->dstFormat.height; frame->truncated = maxRows < this->dstFormat.desc.Height; frame->pitch = this->pitch; frame->stride = this->pitch / bpp; frame->format = this->dstFormat.format; frame->hdr = this->dstFormat.colorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020; frame->hdrPQ = false; frame->rotation = desc.rotation; D12Effect * effect; vector_forEach(effect, &this->effects) if (effect->enabled) d12_effectAdjustDamage(effect, desc.dirtyRects, &desc.nbDirtyRects); { // create a clean list of rects FrameDamageRect allRects[desc.nbDirtyRects]; unsigned count = 0; for(const RECT * rect = desc.dirtyRects; rect < desc.dirtyRects + desc.nbDirtyRects; ++rect) allRects[count++] = (FrameDamageRect){ .x = rect->left, .y = rect->top, .width = (rect->right - rect->left), .height = (rect->bottom - rect->top) }; count = rectsMergeOverlapping(allRects, count); // if there are too many rects if (unlikely(count > ARRAY_LENGTH(frame->damageRects))) frame->damageRectsCount = 0; else { // send the list of dirty rects for this frame frame->damageRectsCount = count; memcpy(frame->damageRects, allRects, sizeof(*allRects) * count); } } 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(3); D12FrameDesc desc; comRef_defineLocal(ID3D12Resource, src); *src = d12_backendFetch(this->backend, frameBufferIndex, &desc); 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; // place a fence into the queue result = d12_backendSync(this->backend, this->effectsActive ? *this->computeQueue : *this->copyQueue); if (result != CAPTURE_RESULT_OK) goto exit; ID3D12Resource * next = *src; D12Effect * effect; vector_forEach(effect, &this->effects) { if (!effect->enabled) continue; next = d12_effectRun(effect, *this->device, *this->computeCommand.gfxList, next, desc.dirtyRects, &desc.nbDirtyRects); } // copy into the framebuffer resource D3D12_TEXTURE_COPY_LOCATION srcLoc = { .pResource = next, .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 = this->dstFormat.desc.Format, .Width = this->dstFormat.desc.Width, .Height = this->dstFormat.desc.Height, .Depth = 1, .RowPitch = this->pitch } } }; // if full frame damage if (desc.nbDirtyRects == 0) { this->nbDirtyRects = 0; ID3D12GraphicsCommandList_CopyTextureRegion( *this->copyCommand.gfxList, &dstLoc, 0, 0, 0, &srcLoc, NULL); } else { /* if the prior frame was a full update */ if (this->nbDirtyRects == 0) { /* the prior frame was fully damaged, we must update everything */ ID3D12GraphicsCommandList_CopyTextureRegion( *this->copyCommand.gfxList, &dstLoc, 0, 0, 0, &srcLoc, NULL); } else { FrameDamageRect allRects[this->nbDirtyRects + desc.nbDirtyRects]; unsigned count = 0; /* we must update the rects that were dirty in the prior frame also, * otherwise the frame in memory will not be consistent when areas need to * be redrawn by the client, such as under the cursor */ for(const RECT * rect = this->dirtyRects; rect < this->dirtyRects + this->nbDirtyRects; ++rect) allRects[count++] = (FrameDamageRect){ .x = rect->left, .y = rect->top, .width = rect->right - rect->left, .height = rect->bottom - rect->top }; /* add the new dirtyRects to the array */ for(const RECT * rect = desc.dirtyRects; rect < desc.dirtyRects + desc.nbDirtyRects; ++rect) allRects[count++] = (FrameDamageRect){ .x = rect->left, .y = rect->top, .width = rect->right - rect->left, .height = rect->bottom - rect->top }; /* resolve the rects */ count = rectsMergeOverlapping(allRects, count); /* copy all the rects */ for(FrameDamageRect * rect = allRects; rect < allRects + count; ++rect) { D3D12_BOX box = { .left = rect->x, .top = rect->y, .front = 0, .back = 1, .right = rect->x + rect->width, .bottom = rect->y + rect->height }; ID3D12GraphicsCommandList_CopyTextureRegion( *this->copyCommand.gfxList, &dstLoc, box.left, box.top, 0, &srcLoc, &box); } } /* store the dirty rects for the next frame */ memcpy(this->dirtyRects, desc.dirtyRects, desc.nbDirtyRects * sizeof(*this->dirtyRects)); this->nbDirtyRects = desc.nbDirtyRects; } // execute the compute commands if (this->effectsActive) { d12_commandGroupExecute(*this->computeQueue, &this->computeCommand); // insert a fence to wait for the compute commands to finish ID3D12CommandQueue_Wait(*this->copyQueue, *this->computeCommand.fence, this->computeCommand.fenceValue); } // execute the copy commands d12_commandGroupExecute(*this->copyQueue, &this->copyCommand); // wait for the copy to complete d12_commandGroupWait(&this->copyCommand); // signal the frame is complete framebuffer_set_write_ptr(frameBuffer, this->dstFormat.desc.Height * this->pitch); // reset the command queues if (this->effectsActive && !d12_commandGroupReset(&this->computeCommand)) goto exit; if (!d12_commandGroupReset(&this->copyCommand)) goto exit; 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; const char * _optAdapter = option_get_string("d12", "adapter"); const char * _optOutput = option_get_string("d12", "output" ); wchar_t * optAdapter = NULL; wchar_t * optOutput = NULL; if (_optAdapter) { optAdapter = malloc((strlen(_optAdapter) + 1) * 2); mbstowcs(optAdapter, _optAdapter, strlen(_optAdapter)); } if (_optOutput) { optOutput = malloc((strlen(_optOutput) + 1) * 2); mbstowcs(optOutput, _optOutput, strlen(_optOutput)); } 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; if (optAdapter) { if (wcsstr(adapterDesc.Description, optAdapter) == NULL) { DEBUG_INFO("Not using adapter: %ls", adapterDesc.Description); continue; } DEBUG_INFO("Adapter matched, trying: %ls", adapterDesc.Description); } for( int n = 0; IDXGIAdapter1_EnumOutputs(*adapter, n, output) != DXGI_ERROR_NOT_FOUND; ++n, comRef_release(output)) { IDXGIOutput_GetDesc(*output, &outputDesc); if (optOutput) { if (wcsstr(outputDesc.DeviceName, optOutput) == NULL) { DEBUG_INFO("Not using adapter output: %ls", outputDesc.DeviceName); continue; } DEBUG_INFO("Adapter output matched, trying: %ls", outputDesc.DeviceName); } if (outputDesc.AttachedToDesktop) break; } if (*output) break; } free(optAdapter); free(optOutput ); 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_heapTest(ID3D12Device3 * device, ID3D12Heap * heap) { bool result = false; comRef_scopePush(1); D3D12_RESOURCE_DESC desc = { .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER, .Alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT, .Width = 1048576, .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( device, heap, 0, &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; } result = true; exit: comRef_scopePop(); return result; } 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 };