From 1b2898b0b8dfad9da9e93b803bc16eeb0281bd9b Mon Sep 17 00:00:00 2001 From: Awawa <69086569+awawa-dev@users.noreply.github.com> Date: Wed, 30 Oct 2024 02:15:10 +0100 Subject: [PATCH] Initial multi-monitor support --- include/base/Grabber.h | 2 + include/grabber/windows/DX/DxGrabber.h | 8 +- include/image/Image.h | 2 + sources/base/Grabber.cpp | 28 ++++ sources/grabber/windows/DX/DxGrabber.cpp | 204 +++++++++++++++++++---- sources/image/Image.cpp | 20 +++ 6 files changed, 230 insertions(+), 34 deletions(-) diff --git a/include/base/Grabber.h b/include/base/Grabber.h index 0ab0369af..7b7b97c91 100644 --- a/include/base/Grabber.h +++ b/include/base/Grabber.h @@ -176,6 +176,8 @@ public slots: int getTargetSystemFrameDimension(int& targetSizeX, int& targetSizeY); + int getTargetSystemFrameDimension(int actualWidth, int actualHeight, int& targetSizeX, int& targetSizeY); + void processSystemFrameBGRA(uint8_t* source, int lineSize = 0, bool useLut = true); void processSystemFrameBGR(uint8_t* source, int lineSize = 0); diff --git a/include/grabber/windows/DX/DxGrabber.h b/include/grabber/windows/DX/DxGrabber.h index 8a0b277b1..ff3e6a5bc 100644 --- a/include/grabber/windows/DX/DxGrabber.h +++ b/include/grabber/windows/DX/DxGrabber.h @@ -39,6 +39,8 @@ template void SafeRelease(T** ppT) struct DisplayHandle { QString name; + int warningCounter = 6; + bool wideGamut = false; int actualDivide = -1, actualWidth = 0, actualHeight = 0; uint targetMonitorNits = 0; ID3D11Texture2D* d3dConvertTexture = nullptr; @@ -51,7 +53,7 @@ struct DisplayHandle ID3D11InputLayout* d3dVertexLayout = nullptr; IDXGIOutputDuplication* d3dDuplicate = nullptr; ID3D11Texture2D* d3dSourceTexture = nullptr; - DXGI_OUTDUPL_DESC surfaceProperties; + DXGI_OUTDUPL_DESC surfaceProperties{}; DisplayHandle() = default; DisplayHandle(const DisplayHandle&) = delete; @@ -107,6 +109,8 @@ public slots: void captureFrame(DisplayHandle& display); + int captureFrame(DisplayHandle& display, Image& image); + QString GetSharedLut(); void enumerateDevices(bool silent); @@ -127,8 +131,6 @@ public slots: QString _configurationPath; QTimer* _timer; QTimer* _retryTimer; - int _warningCounter; - bool _wideGamut; bool _multiMonitor; bool _dxRestartNow; diff --git a/include/image/Image.h b/include/image/Image.h index 78c1d45b9..f77dcca26 100644 --- a/include/image/Image.h +++ b/include/image/Image.h @@ -28,6 +28,8 @@ class Image void gradientVBox(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint8_t r, uint8_t g, uint8_t b); + void insertHorizontal(int x, Image& source); + unsigned width() const; unsigned height() const; diff --git a/sources/base/Grabber.cpp b/sources/base/Grabber.cpp index 4a0419757..75a55fae0 100644 --- a/sources/base/Grabber.cpp +++ b/sources/base/Grabber.cpp @@ -518,6 +518,34 @@ int Grabber::getTargetSystemFrameDimension(int& targetSizeX, int& targetSizeY) return divide; } +int Grabber::getTargetSystemFrameDimension(int actualWidth, int actualHeight, int& targetSizeX, int& targetSizeY) +{ + int startX = _cropLeft; + int startY = _cropTop; + int realSizeX = actualWidth - startX - _cropRight; + int realSizeY = actualHeight - startY - _cropBottom; + + if (realSizeX <= 16 || realSizeY <= 16) + { + realSizeX = actualWidth; + realSizeY = actualHeight; + } + + int checkWidth = realSizeX; + int divide = 1; + + while (checkWidth > _width) + { + divide++; + checkWidth = realSizeX / divide; + } + + targetSizeX = realSizeX / divide; + targetSizeY = realSizeY / divide; + + return divide; +} + void Grabber::processSystemFrameBGRA(uint8_t* source, int lineSize, bool useLut) { int targetSizeX, targetSizeY; diff --git a/sources/grabber/windows/DX/DxGrabber.cpp b/sources/grabber/windows/DX/DxGrabber.cpp index 1d795180b..ae05fefc4 100644 --- a/sources/grabber/windows/DX/DxGrabber.cpp +++ b/sources/grabber/windows/DX/DxGrabber.cpp @@ -56,17 +56,13 @@ #define CHECK(hr) SUCCEEDED(hr) #define CLEAR(x) memset(&(x), 0, sizeof(x)) -#define MAX_WARNINGS 6 DxGrabber::DxGrabber(const QString& device, const QString& configurationPath) : Grabber(configurationPath, "DX11_SYSTEM:" + device.left(14)) , _configurationPath(configurationPath) , _timer(new QTimer(this)) , _retryTimer(new QTimer(this)) - , _warningCounter(MAX_WARNINGS) - , _wideGamut(false) , _multiMonitor(false) - , _dxRestartNow(false) , _d3dDevice(nullptr) @@ -134,7 +130,6 @@ void DxGrabber::uninit() SafeRelease(&_d3dContext); SafeRelease(&_d3dDevice); - _warningCounter = MAX_WARNINGS; _initialized = false; if (_dxRestartNow) @@ -310,17 +305,18 @@ bool DxGrabber::initDirectX(QString selectedDeviceName) for (UINT i = 0; pFactory->EnumAdapters1(i, &pAdapter) != DXGI_ERROR_NOT_FOUND && !exitNow; i++) { DeviceProperties properties; - DXGI_ADAPTER_DESC1 pDesc; - QString multiName = MULTI_MONITOR + "|" + QString::fromWCharArray(pDesc.Description); + DXGI_ADAPTER_DESC1 pDesc{}; pAdapter->GetDesc1(&pDesc); + QString multiName = MULTI_MONITOR + "|" + QString::fromWCharArray(pDesc.Description); + _multiMonitor = (selectedDeviceName == multiName); IDXGIOutput* pOutput; for (UINT j = 0; pAdapter->EnumOutputs(j, &pOutput) != DXGI_ERROR_NOT_FOUND && (!exitNow || _multiMonitor); j++) { - DXGI_OUTPUT_DESC oDesc; + DXGI_OUTPUT_DESC oDesc{}; pOutput->GetDesc(&oDesc); QString currentName = (QString::fromWCharArray(oDesc.DeviceName) + "|" + QString::fromWCharArray(pDesc.Description)); @@ -369,26 +365,26 @@ bool DxGrabber::initDirectX(QString selectedDeviceName) HRESULT status = E_FAIL; DXGI_OUTPUT_DESC1 descGamut; + std::unique_ptr display = std::make_unique(); + display->name = currentName; + display->targetMonitorNits = _targetMonitorNits; + pOutput6->GetDesc1(&descGamut); - _wideGamut = descGamut.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020; + display->wideGamut = descGamut.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020; Info(_log, "Gamut: %s, min nits: %0.2f, max nits: %0.2f, max frame nits: %0.2f, white point: [%0.2f, %0.2f]", - (_wideGamut) ? "HDR" : "SDR", descGamut.MinLuminance, descGamut.MaxLuminance, descGamut.MaxFullFrameLuminance, + (display->wideGamut) ? "HDR" : "SDR", descGamut.MinLuminance, descGamut.MaxLuminance, descGamut.MaxFullFrameLuminance, descGamut.WhitePoint[0], descGamut.WhitePoint[1]); - if (_wideGamut && _targetMonitorNits == 0) + if (display->wideGamut && display->targetMonitorNits == 0) { - Warning(_log, "Target SDR brightness is set to %i nits. Disabling wide gamut.", _targetMonitorNits); - _wideGamut = false; + Warning(_log, "Target SDR brightness is set to %i nits. Disabling wide gamut.", display->targetMonitorNits); + display->wideGamut = false; } - std::unique_ptr display = std::make_unique(); - - display->name = currentName; - - if (_hardware && _wideGamut) + if (_hardware && display->wideGamut) { - Info(_log, "Using wide gamut for HDR. Target SDR brightness: %i nits", _targetMonitorNits); + Info(_log, "Using wide gamut for HDR. Target SDR brightness: %i nits", display->targetMonitorNits); std::vector wideFormat({ DXGI_FORMAT_R16G16B16A16_FLOAT, DXGI_FORMAT_R10G10B10A2_UNORM }); status = pOutput6->DuplicateOutput1(_d3dDevice, 0, static_cast(wideFormat.size()), wideFormat.data(), &display->d3dDuplicate); @@ -396,7 +392,7 @@ bool DxGrabber::initDirectX(QString selectedDeviceName) if (!CHECK(status)) { Warning(_log, "No support for DXGI_FORMAT_R16G16B16A16_FLOAT/DXGI_FORMAT_R10G10B10A2_UNORM. Fallback to BGRA"); - _wideGamut = false; + display->wideGamut = false; } } @@ -422,7 +418,7 @@ bool DxGrabber::initDirectX(QString selectedDeviceName) display->actualWidth = targetSizeX; display->actualHeight = targetSizeY; - if (!_wideGamut) + if (!display->wideGamut) { int maxSize = std::max((display->actualWidth - _cropLeft - _cropRight), (display->actualHeight - _cropTop - _cropBottom)); @@ -719,9 +715,9 @@ HRESULT DxGrabber::deepScaledCopy(DisplayHandle& display, ID3D11Texture2D* sourc if (!CHECK(status)) { - if (_warningCounter > 0) + if (display.warningCounter > 0) { - Error(_log, "CreateShaderResourceView failed (%i). Reason: %i", _warningCounter--, status); + Error(_log, "CreateShaderResourceView failed (%i). Reason: %i", display.warningCounter--, status); } return status; } @@ -738,7 +734,67 @@ HRESULT DxGrabber::deepScaledCopy(DisplayHandle& display, ID3D11Texture2D* sourc void DxGrabber::grabFrame() { - if (_handles.size() > 0) + if (_handles.size() == 0) + return; + + if (_multiMonitor) + { + int width = 0; + int height = 0; + bool useCache = false; + std::list>> images; + + for (auto&& display : _handles) + { + Image image; + auto result = captureFrame(*display, image); + + if (result < 0) + return; + else if (result == 0) + { + useCache = true; + } + else + { + images.push_back(std::pair>(width, image)); + } + + width += display->actualWidth; + height = std::max(display->actualHeight, height); + } + + if (useCache && (_cacheImage.width() != width || _cacheImage.height() != height)) + { + Warning(_log, "Invalid cached image size. Cached: %i x %i vs new: %i x %i", _cacheImage.width(), _cacheImage.height(), width, height); + return; + } + + Image image(width, height); + + if (useCache) + { + memcpy(image.rawMem(), _cacheImage.rawMem(), image.size()); + } + else + { + memset(image.rawMem(), 0, image.size()); + } + + for (auto&& source : images) + { + image.insertHorizontal(source.first, source.second); + } + + if (_signalDetectionEnabled) + { + if (checkSignalDetectionManual(image)) + emit SignalNewCapturedFrame(image); + } + else + emit SignalNewCapturedFrame(image); + } + else { captureFrame(*_handles.front()); } @@ -773,24 +829,24 @@ void DxGrabber::captureFrame(DisplayHandle& display) if (CHECK(status) && CHECK(_d3dContext->Map(display.d3dSourceTexture, 0, D3D11_MAP_READ, 0, &internalMap))) { - processSystemFrameBGRA((uint8_t*)internalMap.pData, (int)internalMap.RowPitch, !(_hardware && _wideGamut)); + processSystemFrameBGRA((uint8_t*)internalMap.pData, (int)internalMap.RowPitch, !(_hardware && display.wideGamut)); _d3dContext->Unmap(display.d3dSourceTexture, 0); } SafeRelease(&texDesktop); } - else if (_warningCounter > 0) + else if (display.warningCounter > 0) { Error(_log, "ResourceDesktop->QueryInterface failed. Reason: %i", status); - _warningCounter--; + display.warningCounter--; } } else if (status == DXGI_ERROR_WAIT_TIMEOUT) { - if (_warningCounter > 0) + if (display.warningCounter > 0) { Debug(_log, "AcquireNextFrame didn't return the frame. Just warning: the screen has not changed?"); - _warningCounter--; + display.warningCounter--; } if (_cacheImage.width() > 1) @@ -803,10 +859,10 @@ void DxGrabber::captureFrame(DisplayHandle& display) Error(_log, "Lost DirectX capture context. Stopping."); _dxRestartNow = true; } - else if (_warningCounter > 0) + else if (display.warningCounter > 0) { Error(_log, "AcquireNextFrame failed. Reason: %i", status); - _warningCounter--; + display.warningCounter--; } SafeRelease(&resourceDesktop); @@ -819,6 +875,92 @@ void DxGrabber::captureFrame(DisplayHandle& display) } } +int DxGrabber::captureFrame(DisplayHandle& display, Image& image) +{ + int result = -1; + DXGI_OUTDUPL_FRAME_INFO infoFrame{}; + IDXGIResource* resourceDesktop = nullptr; + + auto status = display.d3dDuplicate->AcquireNextFrame(0, &infoFrame, &resourceDesktop); + + if (CHECK(status)) + { + D3D11_MAPPED_SUBRESOURCE internalMap{}; + ID3D11Texture2D* texDesktop = nullptr; + + CLEAR(internalMap); + + status = resourceDesktop->QueryInterface(__uuidof(ID3D11Texture2D), (void**)&texDesktop); + + if (CHECK(status) && texDesktop != nullptr) + { + if (_hardware) + { + status = deepScaledCopy(display, texDesktop); + } + else + { + _d3dContext->CopyResource(display.d3dSourceTexture, texDesktop); + } + + if (CHECK(status) && CHECK(_d3dContext->Map(display.d3dSourceTexture, 0, D3D11_MAP_READ, 0, &internalMap))) + { + int lineSize = (int)internalMap.RowPitch; + bool useLut = !(_hardware && display.wideGamut); + int targetSizeX, targetSizeY; + int divide = getTargetSystemFrameDimension(display.actualWidth, display.actualHeight, targetSizeX, targetSizeY); + + image = Image(targetSizeX, targetSizeY); + FrameDecoder::processSystemImageBGRA(image, targetSizeX, targetSizeY, _cropLeft, _cropTop, (uint8_t*)internalMap.pData, display.actualWidth, display.actualHeight, divide, (_hdrToneMappingEnabled == 0 || !_lutBufferInit || !useLut) ? nullptr : _lut.data(), lineSize); + + result = 1; + _d3dContext->Unmap(display.d3dSourceTexture, 0); + } + + SafeRelease(&texDesktop); + } + else if (display.warningCounter > 0) + { + Error(_log, "ResourceDesktop->QueryInterface failed. Reason: %i", status); + display.warningCounter--; + } + } + else if (status == DXGI_ERROR_WAIT_TIMEOUT) + { + if (display.warningCounter > 0) + { + Debug(_log, "AcquireNextFrame didn't return the frame. Just warning: the screen has not changed?"); + display.warningCounter--; + } + + if (_cacheImage.width() > 1) + { + result = 0; + } + } + else if (status == DXGI_ERROR_ACCESS_LOST) + { + Error(_log, "Lost DirectX capture context. Stopping."); + _dxRestartNow = true; + } + else if (display.warningCounter > 0) + { + Error(_log, "AcquireNextFrame failed. Reason: %i", status); + display.warningCounter--; + } + + SafeRelease(&resourceDesktop); + + display.d3dDuplicate->ReleaseFrame(); + + if (_dxRestartNow) + { + uninit(); + } + + return result; +} + void DxGrabber::setCropping(unsigned cropLeft, unsigned cropRight, unsigned cropTop, unsigned cropBottom) { diff --git a/sources/image/Image.cpp b/sources/image/Image.cpp index ff590ff26..2982b6d68 100644 --- a/sources/image/Image.cpp +++ b/sources/image/Image.cpp @@ -27,6 +27,7 @@ #include +#include template Image::Image() : @@ -96,6 +97,25 @@ void Image::gradientVBox(uint16_t x1, uint16_t y1, uint16_t x2, uint _sharedData->gradientVBox(x1, y1, x2, y2, r, g, b); } + +template +void Image::insertHorizontal(int x, Image& source) +{ + int copyX = ((x + source.width()) > width()) ? width() - x : source.width(); + int copyY = std::min(source.height(), height()); + uint8_t* dest = rawMem() + sizeof(ColorSpace) * x; + uint8_t* src = source.rawMem(); + auto destRowSize = sizeof(ColorSpace) * width(); + auto srcRowSize = sizeof(ColorSpace) * source.width(); + + for (int y = 0; y < copyY; y++) + { + memcpy(dest, src, copyX * sizeof(ColorSpace)); + dest += destRowSize; + src += srcRowSize; + } +} + template unsigned Image::width() const {